|
| 1 | +# Chain-Specific Fee Collection |
| 2 | + |
| 3 | +This document explains how to collect extra fees for specific chain paths (e.g., to/from Reya) using existing Socket DL contract parameters without any code changes. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Socket DL allows collecting additional fees for specific chains by adjusting existing fee parameters. This is useful for: |
| 8 | + |
| 9 | +- Charging premium fees for specific destination chains |
| 10 | +- Collecting protocol fees for messages from specific source chains |
| 11 | +- Implementing tiered pricing based on chain pairs |
| 12 | + |
| 13 | +## Recommended Parameter: `transmissionFees` (with single-message packets) |
| 14 | + |
| 15 | +If packets always contain exactly one message (`maxPacketLength = 1`), then transmission fees become per-message. That makes `transmissionFees` the cleanest mechanism for a flat surcharge with separate accounting. |
| 16 | + |
| 17 | +### Why `transmissionFees` is Best (when `maxPacketLength = 1`) |
| 18 | + |
| 19 | +| Criteria | overhead | verificationOverheadFees | transmissionFees | |
| 20 | +| -------------------- | ------------------ | ------------------------ | ----------------------------------- | |
| 21 | +| Per-message | ✅ Yes | ✅ Yes | ✅ Yes (with `maxPacketLength = 1`) | |
| 22 | +| Per-destination | ✅ Yes | ✅ Yes | ✅ Yes | |
| 23 | +| Easy to update | ⚠️ Struct | ⚠️ Two params | ✅ Single value | |
| 24 | +| Separate accounting | ❌ Mixed with exec | ❌ Mixed with exec | ✅ Separate bucket | |
| 25 | +| Batch update support | ✅ SocketBatcher | ❌ No batcher | ❌ No batcher | |
| 26 | + |
| 27 | +### How `transmissionFees` Works |
| 28 | + |
| 29 | +**Storage** ([ExecutionManagerDF.sol:166](../contracts/ExecutionManagerDF.sol#L166)): |
| 30 | + |
| 31 | +```solidity |
| 32 | +mapping(address => mapping(uint32 => uint128)) public transmissionMinFees; |
| 33 | +``` |
| 34 | + |
| 35 | +**Fee Calculation** ([ExecutionManagerDF.sol:350-358](../contracts/ExecutionManagerDF.sol#L350-L358)): |
| 36 | + |
| 37 | +```solidity |
| 38 | +transmissionFees = |
| 39 | + transmissionMinFees[transmitManager_][siblingChainSlug_] / maxPacketLength_; |
| 40 | +``` |
| 41 | + |
| 42 | +With `maxPacketLength = 1`, `transmissionFees` equals the per-message surcharge. |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +## Implementation Guide |
| 47 | + |
| 48 | +### Scenario: Charge $2 Extra for Messages TO and FROM Reya |
| 49 | + |
| 50 | +#### 1. Messages TO Reya (Inbound to Reya) |
| 51 | + |
| 52 | +On **each source chain's** `TransmitManager`, increase `transmissionFees` for Reya's chainSlug: |
| 53 | + |
| 54 | +```solidity |
| 55 | +// Example: On Ethereum's TransmitManager |
| 56 | +TransmitManager.setTransmissionFees( |
| 57 | + nonce, |
| 58 | + REYA_CHAIN_SLUG, // 1324967486 |
| 59 | + currentTransmissionFees + surchargeInSourceNativeToken, |
| 60 | + signature |
| 61 | +); |
| 62 | +``` |
| 63 | + |
| 64 | +**Off-chain calculation**: `surcharge = $2 / sourceChainNativeTokenPriceUSD` |
| 65 | + |
| 66 | +Example: If ETH = $2500, surcharge = 0.0008 ETH = 800000000000000 wei |
| 67 | + |
| 68 | +#### 2. Messages FROM Reya (Outbound from Reya) |
| 69 | + |
| 70 | +On **Reya's** `TransmitManager`, increase `transmissionFees` for ALL destination chains: |
| 71 | + |
| 72 | +```solidity |
| 73 | +// On Reya's TransmitManager |
| 74 | +TransmitManager.setTransmissionFees( |
| 75 | + nonce, |
| 76 | + DESTINATION_CHAIN_SLUG, // e.g., Ethereum = 1 |
| 77 | + currentTransmissionFees + surchargeInReyaNativeToken, |
| 78 | + signature |
| 79 | +); |
| 80 | +``` |
| 81 | + |
| 82 | +This must be done for each destination chain that Reya can send to. |
| 83 | + |
| 84 | +--- |
| 85 | + |
| 86 | +## Fee Flow Visualization |
| 87 | + |
| 88 | +``` |
| 89 | +┌─────────────────────────────────────────────────────────────────┐ |
| 90 | +│ TO DESTINATION CHAIN (surcharge) │ |
| 91 | +├─────────────────────────────────────────────────────────────────┤ |
| 92 | +│ │ |
| 93 | +│ Source Chain A ──┐ │ |
| 94 | +│ Source Chain B ──┼──► transmissionFees[DEST_SLUG] += $X ─► Dest│ |
| 95 | +│ Source Chain C ──┘ (set on each source chain) │ |
| 96 | +│ │ |
| 97 | +└─────────────────────────────────────────────────────────────────┘ |
| 98 | +
|
| 99 | +┌─────────────────────────────────────────────────────────────────┐ |
| 100 | +│ FROM SOURCE CHAIN (surcharge) │ |
| 101 | +├─────────────────────────────────────────────────────────────────┤ |
| 102 | +│ │ |
| 103 | +│ transmissionFees[DEST_A] += $X ──► Dest A │ |
| 104 | +│ Source ─► transmissionFees[DEST_B] += $X ──► Dest B │ |
| 105 | +│ transmissionFees[DEST_C] += $X ──► Dest C │ |
| 106 | +│ (set on source chain's TransmitManager) │ |
| 107 | +│ │ |
| 108 | +└─────────────────────────────────────────────────────────────────┘ |
| 109 | +``` |
| 110 | + |
| 111 | +--- |
| 112 | + |
| 113 | +## Batch Updates |
| 114 | + |
| 115 | +There is no batch setter for `transmissionFees`. Updates must be sent per destination chain. |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## Setter Function Details |
| 120 | + |
| 121 | +### TransmitManager.setTransmissionFees() |
| 122 | + |
| 123 | +**Location**: [contracts/TransmitManager.sol:94-126](../contracts/TransmitManager.sol#L94-L126) |
| 124 | + |
| 125 | +```solidity |
| 126 | +function setTransmissionFees( |
| 127 | + uint256 nonce_, |
| 128 | + uint32 siblingChainSlug_, |
| 129 | + uint128 transmissionFees_, |
| 130 | + bytes calldata signature_ |
| 131 | +) external |
| 132 | +``` |
| 133 | + |
| 134 | +**Parameters**: |
| 135 | + |
| 136 | +- `nonce_`: Incrementing nonce for the fee updater (replay protection) |
| 137 | +- `siblingChainSlug_`: Destination chain identifier |
| 138 | +- `transmissionFees_`: Fee per packet (equals per-message when `maxPacketLength = 1`) |
| 139 | +- `signature_`: Signature from `FEES_UPDATER_ROLE` holder |
| 140 | + |
| 141 | +**Signature Digest**: |
| 142 | + |
| 143 | +```solidity |
| 144 | +keccak256(abi.encode( |
| 145 | + FEES_UPDATE_SIG_IDENTIFIER, |
| 146 | + address(this), // TransmitManager address |
| 147 | + chainSlug, // Source chain slug |
| 148 | + siblingChainSlug_, // Destination chain slug |
| 149 | + nonce_, |
| 150 | + transmissionFees_ |
| 151 | +)) |
| 152 | +``` |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +## Where Surcharge Fees Go |
| 157 | + |
| 158 | +The surcharge added via `transmissionFees` becomes part of **transmission fees**: |
| 159 | + |
| 160 | +| Aspect | Details | |
| 161 | +| ---------- | -------------------------------------------------------------------- | |
| 162 | +| Storage | `totalExecutionAndTransmissionFees[chainSlug].totalTransmissionFees` | |
| 163 | +| Withdrawal | `withdrawTransmissionFees(chainSlug, amount)` | |
| 164 | +| Access | Requires `WITHDRAW_ROLE` | |
| 165 | +| Recipient | Managed by `TransmitManager.withdrawFees()` | |
| 166 | + |
| 167 | +**Note**: Surcharge is combined with base transmission fees, but remains in a separate bucket from execution fees. |
| 168 | + |
| 169 | +--- |
| 170 | + |
| 171 | +## Off-Chain Service Requirements |
| 172 | + |
| 173 | +To maintain USD-denominated surcharges, the off-chain service needs to: |
| 174 | + |
| 175 | +1. **Track token prices**: Native token price in USD for each chain |
| 176 | +2. **Calculate surcharge**: `surchargeWei = $USD_AMOUNT / nativeTokenPriceUSD * 1e18` |
| 177 | +3. **Sign updates**: Generate signature using `FEES_UPDATER_ROLE` private key |
| 178 | +4. **Submit transactions**: Call `setTransmissionFees()` per destination chain |
| 179 | +5. **Update frequency**: When token price moves >X% (e.g., 5%) |
| 180 | + |
| 181 | +### Example Calculation |
| 182 | + |
| 183 | +```javascript |
| 184 | +const USD_SURCHARGE = 2; // $2 |
| 185 | +const ethPriceUSD = 2500; |
| 186 | +const surchargeWei = BigInt((USD_SURCHARGE / ethPriceUSD) * 1e18); |
| 187 | +// surchargeWei = 800000000000000n (0.0008 ETH) |
| 188 | +``` |
| 189 | + |
| 190 | +--- |
| 191 | + |
| 192 | +## Alternative: Use verificationOverheadFees |
| 193 | + |
| 194 | +If you want surcharge fees to go to the **switchboard** instead of executors: |
| 195 | + |
| 196 | +```solidity |
| 197 | +FastSwitchboard.setFees( |
| 198 | + nonce, |
| 199 | + DESTINATION_CHAIN_SLUG, |
| 200 | + switchboardFees, // unchanged |
| 201 | + verificationOverheadFees + surchargeAmount, // add surcharge here |
| 202 | + signature |
| 203 | +); |
| 204 | +``` |
| 205 | + |
| 206 | +**Difference**: |
| 207 | + |
| 208 | +- `transmissionFees` → goes to transmission fees bucket → withdrawn by transmit manager |
| 209 | +- `verificationOverheadFees` → goes to switchboard fees bucket → withdrawn by switchboard |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## Limitations |
| 214 | + |
| 215 | +1. **No batch setter**: Must update each destination chain separately |
| 216 | +2. **Multi-chain updates**: TO a chain requires updates on all source chains |
| 217 | +3. **Price volatility**: USD value fluctuates with native token price |
| 218 | +4. **All destinations**: FROM a chain applies surcharge to all destinations equally |
| 219 | + |
| 220 | +--- |
| 221 | + |
| 222 | +## Summary |
| 223 | + |
| 224 | +| Use Case | Where to Set | Parameter | Function | |
| 225 | +| ------------------------- | ----------------- | ------------------------------------- | ----------------------- | |
| 226 | +| Charge extra TO Chain X | All source chains | `transmissionFees` for X's slug | `setTransmissionFees()` | |
| 227 | +| Charge extra FROM Chain Y | Chain Y | `transmissionFees` for all dest slugs | `setTransmissionFees()` | |
| 228 | +| Batch update | Any chain | N/A | N/A | |
| 229 | + |
| 230 | +**Key Files**: |
| 231 | + |
| 232 | +- [contracts/ExecutionManagerDF.sol](../contracts/ExecutionManagerDF.sol) - Fee storage |
| 233 | +- [contracts/TransmitManager.sol](../contracts/TransmitManager.sol) - Transmission fee setters |
| 234 | +- [contracts/interfaces/ITransmitManager.sol](../contracts/interfaces/ITransmitManager.sol) - Interface definitions |
0 commit comments