# Fees

> Plach fees: fee configurations of pools, fixed and scheduled fees, the assets fees are paid in, the protocol fee, withdrawing and claiming fees, and signed fee discounts.

This page is part of the Plach docs. Plach is the DEX `slimedragon.near/xyk` on the Intear DEX contract, `dex.intear.near`, and every call goes to that contract. https://docs.intear.tech/plach/llms-full.txt has all of its pages in one file, and https://docs.intear.tech/llms.txt lists every page of the Intear docs.

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%.

The creator of a pool sets its **pool fees**, and Plach takes 20% of each of them as its **protocol fee**. The protocol fee isn't added on top: a fee of 1% for an account pays the account 0.8% and Plach 0.2%, and a pool without fees pays no protocol fee either. The protocol's part of each fee is 20% of its fraction, rounded down.

Each fee is its fraction of the input, rounded down, and each pool of a [route](https://docs.intear.tech/plach/guides/swaps.md#through-several-pools) charges its own.

## Pool fees

A pool's fee configuration is a list of receivers and their fees:

```rust
struct FeeConfiguration {
    receivers: Vec<(FeeReceiver, FeeAmount)>,
}

enum FeeReceiver {
    /// The fee stays in the pool, or goes to its liquidity providers
    Pool,
    /// The fee is collected for this account
    Account { account_id: AccountId, payout_type: FeePayoutType, claim_mode: ClaimMode },
    /// Only in the fees of a swap, never in a fee configuration
    Protocol,
}

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.

Limits, checked with the fees at the time of the call 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, and neither can `Protocol`,
- an `Account` receiver can't `Reinvest`.

Who can change a pool's fees depends on its [kind](https://docs.intear.tech/plach/guides/pools.md#kinds-of-pools): only the owner of an unlocked private pool, with [`edit_fees`](https://docs.intear.tech/plach/guides/pools.md#edit-fees).

A `Pool` fee goes to the [liquidity providers](https://docs.intear.tech/plach/guides/liquidity.md#fees) of a public pool, and into the liquidity of private and launch pools, which have no providers.

## The asset a fee is paid in

A swap pays its fees in its input asset. The `payout_type` of an `Account` receiver says what the receiver takes:

```rust
enum FeePayoutType {
    /// The asset the swap paid in, so either asset of the pool
    KeepBoth,
    /// Always the pool's first asset
    ConvertToFirstAsset,
    /// Always the pool's second asset
    ConvertToSecondAsset,
    /// Only for liquidity providers: more shares of the pool
    Reinvest,
}
```

A fee in the other asset is swapped in the same pool, without fees, right after the swap, which moves the pool's price a little further. In a launch pool, an `Account` receiver with `ConvertToFirstAsset` is always paid in the quote asset, sells included.

## Protocol fee

The protocol fee is 20% of each pool fee, for `plach.intear.near`. It's paid in a stable asset if the pool has one, the first asset if both are, and otherwise in the asset the swap paid in. The stable assets are NEAR, USDt (`usdt.tether-token.near`) and USDC (`17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1`). Intear may change this list.

The `fees` of a [`PoolView`](https://docs.intear.tech/plach/reference/types.md#poolview) are the fees of a swap right now: each receiver's share after the protocol's part, and the protocol fee last.

## Collected fees

Plach keeps the fees of `Account` receivers and the protocol fee for each account and asset. The pool registers each receiver for the assets its payout takes when the pool is created or its fees are edited, so receivers don't have to do anything first. The `claim_mode` of a receiver says how its fees leave Plach:

```rust
enum ClaimMode {
    /// The account withdraws them to its inner balance with withdraw_fees
    OnlyOwner,
    /// Anyone sends them to the account's wallet with claim_fees_anyone
    Anyone,
}
```

`OnlyOwner` fees are shown by `get_pending_fees`, and `withdraw_fees` moves them to the account's [inner balance](https://docs.intear.tech/intear-dex/guides/inner-balances.md), so the assets must be registered for it there. Fees that [liquidity providers](https://docs.intear.tech/plach/guides/liquidity.md#fees) collect are withdrawn the same way.

`register_fee_assets` registers assets for the caller's collected fees, with NEAR attached for the storage. Nobody needs it now, since the pool registers its receivers. It was for the [referral fees](#referral-fees) Plach used to charge.

With near CLI, the examples below run as alice.near after a [storage deposit](https://docs.intear.tech/intear-dex/guides/storage.md#accounts) and this setup. It registers NEAR and USDt for alice.near in the engine, and deposits 1.12 NEAR and 5 USDt: 1 NEAR and 5 USDt for a pool, 0.02 NEAR for its storage, and 0.1 NEAR to sell. alice.near trades in its own pool to keep the example short. Together they're [one script](#the-whole-script).

```sh
# Fees are withdrawn to the inner balance, so register their assets there too
near contract call-function as-transaction dex.intear.near register_assets \
  json-args '{"asset_ids": ["near", "nep141:usdt.tether-token.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 '1.12 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": "5000000", "msg": ""}' \
  prepaid-gas '50.0 Tgas' attached-deposit '1 yoctoNEAR' \
  sign-as alice.near network-config mainnet sign-with-keychain send
```

The whole file, tested in CI: https://docs.intear.tech/snippets/cli/plach-fees.sh

This creates a private pool of NEAR and USDt with a fee of 1% for alice.near, paid in NEAR, as described in [Create a pool](https://docs.intear.tech/plach/guides/pools.md#create-a-pool):

```
00                                       asset 0: near
01 16 00 00 00 "usdt.tether-token.near"  asset 1: nep141:usdt.tether-token.near
01 00 00 00                              fees: 1 receiver
03 0a 00 00 00 "alice.near" 01 00          Account(alice.near, ConvertToFirstAsset, OnlyOwner)
00 10 27 00 00                             Fixed(10000), 1%
00                                       pool type: PrivateLatest
01                                       initial liquidity: Some
00 00 00 a1 ed cc ce 1b c2 d3 00 00 00 00 00 00    1000000000000000000000000 (1 NEAR)
40 4b 4c 00 00 00 00 00 00 00 00 00 00 00 00 00    5000000 (5 USDt)
00                                       creator's fee payout: None
```

```sh
# A private pool of NEAR and USDt with a fee of 1% for alice.near, paid in
# NEAR, and 1 NEAR and 5 USDt of liquidity. The other 0.02 NEAR pays for its storage, and what
# it doesn't use goes to alice.near's wallet.
POOL_ID=$(near --quiet contract call-function as-transaction dex.intear.near dex_call \
  json-args '{
    "dex_id": "slimedragon.near/xyk",
    "method": "create_pool",
    "args": "AAEWAAAAdXNkdC50ZXRoZXItdG9rZW4ubmVhcgEAAAADCgAAAGFsaWNlLm5lYXIBAAAQJwAAAAEAAACh7czOG8LTAAAAAAAAQEtMAAAAAAAAAAAAAAAAAAA=",
    "attached_assets": {
      "near": "1020000000000000000000000",
      "nep141:usdt.tether-token.near": "5000000"
    }
  }' \
  prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \
  sign-as alice.near network-config mainnet sign-with-keychain send | jq -r .)
```

The whole file, tested in CI: https://docs.intear.tech/snippets/cli/plach-fees.sh

A swap of 0.1 NEAR in the pool pays alice.near 0.0008 NEAR, and `plach.intear.near` the other 0.0002 NEAR of the 1% fee:

```sh
# Sell 0.1 NEAR in the pool. The pool ID alone is the message of a swap through it.
AMOUNT_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
  json-args "$(jq -n --arg pool_id "$POOL_ID" '{
    "dex_id": "slimedragon.near/xyk",
    "message": $pool_id,
    "trader": "alice.near",
    "asset_in": "near",
    "asset_out": "nep141:usdt.tether-token.near",
    "amount": {"ExactIn": "100000000000000000000000"}
  }')" \
  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 pool_id "$POOL_ID" --arg min_amount_out "$MIN_AMOUNT_OUT" '{
    "dex_id": "slimedragon.near/xyk",
    "message": $pool_id,
    "asset_in": "near",
    "asset_out": "nep141:usdt.tether-token.near",
    "amount": {"ExactIn": "100000000000000000000000"},
    "constraint": $min_amount_out
  }')" \
  prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \
  sign-as alice.near network-config mainnet sign-with-keychain send
```

The whole file, tested in CI: https://docs.intear.tech/snippets/cli/plach-fees.sh

### Withdraw fees

`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, which is what alice.near is paid in:

```sh
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
```

The whole file, tested in CI: https://docs.intear.tech/snippets/cli/plach-fees.sh

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

```sh
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
```

The whole file, tested in CI: https://docs.intear.tech/snippets/cli/plach-fees.sh

### Fees anyone can claim

An `Account` receiver with `claim_mode: Anyone` doesn't call Plach to get its fees: anyone can send them to its wallet with `claim_fees_anyone`, which takes the account and a list of assets (`ClaimFeesArgs { account_id, asset_ids }`). `get_claimable_fees` shows them, with the same arguments. It's for accounts that don't call Intear DEX themselves, like a community's treasury, or a contract that shares the fees with a token's holders.

The transfer doesn't check that the account can receive the asset, like a storage registration on its token contract. Use `OnlyOwner` for any account that can call `withdraw_fees` itself.

The `Community` receivers of launch pools from before are `Anyone` receivers now, paid in the quote asset.

## Referral fees

Apps charge their own fees on the swaps they send with the engine's [referral fees](https://docs.intear.tech/intear-dex/guides/referral-fees.md), which work the same on every DEX: out of the swap's input, before Plach gets it. Pool and protocol fees, and fee discounts, apply to the rest.

Swap messages from before the engine took referral fees listed them after the fee discount. Plach still reads those messages, and ignores their referral fees.

## Fee discounts

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 or staking TEAR](https://docs.intear.tech/dex-aggregator/guides/amounts.md#fee-discounts).

```rust
struct SignedFeeDiscount {
    discount: FeeDiscount,
    /// Signature of sha256(borsh(FeeDiscountSignaturePayload)) 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,
}

/// What the signer signs, so that a discount works only on one chain, engine and DEX
struct FeeDiscountSignaturePayload {
    chain_id: String,           // "mainnet"
    engine_contract_id: AccountId,  // dex.intear.near
    dex_id: DexId,              // deployer "slimedragon.near", id "xyk"
    discount: FeeDiscount,
}
```

The discount is only valid for exactly this swap: the same routes, assets and amount, by `trader_id`, until the block height. For a swap in the operations of a deposit, `trader_id` must be the [alleged sender](https://docs.intear.tech/intear-dex/guides/dexes.md#what-a-dex-sees) and the deposited asset must be `asset_in`. The signature is Ed25519 (64 bytes), or secp256k1 with the recovery ID (0 or 1, not 27 or 28) as the last byte, by the key that `get_fee_discount_signer` returns. If that's `None`, fee discounts are off.

## The whole script

```sh
#!/usr/bin/env bash
# Creates a private NEAR/USDt pool on Plach with a fee of 1% for alice.near,
# swaps in it, and withdraws the fee. alice.near trades in its own pool to keep
# it short. 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

# Fees are withdrawn to the inner balance, so register their assets there too
near contract call-function as-transaction dex.intear.near register_assets \
  json-args '{"asset_ids": ["near", "nep141:usdt.tether-token.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 '1.12 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": "5000000", "msg": ""}' \
  prepaid-gas '50.0 Tgas' attached-deposit '1 yoctoNEAR' \
  sign-as alice.near network-config mainnet sign-with-keychain send

# A private pool of NEAR and USDt with a fee of 1% for alice.near, paid in
# NEAR, and 1 NEAR and 5 USDt of liquidity. The other 0.02 NEAR pays for its storage, and what
# it doesn't use goes to alice.near's wallet.
POOL_ID=$(near --quiet contract call-function as-transaction dex.intear.near dex_call \
  json-args '{
    "dex_id": "slimedragon.near/xyk",
    "method": "create_pool",
    "args": "AAEWAAAAdXNkdC50ZXRoZXItdG9rZW4ubmVhcgEAAAADCgAAAGFsaWNlLm5lYXIBAAAQJwAAAAEAAACh7czOG8LTAAAAAAAAQEtMAAAAAAAAAAAAAAAAAAA=",
    "attached_assets": {
      "near": "1020000000000000000000000",
      "nep141:usdt.tether-token.near": "5000000"
    }
  }' \
  prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \
  sign-as alice.near network-config mainnet sign-with-keychain send | jq -r .)

# Sell 0.1 NEAR in the pool. The pool ID alone is the message of a swap through it.
AMOUNT_OUT=$(near contract call-function as-read-only dex.intear.near simulate_swap_simple \
  json-args "$(jq -n --arg pool_id "$POOL_ID" '{
    "dex_id": "slimedragon.near/xyk",
    "message": $pool_id,
    "trader": "alice.near",
    "asset_in": "near",
    "asset_out": "nep141:usdt.tether-token.near",
    "amount": {"ExactIn": "100000000000000000000000"}
  }')" \
  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 pool_id "$POOL_ID" --arg min_amount_out "$MIN_AMOUNT_OUT" '{
    "dex_id": "slimedragon.near/xyk",
    "message": $pool_id,
    "asset_in": "near",
    "asset_out": "nep141:usdt.tether-token.near",
    "amount": {"ExactIn": "100000000000000000000000"},
    "constraint": $min_amount_out
  }')" \
  prepaid-gas '100.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 dex_view \
  json-args '{
    "dex_id": "slimedragon.near/xyk",
    "method": "get_pending_fees",
    "args": "CgAAAGFsaWNlLm5lYXIBAAAAAA=="
  }' \
  network-config mainnet now

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
```
