Skip to content

Operations

Operations are steps that run one after another in a single call: swaps, withdrawals, transfers, DEX calls, registrations and storage deposits. If one fails, the whole call fails and nothing changes.

There are two ways to run them, and they differ in whose assets they use:

execute_operations Operations in a deposit
How Call execute_operations on the engine Put them in msg of ft_transfer_call, nft_transfer_call, mt_transfer_call or mt_batch_transfer_call, or in operations of deposit_near
Assets used Your inner balance Only the assets of this deposit
What’s left at the end Stays in your inner balance Must be zero, or the call fails
RegisterAssets, DeployDexCode Allowed Not allowed

See Operation for the fields of every operation.

The near CLI examples on this page run as alice.near after a storage deposit and registering near, nep141:usdt.tether-token.near and USDC. Together they’re one script.

This deposits 1 NEAR, swaps all NEAR in the inner balance for USDt, and withdraws the USDt to the wallet, in one call:

AMOUNT_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": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": {"ExactIn": "1000000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[1]')
MIN_AMOUNT_OUT=$((AMOUNT_OUT * 99 / 100))
# Deposits the attached 1 NEAR, swaps all NEAR in the inner balance for USDt,
# and withdraws the USDt
near contract call-function as-transaction dex.intear.near execute_operations \
json-args "$(jq -n --arg min_amount_out "$MIN_AMOUNT_OUT" '{
"operations": [
{
"SwapSimple": {
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"asset_in": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": "EntireBalanceIn",
"constraint": $min_amount_out
}
},
{
"Withdraw": {
"asset_id": "nep141:usdt.tether-token.near",
"amount": "PreviousSwapOutput",
"to": null,
"rescue_address": null
}
}
]
}')" \
prepaid-gas '150.0 Tgas' attached-deposit '1 NEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

At least 1 yoctoNEAR must be attached. If you attach more, the whole deposit is added to your NEAR inner balance before the operations run, so you can deposit NEAR and use it in the same call. NEAR must be registered for you in that case.

referrer is optional and applies to every swap and DEX call in the list. referral_fees is optional too: the referral fees of the first swap in the list.

With a non-empty msg, a deposit doesn’t go to your inner balance. Instead, the deposited assets are put aside for this call, and the operations can only use them. This is how you deposit, swap and withdraw in one transaction.

This sends USDt with ft_transfer_call, swaps it for exactly 0.1 NEAR, and withdraws both the NEAR and the USDt the swap didn’t use. msg is the list of operations as a JSON string, so it’s built with jq first:

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]')
# Send up to 1% more than the simulation. What the swap doesn't use comes back.
MAX_AMOUNT_IN=$((AMOUNT_IN * 101 / 100))
# Swaps USDt for exactly 0.1 NEAR, then withdraws the NEAR and the USDt that's left
MSG=$(jq -nc '[
{
"SwapSimple": {
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "near",
"amount": {"Amount": {"ExactOut": "100000000000000000000000"}},
"constraint": null
}
},
{
"Withdraw": {
"asset_id": "near",
"amount": "PreviousSwapOutput",
"to": null,
"rescue_address": null
}
},
{
"Withdraw": {
"asset_id": "nep141:usdt.tether-token.near",
"amount": {"Full": {"at_least": null}},
"to": null,
"rescue_address": null
}
}
]')
near contract call-function as-transaction usdt.tether-token.near ft_transfer_call \
json-args "$(jq -n --arg amount "$MAX_AMOUNT_IN" --arg msg "$MSG" '{
"receiver_id": "dex.intear.near",
"amount": $amount,
"msg": $msg
}')" \
prepaid-gas '300.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

With an exact output, the amount you send is already the most you pay, so the swap doesn’t need a constraint.

msg is either the list of operations, or an object with a referrer, referral fees for the first swap, or both:

{ "operations": [ ... ], "referrer": "myapp.near", "referral_fees": [["myapp.near", 5000]] }

For NEAR, pass the same list or object as operations to deposit_near, as JSON rather than a string, with the NEAR attached.

Rules for operations in a deposit:

  • Everything must be used. At the end, every asset put aside must be at zero: swapped, withdrawn, transferred or attached to a DEX call. Otherwise the call fails with Sandboxed assets must be empty after execution. Did you forget to withdraw ...?. With an ExactOut swap, withdraw the rest of the input too.
  • On failure, the deposit goes back. The call fails as a whole, and the token contract returns the tokens to the sender.
  • No registrations or deployments. RegisterAssets and DeployDexCode fail with Operation only available in execute_actions.
  • The sender is the one the token contract reports. For NEP-141 it’s sender_id. For NFTs and multi tokens, sender_id must equal previous_owner_id, so tokens sent with an approval can’t run operations.
  • One token per multi token transfer. An mt_batch_transfer_call of several tokens can’t run operations, and fails with Operations can only follow a transfer of one token.
  • DEXs know the difference. A DEX called from a deposit sees the sender as alleged, along with the asset that was deposited, and can refuse actions that need the trader’s permission. See DEXs on the engine.

A Withdraw in a deposit needs a place to refund the assets if the transfer fails. The engine refunds to an inner balance, in this order:

  1. the receiver (to), if the asset is registered for them,
  2. the sender, if the asset is registered for them,
  3. rescue_address, if the sender doesn’t have the asset registered.

When the operation runs, the sender must have the asset registered, or rescue_address must be set and have it registered. Otherwise it fails with No rescue address provided and user doesn't have a registered balance for this asset, or with Asset <asset> is not registered for Account(<rescue_address>) if rescue_address doesn’t have it.

To deposit, swap and keep the output in the engine, use a TransferAsset to yourself instead of a Withdraw. TransferAsset needs an exact amount, so this works best with an ExactOut swap. This swaps NEAR for exactly 1 USDt, keeps the USDt in the inner balance, and sends the rest of the NEAR back:

# Swaps the attached NEAR for exactly 1 USDt, keeps the USDt in the inner
# balance, and withdraws the NEAR that's left
near contract call-function as-transaction dex.intear.near deposit_near \
json-args '{
"operations": [
{
"SwapSimple": {
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"asset_in": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": {"Amount": {"ExactOut": "1000000"}},
"constraint": null
}
},
{
"TransferAsset": {
"to": {"Account": "alice.near"},
"asset_id": "nep141:usdt.tether-token.near",
"amount": "1000000"
}
},
{
"Withdraw": {
"asset_id": "near",
"amount": {"Full": {"at_least": null}},
"to": null,
"rescue_address": null
}
}
]
}' \
prepaid-gas '150.0 Tgas' attached-deposit '1 NEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

SwapAdvanced swaps on one of several options, each a DEX and the message to pass to it, like the dex_id and message of a SwapSimple. The options can be different DEXs, or different routes on the same DEX. The engine simulates the swap on them in order, and with mode:

  • FindBest swaps on the option that gives the most output for an exact input, or takes the least input for an exact output. The constraint applies to that option.
  • FallbackQueue swaps on the first option that works and meets the constraint, without trying the ones after it. Put the option you prefer first, and the others as fallbacks.

An option whose simulation fails is skipped, with a log of Skipping swap option on <dex id>: <error>. If no option is left, the operation fails with All swap options failed. An option on a DEX that doesn’t exist fails the whole operation, and so does a picked option whose DEX doesn’t hold enough asset_out. The engine doesn’t run the DEX again for the option it picks: the simulation is made real.

This deposits 0.1 NEAR and swaps it for USDC in whichever of two Plach pools gives more:

# Two Plach pools of NEAR and USDC: 85 and 22
OUT_85=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAAVQAAAAA=",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "100000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[1]')
OUT_22=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAAFgAAAAA=",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "100000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[1]')
# Accept up to 1% less than the better of the two
MIN_AMOUNT_OUT=$(( (OUT_85 > OUT_22 ? OUT_85 : OUT_22) * 99 / 100 ))
# Deposits the attached 0.1 NEAR and swaps it for USDC in the pool that gives
# more, keeping the USDC in the inner balance
near contract call-function as-transaction dex.intear.near execute_operations \
json-args "$(jq -n --arg min_amount_out "$MIN_AMOUNT_OUT" '{
"operations": [
{
"SwapAdvanced": {
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": "EntireBalanceIn",
"constraint": $min_amount_out,
"options": [
{"dex_id": "slimedragon.near/xyk", "message": "AQAAAEBCDwABAAAAVQAAAAA="},
{"dex_id": "slimedragon.near/xyk", "message": "AQAAAEBCDwABAAAAFgAAAAA="}
],
"mode": "FindBest"
}
}
]
}')" \
prepaid-gas '200.0 Tgas' attached-deposit '0.1 NEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

Referral fees are paid once, for the option that swaps, and only if it’s the first swap of the list, as with SwapSimple.

SwapSimple and SwapAdvanced take an amount of:

Value Meaning
{ "Amount": { "ExactIn": "..." } } or { "Amount": { "ExactOut": "..." } } A fixed amount
"OutputOfLastIn" Exact input of the output of the last swap before it. Its asset_out must be this asset_in
"EntireBalanceIn" Exact input of everything you have of asset_in: your inner balance with execute_operations, what’s left of the deposit with operations in a deposit

Withdraw takes an amount of:

Value Meaning
{ "Exact": "..." } A fixed amount
{ "Full": { "at_least": "..." } } Everything you have of the asset. Fails if it’s less than at_least, which can be null
"PreviousSwapOutput" The output of the last swap before it. Its asset_out must be this asset_id

“The last swap” is the most recent SwapSimple or SwapAdvanced in the list, even if other operations come after it.

#!/usr/bin/env bash
# Runs operations: on the inner balance with execute_operations, and on the
# tokens of a deposit. 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
AMOUNT_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": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": {"ExactIn": "1000000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[1]')
MIN_AMOUNT_OUT=$((AMOUNT_OUT * 99 / 100))
# Deposits the attached 1 NEAR, swaps all NEAR in the inner balance for USDt,
# and withdraws the USDt
near contract call-function as-transaction dex.intear.near execute_operations \
json-args "$(jq -n --arg min_amount_out "$MIN_AMOUNT_OUT" '{
"operations": [
{
"SwapSimple": {
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"asset_in": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": "EntireBalanceIn",
"constraint": $min_amount_out
}
},
{
"Withdraw": {
"asset_id": "nep141:usdt.tether-token.near",
"amount": "PreviousSwapOutput",
"to": null,
"rescue_address": null
}
}
]
}')" \
prepaid-gas '150.0 Tgas' attached-deposit '1 NEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send
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]')
# Send up to 1% more than the simulation. What the swap doesn't use comes back.
MAX_AMOUNT_IN=$((AMOUNT_IN * 101 / 100))
# Swaps USDt for exactly 0.1 NEAR, then withdraws the NEAR and the USDt that's left
MSG=$(jq -nc '[
{
"SwapSimple": {
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"asset_in": "nep141:usdt.tether-token.near",
"asset_out": "near",
"amount": {"Amount": {"ExactOut": "100000000000000000000000"}},
"constraint": null
}
},
{
"Withdraw": {
"asset_id": "near",
"amount": "PreviousSwapOutput",
"to": null,
"rescue_address": null
}
},
{
"Withdraw": {
"asset_id": "nep141:usdt.tether-token.near",
"amount": {"Full": {"at_least": null}},
"to": null,
"rescue_address": null
}
}
]')
near contract call-function as-transaction usdt.tether-token.near ft_transfer_call \
json-args "$(jq -n --arg amount "$MAX_AMOUNT_IN" --arg msg "$MSG" '{
"receiver_id": "dex.intear.near",
"amount": $amount,
"msg": $msg
}')" \
prepaid-gas '300.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send
# Swaps the attached NEAR for exactly 1 USDt, keeps the USDt in the inner
# balance, and withdraws the NEAR that's left
near contract call-function as-transaction dex.intear.near deposit_near \
json-args '{
"operations": [
{
"SwapSimple": {
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAA=",
"asset_in": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": {"Amount": {"ExactOut": "1000000"}},
"constraint": null
}
},
{
"TransferAsset": {
"to": {"Account": "alice.near"},
"asset_id": "nep141:usdt.tether-token.near",
"amount": "1000000"
}
},
{
"Withdraw": {
"asset_id": "near",
"amount": {"Full": {"at_least": null}},
"to": null,
"rescue_address": null
}
}
]
}' \
prepaid-gas '150.0 Tgas' attached-deposit '1 NEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send
# Two Plach pools of NEAR and USDC: 85 and 22
OUT_85=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAAVQAAAAA=",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "100000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[1]')
OUT_22=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAAFgAAAAA=",
"trader": "alice.near",
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": {"ExactIn": "100000000000000000000000"}
}' \
network-config mainnet now | jq -r '.[1]')
# Accept up to 1% less than the better of the two
MIN_AMOUNT_OUT=$(( (OUT_85 > OUT_22 ? OUT_85 : OUT_22) * 99 / 100 ))
# Deposits the attached 0.1 NEAR and swaps it for USDC in the pool that gives
# more, keeping the USDC in the inner balance
near contract call-function as-transaction dex.intear.near execute_operations \
json-args "$(jq -n --arg min_amount_out "$MIN_AMOUNT_OUT" '{
"operations": [
{
"SwapAdvanced": {
"asset_in": "near",
"asset_out": "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
"amount": "EntireBalanceIn",
"constraint": $min_amount_out,
"options": [
{"dex_id": "slimedragon.near/xyk", "message": "AQAAAEBCDwABAAAAVQAAAAA="},
{"dex_id": "slimedragon.near/xyk", "message": "AQAAAEBCDwABAAAAFgAAAAA="}
],
"mode": "FindBest"
}
}
]
}')" \
prepaid-gas '200.0 Tgas' attached-deposit '0.1 NEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send