Skip to content

Fees

Fees are parts of a swap’s input. Plach writes them as a u32 out of 1000000, so 3000 is 0.3% and 1000000 would be 100%.

Each pool swap pays, from its input:

  • the pool fees, set by the pool’s creator,
  • the protocol fee, to Plach,
  • referral fees, if the swap’s message lists any.

Each fee is its fraction of the input, rounded down, and each pool of a route charges its own. When a swap sells a launched token in a launch pool, the fees are taken in the token and swapped to the quote asset in the same pool, so they’re always paid in the quote asset.

A pool’s fee configuration is a list of receivers and their fees. Version 2, which all new pools can use, is:

enum FeeConfiguration {
V1(CurrentFees),
V2 { receivers: Vec<(FeeReceiver, FeeAmount)> },
}
enum FeeReceiver {
/// The fee is collected for this account, which withdraws it
Account(AccountId),
/// The fee stays in the pool, for its liquidity providers or owner
Pool,
/// Only in launch pools quoted in NEAR, see "Community fees"
Community(AccountId),
}
enum FeeAmount {
Fixed(u32),
/// Goes down from the start fee to the end fee, between the two times
Scheduled { start: (u64, u32), end: (u64, u32), curve: ScheduledFeeCurve },
/// Not supported yet
Dynamic { min: u32, max: u32 },
}

Times are in nanoseconds, like NEAR block timestamps, and the only curve is Linear. A scheduled fee must go down: the start time must be before the end time, and the start fee higher than the end fee. Before the start it’s the start fee, after the end it’s the end fee. For example, a launch pool can start with a high fee that goes down over its first hours.

V1(CurrentFees) is a plain list of (FeeReceiver, u32), the format of V1 pools.

Limits, checked when a pool is created and when its fees change:

  • at most 20 receivers,
  • each fee below 1000000, and all of them together at most 900000 (90%),
  • plach.intear.near, the protocol fee receiver, can’t be a receiver.

Who can change a pool’s fees depends on its kind: only the owner of an unlocked private pool, with edit_fees.

Plach adds a protocol fee for plach.intear.near to the pool fees:

Pool Protocol fee
Both assets are stable 0.0001% (1)
Any other 0.1% (1000)

The stable assets are NEAR, USDT (usdt.tether-token.near), USDC (17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1), and tokens of omft.near and omni.hot.tg accounts. Intear may change this list.

While a scheduled fee hasn’t reached its end, 5% of it goes to the protocol fee instead of its receiver.

The fees of a PoolView are the fees of a swap right now, with the protocol fee last.

Fees for Account receivers, referral fees and the protocol fee are kept by Plach for each account and asset. get_pending_fees shows them, and withdraw_fees moves them to the account’s inner balance, so the assets must be registered for it there. See the example below.

An app can charge its own fees on the swaps it sends, by listing them in the swap’s message as referral_fees: accounts and the fee each one gets, on top of the pool and protocol fees. Nothing is set up in advance, so each swap can charge different fees.

  • At most 5 accounts, each listed once, with fees that add up to at most 5% (50000) in each pool. A fee of 0 charges nothing.
  • 20% of each referral fee goes to the protocol fee receiver, plach.intear.near, rounded down, and the rest to the account.
  • An account is only paid in assets it registered with register_fee_assets: the input asset of the swap, or the quote asset when the swap sells into a launch pool. Otherwise that account’s fee isn’t charged, and neither is the protocol’s share of it. Registering an asset twice fails.
  • Fee discounts don’t reduce referral fees.
  • The referrer of a swap is only logged, it doesn’t charge anything.

The Developers page of the Plach app registers fee assets for you, with a list of common ones. The DEX Aggregator puts its referral_fees in the message of Plach swaps, and includes them in its quotes.

With near CLI, the examples below run as alice.near after a storage deposit, registering NEAR and USDT for it in the engine, and depositing 1.02 NEAR. alice.near refers its own swap to keep the example short. Together they’re one script.

Terminal window
# Collect referral fees in NEAR and USDT
near contract call-function as-transaction dex.intear.near dex_call \
json-args '{
"dex_id": "slimedragon.near/xyk",
"method": "register_fee_assets",
"args": "AgAAAAABFgAAAHVzZHQudGV0aGVyLXRva2VuLm5lYXI=",
"attached_assets": {"near": "10000000000000000000000"}
}' \
prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

This message swaps through pool 221 with a referral fee of 1% (10000) for alice.near:

01 00 00 00 1 route
40 42 0f 00 fraction: 1000000 (100%)
01 00 00 00 1 pool
dd 00 00 00 221
00 no fee discount
01 00 00 00 1 referral fee
0a 00 00 00 10 bytes of account ID
61 6c 69 63 65 2e 6e 65 61 72 alice.near
10 27 00 00 10000 (1%)

Simulate it with the same message, since the fee changes the output. alice.near gets 0.8% of the 1 NEAR it sells, and plach.intear.near 0.2%:

Terminal window
# A swap through pool 221 that pays alice.near a referral fee of 1%. Simulate it
# with the same message, since the referral fee changes the output.
AMOUNT_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
json-args '{
"dex_id": "slimedragon.near/xyk",
"message": "AQAAAEBCDwABAAAA3QAAAAABAAAACgAAAGFsaWNlLm5lYXIQJwAA",
"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))
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": "AQAAAEBCDwABAAAA3QAAAAABAAAACgAAAGFsaWNlLm5lYXIQJwAA",
"asset_in": "near",
"asset_out": "nep141:usdt.tether-token.near",
"amount": {"ExactIn": "1000000000000000000000000"},
"constraint": $min_amount_out
}')" \
prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

get_pending_fees takes an account and a list of assets, and returns a Borsh HashMap<AssetId, u128> of the fees collected in each, for the assets that are registered. Here NEAR, since the swap paid in NEAR:

Terminal window
near contract call-function as-read-only dex.intear.near dex_view \
json-args '{
"dex_id": "slimedragon.near/xyk",
"method": "get_pending_fees",
"args": "CgAAAGFsaWNlLm5lYXIBAAAAAA=="
}' \
network-config mainnet now

withdraw_fees takes a list of assets, and moves all of the caller’s collected fees in them to their inner balance:

Terminal window
near contract call-function as-transaction dex.intear.near dex_call \
json-args '{
"dex_id": "slimedragon.near/xyk",
"method": "withdraw_fees",
"args": "AQAAAAA=",
"attached_assets": {}
}' \
prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \
sign-as alice.near network-config mainnet sign-with-keychain send

A launch pool quoted in NEAR can have Community(account) receivers: fees in NEAR for an account that doesn’t have to do anything to get them, for example the community of the launched token. Anyone can send them to the account’s wallet with withdraw_community_fee, which takes the account ID. get_community_owned_fees shows how much is waiting, in yoctoNEAR.

A swap message can carry a signed fee discount, which reduces the pool and protocol fees of that swap. The DEX Aggregator signs them for traders holding TEAR.

struct SignedFeeDiscount {
discount: FeeDiscount,
/// Signature of sha256(borsh(discount)) by the fee discount signer
signature: Vec<u8>,
}
struct FeeDiscount {
routes: Vec<SwapRoute>,
asset_in: AssetId,
asset_out: AssetId,
/// Only this trader can use it, anyone if `None`
trader_id: Option<AccountId>,
amount: SwapRequestAmount,
/// 1000000 waives the pool and protocol fees
fee_discount: u32,
expiration_block_height: u64,
}

The discount is only valid for exactly this swap: the same routes, assets and amount, by trader_id (the alleged trader), until the block height. The signature is Ed25519, or secp256k1 with the recovery ID as the last byte, by the key that get_fee_discount_signer returns. If that’s None, fee discounts are off.