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#swapstepstruct 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#swapparamsstruct 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;}
Swap Toncoin for any Jetton
/// https://docs.dedust.io/reference/tlb-schemes#message-swapmessage(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 nanoToncoinsfun 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;}
Swap a Jetton for another Jetton or Toncoin
/// https://docs.dedust.io/reference/tlb-schemes#message-swap-1message(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 snippetfun 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}
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-1message(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_liquiditymessage(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#assetstruct 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 issuesfun 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}
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 Toncoin — excess amount will be returned.