Single-node Redbelly loop-consensus chain for local dApp testing before testnet & mainnet.
583
Single-node Redbelly Network (SEVM) for local development: loop-consensus chain with core governance and KYB contracts deployed, JSON-RPC on 8547, and tx helpers for funding wallets, permissions, and business flows.
Validate dApps locally, then retarget the same contracts at Redbelly testnet / mainnet.
docker pull redbellynetwork/sevm-local:latest
docker run --rm -p 8547:8547 --name sevm-local redbellynetwork/sevm-local:latest
In a second terminal:
docker exec sevm-local tx check-rpc
docker exec sevm-local tx get-balance
Point your wallet or ethers provider at:
| Setting | Value |
|---|---|
| RPC URL | http://127.0.0.1:8547 |
| Chain ID | 161 |
| Native token | RBNT (18 decimals) |
Prefer an immutable version tag. Tags encode three independently versioned pieces:
| Component | Meaning |
|---|---|
bin-… | SEVM node binary |
core-… | Core / bootstrap contracts |
id-… | KYB contracts (AllowedList + BusinessIdentifier factory) |
| Tag | Meaning |
|---|---|
latest | Newest unpinned publish (mutable) |
bin-<binary>-core-<core>-id-<id> | Pins one or more component versions |
sha-<gitsha> | Exact packaging-repo commit (optional maintainer tag) |
In a composite tag, an unpinned segment is the literal string latest (cloned from dev at build time) — not the Hub image tag :latest.
Examples:
| Tag | What you get |
|---|---|
latest | No component versions were specified at build time |
bin-v1.3.17-core-v0.47.2-id-v1.2.13 | Binary 1.3.17, core contracts 0.47.2, business/KYB contracts 1.2.13 |
Pull examples:
docker pull redbellynetwork/sevm-local:latest
docker pull redbellynetwork/sevm-local:bin-v1.3.17-core-v0.47.2-id-v1.2.13
docker pull redbellynetwork/sevm-local@sha256:<digest>
Pin by digest when you need bit-for-bit reproducibility:
docker image inspect --format='{{index .RepoDigests 0}}' \
redbellynetwork/sevm-local:bin-v1.3.17-core-v0.47.2-id-v1.2.13
CONSENSUS=loop)0)0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5/opt/sevm/network-config.jsontx CLI (ethers-based helpers) available via docker execLocal / development only. Genesis keys and sample IDP credentials are public test values. Never reuse them on testnet or mainnet.
| Name | How you reach it | Used for |
|---|---|---|
| Bootstrap registry | fixed address above | Resolve permission, gasfees, pricefeed, networkconfiguration, … |
| Permission | registry("permission") | Network allow-list (isAllowed / VC request) |
| GasFees | registry("gasfees") | Base gas fee reads / updates |
| PriceFeed | registry("pricefeed") | RBNT/USD price, decimals, updates |
| NetworkConfiguration | registry("networkconfiguration") | Network size / boot nodes |
| AllowedList + BusinessIdentifierFactory | /opt/sevm/network-config.json | KYB deploy / registration / roles |
| Field | Value |
|---|---|
| Address | 0xd846B0bA0a0D99FA7449eD42e65760F4a0e617cb |
| Private key | c8744d99f848ce750f47e75779c4a817009de9b20e56a321ec68cc7bbb33e473 |
Sole genesis-funded wallet for built-in tx helpers (default SIGNER / FROM) and local IDP admin for representative changes.
Baked into /opt/sevm-tx/scripts/shared/sandbox-accounts.ts so tx request-permission can mint a VC without extra setup:
| Field | Value |
|---|---|
| DID | did:key:z6MkjeGCfcWj8skMsyAx9qkZNStUX8crPHdaFaj8enbYcLC6 |
| Ed25519 private key (base64) | XxMbC7rCvo79wYuLmLLiRSCdvoVPiYeE3JFNPCvgYzVNHAAdtQttqV/hsoKkbtI1W4r4FCV3uwrs3L+e90zwlw== |
Public sandbox fixtures for this image only — do not reuse on testnet or mainnet.
| Command | How the funded key is used |
|---|---|
send-funds | Signs and sends RBNT to required TO |
get-balance | Default address when ADDRESS omitted |
deploy-business | Signs deploy / AllowedList registration unless SIGNER overridden |
add-representative / remove-representative | Typical AUTH_SIGNER (IDP admin) |
add-delegate / remove-delegate | IDP admin or authorised representative |
Override with -e SIGNER=<key> / -e FROM=<address> on docker exec. For transfer-funds and request-permission, pass WALLET_PRIVATE_KEY instead.
docker run --rm -p 8547:8547 --name sevm-local redbellynetwork/sevm-local:latest
docker run -d --name sevm-local -p 8547:8547 redbellynetwork/sevm-local:latest
docker logs -f sevm-local
docker run --rm -p 8547:8547 \
-v sevm-local-data:/data \
--name sevm-local \
redbellynetwork/sevm-local:latest
docker run --rm -p 18547:8547 --name sevm-local redbellynetwork/sevm-local:latest
# RPC → http://127.0.0.1:18547
docker run --rm redbellynetwork/sevm-local:latest help
tx commandsCommands talk to the node inside the container (http://127.0.0.1:8547). Override with -e on docker exec.
docker exec sevm-local tx <command>
| Command | Purpose | Required env |
|---|---|---|
check-rpc | Verify JSON-RPC / chain id | — |
send-funds | Send RBNT from the default funded account | TO and exactly one of AMOUNT_WEI / AMOUNT_RBNT |
transfer-funds | Transfer RBNT from a supplied wallet | FROM, TO, WALLET_PRIVATE_KEY, and exactly one of AMOUNT_WEI / AMOUNT_RBNT |
get-balance | Print balance in wei (uRBNT) and RBNT | optional ADDRESS |
check-permission | Check if an address is permissioned | ADDRESS |
request-permission | Request network permission for a wallet | ADDRESS, WALLET_PRIVATE_KEY |
read-gas-fees | Read GasFees | — |
set-gas-fees | Update base gas fee | BASE_GAS_FEE |
read-price-feed | Read PriceFeed latest price / threshold | — |
read-price-decimals | Read PriceFeed decimals() | — |
set-price | Update RBNT price | PRICE |
read-network-config | Read NetworkConfiguration | — |
read-product-contracts | Print AllowedList / factory addresses | — |
deploy-business | Deploy BusinessIdentifier + register in AllowedList | see below |
get-business | Read KYB metadata / roles | BI_ADDRESS or BUSINESS_WALLET; optional ADDRESS |
add-representative | Add authorised representative(s) — IDP admin only | BI_ADDRESS or BUSINESS_WALLET, ADDRESSES, AUTH_SIGNER |
remove-representative | Remove authorised representative(s) — IDP admin only | same as add |
add-delegate | Add authorised delegate(s) — IDP admin or authorised representative | BI_ADDRESS or BUSINESS_WALLET, ADDRESSES, AUTH_SIGNER |
remove-delegate | Remove authorised delegate(s) — IDP admin or authorised representative | same as add |
# Health
docker exec sevm-local tx check-rpc
# Transfers (AMOUNT_WEI or AMOUNT_RBNT required)
docker exec -e TO=0xYourAddress -e AMOUNT_RBNT=1.5 -e WAIT_FOR_RECEIPT=true \
sevm-local tx send-funds
docker exec -e TO=0xYourAddress -e AMOUNT_WEI=500000000000000000 -e WAIT_FOR_RECEIPT=true \
sevm-local tx send-funds
docker exec -e WALLET_PRIVATE_KEY=<hex> -e FROM=0xSender -e TO=0xRecipient -e AMOUNT_RBNT=1 \
sevm-local tx transfer-funds
# Balances & permissions
docker exec sevm-local tx get-balance
docker exec -e ADDRESS=0xYourAddress sevm-local tx get-balance
docker exec -e ADDRESS=0xYourAddress sevm-local tx check-permission
# Builds a sample-IDP verifiable credential and calls Permission.request
docker exec -e ADDRESS=0xYourAddress -e WALLET_PRIVATE_KEY=<hex-matching-address> \
sevm-local tx request-permission
# Bootstrap reads & writes
docker exec sevm-local tx read-gas-fees
docker exec -e BASE_GAS_FEE=10000 -e WAIT_FOR_RECEIPT=true sevm-local tx set-gas-fees
docker exec sevm-local tx read-price-feed
docker exec sevm-local tx read-price-decimals
docker exec -e PRICE=2000000 -e WAIT_FOR_RECEIPT=true sevm-local tx set-price
docker exec sevm-local tx read-network-config
docker exec sevm-local tx read-product-contracts
# ---------------------------------------------------------------------------
# Full KYB flow (docker exec)
# ---------------------------------------------------------------------------
# Roles:
# - IDP admin = default funded account (SIGNER / AUTH_SIGNER below)
# - Business wallet = owner wallet passed to deploy-business; after deploy it
# is also an authorised representative
# - Authorised representative = can add/remove delegates (and gets network permission)
# - Authorised delegate = gets network permission via the business permission path
#
# 1) Deploy BusinessIdentifier for the business wallet and register it
docker exec \
-e BUSINESS_WALLET=0xBusinessOwner \
-e COMPANY_NAME="Acme Pty Ltd" \
-e INCORPORATED_NAME="Acme Pty Ltd" \
-e IDENTIFIER_TYPE=ABN \
-e IDENTIFIER=12345678901 \
-e BUSINESS_ADDRESS="1 Test St" \
-e IS_BENEFICIAL_OWNER=true \
sevm-local tx deploy-business
# → prints BI_ADDRESS; business wallet is already an authorised representative
# 2) IDP admin adds another authorised representative (grants network permission)
docker exec \
-e BI_ADDRESS=0xBiAddress \
-e ADDRESSES=0xNewRep \
-e AUTH_SIGNER=c8744d99f848ce750f47e75779c4a817009de9b20e56a321ec68cc7bbb33e473 \
sevm-local tx add-representative
docker exec -e ADDRESS=0xNewRep sevm-local tx check-permission
# 3a) Add a delegate as IDP admin
docker exec \
-e BI_ADDRESS=0xBiAddress \
-e ADDRESSES=0xDelegateViaIdp \
-e AUTH_SIGNER=c8744d99f848ce750f47e75779c4a817009de9b20e56a321ec68cc7bbb33e473 \
sevm-local tx add-delegate
docker exec -e ADDRESS=0xDelegateViaIdp sevm-local tx check-permission
# 3b) Add a delegate as the business owner (authorised representative key)
# Fund the business wallet first if it will sign txs.
docker exec -e TO=0xBusinessOwner -e AMOUNT_RBNT=1 sevm-local tx send-funds
docker exec \
-e BI_ADDRESS=0xBiAddress \
-e ADDRESSES=0xDelegateViaOwner \
-e AUTH_SIGNER=<business-owner-private-key> \
sevm-local tx add-delegate
# Confirm BI role + network permission on the delegate
docker exec -e BI_ADDRESS=0xBiAddress -e ADDRESS=0xDelegateViaOwner sevm-local tx get-business
docker exec -e ADDRESS=0xDelegateViaOwner sevm-local tx check-permission
# 3c) Same as 3b, but AUTH_SIGNER is another authorised representative (not the owner)
docker exec \
-e BI_ADDRESS=0xBiAddress \
-e ADDRESSES=0xDelegateViaRep \
-e AUTH_SIGNER=<authorised-representative-private-key> \
sevm-local tx add-delegate
docker exec -e ADDRESS=0xDelegateViaRep sevm-local tx check-permission
# 4) Remove roles (mirrors who may add them)
docker exec \
-e BI_ADDRESS=0xBiAddress \
-e ADDRESSES=0xDelegateViaOwner \
-e AUTH_SIGNER=<business-owner-or-rep-private-key> \
sevm-local tx remove-delegate
docker exec \
-e BI_ADDRESS=0xBiAddress \
-e ADDRESSES=0xNewRep \
-e AUTH_SIGNER=c8744d99f848ce750f47e75779c4a817009de9b20e56a321ec68cc7bbb33e473 \
sevm-local tx remove-representative
| Variable | Default | Description |
|---|---|---|
RPC_URL | http://127.0.0.1:8547 | JSON-RPC endpoint (inside container) |
CHAIN_ID | 161 | Chain id |
SIGNER | c8744d99f848ce750f47e75779c4a817009de9b20e56a321ec68cc7bbb33e473 | Default sender private key (0xd846…17cb) |
FROM | — | Sender (transfer-funds; optional override for send-funds) |
TO | — | Recipient (send-funds, transfer-funds) |
AMOUNT_WEI | — | Transfer amount in wei (exactly one of AMOUNT_WEI / AMOUNT_RBNT) |
AMOUNT_RBNT | — | Transfer amount in RBNT, up to 18 decimals (converted to wei) |
PRICE | — | New price for set-price |
BASE_GAS_FEE | — | New base gas fee for set-gas-fees |
ADDRESS | — | Target wallet for balance / permission |
WALLET_PRIVATE_KEY | — | Key for transfer-funds / request-permission (must match FROM for transfers) |
AUTH_SIGNER | — | IDP admin (reps) or IDP admin / authorised representative (delegates) |
ADDRESSES | — | Comma-separated addresses for role commands |
BOOTSTRAP_REGISTRY | 0xDAFE…8Bc5 | Bootstrap registry |
WAIT_FOR_RECEIPT | — | Set true to wait for confirmation |
RECEIPT_TIMEOUT_MS | 30000 | Receipt wait timeout |
Business deploy also accepts: BUSINESS_WALLET, COMPANY_NAME, INCORPORATED_NAME, IDENTIFIER_TYPE, IDENTIFIER, BUSINESS_ADDRESS, IS_BENEFICIAL_OWNER, BI_ADDRESS.
With the container running and port 8547 published, use any Ethereum client against http://127.0.0.1:8547. Shared constants:
import { Contract, JsonRpcProvider, Wallet, formatEther, parseEther } from "ethers";
const RPC = "http://127.0.0.1:8547";
const CHAIN_ID = 161;
const BOOTSTRAP_REGISTRY = "0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5";
const FUNDED_PK = "c8744d99f848ce750f47e75779c4a817009de9b20e56a321ec68cc7bbb33e473";
const FUNDED = "0xd846B0bA0a0D99FA7449eD42e65760F4a0e617cb";
const IDP_DID = "did:key:z6MkjeGCfcWj8skMsyAx9qkZNStUX8crPHdaFaj8enbYcLC6";
const REGISTRY_ABI = ["function registry(string) view returns (address)"];
const GAS_FEES_ABI = [
"function getBaseGasFee() view returns (uint256, string, uint8)",
"function updateGasFee(uint256 baseGasFee)",
];
const PRICE_FEED_ABI = [
"function getLatestPrice() view returns (int256, uint256, bool)",
"function thresholdForNextUpdate() view returns (uint256)",
"function decimals() view returns (uint8)",
"function setPrice(uint256 newPrice)",
];
const NETWORK_CONFIG_ABI = [
"function getNetworkSize() view returns (uint256)",
"function getBootNodeAddresses() view returns (address[])",
];
const PERMISSION_ABI = [
"function isAllowed(address) view returns (bool)",
"function isPermissionedAccessEnabled() view returns (bool)",
"function enablePermissionedAccess()",
"function request(string issuerDid, string vcs, string proofSignature)",
];
const FACTORY_ABI = [
"function deployContract(address,string,string,string,string,string,bool)",
"function businessContracts(address) view returns (address)",
];
const ALLOWED_LIST_ABI = [
"function registerBusiness(address)",
"function isBusinessIdentifierValid(address) view returns (bool)",
];
const BI_ABI = [
"function AUTHORISED_REPRESENTATIVE_ROLE() view returns (bytes32)",
"function AUTHORISED_DELEGATE_ROLE() view returns (bytes32)",
"function hasRole(bytes32,address) view returns (bool)",
"function addAuthorisedRepresentative(address[])",
"function removeAuthorisedRepresentative(address[])",
"function addAuthorisedDelegate(address[])",
"function removeAuthorisedDelegate(address[])",
];
const provider = new JsonRpcProvider(RPC, CHAIN_ID);
const wallet = new Wallet(FUNDED_PK, provider); // IDP admin / funded account
const registry = new Contract(BOOTSTRAP_REGISTRY, REGISTRY_ABI, provider);
Product addresses (copy once from the running container):
docker exec sevm-local cat /opt/sevm/network-config.json
{
"chainId": 161,
"redbellyRegistry": "0x...",
"allowedList": "0x...",
"businessIdentifierFactory": "0x..."
}
console.log("chainId", (await provider.getNetwork()).chainId);
console.log("balance RBNT", formatEther(await provider.getBalance(FUNDED)));
const tx = await wallet.sendTransaction({
to: "0xYourAddress",
value: parseEther("1.5"),
});
console.log("tx", tx.hash);
await tx.wait();
const permission = new Contract(
await registry.registry("permission"),
PERMISSION_ABI,
provider,
);
console.log("permissioned access enabled", await permission.isPermissionedAccessEnabled());
console.log("isAllowed(FUNDED)", await permission.isAllowed(FUNDED));
console.log("isAllowed(0xYourAddress)", await permission.isAllowed("0xYourAddress"));
Prefer the built-in helper for a local allow-list entry (sample IDP VC):
docker exec -e ADDRESS=0xYourAddress -e WALLET_PRIVATE_KEY=<matching-hex> \
sevm-local tx request-permission
Ethers shape (grantee signs request; supply VC + proof for the baked sample IDP — easiest via tx request-permission):
const grantee = new Wallet("<grantee-private-key>", provider);
const permissionAsGrantee = new Contract(
await registry.registry("permission"),
PERMISSION_ABI,
grantee,
);
const requestTx = await permissionAsGrantee.request(IDP_DID, vcCanonical, proofCanonical);
await requestTx.wait();
console.log("isAllowed", await permissionAsGrantee.isAllowed(grantee.address));
const gasFees = new Contract(await registry.registry("gasfees"), GAS_FEES_ABI, wallet);
const [base, currency, decimals] = await gasFees.getBaseGasFee();
console.log({ base: base.toString(), currency, decimals });
const updateGasTx = await gasFees.updateGasFee(10_000n);
await updateGasTx.wait();
const priceFeed = new Contract(await registry.registry("pricefeed"), PRICE_FEED_ABI, wallet);
const [price, updatedAt, valid] = await priceFeed.getLatestPrice();
console.log({
price: price.toString(),
updatedAt: updatedAt.toString(),
valid,
threshold: (await priceFeed.thresholdForNextUpdate()).toString(),
decimals: await priceFeed.decimals(), // typically 6
});
const updatePriceTx = await priceFeed.setPrice(2_000_000n);
await updatePriceTx.wait();
Or via the helper:
docker exec sevm-local tx read-price-decimals
const networkConfig = new Contract(
await registry.registry("networkconfiguration"),
NETWORK_CONFIG_ABI,
provider,
);
console.log("network size", (await networkConfig.getNetworkSize()).toString());
console.log("boot nodes", await networkConfig.getBootNodeAddresses());
Replace allowedList / businessIdentifierFactory from network-config.json. After deployContract, the business wallet becomes an authorised representative (same as tx deploy-business).
const { allowedList: ALLOWED_LIST, businessIdentifierFactory: FACTORY } = {
allowedList: "0x...",
businessIdentifierFactory: "0x...",
};
const businessOwner = Wallet.createRandom().connect(provider);
const otherRep = Wallet.createRandom().connect(provider);
const delegateViaIdp = Wallet.createRandom().connect(provider);
const delegateViaOwner = Wallet.createRandom().connect(provider);
const delegateViaRep = Wallet.createRandom().connect(provider);
// Fund wallets that will send txs
for (const addr of [businessOwner.address, otherRep.address]) {
await (await wallet.sendTransaction({ to: addr, value: parseEther("1") })).wait();
}
const idpAdmin = wallet; // DEFAULT_ADMIN on BusinessIdentifier for local image
const factory = new Contract(FACTORY, FACTORY_ABI, idpAdmin);
const allowedList = new Contract(ALLOWED_LIST, ALLOWED_LIST_ABI, idpAdmin);
// 1) Deploy + register (IDP admin)
await (await factory.deployContract(
businessOwner.address,
"Acme Pty Ltd",
"Acme Pty Ltd",
"ABN",
"12345678901",
"1 Test St",
true,
)).wait();
const biAddress = await factory.businessContracts(businessOwner.address);
await (await allowedList.registerBusiness(biAddress)).wait();
// Make business owner an authorised representative (IDP admin only)
const biAsIdp = new Contract(biAddress, BI_ABI, idpAdmin);
await (await biAsIdp.addAuthorisedRepresentative([businessOwner.address])).wait();
const permission = new Contract(await registry.registry("permission"), PERMISSION_ABI, provider);
const repRole = await biAsIdp.AUTHORISED_REPRESENTATIVE_ROLE();
const delegateRole = await biAsIdp.AUTHORISED_DELEGATE_ROLE();
// 2) IDP admin adds another representative (also grants network permission)
await (await biAsIdp.addAuthorisedRepresentative([otherRep.address])).wait();
console.log("otherRep is rep", await biAsIdp.hasRole(repRole, otherRep.address));
console.log("otherRep isAllowed", await permission.isAllowed(otherRep.address));
// 3a) IDP admin adds a delegate
await (await biAsIdp.addAuthorisedDelegate([delegateViaIdp.address])).wait();
console.log("delegateViaIdp role", await biAsIdp.hasRole(delegateRole, delegateViaIdp.address));
console.log("delegateViaIdp isAllowed", await permission.isAllowed(delegateViaIdp.address));
// 3b) Business owner (authorised representative) adds a delegate
const biAsOwner = new Contract(biAddress, BI_ABI, businessOwner);
await (await biAsOwner.addAuthorisedDelegate([delegateViaOwner.address])).wait();
console.log("delegateViaOwner role", await biAsOwner.hasRole(delegateRole, delegateViaOwner.address));
console.log("delegateViaOwner isAllowed", await permission.isAllowed(delegateViaOwner.address));
// 3c) Other authorised representative adds a delegate
const biAsRep = new Contract(biAddress, BI_ABI, otherRep);
await (await biAsRep.addAuthorisedDelegate([delegateViaRep.address])).wait();
console.log("delegateViaRep role", await biAsRep.hasRole(delegateRole, delegateViaRep.address));
console.log("delegateViaRep isAllowed", await permission.isAllowed(delegateViaRep.address));
// 4) Remove (same callers as add)
await (await biAsOwner.removeAuthorisedDelegate([delegateViaOwner.address])).wait();
await (await biAsIdp.removeAuthorisedRepresentative([otherRep.address])).wait();
console.log("removed delegate role", await biAsOwner.hasRole(delegateRole, delegateViaOwner.address));
console.log("removed rep role", await biAsIdp.hasRole(repRole, otherRep.address));
docker pull redbellynetwork/sevm-local:<tag> (e.g. latest or bin-v1.3.17-core-v0.47.2-id-v1.2.13)tx check-rpc succeedssend-funds with TO)request-permission):8547deploy-business and role commands| Item | Value |
|---|---|
| Exposed port | 8547 (JSON-RPC: eth, net, web3, rbn) |
| Volume | /data (chain datadir) |
| Entrypoint | starts the node; tx / help are alternate modes |
| Platforms | linux/amd64, linux/arm64 (multi-arch manifest) |
Content type
Image
Digest
sha256:2631adebe…
Size
137.5 MB
Last updated
3 months ago
docker pull redbellynetwork/sevm-local