abapay-sdk
v0.5.1
Published
Pay real-world bills (airtime, data, electricity, cable) from an agent's own Celo wallet — zero-setup x402, or a linked-wallet client for the fuller MCP tool catalog.
Maintainers
Readme
abapay-sdk
Pay real-world bills — airtime, mobile data, electricity, cable TV — from an agent's own Celo wallet. Two ways in, matching agents.abapays.com:
payBillViaX402— zero setup. No account, no API key, no PIN, no visit to abapays.com. Your agent's wallet signs one EIP-3009 authorization and the bill is paid.AbaPayAgent— a linked-wallet client for the fuller MCP tool catalog (balances, history, schedules, batch payments). One on-chain signature to link, then an API key + PIN per payment.
Full protocol details: docs/AGENT_INTEGRATION.md. Celo mainnet only in this version.
Install
npm install abapay-sdk viemviem is a peer dependency — this package signs through whatever Account/WalletClient your
own viem install already gives you, rather than bundling a second copy.
Quickstart on Celo (about 5 minutes)
- A wallet. Any EVM key works; for a fresh one:
node -e "console.log(require('viem/accounts').generatePrivateKey())" - Fund it on Celo with about $1 of USDC, USD₮ or USA₮. You don't need CELO: with x402 the facilitator submits the transfer and pays the gas.
- Pay ₦100 of MTN airtime (well under $1, fees included):
npm install abapay-sdk viem
PRIVATE_KEY=0x... npx abapay pay --service AIRTIME --provider mtn --to 08012345678 --amount 100The output carries the Celo transaction hash: look it up on celoscan.io to see the payment. The same thing in code is below.
For a complete agent built on this package (spending cap, approval prompt, plain-English requests), see abapay-reference-agent.
Zero setup: pay a bill via x402
import { privateKeyToAccount } from "viem/accounts";
import { payBillViaX402 } from "abapay-sdk";
const key = process.env.PRIVATE_KEY;
if (!/^0x[0-9a-fA-F]{64}$/.test(key ?? "")) throw new Error("PRIVATE_KEY must be 0x + 64 hex characters");
const account = privateKeyToAccount(key as `0x${string}`);
const result = await payBillViaX402({
signer: account,
bill: {
serviceID: "mtn",
serviceCategory: "AIRTIME",
network: "MTN",
billersCode: "08012345678",
nairaAmount: 1000,
token: "USDT", // or "USDC", "USAT" (USA₮)
},
});
console.log(result.status, result.tx_hash);That's the whole integration. account never touches abapays.com beyond this one HTTP call —
no account creation, no API key, no PIN. This is the flow described at
agents.abapays.com/#x402: the agent holds a Celo wallet,
calls the endpoint, gets a 402 Payment Required, signs the exact amount named in the
challenge, and retries. payBillViaX402 does the challenge/sign/retry for you; nothing about
the wire format is hidden — read src/x402.ts if you want to see or reimplement it in another
language.
Linked wallet: the fuller tool catalog
import { privateKeyToAccount } from "viem/accounts";
import { AbaPayAgent } from "abapay-sdk";
const key = process.env.PRIVATE_KEY;
if (!/^0x[0-9a-fA-F]{64}$/.test(key ?? "")) throw new Error("PRIVATE_KEY must be 0x + 64 hex characters");
const account = privateKeyToAccount(key as `0x${string}`);
// One-time: prove wallet ownership, mint an Agent Hub api_key. Save agent.apiKey somewhere —
// there is no recovery flow other than linking again.
const agent = await AbaPayAgent.link({ signer: account, pin: "123456" });
// Elsewhere, an on-chain approve() + setSpendingAllowance() grants AbaPay a bounded, revocable
// allowance — see examples/agent-quickstart.mjs in the main repo for the two calls. Nothing in
// this SDK can raise that allowance on your behalf; only your own wallet can.
console.log(await agent.checkBalance());
await agent.payBill({
pin: "123456",
service: "AIRTIME",
provider: "mtn",
account_number: "08012345678",
amount_ngn: 1000,
idempotency_key: crypto.randomUUID(), // a retry with the same key never pays twice
});Reattach to a previously-minted key without signing again:
const agent = AbaPayAgent.fromApiKey(savedApiKey, walletAddress);Which one do I want?
| | payBillViaX402 | AbaPayAgent |
| ------------------------ | ---------------------------------- | --------------------------------------- |
| Setup | None | One signed message, once |
| Credential per call | None — just the signature | api_key + PIN, every payment |
| Tool catalog | Pay a bill, that's it | Balance, history, schedules, batch, more |
| Best for | An external agent that already has a wallet and wants to pay, now | An agent that will call AbaPay repeatedly and wants the full catalog |
Self Agent ID
If your agent is registered at app.ai.self.xyz (the SelfAgentRegistry on Celo, which ties the agent key to a verified human), pass that key and every request is signed with it:
const selfAgent = privateKeyToAccount(process.env.SELF_AGENT_KEY as `0x${string}`);
const result = await payBillViaX402({ signer: account, selfAgent, bill: { /* … */ } });
console.log(result.self_agent); // { status: "verified", agent_id: "…" }
const signedAgent = agent.withSelfAgent(selfAgent); // MCP calls, signedThe CLI reads SELF_AGENT_KEY. The agent key need not be the paying wallet. Each request is
signed afresh (AbaPay refuses a repeated or stale signature with 401 SELF_AGENT_INVALID);
an agent that is expired, revoked or not registered still pays, just unverified. For other
languages, selfSigningDigest and selfAgentHeaders are exported. Protocol details:
docs/gitbook/self-agent-id.md.
Errors
Every failure — a bad request, a rejected signature, a settlement AbaPay itself reports as
unsuccessful — throws AbaPayError (message, and response carrying whatever AbaPay's API
returned, when there was a response to carry). Full error code reference:
agents.abapays.com/errors.
CLI
The same two paths, from a terminal — no code required:
npm install -g abapay-sdk
PRIVATE_KEY=0x... abapay pay --service AIRTIME --provider mtn --to 08012345678 --amount 1000
PRIVATE_KEY=0x... abapay link --pin 123456
abapay balance --api-key aba_mcp_xxxxx
abapay history --api-key aba_mcp_xxxxx --limit 5abapay help prints the full command reference. PRIVATE_KEY never leaves your machine —
every command signs locally via viem, the same as the library calls above.
License
MIT
