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(
// ...
Warning: USDT approval caveat
To change the approved amount for USDT, you must first set the allowance to zero if it is not already zero. This prevents a race condition described in the EIP-20 issue comment.
Source: USDT contract on Etherscan
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, whileexpectedAmount
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 onexpectedAmount
.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 infee_token
,gas_cost
— the amount the relayer spends on network fee,commission
— the amount the relayer charges for the service (calculated as a percentage ofrelayer_cost
),
all of which are represented either in the native token (holding the
..._native
suffix) or in thefee_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
feeInluded
parameter behaviorThe feeIncluded
parameter determines how the protocol fee is handled in relation to the amount you want to shield or withdraw:
feeIncluded = false
: Theamount
parameter represents the net amount you want to shield/withdraw.feeIncluded = true
: Theamount
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 withdrawnM
- amount of native tokens given as pocket moneyR
- relayer's exchange rate (ERC-20 per 1 native token)
UI/UX considerations
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