Skip to content

Swaps

A Plach swap is an Intear DEX swap with "dex_id": "slimedragon.near/xyk". The message of the swap says which pools to go through. Everything else works as on any DEX of the engine: swap_simple, simulate_swap_simple, and SwapSimple operations, with exact input or exact output.

The examples on this page swap from alice.near’s inner balance, after a storage deposit, registering NEAR, USDt and USDC, and depositing 1 NEAR and 1 USDt. Together they’re one script.

The message is a Borsh SwapArgs: a list of routes, and an optional fee discount.

struct SwapArgs {
routes: Vec<SwapRoute>,
fee_discount: Option<SignedFeeDiscount>,
}
struct SwapRoute {
/// Share of the amount that goes through this route, 1000000 = 100%
fraction: u32,
/// Pools to swap through, in order
pool_ids: Vec<u32>,
}

Referral fees are an argument of the engine’s swap, not part of the message. Messages from before they moved there can still list them after fee_discount. Plach reads and ignores them, so they don’t charge anything.

For a swap of the whole amount through one pool, without a fee discount, the message can also be just the pool ID, a little-endian u32. 3QAAAA== is pool 221, NEAR/USDt:

near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "3QAAAA==",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": {"ExactIn": "1000000000000000000000000"}
}' \
network-config mainnet now

Each pool of a route swaps the output of the one before into its other asset, and the last one must output asset_out. This message goes from USDt to NEAR in pool 221, then from NEAR to USDC in pool 85:

01 00 00 00 1 route
40 42 0f 00 fraction: 1000000 (100%)
02 00 00 00 2 pools
dd 00 00 00 221
55 00 00 00 85
00 no fee discount

That’s AQAAAEBCDwACAAAA3QAAAFUAAAAA in base64:

# USDt to NEAR in pool 221, then NEAR to USDC in pool 85
AMOUNT_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwACAAAA3QAAAFUAAAAA",
"trader": "alice.near",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "1000000"}
}' \
network-config mainnet now | jq -r '.[1]')
MIN_AMOUNT_OUT=$((AMOUNT_OUT * 99 / 100))
near contract call-function as-transaction dex.intear.near swap_simple \
json-args "$(jq -n --arg min_amount_out "$MIN_AMOUNT_OUT" '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwACAAAA3QAAAFUAAAAA",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "1000000"},
"constraint": $min_amount_out
}')" \
prepaid-gas '150.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

Unlike several SwapSimple operations, the intermediate asset (NEAR here) doesn’t go through your inner balance, so it doesn’t need to be registered for you, and it’s one swap for the engine.

With several routes, each takes its fraction of the amount, and the fractions must add up to 1000000. The first route also takes what’s left after rounding. This one swaps 70% of 1 NEAR to USDC in pool 85, and 30% in pool 22:

02 00 00 00 2 routes
60 ae 0a 00 fraction: 700000 (70%)
01 00 00 00 1 pool
55 00 00 00 85
e0 93 04 00 fraction: 300000 (30%)
01 00 00 00 1 pool
16 00 00 00 22
00 no fee discount
# 70% through pool 85 and 30% through pool 22, both NEAR/USDC
AMOUNT_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AgAAAGCuCgABAAAAVQAAAOCTBAABAAAAFgAAAAA=",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "1000000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[1]')
MIN_AMOUNT_OUT=$((AMOUNT_OUT * 99 / 100))
near contract call-function as-transaction dex.intear.near swap_simple \
json-args "$(jq -n --arg min_amount_out "$MIN_AMOUNT_OUT" '{
"dex_id": "slimedragon.near/xyk",
"message": "AgAAAGCuCgABAAAAVQAAAOCTBAABAAAAFgAAAAA=",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "1000000000000000000000000"},
"constraint": $min_amount_out
}')" \
prepaid-gas '150.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

Splits work for exact output too: then each route delivers its fraction of amount_out.

The pool ID 4294967295 (u32::MAX, ffffffff) means the pool created last. Use it to create a pool and swap in it in one execute_operations, before you know its ID.

In each pool, the fees are taken from the input first. The rest goes into the pool, and the output keeps the product of the pool’s balances:

amount_out = in_after_fees * out_balance / (in_balance + in_after_fees)

rounded down. For an exact output, the input is worked out backwards from the same formula and rounded up, so exactly amount_out comes out after fees. The input can be a few units more than the least that would do. A swap can’t take a pool’s whole balance of an asset, and fails on an empty pool.

Each pool of a route charges its own fees. Fees paid out in the pool’s other asset are swapped into it in the same pool right after, without fees, which moves its price a little more than the formula says. See Fees for what they are and where they go.

Each pool a swap goes through logs a Swap event with its amounts and fees, and a PoolUpdated event with its new balances.

#!/usr/bin/env bash
# Swaps on Plach with a split route and a route through two pools. alice.near
# holds USDt in the wallet and has never used Intear DEX.
set -euo pipefail
near contract call-function as-transaction dex.intear.near storage_deposit \
json-args '{}' \
prepaid-gas '30.0 Tgas' attached-deposit '0.01 NEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send
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
near contract call-function as-transaction dex.intear.near deposit_near \
json-args '{}' \
prepaid-gas '30.0 Tgas' attached-deposit '1 NEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send
near contract call-function as-transaction usdt.tether-token.near ft_transfer_call \
json-args '{"receiver_id": "dex.intear.near", "amount": "1000000", "msg": ""}' \
prepaid-gas '50.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send
near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "3QAAAA==",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": {"ExactIn": "1000000000000000000000000"}
}' \
network-config mainnet now
# 70% through pool 85 and 30% through pool 22, both NEAR/USDC
AMOUNT_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AgAAAGCuCgABAAAAVQAAAOCTBAABAAAAFgAAAAA=",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "1000000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[1]')
MIN_AMOUNT_OUT=$((AMOUNT_OUT * 99 / 100))
near contract call-function as-transaction dex.intear.near swap_simple \
json-args "$(jq -n --arg min_amount_out "$MIN_AMOUNT_OUT" '{
"dex_id": "slimedragon.near/xyk",
"message": "AgAAAGCuCgABAAAAVQAAAOCTBAABAAAAFgAAAAA=",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "1000000000000000000000000"},
"constraint": $min_amount_out
}')" \
prepaid-gas '150.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send
# USDt to NEAR in pool 221, then NEAR to USDC in pool 85
AMOUNT_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwACAAAA3QAAAFUAAAAA",
"trader": "alice.near",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "1000000"}
}' \
network-config mainnet now | jq -r '.[1]')
MIN_AMOUNT_OUT=$((AMOUNT_OUT * 99 / 100))
near contract call-function as-transaction dex.intear.near swap_simple \
json-args "$(jq -n --arg min_amount_out "$MIN_AMOUNT_OUT" '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwACAAAA3QAAAFUAAAAA",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "1000000"},
"constraint": $min_amount_out
}')" \
prepaid-gas '150.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send