Skip to main content

Example: Single-Pair Swap Lyquid

This example shows how to achieve composability through network functions like traditional contracts that can call each other directly on chain. The AMM logic follows the original Uniswap V2 Core implementation, while the pricing/slippage helpers are inspired by the Uniswap V2 periphery (Library/Router). We translate these ideas into a Lyquid to show how you can do the same. This only implements the core part that enables a standard and realistic AMM to allow swaps for one pair of tokens. It's not a ready-to-use product and does not include UI for simplicity of demonstration.

Prerequisites

Make sure you have started the local devnet.

Deployment

  1. Deploy Token A (First ERC20 Token)

    shaker deploy ~/.shakenup/ldk/lyquid-examples/erc20/Cargo.toml

    This command will give you something like

    created Lyquid-Adm8r3j64FbzRjwcBK4ZZRFcgLQjTqtt => 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512

    Keep both the Lyquid ID LYQUID_A=Lyquid-Adm8r3j64FbzRjwcBK4ZZRFcgLQjTqtt, and the address: TOKEN_A=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512

  2. Deploy Token B (Second ERC20 Token)

    shaker deploy ~/.shakenup/ldk/lyquid-examples/erc20/Cargo.toml

    This command will output a different set of Lyquid ID/address:

    created Lyquid-HTZTGB7jxhgcKGWn7R9897gzuCZpPhL8C => 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0

    Keep both the Lyquid ID LYQUID_B=Lyquid-HTZTGB7jxhgcKGWn7R9897gzuCZpPhL8C, and this address: TOKEN_B=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0

  3. Deploy the BasicSwap Contract

    The constructor of BasicSwap takes two LyquidIDs. In the ETH composability layer LyquidIDs are represented by address, but should not be confused with the token address used by a user's wallet. You can use shaker to-hex tool to convert LyquidID to hex format.

    LYQUID_A="Lyquid-Adm8r3j64FbzRjwcBK4ZZRFcgLQjTqtt"
    LYQUID_B="Lyquid-HTZTGB7jxhgcKGWn7R9897gzuCZpPhL8C"
    shaker deploy ~/.shakenup/ldk/lyquid-examples/basicswap/Cargo.toml --input $(cast abi-encode "constructor(address,address)" $(shaker to-hex "$LYQUID_A") $(shaker to-hex "$LYQUID_B"))

    This will output:

    created Lyquid-Kpdt9vk6tiBdQpgv6kqHpLwG2Sg9na58Y => 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9

    Keep this address: PAIR=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9. And convert the Lyquid ID to hex format.

    shaker to-hex Lyquid-Kpdt9vk6tiBdQpgv6kqHpLwG2Sg9na58Y
    # Output: "0xce749A113606ee9E8afE4C0927A4ebCe63F0a447"

Play with the BasicSwap

Here is an example to try all the functions.

Liquidity Deposit

export FOUNDRY_ETH_RPC_URL="http://localhost:10087/api"
export KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"

YOUR_ADDR="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
RECIPIENT="0x27Ef5cDBe01777D62438AfFeb695e33fC2335979"

# Contract addresses (from deployment above)
TOKEN_A="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
TOKEN_B="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"
PAIR="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"
PAIR_LYQUID="0xce749a113606ee9e8afe4c0927a4ebce63f0a447"

echo "=== Adding Liquidity ==="
# Transfer tokens to the pair
cast send $TOKEN_A "transfer(address, uint256)" $PAIR_LYQUID 1000000000000000000 --private-key "$KEY" -q
cast send $TOKEN_B "transfer(address, uint256)" $PAIR_LYQUID 1000000000000000000 --private-key "$KEY" -q
sleep 1
cast abi-decode "balanceOf(address) returns (uint256)" $(cast call 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 "balanceOf(address)" $PAIR_LYQUID)
# Output: "1000000000000000000 [1e18]"
cast abi-decode "balanceOf(address) returns (uint256)" $(cast call 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 "balanceOf(address)" $PAIR_LYQUID)
# Output: "1000000000000000000 [1e18]"

# Mint LP tokens to YOUR_ADDR
cast send $PAIR "mint(address)" $YOUR_ADDR --private-key "$KEY" -q
sleep 1

echo "=== LP Balance ==="
cast abi-decode "balanceOf(address) returns (uint256)" $(cast call $PAIR "balanceOf(address)" $YOUR_ADDR)
# Output: "999999999999999000 [9.999e17]"

echo "=== Initial Prices ==="
cast abi-decode "getPrice0() returns (uint256)" $(cast call $PAIR "getPrice0()")
# Output: "1000000000000000000 [1e18]"
cast abi-decode "getPrice1() returns (uint256)" $(cast call $PAIR "getPrice1()")
# Output: "1000000000000000000 [1e18]"

Use the slippage-based swap helpers so you only specify the amount and a slippage cap.

Flow overview:

  1. Approve input token to the pair’s LyquidID address so the pair can pull tokens from you.
  2. Exact-in swap (swapExactInWithSlippage): you provide how much you spend. The pair quotes the output and enforces the slippage cap (minimum output). token0_to_token1 indicates the swap direction: set it to true to swap token0 for token1; set it to false to swap token1 for token0.
  3. Exact-out swap (swapExactOutWithSlippage): you provide how much you want. The pair quotes the required input and enforces the slippage cap (maximum input).
  4. Observe prices after each swap to see the pool move.
# Token approval (approve the pair's LyquidID address for the input token)
cast send $TOKEN_A "approve(address, uint256)" $PAIR_LYQUID 2000000000000000000 --private-key "$KEY" -q
sleep 1

print_prices() {
echo "-- Price0 (token0 in token1) --"
cast abi-decode "getPrice0() returns (uint256)" $(cast call $PAIR "getPrice0()")
echo "-- Price1 (token1 in token0) --"
cast abi-decode "getPrice1() returns (uint256)" $(cast call $PAIR "getPrice1()")
}

echo "=== Swap 1: token0 -> token1 (exact in, 1% slippage) ==="
# token0_to_token1 = true, slippage_bps = 100 (1%), spend 0.1 token0
cast send $PAIR "swapExactInWithSlippage(bool,uint256,uint32,address)" true 100000000000000000 100 $RECIPIENT --private-key "$KEY" -q
sleep 1
print_prices
# -- Price0 (token0 in token1) --
# 826671736919986442 [8.266e17]
# -- Price1 (token1 in token0) --
# 1209669999999999999 [1.209e18]

echo "=== Swap 2: token1 -> token0 (exact out, 1% slippage) ==="
# Approve token1 as input
cast send $TOKEN_B "approve(address, uint256)" $PAIR_LYQUID 2000000000000000000 --private-key "$KEY" -q
sleep 1
# token0_to_token1 = false, slippage_bps = 100 (1%), want 0.05 token0
cast send $PAIR "swapExactOutWithSlippage(bool,uint256,uint32,address)" false 50000000000000000 100 $RECIPIENT --private-key "$KEY" -q
sleep 1
print_prices
# -- Price0 (token0 in token1) --
# 907401009472640909 [9.074e17]
# -- Price1 (token1 in token0) --
# 1102048586634453206 [1.102e18]

Liquidity Removal

echo "=== Liquidity Burn ==="
# Burn half of the LP tokens (500000000000000000)
cast send $PAIR "burn(address, uint256)" $YOUR_ADDR 500000000000000000 --private-key "$KEY" -q
sleep 1
# Check balances after burning
cast abi-decode "balanceOf(address) returns (uint256)" $(cast call $PAIR "balanceOf(address)" $YOUR_ADDR)
# === Liquidity Burn ===
# 499999999999999000 [4.999e17]