devto 2026-07-10 원문 보기 ↗
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.
| 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 |
The single most Redbelly-specific roadblock: write access to the network is permissioned per account. Entry 21.
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:
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.
.env).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.
Connection problems between your tooling and the Redbelly RPC endpoints. Entries 1 to 4.
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
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.all over hundreds of calls with a small concurrency pool (for example p-limit set 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.
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
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_PROXY environment variables, VPN split tunnelling, and that your .env value 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.
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
https://governors.testnet.redbelly.network
https://governors.mainnet.redbelly.network
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"
hardhat.config.js/foundry.toml, .env files 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.
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],
},
},
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.
Problems connecting MetaMask and other wallets to Redbelly. Entries 5 and 6.
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):
Redbelly Testnet
https://governors.testnet.redbelly.network
153
RBNT
https://redbelly.testnet.routescan.io
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.
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
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.
Nonce errors, gas estimation failures, stuck and underfunded transactions. Entries 7 to 11.
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
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 });
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.
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
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.
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
eth_call:
try {
await contract.myFunction.staticCall(arg1, arg2, { from: sender });
} catch (err) {
console.log(err.reason || err.data || err); // the real error
}
const ok = await eligibilityContract.hasChainPermission(sender);
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.
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
## 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}'
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.
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
const [deployer] = await ethers.getSigners();
console.log("Deployer:", deployer.address);
console.log("Balance:", ethers.formatEther(await ethers.provider.getBalance(deployer.address)), "RBNT");
PRIVATE_KEY in .env (no duplicated 0x prefix, no whitespace) and confirm dotenv is loaded at the top of hardhat.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.
Hardhat configuration, explorer verification and EVM compatibility. Entries 12 to 14.
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
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] : [],
},
},
};
npx hardhat run scripts/deploy.js --network redbellyTestnet
eth_chainId curl 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.
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
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",
},
},
],
},
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. Run npx hardhat clean && npx hardhat compile first 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.
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 in hardhat.config.js:
solidity: {
version: "0.8.24",
settings: {
optimizer: { enabled: true, runs: 200 },
evmVersion: "paris",
},
},
Foundry equivalent in foundry.toml:
evm_version = "paris"
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.
Installation, widget rendering, cross-origin behaviour and verification checks for Redbelly's compliance tooling. Entries 15 to 19.
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:packages scope: GitHub > Settings > Developer settings > Personal access tokens.
Create .npmrc in your project root:
@redbellynetwork:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
export GITHUB_TOKEN=your_token_here
npm i @redbellynetwork/eligibility-sdk
GITHUB_TOKEN as 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.
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.local and actually reach the client. In Next.js, server-only names are invisible to browser code; the widget config must come from NEXT_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
"use client";
import dynamic from "next/dynamic";
const OnboardingWidget = dynamic(() => import("./OnboardingWidget"), { ssr: false });
.env.local change; 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.
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
Refused to frame ... means your own CSP is the blocker. Allow the SDK origin in frame-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.origin once 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.
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
ngrok http 3000
Use the generated https://xxxx.ngrok-free.app URL 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.
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
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_DID matches 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.
Getting and keeping testnet RBNT. Entry 20.
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
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.