Understanding Fees

This document provides a comprehensive overview of the fee structure in the Blanksquare protocol. It covers both protocol-specific fees and EVM gas fees associated with different transaction types, and it also guides you on how to implement these fees in your integration.

Shielder operations

Fees incurred when using Blanksquare consist of several components that work together to enable private transactions. The total fee a user pays depends on the operation type.


/*
  The `feeIncluded` parameter specifies if the protocol fee should be added to `amount`
  passed (`feeIncluded = false`), or if the passed amount is the final amount and
  protocol fee should be carved out of it (`feeIncluded = true`).
*/
const feeIncluded = true
const { amount: amountWithProtocolFee, protocolFee } = await shielderClient.getProtocolShieldFee(amount, feeIncluded)

await shielderClient.shield(
  token,
  amountWithProtocolFee,
  transactionParams => walletClient.sendTransaction(transactionParams),
  accountAddress,
  protocolFee
)

Note: since the contract deals with ERC20 tokens, you might need to precede shield with an allowance transaction, which incurs a network fee:

const hash = await walletClient.writeContract({
  address: token.address,
  abi: erc20Abi,
  functionName: "approve",
  args: [shielderContractAddress, amountWithProtocolFee],
});

await publicClient.waitForTransactionReceipt({ hash });

await shielderClient.shield(
  // ...

const expectedAmount = ... // the amount the user will receive at the destination address

const relayerFees = await shielderClient.getRelayerFees(
  token,
  pocketMoney
)
const totalRelayerCost = relayerFees.fee_details.total_cost_fee_token

const {
  amount: totalAmount,
  protocolFee
} = await shielderClient.getProtocolWithdrawFee(expectedAmount + totalRelayerCost, false)

await shielderClient.withdraw(
  token,
  totalAmount,
  relayerFees,
  to,
  pocketMoney,
  protocolFee,
  memo
)

Notes:

  • totalAmount is what is subtracted from the user's shielded account, while expectedAmount is what the user receives on the destination address. The relation between them is as follows:

    expectedAmount = totalAmount - (totalAmount * protocolFeeBps) - totalRelayerCost
  • The protocol fee is always calculated on the entire withdrawn amount: expectedAmount + totalRelayerCost, not just on expectedAmount.

  • All fees are paid in the token being withdrawn (fee_token).

  • The fees estimations received from getRelayerFees() are guaranteed for some time. The relayer records the user's "withdraw request" and, during the final withdrawal, executes it by deducting the previously agreed fees from the user's transaction. The network fees are included in the relayer fees, so variations in actual network costs don't affect the user's final payment.

  • Calling shielderClient.getRelayerFees is equivalent to making a POST request to the /quote_fees endpoint of the relayer API with the following payload:

    {
      fee_token: <<token>>,
      pocketMoney: <<value>>
    }

    and a return value like this:

    {
      fee_details: {
        total_cost_native,
        total_cost_fee_token,
        gas_cost_native,
        gas_cost_fee_token,
        relayer_cost_native,
        pocket_money_native,
        pocket_money_fee_token,
        commission_native,
        commission_fee_token
      },
      price_details: {
        gas_price,
        native_token_price,
        native_token_unit_price,
        fee_token_price,
        fee_token_unit_price,
        token_price_ratio
      }
    }

    where:

    • total_cost — the total amount the user pays to the relayer,

    • relayer_cost — the amount the relayer spends on network fee + pocket money,

    • pocket_money — the amount the relayer spends on pocket money; pocket money is always received in the native token, but the user pays the relayer in fee_token,

    • gas_cost — the amount the relayer spends on network fee,

    • commission — the amount the relayer charges for the service (calculated as a percentage of relayer_cost),

    all of which are represented either in the native token (holding the ..._native suffix) or in the fee_token (holding the ..._fee_token suffix).

Relayer fees structure summary

Total relayer fee: $75
├── Relayer cost: $50
│   ├── Network fee: $20
│   └── Pocket money: $30
└── Relayer commission: $25 (% of Relay Cost)

The feeInluded parameter behavior

The feeIncluded parameter determines how the protocol fee is handled in relation to the amount you want to shield or withdraw:

  • feeIncluded = false: The amount parameter represents the net amount you want to shield/withdraw.

  • feeIncluded = true: The amount parameter represents the total amount including the protocol fee.

Example with amount = 100 and protocolFee = 1%:

feeIncluded = true:
├── User provides: amount = 100
├── Base for protocol fee: 100 / 1.01 ≈ 99.0099
├── Protocol fee "carved out": 100 / 1.01 * 1% ≈ 0.99
├── Total charged: amountWithProtocolFee ≈ 100
└── Net amount received: ≈ 99.0099
feeIncluded = false:
├── User provides: amount = 100
├── Base for protocol fee: 100
├── Protocol fee: 100 * 1% = 1
├── Total charged: amountWithProtocolFee = 101
└── Net amount received: 100

Choose feeIncluded = true when the user-supplied value already represents the final shielded balance they should receive. Choose feeIncluded = false when you want the user to deposit exactly what they entered and pay protocol fees on top.

Pocket money mechanics

When calling withdraw, the relayer can optionally include a small amount of the native token (called pocket money) alongside the withdrawn ERC-20 tokens.

The relayer funds this pocket money by deducting a proportional amount of ERC-20 tokens from the withdrawal, using a relayer-defined exchange rate.

If a user withdraws A ERC-20 tokens, the actual received amounts are:

ERC-20 received = A - (M / R)
Native tokens received (pocket money) = M

Where:

  • A - total ERC-20 tokens withdrawn

  • M - amount of native tokens given as pocket money

  • R - relayer's exchange rate (ERC-20 per 1 native token)

UI/UX considerations

  • In the UI, it's encouraged for transparency, to present fees as additional costs on top of the amount the user wants to shield or withdraw. Example:

    • The user wants to shield 100 USDC.

    • The user receives 100 USDC shielded.

    • The user pays 100 USDC plus all applicable fees.

  • Pocket money is determined by the frontend/user based on whether the recipient needs native tokens for future transactions.

Glossary

  • Shielding - Moving tokens from a public address into the Shielded Pool.

  • Withdrawing (or Sending privately) - Sending funds from the Shielded Pool to a public address.

  • Network fee - This is the mandatory fee paid directly to the blockchain network to process and confirm the user's transaction. The exact amount depends on network conditions, such as congestion and transaction complexity. Represented in the relayer api as gas_cost.

  • Relayer fee - A single upfront fee that covers both the network fee and the relayer’s service fee. The user provides the relayer with tokens, and the relayer handles the shielder transaction end-to-end on the user’s behalf, while keeping privacy fully protected.

  • Protocol fee - A fee paid to the protocol when entering or exiting the Shielded Pool. Unlike the relayer fee, this is the protocol’s own commission, used to support its maintenance and further development.

  • Allowance fee - The network fee paid when granting a smart contract permission to use a specified amount of ERC-20 tokens. It is charged once when the approval is signed, and afterwards normal transactions can be executed within the approved limit.

  • Auto fee reserve (also known as Pocket money) - A small portion of the user’s tokens is swapped by the relayer into the native token of the destination account. This ensures the receiver has enough balance to cover the withdrawal network fee. This mechanism prevents users from getting “stuck” with tokens they can’t move or use due to insufficient funds for the next transaction, and it improves overall usability — especially when sending assets to new or empty accounts.

Last updated