I have been building on Redbelly Network for a while now, and the same pattern kept repeating: I would hit an error, spend an hour on trial and error, solve it, and then a week later watch someone ask about the exact same error in the developer Discord. The official docs are strong on architecture and SDK reference material, but there was no single place that answered "here is my error message, what do I do".
So I built one. This guide documents the 21 most common Redbelly developer roadblocks, sourced from recurring questions in the Redbelly Discord and Telegram support channels and from my own projects on the network. Every entry follows the same structure: Symptom (the exact error), Root Cause (why it happens), Solution (step-by-step fix with exact commands), and Prevention (how to avoid it next time).
Two findings worth calling out up front, both verified against the live network while writing this guide. First, Redbelly permissions write access per account: a freshly generated wallet cannot send any transaction until it is enabled through the official access dApp, and the resulting error, Sender not authorised to write transactions, is covered in entry 21. Second, the testnet RPC hostname that several older guides reference, rpc-testnet.redbelly.network, no longer resolves; the current canonical endpoint is https://governors.testnet.redbelly.network, and entry 3 documents the migration.
The maintained version of this guide lives on GitHub: https://github.com/Rahmandefi/Redbelly-troubleshooting-wiki. Issues and pull requests are welcome.
Network quick reference
| Testnet | Mainnet | |
|---|---|---|
| Chain ID |
153 (hex 0x99) |
151 (hex 0x97) |
| RPC URL | https://governors.testnet.redbelly.network |
https://governors.mainnet.redbelly.network |
| Explorer | https://redbelly.testnet.routescan.io | https://redbelly.routescan.io |
| Gas token | RBNT | RBNT |
| Faucet | https://redbelly.faucetme.pro (Discord login) | n/a |
Account Access and Permissioning
The single most Redbelly-specific roadblock: write access to the network is permissioned per account. Entry 21.
21. "Sender not authorised to write transactions"
Reproduced and resolved end to end on Redbelly Testnet, July 2026: a freshly generated wallet's first deployment attempt returned this exact error; after completing the credential and enablement flow below, the same key deployed successfully (tx 0x6b852e17bc77f0cad8a7e5b03e264a08eed9f297c20a72f1b8498c6ee51a40e9).
Symptom
ProviderError: Sender not authorised to write transactions
The very first transaction from a wallet fails with this error: contract deployment, token transfer, anything that writes. Read calls (eth_call, balance queries) work fine. This hits hardest in scripted setups, because a deployer key generated by Hardhat, a CI pipeline or a tool like thirdweb has never been near the access flow.
Related symptoms in the access dApp itself: "Account status: Not enabled" even though the account holds the access credential, and "Balance: Insufficient funds to enable account" even though you funded the account on testnet (the dApp is checking your balance on a different network than the one you funded).
Root Cause
Redbelly is a compliance-first chain: unlike most EVM networks, the protocol itself restricts transaction submission to enabled accounts. Getting write access is a three-part sequence, and each part is separate:
- The account must hold the Redbelly Network Access Credential, issued by the accredited issuer (Averer) after identity verification.
- The account must be funded, because the enablement itself is an on-chain transaction that costs gas. You cannot enable an empty account.
- The account must be enabled on each network separately. Enabling on Mainnet does not enable Testnet, and the access dApp operates against the network your wallet is currently connected to, so it can report "insufficient funds" while your testnet balance is healthy simply because your wallet is pointed at Mainnet.
A brand-new keypair, however well funded, is not authorised until all three parts are complete. Receiving RBNT does not require enablement (transfers in always work), which makes the situation confusing: the faucet succeeds, the explorer shows a balance, and the first outgoing transaction still fails. This also breaks tools that deploy through auto-generated intermediate wallets (thirdweb's presumptive deployment fails on Redbelly for exactly this reason; see thirdweb-dev/contracts issue 615).
Solution
The working order is: credential, fund, connect to the right network, enable, transact.
- Open the official access dApp at https://access.redbelly.network/ and connect the wallet your transactions will be sent from (MetaMask and Coinbase Wallet are supported).
- First time on the network: follow the prompts to verify your identity with the accredited issuer (Averer) and claim the Redbelly Network Access Credential. Identity verification is free; the issuer covers the associated fees.
- Already verified: you do not need to repeat identity verification for additional wallets. The access dApp lets you add more accounts connected to your existing identity, which is the right way to set up a dedicated deployer key for scripts (import the key into MetaMask, add it as an additional account, then keep it in your project's
.env). - Fund the account before enabling it: the enablement is an on-chain transaction and an empty account cannot pay for it. On Testnet use the faucet (entry 20 in Faucet and Funding).
- Switch your wallet to the network you want access on (chain 153 for Testnet) so the dApp targets the right chain, then complete the enable step and sign the transaction. Repeat per network; Mainnet and Testnet enablement are independent.
- Retry your deployment. If a deployment tool creates its own throwaway deployer wallet under the hood, bypass that feature and deploy directly from an enabled wallet.
Prevention
Because enablement is tied to an identity credential, the practical pattern is to enable one or two long-lived deployer accounts per environment and reuse them across projects, rather than generating a fresh key per project the way you would on other EVM chains. Treat "credential, fund, enable, deploy" as standard onboarding for any new account, and document it in your project README; it is the sequence most tutorials written for other chains will not mention.
A security note that follows from this: if your only enabled wallet is a personal one, do not export its private key into a script's .env just to deploy. Enable a dedicated deployer address under your existing credential (no repeat identity verification needed), or deploy through a browser workflow (Remix with an injected wallet) so the key never leaves your wallet. Keep scripted .env keys for accounts that hold nothing you care about.
Network and RPC Issues
Connection problems between your tooling and the Redbelly RPC endpoints. Entries 1 to 4.
1. RPC endpoint returns 429 (Too Many Requests)
Symptom
ProviderError: 429 Too Many Requests
Scripts that loop over many eth_call requests (indexers, test suites, dashboards) fail intermittently. Single requests work fine.
Root Cause
The public Redbelly RPC endpoints are shared infrastructure and rate-limit aggressive clients. Batch-heavy tools (Hardhat tests, multicall loops, polling dashboards) exceed the per-IP request budget.
Solution
- Add retry with exponential backoff. With ethers v6:
async function withRetry(fn, retries = 5) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (err) {
if (!String(err).includes("429") || i === retries - 1) throw err;
await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
}
}
}
const balance = await withRetry(() => provider.getBalance(address));
Throttle concurrent requests. Replace
Promise.allover hundreds of calls with a small concurrency pool (for examplep-limitset to 3 to 5 concurrent requests).Cache anything static. Contract ABIs, decimals and historical blocks never change; fetch once and store.
Prevention
Design for a rate-limited RPC from day one: batch reads with Multicall3 where deployed, poll no faster than the block time, and keep a client-side cache. For production dashboards, run your own node or request a dedicated endpoint from the Redbelly team.
2. RPC connection timeouts and "could not detect network"
Symptom
Error: could not detect network (event="noNetwork", code=NETWORK_ERROR)
or
FetchError: request to https://governors.testnet.redbelly.network failed, reason: connect ETIMEDOUT
Root Cause
One of three things: (a) the RPC URL is typed incorrectly or missing https://, (b) a local firewall, VPN or corporate proxy blocks the request, or (c) a transient outage of the shared endpoint.
Solution
- Verify the endpoint responds before blaming your code:
curl -s -X POST https://governors.testnet.redbelly.network \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
Expected response: {"jsonrpc":"2.0","id":1,"result":"0x99"} (0x99 = 153, the Testnet chain ID).
If curl works but your app does not, the problem is your environment. Check for
HTTP_PROXY/HTTPS_PROXYenvironment variables, VPN split tunnelling, and that your.envvalue has no trailing whitespace or quotes.If curl also fails, test from another network (mobile hotspot). If it fails everywhere, check the Redbelly Discord announcements channel for maintenance notices before opening a support thread.
On WSL2 specifically, Node.js can pick a broken IPv6 route. Force IPv4-first resolution:
export NODE_OPTIONS="--no-network-family-autoselection"
Prevention
Add the eth_chainId curl check to your project README as a first diagnostic. In long-running services, wrap the provider in retry logic and alert on repeated network errors instead of crashing.
3. Wrong or deprecated RPC endpoint
Verified on Redbelly Testnet, July 2026.
Symptom
Error: getaddrinfo ENOTFOUND rpc-testnet.redbelly.network
or requests to a DevNet URL hang or return errors, even though the URL came from an older guide.
Root Cause
Redbelly's endpoints have changed over time. Older community guides still reference rpc-testnet.redbelly.network or DevNet URLs. DevNet is officially deprecated, the rpc-testnet hostname no longer resolves, and the current canonical endpoints (per the developer portal at vine.redbelly.network) are the governors.* URLs.
Solution
- Replace any old endpoint with the current one:
- Testnet:
https://governors.testnet.redbelly.network - Mainnet:
https://governors.mainnet.redbelly.network
- Confirm with the chain ID check:
curl -s -X POST https://governors.testnet.redbelly.network \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
## expect "result":"0x99"
- Update
hardhat.config.js/foundry.toml,.envfiles and any wagmi/viem chain definitions in the same pass so no stale URL survives.
Prevention
Define the RPC URL once (an .env variable or a shared chains.ts) and import it everywhere. When following any tutorial older than a few months, cross-check endpoints against https://vine.redbelly.network/environments/ before running anything.
4. Chain ID mismatch ("network changed" or "Unrecognized chain")
Symptom
NetworkError: underlying network changed (event="changed", network={"chainId":153,...})
or MetaMask rejects a transaction with Unrecognized chain ID, or Hardhat aborts with a chain ID assertion error.
Root Cause
The chain ID configured in your tooling does not match what the RPC actually reports. Common causes: using Mainnet chain ID 151 with the Testnet RPC (or vice versa), a leftover DevNet chain ID, or MetaMask connected to a different network than your dApp expects.
Solution
Ask the RPC what it really is (see the curl in entry 3).
0x99= 153 = Testnet,0x97= 151 = Mainnet.Make your Hardhat config match:
// hardhat.config.js
networks: {
redbellyTestnet: {
url: "https://governors.testnet.redbelly.network",
chainId: 153,
accounts: [process.env.PRIVATE_KEY],
},
},
- In the frontend, prompt a network switch instead of failing silently:
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x99" }],
});
Prevention
Never hardcode chain IDs in more than one place. Keep a single chain definition object and reference it from Hardhat, wagmi/viem config and deployment scripts.
Wallet and MetaMask Issues
Problems connecting MetaMask and other wallets to Redbelly. Entries 5 and 6.
5. MetaMask not detecting Redbelly Network
Symptom
Redbelly does not appear in MetaMask's network list, or the "Add network" search finds nothing, so the user cannot connect to your dApp at all.
Root Cause
Redbelly is not in MetaMask's built-in popular networks list, so it must be added manually or programmatically. Users following a dApp prompt may also hit this if the dApp only calls wallet_switchEthereumChain, which fails with error 4902 when the chain has never been added.
Solution
Manual (for users):
- MetaMask > Networks > Add a custom network, and enter:
- Network name:
Redbelly Testnet - RPC URL:
https://governors.testnet.redbelly.network - Chain ID:
153 - Currency symbol:
RBNT - Explorer:
https://redbelly.testnet.routescan.io
- Network name:
Programmatic (for dApp developers), handling the 4902 case:
try {
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x99" }],
});
} catch (err) {
if (err.code === 4902) {
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [{
chainId: "0x99",
chainName: "Redbelly Testnet",
rpcUrls: ["https://governors.testnet.redbelly.network"],
nativeCurrency: { name: "RBNT", symbol: "RBNT", decimals: 18 },
blockExplorerUrls: ["https://redbelly.testnet.routescan.io"],
}],
});
} else {
throw err;
}
}
Users can also add the chain in one click from https://chainlist.org/chain/153.
A related point of confusion once the network is added: RBNT is the network's native gas coin, not an ERC-20 token, so there is no contract address to import and MetaMask's "Import tokens" flow is not needed. If a guide asks you for an RBNT contract address, it is describing wrapped RBNT on a different chain, which is a separate asset.
Prevention
Every Redbelly dApp should ship the wallet_addEthereumChain fallback above in its connect flow. Do not assume the user has the network configured.
6. MetaMask shows wrong balance or stuck account state
Symptom
The balance in MetaMask does not match the explorer, sent transactions do not appear, or every new transaction immediately fails even though the account has funds. A variant reported by RBNT holders: the balance appears for a split second when switching networks and then vanishes, while the explorer shows the funds are untouched.
Root Cause
MetaMask caches account state (nonce, balance, activity) per network. After a testnet reset, an RPC switch, or heavy scripted use of the same key outside MetaMask, that cache goes stale and MetaMask signs with wrong assumptions.
Solution
- First confirm the truth on-chain: look the address up on https://redbelly.testnet.routescan.io. The explorer reflects actual state.
- In MetaMask: Settings > Advanced > Clear activity tab data. This resets cached activity and nonce for the selected account and network. It does not touch your keys or funds.
- If the balance is still wrong, remove the Redbelly network entry and re-add it with the current RPC URL (entry 5), then reselect the account. Community members have confirmed this fixes the disappearing-balance variant.
- If MetaMask keeps misbehaving on Redbelly, an ERC-20 compatible alternative such as Rabby works with the same seed or key and the same network details, and is what network moderators suggest for persistent display glitches.
Prevention
Do not share one private key between MetaMask and automated scripts on the same network; the nonce cache will constantly desync (see entry 7 in Gas and Transactions). Use a dedicated deployer key for scripts.
Gas and Transaction Issues
Nonce errors, gas estimation failures, stuck and underfunded transactions. Entries 7 to 11.
7. "Nonce too low" transaction failures
Symptom
Error: nonce too low: next nonce 42, tx nonce 40
Root Cause
The transaction's nonce has already been used. Typical triggers: MetaMask's cached nonce is behind the chain (after scripted transactions from the same key), two scripts sending from the same account concurrently, or resubmitting an already-mined transaction.
Solution
- Get the real next nonce from the chain:
curl -s -X POST https://governors.testnet.redbelly.network \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0xYOUR_ADDRESS","pending"],"id":1}'
In MetaMask: Settings > Advanced > Clear activity tab data, so it refetches the nonce.
In scripts, fetch the pending nonce explicitly instead of trusting a cached signer:
const nonce = await provider.getTransactionCount(wallet.address, "pending");
const tx = await wallet.sendTransaction({ ...txRequest, nonce });
- If two processes share a key, serialise them or give each its own key.
Prevention
One key, one sender. Deployment scripts, keeper bots and your personal MetaMask should each use separate accounts. In bots, track nonces in a single queue rather than firing transactions in parallel.
8. "Replacement transaction underpriced"
Symptom
Error: replacement transaction underpriced
when re-sending a transaction that is already pending.
Root Cause
A transaction with the same nonce is already in the mempool. Nodes only accept a replacement if it bumps the gas price by a sufficient margin (typically at least 10 per cent over the pending one).
Solution
- Check whether the original is still pending on the explorer. If it confirmed, just resend with a fresh nonce (or let your signer pick one).
- To genuinely replace a stuck transaction, reuse the same nonce with a higher gas price:
const pending = await provider.getTransactionCount(wallet.address, "pending");
const latest = await provider.getTransactionCount(wallet.address, "latest");
if (pending > latest) {
const feeData = await provider.getFeeData();
await wallet.sendTransaction({
to: wallet.address, // self-send 0 to cancel
value: 0,
nonce: latest, // the stuck nonce
gasPrice: (feeData.gasPrice * 125n) / 100n, // +25%
});
}
Prevention
Do not blind-retry sends on timeout; check pending state first. If your script retries, always bump the gas price by at least 25 per cent on each attempt.
9. Gas estimation returning null or "cannot estimate gas"
Symptom
Error: cannot estimate gas; transaction may fail or may require manual gas limit
(reason="execution reverted", method="estimateGas")
or eth_estimateGas returns null or empty in your tooling.
Root Cause
Nine times out of ten this is not a gas problem: eth_estimateGas simulates the transaction, and if the simulation reverts (failed require, missing approval, unverified wallet blocked by an eligibility check, wrong constructor arguments), estimation fails. The remaining cases are RPC hiccups or a signer with zero balance.
Solution
- Find the actual revert reason by simulating with
eth_call:
try {
await contract.myFunction.staticCall(arg1, arg2, { from: sender });
} catch (err) {
console.log(err.reason || err.data || err); // the real error
}
- Common Redbelly-specific revert: contracts gated by the EligibilitySDK revert for wallets that have not completed KYC. Check permission first:
const ok = await eligibilityContract.hasChainPermission(sender);
- If the call genuinely should succeed, retry estimation once (transient RPC issue), and as a last resort set a manual gas limit to surface the real on-chain revert:
await contract.myFunction(arg1, arg2, { gasLimit: 500_000 });
Prevention
Treat estimation failure as "this transaction would revert" and surface the decoded reason to users. In eligibility-gated dApps, check hasChainPermission in the UI before enabling action buttons.
10. Transaction pending indefinitely
Symptom
A transaction sits in "pending" in MetaMask or your script for many minutes. Redbelly's DBFT consensus gives fast, deterministic finality, so anything pending for more than a minute is stuck, not slow.
Root Cause
Either (a) the gas price is below what nodes currently accept, (b) an earlier nonce from the same account is stuck, blocking everything behind it, or (c) the transaction never actually reached the network (an RPC error swallowed by the app).
Solution
- Check whether the network ever saw it: search the transaction hash on https://redbelly.testnet.routescan.io. Not found means it never landed; resend it.
- Check for a nonce gap:
## compare "latest" vs "pending" counts; a gap means a stuck earlier tx
curl -s -X POST https://governors.testnet.redbelly.network -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0xYOU","latest"],"id":1}'
curl -s -X POST https://governors.testnet.redbelly.network -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0xYOU","pending"],"id":2}'
- Clear the stuck nonce with a self-send replacement at a higher gas price (exact snippet in entry 8), after which the queued transactions behind it will process.
- In MetaMask, use the built-in "Speed up" on the oldest pending transaction rather than sending new ones on top.
Prevention
Use the network's suggested gas price (provider.getFeeData()) instead of hardcoding one. In scripts, await tx.wait() before sending the next transaction from the same account.
11. "Insufficient funds for gas * price + value"
Symptom
Error: insufficient funds for gas * price + value
often during npx hardhat run scripts/deploy.js --network redbellyTestnet, sometimes even though you believe the wallet is funded.
Root Cause
The sending account cannot cover gas cost plus sent value on the network you are actually targeting. The three classic variants: (a) the account genuinely has 0 RBNT, (b) Hardhat derived a different address than the one you funded (wrong PRIVATE_KEY in .env), or (c) you funded the account on a different network than the one being used.
Note that on Redbelly a brand-new account fails earlier with Sender not authorised to write transactions (entry 21 in Account Access and Permissioning); this entry applies to accounts already enabled for write access.
Solution
- Print which address your script is actually using:
const [deployer] = await ethers.getSigners();
console.log("Deployer:", deployer.address);
console.log("Balance:", ethers.formatEther(await ethers.provider.getBalance(deployer.address)), "RBNT");
- If the address is not the one you funded, fix
PRIVATE_KEYin.env(no duplicated0xprefix, no whitespace) and confirmdotenvis loaded at the top ofhardhat.config.js:
require("dotenv").config();
If the address is right but the balance is 0, fund it from the faucet (entry 20 in Faucet and Funding) and verify on the explorer before retrying.
If the balance looks fine, check you are on the intended network:
console.log(await ethers.provider.getNetwork())should report chain ID 153 for Testnet.
Prevention
Add a balance-and-network preflight to every deploy script that aborts with a clear message when the deployer has no RBNT or is on an unexpected chain ID.
Contract Deployment Issues
Hardhat configuration, explorer verification and EVM compatibility. Entries 12 to 14.
12. Hardhat cannot connect (HH108) or misconfigured network
Verified on Redbelly Testnet, July 2026: the config block below deployed a live contract to 0xd583cbaf72849Ad868445C4D025dc49dF84358d6 (tx 0x6b852e17bc77f0cad8a7e5b03e264a08eed9f297c20a72f1b8498c6ee51a40e9).
Symptom
Error HH108: Cannot connect to the network redbellyTestnet
or
Error HH100: Network redbellyTestnet doesn't exist
Root Cause
HH100: the --network name you passed does not match any key under networks in hardhat.config.js. HH108: the name matches but the RPC URL is wrong, unreachable, or the environment variable holding it is undefined (so Hardhat sees url: undefined).
Solution
- Use a known-good config block:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: "0.8.24",
networks: {
redbellyTestnet: {
url: "https://governors.testnet.redbelly.network",
chainId: 153,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
};
- Confirm the exact network name in the command matches the config key:
npx hardhat run scripts/deploy.js --network redbellyTestnet
- If HH108 persists, run the
eth_chainIdcurl from entry 2 in Network and RPC. If curl succeeds but Hardhat fails, check proxies and the WSL2 IPv6 note in the same entry.
Prevention
Commit a working hardhat.config.js with the Redbelly network block to your project template, and validate required environment variables at config load with a clear throw if missing.
13. Contract verification failing on the block explorer
Verified on Redbelly Testnet, July 2026: the custom chain config below verified the contract at 0xd583cbaf72849Ad868445C4D025dc49dF84358d6 on Routescan; the explorer now serves its full source and ABI.
Symptom
npx hardhat verify fails with "network not supported", "unable to verify" or bytecode mismatch errors; or manual verification on the explorer rejects the source.
Root Cause
Redbelly's explorer is Routescan, which is not in hardhat-verify's built-in chain list, so it must be added as a custom chain. Bytecode mismatches come from verifying with different compiler settings (version, optimizer runs, evmVersion) or different constructor arguments than the deployed build.
Solution
- Add Routescan's Etherscan-compatible API as a custom chain in
hardhat.config.js:
etherscan: {
apiKey: { redbellyTestnet: "verifyContract" }, // Routescan accepts any non-empty string
customChains: [
{
network: "redbellyTestnet",
chainId: 153,
urls: {
apiURL: "https://api.routescan.io/v2/network/testnet/evm/153/etherscan",
browserURL: "https://redbelly.testnet.routescan.io",
},
},
],
},
- Verify with the exact constructor arguments used at deployment:
npx hardhat verify --network redbellyTestnet 0xDEPLOYED_ADDRESS "constructorArg1" "constructorArg2"
On bytecode mismatch: verify from the same commit and the same
hardhat.config.js(solc version, optimizer settings, evmVersion) that produced the deployment. Runnpx hardhat clean && npx hardhat compilefirst to rule out stale artefacts.Manual alternative: on the contract's page at https://redbelly.testnet.routescan.io, use "Verify Contract" and paste the flattened source, selecting the exact compiler version, optimizer setting and EVM version used at deployment. Useful when the deployment was done through Remix or another tool outside Hardhat.
Prevention
Verify immediately after deploying, from the same machine and commit, ideally in the deploy script itself via hardhat.run("verify:verify", {...}). Record constructor arguments in your deployment output JSON.
14. "Invalid opcode" on deployment (EVM version mismatch)
Tested on Redbelly Testnet, July 2026: the same contract was deployed three times with solc 0.8.24 targeting paris, shanghai and cancun. All three deployed and executed correctly, including the shanghai build carrying 27 PUSH0 opcodes in its runtime bytecode. Testnet currently accepts post-paris builds.
Symptom
A contract that compiles cleanly and deploys fine on another network reverts on Redbelly with invalid opcode during deployment or on first interaction.
Root Cause
Solidity 0.8.20 and later target the Shanghai EVM by default and emit the PUSH0 opcode (Cancun targets add MCOPY and transient storage opcodes on top). Chains whose execution layer has not enabled a given fork reject those opcodes, and older reports of this error on Redbelly stem from that gap. As of July 2026, Redbelly Testnet executes shanghai and cancun builds correctly (see the test note above), so a fresh invalid opcode there usually has a different cause: stale build artefacts deployed from an old artifacts/ directory, a constructor revert being misreported by the tooling, or a library compiled with different settings than the importing project.
Two version caveats worth knowing. Hardhat 2 pins the EVM target to paris for solc 0.8.20 to 0.8.24 precisely to avoid PUSH0 incompatibilities, so a default Hardhat build is already pre-shanghai without you asking. And Mainnet enablement of a fork can lag Testnet, so retest there before assuming parity.
Solution
Rule out stale artefacts first:
npx hardhat clean && npx hardhat compile, then redeploy.Isolate the EVM version question: deploy a trivial contract compiled with
evmVersion: "paris"(pre-PUSH0). If the paris build works while the default build fails, it is a fork mismatch on the network you are targeting; pin the version inhardhat.config.js:
solidity: {
version: "0.8.24",
settings: {
optimizer: { enabled: true, runs: 200 },
evmVersion: "paris",
},
},
Foundry equivalent in foundry.toml:
evm_version = "paris"
- If the paris build fails the same way, the EVM version is not your problem: check that the deployer account is enabled for the target network (entry 21 in Account Access) and inspect the transaction on Routescan for the actual revert point.
Prevention
Set evmVersion explicitly in your project template rather than relying on compiler or framework defaults, retest after every Solidity version bump, and remember that the same settings must be used again at verification time (entry 13). When targeting Mainnet, confirm fork support there rather than extrapolating from Testnet.
EligibilitySDK and Onboarding SDK Issues
Installation, widget rendering, cross-origin behaviour and verification checks for Redbelly's compliance tooling. Entries 15 to 19.
15. npm install fails with 401 for @redbellynetwork packages
Symptom
npm ERR! code E401
npm ERR! 401 Unauthorized - GET https://npm.pkg.github.com/@redbellynetwork%2feligibility-sdk
or 404 Not Found for the same package.
Root Cause
Redbelly SDK packages are hosted on GitHub Packages, not the public npm registry. Without an .npmrc pointing the @redbellynetwork scope at GitHub's registry plus a valid GitHub token, npm either looks in the wrong registry (404) or is rejected (401).
Solution
Create a GitHub Personal Access Token (classic) with the
read:packagesscope: GitHub > Settings > Developer settings > Personal access tokens.Create
.npmrcin your project root:
@redbellynetwork:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
- Export the token and install:
export GITHUB_TOKEN=your_token_here
npm i @redbellynetwork/eligibility-sdk
- In CI, set
GITHUB_TOKENas a secret; never commit a literal token in.npmrc.
Prevention
Commit the .npmrc with the ${GITHUB_TOKEN} placeholder (safe, contains no secret) and document the token requirement in your README so every new contributor and CI runner sets it before npm install.
16. EligibilitySDK widget not rendering
Symptom
The onboarding widget area is blank, stuck on a spinner, or the component mounts with no iframe. The browser console may show SDK initialisation errors or 401/403 responses from the verifier service.
Root Cause
The widget aborts silently when its required configuration is missing or wrong. The usual suspects: REDBELLY_API_KEY or ALLOWED_ISSUER_DID unset (or not exposed to the browser build), the SDK initialised for the wrong environment, or the component rendered server-side where window does not exist.
Solution
Check the browser console and network tab first: a 401 from the verifier API means the API key is missing or invalid; contact the Redbelly team if you have not been issued one.
Confirm the environment variables are present in
.env.localand actually reach the client. In Next.js, server-only names are invisible to browser code; the widget config must come fromNEXT_PUBLIC_-prefixed variables or be passed down from a server component:
## .env.local
REDBELLY_API_KEY=your_key # server-side verification
ALLOWED_ISSUER_DID=did:receptor:redbelly:testnet:31K82iKCtE6ciDc7oAr3T5EpjZb4S1EFM7c4xJaWkM2
- In the Next.js App Router, render the widget client-side only:
"use client";
import dynamic from "next/dynamic";
const OnboardingWidget = dynamic(() => import("./OnboardingWidget"), { ssr: false });
- Restart the dev server after any
.env.localchange; Next.js only reads env files at startup.
Prevention
Validate SDK config at startup and render a visible error state ("Verification service not configured") instead of an empty div. Keep testnet and mainnet issuer DIDs in separate env files.
17. Cross-origin and iframe issues with the SDK
Symptom
Blocked a frame with origin "https://..." from accessing a cross-origin frame
or the SDK iframe refuses to load with a CSP / X-Frame-Options error in the console, or postMessage events from the widget never arrive.
Root Cause
The onboarding widget runs in an iframe served from a Redbelly domain and communicates via postMessage. Your site's Content-Security-Policy can block the frame from loading (frame-src), and overly strict message-origin filtering (or third-party cookie blocking in the browser) can break the callback channel.
Solution
- Read the exact console error.
Refused to frame ...means your own CSP is the blocker. Allow the SDK origin inframe-src. In Next.js:
// next.config.js
headers: async () => [{
source: "/(.*)",
headers: [{
key: "Content-Security-Policy",
value: "frame-src 'self' https://*.redbelly.network;",
}],
}],
If you listen for widget events yourself, do not drop messages by checking the wrong origin; log
event.originonce to see the real value, then allowlist exactly that.Test in a normal browser profile. Extensions and "block third-party cookies" settings break iframe sessions; if it works in a clean profile, it is a browser privacy setting, not your code.
Serve your app over HTTPS (or localhost); mixed-content rules block an HTTPS iframe inside an HTTP page.
Prevention
If you deploy a CSP, include frame-src for Redbelly domains from the start, and test the full onboarding flow in Chrome, Firefox and Safari before release; Safari's tracking prevention is the strictest.
18. Onboarding wallet callback never arrives in local development
Symptom
You scan the QR code or open the identity wallet (Keyper or Privado), complete the credential step on your phone, but your locally running dApp never receives the completion callback. The flow works when deployed.
Root Cause
The onboarding flow depends on the identity wallet calling your application back over the network. http://localhost:3000 is not reachable from your phone or from Redbelly's services, so the callback dies. This is why the official getting-started guide requires a tunnel (ngrok) for local development.
Solution
- Start a tunnel to your dev server:
ngrok http 3000
Use the generated
https://xxxx.ngrok-free.appURL as the app URL in your SDK configuration and open the app through that URL (not localhost) while testing.Restart the flow from scratch after switching URLs; sessions started on localhost will not resume on the tunnel URL.
If the phone and callback still fail, confirm the phone has internet access (not just isolated local Wi-Fi) and that the tunnel is still alive; free ngrok URLs change on every restart.
Prevention
Script the tunnel into your dev workflow (for example an npm script that starts ngrok and prints the URL to paste into .env.local). For team testing, use a deployed preview environment instead of individual tunnels.
19. hasChainPermission returns false for a verified user
Symptom
A user completed the KYC flow in the onboarding widget, but hasChainPermission(address) (contract call or useHasChainPermission hook) still returns false, so your gated contract reverts or your UI keeps them locked out.
Root Cause
Verification status is per-address, per-environment and per-issuer. The mismatch is usually one of: the user verified a different wallet address than the one connected, they verified on a different environment (testnet vs mainnet) than your app queries, your app trusts a different issuer DID than the one that issued the credential, or the credential has expired.
Solution
- Log exactly what you are checking:
console.log("checking address:", address);
const ok = await eligibility.hasChainPermission(address);
console.log("hasChainPermission:", ok);
Compare that address character for character with the wallet the user verified. Multiple accounts in MetaMask is the most common source of this bug.
Confirm your app and the verification flow point at the same environment: chain ID 153 config with a testnet issuer DID, or 151 with mainnet. A testnet credential never validates on mainnet.
Check
ALLOWED_ISSUER_DIDmatches the issuer used by the onboarding widget configuration.If everything matches and it worked before, the credential may have expired; run the user through the onboarding flow again.
Prevention
In the UI, always display which connected address is being checked, and gate actions on the same address object you pass to the hook. Keep environment config (chain plus issuer DID) in one shared module so testnet and mainnet cannot mix.
Faucet and Funding Issues
Getting and keeping testnet RBNT. Entry 20.
20. Testnet faucet not distributing RBNT
Symptom
You request testnet RBNT but nothing arrives, the faucet reports you as rate-limited, or the faucet page errors out.
Root Cause
The official faucet (FAUCETME) requires joining with a Discord account and enforces a claim cooldown (one claim per 24 hours). Requests fail when the Discord link is not completed, the cooldown has not elapsed, the wrong address was pasted, or the faucet is temporarily out of funds.
Solution
- Use the official faucet at https://redbelly.faucetme.pro and complete the "join with Discord" step; claims without the Discord link do not process.
- Double-check the address you pasted and that you are watching the same address on the explorer: search it at https://redbelly.testnet.routescan.io to see whether the transfer actually landed (MetaMask can display stale balances; see entry 6 in Wallet and MetaMask).
- If you claimed recently, wait out the 24-hour cooldown; repeat attempts inside the window are rejected.
- Try the third-party thirdweb faucet at https://thirdweb.com/redbelly-network-testnet as a fallback (small amounts, also rate-limited).
- If nothing arrives after the cooldown, ask in the Redbelly Discord developer channel with your address and timestamp; the faucet occasionally needs a refill by the team.
Prevention
Claim faucet funds before you need them, and consolidate testing on one or two funded addresses instead of spreading dust across many throwaway wallets. Deployment-heavy days may need more than one day's claim, so plan a day ahead.
If this saved you an hour, the full maintained version with a searchable index is on GitHub: https://github.com/Rahmandefi/Redbelly-troubleshooting-wiki. Found an error pattern I missed? Open an issue or find me in the Redbelly Discord developer channel.
Top comments (0)