Swaps
This page is also Markdown, for agents: /plach/guides/swaps.md, and /llms.txt lists every page.
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
Section titled “The message”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 nowThrough several pools
Section titled “Through several pools”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 route40 42 0f 00 fraction: 1000000 (100%)02 00 00 00 2 poolsdd 00 00 00 22155 00 00 00 8500 no fee discountThat’s AQAAAEBCDwACAAAA3QAAAFUAAAAA in base64:
# USDt to NEAR in pool 221, then NEAR to USDC in pool 85AMOUNT_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 sendUnlike 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.
Split across routes
Section titled “Split across routes”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 routes60 ae 0a 00 fraction: 700000 (70%)01 00 00 00 1 pool55 00 00 00 85e0 93 04 00 fraction: 300000 (30%)01 00 00 00 1 pool16 00 00 00 2200 no fee discount# 70% through pool 85 and 30% through pool 22, both NEAR/USDCAMOUNT_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 sendSplits work for exact output too: then each route delivers its fraction of amount_out.
The last created pool
Section titled “The last created pool”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.
Prices
Section titled “Prices”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.
Events
Section titled “Events”Each pool a swap goes through logs a Swap event with its amounts and fees, and a PoolUpdated event with its new balances.
The whole script
Section titled “The whole script”#!/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/USDCAMOUNT_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 85AMOUNT_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