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(docs): add stonfi cookbook #956

Merged
merged 12 commits into from
Nov 28, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New CSpell dictionaries: TVM instructions and adjusted list of Fift words: PR [#881](https://github.com/tact-lang/tact/pull/881)
- Docs: the `description` property to the frontmatter of the each page for better SEO: PR [#916](https://github.com/tact-lang/tact/pull/916)
- Docs: Google Analytics tags per every page: PR [#921](https://github.com/tact-lang/tact/pull/921)
- Docs: Added Ston.fi cookbook: PR [#956](https://github.com/tact-lang/tact/pull/956)
- Docs: Added NFTs cookbook: PR [#958](https://github.com/tact-lang/tact/pull/958)
- Ability to specify a compile-time method ID expression for getters: PR [#922](https://github.com/tact-lang/tact/pull/922) and PR [#932](https://github.com/tact-lang/tact/pull/932)
- Destructuring of structs and messages: PR [#856](https://github.com/tact-lang/tact/pull/856)
Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"Stateinit",
"stdlib",
"stmts",
"Ston",
"struct",
"structs",
"subtyping",
Expand Down
2 changes: 2 additions & 0 deletions docs/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
"Seamus",
"Sedov",
"Stateinit",
"Ston",
"Stonfi",
novusnota marked this conversation as resolved.
Show resolved Hide resolved
"Sánchez",
"THROWIFNOT",
"TIMELOCK",
Expand Down
356 changes: 354 additions & 2 deletions docs/src/content/docs/cookbook/dexes/stonfi.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,362 @@ sidebar:
order: 2
---

# STON.fi

[STON.fi](https://ston.fi) is a decentralized automated market maker (AMM) built on [TON blockchain](https://ton.org) providing virtually zero fees, low slippage, an extremely easy interface, and direct integration with TON wallets.

:::danger[Not implemented]
:::caution

This api is about STON.fi version 2, which is under development. Use it at your own risk.

:::

## Recommended Reading

Before proceeding further, you may find it helpful to explore the following articles:

* [Receive messages](/book/receive/)
* [Sending messages](/book/send/)
* [Fungible Tokens (Jettons)](/cookbook/jettons/)

## Swaps

More about swaps [here](https://docs.ston.fi/docs/developer-section/api-reference-v2/example_swap).

Some variables like `offerAmount` are hardcoded for demonstration purposes. Don't forget to change them in real case scenarios.

Swaps have several common structures and messages. Don't forget to use them with the code examples below!

```tact
message(0x6664de2a) StonfiSwap {
otherTokenWallet: Address; // Address of the other Router token wallet
refundAddress: Address; // Address where refund will be sent if swap fails
excessesAddress: Address; // Address where TON excesses will be sent
deadline: Int as uint64; // Timestamp of execution deadline for this tx
additionalData: SwapAdditionalData; // Defined below
}

struct SwapAdditionalData {
minOut: Int as coins; // Minimum required amount of tokens to receive
receiver: Address; // Address where tokens will be sent after swap
fwdGas: Int as coins = 0; // Gas used to forward a message in transfer_notification after swap if custom_payload is present
customPayload: Cell? = null; // Payload sent in transfer_notification after swap
refundFwdGas: Int as coins = 0; // Gas used to forward a message in transfer_notification if swap fails if refund_payload is present
refundPayload: Cell? = null; // Payload sent in transfer_notification if swap fails
refFee: Int as uint16 = 10; // Referral fee. Max amount is 100 (1%)
referralAddress: Address? = null; // Referral address
}

message(0xf8a7ea5) JettonTransfer {
queryId: Int as uint64;
amount: Int as coins;
destination: Address;
responseDestination: Address?;
customPayload: Cell? = null;
forwardTonAmount: Int as coins;
forwardPayload: Cell?; // hack for now, equivalent to Slice as remaining with first bit set to 1
}
```

Below are shown gas constants. All of them are taken from the sdk [STON.fi sdk](https://github.com/ston-fi/sdk). They are slightly differ from reality, but it is ok for now.
novusnota marked this conversation as resolved.
Show resolved Hide resolved

```tact
const GasSwapJettonToJetton: Int = ton("0.3");
const GasSwapJettonToJettonFwd: Int = ton("0.24");

const GasSwapJettonToTon: Int = ton("0.3");
const GasSwapJettonToTonFwd: Int = ton("0.24");

const GasSwapTonToJettonFwd: Int = ton("0.3");
const GasSwapTonToJetton: Int = ton("0.01");
```

### Jetton to Jetton

```tact
const RouterAddress: Address = address("kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v"); // CPI Router v2.1.0
const RouterJettonWallet: Address = address("kQAtX3x2s-wMtYTz8CfmAyloHAB73vONzJM5S2idqXl-_5xK"); // Router Jetton Wallet Address

message SwapJettonJetton {
myJettonWalletAddress: Address;
}

contract JettonToJetton {
receive() {}
receive(msg: SwapJettonJetton) {
// This example directly uses the `myJettonWalletAddress` provided in the received message.
// In real-world scenarios, it's more reliable to calculate this address on-chain or save it during initialization to ensure accuracy and trustworthiness.
let myJettonWalletAddress: Address = msg.myJettonWalletAddress;

let offerAmount: Int = 100000; // the quantity of jettons being swapped

let forwardPayload = StonfiSwap{
novusnota marked this conversation as resolved.
Show resolved Hide resolved
otherTokenWallet: RouterJettonWallet,
refundAddress: myAddress(),
excessesAddress: myAddress(),
deadline: now() + 10000,
additionalData: SwapAdditionalData{
minOut: 1, // swap will fail if a receiver should receive less than minOut of tokens as a result
receiver: myAddress(),
}
};

send(SendParameters{
to: myJettonWalletAddress,
value: GasSwapJettonToJetton,
body: JettonTransfer{
queryId: 42,
amount: offerAmount,
destination: RouterAddress,
responseDestination: myAddress(),
forwardTonAmount: GasSwapJettonToJettonFwd,
forwardPayload: forwardPayload.toCell(),
}.toCell()
});
}
}
```

### Jetton to TON

Swapping jetton to TON is similar to jetton/jetton swap. The only one difference is that `RouterJettonWallet` address should be replaced with `RouterProxyTonWallet`.

```tact
const RouterAddress: Address = address("kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v"); // CPI Router v2.1.0
novusnota marked this conversation as resolved.
Show resolved Hide resolved
const RouterProxyTonWallet: Address = address("kQBbJjnahBMGbMUJwhAXLn8BiigcGXMJhSC0l7DBhdYABhG7"); // Router's pTON Address

message SwapJettonTon {
myJettonWalletAddress: Address;
}

contract JettonToTon {
receive() {}
receive(msg: SwapJettonTon) {
// This example directly uses the `myJettonWalletAddress` provided in the received message.
// In real-world scenarios, it's more reliable to calculate this address on-chain or save it during initialization to ensure accuracy and trustworthiness.
novusnota marked this conversation as resolved.
Show resolved Hide resolved
let myJettonWalletAddress: Address = msg.myJettonWalletAddress;

let offerAmount: Int = 100000; // the quantity of jettons being swapped

let forwardPayload = StonfiSwap{
otherTokenWallet: RouterProxyTonWallet,
refundAddress: myAddress(),
excessesAddress: myAddress(),
deadline: now() + 10000,
additionalData: SwapAdditionalData{
minOut: 1, // swap will fail if a receiver should receive less than minOut of tokens as a result
receiver: myAddress(),
}
};

send(SendParameters{
to: myJettonWalletAddress,
value: GasSwapJettonToTon,
body: JettonTransfer{
queryId: 42,
amount: offerAmount,
destination: RouterAddress,
responseDestination: myAddress(),
forwardTonAmount: GasSwapJettonToTonFwd,
forwardPayload: forwardPayload.toCell(),
}.toCell()
});
}
}
```

### TON to Jetton

TON to jetton swap introduces `ProxyTonTransfer` cause all interaction is done with pTON contract.

Jetton `pTON` is a specialized asset within the STON.fi ecosystem on the TON blockchain. It functions as an intermediary within the STON.fi ecosystem to manage and facilitate efficient TON transfers.
novusnota marked this conversation as resolved.
Show resolved Hide resolved

```tact
message(0x01f3835d) ProxyTonTransfer {
queryId: Int as uint64 = 0;
tonAmount: Int as coins;
refundAddress: Address;
forwardPayload: Cell?;
}

const RouterProxyTonWallet: Address = address("kQBbJjnahBMGbMUJwhAXLn8BiigcGXMJhSC0l7DBhdYABhG7"); // Router's pTON wallet Address
novusnota marked this conversation as resolved.
Show resolved Hide resolved
const RouterJettonWallet: Address = address("kQAtX3x2s-wMtYTz8CfmAyloHAB73vONzJM5S2idqXl-_5xK"); // Router's Jetton Wallet Address

contract TonToJetton {
receive() {}
receive("ton-jetton") {
novusnota marked this conversation as resolved.
Show resolved Hide resolved
let offerAmount: Int = 100000; // the quantity of TON being swapped

let forwardPayload = StonfiSwap{
otherTokenWallet: RouterJettonWallet,
refundAddress: myAddress(),
excessesAddress: myAddress(),
deadline: now() + 10000,
additionalData: SwapAdditionalData{
minOut: 1, // swap will fail if a receiver should receive less than minOut of tokens as a result
receiver: myAddress(),
}
};

send(SendParameters{
to: RouterProxyTonWallet,
value: GasSwapTonToJetton + GasSwapTonToJettonFwd + offerAmount,
body: ProxyTonTransfer{
queryId: 42,
tonAmount: offerAmount,
refundAddress: myAddress(),
forwardPayload: forwardPayload.toCell(),
}.toCell()
});
}
}
```

## Liquidity provision

More about liquidity provision [here](https://docs.ston.fi/docs/developer-section/api-reference-v2/example_lp_provide).

It is possible to deposit liquidity by sending just 1 type of token, the pool will automatically perform a swap and use the resulting amount to mint lp tokens. Non-single side provision is done just with `bothPositive` flag equals `true`.

Liquidity provisions have several common structures and messages. Don't forget to use them with the code examples below!

```tact
message(0x37c096df) ProvideLP {
otherTokenWallet: Address; // Address of the other Router token wallet
refundAddress: Address; // Address where refund will be sent if swap fails
excessesAddress: Address; // Address where TON excesses will be sent
deadline: Int as uint64; // Timestamp of execution deadline for this tx
additionalData: ProvideLPAdditionalData;
}

struct ProvideLPAdditionalData {
minLpOut: Int as coins; // Minimum required amount of lp tokens to receive
receiverAddress: Address; // Address where lp tokens will be sent
bothPositive: Bool = true; // Trigger liquidity deposit only if both token amounts are non-zero
fwdGas: Int as coins = 0; // Gas used to forward a message in transfer notification after mint if customPayload is present
customPayload: Cell? = null;
}
```

Below are shown gas constants. All of them are taken from the sdk [STON.fi sdk](https://github.com/ston-fi/sdk). They are slightly differ from reality, but it is ok for now.
novusnota marked this conversation as resolved.
Show resolved Hide resolved

```tact
const SingleSideProvideLpJettonGas: Int = ton("1");
const SingleSideProvideLpJettonGasFwd: Int = ton("0.8");
const SingleSideProvideLpTonGas: Int = ton("0.8");
const TonTransferGas: Int = ton("0.01");
```

### Jetton deposit


```tact
message(0xf8a7ea5) JettonTransfer {
queryId: Int as uint64;
amount: Int as coins;
destination: Address;
responseDestination: Address?;
customPayload: Cell? = null;
forwardTonAmount: Int as coins;
forwardPayload: Cell?; // hack for now, equivalent to Slice as remaining with first bit set to 1
novusnota marked this conversation as resolved.
Show resolved Hide resolved
}

message SampleProvideLP {
myJettonWalletAddress: Address;
}

const RouterAddress: Address = address("kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v"); // CPI Router v2.1.0
const RouterProxyTonWallet: Address = address("kQBbJjnahBMGbMUJwhAXLn8BiigcGXMJhSC0l7DBhdYABhG7"); // Router's pTON wallet Address
const RouterJettonWallet: Address = address("kQAtX3x2s-wMtYTz8CfmAyloHAB73vONzJM5S2idqXl-_5xK"); // Router's Jetton Wallet Address

contract Sample {
receive() {}
receive(msg: SampleProvideLP) {
// This example directly uses the `myJettonWalletAddress` provided in the received message.
// In real-world scenarios, it's more reliable to calculate this address on-chain or save it during initialization to ensure accuracy and trustworthiness.
let myJettonWalletAddress = msg.myJettonWalletAddress;

let offerAmount = 1000000; // the quantity of jettons being transferred for liquidity provisioning

let forwardPayload = ProvideLP{
otherTokenWallet: RouterProxyTonWallet,
refundAddress: myAddress(),
excessesAddress: myAddress(),
deadline: now() + 1000,
additionalData: ProvideLPAdditionalData{
minLpOut: 1,
receiverAddress: myAddress(),
bothPositive: false, // false means single side
}
};

send(SendParameters{
to: myJettonWalletAddress,
value: SingleSideProvideLpJettonGas,
body: JettonTransfer{
queryId: 42,
amount: offerAmount,
destination: RouterAddress,
responseDestination: myAddress(),
forwardTonAmount: SingleSideProvideLpJettonGasFwd,
forwardPayload: forwardPayload.toCell(),
}.toCell(),
});
}
}
```

### TON Deposit

```tact
message(0x01f3835d) ProxyTonTransfer {
queryId: Int as uint64 = 0;
tonAmount: Int as coins;
refundAddress: Address;
forwardPayload: Cell?;
}

const RouterProxyTonWallet: Address = address("kQBbJjnahBMGbMUJwhAXLn8BiigcGXMJhSC0l7DBhdYABhG7"); // Router's pTON wallet Address
const RouterJettonWallet: Address = address("kQAtX3x2s-wMtYTz8CfmAyloHAB73vONzJM5S2idqXl-_5xK"); // Router's Jetton Wallet Address

contract Sample {
receive() {}
receive("provide-lp-ton") {
let offerAmount = 1000000; // the quantity of TON being transferred for liquidity provisioning

let forwardPayload = ProvideLP{
otherTokenWallet: RouterJettonWallet,
refundAddress: myAddress(),
excessesAddress: myAddress(),
deadline: now() + 1000,
additionalData: ProvideLPAdditionalData{
minLpOut: 1,
receiverAddress: myAddress(),
bothPositive: false, // false means single side
}
};

send(SendParameters{
to: RouterProxyTonWallet,
value: SingleSideProvideLpTonGas + TonTransferGas + offerAmount,
body: ProxyTonTransfer{
queryId: 42,
tonAmount: offerAmount,
refundAddress: myAddress(),
forwardPayload: forwardPayload.toCell(),
}.toCell(),
});
}
}
```

### Withdraw liquidly

Liquidly is withdrawn from a pool by burning lp tokens, a user then receives both pool tokens based on the current exchange rate.
novusnota marked this conversation as resolved.
Show resolved Hide resolved
You can refer to examples of jetton burning [here](/cookbook/jettons#burning-jetton).
novusnota marked this conversation as resolved.
Show resolved Hide resolved

:::tip[Hey there!]

This page is a stub until it gets new content in [#149](https://github.com/tact-lang/tact-docs/issues/149).
Didn't find your favorite example of STON.fi interaction? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact/issues)

:::
Loading