Celo mainnet
TypeScript
abapay-sdk
Two functions, matching the two protocol-driven paths on this site. Signs with whatever viem account your agent already has — nothing hidden, the source is the same wire format documented on /agents/x402 and /agents/a2a.
Live on npm —
npmjs.com →npm install abapay-sdk viemZero setup: payBillViaX402
Fetches the 402 challenge, signs it, retries, returns the settlement.
import { privateKeyToAccount } from "viem/accounts";
import { payBillViaX402 } from "abapay-sdk";
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const result = await payBillViaX402({
signer: account,
bill: {
serviceID: "mtn", serviceCategory: "AIRTIME",
network: "MTN", billersCode: "08012345678",
nairaAmount: 1000, token: "USDC",
},
});
console.log(result.status, result.tx_hash);Linked wallet: AbaPayAgent
Links once, sets the PIN in that same call, then reuses the api_key for the full tool catalog.
import { privateKeyToAccount } from "viem/accounts";
import { AbaPayAgent } from "abapay-sdk";
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
// One-time -- no browser, PIN chosen right here.
const agent = await AbaPayAgent.link({ signer: account, pin: "123456" });
console.log(await agent.checkBalance());
await agent.payBill({
pin: "123456", service: "AIRTIME", provider: "mtn",
account_number: "08012345678", amount_ngn: 1000,
});Full function reference
| Export | Signature | Description |
|---|---|---|
payBillViaX402 | (params: { signer, bill, baseUrl? }) => Promise<X402PayResult> | Zero-setup path. Fetches the 402 challenge, signs it, retries, returns the settlement. |
AbaPayAgent.link | static (params: LinkParams) => Promise<AbaPayAgent> | Links a wallet with a plain signed message and mints an api_key. Does NOT grant any on-chain allowance itself — approve() + setSpendingAllowance() stay a separate, explicit step. |
AbaPayAgent.fromApiKey | static (apiKey, walletAddress, baseUrl?) => AbaPayAgent | Reattach to an api_key minted earlier — no new signature needed. |
agent.callTool | (name: string, args?: object) => Promise<string> | Low-level: call any of the 11 MCP tools by name. Every typed method below is a thin wrapper over this. |
agent.checkBalance | (chain?: 'CELO') => Promise<string> | Wraps check_balance. |
agent.payBill | (args: { pin, service, provider, account_number, amount_ngn, chain?, token?, variation_code? }) => Promise<string> | Wraps pay_bill. |
agent.scheduleBill | (args: object) => Promise<string> | Wraps schedule_bill — same fields as the MCP tool, see the Tools reference. |
agent.transactionHistory | (args?: object) => Promise<string> | Wraps transaction_history. |
Types
| Type | Shape |
|---|---|
BillDetails | { serviceID, serviceCategory, network, billersCode, nairaAmount, token: 'USDC'|'USD₮'|'USA₮', wallet_address } |
X402PayResult | { success, status: 'SUCCESS'|'FAILED_VENDING'|'TIMEOUT'|string, purchased_code?, units?, request_id?, tx_hash?, message? } |
LinkParams | { signer, pin, approvedChain?: 'CELO', approvedToken?, label?, baseUrl? } |
AbaPayError | extends Error — carries .cause and .response for the original failure |
Correctness, not just types
A test in the package asserts the signed typed-data's domain and message match a real 402 challenge byte-for-byte — the exact contract AbaPay's own server-side verification reconstructs to check the signature against. A silent drift there would be a signature that fails to verify; the test exists so that drift fails loudly instead.
Source, tests, and the full README →