Blockchain Integration Guide
What is a blockchain?
Reads are free RPC calls; writes are key-signed txs that cost gas and finalise over confirmations.
EOAs (key-controlled) and contract accounts share one state machine, the EVM. A write is signed locally (key never leaves the wallet), broadcast to the mempool, included by a validator, then settles over N confirmations. Use viem (createPublicClient for reads; simulateContract → writeContract → waitForTransactionReceipt for writes) or ethers v6. Work in bigint wei; parseUnits/formatUnits only at the edge. For codeAmani the realistic play is USDC on an L2 (Base) as a borderless settlement rail behind an M-Pesa cash-out.
Six things to get right
The concepts that separate "I shipped a payment rail" from "I lost the keys." Tap a card for the details.
The trilemma — pick two, see what you give up
Every chain optimises at most two of decentralisation, security, and scalability. Choose your two and watch which real-world archetype — and which throughput/fee/finality reality — that commits you to. This single trade-off explains why Base (an L2), not mainnet, is the pragmatic default for stablecoin settlement.
Base-layer L1 (Ethereum / Bitcoin)
e.g. Ethereum mainnet, Bitcoin
Maximally trustless and secure — the right home for high-value settlement and the security other layers borrow. You pay in throughput and fee volatility.
The escape hatch: L2 rollups (Base, Arbitrum, Optimism) execute cheaply off-chain and post proofs back to Ethereum L1 — so they feel scalable while inheriting L1 security. That's why codeAmani's default for USDC settlement is an L2, not mainnet and not a standalone fast L1.
██████╗ ██╗ ██████╗ ██████╗██╗ ██╗ ██████╗██╗ ██╗ █████╗ ██╗███╗ ██╗
██╔══██╗██║ ██╔═══██╗██╔════╝██║ ██╔╝██╔════╝██║ ██║██╔══██╗██║████╗ ██║
██████╔╝██║ ██║ ██║██║ █████╔╝ ██║ ███████║███████║██║██╔██╗ ██║
██╔══██╗██║ ██║ ██║██║ ██╔═██╗ ██║ ██╔══██║██╔══██║██║██║╚██╗██║
██████╔╝███████╗╚██████╔╝╚██████╗██║ ██╗╚██████╗██║ ██║██║ ██║██║██║ ╚████║
╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝Blockchain Integration Guide
Focus: what a blockchain actually is (a replicated append-only ledger nobody owns), the trade-off that governs every design choice (the trilemma), and where it earns its keep for codeAmani — stablecoin settlement and remittance rails that complement M-Pesa, not replace it. EVM-anchored, read-mostly, secrets server-side.
Overview
A blockchain is a shared, append-only ledger replicated across thousands of independent computers, where new entries ("blocks") are only accepted if a majority agree they follow the rules. Each block carries the cryptographic hash of the one before it, so the chain is tamper-evident: change one historical transaction and every later block's hash breaks, and the network rejects your fork. No single party — no bank, no company, no government — can silently edit it. That single property (verifiable state without a trusted operator) is the whole point; everything else is engineering trade-offs around it.
Three honest truths frame every blockchain decision:
- You can't have it all — the trilemma. A chain optimises at most two of decentralisation, security, and scalability. Bitcoin and Ethereum L1 pick decentralisation + security and pay with low throughput and higher fees. High-TPS L1s buy scalability by reducing the number of validators (less decentralised). L2 rollups (Base, Arbitrum, Optimism) are today's pragmatic answer: they execute cheaply off-chain and inherit Ethereum's security by posting proofs back to L1.
- Reads are free and easy; writes are slow, public, and cost gas. Querying chain state (a balance, a token's supply) is a cheap RPC call. Writing (a transfer, a contract call) is signed by a private key, broadcast to the mempool, included in a block by a validator, and costs a gas fee. Until it has enough confirmations it can still be reorged — writes are eventually-final, not instantly-final.
- Code is law, and bugs are permanent. A deployed smart contract is immutable and usually controls real money. There is no "undo," no support line. This is why audits, well-trodden token standards (ERC-20, ERC-721), and "don't roll your own crypto" matter more here than almost anywhere else.
For codeAmani, the realistic, non-speculative use is digital dollars on a cheap fast chain: USDC/USDT (stablecoins) on an L2 settle cross-border value in seconds for cents, which is a genuine complement to M-Pesa's domestic strength. The rest of this guide is EVM-centric (Ethereum + its L2s) because that ecosystem has the deepest tooling — viem and ethers — and the widest stablecoin liquidity.
Official Documentation
| Source | URL | What it covers |
|---|---|---|
| Ethereum dev docs | https://ethereum.org/en/developers/docs/ | Accounts, transactions, gas, EVM, consensus — the canonical mental model |
| viem | https://viem.sh/docs/getting-started | Modern TypeScript Ethereum interface (clients, reads, writes, ABIs) |
| ethers v6 | https://docs.ethers.org/v6/ | Mature JS library — providers, contracts, wallets, formatting |
| Solidity | https://docs.soliditylang.org/ | The dominant smart-contract language |
| Circle (USDC) | https://developers.circle.com/stablecoins | Stablecoin standards, supported chains, on/off-ramp APIs |
How a block chain holds together
Each block bundles a batch of transactions plus the hash of the previous block. Because the hash is derived from the contents, any change anywhere ripples forward and invalidates every subsequent block — that's what makes the ledger tamper-evident rather than merely "a database with backups."
Consensus is how the network agrees on which block is next. Ethereum uses Proof of Stake: validators lock up (stake) ETH and are chosen to propose/attest blocks; misbehaviour gets their stake slashed. This replaced energy-hungry Proof of Work and is why "Ethereum is bad for the environment" is now outdated.
The transaction lifecycle (a write)
Reading is a free RPC query. Writing money or state is the part with real consequences — sign locally, broadcast, wait for inclusion, then wait for finality.
Key terms you must internalise:
| Term | Meaning |
|---|---|
| Account / address | 0x… 20-byte identifier. EOA = controlled by a private key; contract account = controlled by code. |
| Private key / seed phrase | The secret that authorises spends. Whoever holds it owns the funds. Never on a server, never in git, never NEXT_PUBLIC_*. |
| Gas | Compute units a tx consumes × gas price (in gwei). The fee. Failed txs still cost gas. |
| Nonce | Per-account counter; orders an account's txs and prevents replays. |
| Confirmation / finality | Blocks built on top of yours. More confirmations = harder to reverse. Treat money as received only after your finality threshold. |
| Wei / gwei / ether | Denominations. 1 ether = 10⁹ gwei = 10¹⁸ wei. Always work in the smallest unit (bigint); format only for display. |
Reading the chain (viem)
viem is the modern, type-safe TypeScript interface. A Public Client reads; install and query in three lines. Source: viem getting-started + clients/public docs.
npm i viem// lib/chain.ts
import { createPublicClient, http } from "viem";
import { base } from "viem/chains"; // Base = cheap Ethereum L2 — good default for payments
// A public client is read-only. The transport is your RPC endpoint.
export const publicClient = createPublicClient({
chain: base,
transport: http(process.env.RPC_URL), // server-side RPC; falls back to a public node if omitted
});
// Cheap, free reads:
const block = await publicClient.getBlockNumber();
const wei = await publicClient.getBalance({ address: "0xA0Cf…251e" });Reading an ERC-20 (e.g. a USDC balance)
Tokens like USDC are smart contracts, not native chain balance — you read them by calling balanceOf on the contract. Pattern straight from viem's reading-contracts example.
import { erc20Abi, formatUnits } from "viem";
import { publicClient } from "./chain";
const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base
const [symbol, decimals, raw] = await Promise.all([
publicClient.readContract({ address: USDC, abi: erc20Abi, functionName: "symbol" }),
publicClient.readContract({ address: USDC, abi: erc20Abi, functionName: "decimals" }),
publicClient.readContract({ address: USDC, abi: erc20Abi, functionName: "balanceOf", args: ["0xA0Cf…251e"] }),
]);
const human = formatUnits(raw, decimals); // e.g. "42.5" → never do float math on raw bigint balancesThe same read in ethers v6
ethers is the mature alternative; many existing dapps use it. Source: ethers v6 getting-started.
npm i ethersimport { JsonRpcProvider, Contract, formatUnits } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const abi = [
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
"function balanceOf(address) view returns (uint)",
];
const usdc = new Contract("0x8335…2913", abi, provider);
const decimals = await usdc.decimals();
const balance = await usdc.balanceOf("0xA0Cf…251e");
const human = formatUnits(balance, decimals);viem vs ethers, for a new codeAmani project: prefer viem — smaller bundle (matters on 2G/3G), first-class TypeScript inference, and it pairs with wagmi for React wallet hooks. Use ethers when integrating with an existing codebase or tutorial that already assumes it.
Writing the chain (and why the key stays off your server)
A write is signed by a private key. There are two safe places for that key, and your app server is never one of them:
- User-custodied (recommended for consumer apps): the user's browser wallet (MetaMask, Coinbase Wallet, or a WalletConnect mobile wallet) holds the key. Your frontend builds the tx; the wallet signs it. Use wagmi + viem (or RainbowKit) for the connect/sign UX. Your backend never touches a key.
- App-custodied (for treasury/automation): use a KMS/HSM-backed signer (AWS KMS, GCP KMS) or a custody provider so the raw key never sits in app memory or env files.
viemandethersboth support remote signers.
// Frontend write with a browser wallet (viem) — simulate first, then send.
import { createWalletClient, custom, parseUnits, erc20Abi } from "viem";
import { base } from "viem/chains";
import { publicClient } from "./chain";
const walletClient = createWalletClient({ chain: base, transport: custom(window.ethereum) });
const [account] = await walletClient.getAddresses();
// 1. Simulate — catches reverts BEFORE the user pays gas.
const { request } = await publicClient.simulateContract({
account,
address: "0x8335…2913", // USDC
abi: erc20Abi,
functionName: "transfer",
args: ["0xRecipient…", parseUnits("10", 6)], // 10 USDC (6 decimals)
});
// 2. The wallet signs & broadcasts; the key never leaves the wallet.
const hash = await walletClient.writeContract(request);
// 3. Wait for finality before treating it as paid.
const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 3 });The simulate → write → wait sequence is the canonical safe pattern: simulation surfaces a revert before any gas is spent, and waitForTransactionReceipt is where you enforce your finality threshold.
Smart contracts in one breath
A smart contract is code (usually Solidity) deployed to an address that runs on the EVM exactly as written, by every validator, forever. You mostly consume existing contracts via their ABI (the JSON describing their functions) rather than writing your own. When you do write one:
- Don't reinvent standards. Use audited OpenZeppelin implementations of ERC-20 (fungible tokens), ERC-721/1155 (NFTs), access control, and pausability.
- Immutability cuts both ways. Plan upgrades (proxy patterns) before deploy, or accept that the bytecode is final.
- Test on a testnet first (Base Sepolia, Sepolia) with free faucet ETH before mainnet. Same discipline as testing M-Pesa against the Daraja sandbox before going live.
Possible use cases for codeAmani (mapped to the existing stack)
Blockchain is not a replacement for the repo's rails — it's a settlement layer that plugs into them. Each row pairs a real product idea with the tech-stack guide it builds on.
| Use case | What blockchain adds | Builds on (repo guide) |
|---|---|---|
| Cross-border remittance / settlement | USDC on an L2 moves value across borders in seconds for cents, then off-ramps to M-Pesa locally. Cheaper and faster than correspondent banking for diaspora → Kenya flows. | MPESA_PATTERNS.md, daraja-api, stripe (card on-ramp) |
| Stablecoin payouts to SMEs/creators | Pay suppliers or gig workers in digital dollars; they hold value against KES inflation, cash out to M-Pesa on demand. | daraja-api, africas-talking |
| On-chain proof / provenance | Anchor a hash of a document, certificate, or supply-chain event on-chain for tamper-evident verification — cheap, no token speculation. | supabase (store the doc, index the tx) |
| Auditable payment ledger | Mirror on-chain transfers into Postgres so your app reads from a fast DB, with the chain as the source of truth. Dedupe on tx hash, exactly like webhook idempotency. | supabase/neon, webhooks |
| Wallet-based identity / access | "Sign-In with Ethereum" (a signed message, no gas) as a passwordless login or token-gated access, alongside Clerk for email/social. | clerk (hybrid auth) |
| Transparent disbursements (NGO/treasury) | Publicly verifiable fund flows for grants or community payouts — anyone can audit, no trust in a single operator. | supabase (off-chain metadata) |
The honest framing for the East African market: stablecoins are the killer app, speculation is not. USDC settlement complements M-Pesa's last-mile reach; treat the chain as a fast, borderless clearing layer and let Daraja handle the cash-in/cash-out that users actually touch.
On/off-ramp reality (the hard part)
Moving between fiat (KES) and on-chain dollars is a regulated, partner-dependent step, not an API you self-host:
- On-ramp (KES/M-Pesa/card → USDC): integrate a licensed provider/aggregator; your app orchestrates, the provider handles KYC and custody.
- Off-ramp (USDC → M-Pesa): same — a licensed partner credits the recipient's M-Pesa via Daraja B2C after receiving the stablecoin.
- Your job is orchestration + reconciliation: persist every leg (
CheckoutRequestIDfor the M-Pesa leg, tx hash for the chain leg), dedupe, and reconcile out of band — the same idempotency discipline as the webhooks guide.
Security checklist
- Private keys / seed phrases never on a server, in env files, in git, or in
NEXT_PUBLIC_*. User-custodied wallets sign in the browser; app-custodied keys live in KMS/HSM. - RPC URLs and provider API keys are server-side env vars; proxy chain reads through your backend so keys don't ship to the client bundle.
- Always
simulateContractbeforewriteContract— catch reverts before spending gas. - Enforce a finality threshold (
waitForTransactionReceiptwith a confirmations count) before treating a payment as received. - Work in the smallest unit (
bigint);parseUnits/formatUnitsonly at the boundary. Never use JS floats for money. - Use audited token standards (OpenZeppelin) and audited contracts; never roll your own crypto or token logic.
- Validate addresses and amounts at every system boundary (forms, API routes) — a typo'd address is an irreversible loss.
- Dedupe on transaction hash when indexing on-chain events into your DB (at-least-once, like webhooks).
- Test on a testnet (Base Sepolia) with faucet funds before mainnet — same gate as the Daraja sandbox.
- Pin chain IDs in config and assert the connected wallet is on the expected network before any write.
codeAmani notes
- Secrets server-side only.
RPC_URL, provider/custody API keys →.env.locallocally, Vercel env vars in prod. Signing keys belong in KMS/HSM or the user's wallet — never in your code. This is the non-negotiable rule of the whole space. - AI routing is unaffected. Blockchain is a settlement layer, not an AI provider — keep Claude (primary) / OpenAI (structured) routing as-is. Where they meet: an agent can read chain state to answer "did this payment land?" but should never hold a signing key.
- M-Pesa stays the last mile. Don't pitch crypto as a replacement for M-Pesa to Kenyan users — pitch USDC as the borderless settlement rail behind an M-Pesa cash-out. Daraja handles the touchpoint; the chain handles the clearing.
- Mobile-first & low-bandwidth. Prefer viem over ethers/web3.js for bundle size on 2G/3G, lazy-load wallet-connect UI, and proxy reads server-side so the phone isn't hammering an RPC endpoint.
- Reconciliation is a first-class job, exactly like the M-Pesa pattern: persist both legs (tx hash +
CheckoutRequestID), dedupe, reconcile out of band, and only mark "settled" after on-chain finality and the M-Pesa B2C result.