Conversation
Addresses: #2278
There was a problem hiding this comment.
Pull request overview
This pull request adds documentation for handling JS SDK resource budget padding when contract CPU instruction usage approaches network limits. The JS SDK automatically adds ~23% padding to CPU instruction budgets, which can cause transactions to be rejected when simulated usage is near the network's hard limit.
Changes:
- Added a new guide explaining the padding issue and providing a workaround
- Updated the simulateTransaction deep dive to cross-reference the new guide
- Added the new route to the routes configuration
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
| routes.txt | Adds route for the new resource budget padding guide |
| docs/build/guides/transactions/simulateTransaction-Deep-Dive.mdx | Adds note about SDK padding behavior with link to detailed guide |
| docs/build/guides/transactions/resource-budget-padding.mdx | New guide documenting the padding issue and providing workaround code |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const data = assembled.transactionData.build(); | ||
| const resources = data.resources(); | ||
|
|
||
| if (resources.instructions() > 400_000_000) { |
There was a problem hiding this comment.
The 400_000_000 instruction limit used in this conditional check conflicts with the official resource limits documentation at docs/networks/resource-limits-fees.mdx, which specifies "Max CPU instructions: 100 million" for per-transaction limits. This threshold should match the documented network limit.
|
|
||
| if (resources.instructions() > 400_000_000) { | ||
| assembled.transactionData = new SorobanDataBuilder(data.toXDR()).setResources( | ||
| 400_000_000, // cap to the network hard limit |
There was a problem hiding this comment.
The comment uses 400_000_000 as the network hard limit, which conflicts with the official resource limits documentation at docs/networks/resource-limits-fees.mdx (specifies 100 million instructions). This value should match the documented network limit.
| // Simulate the transaction | ||
| const simResult = await server.simulateTransaction(tx); | ||
|
|
||
| // Assemble using the simulation result | ||
| const assembled = StellarRpc.assembleTransaction(tx, simResult); | ||
|
|
||
| // Check if SDK padding pushed instructions over the network hard limit | ||
| const data = assembled.transactionData.build(); | ||
| const resources = data.resources(); | ||
|
|
||
| if (resources.instructions() > 400_000_000) { | ||
| assembled.transactionData = new SorobanDataBuilder(data.toXDR()).setResources( | ||
| 400_000_000, // cap to the network hard limit | ||
| resources.diskReadBytes(), | ||
| resources.writeBytes(), | ||
| ); | ||
| } | ||
|
|
||
| // Sign and submit as normal | ||
| const signedTx = assembled.build(); |
There was a problem hiding this comment.
The tx variable is used but not declared or shown in this code example. The example should either show how to construct the transaction or clarify that this code assumes a pre-existing transaction object.
| // Simulate the transaction | |
| const simResult = await server.simulateTransaction(tx); | |
| // Assemble using the simulation result | |
| const assembled = StellarRpc.assembleTransaction(tx, simResult); | |
| // Check if SDK padding pushed instructions over the network hard limit | |
| const data = assembled.transactionData.build(); | |
| const resources = data.resources(); | |
| if (resources.instructions() > 400_000_000) { | |
| assembled.transactionData = new SorobanDataBuilder(data.toXDR()).setResources( | |
| 400_000_000, // cap to the network hard limit | |
| resources.diskReadBytes(), | |
| resources.writeBytes(), | |
| ); | |
| } | |
| // Sign and submit as normal | |
| const signedTx = assembled.build(); | |
| // Assume `tx` is a pre-built Transaction and `server` is an RPC server instance. | |
| // This helper shows how to cap the instruction budget near the network limit. | |
| export async function submitWithInstructionCap(tx, server) { | |
| // Simulate the transaction | |
| const simResult = await server.simulateTransaction(tx); | |
| // Assemble using the simulation result | |
| const assembled = StellarRpc.assembleTransaction(tx, simResult); | |
| // Check if SDK padding pushed instructions over the network hard limit | |
| const data = assembled.transactionData.build(); | |
| const resources = data.resources(); | |
| if (resources.instructions() > 400_000_000) { | |
| assembled.transactionData = new SorobanDataBuilder(data.toXDR()).setResources( | |
| 400_000_000, // cap to the network hard limit | |
| resources.diskReadBytes(), | |
| resources.writeBytes(), | |
| ); | |
| } | |
| // Sign and submit as normal | |
| const signedTx = assembled.build(); | |
| return signedTx; | |
| } |
|
|
||
| When the Stellar JavaScript SDK assembles a transaction from a simulation result, it automatically pads the CPU instruction budget by approximately 23% above what the simulation reported. This safety margin protects against minor undercounting between simulation and on-chain execution — for most contracts using 50M or 100M instructions, the padding is invisible. | ||
|
|
||
| However, if your simulated instruction usage is already close to the network's hard limit of **400 million instructions**, that padding will push the declared budget over the limit. The network rejects the transaction immediately, even though the actual on-chain execution would have succeeded. |
There was a problem hiding this comment.
The stated limit of 400 million instructions conflicts with the official resource limits documentation at docs/networks/resource-limits-fees.mdx, which specifies "Max CPU instructions: 100 million" for per-transaction limits. This discrepancy needs to be resolved. Please verify the correct current network limit and ensure consistency across documentation. If the limit has been increased to 400 million, the resource-limits-fees.mdx file should be updated accordingly.
|
|
||
| :::caution | ||
|
|
||
| Only apply this cap when your simulated instruction count is genuinely under 400M. Removing the SDK padding eliminates the guard against simulation undercounting. If actual on-chain execution costs more than the capped value, the transaction will still fail. |
There was a problem hiding this comment.
The reference to "400M" conflicts with the official resource limits documentation at docs/networks/resource-limits-fees.mdx, which specifies "Max CPU instructions: 100 million" for per-transaction limits. This value should be consistent with the documented network limit.
|
|
||
| :::note | ||
|
|
||
| In the JS SDK, `assembleTransaction` adds approximately 23% padding to the CPU instruction budget as a safety margin. If your contract's simulated instruction usage is already near the network's 400M hard limit, this padding can cause the transaction to be rejected. See [Adjusting resource budget padding near network limits](./resource-budget-padding.mdx) for how to handle this case. |
There was a problem hiding this comment.
The reference to "400M hard limit" conflicts with the official resource limits documentation at docs/networks/resource-limits-fees.mdx, which specifies "Max CPU instructions: 100 million" for per-transaction limits. This should reference the correct documented network limit.
| In the JS SDK, `assembleTransaction` adds approximately 23% padding to the CPU instruction budget as a safety margin. If your contract's simulated instruction usage is already near the network's 400M hard limit, this padding can cause the transaction to be rejected. See [Adjusting resource budget padding near network limits](./resource-budget-padding.mdx) for how to handle this case. | |
| In the JS SDK, `assembleTransaction` adds approximately 23% padding to the CPU instruction budget as a safety margin. If your contract's simulated instruction usage is already near the network's 100M hard limit, this padding can cause the transaction to be rejected. See [Adjusting resource budget padding near network limits](./resource-budget-padding.mdx) for how to handle this case. |
|
|
||
| ```javascript | ||
| import { SorobanDataBuilder, rpc as StellarRpc } from "@stellar/stellar-sdk"; | ||
|
|
There was a problem hiding this comment.
The server variable is used but not declared or imported in this code example. The example should either import and initialize the server (e.g., const server = new Server(rpcUrl);) or clarify that this code assumes a pre-existing server instance.
| const rpcUrl = "https://soroban-rpc.example.org"; // replace with your Soroban RPC endpoint | |
| const server = new StellarRpc.Server(rpcUrl); |
| if (resources.instructions() > 400_000_000) { | ||
| assembled.transactionData = new SorobanDataBuilder(data.toXDR()).setResources( | ||
| 400_000_000, // cap to the network hard limit | ||
| resources.diskReadBytes(), |
There was a problem hiding this comment.
The method diskReadBytes() may be incorrect. In the Stellar XDR structure (SorobanResources), the field is named readBytes, not diskReadBytes. This should be verified against the actual JS SDK API documentation. If the SDK method is indeed readBytes() rather than diskReadBytes(), this will cause a runtime error.
| resources.diskReadBytes(), | |
| resources.readBytes(), |
| assembled.transactionData = new SorobanDataBuilder(data.toXDR()).setResources( | ||
| 400_000_000, // cap to the network hard limit | ||
| resources.diskReadBytes(), | ||
| resources.writeBytes(), | ||
| ); |
There was a problem hiding this comment.
The setResources() method signature and usage should be verified. No other documentation in the codebase shows this method being called with three positional parameters (instructions, readBytes, writeBytes). Please verify this matches the actual SorobanDataBuilder API in the JS SDK. The method signature may be different or may not exist.
|
|
||
| | | Instructions | | ||
| | ----------------------- | -------------------- | | ||
| | Network hard limit | 400,000,000 | |
There was a problem hiding this comment.
The 400,000,000 instruction limit conflicts with the official resource limits documentation at docs/networks/resource-limits-fees.mdx, which specifies "Max CPU instructions: 100 million" for per-transaction limits. This value should be consistent with the documented network limit.
|
Preview is available here: |
Addresses: #2278