Multi-hop Routing
Overview
There are some cases where liquidity between a pair may be low or not exist and you need to perform a multi-hop swap between two or more pairs. The Router
contract enables multi-hop swap operations for Cosmos native and CW20 tokens. Swaps execute one-by-one and the last swap will return the ask token.
The initial swap will contain the offer token which determines how the contract message gets proccessed by the Router
contract. If the offer token is a native token, we can send an ExecuteMsg
to the Router
contract directly. Otherwise (if it's a CW20 token), we will need to encode our message within a Cw20ExecuteMsg
that gets sent from the address of the token we are swapping.
Native Routing
Use when the initial offer asset is a Cosmos native token.
execute_swap_operations
To create a native multi-hop swap, you need to execute a contract message pointing to the execute_swap_operations
endpoint in the Router
contract
operations
is the only required parameter and holds information regarding each swap stored in a vector containing objects of type SwapOperation
.
SwapOperation
is a helper enum that filters between astro_swaps
and native_swaps
. The former gets processed as swaps through Astroport's liquidity pools and the latter gets swapped natively (only between native assets).
This should not be confused with Native Routing. Our initial offer asset in this section is still a native asset despite our swap being an astro_swap
. As an astro_swap
, we are simply swapping between a native and CW20 token through an Astroport liquidity pool.
_37{_37 "execute_swap_operations": {_37 "operations": [_37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "native_token": {_37 "denom": "uluna"_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 }_37 }_37 }, _37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": zeroAddress_37 }_37 }_37 }_37 }_37 ], _37 "minimum_receive": beliefPrice,_37 "max_spread": "0.005", _37 "to": "..."_37 }_37}
Coins
Since our initial offer token is a native asset, we need to send the native token along with the transaction message.
The code in this section is an example of what a complete swap message may look like using feather.js to send Coins with our transaction.
_60// testnet contract addresses_60const routerAddress = 'terra1na348k6rvwxje9jj6ftpsapfeyaejxjeq6tuzdmzysps20l6z23smnlv64';_60const usdcAddress = 'terra14m3gtr8072t0hsefclfn50w4tdcphn66hvcu8qqf9wmtjyc9uasszqckyc'_60const zeroAddress = 'terra1juvel8n8tn3m3tnkq779nktrk7hf9t2kprx7wzs7jlz0gcaqrmqq6rlre5'_60_60const nativeRouterSwap = new MsgExecuteContract(_60wallet.key.accAddress('terra'),_60routerAddress,_60{_60 "execute_swap_operations": {_60 "operations": [_60 {_60 "astro_swap": {_60 "offer_asset_info": {_60 "native_token": {_60 "denom": "uluna"_60 }_60 },_60 "ask_asset_info": {_60 "token": {_60 "contract_addr": usdcAddress_60 }_60 }_60 }_60 }, _60 {_60 "astro_swap": {_60 "offer_asset_info": {_60 "token": {_60 "contract_addr": usdcAddress_60 }_60 },_60 "ask_asset_info": {_60 "token": {_60 "contract_addr": zeroAddress_60 }_60 }_60 }_60 }_60 ], _60 "minimum_receive": beliefPrice,_60 "max_spread": "0.005", _60 "to": "..."_60 }_60}, _60new Coins({ "uluna": "100000" })_60)_60_60const fee = new Fee(2000000, { uluna: 200000 });_60_60// // BROADCAST TRANSACTION_60const tx = await wallet.createAndSignTx({_60 msgs: [nativeRouterSwap],_60 fee,_60 chainID: 'pisco-1',_60});_60_60const result = await lcd.tx.broadcast(tx, 'pisco-1');_60_60console.log(result);
Optional Parameters
The execute_swap_operations
endpoint takes in the following optional parameters:
minimum_receive
: Minimum expected return amountmax_spread
: The difference between the ask amount before and after the swap. If the swap spread exceeds the provided max limit, the swap will fail.to
: Address receiving tokens (if different from sender)
_37{_37 "execute_swap_operations": {_37 "operations": [_37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "native_token": {_37 "denom": "uluna"_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 }_37 }_37 }, _37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": zeroAddress_37 }_37 }_37 }_37 }_37 ], _37 "minimum_receive": beliefPrice,_37 "max_spread": "0.005",_37 "to": "..."_37 }_37}
minimum_receive
+ max_spread
If minimum_receive
is provided in combination with max_spread
, the pool will check the difference between the return amount (using belief_price
) and the real pool price.
minimum_receive
+/- the max_spread
is the range of possible acceptable prices for this swap.
_37{_37 "execute_swap_operations": {_37 "operations": [_37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "native_token": {_37 "denom": "uluna"_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 }_37 }_37 }, _37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": zeroAddress_37 }_37 }_37 }_37 }_37 ], _37 "minimum_receive": beliefPrice,_37 "max_spread": "0.005",_37 "to": "..."_37 }_37}
Calculating minimum_receive
Astroport's native solution to obtain minimum_receive
is to query the simulate_swap_operations
endpoint from the Router
contract and to pass the result of this query into the swap message.
_37{_37 "execute_swap_operations": {_37 "operations": [_37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "native_token": {_37 "denom": "uluna"_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 }_37 }_37 }, _37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": zeroAddress_37 }_37 }_37 }_37 }_37 ], _37 "minimum_receive": beliefPrice,_37 "max_spread": "0.005",_37 "to": "..."_37 }_37}
simulate_swap_operations
The simulate_swap_operations
endpoint takes in two parameters:
offer_amount
: The amount of tokens to swapoperations
: The swap operations to perform
The operations
parameter takes in the same vector of SwapOperation
objects that our swap message contains.
_35{_35 "simulate_swap_operations": {_35 "offer_amount": "100000",_35 "operations": [_35 {_35 "astro_swap": {_35 "offer_asset_info": {_35 "native_token": {_35 "denom": "uluna"_35 }_35 },_35 "ask_asset_info": {_35 "token": {_35 "contract_addr": usdcAddress_35 }_35 }_35 }_35 }, _35 {_35 "astro_swap": {_35 "offer_asset_info": {_35 "token": {_35 "contract_addr": usdcAddress_35 }_35 },_35 "ask_asset_info": {_35 "token": {_35 "contract_addr": zeroAddress_35 }_35 }_35 }_35 }_35 ]_35 }_35}
execute_swap_operations
To create a native multi-hop swap, you need to execute a contract message pointing to the execute_swap_operations
endpoint in the Router
contract
operations
is the only required parameter and holds information regarding each swap stored in a vector containing objects of type SwapOperation
.
SwapOperation
is a helper enum that filters between astro_swaps
and native_swaps
. The former gets processed as swaps through Astroport's liquidity pools and the latter gets swapped natively (only between native assets).
This should not be confused with Native Routing. Our initial offer asset in this section is still a native asset despite our swap being an astro_swap
. As an astro_swap
, we are simply swapping between a native and CW20 token through an Astroport liquidity pool.
Coins
Since our initial offer token is a native asset, we need to send the native token along with the transaction message.
The code in this section is an example of what a complete swap message may look like using feather.js to send Coins with our transaction.
Optional Parameters
The execute_swap_operations
endpoint takes in the following optional parameters:
minimum_receive
: Minimum expected return amountmax_spread
: The difference between the ask amount before and after the swap. If the swap spread exceeds the provided max limit, the swap will fail.to
: Address receiving tokens (if different from sender)
minimum_receive
+ max_spread
If minimum_receive
is provided in combination with max_spread
, the pool will check the difference between the return amount (using belief_price
) and the real pool price.
minimum_receive
+/- the max_spread
is the range of possible acceptable prices for this swap.
Calculating minimum_receive
Astroport's native solution to obtain minimum_receive
is to query the simulate_swap_operations
endpoint from the Router
contract and to pass the result of this query into the swap message.
simulate_swap_operations
The simulate_swap_operations
endpoint takes in two parameters:
offer_amount
: The amount of tokens to swapoperations
: The swap operations to perform
The operations
parameter takes in the same vector of SwapOperation
objects that our swap message contains.
_37{_37 "execute_swap_operations": {_37 "operations": [_37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "native_token": {_37 "denom": "uluna"_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 }_37 }_37 }, _37 {_37 "astro_swap": {_37 "offer_asset_info": {_37 "token": {_37 "contract_addr": usdcAddress_37 }_37 },_37 "ask_asset_info": {_37 "token": {_37 "contract_addr": zeroAddress_37 }_37 }_37 }_37 }_37 ], _37 "minimum_receive": beliefPrice,_37 "max_spread": "0.005", _37 "to": "..."_37 }_37}
CW20 Routing
Use when the initial offer asset is a CW20 token.
Cw20HookMsg
To perform a CW20 swap, you need to execute a contract message pointing to the send
endpoint in the CW20 contract of the token you are swapping.
send
includes the contract the tokens are being sent to (router contract), the amount to send/swap, and a binary encoded msg containing our contract call.
_44{_44 "send": {_44 "contract": routerAddress, _44 "amount": "1000000", _44 "msg": toBase64(_44 {_44 "execute_swap_operations": {_44 "operations": [_44 {_44 "astro_swap": {_44 "offer_asset_info": {_44 "token": {_44 "contract_addr": astroAddress_44 }_44 },_44 "ask_asset_info": {_44 "native_token": {_44 "denom": "uluna"_44 }_44 }_44 }_44 }, _44 {_44 "astro_swap": {_44 "offer_asset_info": {_44 "native_token": {_44 "denom": "uluna"_44 }_44 },_44 "ask_asset_info": {_44 "token": {_44 "contract_addr": steakAddress_44 }_44 }_44 }_44 }_44 ], _44 "minimum_receive": nativeBeliefPrice,_44 "max_spread": "0.005"_44 }_44 }_44 )_44 }_44}
execute_swap_operations
Our encoded swap message performs a contract call to the execute_swap_operations
endpoint in the Router
contract. This is the same operation covered above in Native Routing.
_44{_44 "send": {_44 "contract": routerAddress, _44 "amount": "1000000", _44 "msg": toBase64(_44 {_44 "execute_swap_operations": {_44 "operations": [_44 {_44 "astro_swap": {_44 "offer_asset_info": {_44 "token": {_44 "contract_addr": astroAddress_44 }_44 },_44 "ask_asset_info": {_44 "native_token": {_44 "denom": "uluna"_44 }_44 }_44 }_44 }, _44 {_44 "astro_swap": {_44 "offer_asset_info": {_44 "native_token": {_44 "denom": "uluna"_44 }_44 },_44 "ask_asset_info": {_44 "token": {_44 "contract_addr": steakAddress_44 }_44 }_44 }_44 }_44 ], _44 "minimum_receive": nativeBeliefPrice,_44 "max_spread": "0.005"_44 }_44 }_44 )_44 }_44}
Encoding our Msg
To encode our message, there are two common options:
- An online base64 encoder
- A custom function
The code in this section uses a custom function (toBase64
) to display our binary message - this function needs to be defined elsewhere to be used. The actual string representation of our message would be an encoded binary.
_44{_44 "send": {_44 "contract": routerAddress, _44 "amount": "1000000", _44 "msg": toBase64(_44 {_44 "execute_swap_operations": {_44 "operations": [_44 {_44 "astro_swap": {_44 "offer_asset_info": {_44 "token": {_44 "contract_addr": astroAddress_44 }_44 },_44 "ask_asset_info": {_44 "native_token": {_44 "denom": "uluna"_44 }_44 }_44 }_44 }, _44 {_44 "astro_swap": {_44 "offer_asset_info": {_44 "native_token": {_44 "denom": "uluna"_44 }_44 },_44 "ask_asset_info": {_44 "token": {_44 "contract_addr": steakAddress_44 }_44 }_44 }_44 }_44 ], _44 "minimum_receive": nativeBeliefPrice,_44 "max_spread": "0.005"_44 }_44 }_44 )_44 }_44}
toBase64
Encoder
The code in this section is an example of what a send
message may look like feather.js to define our custom function and encode our message.
_65const toBase64 = (obj) => {_65 return Buffer.from(JSON.stringify(obj)).toString("base64");_65};_65_65const cw20RouterSwap = new MsgExecuteContract(_65 wallet.key.accAddress('terra'),_65 astroAddress,_65 {_65 "send": {_65 "contract": routerAddress, _65 "amount": "1000000", _65 "msg": toBase64(_65 {_65 "execute_swap_operations": {_65 "operations": [_65 {_65 "astro_swap": {_65 "offer_asset_info": {_65 "token": {_65 "contract_addr": astroAddress_65 }_65 },_65 "ask_asset_info": {_65 "native_token": {_65 "denom": "uluna"_65 }_65 }_65 }_65 }, _65 {_65 "astro_swap": {_65 "offer_asset_info": {_65 "native_token": {_65 "denom": "uluna"_65 }_65 },_65 "ask_asset_info": {_65 "token": {_65 "contract_addr": steakAddress_65 }_65 }_65 }_65 }_65 ], _65 "minimum_receive": nativeBeliefPrice,_65 "max_spread": "0.005"_65 }_65 }_65 )_65 }_65 }_65)_65_65const fee = new Fee(2000000, { uluna: 200000 });_65_65// // BROADCAST TRANSACTION_65const tx = await wallet.createAndSignTx({_65 msgs: [cw20RouterSwap],_65 fee,_65 chainID: 'pisco-1',_65});_65_65const result = await lcd.tx.broadcast(tx, 'pisco-1');_65_65console.log(result);
Calculating minimum_receive
Lastly, calculating minimum_receive
for CW20 swaps is the exact same as it is for native swaps.
Note that since we are performing a CW20 swap, our initial offer asset is a CW20 token and not a native asset like uluna
, but the logic remains the same. To learn more about this topic, refer to the section above.
_35{_35 "simulate_swap_operations": {_35"offer_amount": "1000000",_35 "operations": [_35 {_35 "astro_swap": {_35 "offer_asset_info": {_35 "token": {_35 "contract_addr": astroAddress_35 }_35 },_35 "ask_asset_info": {_35 "native_token": {_35 "denom": "uluna"_35 }_35 }_35 }_35 }, _35 {_35 "astro_swap": {_35 "offer_asset_info": {_35 "native_token": {_35 "denom": "uluna"_35 }_35 },_35 "ask_asset_info": {_35 "token": {_35 "contract_addr": steakAddress_35 }_35 }_35 }_35 }_35 ]_35 }_35}
Cw20HookMsg
To perform a CW20 swap, you need to execute a contract message pointing to the send
endpoint in the CW20 contract of the token you are swapping.
send
includes the contract the tokens are being sent to (router contract), the amount to send/swap, and a binary encoded msg containing our contract call.
execute_swap_operations
Our encoded swap message performs a contract call to the execute_swap_operations
endpoint in the Router
contract. This is the same operation covered above in Native Routing.
Encoding our Msg
To encode our message, there are two common options:
- An online base64 encoder
- A custom function
The code in this section uses a custom function (toBase64
) to display our binary message - this function needs to be defined elsewhere to be used. The actual string representation of our message would be an encoded binary.
toBase64
Encoder
The code in this section is an example of what a send
message may look like feather.js to define our custom function and encode our message.
Calculating minimum_receive
Lastly, calculating minimum_receive
for CW20 swaps is the exact same as it is for native swaps.
Note that since we are performing a CW20 swap, our initial offer asset is a CW20 token and not a native asset like uluna
, but the logic remains the same. To learn more about this topic, refer to the section above.
_44{_44 "send": {_44 "contract": routerAddress, _44 "amount": "1000000", _44 "msg": toBase64(_44 {_44 "execute_swap_operations": {_44 "operations": [_44 {_44 "astro_swap": {_44 "offer_asset_info": {_44 "token": {_44 "contract_addr": astroAddress_44 }_44 },_44 "ask_asset_info": {_44 "native_token": {_44 "denom": "uluna"_44 }_44 }_44 }_44 }, _44 {_44 "astro_swap": {_44 "offer_asset_info": {_44 "native_token": {_44 "denom": "uluna"_44 }_44 },_44 "ask_asset_info": {_44 "token": {_44 "contract_addr": steakAddress_44 }_44 }_44 }_44 }_44 ], _44 "minimum_receive": nativeBeliefPrice,_44 "max_spread": "0.005"_44 }_44 }_44 )_44 }_44}