Network
Robinhood Chain
Purchase compute credits with USDG and anchor receipt batches on Robinhood Chain.
Overview
The platform uses Robinhood Chain for two things:
- Credit purchases. Pay with USDG to add compute credit to a project balance. Beta
- Receipt anchoring. Publish Merkle roots of receipt batches so anyone can check that a receipt existed at a point in time. Beta
API usage itself does not touch the chain. Requests are billed off-chain against the prepaid balance, and gas is needed only for the purchase transactions you send.
Networks
| Robinhood Chain | Robinhood Chain Testnet | |
|---|---|---|
| Chain ID | 4663 | 46630 |
| Gas token | ETH | ETH |
| Public RPC | https://rpc.mainnet.chain.robinhood.com | https://rpc.testnet.chain.robinhood.com |
| Explorer | robinhoodchain.blockscout.com (opens in a new tab) | Deployment-specific |
| USDG | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 | No canonical deployment; test deployments use a mock token |
Deployments use the testnet unless the operator explicitly configures mainnet. The console shows which network, token and contracts your deployment uses.
Contracts
Two contracts are deployed per network. Their addresses are deployment-specific and shown on the console Billing page.
ComputeCreditRouter
purchaseCreditsmovesamountUSDG from the caller directly to the treasury in a single transfer and emitsCreditPurchased. The router never holds funds and has no withdrawal function.- The treasury's balance is measured before and after the transfer. If it did not increase by exactly
amount(fee-on-transfer or rebasing tokens), the call reverts, so the emitted amount is always what the treasury received. - The token address is fixed at deployment. Zero amounts and a zero account ID revert. The owner can pause purchases and change the treasury; ownership transfers are two-step.
ReceiptAnchor
anchor(root, count)is restricted to allowlisted anchorers, assigns sequential batch IDs starting at 1, and rejects zero roots, zero counts and roots that were already anchored.anchoredAt(root)returns the block timestamp of anchoring, or 0.
// ComputeCreditRouter
function purchaseCredits(bytes32 accountId, uint256 amount) external;
function token() external view returns (address);
function treasury() external view returns (address);
function paused() external view returns (bool);
event CreditPurchased(
address indexed payer,
bytes32 indexed accountId,
address indexed token,
uint256 amount,
address treasury
);
// ReceiptAnchor
function anchor(bytes32 root, uint256 count) external returns (uint256 batchId);
function anchoredAt(bytes32 root) external view returns (uint64);
function batchCount() external view returns (uint256);
function isAnchorer(address account) external view returns (bool);
event ReceiptBatchAnchored(
uint256 indexed batchId,
bytes32 indexed root,
uint256 count,
uint256 timestamp,
address indexed anchorer
);Purchasing credits
- Open the console Billing page and connect a wallet on Robinhood Chain.
- Approve the router to spend the USDG amount (
approve(router, amount)). - Call
purchaseCredits(accountId, amount). USDG goes directly to the treasury and the router emitsCreditPurchased. - The backend independently verifies the transaction (see below) and credits the project once it has the required confirmations, 3 by default.
Credit is 1:1 with USD: 1 USDG buys 1,000,000 µUSD of compute credit. Token amounts are converted using the decimals read from the token contract, rounding down to the nearest µUSD.
Account ID
Purchases identify the project by a 32-byte account ID derived from the project ID. Always copy it from the Billing page; a purchase for an account ID that matches no project is not credited.
import { keccak256, stringToBytes } from "viem";
// The console's Billing page shows this value for your project.
const accountId = keccak256(stringToBytes(`account:${projectId}`));Purchase example
The console performs this flow for you. The equivalent with viem:
import {
createPublicClient,
createWalletClient,
custom,
defineChain,
http,
parseAbi,
parseUnits,
type Address,
type Hex,
} from "viem";
const robinhoodChain = defineChain({
id: 4663,
name: "Robinhood Chain",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: { default: { http: ["https://rpc.mainnet.chain.robinhood.com"] } },
blockExplorers: { default: { name: "Blockscout", url: "https://robinhoodchain.blockscout.com" } },
});
const USDG: Address = "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168";
const ROUTER = "0x…" as Address; // ComputeCreditRouter address, from the console Billing page
const ACCOUNT_ID = "0x…" as Hex; // your project's account ID, from the console Billing page
const erc20 = parseAbi([
"function decimals() view returns (uint8)",
"function approve(address spender, uint256 amount) returns (bool)",
]);
const router = parseAbi(["function purchaseCredits(bytes32 accountId, uint256 amount)"]);
const publicClient = createPublicClient({ chain: robinhoodChain, transport: http() });
const walletClient = createWalletClient({ chain: robinhoodChain, transport: custom(window.ethereum!) });
const [account] = await walletClient.requestAddresses();
// Never assume decimals: read them from the token.
const decimals = await publicClient.readContract({ address: USDG, abi: erc20, functionName: "decimals" });
const amount = parseUnits("25", decimals); // 25 USDG → $25.00 of compute credit
const approval = await walletClient.writeContract({
account, address: USDG, abi: erc20, functionName: "approve", args: [ROUTER, amount],
});
await publicClient.waitForTransactionReceipt({ hash: approval });
const purchase = await walletClient.writeContract({
account, address: ROUTER, abi: router, functionName: "purchaseCredits", args: [ACCOUNT_ID, amount],
});
await publicClient.waitForTransactionReceipt({ hash: purchase, confirmations: 3 });How purchases are verified
The transaction hash submitted from the browser is only a hint. Before crediting, the backend fetches the transaction receipt from its own RPC and checks:
- the RPC's chain ID matches the configured network,
- the transaction exists and succeeded (reverted transactions are rejected),
- it has at least the required number of confirmations,
- the log was emitted by the configured ComputeCreditRouter and decodes as CreditPurchased,
- the token is the configured USDG and the treasury is the configured treasury,
- the amount is positive and the account ID belongs to a project,
- the token's decimals, read on-chain, for conversion to µUSD.
Crediting is idempotent per (chain ID, transaction hash, log index), enforced both by a unique database index and by the ledger entry's idempotency key. Submitting the same transaction twice credits it once. A background indexer also scans router events from the deployment block onward, so purchases are credited even if the browser is closed before confirmation.
Receipt anchoring
Receipt hashes are batched into Merkle trees and the roots are anchored with ReceiptAnchor.anchor. Broadcasting is enabled by the operator; until then batches are built but not sent. See Receipts for tree construction and Receipt Verification to check a root on-chain.
Affiliation
The platform builds on Robinhood Chain. It is not affiliated with, sponsored by or endorsed by Robinhood. USDG is issued by a third party; the platform does not issue or redeem it.