/** * 0G Router Payment Layer ops, the prepaid credit vault that funds Router * inference (the analog of the serving-broker compute ledger for the Direct * path, but a SINGLE shared credit balance, not per-provider envelopes). * * Topology + ABI verified live Jun 16 2026 (see `reference-0g-router-payment-layer`): * a beacon proxy over a credit vault. `balanceOf(account)` reads credit; * `depositFor(account)` is payable-native (deposit 0G, credited to `account`). * * CRITICAL: deposits are NON-RECOVERABLE, there is no user-facing withdraw * (only an admin-gated `withdrawUnallocated`). Callers MUST deposit the minimum * and surface this to the operator. This is the real cost vs the Direct ledger * (which the agent can drain back via retrieveFund/refund). */ import { http, type Address, type Hex, createPublicClient, parseEther } from 'viem' import { getGasPriceWithFloor, makeViemClients, ogChain } from '../chain' import { type AnimaNetwork, NETWORK_RPC } from '../config' /** Payment Layer (beacon proxy) addresses. Verified Jun 16 2026. */ export const ROUTER_PAYMENT_LAYER: Record = { '0g-mainnet': '0xA3b15Bd2aD18BFB6b5f92D8AA9F444Dd59d1cE32', '0g-testnet': '0x0AD9690e0b34aB2d493DE02cDF149ee34f6C9939', } export const PAYMENT_LAYER_ABI = [ { type: 'function', name: 'balanceOf', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], }, { type: 'function', name: 'depositFor', stateMutability: 'payable', inputs: [{ name: 'account', type: 'address' }], outputs: [], }, ] as const function paymentLayerFor(network: AnimaNetwork, override?: Address): Address { return override ?? ROUTER_PAYMENT_LAYER[network] } /** * Read the agent's prepaid Router credit (Payment Layer `balanceOf`), in 0G. * Returns null on RPC failure. Read-only: no key needed. */ export async function getRouterCreditBalance(opts: { network: AnimaNetwork account: Address paymentLayer?: Address }): Promise { try { const publicClient = createPublicClient({ chain: ogChain(opts.network), transport: http(NETWORK_RPC[opts.network]), }) const wei = (await publicClient.readContract({ address: paymentLayerFor(opts.network, opts.paymentLayer), abi: PAYMENT_LAYER_ABI, functionName: 'balanceOf', args: [opts.account], })) as bigint return Number(wei) / 1e18 } catch { return null } } /** * Deposit `amountOg` 0G into the Payment Layer, credited to `account` (defaults * to the depositor). The agent EOA signs + pays. NON-RECOVERABLE, deposit the * minimum. Returns the tx hash. */ export async function depositRouterCredit(opts: { network: AnimaNetwork privkeyHex: Hex amountOg: number /** Beneficiary; defaults to the signer's own address. */ account?: Address paymentLayer?: Address }): Promise { // Guard tiny/invalid amounts: Number.toString() emits scientific notation // ("5e-7") for magnitudes < 1e-6, which parseEther rejects with a cryptic // "not a valid decimal number" error; and a sub-gas deposit is never intended. // Fail with a clear message instead. if (!Number.isFinite(opts.amountOg) || opts.amountOg < 1e-6) { throw new Error( `Router deposit amount (${opts.amountOg} 0G) is invalid or below the 0.000001 0G minimum; refusing to deposit.`, ) } const { walletClient, publicClient, account, chain } = makeViemClients({ network: opts.network, privkeyHex: opts.privkeyHex, }) const beneficiary = opts.account ?? account.address const gasPrice = await getGasPriceWithFloor(publicClient) return walletClient.writeContract({ address: paymentLayerFor(opts.network, opts.paymentLayer), abi: PAYMENT_LAYER_ABI, functionName: 'depositFor', args: [beneficiary], value: parseEther(opts.amountOg.toString() as `${number}`), account, chain, maxFeePerGas: gasPrice, maxPriorityFeePerGas: gasPrice, }) }