---
title: DeDust.io
description: "This page lists examples of working with DeDust, a decentralized exchange and automated market maker built natively on TON Blockchain and DeDust Protocol."
sidebar:
  order: 1
---

[DeDust](https://dedust.io) is a decentralized exchange (DEX) and automated market maker (AMM) built natively on [TON Blockchain](https://ton.org) and [DeDust Protocol 2.0](https://docs.dedust.io/reference/tlb-schemes). DeDust is designed with meticulous attention to user experience (UX), gas efficiency, and extensibility.

Before going further, familiarize yourself with the following:

* [Receiving messages](/book/receive/)
* [Sending messages](/book/send/)
* [Fungible Tokens (Jettons)](/cookbook/jettons/)
* [DeDust Docs: Concepts](https://docs.dedust.io/docs/concepts)

## Swaps

Read more about swaps in the [DeDust documentation](https://docs.dedust.io/docs/swaps).

:::caution

  It is important to ensure that contracts are deployed. Sending funds to an inactive contract could result in irretrievable loss.

:::

All kinds of swaps use the `SwapStep{:tact}` and `SwapParams{:tact}` structures:

```tact
/// https://docs.dedust.io/reference/tlb-schemes#swapstep
struct SwapStep {
    // The pool that will perform the swap, e.g., pairs like TON/USDT or USDT/DUST
    poolAddress: Address;

    // The kind of swap to do: can only be 0 as of now
    kind: Int as uint1 = 0;

    // Minimum output of the swap
    // If the actual value is less than specified, the swap will be rejected
    limit: Int as coins = 0;

    // Reference to the next step, which can be used for multi-hop swaps
    // The type here is actually `SwapStep?`,
    // but specifying recursive types isn't allowed in Tact yet
    nextStep: Cell?;
}

/// https://docs.dedust.io/reference/tlb-schemes#swapparams
struct SwapParams {
    // Specifies a deadline to reject the swap if it arrives at the pool late
    // Accepts the number of seconds passed since the UNIX Epoch
    // Defaults to 0, which removes the deadline
    deadline: Int as uint32 = 0;

    // Specifies an address where funds will be sent after the swap
    // Defaults to `null`, causing the swap to use the sender's address
    recipientAddress: Address? = null;

    // Referral address required for the DeDust referral program
    // Defaults to `null`
    referralAddress: Address? = null;

    // Custom payload that will be attached to the fund transfer upon a successful swap
    // Defaults to `null`
    fulfillPayload: Cell? = null;

    // Custom payload that will be attached to the fund transfer upon a rejected swap
    // Defaults to `null`
    rejectPayload: Cell? = null;
}
```

### Swap Toncoin for any Jetton

:::note
The guides below use the [Jetton vault](https://docs.dedust.io/docs/concepts#vault). To obtain its address for your Jetton, refer to [this guide](https://docs.dedust.io/docs/swaps#step-1-find-the-vault-scale).
:::

```tact
/// https://docs.dedust.io/reference/tlb-schemes#message-swap
message(0xea06185d) NativeSwap {
    // Unique identifier used to trace transactions across multiple contracts
    // Defaults to 0, which means we don't mark messages to trace their chains
    queryId: Int as uint64 = 0;

    // Toncoin amount for the swap
    amount: Int as coins;

    // Inlined fields of SwapStep struct
    poolAddress: Address;
    kind: Int as uint1 = 0;
    limit: Int as coins = 0;
    nextStep: SwapStep? = null;

    // Set of parameters relevant for the whole swap
    swapParams: SwapParams;
}

// Let's say `swapAmount` is `ton("0.1")`, which is 10000000 nanoToncoins
fun swapToncoinForUSDT(swapAmount: Int) {
    send(SendParameters {
        // Address of the TON vault to send the message to
        to: address("EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_"),
        // Amount to swap plus a trade fee
        value: swapAmount + ton("0.2"),
        body: NativeSwap {
            amount: swapAmount,
            // Address of the swap pool, which is the TON/USDT pair in this case
            poolAddress: address("EQA-X_yo3fzzbDbJ_0bzFWKqtRuZFIRa1sJsveZJ1YpViO3r"),
            // Set of parameters relevant for the whole swap
            swapParams: SwapParams {}, // use defaults
        }.toCell(),
    });
}

//
// Helper structs described earlier on this page
//

struct SwapStep {
    poolAddress: Address;
    kind: Int as uint1 = 0;
    limit: Int as coins = 0;
    nextStep: Cell?;
}

struct SwapParams {
    deadline: Int as uint32 = 0;
    recipientAddress: Address? = null;
    referralAddress: Address? = null;
    fulfillPayload: Cell? = null;
    rejectPayload: Cell? = null;
}
```

### Swap a Jetton for another Jetton or Toncoin

```tact
/// https://docs.dedust.io/reference/tlb-schemes#message-swap-1
message(0xe3a0d482) JettonSwapPayload {
    // Inlined fields of SwapStep struct
    poolAddress: Address;
    kind: Int as uint1 = 0;
    limit: Int as coins = 0;
    nextStep: SwapStep? = null;

    // Set of parameters relevant for the entire swap
    swapParams: SwapParams;
}

/// NOTE: To calculate and provide the Jetton wallet address for the target user,
///       make sure to check the links after this code snippet.
fun swapJetton(targetJettonWalletAddress: Address) {
    send(SendParameters {
        to: targetJettonWalletAddress,
        value: ton("0.3"),
        body: JettonTransfer {
            // Unique identifier used to trace transactions across multiple contracts.
            // Set to 0, which means we don't mark messages to trace their chains.
            queryId: 0,
            // Jetton amount for the swap.
            amount: 10, // NOTE: change to your amount
            // Address of the Jetton vault to send the message to.
            destination: address("EQAYqo4u7VF0fa4DPAebk4g9lBytj2VFny7pzXR0trjtXQaO"),
            // Address to return any exceeding funds.
            responseDestination: myAddress(),
            forwardTonAmount: ton("0.25"),
            forwardPayload: JettonSwapPayload {
                // Address of the swap pool, which is the TON/USDT pair in this case.
                poolAddress: address("EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs"),
                // Set of parameters relevant for the entire swap.
                swapParams: SwapParams {}, // use defaults
            }.toCell(),
        }.toCell(),
    });
}

//
// Helper structs described earlier on this page
//

struct SwapStep {
    poolAddress: Address;
    kind: Int as uint1 = 0;
    limit: Int as coins = 0;
    nextStep: Cell?;
}

struct SwapParams {
    deadline: Int as uint32 = 0;
    recipientAddress: Address? = null;
    referralAddress: Address? = null;
    fulfillPayload: Cell? = null;
    rejectPayload: Cell? = null;
}

//
// Messages from the Jetton standard
//

message(0xf8a7ea5) JettonTransfer {
    queryId: Int as uint64;
    amount: Int as coins;
    destination: Address;
    responseDestination: Address;
    customPayload: Cell? = null;
    forwardTonAmount: Int as coins;
    forwardPayload: Cell?; // slightly adjusted
}
```

:::note[Useful links:]

  [Retrieving Jetton wallet address in TON Docs](https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user)\
  [How to calculate user's Jetton wallet address (offline)?](https://docs.ton.org/v3/guidelines/dapps/cookbook#how-to-calculate-users-jetton-wallet-address-offline)

:::

## Liquidity Provisioning

To provide liquidity to a particular DeDust pool, you must provide both assets. The pool will then issue special _LP tokens_ to the depositor's address.

Read more about liquidity provisioning in the [DeDust documentation](https://docs.dedust.io/docs/liquidity-provisioning).

```tact
/// https://docs.dedust.io/reference/tlb-schemes#message-deposit_liquidity-1
message(0x40e108d6) JettonDepositLiquidity {
    // Pool type: 0 for volatile, 1 for stable
    // A volatile pool is based on the "Constant Product" formula
    // A stable-swap pool is optimized for assets of near-equal value,
    // e.g., USDT/USDC, TON/stTON, etc.
    poolType: Int as uint1;

    // Provided assets
    asset0: Asset;
    asset1: Asset;

    // Minimal amount of LP tokens to be received
    // If less liquidity is provided, the provisioning will be rejected
    // Defaults to 0, making this value ignored
    minimalLpAmount: Int as coins = 0;

    // Target amount of the first asset
    targetBalances0: Int as coins;

    // Target amount of the second asset
    targetBalances1: Int as coins;

    // Custom payload attached to the transaction if the provisioning is successful
    // Defaults to `null`, which means no payload
    fulfillPayload: Cell? = null;

    // Custom payload attached to the transaction if the provisioning is rejected
    // Defaults to `null`, which means no payload
    rejectPayload: Cell? = null;
}

/// https://docs.dedust.io/reference/tlb-schemes#message-deposit_liquidity
message(0xd55e4686) NativeDepositLiquidity {
    // Unique identifier used to trace transactions across multiple contracts
    // Defaults to 0, which means messages are not marked to trace their chains
    queryId: Int as uint64 = 0;

    // Toncoin amount for the deposit
    amount: Int as coins;

    // Inlined fields of JettonDepositLiquidity message without the opcode prefix
    poolType: Int as uint1;
    asset0: Asset;
    asset1: Asset;
    minimalLpAmount: Int as coins = 0;
    targetBalances0: Int as coins;
    targetBalances1: Int as coins;
    fulfillPayload: Cell? = null;
    rejectPayload: Cell? = null;
}

/// https://docs.dedust.io/reference/tlb-schemes#asset
struct Asset {
    // Specify 0 for native (TON) and omit all following fields
    // Specify 1 for Jetton, then you must set non-null values for the following fields
    type: Int as uint4;

    workchain: Int as uint8 = 0; // Both these zeros will be removed during the .build() function. Only type will remain.
    address: Int as uint256 = 0;
}

const PoolTypeVolatile: Int = 0;
const PoolTypeStable: Int = 1;

const AssetTypeNative: Int = 0b0000;
const AssetTypeJetton: Int = 0b0001;

const JettonProvideLpGas: Int = ton("0.5");
const JettonProvideLpGasFwd: Int = ton("0.4");
const TonProvideLpGas: Int = ton("0.15");

// This example directly uses the provided `myJettonWalletAddress`.
// In real-world scenarios, it's more reliable to calculate this address on-chain or save it during initialization to prevent any issues.
fun provideLiquidity(myJettonWalletAddress: Address) {
    let jettonMasterRaw = parseStdAddress(
        address("EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs").asSlice(),
    );

    // Step 1. Prepare input
    let jettonAmount = ton("1");
    let tonAmount = ton("1");

    let asset0 = Asset {
        type: AssetTypeNative,
    };
    let asset1 = Asset {
        type: AssetTypeJetton,
        workchain: jettonMasterRaw.workchain,
        address: jettonMasterRaw.address,
    };

    // Step 2. Deposit Jetton to Vault
    let jettonDepositBody = JettonDepositLiquidity {
        poolType: PoolTypeVolatile,
        asset0,
        asset1,
        targetBalances0: tonAmount,
        targetBalances1: jettonAmount,
    }.build();
    // Notice .build() instead of .toCell(),
    // since we want some custom serialization logic.

    send(SendParameters {
        to: myJettonWalletAddress,
        value: JettonProvideLpGas,
        body: JettonTransfer {
            queryId: 42,
            amount: jettonAmount,
            // Jetton Vault
            destination: address("EQAYqo4u7VF0fa4DPAebk4g9lBytj2VFny7pzXR0trjtXQaO"),
            responseDestination: myAddress(),
            forwardTonAmount: JettonProvideLpGasFwd,
            forwardPayload: jettonDepositBody,
        }.toCell(),
    });

    // Step 3. Deposit TON to Vault
    let nativeDepositBody = NativeDepositLiquidity {
        queryId: 42,
        amount: tonAmount,
        poolType: PoolTypeVolatile,
        asset0,
        asset1,
        targetBalances0: tonAmount,
        targetBalances1: jettonAmount,
    }.build();
    // Notice .build() instead of .toCell(),
    // since we want some custom serialization logic.

    send(SendParameters {
        to: address("EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_"),
        value: tonAmount + TonProvideLpGas,
        body: nativeDepositBody,
    });
}

//
// Helper extension functions to build respective structures and messages
//

extends fun build(self: Asset): Cell {
    let assetBuilder = beginCell().storeUint(self.type, 4);

    if (self.type == AssetTypeNative) {
        return assetBuilder.endCell();
    }

    if (self.type == AssetTypeJetton) {
        return assetBuilder
            .storeUint(self.workchain, 8)
            .storeUint(self.address, 256)
            .endCell();
    }

    // Unknown asset type
    return beginCell().endCell();
}

extends fun build(self: JettonDepositLiquidity): Cell {
    return beginCell()
        .storeUint(0x40e108d6, 32)
        .storeUint(self.poolType, 1)
        .storeSlice(self.asset0.build().asSlice())
        .storeSlice(self.asset1.build().asSlice())
        .storeCoins(self.minimalLpAmount)
        .storeCoins(self.targetBalances0)
        .storeCoins(self.targetBalances1)
        .storeMaybeRef(self.fulfillPayload)
        .storeMaybeRef(self.rejectPayload)
        .endCell();
}

extends fun build(self: NativeDepositLiquidity): Cell {
    return beginCell()
        .storeUint(0xd55e4686, 32)
        .storeUint(self.queryId, 64)
        .storeCoins(self.amount)
        .storeUint(self.poolType, 1)
        .storeSlice(self.asset0.build().asSlice())
        .storeSlice(self.asset1.build().asSlice())
        .storeRef(
            beginCell()
                .storeCoins(self.minimalLpAmount)
                .storeCoins(self.targetBalances0)
                .storeCoins(self.targetBalances1)
                .endCell(),
        )
        .storeMaybeRef(self.fulfillPayload)
        .storeMaybeRef(self.rejectPayload)
        .endCell();
}

//
// Messages from the Jetton standard
//

message(0xf8a7ea5) JettonTransfer {
    queryId: Int as uint64;
    amount: Int as coins;
    destination: Address;
    responseDestination: Address?;
    customPayload: Cell? = null;
    forwardTonAmount: Int as coins;
    forwardPayload: Cell?; // Slightly adjusted
}
```

### Withdraw liquidity

To withdraw liquidity, burning LP tokens is required. You can refer to examples of Jetton burning in the [respective section of the Jettons Cookbook page](/cookbook/jettons#burning-jetton). However, more Toncoin should be added than for a normal burn, since adding too few may result in LP tokens being burned but no liquidity (or only partial liquidity) being sent from the pool. Therefore, consider attaching at least $0.5$ Toncoin — the excess amount will be returned.

:::tip[Hey there!]

Didn't find your favorite example of DeDust interaction? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact/issues)

:::
