Executing a Swap

The following pages will walk you through how to execute both a fixed-input and fixed-output swap on Ethereum’s testnet Sepolia. They’ll consistently exchange AUSD for CTK since this is our main pair on testnets. You can also adapt the scripts to do the inverse for testing purposes.

Prerequisites

Wallet Client and Getting Gas

Just like all of the other guides, be sure that you have followed the steps here to configure your Wallet Client. If this has been done correctly you should be able to import your client at the top of your script:

1import { client } from "walletClient";

You also need to make sure that your wallet does have Sepolia ETH for any gas fees on testnet. The instructions for using a faucet to get a small amount of Sepolia are also found in our quick-start guide.

Testnet AUSD

In order to be able to initiate a swap you must have at least one of the Tokens that you’re swapping. Since our examples show swapping from AUSD to CTK, you need to have AUSD in your wallet at the start. You can also choose to get some of both if you want.

Instructions for getting testnet AUSD are in our Getting Testnet Tokens guide.

Whitelisted Wallet

The AgoraStableSwapPair is permissioned, meaning that only approved wallets may call its swap functions. Before spending gas, make sure that your address has the APPROVED_SWAPPER role. If it does not, the swap will not be executed.

Checking your wallet’s role

To check whether the wallet is whitelisted, we’ll call hasRole on the contract with the role identifier (APPROVED_SWAPPER ) and the wallet address.

1async function isWhiteListed(
2 pair: `0x${string}`,
3 callerAddress: `0x${string}`,
4) {
5 return await client.readContract({
6 address: pair,
7 abi: stableSwapAbi,
8 functionName: "hasRole",
9 args: ["APPROVED_SWAPPER", callerAddress],
10 });
11}

If running this returns true then you’re all set!

Self-whitelisting

Testnet only

This self-service whitelisting is only available on testnets. On mainnet, whitelisting requires completing KYC verification through Agora.

On testnets, Agora deploys a Whitelister contract that holds the WHITELISTER_ROLE on all pairs. This allows any user to whitelist themselves by calling setApprovedSwapper.

The Whitelister contract address is the same on all testnets:

0x7c10F56d6f04a51376393a1C3670e966863F6BD5

See Protocol Deployments for the full list of testnet addresses.

Call the setApprovedSwapper function with your wallet address to grant yourself the APPROVED_SWAPPER role on all testnet pairs:

Helper Function:

1const WHITELISTER = "0x7c10F56d6f04a51376393a1C3670e966863F6BD5";
2
3const whitelisterAbi = [
4 {
5 type: "function",
6 name: "setApprovedSwapper",
7 inputs: [{ name: "swapper", type: "address" }],
8 outputs: [],
9 stateMutability: "nonpayable",
10 },
11] as const;
12
13async function whitelistSelf(callerAddress: `0x${string}`) {
14 const { request } = await client.simulateContract({
15 address: WHITELISTER,
16 abi: whitelisterAbi,
17 functionName: "setApprovedSwapper",
18 args: [callerAddress],
19 });
20 return await client.writeContract(request);
21}

Using this helper function:

1(async () => {
2 const [callerAddress] = await client.getAddresses();
3 console.log(`Caller Address: ${callerAddress}`);
4
5 // Check if already whitelisted
6 const isWhitelisted = await isWhiteListed(TESTNET_PAIR, callerAddress);
7
8 if (!isWhitelisted) {
9 console.log("Not whitelisted, requesting whitelist...");
10 const txHash = await whitelistSelf(callerAddress);
11 await client.waitForTransactionReceipt({ hash: txHash });
12 console.log(`Whitelisted! Transaction: ${txHash}`);
13 } else {
14 console.log("Already whitelisted!");
15 }
16})();

Output:

Caller Address: 0x99B0E95Fa8F5C3b86e4d78ED715B475cFCcf6E97
Not whitelisted, requesting whitelist...
Whitelisted! Transaction: 0x3f6478e712e688eab150dd86ffc4bdd5a49b62619ad80fcf2c5a4f226c0ec6e5

Once whitelisted, you can proceed to execute swaps on any testnet pair.

Fetch token decimals

In order to calculate the swap amounts correctly, you need to know the decimal precision of both tokens in the pair. The AgoraStableSwapPair contract exposes the decimals for both tokens through getter functions:

1async function getTokenDecimals(
2 client: PublicActions,
3 pairAddress: `0x${string}`,
4) {
5 const [decimalsToken0, decimalsToken1] = await Promise.all([
6 client.readContract({
7 address: pairAddress,
8 abi: stableSwapAbi,
9 functionName: "token0Decimals",
10 args: [],
11 }),
12 client.readContract({
13 address: pairAddress,
14 abi: stableSwapAbi,
15 functionName: "token1Decimals",
16 args: [],
17 }),
18 ]);
19
20 return { decimalsToken0, decimalsToken1 };
21}

Define a swapPath

An important step in swapping using our pair contract is making sure that you determine a swapPath. This essentially means that you clearly define which token you are inputting and what token will be output. This keeps things clear so that you make sure you don’t accidentally swap in the wrong direction.

  • Setting a swapPath of [token0, token1] means you will swap token0 for token1
  • Setting a swapPath of [token1, token0] means you will swap token1 for token0

Finding token order in the pair

How do I know what token is token0 and which is token1 in an Agora pair?

Each Agora pair has a name function. The name returned is purposefully configured to show the order in the pair.

1async function getTokenOrder(
2 client: PublicActions,
3 pairAddress: `0x${string}`,
4) {
5 const [name, token0, token1] = await Promise.all([
6 client.readContract({
7 address: pairAddress,
8 abi: stableSwapAbi,
9 functionName: "name",
10 args: [],
11 }),
12 client.readContract({
13 address: pairAddress,
14 abi: stableSwapAbi,
15 functionName: "token0",
16 args: [],
17 }),
18 client.readContract({
19 address: pairAddress,
20 abi: stableSwapAbi,
21 functionName: "token1",
22 args: [],
23 }),
24 ]);
25
26 const [name0, name1] = (name as string).split("/");
27
28 return {
29 token0: { name: name0, address: token0 as `0x${string}` },
30 token1: { name: name1, address: token1 as `0x${string}` },
31 };
32}

The above example will return a name of “CTK/AUSD”. With that name, it means CTK is token0 and AUSD is token1.

Example swapPath

Because you funded your wallet with AUSD earlier (see Getting Testnet Tokens), we’ll set our swapPathto swap AUSD for CTK:

1const { token0, token1 } = await getTokenOrder(client, TESTNET_PAIR_AUSD_CTK);
2
3// this will give us
4// token0 = { name: "CTK", address: "0x..." }
5// token1 = { name: "AUSD", address: "0x..." }
6
7const swapPath = [token1.address, token0.address] as const; // AUSD → CTK
8
9// Need the opposite direction? Use:
10// const swapPath = [token0.address, token1.address] as const; // CTK → AUSD