|
| 1 | +--- |
| 2 | +title: "Stellar Fee Estimation & Budgeting" |
| 3 | +description: "A comprehensive guide to Stellar classic operation fees, Soroban resource fees, fee-bump transaction sponsorship, and budgeting heuristics for Wraith applications." |
| 4 | +--- |
| 5 | + |
| 6 | +Predictable budgeting on Stellar requires understanding both its classic payment fees and its multi-dimensional Soroban resource pricing. This guide explains how fees are calculated, outlines typical baseline costs for Wraith smart contract operations, and details how to estimate and budget for production-ready applications. |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## The Stellar Fee Model |
| 11 | + |
| 12 | +Stellar uses a hybrid fee structure consisting of two distinct layers: **Inclusion Fees** (classic operation fees) and **Resource Fees** (Soroban-specific execution and storage fees). |
| 13 | + |
| 14 | +### 1. Classic Operation Fees |
| 15 | + |
| 16 | +For non-Soroban operations (such as native XLM payments or creating accounts) and as a baseline inclusion fee for all transactions, Stellar charges a base fee per operation: |
| 17 | +* **Base Fee**: Defined by the network (currently `100 stroops` or `0.00001 XLM` per operation; $1 \text{ XLM} = 10,000,000 \text{ stroops}$). |
| 18 | +* **Formula**: $\text{Inclusion Fee} = \text{Base Fee} \times \text{Operation Count}$ |
| 19 | +* **Payment**: Deducted from the transaction source account's balance. |
| 20 | + |
| 21 | +### 2. Soroban Resource Fees |
| 22 | + |
| 23 | +Soroban smart contracts introduce multi-dimensional resource metering to prevent execution bottlenecks. Instead of a single gas price, Soroban tracks resource usage across several distinct dimensions: |
| 24 | + |
| 25 | +* **CPU Instructions**: Modeled CPU instructions executed by the WebAssembly (Wasm) runtime. |
| 26 | +* **Memory Bytes**: Peak RAM utilized during transaction execution. |
| 27 | +* **Ledger Read Entries**: The number of distinct ledger entries read from the database. |
| 28 | +* **Ledger Write Entries**: The number of distinct ledger entries created or modified. |
| 29 | +* **Ledger Read Bytes**: The total serialized size in bytes of all ledger entries read. |
| 30 | +* **Ledger Write Bytes**: The total serialized size in bytes of all ledger entries written. |
| 31 | +* **Event Bytes**: The serialized size of contract events emitted during execution. |
| 32 | + |
| 33 | +Soroban uses these metrics to compute a fee in stroops based on current network parameters. |
| 34 | + |
| 35 | +<Info> |
| 36 | + Transactions containing Soroban contract invocations must submit an inclusion fee *plus* the calculated Soroban resource fee. |
| 37 | +</Info> |
| 38 | + |
| 39 | +--- |
| 40 | + |
| 41 | +## Network Surge Pricing & Congestion |
| 42 | + |
| 43 | +During high-traffic periods, if the network capacity is exceeded, Stellar enters **surge pricing**: |
| 44 | +* **Classic Surge**: Transactions are prioritized based on their `fee` (inclusion fee bidding). Transactions with higher inclusion fees are processed first. |
| 45 | +* **Soroban Surge**: Soroban enforces separate per-ledger resource limits (e.g., maximum instructions or write bytes per ledger). If a transaction's resource demands exceed limits, or if resource contention occurs, transactions undergo a resource fee auction. |
| 46 | +* **Mitigation**: Production applications should dynamically adjust the base fee bid multiplier during periods of high congestion. |
| 47 | + |
| 48 | +--- |
| 49 | + |
| 50 | +## Fee-Bump Transactions & Sponsorship |
| 51 | + |
| 52 | +UX is critical for privacy applications. To spare users from having to hold native XLM to pay for stealth transaction fees, developers can use **Fee-Bump Transactions**: |
| 53 | + |
| 54 | +* **Mechanics**: A fee-bump transaction wraps an inner transaction. The outer fee-bump envelope is signed by a sponsor (the fee-bump payer), while the inner transaction is signed by the user. |
| 55 | +* **Who Pays**: The sponsor paying the fee-bump pays the entire transaction fee (including both the inclusion fee and any Soroban resource fees). |
| 56 | +* **Stealth Use Case**: This allows users with a newly derived stealth address (which contains no XLM) to spend their incoming stealth tokens immediately, sponsored by a relayer. The relayer can recoup the fee in tokens off-chain or via a deduction in the transfer payload. |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +## Per-Wraith-Operation Resource Baselines |
| 61 | + |
| 62 | +Below are the audited, post-optimization resource baselines for the Wraith smart contract suite on Stellar. These numbers are captured from the standard benchmark tests (`stellar/bench` running in `soroban-sdk = 22.0.0` environment). |
| 63 | + |
| 64 | +| Contract | Function / Entry Point | Parameters | CPU Instructions | Memory (RAM) | Read Entries | Write Entries | Read Bytes | Write Bytes | Event Bytes | |
| 65 | +|---|---|---|---:|---:|---:|---:|---:|---:|---:| |
| 66 | +| **stealth-announcer** | `announce` | `metadata_len=32` | 15,458 | 1,666 B | 1 | 0 | 104 B | 0 B | 248 B | |
| 67 | +| **stealth-registry** | `register_keys` | First-time | 33,345 | 4,461 B | 1 | 2 | 104 B | 332 B | 188 B | |
| 68 | +| **stealth-sender** | `send` | Asset: XLM | 182,403 | 28,137 B | 5 | 3 | 1,068 B | 520 B | 484 B | |
| 69 | +| **stealth-sender** | `batch_send` | Batch size: 5 | 807,519 | 120,229 B | 5 | 7 | 1,068 B | 1,416 B | 2,420 B | |
| 70 | +| **stealth-sender** | `batch_send` | Batch size: 10 | 1,633,634 | 245,649 B | 5 | 12 | 1,068 B | 2,536 B | 4,840 B | |
| 71 | +| **wraith-names** | `register` | `name_len=3` | 59,792 | 6,240 B | 1 | 2 | 104 B | 516 B | 204 B | |
| 72 | +| **wraith-names** | `resolve` | Hit | 46,096 | 5,456 B | 1 | 0 | 452 B | 0 B | 0 B | |
| 73 | +| **wraith-names** | `name_of` | Hit | 47,042 | 5,383 B | 1 | 0 | 452 B | 0 B | 0 B | |
| 74 | + |
| 75 | +### Storage Rent Costs |
| 76 | + |
| 77 | +Soroban contracts pay rent for persistent ledger entries based on their serialized size. |
| 78 | +* **Sizes**: A registered stealth meta-address costs ~176 bytes. A name mapping costs ~224 bytes, and its reverse lookup maps to ~136 bytes. |
| 79 | +* **Protocol 23 Low-Fee Model**: At `fee_write_1kb = 3,500 stroops` and `persistentRentRateDenominator = 1,402`, the annual storage rate is **~0.001538 XLM per byte**. |
| 80 | +* **Impact**: Registering a name (mapping + reverse) totals 360 bytes of storage, costing approximately **0.00055 XLM per year** in storage rent. |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +## Worked Cost Scenarios |
| 85 | + |
| 86 | +These examples demonstrate how specific workflows translate to real-world costs on the Stellar network (assuming typical testnet parameters). |
| 87 | + |
| 88 | +### Scenario A: Sending a Stealth Payment |
| 89 | + |
| 90 | +A user sends a payment to a recipient's stealth address. This requires a classic `createAccount` operation (or `payment` for pre-existing accounts) and a Soroban invocation of `stealth-sender::send` to emit the stealth announcement event. |
| 91 | + |
| 92 | +* **Inclusion Fees**: 2 operations (create account + contract invoke) = `200 stroops`. |
| 93 | +* **Soroban Resource Fees**: ~35,000 stroops (based on 182,403 instructions, 5 reads, and 3 writes). |
| 94 | +* **Total Cost**: **~35,200 stroops (~0.00352 XLM)**. |
| 95 | + |
| 96 | +### Scenario B: Withdrawing Stealth Funds |
| 97 | + |
| 98 | +A recipient checks for incoming payments, matches their view tags, and withdraws the balance to their hot wallet. Because the stealth address is a standard keypair, the withdrawal is a simple, classic payment transaction. |
| 99 | + |
| 100 | +* **Operations**: 1 payment operation. |
| 101 | +* **Soroban Invocations**: None (pure classic transaction). |
| 102 | +* **Total Cost**: **100 stroops (0.00001 XLM)**. |
| 103 | + |
| 104 | +### Scenario C: Batch Sending (10 Recipients) |
| 105 | + |
| 106 | +An application distributes payroll to 10 stealth addresses. Calling `stealth-sender::batch_send` processes the payments atomically and groups event announcements. |
| 107 | + |
| 108 | +* **Inclusion Fees**: 11 operations (10 payments/creates + 1 contract invoke) = `1,100 stroops`. |
| 109 | +* **Soroban Resource Fees**: ~110,000 stroops (based on 1,633,634 instructions and 12 write entries). |
| 110 | +* **Total Cost**: **~111,100 stroops (~0.01111 XLM)**. |
| 111 | +* **Savings**: Batching reduces CPU instructions and read entries significantly, saving over **60%** in fees compared to 10 separate transactions. |
| 112 | + |
| 113 | +--- |
| 114 | + |
| 115 | +## SDK Fee Estimation Helper |
| 116 | + |
| 117 | +The `@stellar/stellar-sdk` library provides the `prepareTransaction` helper to simulate contract invocations and automatically calculate the necessary Soroban resource budgets and transaction fees. |
| 118 | + |
| 119 | +```typescript |
| 120 | +import { Account, TransactionBuilder, rpc, Operation } from "@stellar/stellar-sdk"; |
| 121 | + |
| 122 | +// Initialize Soroban RPC Server |
| 123 | +const rpcServer = new rpc.Server("https://soroban-testnet.stellar.org"); |
| 124 | + |
| 125 | +async function estimateFees(sourcePublicKey: string, contractId: string) { |
| 126 | + // 1. Fetch account sequence number |
| 127 | + const account = await rpcServer.getAccount(sourcePublicKey); |
| 128 | + |
| 129 | + // 2. Build the preliminary transaction |
| 130 | + const tx = new TransactionBuilder(account, { |
| 131 | + fee: "100", // Start with network minimum base fee |
| 132 | + networkPassphrase: "Test SDF Network ; September 2015", |
| 133 | + }) |
| 134 | + .addOperation( |
| 135 | + Operation.invokeContractFunction({ |
| 136 | + contract: contractId, |
| 137 | + function: "announce", |
| 138 | + args: [], // Add arguments as XDR ScVal structures |
| 139 | + }) |
| 140 | + ) |
| 141 | + .build(); |
| 142 | + |
| 143 | + // 3. Simulate and prepare transaction |
| 144 | + console.log("Simulating transaction on-chain..."); |
| 145 | + const preparedTx = await rpcServer.prepareTransaction(tx); |
| 146 | + |
| 147 | + // 4. Extract calculated fee components |
| 148 | + const totalFee = preparedTx.fee; // Combined inclusion + resource fee (in stroops) |
| 149 | + const resourceFee = preparedTx.sorobanData.resources().fee().toString(); |
| 150 | + const inclusionFee = (BigInt(totalFee) - BigInt(resourceFee)).toString(); |
| 151 | + |
| 152 | + console.log(`Inclusion Fee: ${inclusionFee} stroops (${Number(inclusionFee) / 1e7} XLM)`); |
| 153 | + console.log(`Resource Fee: ${resourceFee} stroops (${Number(resourceFee) / 1e7} XLM)`); |
| 154 | + console.log(`Total Fee: ${totalFee} stroops (${Number(totalFee) / 1e7} XLM)`); |
| 155 | + |
| 156 | + return preparedTx; |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## Budgeting Heuristics for Production |
| 163 | + |
| 164 | +To ensure smooth operations in production apps, implement these heuristics: |
| 165 | + |
| 166 | +1. **Keep a 5 XLM Fee Buffer**: Newly derived stealth accounts should keep a tiny amount of XLM (typically 5 XLM) reserved for fee bids and the minimum ledger reserve (1 XLM base account reserve + 0.5 XLM per sub-entry or trustline). |
| 167 | +2. **Prioritize Batching**: Always batch transfers when sending payments to multiple recipients. Calling `batch_send` reduces transaction fee costs and cuts ledger read overhead. |
| 168 | +3. **Set a Surge Pricing Margin**: In production, configure your fee estimator to add a margin (e.g., 20% to 50%) to the suggested base fee during network congestion to prevent transactions from getting stuck in the transaction queue. |
| 169 | +4. **Account for Rent Renewal**: For stateful registries, monitor the Time-to-Live (TTL) of storage entries. Implement automated routines to call the `extend_ttl` endpoint on contracts to prevent crucial records (like registrant meta-addresses) from being archived. |
0 commit comments