Skip to content

Swaps

A swap trades one asset for another on one DEX. The DEX decides the amounts, the engine moves the assets: amount_in from the trader to the DEX, amount_out from the DEX to the trader.

The near CLI examples on this page swap USDT from alice.near’s inner balance, after a storage deposit, these registrations, and a deposit of 10 USDT. Together they’re one script.

Terminal window
near contract call-function as-transaction dex.intear.near register_assets \
json-args '{
"asset_ids": [
"near",
"nep141:usdt.tether-token.near",
"nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1"
]
}' \
prepaid-gas '30.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

simulate_swap_simple is a view method that runs a swap without saving anything. It takes the same arguments as swap_simple plus trader, the account the DEX should see as the trader, and without constraint. It returns [amount_in, amount_out].

This one asks how much USDT it takes to get exactly 0.1 NEAR, and allows 1% more for the swap:

Terminal window
AMOUNT_IN=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"trader": "alice.near",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "near",
"amount": {"ExactOut": "100000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[0]')
# Pay up to 1% more than the simulation
MAX_AMOUNT_IN=$((AMOUNT_IN * 101 / 100))

The simulation doesn’t check balances or registrations, so a swap that simulates fine can still fail for those reasons.

Terminal window
near contract call-function as-transaction dex.intear.near swap_simple \
json-args "$(jq -n --arg max_amount_in "$MAX_AMOUNT_IN" '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "near",
"amount": {"ExactOut": "100000000000000000000000"},
"constraint": $max_amount_in
}')" \
prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send
Argument Description
dex_id The DEX to swap on
message Base64 bytes passed to the DEX as is. Each DEX defines its own format, usually the pool or route. For Plach, AQAAAEBCDwABAAAA3QAAAAA= is pool 221 (NEAR/USDT) and AQAAAEBCDwABAAAAVQAAAAA= is pool 85 (NEAR/USDC). See Plach routes
asset_in, asset_out The assets to swap
amount {"ExactIn": "<amount>"} to pay an exact amount, or {"ExactOut": "<amount>"} to receive an exact amount
constraint Optional. With ExactIn, the minimum amount_out. With ExactOut, the maximum amount_in. The swap fails if it’s not met
referrer Optional. Passed to the DEX, and logged in the swap event. Plach only logs it: its referral fees are in the message. Pass the same referrer to the simulation, in case a DEX uses it

It returns [amount_in, amount_out].

Before swapping:

  • Your inner balance must have at least amount_in of asset_in.
  • asset_out must be registered for you, or the output can’t be credited.

If anything fails, including the constraint, the whole call fails and no balances change.

The DEX returns amount_in and amount_out. The engine then checks that:

  • with ExactIn, the DEX’s amount_in equals the requested amount (Amount in does not match otherwise),
  • with ExactOut, the DEX’s amount_out equals the requested amount (Amount out does not match otherwise),
  • the constraint holds,
  • the DEX has enough asset_out in its inner balance.

The DEX can’t take more than amount_in from you or give out tokens it doesn’t hold.

To swap through several pools or DEXs, use execute_operations with several SwapSimple operations. Each one can take the output of the previous one as input with "amount": "OutputOfLastIn".

This swaps 1 USDT to USDC through NEAR, with two Plach pools. To get the least USDC to accept, simulate each swap with the output of the one before:

Terminal window
NEAR_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"trader": "alice.near",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "near",
"amount": {"ExactIn": "1000000"}
}' \
network-config mainnet now | jq -r '.[1]')
USDC_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args "$(jq -n --arg near_out "$NEAR_OUT" '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAAVQAAAAA=",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": $near_out}
}')" \
network-config mainnet now | jq -r '.[1]')
MIN_USDC_OUT=$((USDC_OUT * 99 / 100))
Terminal window
near contract call-function as-transaction dex.intear.near execute_operations \
json-args "$(jq -n --arg min_usdc_out "$MIN_USDC_OUT" '{
"operations": [
{
"SwapSimple": {
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "near",
"amount": {"Amount": {"ExactIn": "1000000"}},
"constraint": null
}
},
{
"SwapSimple": {
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAAVQAAAAA=",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": "OutputOfLastIn",
"constraint": $min_usdc_out
}
}
]
}')" \
prepaid-gas '200.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

Put the constraint on the last swap: it limits the result of the whole chain. The intermediate assets must be registered for you, because each output is credited to your inner balance before the next swap takes it.

Plach can also route through several of its pools in one swap, with a longer route in message. Several SwapSimple operations are for routes that go through different DEXs, or when you build the route yourself.