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 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 and nep141:usdt.tether-token.near. Together they’re one script.
execute_operations
Section titled “execute_operations”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 USDTnear 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 sendAt 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.
Operations in a deposit
Section titled “Operations in a deposit”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 leftMSG=$(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 sendWith 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:
{ "operations": [ ... ], "referrer": "myapp.near" }For NEAR, pass the same value as operations to deposit_near, 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 anExactOutswap, 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.
RegisterAssetsandDeployDexCodefail withOperation 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_idmust equalprevious_owner_id, so tokens sent with an approval can’t run operations. - DEXs know the difference. A DEX called from a deposit sees the call as not authorized, and can refuse actions that need the trader’s permission. See DEXs on the engine.
Withdrawing from a deposit
Section titled “Withdrawing from a deposit”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:
- the receiver (
to), if the asset is registered for them, - the sender, if the asset is registered for them,
rescue_address, if the sender doesn’t have the asset registered.
When the operation runs, the sender or rescue_address must have the asset registered, or it fails with No rescue address provided and user doesn't have a registered balance for this asset.
Into the inner balance
Section titled “Into the inner balance”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 leftnear 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 sendAmounts that depend on earlier steps
Section titled “Amounts that depend on earlier steps”SwapSimple takes an amount of:
| Value | Meaning |
|---|---|
{ "Amount": { "ExactIn": "..." } } or { "Amount": { "ExactOut": "..." } } |
A fixed amount |
"OutputOfLastIn" |
Exact input of the output of the last SwapSimple 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 SwapSimple before it. Its asset_out must be this asset_id |
“The last SwapSimple” is the most recent one in the list, even if other operations come after it.