Skip to content

Commit 414a6f4

Browse files
committed
feat(docs): swap USDT0 to FXRP using Uniswap V3 guide
1 parent 2c168f5 commit 414a6f4

File tree

4 files changed

+528
-0
lines changed

4 files changed

+528
-0
lines changed

docs/fassets/developer-guides.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ Guides for [redeeming FAssets](/fassets/redemption) back to underlying assets.
9696
]}
9797
/>
9898

99+
## FXRP Token Interactions
100+
101+
Guides for interacting with FXRP token.
102+
103+
<DocCardList
104+
items={[
105+
{
106+
type: "link",
107+
label: "Swap USDT0 to FXRP",
108+
href: "/fassets/developer-guides/usdt0-fxrp-swap",
109+
docId: "fassets/developer-guides/usdt0-fxrp-swap",
110+
},
111+
]}
112+
/>
113+
99114
## Agent Information
100115

101116
Guides for reading and working with agent data.
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
---
2+
title: Swap USDT0 to FXRP
3+
tags: [intermediate, fassets]
4+
slug: usdt0-fxrp-swap
5+
description: Swap USDT0 to FXRP tokens using Uniswap V3 router
6+
keywords: [fassets, flare-network]
7+
---
8+
9+
import CodeBlock from "@theme/CodeBlock";
10+
import UniswapV3Wrapper from "!!raw-loader!/examples/developer-hub-solidity/UniswapV3Wrapper.sol";
11+
import UniswapV3WrapperScript from "!!raw-loader!/examples/developer-hub-javascript/uniswapV3Wrapper.ts";
12+
import Remix from "@site/src/components/remix";
13+
14+
## Overview
15+
16+
In this guide, you will learn how to swap USDT0 to FXRP tokens using the Uniswap V3 router (SparkDEX).
17+
FXRP is the ERC-20 representation of XRP used by [FAssets](/fassets/overview).
18+
19+
## Prerequisites
20+
21+
Before starting, make sure you have:
22+
23+
- Basic understanding of the [FAssets system](/fassets/overview).
24+
- [Flare Network Periphery Contracts](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) package.
25+
- Familiarity with [Uniswap V3 swaps](https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps).
26+
- Addresses on Flare Mainnet:
27+
- V3 SwapRouter (SparkDEX): [0x8a1E35F5c98C4E85B36B7B253222eE17773b2781](https://flarescan.com/address/0x8a1E35F5c98C4E85B36B7B253222eE17773b2781/contract/14/code).
28+
- USDT0: [0xe7cd86e13AC4309349F30B3435a9d337750fC82D](https://flarescan.com/address/0xe7cd86e13AC4309349F30B3435a9d337750fC82D).
29+
- FXRP: [resolved dynamically from the Asset Manager](/fassets/developer-guides/fassets-fxrp-address).
30+
31+
## Solidity Uniswap V3 Wrapper Contract
32+
33+
This Solidity contract wraps the Uniswap V3 router, providing safe token transfers, pool checks, and event logging for debugging.
34+
35+
<CodeBlock language="solidity" title="contracts/fassets/UniswapV3Wrapper.sol">
36+
{UniswapV3Wrapper}
37+
</CodeBlock>
38+
39+
{/* prettier-ignore */}
40+
<Remix fileName="UniswapV3Wrapper.sol">Open in Remix</Remix>
41+
<br></br>
42+
43+
### Code Explanation
44+
45+
1. Defines the Uniswap V3 router function for swaps, which allows interaction with the Uniswap V3 protocol.
46+
47+
2. `exactInputSingle` function used for direct swaps between two tokens in a single pool.
48+
49+
3. Uniswap V3 factory interface that returns the factory address responsible for creating pools.
50+
51+
4. Uniswap V3 factory interface, used to locate a specific pool for a given token pair and fee tier.
52+
53+
5. The Uniswap V3 pool interface provides liquidity and token details for a given pool.
54+
55+
6. `UniswapV3Wrapper` contract, which acts as a wrapper around Uniswap V3 to simplify swaps and add safety checks.
56+
57+
7. Existing Uniswap V3 SwapRouter on Flare (SparkDEX), used as the main entry point for executing swaps.
58+
59+
8. The Uniswap V3 factory is stored in the contract for looking up pools.
60+
61+
9. Events, which log important actions such as swaps executed, pools checked, and tokens approved.
62+
63+
10. Constructor that initializes the swap router and factory, ensuring both references are immutable.
64+
65+
11. Check if the pool exists and has liquidity, preventing execution of swaps in empty or non-existent pools.
66+
67+
12. Swap exact input single function, which executes a single-hop swap through the Uniswap V3 router protocol.
68+
69+
12.1. Check if pool exists, ensuring a valid pool address is returned.
70+
71+
12.2. Check if the pool has liquidity, requiring that the available liquidity is greater than zero.
72+
73+
12.3. Transfer tokens from user to this contract, using SafeERC20 for secure transfers.
74+
75+
12.4. Approve router to spend tokens using the `SafeERC20` library, allowing the router to perform the swap.
76+
77+
12.5. Prepare swap parameters, filling the `ExactInputSingleParams` struct with all necessary details.
78+
79+
12.6. Execute swap, calling `swapRouter.exactInputSingle` function to perform the transaction.
80+
81+
12.7. Emit swap executed event, logging details about the user, tokens, and amounts.
82+
83+
## Execute the Swap
84+
85+
To execute a swap, you need to deploy the wrapper contract and call `swapExactInputSingle` with the swap parameters.
86+
87+
<CodeBlock
88+
language="typescript"
89+
title="scripts/fassets/swapExactInputSingle.ts"
90+
>
91+
{UniswapV3WrapperScript}
92+
</CodeBlock>
93+
94+
### Code Breakdown
95+
96+
1. Contract artifacts are imported, including `IAssetManager`, `UniswapV3Wrapper`, and `ERC20`, which provide access to the deployed contracts.
97+
98+
2. Flare Uniswap V3 addresses (SparkDEX) are defined, specifying the SwapRouter address used for executing swaps.
99+
100+
3. USDT0 token address on Flare Mainnet, which represents the stablecoin used as the input asset for the swap.
101+
102+
4. Pool fee tier is set to 500 (0.05%), which defines the fee level of the pool to use on the Uniswap V3 router (SparkDEX).
103+
104+
5. Swap parameters are defined.
105+
106+
6. Define the function `deployAndVerifyContract()` that deploys the UniswapV3Wrapper contract and verifies it on the blockchain explorer.
107+
108+
7. The `main()` function is the entry point to execute the complete swap logic.
109+
110+
8. Deploy and verify the `UniswapV3Wrapper` smart contract, retrieving its deployed address for later use.
111+
112+
9. Get the deployer account using `web3.eth.getAccounts()`.
113+
114+
10. Retrieve the FXRP token address dynamically from the Asset Manager contract.
115+
116+
11. Define USDT0 and FXRP contract instances.
117+
118+
12. Check the deployer account's initial balances of USDT0 and FXRP.
119+
120+
13. Verify that there is enough USDT0 to perform the swap.
121+
122+
14. Check the Uniswap V3 pool using the wrapper contract to ensure that the USDT0/FXRP pool exists and has liquidity.
123+
124+
15. Approve the USDT0 token to the wrapper contract, allowing it to spend the tokens on behalf of the deployer.
125+
126+
16. Execute the swap using the UniswapV3Wrapper contract's `swapExactInputSingle` method.
127+
128+
17. Extract the swap result by checking the new FXRP balance and calculating the difference from the initial balance.
129+
130+
18. Check final balances after the swap to verify execution.
131+
132+
### Run the Script
133+
134+
To run the script, you need to execute the following command:
135+
136+
```bash
137+
yarn hardhat run scripts/fassets/uniswapV3Wrapper.ts --network flare
138+
```
139+
140+
:::info
141+
This script is included in the [Flare Hardhat Starter Kit](/network/guides/hardhat-foundry-starter-kit).
142+
:::
143+
144+
## Summary
145+
146+
In this guide, you learned how to swap USDT0 to FXRP using the Uniswap V3 router (SparkDEX).
147+
148+
:::tip[What's next]
149+
150+
To continue your FAssets development journey, you can:
151+
152+
- Learn how to [mint FAssets using the executor](/fassets/developer-guides/fassets-mint-executor).
153+
- Understand how to [redeem FXRP](/fassets/developer-guides/fassets-redeem).
154+
155+
:::
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { ethers, run } from "hardhat";
2+
import { web3 } from "hardhat";
3+
4+
import { UniswapV3WrapperInstance } from "../../typechain-types";
5+
import { getAssetManagerFXRP } from "../utils/getters";
6+
import { ERC20Instance } from "../../typechain-types/@openzeppelin/contracts/token/ERC20/ERC20";
7+
8+
// yarn hardhat run scripts/fassets/uniswapV3Wrapper.ts --network flare
9+
10+
// 1. Contract artifacts
11+
const IAssetManager = artifacts.require("IAssetManager");
12+
const UniswapV3Wrapper = artifacts.require("UniswapV3Wrapper");
13+
const ERC20 = artifacts.require("ERC20");
14+
15+
// 2. Flare Uniswap V3 addresses (SparkDEX)
16+
// https://docs.sparkdex.ai/additional-information/smart-contract-overview/v2-v3.1-dex
17+
const SWAP_ROUTER = "0x8a1E35F5c98C4E85B36B7B253222eE17773b2781";
18+
19+
// 3. USDT0 token addresses on Flare
20+
// https://docs.usdt0.to/technical-documentation/developer#flare-eid-30295
21+
const USDT0 = "0xe7cd86e13AC4309349F30B3435a9d337750fC82D";
22+
23+
// 4. Pool fee tier
24+
const FEE = 500; // 0.05%
25+
26+
// 5. Swap parameters
27+
const AMOUNT_IN = ethers.parseUnits("1.0", 6); // 1 USDT0 (6 decimals)
28+
const AMOUNT_OUT_MIN = ethers.parseUnits("0.3", 6); // 0.3 FXRP minimum expected (6 decimals)
29+
30+
// 6. Function to deploy and verify the smart contract
31+
async function deployAndVerifyContract() {
32+
const args = [SWAP_ROUTER];
33+
const uniswapV3Wrapper: UniswapV3WrapperInstance = await UniswapV3Wrapper.new(
34+
...args,
35+
);
36+
37+
const uniswapV3WrapperAddress = await uniswapV3Wrapper.address;
38+
39+
try {
40+
await run("verify:verify", {
41+
address: uniswapV3WrapperAddress,
42+
constructorArguments: args,
43+
});
44+
} catch (e: any) {
45+
console.log(e);
46+
}
47+
48+
console.log("UniswapV3Wrapper deployed to:", uniswapV3WrapperAddress);
49+
50+
return uniswapV3Wrapper;
51+
}
52+
53+
// 7. The main function to execute the swap
54+
async function main() {
55+
// 8. Deploy and verify the UniswapV3 wrapper smart contract and get its address
56+
const uniswapV3Wrapper: UniswapV3WrapperInstance =
57+
await deployAndVerifyContract();
58+
const uniswapV3WrapperAddress = await uniswapV3Wrapper.address;
59+
60+
// 9. Get the deployer account
61+
const accounts = await web3.eth.getAccounts();
62+
const deployer = accounts[0];
63+
64+
console.log("Deployer:", deployer);
65+
console.log("Total accounts available:", accounts.length);
66+
67+
// 10. Get the FXRP address on Flare (from the Asset Manager)
68+
const assetManager = await getAssetManagerFXRP();
69+
const FXRP = await assetManager.fAsset();
70+
71+
console.log("USDT0:", USDT0);
72+
console.log("FXRP:", FXRP);
73+
console.log("Fee:", FEE);
74+
console.log("Amount In:", AMOUNT_IN.toString());
75+
console.log("Amount Out Min:", AMOUNT_OUT_MIN.toString());
76+
console.log("");
77+
78+
// 11. Define the USDT0 and FXRP token addresses
79+
const usdt0: ERC20Instance = await ERC20.at(USDT0);
80+
const fxrp: ERC20Instance = await ERC20.at(FXRP);
81+
82+
// 12. Check initial balances
83+
const initialUsdt0Balance = BigInt(
84+
(await usdt0.balanceOf(deployer)).toString(),
85+
);
86+
const initialFxrpBalance = BigInt(
87+
(await fxrp.balanceOf(deployer)).toString(),
88+
);
89+
90+
console.log("Initial USDT0 balance:", initialUsdt0Balance.toString());
91+
console.log("Initial FXRP balance:", initialFxrpBalance.toString());
92+
93+
// 13. Check if there is enough USDT0 to perform the swap
94+
const amountInBN = AMOUNT_IN;
95+
if (initialUsdt0Balance < amountInBN) {
96+
console.log(
97+
"❌ Insufficient USDT0 balance. Need:",
98+
AMOUNT_IN.toString(),
99+
"Have:",
100+
initialUsdt0Balance.toString(),
101+
);
102+
console.log(
103+
"Please ensure you have sufficient USDT0 tokens to perform the swap.",
104+
);
105+
return;
106+
}
107+
108+
// 14 Check Uniswap V3 pool using wrapper if it exists and has liquidity
109+
console.log("\n=== Step 1: Pool Verification ===");
110+
const poolInfo = await uniswapV3Wrapper.checkPool(USDT0, FXRP, FEE);
111+
console.log("Pool info:", poolInfo);
112+
const poolAddress = poolInfo.poolAddress;
113+
const hasLiquidity = poolInfo.hasLiquidity;
114+
const liquidity = poolInfo.liquidity;
115+
116+
console.log("Pool Address:", poolAddress);
117+
console.log("Has Liquidity:", hasLiquidity);
118+
console.log("Liquidity:", liquidity.toString());
119+
120+
if (poolAddress === "0x0000000000000000000000000000000000000000") {
121+
console.log("❌ Pool does not exist for this token pair and fee tier");
122+
console.log("Please check if the USDT0/FXRP pool exists on SparkDEX");
123+
return;
124+
}
125+
126+
if (!hasLiquidity) {
127+
console.log("❌ Pool exists but has no liquidity");
128+
return;
129+
}
130+
131+
console.log("✅ Pool verification successful!");
132+
133+
// 15. Approve USDT0 to wrapper contract to spend the tokens
134+
console.log("\n=== Step 2: Token Approval ===");
135+
const approveTx = await usdt0.approve(
136+
uniswapV3WrapperAddress,
137+
AMOUNT_IN.toString(),
138+
);
139+
console.log("✅ USDT0 approved to wrapper contract", approveTx);
140+
141+
// 16. Execute swap using the Uniswap V3 wrapper contract
142+
console.log("\n=== Step 3: Execute SparkDEX Swap ===");
143+
const deadline = Math.floor(Date.now() / 1000) + 20 * 60; // 20 minutes
144+
console.log("Deadline:", deadline);
145+
146+
console.log("Executing SparkDEX swap using wrapper...");
147+
const swapTx = await uniswapV3Wrapper.swapExactInputSingle(
148+
USDT0,
149+
FXRP,
150+
FEE,
151+
AMOUNT_IN.toString(),
152+
AMOUNT_OUT_MIN.toString(),
153+
deadline,
154+
0, // sqrtPriceLimitX96 = 0 (no limit)
155+
);
156+
157+
console.log("Transaction submitted:", swapTx);
158+
159+
const swapReceipt = await swapTx.receipt;
160+
console.log("✅ SparkDEX swap executed successfully!");
161+
162+
// 17. Extract amount out from events or calculate from balance change
163+
const finalFxrpBalance = BigInt((await fxrp.balanceOf(deployer)).toString());
164+
const amountOut = finalFxrpBalance - initialFxrpBalance;
165+
console.log("Amount out:", amountOut.toString());
166+
167+
// 18. Check final balances to verify the swap
168+
console.log("\n=== Step 4: Final Balances ===");
169+
const finalUsdt0Balance = BigInt(
170+
(await usdt0.balanceOf(deployer)).toString(),
171+
);
172+
const finalFxrpBalanceAfter = BigInt(
173+
(await fxrp.balanceOf(deployer)).toString(),
174+
);
175+
176+
console.log("Final USDT0 balance:", finalUsdt0Balance.toString());
177+
console.log("Final FXRP balance:", finalFxrpBalanceAfter.toString());
178+
console.log(
179+
"USDT0 spent:",
180+
(initialUsdt0Balance - finalUsdt0Balance).toString(),
181+
);
182+
console.log(
183+
"FXRP received:",
184+
(finalFxrpBalanceAfter - initialFxrpBalance).toString(),
185+
);
186+
}
187+
188+
main().catch((error) => {
189+
console.error(error);
190+
process.exitCode = 1;
191+
});

0 commit comments

Comments
 (0)