ByAUJay
Seamless Blockchain API Development: Design Patterns and Testing Strategies
Decision-Makers
This field guide is your go-to resource for shipping reliable and scalable blockchain APIs that your product can trust. We break down the fast-changing protocol updates--like EIP-4844 blobs, ERC-4337, EIP-7702, Solana priority fees, and SWQoS--into clear patterns, detailed test plans, and code snippets that you can implement in CI right now. Check out the specifics at eips.ethereum.org.
Executive summary
To build solid blockchain APIs, you've got to ensure they stay consistent even during reorgs, handle the oddities of multiple clients, adapt to fee market ups and downs, and accommodate new transaction types like blobs and user operations. The quickest way to achieve that “it just works” feeling is through a layered approach: think RPC gateway, read models, async eventing, and thorough simulations of forks, reorgs, and fees in your CI. Check out more details at (eips.ethereum.org).
1) Why blockchain API design is different
- Finality and reorgs are part of the game: When you're working with Ethereum JSON‑RPC, you can tie your reads to a specific block hash and make sure you're getting the right data every time. This is super important for consistency across multiple reads. There are tags like latest, safe, and finalized that cater to different levels of risk. Check out more here.
- Client diversity is key: Geth, Nethermind, and Erigon may all offer the same core methods, but they each have their quirks when it comes to tracing, debugging APIs, and even performance. So, don’t just assume they're all the same in those areas. Make sure to secure your debug endpoints if you’re in a production environment. More info can be found here.
- Fee markets are changing: With the London upgrade (EIP‑1559), we saw a shift in how transactions work--now it's all about maxFeePerGas and maxPriorityFeePerGas, and receipts/blocks include effectiveGasPrice and baseFeePerGas. Coming up on March 13, 2024, Dencun will roll out blob transactions that bring in an independent blob fee market, plus new RPC methods like eth_blobBaseFee. Your API should be able to handle both gas markets! For more details, check this out here.
- New transaction types are shaking things up: ERC‑4337 has brought about UserOperation flows through bundlers, while EIP‑7702 (Set Code for EOAs) aims for compatibility with 4337 and introduces an authorization tuple in transactions. Get ready for some new RPCs and extra steps for validation. For further reading, see this link.
- Other ecosystems have their own tricks: If you’re diving into Solana, you’ll be estimating and attaching priority fees based on account sets. Plus, there are delivery perks to be had through Stake‑weighted QoS (SWQoS) peering. More info can be found here.
2) API design patterns that survive mainnet
2.1 Read consistency: always pin state
- Pattern: When you're handling multi-call reads that need to show a single state, make sure to send a block identifier object that includes
blockHashwith eacheth_call/get*method. Also, don’t forget to setrequireCanonicalto keep things accurate, especially during reorganizations. Here’s how your payload could look:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [
{ "to": "0x…", "data": "0x…" },
{ "blockHash": "0xabc…", "requireCanonical": true }
]
}
This helps prevent drift during sequential reads. Create a helper that makes sure engineers don’t accidentally use "latest" in code paths where consistency really matters. (eips.ethereum.org)
- When dealing with batched front-end dashboards, it's a good idea to use Multicall3 on the chain. This ensures you get consistent results from the same block and cuts down on unnecessary round-trips. Don’t forget to pair it with the block number or timestamp in your return to help spot any stale data. Check it out here: (github.com)
2.2 Write paths: idempotency and replacement semantics
- Think of the idempotency anchor as a nonce; basically, treat every business operation as a “one nonce” deal. If you need to resubmit your transaction under EIP‑1559, just replace the pending transaction using that same nonce and make sure to increase both maxPriorityFeePerGas and maxFeePerGas (a bump of around 10% is usually what nodes/mempools expect). It’s a good idea to create a retry policy based on this tip. (alchemy.com)
- For the internal API layer, don't forget to add an Idempotency‑Key header that’s linked to a durable nonce reservation record. This way, you can reject any duplicate attempts that might mess with the sequential nonces. It’s a solid way to avoid any accidental double-spending above the RPC layer.
2.3 Batching and connection hygiene
- Consider using JSON-RPC 2.0 batching to cut down on those annoying HTTP round-trips and make the most of your TLS/connection overhead. Implement batch chunking and keep track of responses using their IDs; don't just depend on the order in the array. (json-rpc.org)
- It's a good idea to separate your hot read paths (you know, the cached stuff and subgraph/Firehose) from write paths (like RPC). This is basically CQRS for web3: you handle reads from an indexed model and push writes through canonical nodes. (thegraph.com)
2.4 Eventing instead of polling
- Switch out address log polling for webhooks or streams. With providers like Alchemy, you can get updates on mined and dropped transactions plus address activity directly pushed to you. This not only cuts down on RPC costs but also helps you stay in the loop during reorgs. Make sure to use retries and verify the signatures on those webhook payloads. (alchemy.com)
- When it comes to managing pending transactions, it’s a good idea to tap into provider-specific pending streams (like alchemy_pendingTransactions) and mix that data with your node’s mempool view to get the best coverage possible. (alchemy.com)
2.5 Observability from day one
- To get your client metrics exported, just enable Geth’s Prometheus endpoint (-metrics, /debug/metrics/prometheus) and pump that data into your observability stack. Plus, if you're using Nethermind, you’ll find that it comes with Prometheus/Grafana dashboards right out of the box. Keep an eye on p95 eth_call latency, track error rates by method, and monitor those pesky reorg-induced rollbacks. (geth.ethereum.org)
- Don't forget to emit OpenTelemetry spans for your external RPC and webhook processing! This is super helpful for connecting an on-chain write with the event that confirms it. The good news? Prometheus now natively supports OTLP HTTP ingestion, so you can easily wire your OTel SDKs to Prom via the OTLP receiver. (prometheus.io)
3) Platform‑specific integration details that matter
3.1 Ethereum after Dencun (EIP‑4844)
- Your API should be able to identify blob transactions (type 0x03) and handle fields like max_fee_per_blob_gas and blob_versioned_hashes. Remember, “blob gas” has its own base fee schedule that's separate from the usual gas fees. You'll want to include blob fee estimation (eth_blobBaseFee) along with eth_feeHistory for standard gas. Check out more details on this here.
- When it comes to your fee strategy, keep in mind that if blobs get congested, your regular gas estimator might not capture the blob base fee trends accurately. Think of blob and normal gas as two different controls you can tweak. Make sure to have a separate surge policy in place and set up alerts for “blob capacity exhausted” situations that come from your builder/relay telemetry. You can dive deeper into this topic here.
3.2 Account abstraction in practice (ERC‑4337 + EIP‑7702)
- If you're all about smart accounts, you'll want to include those bundler RPCs:
eth_sendUserOperation,eth_estimateUserOperationGas,eth_getUserOperationReceipt, andeth_supportedEntryPoints. Make sure to simulate validation before you enqueue anything, and if something goes wrong, make sure the client gets the lowdown on preVerificationGas and verificationGasLimit. Check out more details here. - EIP‑7702 is pretty interesting because it lets EOAs attach authorization tuples, which means they can behave like contracts either temporarily or permanently, depending on how the spec evolves. The latest drafts are aiming for compatibility with 4337 EntryPoint v0.8, which includes an optional
eip7702Authfield forsendUserOperation. Just a heads up to audit any flows that depend onmsg.sender == tx.origin. You can dive deeper here.
Code: Sending a UserOperation with Gas Estimation
To send a UserOperation with gas estimation, you'll want to follow these steps. First, ensure you've got the necessary libraries imported and your environment set up. Here’s a simple example to get you rolling:
import { ethers } from "ethers";
import { YourSmartContract } from "./YourSmartContract";
// Initialize provider and signer
const provider = new ethers.providers.JsonRpcProvider("YOUR_RPC_URL");
const wallet = new ethers.Wallet("YOUR_PRIVATE_KEY", provider);
const contract = new YourSmartContract(wallet);
// Create a UserOperation object
const userOperation = {
target: contract.address,
value: ethers.utils.parseEther("0.01"), // Amount of ether to send
data: contract.interface.encodeFunctionData("yourFunction", [/* parameters */]),
gasLimit: ethers.utils.hexlify(21000), // Initial gas limit
};
// Estimate gas
async function estimateGas() {
try {
const estimatedGas = await provider.estimateGas(userOperation);
console.log(`Estimated gas: ${estimatedGas.toString()}`);
userOperation.gasLimit = estimatedGas;
} catch (error) {
console.error("Error estimating gas: ", error);
}
}
// Send the UserOperation
async function sendUserOperation() {
await estimateGas();
try {
const tx = await wallet.sendTransaction(userOperation);
console.log(`Transaction hash: ${tx.hash}`);
// Wait for the transaction to be confirmed
await tx.wait();
console.log("Transaction confirmed!");
} catch (error) {
console.error("Error sending UserOperation: ", error);
}
}
// Execute the function to send the UserOperation
sendUserOperation();
Quick Breakdown:
- Provider Setup: It all starts with setting up your provider and wallet. Make sure to replace
YOUR_RPC_URLandYOUR_PRIVATE_KEYwith your actual RPC URL and wallet's private key. - Creating UserOperation: The
userOperationobject is where you'll define the target, value, data, and starting gas limit. - Gas Estimation: This is crucial! Always estimate the gas before sending your transaction to avoid failures due to gas limitations.
- Sending the Transaction: After estimating, you can send the transaction and even wait for it to confirm.
Just remember to handle any potential errors at each step to ensure a smooth execution. Happy coding!
[
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_estimateUserOperationGas",
"params": [
{
"sender": "0xSender",
"nonce": "0x0",
"initCode": "0x",
"callData": "0x…",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
"paymasterAndData": "0x"
},
"0xEntryPoint",
"0x1" // chainId
]
}
]
Just go ahead and submit using eth_sendUserOperation with the gas fields you got back. Don't forget to include eip7702Auth only on the networks that have 7702 enabled. For more details, check out the Alchemy documentation.
- MEV Protection: You can route your wallet traffic through Flashbots Protect for private transactions or consider integrating MEV-Share to snag some order flow rebates. For those frontrun-sensitive flows, your API gateway can easily proxy
eth_sendRawTransactionto a Protect RPC. Check it out here.
3.3 Solana production specifics
- Priority fees: To get a good estimate, use
getRecentPrioritizationFeesand make sure to include all the writable accounts involved in your transaction--just a heads up, fee requirements can differ based on the accounts you’re working with. Don't forget to pair it withgetFeeForMessageto cover the base fees. (solana.com) - Stake‑weighted QoS (SWQoS): Team up with staked validators or RPCs that are connected to them. Just so you know, 80% of the leader's TPU capacity is set aside for staked peers by default. This really helps boost your chances of success when things get busy. Also, make sure to configure
rpc‑send‑transaction‑tpu‑peer. (solana.com) - Confirmations: It’s a good idea to make commitment levels (processed/confirmed/finalized) primary options in your API. Don’t just stick with finalized by default; it’ll help improve user experience and responsiveness. (solana.com)
4) Testing strategies your CI must include
4.1 Deterministic mainnet forking
- Fork the mainnet at a specific block for reliable tests and cache the state to make your runs quicker. You can use Hardhat or Anvil with an archive RPC; just snapshot and revert between tests. Pinning this way lets you rerun tests about 20 times faster. (hardhat.org)
Anvil Methods to Script Chaos:
Here's a breakdown of some cool methods you can use with Anvil to create a bit of chaos in your code!
1. Random Number Generation
Randomness is a great way to introduce unpredictability into your scripts. You can use Python’s built-in random module to generate numbers or make decisions.
import random
# Generate a random number between 1 and 10
random_number = random.randint(1, 10)
print(random_number)
2. Shuffling Lists
Tired of the same old order? Shuffling a list can give your outputs a fresh twist!
my_list = [1, 2, 3, 4, 5]
random.shuffle(my_list)
print(my_list)
3. Randomly Selecting Items
Need a random choice from a list? Anvil makes it easy to select an item without having to think too hard about it.
choices = ['apple', 'banana', 'cherry']
random_choice = random.choice(choices)
print(random_choice)
4. Creating Chaos with Delays
Adding some delays can create a sense of suspense or randomness in your application. Use time.sleep() to pause execution for a bit.
import time
print("Getting ready...")
time.sleep(2) # Wait for 2 seconds
print("Go!")
5. Error Simulation
Want to spice things up by simulating errors? You can raise exceptions deliberately to see how your app handles chaos.
try:
raise Exception("Oops! Something went wrong.")
except Exception as e:
print(e)
Feel free to play around with these methods and add your own spin to make chaos a part of your coding adventures!
- Use
anvil_snapshotoranvil_revertto time travel in your blockchain experiments. - Try out
anvil_setNextBlockBaseFeePerGasto mimic those fee spikes you might encounter. - Leverage
anvil_impersonateAccountfor some protocol-level testing. - Utilize
anvil_minealong withevm_setNextBlockTimestampto play around with timing in your scripts. (learnblockchain.cn)
4.2 Reorg and consistency tests
- When you've got a business flow that does N reads and one write, make sure to run it twice: (a) once with "latest" (you might see some flakiness here), and (b) again using EIP‑1898 blockHash pinning (this should be much more stable). If (a) and (b) end up causing different side effects, then it's time to fail the build--this helps catch any sneaky “latest” usage creeping into your core code. (eips.ethereum.org)
- Don’t forget to add tests for safe/finalized tags to make sure your confirmation policy toggles are working as they should. (ethereum.org)
4.3 Fee‑market tests
- EIP‑1559 replacement: Try simulating a “replacement transaction underpriced” by resubmitting with the same nonce but not offering enough tip. Make sure your policy adjusts both the
maxPriorityFeePerGasandmaxFeePerGas, and check that the UI displays “replaced” semantics. (alchemy.com) - Blob stress: When you notice a spike in
eth_blobBaseFee, be sure to validate your fallback options. For example, you might want to degrade to calldata when it’s allowed or just queue up the operation. (docs.metamask.io) - External estimators: Think about hooking up Blocknative’s gas APIs to double-check predictions for next-block inclusion; starting with some canary regions might be a good idea. (docs.blocknative.com)
4.4 Tracing and client‑compat tests
- When integrating any protocol, be sure to run
debug_traceTransactionon both Geth and Erigon. Check out the output structures and keep a record of any differences. This will help you spot any vendor-specific assumptions. Remember to only enable debug on your private infrastructure. (geth.ethereum.org)
4.5 ERC‑4337 validation suites
- In CI, make sure to call
eth_estimateUserOperationGasand runsimulateValidationbefore the bundle. It’s important to fail quickly on any validation reverts. Also, check that your paymaster and signature setup accurately sets the validAfter and validUntil time windows. (docs.erc4337.io)
4.6 Solana devnet tests
- Run a test to calculate the priority fee using the specific account lists that your instruction locks as writable. Make sure to check the results under both SWQoS and non-SWQoS routing to see the benefits in action. Utilize
getFeeForMessageto compare base fee changes across different block hashes. You can find more info here.
5) Reference implementation snippets
5.1 A resilient Ethereum RPC gateway (Node.js/TypeScript)
import pLimit from "p-limit";
import { request } from "undici";
const limit = pLimit(16); // cap concurrency per provider
type RpcReq = { id:number; jsonrpc:"2.0"; method:string; params:any[] };
type RpcRes = { id:number; jsonrpc:"2.0"; result?:any; error?:any };
// provider pool with health + method allowlists
const providers = [
{ url: process.env.RPC_A!, methods: new Set(["eth_call","eth_sendRawTransaction","eth_feeHistory","eth_blobBaseFee"]) },
{ url: process.env.RPC_B!, methods: new Set(["eth_call","eth_sendRawTransaction","eth_feeHistory"]) }
];
export async function rpcCall<T=any>(req: RpcReq): Promise<T> {
const candidates = providers.filter(p => p.methods.has(req.method));
let lastErr: any;
for (const p of candidates) {
try {
const { body } = await limit(() => request(p.url, {
method: "POST",
headers: { "content-type":"application/json" },
body: JSON.stringify(req)
}));
const res = await body.json() as RpcRes;
if (res.error) throw Object.assign(new Error(res.error.message), { code: res.error.code });
return res.result as T;
} catch (e) { lastErr = e; /* mark p unhealthy, continue */ }
}
throw lastErr;
}
- Notes: only allow
eth_blobBaseFeewhere it’s accessible; keep an eye on the health of each provider and switch things up if error rates get too high; batch requests when it’s safe to do so for reads. (docs.metamask.io)
5.2 Consistent reads using blockHash pinning
export async function consistentCall(to: string, data: string, blockHash: string) {
return rpcCall({
id: 1,
jsonrpc: "2.0",
method: "eth_call",
params: [{ to, data }, { blockHash, requireCanonical: true }]
});
}
Make sure to stick all follow-up calls--like storage, code, and balances--to the same object. Check it out here: (eips.ethereum.org).
5.3 ERC‑4337: estimate and submit
// estimate
const est = await rpcCall({
id: 1,
jsonrpc: "2.0",
method: "eth_estimateUserOperationGas",
params: [ userOp, entryPoint, chainIdHex ]
});
// submit
userOp.preVerificationGas = est.preVerificationGas;
userOp.verificationGasLimit = est.verificationGasLimit;
userOp.callGasLimit = est.callGasLimit;
const uoHash = await rpcCall({
id: 2,
jsonrpc: "2.0",
method: "eth_sendUserOperation",
params: [ userOp, entryPoint ]
});
If the network is compatible with 7702 and you're upgrading EOAs in your flow, be sure to include eip7702Auth. Check out the details here.
5.4 Solana: estimating priority fees correctly
// Identify all writable accounts in your Message
const accounts = ["CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY", /* ... */];
const resp = await fetch(SOL_RPC, {
method: "POST",
headers: { "content-type":"application/json" },
body: JSON.stringify({
jsonrpc:"2.0", id:1, method:"getRecentPrioritizationFees", params:[accounts]
})
}).then(r => r.json());
const priorityMicrolamports = Math.max(...resp.result.map((r:any)=>r.prioritizationFee));
Merge it with getFeeForMessage to establish the complete fee. Check it out here: (solana.com)
6) Emerging best practices we’re adopting at 7Block Labs
- Think of “read state” and “event state” as separate products. To boost query performance and keep things stable, shift those heavy reads away from raw RPC and into subgraphs or Firehose/Substreams. Also, make sure to maintain a Prometheus-backed health budget for your indexers. (thegraph.com)
- For fee intelligence, consider this a service: bundle up eth_feeHistory, eth_blobBaseFee, and a cool external estimator (like Blocknative) behind one internal endpoint. You can then offer reliable “next‑block,” “fast,” and “economy” recommendations, complete with confidence intervals. (anukul.js.org)
- Multi‑client staging is key: make sure every release gets smoke-tested against Geth, Erigon, and Besu (when it's relevant) using the same test vectors. Plus, you’ll want to have block debug/trace in production. (geth.ethereum.org)
- Stay ahead with proactive 4337 validation: before each UserOperation goes live, pre-simulate it and show wallet-readable failure reasons. Don’t forget to expose that “why it failed” telemetry to support teams. (docs.erc4337.io)
- For Solana SWQoS contracts: when dealing with high-stakes flows, make sure your RPC partner has connections with staked validators and fully supports SWQoS. It’s also important to measure the uplift against public RPC. (solana.com)
7) Decision checklist
- Consistency
- Ensure all
eth_call/get*reads take ablockHashobject as per EIP‑1898 in your SDK. - Use Multicall3 for your dashboards; responses should include block metadata. (eips.ethereum.org)
- Ensure all
- Writes
- Implement nonce reservation and idempotency keys in your internal API.
- Update your replacement policy to adjust both
maxPriorityFeePerGasandmaxFeePerGas, and set up alerts for underpriced replacements. (alchemy.com)
- Fees
- Create a dual estimator for standard gas and blob gas, and make sure you get an alert when there's blob congestion. (eips.ethereum.org)
- 4337
- Make sure bundler endpoints are available, the simulateValidation gate is enabled, and the 7702 paths are secured. (alchemy.com)
- Solana
- Calculate priority fees using account-aware estimates and have SWQoS routing ready to roll. (solana.com)
- Observability
- Set up Prometheus metrics for Geth/Nethermind; and use OpenTelemetry spans for RPC and webhooks. (geth.ethereum.org)
- Testing
- Run a forked version with pinned-block CI; include reorg tests, client-compat diffs, 4337 validation tests, and Solana devnet fee tests. (hardhat.org)
Appendix: method/name quick reference
- Ethereum JSON‑RPC: You’ll want to check out EIP‑1474 (core), EIP‑1898 (for the blockHash parameter), EIP‑1193 (which is all about the provider API), EIP‑1559 (the fee model we talk about a lot), and the new
eth_feeHistoryandeth_blobBaseFee(that’s for blob fees). You can dive deeper here. - ERC‑4337 Bundler: For those working with it, you’ll find
eth_estimateUserOperationGas,eth_sendUserOperation,eth_getUserOperationReceipt, andeth_supportedEntryPointsare super handy. Check out the details over at Alchemy. - EIP‑7702: This one’s all about setting code for EOAs and it’s forward-compatible with account abstraction. Curious? Find out more here.
- Solana: If you’re diving into Solana, keep an eye on
getRecentPrioritizationFees,getFeeForMessage, and also check out SWQoS. More info’s available here.
If you're looking for a thorough review of your API gateway or test plan, check out what 7Block Labs can do for you. We offer a two-week “production readiness” sprint that covers everything from a consistency audit to fee strategies, 4337 simulation gates, and Solana SWQoS routing. Plus, we customize everything to fit your specific stack and chain mix!
Like what you're reading? Let's build together.
Get a free 30-minute consultation with our engineering team.
Related Posts
ByAUJay
Building a Donation-Based Crowdfunding Platform That Gives Tax Receipts
**Summary:** Donation-based crowdfunding that includes tax receipts has become quite the complex puzzle across different regions. You've got to navigate IRS Pub 1771/526 rules, UK Gift Aid declarations, Canada’s CRA receipting, and the new eIDAS/OpenID4VCI wallets--all while keeping everything running smoothly.
ByAUJay
Why 'Full-Lifecycle Advisory' Beats Just Coding
**Summary:** Engineering teams that focus solely on “writing Solidity” often find themselves caught off guard by shifts in protocols, the need for composable security, and the procurement hurdles that are now impacting real ROI. Our full-lifecycle advisory service bridges the gap by connecting EIP-7702 smart accounts, modular decentralized applications (DA), and ZK-based compliance solutions.
ByAUJay
Why Your Project Could Really Use a 'Protocol Economist
Summary: A lot of Web3 teams are missing a crucial player: the “protocol economist.” And you can really see the impact--value slips away through MEV routing, token incentives that are all out of whack, and those sneaky changes to wallets after Pectra that end up messing with the unit economics. In this playbook, we’ll explore what a protocol economist can do to tackle these issues head-on.

