Sign inSign up

redbellynetwork/sevm-local

By redbellynetwork

•Updated about 1 month ago

Single-node Redbelly loop-consensus chain for local dApp testing before testnet & mainnet.

Image
Networking
0

583

redbellynetwork/sevm-local repository overview

⁠redbelly-network/sevm-local

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.


⁠Quick start

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:

SettingValue
RPC URLhttp://127.0.0.1:8547
Chain ID161
Native tokenRBNT (18 decimals)

⁠Supported tags

Prefer an immutable version tag. Tags encode three independently versioned pieces:

ComponentMeaning
bin-…SEVM node binary
core-…Core / bootstrap contracts
id-…KYB contracts (AllowedList + BusinessIdentifier factory)
⁠Tag formats
TagMeaning
latestNewest 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:

TagWhat you get
latestNo component versions were specified at build time
bin-v1.3.17-core-v0.47.2-id-v1.2.13Binary 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

⁠What is baked into the image

  • SEVM node running loop consensus (CONSENSUS=loop)
  • Genesis hard forks enabled (Cancun / Prague / Maroubra / Malabar at time 0)
  • Core bootstrap contracts (lookup via registry below)
  • KYB product contracts (AllowedList + BusinessIdentifier factory)
  • Bootstrap registry at 0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5
  • Product addresses written to /opt/sevm/network-config.json
  • tx CLI (ethers-based helpers) available via docker exec

Local / development only. Genesis keys and sample IDP credentials are public test values. Never reuse them on testnet or mainnet.

⁠Contracts the helpers talk to
NameHow you reach itUsed for
Bootstrap registryfixed address aboveResolve permission, gasfees, pricefeed, networkconfiguration, …
Permissionregistry("permission")Network allow-list (isAllowed / VC request)
GasFeesregistry("gasfees")Base gas fee reads / updates
PriceFeedregistry("pricefeed")RBNT/USD price, decimals, updates
NetworkConfigurationregistry("networkconfiguration")Network size / boot nodes
AllowedList + BusinessIdentifierFactory/opt/sevm/network-config.jsonKYB deploy / registration / roles
⁠Default funded account (local only)
FieldValue
Address0xd846B0bA0a0D99FA7449eD42e65760F4a0e617cb
Private keyc8744d99f848ce750f47e75779c4a817009de9b20e56a321ec68cc7bbb33e473

Sole genesis-funded wallet for built-in tx helpers (default SIGNER / FROM) and local IDP admin for representative changes.

⁠Sample IDP signing key (local only)

Baked into /opt/sevm-tx/scripts/shared/sandbox-accounts.ts so tx request-permission can mint a VC without extra setup:

FieldValue
DIDdid:key:z6MkjeGCfcWj8skMsyAx9qkZNStUX8crPHdaFaj8enbYcLC6
Ed25519 private key (base64)XxMbC7rCvo79wYuLmLLiRSCdvoVPiYeE3JFNPCvgYzVNHAAdtQttqV/hsoKkbtI1W4r4FCV3uwrs3L+e90zwlw==

Public sandbox fixtures for this image only — do not reuse on testnet or mainnet.

CommandHow the funded key is used
send-fundsSigns and sends RBNT to required TO
get-balanceDefault address when ADDRESS omitted
deploy-businessSigns deploy / AllowedList registration unless SIGNER overridden
add-representative / remove-representativeTypical AUTH_SIGNER (IDP admin)
add-delegate / remove-delegateIDP 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.


⁠Run options

⁠Foreground (logs in terminal)
docker run --rm -p 8547:8547 --name sevm-local redbellynetwork/sevm-local:latest
⁠Detached
docker run -d --name sevm-local -p 8547:8547 redbellynetwork/sevm-local:latest
docker logs -f sevm-local
⁠Persist chain data
docker run --rm -p 8547:8547 \
  -v sevm-local-data:/data \
  --name sevm-local \
  redbellynetwork/sevm-local:latest
⁠Custom host port
docker run --rm -p 18547:8547 --name sevm-local redbellynetwork/sevm-local:latest
# RPC → http://127.0.0.1:18547
⁠Help
docker run --rm redbellynetwork/sevm-local:latest help

⁠Built-in tx commands

Commands 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 reference
CommandPurposeRequired env
check-rpcVerify JSON-RPC / chain id—
send-fundsSend RBNT from the default funded accountTO and exactly one of AMOUNT_WEI / AMOUNT_RBNT
transfer-fundsTransfer RBNT from a supplied walletFROM, TO, WALLET_PRIVATE_KEY, and exactly one of AMOUNT_WEI / AMOUNT_RBNT
get-balancePrint balance in wei (uRBNT) and RBNToptional ADDRESS
check-permissionCheck if an address is permissionedADDRESS
request-permissionRequest network permission for a walletADDRESS, WALLET_PRIVATE_KEY
read-gas-feesRead GasFees—
set-gas-feesUpdate base gas feeBASE_GAS_FEE
read-price-feedRead PriceFeed latest price / threshold—
read-price-decimalsRead PriceFeed decimals()—
set-priceUpdate RBNT pricePRICE
read-network-configRead NetworkConfiguration—
read-product-contractsPrint AllowedList / factory addresses—
deploy-businessDeploy BusinessIdentifier + register in AllowedListsee below
get-businessRead KYB metadata / rolesBI_ADDRESS or BUSINESS_WALLET; optional ADDRESS
add-representativeAdd authorised representative(s) — IDP admin onlyBI_ADDRESS or BUSINESS_WALLET, ADDRESSES, AUTH_SIGNER
remove-representativeRemove authorised representative(s) — IDP admin onlysame as add
add-delegateAdd authorised delegate(s) — IDP admin or authorised representativeBI_ADDRESS or BUSINESS_WALLET, ADDRESSES, AUTH_SIGNER
remove-delegateRemove authorised delegate(s) — IDP admin or authorised representativesame as add
⁠Examples
# 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
⁠Common environment variables
VariableDefaultDescription
RPC_URLhttp://127.0.0.1:8547JSON-RPC endpoint (inside container)
CHAIN_ID161Chain id
SIGNERc8744d99f848ce750f47e75779c4a817009de9b20e56a321ec68cc7bbb33e473Default 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_REGISTRY0xDAFE…8Bc5Bootstrap registry
WAIT_FOR_RECEIPT—Set true to wait for confirmation
RECEIPT_TIMEOUT_MS30000Receipt wait timeout

Business deploy also accepts: BUSINESS_WALLET, COMPANY_NAME, INCORPORATED_NAME, IDENTIFIER_TYPE, IDENTIFIER, BUSINESS_ADDRESS, IS_BENEFICIAL_OWNER, BI_ADDRESS.


⁠Programmatic access (ethers)

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..."
}
⁠Connect + balance
console.log("chainId", (await provider.getNetwork()).chainId);
console.log("balance RBNT", formatEther(await provider.getBalance(FUNDED)));
⁠Transfer RBNT from the funded account
const tx = await wallet.sendTransaction({
  to: "0xYourAddress",
  value: parseEther("1.5"),
});
console.log("tx", tx.hash);
await tx.wait();
⁠Check permission
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"));
⁠Request permission (VC flow)

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));
⁠Read / update gas fees
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();
⁠Read / update price feed
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
⁠Read network configuration
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());
⁠Full KYB flow (programmatic)

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));

⁠Typical local workflow before testnet

  1. docker pull redbellynetwork/sevm-local:<tag> (e.g. latest or bin-v1.3.17-core-v0.47.2-id-v1.2.13)
  2. Start the node and wait until tx check-rpc succeeds
  3. Fund your dApp deployer / user wallets (send-funds with TO)
  4. Request permission if your flow requires a permissioned sender (request-permission)
  5. Deploy or interact with your contracts via Hardhat / Foundry / ethers against :8547
  6. Optionally exercise KYB flows with deploy-business and role commands
  7. When ready, retarget the same scripts at Redbelly testnet, then mainnet (new keys, real RPC URLs)

⁠Image details

ItemValue
Exposed port8547 (JSON-RPC: eth, net, web3, rbn)
Volume/data (chain datadir)
Entrypointstarts the node; tx / help are alternate modes
Platformslinux/amd64, linux/arm64 (multi-arch manifest)

⁠Security

  • Not for production consensus — single-node loop-consensus sandbox only.
  • Default private keys are public fixtures. Use your own keys for shared or long-lived environments.

Tag summary

Content type

Image

Digest

sha256:2631adebe…

Size

137.5 MB

Last updated

3 months ago

docker pull redbellynetwork/sevm-local