Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add DeDust examples #341

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
344 changes: 339 additions & 5 deletions pages/cookbook/dexes/dedust.mdx
Original file line number Diff line number Diff line change
@@ -1,13 +1,347 @@
# DeDust.io

import { Callout, Steps, Tabs } from 'nextra/components'

{/* See: https://nextra.site/docs/guide/built-ins */}
import { Callout } from 'nextra-theme-docs';

[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 a meticulous attention to user experience (UX), gas efficiency, and extensibility.

<Callout emoji="🚨">
## Swaps

You can read more about swaps on [DeDust documentation](https://docs.dedust.io/docs/swaps).

This page is a stub until it gets new content in [#146](https://github.com/tact-lang/tact-docs/issues/146).
<Callout type="warning" emoji="⚠️">

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

</Callout>

All kinds of swaps shares same structures:

```tact
struct SwapStep {
poolAddress: Address;
reserved: Cell? = null; // NULL always, reserved.
limit: Int as coins = 0;
nextStep: Cell?; // should be `SwapStep`, but recursive types not supported in tact
}

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

### Swapping native coin

```tact
message(0xea06185d) NativeSwap {
queryId: Int as uint64 = 0;
amount: Int as coins; // TON amount for the swap
poolAddress: Address;
reserved: Cell? = null; // NULL always, reserved.
limit: Int as coins = 0;
nextStep: SwapStep? = null;
swapParams: SwapParams;
}

// address of ton vault to send message
const TON_VAULT_ADDRESS: Address = address("EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_");

// address of pool to swap. Pool is pair like TON/USDT
const POOL_ADDRESS: Address = address("EQCY5ufIEA-wdv1L5Zw09OMDHywwHsqLKEAUBYtngwNnnal4");

const TON_SWAP_GAS_AMOUNT: Int = ton("0.2");

fun makeTonSwap() {
let swapParams = SwapParams{
deadline: 0,
recipientAddress: null,
referralAddress: null,
fulfillPayload: null,
rejectPayload: null,
};

// ton amount to swap in nanotons
let swapAmount = ...;

send(SendParameters{
to: TON_VAULT_ADDRESS,
value: swapAmount + TON_SWAP_GAS_AMOUNT,
body: NativeSwap{
queryId: 0,
amount: swapAmount,
poolAddress: POOL_ADDRESS,
reserved: null,
limit: 0,
nextStep: null,
swapParams,
}.toCell()
});
}
```


### Swapping jetton

```tact
message(0xf8a7ea5) TokenTransfer {
queryId: Int as uint64;
amount: Int as coins;
destination: Address;
responseDestination: Address;
customPayload: Cell?;
forwardTonAmount: Int as coins;
forwardPayload: Cell?;
}

message(0xe3a0d482) JettonSwapPayload {
poolAddress: Address;
reserved: Cell? = null; // NULL always, reserved.
limit: Int as coins = 0;
nextStep: SwapStep? = null;
swapParams: SwapParams;
}

// address of jetton master you want to swap
const JETTON_MASTER_ADDRESS: Address = address("kQCxBOJjV31OIAa0NN9JbaYUp6OXOahcFCipuYnsujx1MDAY");

// address of jetton vault to send message
const JETTON_VAULT_ADDRESS: Address = address("EQCgne1aW1CmkokSeRjMIREmJWbCKFbJxxkRCvtLDiheTTF9");

// address of pool to swap. Pool is pair like TON/USDT
const POOL_ADDRESS: Address = address("EQCY5ufIEA-wdv1L5Zw09OMDHywwHsqLKEAUBYtngwNnnal4");

const JETTON_SWAP_GAS_AMOUNT: Int = ton("0.3");
const JETTON_SWAP_FWD_AMOUNT: Int = ton("0.25");

fun makeJettonSwap() {
let swapParams = SwapParams{
deadline: 0,
recipientAddress: null,
referralAddress: null,
fulfillPayload: null,
rejectPayload: null,
};

let myJettonWalletAddress = ...; // calculate wallet address of jetton you want to swap
let swapAmount = ...; // jetton amount for swap

send(SendParameters{
to: myJettonWalletAddress,
value: JETTON_SWAP_GAS_AMOUNT,
body: TokenTransfer{
queryId: 0,
amount: swapAmount,
destination: JETTON_VAULT_ADDRESS,
responseDestination: myAddress(),
customPayload: null,
forwardTonAmount: JETTON_SWAP_FWD_AMOUNT,
forwardPayload: JettonSwapPayload{
poolAddress: POOL_ADDRESS,
reserved: null,
limit: 0,
nextStep: null,
swapParams,
}.toCell()
}.toCell()
});
}
```


## Liquidity Provisioning

To provide liquidity to a specific DeDust pool, you must supply both assets. After doing so, the pool issues LP tokens to the depositor's address.

You can read more about liquidity provisioning on [DeDust documentation](https://docs.dedust.io/docs/liquidity-provisioning).

```tact
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");

const JettonMaster: Address = address("kQDkRHlWaibL7Tww48T6xAUFevPflca7i8TIiQTacBmnDOrb");
//const JettonMasterRaw: RawAddress = JettonMaster.toRaw(); TODO: maybe there is constant evaluation in tact? Did not find anything
const JettonVault: Address = address("kQDcUuH4xhKejjilZAIeGuBh5JRWpzbVDcO9Qfh_Q_K4q9vk");
const TonVault: Address = address("EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_");

struct RawAddress {
workchain: Int as uint8;
hash: Int as uint256;
}

extends fun toRaw(self: Address): RawAddress {
let addressSlice: Slice = self.asSlice();

addressSlice.skipBits(3);
return RawAddress {
workchain: addressSlice.loadUint(8),
hash: addressSlice.loadUint(256),
}
}

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

struct Asset {
type: Int as uint4;
workchain: Int? as uint8 = null;
hash: Int? as uint256 = null;
}

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.hash!!, 256)
.endCell();
}

require(false, "Unknown asset type");
return beginCell().endCell();
}

message(0x40e108d6) JettonDepositLiquidity {
poolType: Int as uint1; // PoolType
asset0: Asset;
asset1: Asset;
minimalLpAmount: Int as coins = 0;
targetBalances0: Int as coins;
targetBalances1: Int as coins;
fulfillPayload: Cell? = null;
rejectPayload: Cell? = null;
}

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();
}

message(0xd55e4686) NativeDepositLiquidity {
queryId: Int as uint64;
amount: Int as coins;
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;
}

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();
}


message ProvideLp {
myJettonWalletAddress: Address; // calculated offchain for ease of example, in real world scenarios should be calculated onchain
}

contract Example {
receive() {}
receive(msg: ProvideLp) {
let jettonMasterRaw: RawAddress = JettonMaster.toRaw();

// 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,
hash: jettonMasterRaw.hash,
};

// Step 2. Deposit Jetton to Vault
let jettonDepositBody = JettonDepositLiquidity{
poolType: PoolTypeVolatile,
asset0,
asset1,
targetBalances0: tonAmount,
targetBalances1: jettonAmount,
}.build();

send(SendParameters{
to: msg.myJettonWalletAddress,
value: JettonProvideLpGas,
body: JettonTransfer{
queryId: 42,
amount: jettonAmount,
destination: JettonVault,
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();

send(SendParameters{
to: TonVault,
value: tonAmount + TonProvideLpGas,
body: nativeDepositBody,
});
}
}
```
Loading