Skip to main content

Example: Single-Pair Swap Lyquid

This example shows how to achieve composability through network functions like the traditional contracts that can call each other directly on chain. We followed the code from the original Uniswap V2 Core implementation and translate that into a Lyquid to show case how you can easily 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 ETH composability layer LyquidIDs are represented by address, but should not be confused with the token address used by 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]"

Swap (multiple rounds)

# Token approval
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: 0.10 token0 -> 0.09 token1 ==="
cast send $PAIR "swap(uint256, uint256, address, uint256, uint256)" 0 90000000000000000 $RECIPIENT 100000000000000000 0 --private-key "$KEY" -q
sleep 1
print_prices
# -- Price0 (token0 in token1) --
# 827272727272727272 [8.272e17]
# -- Price1 (token1 in token0) --
# 1208791208791208791 [1.208e18]

echo "=== Swap 2: 0.05 token0 -> 0.0394 token1 ==="
cast send $PAIR "swap(uint256, uint256, address, uint256, uint256)" 0 39400000000000000 $RECIPIENT 50000000000000000 0 --private-key "$KEY" -q
sleep 1
print_prices
# -- Price0 (token0 in token1) --
# 757043478260869565 [7.57e17]
# -- Price1 (token1 in token0) --
# 1320928095566276131 [1.32e18]

echo "=== Swap 3: 0.20 token0 -> 0.128 token1 ==="
cast send $PAIR "swap(uint256, uint256, address, uint256, uint256)" 0 128000000000000000 $RECIPIENT 200000000000000000 0 --private-key "$KEY" -q
sleep 1
print_prices
# -- Price0 (token0 in token1) --
# 550074074074074074 [5.5e17]
# -- Price1 (token1 in token0) --
# 1817936978184756261 [1.817e18]

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)
# Output: "499999999999999000 [4.999e17]"