Fees
What is Fee
Fee is a percentage of the transfer amount that is charged by the bridge when transferring tokens between networks.
Purpose
- Covering bridge operational costs
- Economic incentive for maintaining infrastructure
Who Receives Fee
Fees are received by the proxy contract owner. The owner can withdraw accumulated fees at any time via withdrawTokenFee() function.
When Fee is Charged
Fee is charged during TVM→TVM transfers and comes in two types:
| Fee Type | When Charged | Description |
|---|---|---|
| Incoming | When receiving tokens | Fee charged on mint/transfer to recipient |
| Outgoing | When sending tokens | Fee charged on burn/lock of tokens |
Fee Charging Scheme
User sends 1000 tokens
↓
[Source Network]
Outgoing fee = 1000 × 1% = 10 tokens
Recorded in event: 990 tokens
↓
[Destination Network]
Incoming fee = 990 × 1% = 9.9 tokens
Recipient receives: 980.1 tokensImportant
During TVM→TVM transfers, fee may be charged on both sides:
- Outgoing fee — in the source network
- Incoming fee — in the destination network
Fee Calculation
Formula
fee = amount × numerator / 100000Where:
amount— transfer amount in minimum token unitsnumerator— fee numerator (from 0 to 10000)FEE_DENOMINATOR = 100_000— constant denominator
Constraints
| Parameter | Value |
|---|---|
| Maximum fee | 10% (numerator ≤ 10000) |
| Minimum fee | 0% (numerator = 0) |
| Precision | 0.001% (step = 1/100000) |
| Fee type | Percentage only |
Fee Hierarchy
The system supports flexible fee configuration with priorities.
Application Order

Configuration Levels
1. Fee Not Set
If neither token-specific nor default fee is set — fee equals 0.
2. Default Fee
Applied to all tokens that don't have individual fee set.
- Set via
setTvmDefaultFeeNumerator() - Convenient for bulk configuration
3. Token-specific Fee
Overrides default fee for a specific token.
- Set via
setTvmTokenFee() - Removed via
deleteTvmTokenFee()— after this, token uses default fee
BridgeTokenFee Contract
For accumulating fees, a separate BridgeTokenFee contract is needed for each token.
| Proxy Type | BridgeTokenFee Deploy | Token Address for Derive |
|---|---|---|
| Alien Proxy | Automatic on token creation | Jetton Minter address |
| Native Proxy | Manual before enabling fee | Proxy's Jetton Wallet address |
For Native Tokens
BridgeTokenFee for native tokens is not deployed automatically. The proxy owner must deploy the contract manually before enabling fees.
If BridgeTokenFee is not deployed: fee is calculated and deducted from amount, but not accumulated (lost).
Checking and Deploying BridgeTokenFee
Deriving Contract Address
BridgeTokenFee address is deterministically computed from token address using proxy function:
const bridgeTokenFeeAddress = await proxy.methods
.getExpectedTokenFeeAddress({ token: tokenAddress })
.call();Checking Contract Existence
After getting the address, verify that the contract exists in blockchain:
import { TonClient, Address } from '@ton/ton';
async function isBridgeTokenFeeDeployed(
client: TonClient,
bridgeTokenFeeAddress: string
): Promise<boolean> {
const address = Address.parse(bridgeTokenFeeAddress);
const state = await client.getContractState(address);
// Contract exists if state is not empty and has code
return state.state === 'active';
}
// Usage
const isDeployed = await isBridgeTokenFeeDeployed(client, bridgeTokenFeeAddress);
console.log(`BridgeTokenFee deployed: ${isDeployed}`);Deploying BridgeTokenFee for Native Token
If contract doesn't exist, deploy it via proxy:
import { Address, toNano, beginCell } from '@ton/ton';
async function deployBridgeTokenFee(
client: TonClient,
proxyAddress: string,
jettonWalletAddress: string, // Proxy's Jetton Wallet address
senderWallet: any
): Promise<void> {
const proxy = Address.parse(proxyAddress);
const tokenAddress = Address.parse(jettonWalletAddress);
// Call deployTokenFee on proxy contract
// Requires minimum 0.5 TON for deployment
const payload = beginCell()
.storeUint(0x12345678, 32) // op: deployTokenFee
.storeAddress(tokenAddress)
.endCell();
await senderWallet.sendTransfer({
to: proxy,
value: toNano('0.5'),
body: payload,
});
console.log('deployTokenFee transaction sent');
}Full Example: Check and Deploy
import { TonClient, Address, toNano } from '@ton/ton';
interface BridgeTokenFeeCheckResult {
tokenAddress: string;
bridgeTokenFeeAddress: string;
isDeployed: boolean;
}
async function checkAndDeployBridgeTokenFee(
client: TonClient,
proxyContract: any,
jettonWalletAddress: string,
senderWallet?: any
): Promise<BridgeTokenFeeCheckResult> {
// 1. Derive BridgeTokenFee address
const { value0: bridgeTokenFeeAddress } = await proxyContract.methods
.getExpectedTokenFeeAddress({ token: jettonWalletAddress })
.call();
console.log(`Token address: ${jettonWalletAddress}`);
console.log(`BridgeTokenFee address: ${bridgeTokenFeeAddress}`);
// 2. Check contract existence
const address = Address.parse(bridgeTokenFeeAddress);
const state = await client.getContractState(address);
const isDeployed = state.state === 'active';
console.log(`Contract deployed: ${isDeployed}`);
// 3. Deploy if needed and senderWallet provided
if (!isDeployed && senderWallet) {
console.log('Deploying BridgeTokenFee...');
await proxyContract.methods
.deployTokenFee({ token: jettonWalletAddress })
.send({
from: senderWallet.address,
amount: toNano('0.5'),
});
console.log('Deploy transaction sent. Wait for confirmation...');
}
return {
tokenAddress: jettonWalletAddress,
bridgeTokenFeeAddress,
isDeployed,
};
}
// Usage example
async function main() {
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
});
// Jetton Wallet address belonging to Native Proxy
const proxyJettonWallet = '0:abc123...';
const result = await checkAndDeployBridgeTokenFee(
client,
proxyContract,
proxyJettonWallet
);
if (!result.isDeployed) {
console.warn(
'BridgeTokenFee not deployed! Fee will be lost until deployed.'
);
}
}Recommendation
Always check for BridgeTokenFee existence before setting token fees:
- Get address via
getExpectedTokenFeeAddress() - Check contract state in blockchain
- If not deployed — call
deployTokenFee() - Wait for deployment confirmation
- Only then set fee via
setTvmTokenFee()
Fee Management
Access Rights
All fee management functions are available only to contract owner (owner).
Setting Default Fee
setTvmDefaultFeeNumerator(incoming, outgoing)| Parameter | Description |
|---|---|
incoming | Numerator for incoming transfers (0-10000) |
outgoing | Numerator for outgoing transfers (0-10000) |
Example: incoming=1000, outgoing=500 means 1% on incoming and 0.5% on outgoing.
Setting Token Fee
setTvmTokenFee(token, incoming, outgoing)| Parameter | Description |
|---|---|
token | Token address |
incoming | Incoming numerator (0-10000) |
outgoing | Outgoing numerator (0-10000) |
Important — Token Address
- For Native Proxy: proxy's Jetton Wallet address (token wallet address owned by proxy)
- For Alien Proxy: Jetton Minter address (token root contract, not merged)
Withdrawing Accumulated Fee
withdrawTokenFee(token, recipient)Contract Reference
Read Functions (public view)
| Function | Description |
|---|---|
getTvmDefaultFee() | Get default fee |
getTvmFees() | Get all token-specific fees |
getTvmTokenFee(token) | Get fee for specific token |
getExpectedTokenFeeAddress(token) | Get BridgeTokenFee contract address |
Management Functions (onlyOwner)
| Function | Description |
|---|---|
setTvmDefaultFeeNumerator(incoming, outgoing) | Set default fee |
setTvmTokenFee(token, incoming, outgoing) | Set token fee |
deleteTvmTokenFee(token) | Delete token fee |
withdrawTokenFee(token, recipient) | Withdraw accumulated fees |
Events
| Event | When Emitted |
|---|---|
IncomingFeeTaken(fee, token, msgHash) | When incoming fee is charged |
OutgoingFeeTaken(fee, token) | When outgoing fee is charged |
Examples
Transfer with 10% Fee
Settings:
- Incoming fee: 10% (numerator = 10000)
- Outgoing fee: 10% (numerator = 10000)
Calculation for Native → Alien transfer of 1000 tokens:
| Stage | Calculation | Result |
|---|---|---|
| Sent | — | 1000 tokens |
| Outgoing fee (network A) | 1000 × 10% | 100 tokens |
| In event | 1000 - 100 | 900 tokens |
| Incoming fee (network B) | 900 × 10% | 90 tokens |
| Received | 900 - 90 | 810 tokens |
Disabling Fee for Token
If you need to disable fee for a specific token when default fee exists:
- Set
setTvmTokenFee(token, 0, 0) - Token will use zero fee, ignoring default
Errors and Edge Cases
Error Table
| Code | Name | Description |
|---|---|---|
| — | — | Numerator > 10000 → fee not set (silent ignore) |
| 1000 | NOT_OWNER | Calling onlyOwner function not from owner |
| 2713 | LOW_MSG_VALUE | Insufficient gas for deployTokenFee |
Edge Cases
Fee Greater Than Transfer Amount
Theoretically impossible due to 10% limit. Even at maximum settings (10% + 10%) recipient gets minimum 81% of amount.
BridgeTokenFee Not Deployed
If BridgeTokenFee contract doesn't exist:
- Fee is still deducted from transfer amount
accumulateFee()call goes withbounce: false- Transaction won't revert, but fee will be lost
Recommendation
Always deploy BridgeTokenFee before enabling token fees.