DEXs on the engine
This page is also Markdown, for agents: /intear-dex/guides/dexes.md, and /llms.txt lists every page.
A DEX on Intear DEX is a WebAssembly module stored in the engine contract. The engine runs it in an interpreter when someone swaps on it or calls one of its methods. The DEX has its own inner balances and its own storage, and can’t touch those of other DEXs or users.
DEX IDs
Section titled “DEX IDs”A DEX ID is <deployer>/<name>: the account that deployed the code, and a name the deployer chose. For example, slimedragon.near/xyk is Plach, the AMM that slimedragon.near deployed under the name xyk. The same deployer can have several DEXs with different names.
Deploying
Section titled “Deploying”DEX code is deployed with deploy_dex_code, or with a DeployDexCode operation in execute_operations. Only the trusted code deployer can call it (see below), so here are only its arguments:
// deploy_dex_code, deposit: 1 yoctoNEAR{ "last_part_of_id": "xyk", "code_base64": "<base64 of the .wasm>"}This creates or replaces the code of the DEX <caller>/<last_part_of_id>, pays for the code from the DEX’s storage balance, and logs a dex_deployed event with the SHA-256 of the code. Make a storage deposit for the DEX with dex_storage_deposit first.
Calling a DEX
Section titled “Calling a DEX”Besides swap, a DEX can have any other methods: adding and removing liquidity, creating pools, settings for its owner, reads of its state. Reach them with dex_call and dex_view. Arguments and results are bytes, in base64, in whatever format the DEX defines.
dex_call
Section titled “dex_call”This registers alice.near for the liquidity shares of Plach’s pool 221. args is the pool ID in Borsh (221 as a little-endian u32), and the attached NEAR pays for the storage, from alice.near’s inner balance. The DEX uses what it needs, and sends the rest back to alice.near’s wallet. It runs after a storage deposit, registering near, and a deposit of 0.01 NEAR, as in this script.
near contract call-function as-transaction dex.intear.near dex_call \ json-args '{ "dex_id": "slimedragon.near/xyk", "method": "register_liquidity", "args": "3QAAAA==", "attached_assets": {"near": "10000000000000000000000"} }' \ 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 |
method |
The method to call. Can’t be swap |
args |
Base64 arguments, in the DEX’s format |
attached_assets |
Assets to give to the DEX with the call, from your inner balance. The DEX sees them and their amounts, and the engine moves them to the DEX’s inner balance, so each must be registered for the DEX. A DEX can’t register assets for itself: for one it has never held, call register_assets with "for": {"Dex": "<dex id>"} first. Can be {} |
referrer |
Optional. The DEX doesn’t receive it: the engine adds it to the events the DEX logs during the call, in dex_events_borsh |
It returns the DEX’s response, in base64. The DEX can also, in its response:
- send assets from its inner balance to the inner balance of an account or another DEX,
- withdraw assets from its inner balance to a wallet,
- turn NEAR from its inner balance into its storage balance.
A withdrawal to a wallet is refunded like any other if the transfer fails: to the receiver’s inner balance if the asset is registered for them, and otherwise back to the DEX’s, where it isn’t the receiver’s anymore. So register the assets you expect to get from a DEX call before making it.
The same call is available as a DexCall operation.
dex_view
Section titled “dex_view”A view method. The DEX can read its storage but can’t change anything.
This reads Plach’s pool 221. Like with dex_call, args is the pool ID in Borsh:
near contract call-function as-read-only dex.intear.near dex_view \ json-args '{ "dex_id": "slimedragon.near/xyk", "method": "get_pool", "args": "3QAAAA==" }' \ network-config mainnet nowIt returns the DEX’s response in base64, or "" if the DEX returned nothing.
What a DEX sees
Section titled “What a DEX sees”swap |
dex_call |
dex_view |
|
|---|---|---|---|
| Input | Borsh SwapRequest: message, assets, amount, referrer |
Borsh DexCallRequest: attached_assets and args |
args as is |
| Expected output | Borsh SwapResponse: amount_in, amount_out |
Borsh DexCallResponse, or nothing |
Any bytes |
caller |
The trader | The caller | Not available |
| Can write storage | Yes | Yes | No |
The caller host function gives the DEX a Borsh Caller:
Direct(account)when the account called the engine itself:swap_simple,dex_call, or operations inexecute_operations. The account is authorized.OnTransfer { alleged_sender, transfer_of }for operations in a deposit.alleged_senderis whoever the token contract says senttransfer_of, the deposited asset, and a forged token contract can callft_on_transferwith anysender_id. A DEX must not give an alleged sender anything it wouldn’t give anyone, such as access to their liquidity or fees, unless it trusts the contract oftransfer_of. Afterdeposit_near,transfer_ofisnearand the sender is real.
simulate_swap_simple runs the DEX’s swap with Direct(trader).
A DEX can also read its own ID with dex_id, the engine’s account with engine_contract_id, and the chain with chain_id, for example to sign messages that only work on this DEX.
To fail a swap or a call, the DEX panics. The whole engine call fails with an error that contains [<dex id>] Dex panicked: <message>, and no balances change. A message the DEX raises with a Rust panic! comes after panicked at <file>:<line>:<col>: and a line break. One raised with env::panic_str, like Plach’s No routes, has no prefix. See Errors.
A swap runs as a simulation first: what the DEX writes is kept aside, and only written when the engine has checked the result. The DEX sees its own writes and its storage usage as if they were written.
A DEX runs inside the engine’s receipt, so it can’t make cross-contract calls. It can use storage, block height and timestamp, gas counters, the random seed, and hash and signature functions (SHA-256, Keccak, RIPEMD-160, ecrecover, ed25519_verify). It can’t read the predecessor, the signer or the attached deposit, and can’t log text. A DEX that imports any other host function fails every call with Failed to instantiate module, so in near-sdk mark every method #[payable], since the others read the attached deposit, and don’t use log!. Events it logs with the engine’s dex_event host function are collected into one dex_events_borsh event at the end of the call. In Rust, the intear-dex-types crate’s dex feature adds the engine’s own host functions (caller, dex_event, dex_id, engine_contract_id), and the rest come from near-sdk.
The whole script
Section titled “The whole script”#!/usr/bin/env bash# Reads a Plach pool with dex_view, and registers for its liquidity shares with# dex_call, from an account that has never used Intear DEXset -euo pipefail
near contract call-function as-read-only dex.intear.near dex_view \ json-args '{ "dex_id": "slimedragon.near/xyk", "method": "get_pool", "args": "3QAAAA==" }' \ network-config mainnet now
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"]}' \ 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 '0.01 NEAR' \ sign-as alice.near network-config mainnet sign-with-keychain send
near contract call-function as-transaction dex.intear.near dex_call \ json-args '{ "dex_id": "slimedragon.near/xyk", "method": "register_liquidity", "args": "3QAAAA==", "attached_assets": {"near": "10000000000000000000000"} }' \ prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \ sign-as alice.near network-config mainnet sign-with-keychain send