Skip to content

DeDust.io

DeDust is a decentralized exchange (DEX) and automated market maker (AMM) built natively on TON Blockchain and DeDust Protocol 2.0. DeDust is designed with a meticulous attention to user experience (UX), gas efficiency, and extensibility.

Before going further, familiarize yourself with the following:

Swaps

Read more about swaps in the DeDust documentation.

All kinds of swaps use SwapStep and SwapParams structures:

/// https://docs.dedust.io/reference/tlb-schemes#swapstep
struct SwapStep {
// The pool that will do the swapping, i.e. pairs like TON/USDT or USDT/DUST
poolAddress: Address;
// A kind of swap to make, 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 for the swap to reject the swap coming to 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`, which makes the swap use the sender's address
recipientAddress: Address? = null;
// Referral address, required for the referral program of DeDust
// 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;
}
▶️ Open in Web IDE

Swap Toncoin for any Jetton

/// 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 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;
}
▶️ Open in Web IDE

Swap a Jetton for another Jetton or Toncoin

/// 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 whole swap
swapParams: SwapParams;
}
/// NOTE: To calculate and provide Jetton wallet address for the target user,
/// make sure to check 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 yours
// Address of the Jetton vault to the send message to
destination: address("EQAYqo4u7VF0fa4DPAebk4g9lBytj2VFny7pzXR0trjtXQaO"),
// Where to return the 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 whole 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
}
▶️ Open in Web IDE

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.

import "@stdlib/deploy";
/// https://docs.dedust.io/reference/tlb-schemes#message-deposit_liquidity-1
message(0x40e108d6) JettonDepositLiquidity {
// Pool type: 0 for volatile, 1 for stable
// Volatile pool is based on the "Constant Product" formula
// 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 there's less liquidity provided, the provisioning will be rejected
// Defaults to 0, makes 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 we don't mark messages 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 and then you must set non-null values for the following fields
type: Int as uint4;
workchain: Int as uint8 = 0; // Both this zeroes will be removed during .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 the .build() and not .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 the .build() and not .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 Structs 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
}
▶️ Open in Web IDE

Withdraw liquidity

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