# Shika Protocol > API reference for Shika Protocol — identity, stablecoin, savings, lending, JIT liquidity, and RWA services on Base. ## Architecture > **Who it's for:** Developers and technical evaluators who want to understand how Shika's services, contracts, and identity layer connect before building on top of them. > **Why it matters:** Every service is isolated and independent, so teams can integrate one product — say, savings groups — without taking on the complexity of the full stack. ### How Shika Is Built Shika is a stack of independent backend services that sit between your app and a set of smart contracts on Base. Each service owns one domain — identity, stablecoins, savings, lending, liquidity, or real-world assets — and exposes a clean HTTP API. All services share one rule: **no user ever touches a private key**. Transactions are submitted through Shika Wallet on behalf of the user's address. ### Layers ``` Your App (mobile or web) ↓ Shika API Services Identity · Stablecoin · Savings · Lending · Liquidity · RWA ↓ On-Chain Protocol (Base Mainnet) Identity registry · Stablecoin · Lending pools Savings pools · Liquidity engine · RWA tokens ↑ Automated Keepers Price feeds · Late payment detection · Default processing ``` ### Identity as the Root of Trust > **Who it's for:** Any user of any Shika product — they benefit from this automatically, without any extra steps. > **Why it matters:** Verifying once and reusing everywhere means users in Ghana are never asked to re-upload their ID just to switch from savings to borrowing. Every user completes KYC once. That verification is stored as an on-chain credential — silently checked by every product without the user doing anything extra. ``` User completes biometric KYC ↓ Shika issues an on-chain identity credential ↓ All products check that credential automatically: Borrow · Provide liquidity · Hold RWA tokens · Open collateral positions ``` ### How Writes Work Every state-changing action follows the same pattern — your app never waits for a blockchain confirmation: ``` 1. App calls POST /resource/action 2. Service validates the request 3. Transaction submitted via Shika Wallet 4. Service returns { id, status: 'pending' } immediately When the transaction confirms on-chain: 5. Background job picks up the event 6. Status updated to 'active' or 'complete' App polls GET /resource/:id to check finality ``` ### Service Isolation Each service has its own database and owns its domain fully. There are no cross-service database joins — services communicate over internal bindings when needed (e.g. identity checks at loan time). ## Contract Addresses **Network:** Base Mainnet (Chain ID: 8453) ### SSI — Self-Sovereign Identity | Contract | Address | | --------------------- | -------------------------------------------- | | SSIComposable (proxy) | `0xC44357264CcD8aDA10A16889eBa5FA0ab582a876` | | SSIComposable (impl) | `0x9e3CD707a0e9D7B578F06bc6D80a579175D49669` | | Verifier | `0x96a34b41e41df9928e69B51061240f12D19Fdd8e` | | ClaimIssuer | `0x89FAcE94A8738B50F2DE8B5D2Eb8464e463300e2` | | IdentityFactory | `0x4C47d600f056eBD01B538a7bB7eF48C0bd885b4a` | | L2Registrar | `0x10E0EfB9ac9829A83c705067cAc3aED43d5D064d` | | L2Registry (ENS) | `0xe40d1B1D157c1b97Bcc52A170e659Ed65dFBA43c` | **KYC claim topic:** `666` · **ENS domain:** `shikawallet.com` *** ### cGHS Stablecoin | Contract | Address | | -------------------- | -------------------------------------------- | | cGHS Diamond (proxy) | `0x3126627A607E730E3eCF9BbfFD67fa18Deaab846` | | FiatOracleFactory | `0x7856bB6B6EC8609ec656ea7FA52D23e7cC6C3C94` | | GHS Aggregator | `0xF82Db056Ff0291938256b99f11D6DC55A0386a9b` | | GHS Oracle Proxy | `0xaa11f53782a8a929746863C29F19D60414D2194a` | *** ### Lending Diamond | Contract | Address | | -------------------- | -------------------------------------------- | | ShikaLending Diamond | `0x6360CeB8696f46fFef53f5a7B4802EB9Cd022BC4` | **Facets:** OwnableFacet · DiamondCutFacet · DiamondLoupeFacet · StablecoinRegistryFacet · CreditScoringFacet · LendingPoolFacet · BNPLFacet · YieldVaultFacet · SavingsGoalFacet *** ### ROSCA Savings | Contract | Address | | ------------------------ | -------------------------------------------- | | GroupPoolFactory (proxy) | `0x8C3dc00481445239BbE1BA25AeC631645C8fF7B0` | | GroupPoolFactory (impl) | `0x46fE98E428B5Eafc52e568496C0C905dA7667901` | *** ### JIT Liquidity | Contract | Address | | --------------- | -------------------------------------------- | | RouterHook | `0x23350973855cCFe6EfF30b5f5D334Ea1D36280C0` | | P2POfferBook | `0xc4Bdc3A321003D9420DbA21Ed89718904670A5cA` | | GHSPriceRouter | `0x0D4f47a10B151acc82469078C57f853F53036439` | | JITRebalancer | `0x64b7DeD624377Dc0eE5050b3B457814b2b94C5d5` | | PoolManager | `0x498581fF718922c3f8e6A244956aF099B2652b2b` | | PositionManager | `0x7C5f5A4bBd8fD63184577525326123B519429bDc` | **cGHS/USDC Pool ID:** `0x2f7c1084697ba69acb41b675f80b800ede45c61e7b25b4a04d0f8dca249fe2bd` *** ### RWA Token System | Contract | Address | | --------------------- | -------------------------------------------- | | GenericRWAFactory | `0xb66b58904743B390E1e2A1B6Dc541C831E1b8E84` | | RWARegistry | `0x9Ba28AE0b490C157a77Dd670b59c2D3db53115E0` | | ModuleRegistry | `0x84774F70Af109a11E39fc66C5cAFC1F5Bd433c64` | | CountryRestrictModule | `0xa77134750afaACd8Bef15f36b4F5d0715D089499` | | MaxBalanceModule | `0x06a5882B6f5840Eef9861191363E24c3b905A7cE` | *** ### Shika Services All services are deployed under the **offgridlabs** account: | Service | Worker URL | | --------------- | -------------------------------------- | | core-identity | `identity.shika.workers.dev` | | core-stablecoin | `core-stablecoin.workers.dev` | | core-savings | `core-savings.workers.dev` | | core-lending | `core-lending.offgridlabs.workers.dev` | | core-jit | `core-jit.offgridlabs.workers.dev` | | core-rwa | `core-rwa.workers.dev` | *** ### Protocol Roles | Role | Address | | ------------------------ | -------------------------------------------- | | Deployer / Diamond owner | `0x20f42012533B17d8Df25Cf25D9797E327E59FCAb` | | SSI Deployer | `0x9aAFb3e5bC9e77b8Be51D695Cf4890359a42b305` | | Agent / Shika Wallet | `0x953cc3629e4cb2b77B2583Dde084eEeBe6Ccef19` | ## Introduction > **Who it's for:** Developers building financial apps for African markets, and any service integrating mobile money, savings, credit, or digital currency for users in Ghana and beyond. > **Why it matters:** Every product in the stack is pre-wired to a single verified identity — one KYC, zero re-verification, and no private key management for end users. Shika Protocol is a blockchain fintech stack built for African markets — starting with Ghana. It exposes a set of backend services that front a suite of smart contracts on Base. **Every product is gated by on-chain SSI. Identity is the bedrock.** ### Services | Service | Description | URL | | -------------------------------- | ---------------------------------------- | ---------------------------------------------- | | **core-identity** (`worker-two`) | KYC + SSI identity provisioning | `https://identity.shika.workers.dev` | | **core-stablecoin** | cGHS mint/burn, oracle push | `https://core-stablecoin.workers.dev` | | **core-savings** | ROSCA groups, yield vault, savings goals | `https://core-savings.workers.dev` | | **core-lending** | Lending pools, loans, BNPL, collateral | `https://core-lending.offgridlabs.workers.dev` | | **core-jit** | atomic swaps, LP, P2P offers | `https://core-jit.offgridlabs.workers.dev` | | **core-rwa** | RWA token deploy, compliance, oracle | `https://core-rwa.workers.dev` | ### Authentication Write endpoints use Shika Wallet-signed transactions — the caller provides their `addressId` (wallet UUID) rather than a private key. Reads use `viem publicClient` via the Base RPC. Admin-only routes (e.g. `POST /admin/pool/create` in core-jit, `POST /rwa/create` in core-rwa) are guarded by an `x-admin-secret` header. ### Request Pattern All write operations follow the same async pattern: ``` POST /resource/action → Shika Wallet transaction submitted → D1 row inserted with status = 'pending' → { id, status: 'pending' } returned immediately Wallet webhook fires on confirmation: → Background workflow decodes on-chain event → D1 row updated to status = 'active' | 'complete' ``` Poll `GET /resource/:id` to check finality. ### cGHS Decimals cGHS uses **6 decimal places**. All `amount` fields in API requests and responses are in base units: | Human | Base units | | --------- | ---------- | | 1 cGHS | `1000000` | | 10 cGHS | `10000000` | | 0.50 cGHS | `500000` | ## core-stablecoin > **Who it's for:** Users in Ghana who want to hold, send, or use digital money that moves with the Ghana Cedi — and developers building payments or savings products denominated in local currency. > **Why it matters:** cGHS is 100% fiat-backed and verifiable on-chain, giving users in Ghana a stable digital currency they can convert to and from mobile money without touching a bank. `core-stablecoin` manages the **cGHS stablecoin** — the Ghana Cedi on-chain. It's deployed once per currency (`BASE_CURRENCY` env var). The same service handles XOF, KES, etc. as additional named deployments. **cGHS contract:** `0x3126627A607E730E3eCF9BbfFD67fa18Deaab846` ### Route Groups | Prefix | Description | | ----------- | ----------------------------------------------- | | `/deposit` | Fiat in → mint cGHS (Transakt gateway) | | `/withdraw` | Burn cGHS → fiat out (Transakt payout) | | `/oracle` | GHS/USD price push, factory oracles, conversion | | `/reserves` | KZG ZK proof-of-reserves | | `/webhook` | Transakt + wallet callbacks | ### Deposit Flow Deposits go through **Transakt** — Shika's mobile money / bank transfer gateway partner: ``` POST /deposit → Transakt initiates collection (returns ref) → User pays via mobile money → Transakt POSTs /webhook/transakt on success → Service mints cGHS on-chain OR POST /deposit/confirm (manual trigger if webhook missed) ``` ### Cron — Oracle Push Every 6 hours, the service automatically fetches the live GHS/USD rate and pushes it on-chain via `Aggregator.updateAnswer(price)` through the Shika agent wallet. No manual oracle push needed in normal operations. ### Token Specs | Property | Value | | -------- | ------------------------------------------- | | Name | Shika Ghana Cedi | | Symbol | cGHS | | Decimals | 6 | | Chain | Base Mainnet | | Backing | 100% fiat-backed, verified by KZG ZK proofs | ## Deposit & Withdraw > **Who it's for:** Users topping up or cashing out their cGHS balance via mobile money or bank transfer, and developers integrating deposit and withdrawal flows into their apps. > **Why it matters:** Moving money on and off chain is as simple as a mobile money transfer — users pay in GHS through familiar channels and receive spendable cGHS immediately on confirmation. cGHS is minted when a user deposits fiat via **Transakt** (mobile money / bank transfer gateway) and burned on redemption. The flow is two-step: initiate the payment, then confirm/mint once the payment is verified. *** ### `POST /deposit` — Initiate Fiat Deposit Starts a Transakt payment collection. Returns a Transakt reference the user pays into. **Request** ```json { "amount": "100.00", "channel": "mobile_money", "phoneNumber": "+233241234567", "userAddress": "0xabc..." } ``` | Field | Notes | | ------------- | --------------------------------------- | | `amount` | Fiat amount (GHS, human-readable) | | `channel` | `"mobile_money"` or `"bank"` | | `phoneNumber` | Mobile money number (with country code) | | `userAddress` | User's wallet address to receive cGHS | **Response** ```json { "success": true, "depositId": "d1-uuid", "amountGHS": "100.00", "transaktRef": "TRX-123456", "transaktError": null } ``` *** ### `POST /deposit/confirm` — Confirm & Mint Called after Transakt payment is verified (or triggered by the Transakt webhook automatically). Mints cGHS on-chain. **Request** — either by deposit ID: ```json { "depositId": "d1-uuid" } ``` Or by address + amount (manual override): ```json { "amount": "100.00", "userAddress": "0xabc..." } ``` **Response** ```json { "success": true, "depositId": "d1-uuid", "proofHash": "0xproofhash...", "txId": "tx-uuid" } ``` *** ### `GET /deposit/:id` — Deposit Status ```json { "depositId": "d1-uuid", "userAddress": "0xabc...", "amountGHS": "100.00", "status": "complete", "transaktRef": "TRX-123456", "txId": "tx-uuid", "txHash": "0xdeadbeef...", "createdAt": "2026-03-19T10:00:00Z" } ``` *** ### `POST /withdraw` — Withdraw (Burn cGHS) Burns cGHS and triggers a Transakt payout to the user's bank/mobile money. **Request** ```json { "userAddress": "0xabc...", "amount": "50000000", "channel": "mobile_money", "receiver": "+233241234567" } ``` `amount` is in **base units** (6 decimals) — `50000000` = 50 cGHS. **Response** ```json { "success": true, "withdrawId": "d1-uuid", "amountGHS": "50.00", "userAddress": "0xabc...", "receiver": "+233241234567", "txId": "tx-uuid" } ``` *** ### `GET /withdraw/:id` — Withdrawal Status ```json { "withdrawId": "d1-uuid", "userAddress": "0xabc...", "amountGHS": "50.00", "receiver": "+233241234567", "status": "complete", "transaktRef": "TRX-789012", "txId": "tx-uuid", "txHash": "0xdeadbeef...", "createdAt": "2026-03-19T10:05:00Z" } ``` *** ### Webhooks | Path | Sender | Purpose | | ------------------------ | ------------ | -------------------------------------- | | `POST /webhook/transakt` | Transakt | Payment confirmed → triggers mint | | `POST /webhook/shika` | Shika Wallet | On-chain tx confirmed → updates status | ## Oracle & Reserves > **Who it's for:** Developers building products that use cGHS prices or need proof that the stablecoin is fully backed — including lending, liquidity, and audit integrations. > **Why it matters:** Live GHS/USD rates keep every product fairly priced, and cryptographic proof of reserves means users can verify the stablecoin is fully backed without trusting anyone's word. *** ### Oracle The cGHS price oracle is a Chainlink-compatible feed. The service pushes GHS/USD every 6 hours via cron, and also supports factory-deployed oracles for other currencies. #### `GET /oracle/price` ```json { "currency": "ghs", "priceUsd": "0.065230", "rawAnswer": "6523000", "roundId": "42", "updatedAt": 1710854400 } ``` #### `GET /oracle/round?roundId=42` ```json { "roundId": "42", "answer": "6523000", "startedAt": 1710850800, "updatedAt": 1710854400 } ``` #### `POST /oracle/push` Manually trigger an oracle price push. Useful after deployment or emergencies. **Request:** `{ "force": false }` (set `true` to push even if price hasn't changed) **Response** ```json { "success": true, "currency": "ghs", "fiatUsd": "0.065230", "price": "6523000", "txHash": "0xdeadbeef..." } ``` #### Currency Conversion ``` GET /oracle/convert/usd-to-fiat/:amount → { usdAmount, fiatAmount, currency, rate } GET /oracle/convert/fiat-to-usd/:amount → { fiatAmount, usdAmount, currency, rate } ``` #### Multi-Currency Oracle Factory ``` GET /oracle/list → { factory, currencies[] } GET /oracle/source → { currency, currentFiatUsd, sqrtPriceX96 } POST /oracle/create Body: { currency } → deploy new aggregator + proxy POST /oracle/push/factory Body: { currency } → push price for a factory oracle ``` *** ### Proof of Reserves cGHS is 100% fiat-backed. Reserves are verified on-chain using **KZG** — a zero-knowledge proof system that proves total fiat deposits without revealing individual accounts. #### `GET /reserves/full-proof` Triggers a full proof cycle and returns the result. ```json { "success": true, "preprocessDone": true, "kzgQueryId": "kzg-query-id", "provenReserves": "5012500000000", "onChainSupply": "5000000000000", "fullyBacked": true, "proof": "0xzkproof...", "timestamp": 1710854400 } ``` #### `GET /reserves/compare` Quick check — on-chain supply vs. proven reserves. ```json { "success": true, "onChainSupply": "5000000000000", "provenReserves": "5012500000000", "fullyBacked": true, "timestamp": 1710854400 } ``` #### `GET /reserves/proof?query=...` Submit a KZG query string and return proof details. #### `POST /reserves/verify/:queryId` Verify a previously submitted KZG proof by query ID. #### `GET /reserves/db` Returns all reserve records. ```json { "success": true, "total": 1423, "rows": [ ... ] } ``` ## Savings Goals > **Who it's for:** Individuals saving toward a specific target — school fees, a business asset, a family event, or a down payment — who want their money to grow while it waits. > **Why it matters:** Locking funds toward a named goal creates the discipline of a term deposit with the flexibility to bridge into a purchase the moment the goal matures. Savings Goals let users lock cGHS for a defined period to meet a financial target — school fees, a phone, a car deposit. Funds earn yield inside the vault while locked. An optional early-exit penalty discourages premature withdrawal. ### `POST /goal/create` **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "stablecoin": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "amount": "200000000", "lockUntil": 1757548800, "penaltyBps": 1000, "name": "School Fees 2025" } ``` | Field | Constraints | | ------------ | ----------------------------------------- | | `lockUntil` | `> now + 30 days` and `< now + 3 years` | | `penaltyBps` | `0` – `2000` (max 20% early-exit penalty) | | `name` | Stored as `bytes32` on-chain | | `amount` | Initial deposit in base units | Transaction batch: `[approve(lendingDiamond, amount), createGoal(stablecoin, amount, lockUntil, penaltyBps, nameBytes32)]` **Response** ```json { "id": "d1-uuid", "status": "pending" } ``` *** ### `POST /goal/:goalId/topup` Add more funds to an active goal before it matures. **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "amount": "50000000" } ``` *** ### `POST /goal/:goalId/withdraw` Withdraw from a goal. Returns a preview of penalty before execution. **Response** ```json { "id": "d1-uuid", "status": "pending", "preview": { "gross": "230000000", "penalty": "23000000", "net": "207000000", "isMature": false } } ``` If `isMature: true`, penalty is `0` regardless of `penaltyBps`. *** ### `POST /goal/:goalId/buy` Use a matured savings goal as a down payment to trigger BNPL for a purchase. The goal assets fund the deposit and a lending pool covers the remainder in installments. **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "poolId": "1", "merchant": "0xmerchant...", "totalPrice": "500000000", "numInstallments": 6, "intervalSeconds": 2592000 } ``` Requires: goal must be **matured** (`lockUntil` passed), and `totalPrice > goalAssets`. *** ### Read Endpoints ``` GET /goal/:goalId — goal state + current assets GET /goal/:goalId/preview — { gross, penalty, net, isMature } GET /goal/user/:address — all goals for a user ``` ## ROSCA Groups > **Who it's for:** Market women, traders, and salaried workers who want to save together and take turns receiving a lump sum — just like a traditional *susu* or *tontine*. > **Why it matters:** ROSCA groups eliminate the middleman — no bank account needed, contributions are enforced on-chain, and payouts happen automatically to the elected recipient. ROSCA (Rotating Savings and Credit Association) is the digital version of the African *susu* — a group of people pool contributions each cycle, and one member receives the full pot. ### `POST /group/create` Deploy a new savings group (`createGroup` on-chain). The admin sets all group parameters upfront. **Request** ```json { "adminAddressId": "wallet-uuid", "adminAddress": "0xabc...", "config": { "groupName": "Market Women GH Q1", "amountPerPerson": "50000000", "adminFeeBps": 100, "contributionPeriod": 2592000, "currency": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "collateralAmount": "10000000", "requireCollateral": true, "maxParticipants": 12, "biddingEnabled": false, "defaultHandlingEnabled": true, "interestEnabled": false, "midCycleJoinEnabled": false, "midCycleLeaveEnabled": false, "loanIntegrationEnabled": false, "resetEnabled": false, "biddingPeriod": 0, "minimumBidAmount": "0", "gracePeriod": 86400, "maxMissedContributions": 1, "interestRate": 0 } } ``` | Field | Notes | | -------------------- | -------------------------------------------------------------- | | `amountPerPerson` | Per-cycle contribution in base units (6 dec for cGHS) | | `contributionPeriod` | Seconds between contribution rounds | | `collateralAmount` | Required collateral per participant (if `requireCollateral`) | | `currency` | cGHS address: `0x3126627A607E730E3eCF9BbfFD67fa18Deaab846` | | `adminFeeBps` | Admin fee in basis points (100 = 1%) | | `gracePeriod` | Seconds after due date before a missed contribution is counted | **Response** ```json { "id": "d1-uuid", "status": "pending" } ``` *** ### `POST /group/:groupId/join` Join a group by depositing collateral (if required). **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc..." } ``` The Transaction batch call is: `[approve(diamond, collateralAmount), joinGroup(groupId)]`. *** ### `POST /group/:groupId/contribute` Pay your contribution for the current cycle. **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "amount": "50000000" } ``` *** ### `POST /group/:groupId/vote` Vote for who should receive the pot this cycle (if voting is enabled). **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "candidate": "0xdef..." } ``` *** ### `POST /group/:groupId/disburse` Admin disburses the cycle pot to the elected recipient. **Request** ```json { "adminAddressId": "wallet-uuid", "adminAddress": "0xabc...", "recipient": "0xdef..." } ``` *** ### `POST /group/:groupId/advance` Advance to the next cycle (admin only). *** ### Read Endpoints ``` GET /group/:groupId — on-chain group state (viem) GET /group/:groupId/participants — all participant addresses GET /group/user/:address — groups the user belongs to (D1) GET /group/contribution/:id — contribution status (D1) ``` ## core-savings > **Who it's for:** Individuals who want to save with a group, earn yield on idle cGHS, or lock money away toward a specific goal — from school fees to a business deposit. > **Why it matters:** Shika brings three familiar African savings behaviours on-chain — the *susu* group, the piggy bank, and the goal jar — so users earn more and stay accountable without needing a bank account. `core-savings` covers three distinct savings primitives that share the same Diamond contract (`ShikaLending`): | Route prefix | Primitive | Description | | ------------ | ----------------- | --------------------------------------------------- | | `/group/*` | **ROSCA Groups** | Traditional African *susu* — rotating savings pools | | `/yield/*` | **Yield Vault** | Deposit cGHS, earn yield | | `/goal/*` | **Savings Goals** | Locked savings with optional early-exit penalty | ### Shared Pattern All write operations follow the standard async flow: ``` POST /resource/action → transaction submitted (signed by user's addressId) → D1 row: status = 'pending' → return { id, status: 'pending' } Webhook → background workflow → decode event → D1 status = 'active' | 'complete' ``` ### Contract All three primitives live on the **ShikaLending Diamond**: `0x6360CeB8696f46fFef53f5a7B4802EB9Cd022BC4` The savings Diamond is separate from the ROSCA `GroupPoolFactory` — group savings pools at `0x8C3dc00481445239BbE1BA25AeC631645C8fF7B0` are deployed via the factory. ## Yield Vault > **Who it's for:** Anyone holding cGHS who wants their idle money to grow — from a small business owner keeping working capital to an individual saving between group cycles. > **Why it matters:** Instead of sitting idle, deposited cGHS is put to work funding loans and generating real yield, with no lock-up and no minimum balance. The Yield Vault is a savings vault within the ShikaLending Diamond. Users deposit cGHS and receive shares that appreciate as the lending pool earns interest. ### `POST /yield/deposit` Deposit cGHS and receive vault shares. **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "stablecoin": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "assets": "100000000" } ``` Transaction batch: `[approve(lendingDiamond, assets), deposit(stablecoin, assets)]` **Response** ```json { "id": "d1-uuid", "status": "pending" } ``` On confirmation the workflow decodes `Deposited(depositor, stablecoin, assets, shares)` and records the shares received. *** ### `POST /yield/withdraw` Burn shares and receive cGHS back. **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "stablecoin": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "shares": "99800000" } ``` No token approval needed — the vault burns the caller's shares directly. *** ### Read Endpoints ``` GET /yield/balance/:address?stablecoin=0x... → { shares, assets } GET /yield/preview/deposit?stablecoin=0x...&assets=100000000 → { shares } GET /yield/preview/withdraw?stablecoin=0x...&shares=99800000 → { assets } GET /yield/exchange-rate?stablecoin=0x... → { rate, precision } GET /yield/total?stablecoin=0x... → { totalAssets, totalShares } ``` *** ### How Yield Accrues Vault assets are deployed to the `LendingPool` as liquidity. Interest earned by borrowers flows back to vault depositors via the share exchange rate increasing over time: ``` At deposit: 1 share = 1.000000 cGHS After 1 year: 1 share = 1.085000 cGHS (example, ~8.5% APY) ``` ## core-rwa > **Who it's for:** Asset issuers and investment platforms in Africa who want to bring real-world assets — property, bonds, business equity — on-chain so they can be traded, financed, and held by verified investors. > **Why it matters:** Tokenising a Ghanaian property or government bond makes it investable by anyone with a Shika identity, not just those with access to formal brokerage accounts. `core-rwa` manages the full lifecycle of RWA (Real-World Asset) tokens on Shika. Each RWA is a compliance token with modular rules, a Chainlink-compatible price oracle, and an SSI-linked identity. **Base contracts:** * `GenericRWAFactory` — `0xb66b58904743B390E1e2A1B6Dc541C831E1b8E84` * `RWARegistry` — `0x9Ba28AE0b490C157a77Dd670b59c2D3db53115E0` ### What This Service Does | Route group | Purpose | | ------------------------ | -------------------------------------------------- | | `POST /rwa/create` | Deploy a new RWA token + wire SSI + set compliance | | `POST /rwa/:addr/mint` | Agent-mint RWA tokens to an investor | | `POST /rwa/:addr/burn` | Burn tokens | | `POST /rwa/:addr/freeze` | Freeze / unfreeze investor | | `POST /rwa/compliance/*` | Add/remove compliance modules | | `GET /rwa/list` | All active RWAs from registry | | `POST /oracle/rwa/push` | Push MPC-TLS verified price on-chain | ### Asset Types | Type | `assetType` | Compliance Preset | | ----------- | ----------- | ---------------------------- | | Real Estate | 0 | CountryRestrict + MaxBalance | | Bond | 1 | CountryRestrict + MaxBalance | | Equity | 2 | CountryRestrict + MaxBalance | | Business | 3 | CountryRestrict + MaxBalance | | Commodity | 4 | CountryRestrict only | | Vehicle | 5 | CountryRestrict only | | Invoice | 6 | CountryRestrict only | ### Cron — Oracle Keeper Every 6 hours, the service iterates all active RWAs with stored credentials and: 1. Generates an MPC-TLS price proof 2. Pushes `RWAPriceOracle.setLatestProof(proof)` via the Shika agent wallet ## RWA Oracle > **Who it's for:** Platforms and users who rely on accurate, tamper-proof pricing for tokenised assets — especially when using RWA tokens as collateral or trading them on-chain. > **Why it matters:** Prices are proven using cryptographic verification against real-world data sources, so no single party can manipulate the price of a tokenised Ghanaian property or bond. Each RWA token has its own `RWAPriceOracle` contract. Prices are pushed via **MPC-TLS proofs** — cryptographic proofs generated over standard TLS (Transport Layer Security, the same protocol that secures every HTTPS connection) using multi-party computation, so no single party can forge or manipulate the fetched data. No centralized price feed. ### `POST /oracle/rwa/push` — Push RWA Price **Auth:** `x-admin-secret` header required. **Request** ```json { "rwaAddress": "0xabc...", "price": "125000000000" } ``` The service: 1. Fetches the `priceSource` config for this RWA from D1 2. Generates an MPC-TLS proof against the price API 3. Calls `RWAPriceOracle.setLatestProof(proof, price)` **Response** ```json { "success": true, "price": "125000000000", "proofHash": "0x..." } ``` *** ### `GET /oracle/rwa/:address` — Read Current RWA Price **Response** ```json { "price": "125000000000", "updatedAt": 1710000000, "staleSinceSeconds": 1234, "proofVerified": true } ``` *** ### Price Router After deployment, a `RWAPriceRouter` is deployed pointing at the `RWAPriceOracle`. `core-jit` uses the `RWAPriceRouter` to: * Derive `sqrtPriceX96` for pool initialization * Get the oracle price feed address for `RouterHook` ``` GET /price/rwa/:routerAddress → { sqrtPriceX96, tick, oraclePrice, oracleUpdatedAt } ``` ## Tokens & Compliance > **Who it's for:** Issuers deploying a new tokenised asset and compliance officers who need to control which countries and investor types can hold it. > **Why it matters:** Every RWA token comes with built-in compliance rules that automatically block ineligible investors, so issuers stay within regulatory boundaries without manual oversight. ### `POST /rwa/create` — Deploy RWA Token **Auth:** `x-admin-secret` header required. **Request** ```json { "ownerAddress": "0xabc...", "name": "Accra Commercial Property", "symbol": "ACP", "decimals": 18, "assetType": 0, "assetId": "PROP-GH-001", "jurisdiction": "GH", "priceSource": { "baseUrl": "https://api.pricedata.com", "httpMethod": "GET", "pathTemplate": "/property/{assetId}/price", "priceRegex": "\"price\":(\\d+\\.?\\d*)", "priceDecimals": 8, "credentialsKey": "PRICE_API_KEY" } } ``` The service automatically: 1. Checks if `ownerAddress` has an SSI identity — creates one if not 2. Deploys the RWA Diamond via `GenericRWAFactory.createRWA()` 3. Wires compliance modules based on `assetType` 4. Grants `RouterHook` and `P2POfferBook` `AGENT_ROLE` 5. Stores `priceSource` credentials encrypted in D1 **Response:** `{ "id": "uuid", "status": "pending" }` (202) *** ### `POST /rwa/:address/mint` — Mint to Investor ```json { "investorAddress": "0xdef...", "amount": "1000000000000000000" } ``` `amount` in token base units (typically 18 decimals for RWA). *** ### `POST /rwa/:address/burn` ```json { "from": "0xdef...", "amount": "500000000000000000" } ``` *** ### `POST /rwa/:address/freeze` — Freeze Investor ```json { "investorAddress": "0xdef...", "freeze": true } ``` *** ### `POST /rwa/:address/forced-transfer` Compliance-driven transfer between holders. ```json { "from": "0xdef...", "to": "0xghi...", "amount": "100000000000000000" } ``` *** ### `GET /rwa/list` — All Active RWAs ```json { "tokens": [ { "address": "0x...", "name": "Accra Commercial Property", "symbol": "ACP", "assetType": 0, "jurisdiction": "GH", "totalSupply": "100000000000000000000" } ] } ``` *** ### `GET /rwa/:address` — Full Token State ```json { "address": "0x...", "name": "Accra Commercial Property", "symbol": "ACP", "totalSupply": "100000000000000000000", "paused": false, "complianceAddress": "0x...", "identityRegistry": "0xC44357...", "onchainID": "0x...", "assetConfig": { "assetType": 0, "jurisdiction": "GH" }, "db": { "status": "active", "assetId": "PROP-GH-001" } } ``` *** ### Compliance Modules #### `POST /rwa/compliance/:address/add-country-restrict` ```json { "allowedCountries": ["GH", "NG", "KE"] } ``` #### `POST /rwa/compliance/:address/add-max-balance` ```json { "maxBalance": "10000000000000000000" } ``` *** ### AI Compliance Chat `core-rwa` embeds an AI assistant for managing compliance configuration conversationally. It uses **Server-Sent Events (SSE)** streaming. #### `POST /rwa/compliance/chat/start` Start a new compliance chat session for a Diamond. **Request** ```json { "diamondAddress": "0xrwa..." } ``` **Response** ```json { "sessionId": "chat-uuid", "greeting": "Hi! I can see ACP has CountryRestrict and MaxBalance modules active...", "currentModules": ["CountryRestrictModule", "MaxBalanceModule"] } ``` #### `POST /rwa/compliance/chat/message` (SSE) Send a message and receive a streamed response. **Request** ```json { "sessionId": "chat-uuid", "message": "Add Nigeria to the allowed countries" } ``` **Response** — `Content-Type: text/event-stream` ``` data: {"type": "token", "content": "I'll add Nigeria"} data: {"type": "token", "content": " to the CountryRestrictModule."} data: {"type": "ready", "jobId": "job-uuid", "config": { ... }} data: {"type": "done"} ``` The AI proposes a change. When `type: "ready"` fires, the caller gets a `jobId` with the proposed config. #### `POST /rwa/compliance/apply` Execute the AI-proposed compliance change. ```json { "jobId": "job-uuid" } ``` #### `GET /rwa/compliance/job/:jobId` Poll status of a pending compliance change. ```json { "jobId": "job-uuid", "status": "pending", "config": { ... } } ``` ## BNPL > **Who it's for:** Shoppers who want to spread the cost of a purchase over time, and merchants who want to offer installment plans without managing credit risk themselves. > **Why it matters:** BNPL brings structured installment credit to African markets where consumer finance is scarce — purchases are paid in predictable monthly steps drawn directly from a lending pool. Buy Now Pay Later lets a borrower purchase goods from a merchant and repay in scheduled installments drawn from a lending pool. ### `POST /bnpl/create` **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "poolId": "1", "merchant": "0xmerchant...", "totalAmount": "120000000", "numInstallments": 6, "installmentIntervalSeconds": 2592000 } ``` | Field | Constraints | | ---------------------------- | ----------------------- | | `numInstallments` | 1–36 | | `installmentIntervalSeconds` | ≥ 86400 (1 day minimum) | Transaction batch: `[approve(lendingDiamond, totalAmount), createPlan(...)]` **Response:** `{ id, status: 'pending' }` *** ### `POST /bnpl/:planId/pay-installment` Pays the next unpaid installment. The service reads the due amount on-chain (including any late fee) before submitting. **Request** ```json { "userAddressId": "...", "userAddress": "0x..." } ``` The service: 1. Reads `getInstallment(planId, nextIndex)` to get due amount 2. Checks if overdue → adds 2% late fee 3. Transaction batch: `[approve(lendingDiamond, amount + lateFee), payInstallment(planId)]` *** ### `POST /bnpl/:planId/mark-late/:installmentIndex` Called by the keeper when an installment is overdue. ```json { "callerAddressId": "...", "callerAddress": "0x..." } ``` *** ### Read Endpoints ``` GET /bnpl/:planId — plan summary + D1 tracking GET /bnpl/:planId/installment/:index — { dueDate, amount, status } GET /bnpl/borrower/:address — all plans for an address ``` **Plan summary shape** ```json { "planId": "3", "borrower": "0x...", "merchant": "0x...", "totalAmount": "120000000", "amountPaid": "40000000", "numInstallments": 6, "installmentsPaid": 2, "active": true } ``` ## Collateral & Credit ### CollateralVault > **Who it's for:** Asset holders — property owners, investors, or businesses holding tokenised real-world assets — who want to unlock liquidity without selling what they own. > **Why it matters:** Tokenised assets act as collateral directly on-chain, so holders can borrow cGHS against the value of a property or bond without going through a bank or selling the asset. Borrow cGHS by locking RWA tokens as collateral. The diamond freezes the RWA tokens in-place (no transfer needed) and pays out from reserves. #### `POST /collateral/open` **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "rwaToken": "0xrwa...", "collateralAmount": "1000000000000000000", "borrowToken": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "borrowAmount": "200000000" } ``` Pre-flight checks by the service: * `CreditScoring.maxLtvBps(user) > 0` — user must not be UNRATED * `CollateralVault.reserveOf(borrowToken) >= borrowAmount` — reserve must cover No token approval needed — the diamond freezes the RWA tokens as agent. *** #### `POST /collateral/:positionId/repay` Partial repayment on an open position. ```json { "userAddressId": "...", "userAddress": "0x...", "amount": "50000000" } ``` Transaction batch: `[approve(lendingDiamond, amount), repay(positionId, amount)]` *** #### `POST /collateral/:positionId/close` Repay full outstanding debt and unfreeze RWA collateral. ```json { "userAddressId": "...", "userAddress": "0x..." } ``` *** #### `POST /collateral/:positionId/liquidate` Liquidate a position where `healthFactor < 1.0`. Callable by anyone (keeper or any liquidator). ```json { "liquidatorAddressId": "...", "liquidatorAddress": "0x..." } ``` *** #### Read Endpoints ``` GET /collateral/:positionId — position + healthFactor + totalDue GET /collateral/:positionId/health — { healthFactor, liquidatable } GET /collateral/borrower/:address — all positions ``` *** ### Credit Scoring > **Who it's for:** Any user who has borrowed, repaid, or saved on Shika — their on-chain history is automatically converted into a score that improves their borrowing terms over time. > **Why it matters:** Creditworthiness is built from real financial behaviour rather than a bank statement, giving people with no formal credit history a fair path to better rates. Credit scores are FICO-style composites (350–850) computed from on-chain behaviour. They're **read-only** from the service — scores are updated by the `valuation-service` TEE or the keeper wallet. #### `GET /credit/:address` ```json { "subScores": { "repaymentHistory": 82, "utilizationRate": 74, "accountAge": 60, "incomeVerification": 90 }, "compositeScore": 765, "tier": "PLATINUM", "lastUpdated": 1710854400, "zkTlsVerified": true, "provablyVerified": true } ``` #### `GET /credit/:address/score` ```json { "compositeScore": 765, "tier": "PLATINUM" } ``` #### `GET /credit/:address/ltv` ```json { "maxLtvBps": 7500, "maxLtvPercent": "75.0%" } ``` #### `GET /credit/:address/verified` ```json { "verified": true } ``` ## core-lending > **Who it's for:** Borrowers who need working capital or want to buy now and pay in installments, capital providers looking to earn interest on deployed cGHS, and businesses offering credit to customers. > **Why it matters:** Access to credit on Shika is based on verified identity and on-chain behaviour — not a bank relationship — so more people qualify and terms improve as they build a track record. `core-lending` wraps the **ShikaLending Diamond** at `0x6360CeB8696f46fFef53f5a7B4802EB9Cd022BC4`. It exposes six facets as clean HTTP endpoints: | Facet | Route prefix | Description | | ---------------------- | ------------- | ---------------------------------------- | | `LendingPoolFacet` | `/pool` | Delegate-run lending pools | | `LendingPoolFacet` | `/loan` | Loan lifecycle — request, repay, default | | `BNPLFacet` | `/bnpl` | Buy Now Pay Later installment plans | | `YieldVaultFacet` | `/yield` | Yield vault | | `SavingsGoalFacet` | `/goal` | Locked savings with penalty | | `CollateralVaultFacet` | `/collateral` | RWA-backed collateral positions | | `CreditScoringFacet` | `/credit` | FICO-style credit scores (read-only) | ### SSI Requirement Lending operations require a verified SSI identity: * `requestLoan` — `CreditScoring.maxLtvBps(borrower) > 0` (UNRATED blocked) * `openPosition` — same check * Credit tier drives max LTV: | Tier | Score | Max LTV | | -------- | ------- | ------------ | | UNRATED | — | 0% (blocked) | | BRONZE | 350–499 | 20% | | SILVER | 500–649 | 40% | | GOLD | 650–749 | 60% | | PLATINUM | 750–850 | 75% | ### Protocol Fees | Fee | Value | | ------------------------ | ----------------------------- | | Protocol fee on interest | 5% (`PROTOCOL_FEE_BPS = 500`) | | BNPL late fee | 2% per overdue installment | | SavingsGoal max penalty | 20% (`penaltyBps ≤ 2000`) | | YieldVault max deploy | 80% of deposits | cGHS uses **6 decimals** — `1000000` = 1 cGHS. ## Lending Pool & Loans ### Lending Pools > **Who it's for:** Capital providers — businesses, cooperatives, or investors — who want to earn interest by supplying cGHS to a pool that verified borrowers can draw from. > **Why it matters:** Delegates set their own terms and bear first-loss risk, creating a market-driven lending layer where pool operators are accountable and borrowers get transparent pricing. Pools are created by **delegates** — capital providers who supply first-loss liquidity and set terms. Borrowers draw from the pool. #### `POST /pool/create` **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "stablecoin": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "interestRateBps": 1200, "maxLoanDurationSeconds": 2592000, "firstLossCapAmount": "500000000" } ``` | Field | Constraints | | ------------------------ | ------------------------------------------ | | `interestRateBps` | 1–10000 (1bp = 0.01%) | | `maxLoanDurationSeconds` | Max loan term in seconds | | `firstLossCapAmount` | Delegate's first-loss deposit (base units) | Transaction batch: `[approve(lendingDiamond, firstLossCapAmount), createPool(...)]` **Response:** `{ id, status: 'pending' }` *** #### `POST /pool/:poolId/add-liquidity` ```json { "userAddressId": "...", "userAddress": "0x...", "stablecoin": "0x...", "amount": "100000000" } ``` #### `POST /pool/:poolId/remove-liquidity` ```json { "userAddressId": "...", "userAddress": "0x...", "amount": "100000000" } ``` #### `GET /pool/:poolId` ```json { "stablecoin": "0x...", "delegate": "0x...", "totalLiquidity": "600000000", "totalBorrowed": "200000000", "firstLossCap": "500000000", "interestRateBps": 1200, "maxLoanDuration": 2592000, "active": true } ``` *** ### Loans > **Who it's for:** Verified individuals and small business owners who need short-term working capital and have built enough on-chain credit history to qualify. > **Why it matters:** Borrowers access funds without collateral as long as their credit tier allows it — the more consistently they repay, the more they can borrow and the lower their effective rate. #### `POST /loan/request` Borrow from a pool (`requestLoan` on-chain). Borrower must **not** be UNRATED (`maxLtvBps > 0`). **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "poolId": "1", "principal": "50000000" } ``` No token approval needed — borrower receives tokens. **Response:** `{ id, status: 'pending' }` *** #### `POST /loan/:loanId/repay` Full repayment. Service reads `outstandingDebt(loanId)` first, then submits batch with approval. ```json { "userAddressId": "...", "userAddress": "0x..." } ``` *** #### `POST /loan/:loanId/repay-partial` ```json { "userAddressId": "...", "userAddress": "0x...", "amount": "25000000" } ``` *** #### `POST /loan/:loanId/mark-default` Called by keeper or admin when `dueDate` has passed. ```json { "callerAddressId": "...", "callerAddress": "0x..." } ``` *** #### Read Endpoints ``` GET /loan/:loanId — loan state + D1 tracking GET /loan/:loanId/outstanding — { principal, interest, totalDue } GET /loan/borrower/:address — all loans for an address ``` ## Yield Vault & Savings Goals The `core-lending` service exposes both the YieldVault and SavingsGoal facets on the ShikaLending Diamond. These are the same primitives as in `core-savings` — `core-lending` simply provides the full lending context alongside them. See the full documentation in the core-savings section: Yield Vault and Savings Goals. *** ### Yield Vault Quick Reference ``` POST /yield/deposit { userAddressId, userAddress, stablecoin, assets } POST /yield/withdraw { userAddressId, userAddress, stablecoin, shares } GET /yield/balance/:addr ?stablecoin=0x... → { shares, assets } GET /yield/exchange-rate ?stablecoin=0x... → { rate, precision } GET /yield/total ?stablecoin=0x... → { totalAssets, totalShares } GET /yield/preview/deposit ?stablecoin=0x...&assets=... → { shares } GET /yield/preview/withdraw ?stablecoin=0x...&shares=... → { assets } ``` *** ### Savings Goal Quick Reference ``` POST /goal/create { userAddressId, userAddress, stablecoin, amount, lockUntil, penaltyBps, name } POST /goal/:goalId/topup { userAddressId, userAddress, amount } POST /goal/:goalId/withdraw { userAddressId, userAddress } POST /goal/:goalId/buy { userAddressId, userAddress, poolId, merchant, totalPrice, numInstallments, intervalSeconds } GET /goal/:goalId GET /goal/:goalId/preview → { gross, penalty, net, isMature } GET /goal/user/:address ``` ## On-Chain Keepers > **Who it's for:** The protocol itself — keepers run invisibly in the background, keeping prices fresh, enforcing repayment schedules, and processing defaults without any user needing to trigger them. > **Why it matters:** Automated enforcement makes the protocol trustworthy for everyone — borrowers know the rules are applied consistently, and lenders know late payments and defaults are handled on time. `-onchain-keepers` are scheduled cron jobs that automate time-sensitive protocol operations that no user would reliably trigger themselves. ### Jobs | Cron job | What it does | Schedule | | ----------------- | -------------------------------------------------- | -------- | | `syncAssets` | Syncs on-chain asset prices to the oracle | Every 6h | | `updateRWAPrices` | Pushes MPC-TLS verified prices for all active RWAs | Every 6h | | `markLate` | Marks overdue BNPL installments as late | Daily | | `markDefaults` | Marks loans past due date as defaulted | Daily | ### `markLate` Iterates all active BNPL plans in D1, checks each installment's `dueDate` against current time, and calls `core-lending POST /bnpl/:planId/mark-late/:index` for any overdue installments not yet marked. ### `markDefaults` Iterates all active loans in D1, checks `dueDate + gracePeriod`, and calls `core-lending POST /loan/:loanId/mark-default` for qualifying loans. ### Deployment ```bash cd -onchain-keepers/cloudflare-keeper wrangler deploy ``` Cron triggers are declared in `wrangler.jsonc`: ```jsonc "triggers": { "crons": ["0 */6 * * *", "0 2 * * *"] } ``` ## USSD / Offline Access `rpc-service-offline` is an Express server that bridges feature phones and USSD sessions to the Shika smart contracts. Users with no smartphones can save, borrow, and transfer cGHS via a `*123#` dial code. ### How It Works ``` User dials *123# on any phone ↓ USSD Gateway (Africa's Talking / Telecel) ↓ rpc-service-offline (Express + UssdStateManger) ↓ Session state machine (prompt tree) ↓ RPC calls to worker-two (identity) and core-* services ↓ Response back to user's screen (max 182 chars) ``` ### Session State Machine ``` MAIN_MENU ├── 1 — Balance │ → reads cGHS balance on-chain ├── 2 — Send │ → prompt: recipient phone | amount │ → transfer via Shika Wallet ├── 3 — Savings │ ├── a — ROSCA group │ └── b — Savings goal ├── 4 — Borrow │ → checks SSI + credit score │ → initiates loan request └── 5 — KYC → calls worker-two.initiateKycSsi() → user provides ID details over USSD ``` ### Identity via RPC The offline service calls `worker-two` over internal service bindings (not HTTP): ```typescript // In rpc-service-offline worker-two binding: const result = await env.IDENTITY_SERVICE.initiateKycSsi({ walletIdentifier: phoneNumber, country: 'GH', id_type: 'NATIONAL_ID', id_number: idNumber, first_name, last_name, dob, }) ``` This keeps KYC logic in one place — `worker-two` — and USSD access adds no new trust surface. ### Prompts All user-facing strings live in `app/src/prompt/` — one file per language/country. Currently: English (Ghana). ## core-jit > **Who it's for:** Anyone swapping between cGHS and USDC, liquidity providers wanting to earn fees on GHS pairs, and developers building payment or currency conversion features into their apps. > **Why it matters:** Deep, always-available liquidity for the Ghana Cedi means users get fair exchange rates instantly, whether they're converting remittances or topping up a merchant account. `core-jit` manages the **Shika Liquidity Atomic Swap Engine** — an on-chain hook that routes cGHS/USDC and RWA token swaps, injecting just-in-time liquidity from multiple sources. ### Architecture ``` Swap request ↓ RouterHook (Shika Liquidity Engine) ↓ selects liquidity source autonomously: 1. P2P Offer — match a KYC-gated LP offer (best price) 2. Flash JIT — flash-borrow from LendingPool, inject JIT 3. JITRebalancer — shared fallback pool for large swaps ↓ GHSPriceRouter — reads live GHS/USD from Chainlink oracle ``` ### Contracts | Contract | Address | | --------------- | -------------------------------------------- | | RouterHook | `0x23350973855cCFe6EfF30b5f5D334Ea1D36280C0` | | P2POfferBook | `0xc4Bdc3A321003D9420DbA21Ed89718904670A5cA` | | GHSPriceRouter | `0x0D4f47a10B151acc82469078C57f853F53036439` | | JITRebalancer | `0x64b7DeD624377Dc0eE5050b3B457814b2b94C5d5` | | PoolManager | `0x498581fF718922c3f8e6A244956aF099B2652b2b` | | PositionManager | `0x7C5f5A4bBd8fD63184577525326123B519429bDc` | ### Active Pool — cGHS / USDC | | Value | | ------------- | -------------------------------------------------------------------- | | Pool ID | `0x2f7c1084697ba69acb41b675f80b800ede45c61e7b25b4a04d0f8dca249fe2bd` | | token0 (cGHS) | `0x3126627A607E730E3eCF9BbfFD67fa18Deaab846` | | token1 (USDC) | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | | Fee | 1 bps (100) | | Tick spacing | 10 | ### Route Groups | Prefix | Description | | ------------- | ---------------------------- | | `/swap` | Execute swaps, get quotes | | `/offers` | P2P LP offer lifecycle | | `/liquidity` | Persistent range positions | | `/rebalancer` | JIT Rebalancer shared pool | | `/price` | GHS/USD and RWA oracle reads | | `/pool` | Pool state reads | | `/pools` | All pools list | | `/admin` | Pool creation (admin only) | ## Liquidity > **Who it's for:** Passive investors and active market makers who want to earn fees from cGHS trading activity without managing individual trades. > **Why it matters:** Providing liquidity to the GHS pair generates fee income while directly supporting price stability and trade execution for every user on the platform. Two types of liquidity provision in the JIT system: | Type | Mechanism | Who uses it | | ------------------ | ------------------------------------------------------------- | ------------- | | **Standard LP** | Persistent range positions via `PositionManager` | Passive LPs | | **JIT Rebalancer** | Shared pool — fallback for large swaps with no matching offer | Market makers | *** ### Standard LP Positions #### `POST /liquidity/add` Mint a new LP range position. **Request** ```json { "lpAddressId": "wallet-uuid", "lpAddress": "0xabc...", "token0": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "token1": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "tickLower": -27360, "tickUpper": -27120, "amount0Desired": "5000000000", "amount1Desired": "325000000", "amount0Min": "4900000000", "amount1Min": "320000000" } ``` Ticks must be multiples of `10` (tick spacing). Service computes `liquidityDelta` from current pool price. Transaction batch: `[approve(positionManager, amount0), approve(positionManager, amount1), mintPosition(...)]` **Response:** `{ "id": "uuid", "status": "pending", "liquidity": "..." }` (202) On confirmation the workflow decodes the `Transfer` event from `PositionManager` to record the `tokenId`. *** #### `POST /liquidity/remove` Decrease liquidity from an existing position. ```json { "lpAddressId": "wallet-uuid", "lpAddress": "0xabc...", "tokenId": "42", "liquidity": "1000000000000000000", "amount0Min": "0", "amount1Min": "0" } ``` *** #### `GET /liquidity/positions/:address` All active LP positions with live on-chain state. #### `GET /liquidity/position/:tokenId` ```json { "tracking": { "id": "uuid", "status": "active", "tokenId": "42" }, "onchain": { "tickLower": -27360, "tickUpper": -27120, "liquidity": "1000000000000000000" } } ``` *** ### JIT Rebalancer Pool The `JITRebalancer` at `0x64b7DeD624377Dc0eE5050b3B457814b2b94C5d5` holds shared liquidity used as a flash-JIT fallback. Depositors earn fees from large swaps that route through it. #### `POST /rebalancer/:rebalancerAddress/deposit` ```json { "depositorAddressId": "wallet-uuid", "depositorAddress": "0xabc...", "amount0": "10000000000", "amount1": "650000000" } ``` Default rebalancer: `0x64b7DeD624377Dc0eE5050b3B457814b2b94C5d5` #### `POST /rebalancer/:rebalancerAddress/withdraw` ```json { "depositorAddressId": "wallet-uuid", "depositorAddress": "0xabc...", "shareAmount": "1000000" } ``` #### `GET /rebalancer/:rebalancerAddress` ```json { "token0": "0x3126...", "token1": "0x833...", "totalDepositedToken0": "100000000000", "totalDepositedToken1": "6500000000", "totalShares": "50000000", "currentPrice": "0.065", "priceFeed": "0xaa1..." } ``` ## P2P Offer Book > **Who it's for:** Verified liquidity providers — businesses, remittance operators, or individuals — who want to earn a spread by offering to exchange cGHS at their own price. > **Why it matters:** P2P offers let real market participants set prices based on local GHS demand, keeping the on-chain rate anchored to what the currency is actually worth in the market. LPs create offers by depositing both sides of a token pair. During swaps, `RouterHook` automatically matches against open offers. **SSI KYC is required to create an offer.** ### `POST /offers/create` — Create Offer **SSI required:** Yes — `SSI.isVerified(identity)` must be `true`. **Request** ```json { "lpAddressId": "wallet-uuid", "lpAddress": "0xabc...", "token0": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "token1": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount0": "1000000000", "amount1": "65000000", "priceType": "FLOATING", "spreadBps": 30, "minSwapAmountUsd": "10000000", "maxSwapAmountUsd": "1000000000", "expiry": 1800000000 } ``` | Field | Notes | | --------------------------------------- | ------------------------------------------------------------------- | | `priceType` | `"FIXED"` or `"FLOATING"` | | `fixedPrice` | 8-decimal fixed price (FIXED only) e.g. `"6500000"` = 0.065 USD/GHS | | `spreadBps` | ±5000 bps (FLOATING only). `30` = +0.30% above oracle | | `minSwapAmountUsd` / `maxSwapAmountUsd` | USD-denominated swap size limits (base units) | | `expiry` | Unix timestamp | **Response** ```json { "id": "uuid", "status": "pending" } ``` *** ### `POST /offers/:offerId/cancel` ```json { "lpAddressId": "uuid", "lpAddress": "0xabc..." } ``` *** ### `POST /offers/:offerId/claim-expired` Reclaim tokens from an offer that has passed its expiry. ```json { "lpAddressId": "uuid", "lpAddress": "0xabc..." } ``` *** ### `GET /offers` — List Active Offers ``` GET /offers?token0=0x312...&token1=0x833... ``` **Response** ```json { "offers": [ { "offerId": "5", "lp": "0xabc...", "priceType": "FLOATING", "spreadBps": 30, "remainingAmount0": "950000000", "remainingAmount1": "61750000", "status": "OPEN" } ] } ``` *** ### `GET /offers/lp/:address` — LP's Offers Returns all offers created by a given LP address, with live on-chain status. *** ### Offer Lifecycle ``` created (pending) ↓ OfferCreateWorkflow confirms open ↓ RouterHook.beforeSwap fills open (partially filled) → filled (fully filled) or cancelled / expired → LP claims back tokens ``` ## Swaps & Price > **Who it's for:** Users converting between cGHS and USDC — whether sending money, cashing out, or accessing a different currency for a purchase. > **Why it matters:** Swaps route automatically through the best available liquidity source, so users always get a competitive rate without needing to understand how the market works underneath. ### `POST /swap` Execute a token swap through the Shika Liquidity Engine. The `RouterHook` fires automatically — P2P offer matching, JIT liquidity injection, and flash loan fallback all happen on-chain with no extra service calls. **SSI required:** No — swaps are open to any wallet. **Request** ```json { "userAddressId": "wallet-uuid", "userAddress": "0xabc...", "tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "tokenOut": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "amountIn": "65000000", "minAmountOut": "980000000", "offerId": "5" } ``` | Field | Notes | | ---------------------- | --------------------------------------------------------- | | `tokenIn` / `tokenOut` | Token addresses — service sorts to derive `token0/token1` | | `amountIn` | Exact input, base units | | `minAmountOut` | Slippage protection | | `offerId` | Optional — targets a specific P2P offer via `hookData` | **Response** ```json { "success": true, "txHash": "0xdeadbeef..." } ``` Swaps are fire-and-confirm — no D1 tracking. *** ### `GET /swap/quote` Off-chain output estimate for a given input. ``` GET /swap/quote?tokenIn=0x833...&tokenOut=0x312...&amountIn=65000000 ``` **Response** ```json { "estimatedOut": "984000000", "priceImpactBps": 12, "currentPrice": "0.06823" } ``` *** ### How the Hook Works ``` poolManager.swap(poolKey, params, hookData) ↓ RouterHook.beforeSwap() [automatic] 1. Check P2POfferBook for matching offer → match: lock offer, inject P2P JIT at offer price 2. No match + large swap (> threshold): → flash borrow from LendingPool, inject JIT at oracle price 3. No match + small swap: → JITRebalancer shared pool fallback Swap executes RouterHook.afterSwap() [automatic] → remove JIT liquidity, repay flash loan, unlock offer ``` *** ### `GET /pool` — Default Pool State ```json { "sqrtPriceX96": "...", "currentTick": -27336, "liquidity": "...", "ghsUsdPrice": "0.065230", "lpFee": 100, "protocolFee": 0, "meta": { "token0": "0x3126...", "token1": "0x833..." } } ``` *** ### Price Routes ``` GET /price/ghs → { answer, updatedAt, staleSince } GET /price/ghs/sqrt?spreadBps=0 → { sqrtPriceX96, tick } GET /price/ghs/fixed?price=6500000 → { sqrtPriceX96, tick } GET /price/rwa/:routerAddress → { sqrtPriceX96, tick, oraclePrice, oracleUpdatedAt } GET /price/rwa/:routerAddress/sqrt?spreadBps=0 → { sqrtPriceX96, tick } ``` *** ### Admin — Pool Creation Used when launching a new trading pair (e.g. RWA token / cGHS). #### `GET /admin/pool/derive` Preview pool parameters before creating. ``` GET /admin/pool/derive?rwaToken=0x...&pairedStablecoin=0x...&priceRouter=0x... → { token0, token1, initialSqrtPriceX96, impliedPrice, fee, tickSpacing, hooks, poolId } ``` #### `POST /admin/pool/create` Requires `x-admin-secret`. ```json { "rwaToken": "0xrwa...", "pairedStablecoin": "0x3126627A607E730E3eCF9BbfFD67fa18Deaab846", "priceRouterAddress": "0xpricerouter...", "deployRebalancer": true } ``` **Response:** `{ "id": "uuid", "status": "pending" }` (202) ## Identity — SSI Bedrock > **Who it's for:** Every Shika user — from a market trader in Accra opening their first digital wallet to a developer onboarding customers into a savings or lending product. > **Why it matters:** A single verified identity unlocks every product on the platform, so users never repeat KYC and their financial history compounds over time into a credit score. The identity service (`worker-two`) is the **entry point for all users** on Shika. It handles: 1. **Wallet provisioning** — creates a Shika Wallet for the user 2. **Biometric KYC** — verifies identity via SmileID 3. **On-chain SSI** — deploys an on-chain identity contract and issues a KYC claim (topic `666`) Once issued, the SSI claim is composably shared across every Shika product — no re-verification needed. ### Why SSI? Identity is verified once and reused everywhere. After a user passes KYC, their credential is stored on-chain and silently checked by every Shika product — no re-submitting documents, no repeated verification steps. ✓ Borrow from a lending pool ✓ Provide liquidity via P2P offers ✓ Hold or transfer RWA tokens ✓ Open collateral positions ✓ Build a credit score ### Contracts | Contract | Address | | --------------------- | -------------------------------------------- | | SSIComposable (proxy) | `0xC44357264CcD8aDA10A16889eBa5FA0ab582a876` | | ClaimIssuer | `0x89FAcE94A8738B50F2DE8B5D2Eb8464e463300e2` | | Verifier | `0x96a34b41e41df9928e69B51061240f12D19Fdd8e` | | IdentityFactory | `0x4C47d600f056eBD01B538a7bB7eF48C0bd885b4a` | ### Service Entry Point `worker-two` is an RPC entrypoint (`WorkerEntrypoint`). It's called over internal service bindings by other services (e.g. `rpc-service-offline` for USSD flows) and also exposed as an HTTP API for the mobile/web frontends. #### Identity States ``` No wallet → WALLET_CREATED → KYC_INITIATED → KYC_VERIFIED → SSI_ISSUED ``` ## KYC Flow > **Who it's for:** Mobile and web app developers integrating user onboarding, and any backend service that needs to verify a new user's identity before granting access. > **Why it matters:** The KYC flow handles identity verification, wallet creation, and on-chain credential issuance in one pipeline — so a user goes from sign-up to fully verified in minutes, not days. `core-identity` exposes REST endpoints under: ``` /api/shika-finance/identity/v1/kyc/... /api/shika-finance/identity/v1/ssi/... ``` *** ### KYC #### `POST /kyc/enhanced` — Biometric KYC via SmileID **Request** ```json { "country": "GH", "id_type": "PASSPORT", "id_number": "G12345678", "first_name": "Kwame", "last_name": "Mensah", "dob": "1990-05-15" } ``` | Field | Notes | | ----------- | -------------------------------------------- | | `country` | ISO 3166-1 alpha-2 | | `id_type` | `PASSPORT`, `NATIONAL_ID`, `DRIVERS_LICENSE` | | `id_number` | Document number | | `dob` | `YYYY-MM-DD` | **Response** ```json { "success": true, "data": { "smileVerified": true, "smileResult": { ... } } } ``` #### `POST /kyc/enhanced/callback` — SmileID Webhook Receives SmileID job result callbacks. Triggers SSI provisioning on success. *** ### SSI Wallet & Identity #### `POST /ssi/wallets` — Create / Fetch Shika Wallet Creates a Shika Wallet for the user (idempotent — returns existing wallet if `walletIdentifier` already registered). **Request** ```json { "walletIdentifier": "user@example.com", "useTestKey": false } ``` **Response** ```json { "success": true, "message": "Wallet created", "walletAddress": "0xabc...", "walletCreated": true } ``` *** #### `POST /ssi/initialize` — Initialize SSI Contracts Wire the user's wallet to the protocol's SSI infrastructure (factory, verifier, claim issuer, ENS registrar). Called once per user after wallet creation. **Request** ```json { "walletIdentifier": "user@example.com", "identityFactory": "0x4C47d600f056eBD01B538a7bB7eF48C0bd885b4a", "verifier": "0x96a34b41e41df9928e69B51061240f12D19Fdd8e", "claimIssuer": "0x89FAcE94A8738B50F2DE8B5D2Eb8464e463300e2", "l2Registrar": "0x10E0EfB9ac9829A83c705067cAc3aED43d5D064d", "l2Registry": "0xe40d1B1D157c1b97Bcc52A170e659Ed65dFBA43c" } ``` **Response** ```json { "success": true, "message": "SSI initialized", "transaction": { "id": "tx-uuid", "hash": "0x...", "status": "CONFIRMED" }, "addressId": "wallet-address-uuid" } ``` *** #### `POST /ssi/create-identity` — Deploy On-Chain Identity Deploys an on-chain identity contract for the user. **Request** ```json { "walletIdentifier": "user@example.com", "user": "0xabc...", "salt": "0xsalt..." } ``` **Response** ```json { "success": true, "message": "Identity created", "transaction": { "id": "tx-uuid", "hash": "0x...", "status": "CONFIRMED" }, "addressId": "wallet-address-uuid" } ``` *** #### `POST /ssi/register-ens` — Register ENS Name Registers `{label}.shikawallet.com` for the user's identity. **Request** ```json { "walletIdentifier": "user@example.com", "ensLabel": "kwame" } ``` *** ### RPC Methods (worker-two — USSD binding) When `rpc-service-offline` calls `worker-two` over internal service bindings instead of HTTP: ```typescript // Full KYC + SSI in one call const result = await env.IDENTITY_SERVICE.initiateKycSsi({ walletIdentifier, country, id_type, id_number, first_name, last_name, dob, callback_url?, }) // Get full profile const profile = await env.IDENTITY_SERVICE.getUserProfile({ walletIdentifier }) // Check KYC status only const status = await env.IDENTITY_SERVICE.checkKycStatus({ walletIdentifier }) // PIN management await env.IDENTITY_SERVICE.setPin({ walletIdentifier, pin }) const { isValid } = await env.IDENTITY_SERVICE.verifyPin({ walletIdentifier, pin }) ``` *** ### KYC Status Values | Status | Meaning | | ---------------- | ------------------------------------- | | `WALLET_CREATED` | Shika Wallet created, KYC not started | | `KYC_INITIATED` | SmileID job submitted | | `KYC_VERIFIED` | SmileID approved | | `SSI_ISSUED` | On-chain identity + claim confirmed | | `KYC_FAILED` | SmileID rejected | ## SSI & Claims > **Who it's for:** Service developers building any Shika product that needs to confirm a user is verified before allowing them to borrow, trade, or hold assets. > **Why it matters:** A single on-chain credential check replaces every per-product identity gate — compliance is automatic and consistent across the entire protocol. ### Reading Identity On-Chain Any service can verify a user's KYC status directly from `SSIComposable`: ```solidity // Get a user's on-chain identity contract address address identity = ssi.userToIdentity(userAddress); // Check if the identity holds a valid KYC claim (topic 666) bool verified = ssi.isVerified(identity); ``` #### SSI ABI Fragments Used Across Services ```typescript // Shared across core-jit, core-lending, core-rwa const SSI_ABI = [ { name: 'userToIdentity', type: 'function', inputs: [{ name: 'user', type: 'address' }], outputs: [{ type: 'address' }], stateMutability: 'view', }, { name: 'isVerified', type: 'function', inputs: [{ name: 'identity', type: 'address' }], outputs: [{ type: 'bool' }], stateMutability: 'view', }, ] ``` *** ### Where SSI Is Enforced | Service | Endpoint | Check | | ---------------- | ----------------------- | ----------------------------------------------- | | **core-jit** | `POST /offers/create` | `SSI.isVerified(identity) === true` | | **core-lending** | `POST /loan/request` | `CreditScoring.maxLtvBps > 0` (UNRATED blocked) | | **core-lending** | `POST /collateral/open` | `CreditScoring.maxLtvBps > 0` | | **core-rwa** | `POST /rwa/create` | `SSI.userToIdentity(owner)` must exist | | **core-rwa** | RWA compliance modules | `CountryRestrictModule` reads from SSI | *** ### Protocol Contract Identities Smart contracts themselves also have SSI identities — this allows them to hold compliant RWA tokens and be whitelisted as agents. | Contract | SSI Identity | | -------------------- | -------------------------------------------- | | ShikaLending Diamond | `0xFc38926563CD4421377bfd3C1616BB26D97465E6` | | JITRebalancer | `0x7463BE655FfeE985D71B7799Df2F012fE11B027D` | | RouterHook | `0xD2AE27D69AC765139B89227D876D6B85612444ed` | | P2POfferBook | `0xc7a994aa311FA8AcCdC6248ce13d6cDDA91bD5Bb` | ## Contra KYC — On-Chain Compliance Contra KYC is an on-chain compliance scoring service for Web3 agents and wallets. It indexes EVM activity via [Substreams](https://substreams.streamingfast.io/), stores structured profiles in MongoDB, and exposes an AI-powered AML risk scoring API. Shika Protocol integrates Contra KYC to verify wallet compliance before allowing participation in lending pools, RWA transfers, and JIT liquidity offers. ### Architecture ``` Base / EVM Chain │ Substreams sink (Rust) │ indexes transfers, DeFi events, identity signals, risk flags ↓ MongoDB ──────────────────────────────────────────────┐ address_kyc agent_profiles cursors │ ↓ Hono REST API + MCP server │ AI risk scoring (Claude) │ ContraConsumer contract (Base Sepolia) writes verdict on-chain via CRE ``` ### Key Features * **Full on-chain profile** — ETH transfers, ERC-20, NFTs, approvals, bridge events, DeFi interactions, identity signals * **AI risk assessment** — Claude-powered AML scoring with risk flags and investigation steps * **On-chain verdicts** — Risk scores written to the `ContraConsumer` contract via Chainlink CRE * **MCP server** — Exposes KYC data as Model Context Protocol tools for AI agents * **x402 payment gate** — Paid endpoints use HTTP 402 / on-chain USDC payments * **EIP-8004 agent profiles** — Indexes registered AI agent identities ### Deployments | Contract | Network | Address | | -------------- | ------------ | -------------------------------------------- | | ContraConsumer | Base Sepolia | `0xdF45f60DeCfa86136e9Be956B569876cdb7b7206` | | TestUSDC | Base Sepolia | `0x69d92d7c11c53a2892060942348A312F0f0F6fBf` | ### API Base URL ``` http://localhost:3000 # local dev ``` ### Endpoints | Method | Path | Description | | ------ | ----------------------- | ----------------------------------- | | `GET` | `/kyc/:address` | Full on-chain profile | | `GET` | `/kyc/:address/risk` | AI AML risk assessment (x402 gated) | | `GET` | `/agent/:agentId` | EIP-8004 agent profile | | `GET` | `/agent/owner/:address` | Agents by owner | | `POST` | `/cre/trigger` | Trigger CRE on-chain verdict | | `GET` | `/faucet/:address` | Mint testnet USDC | | `GET` | `/status` | Substreams sink status | | `GET` | `/health` | Health check | | `POST` | `/mcp` | MCP server (Streamable HTTP) | ## `contra-kyc` SDK The `contra-kyc` npm package is the official TypeScript client for the Contra KYC API. It handles authentication, x402 payment headers, and provides typed wrappers for every endpoint. ### Installation :::code-group ```bash [npm] npm install contra-kyc ``` ```bash [yarn] yarn add contra-kyc ``` ```bash [pnpm] pnpm add contra-kyc ``` ::: > **Peer dependency:** `viem ^2.0.0` ### Quick Start ```typescript import { ContraClient } from 'contra-kyc' const client = new ContraClient({ baseUrl: 'http://localhost:3000', }) // Get full on-chain profile const profile = await client.getProfile('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045') console.log(profile.summary) // { // total_eth_transfers: 142, // unique_counterparties: 89, // protocols_used: ['uniswap', 'aave', ...], // ... // } ``` ### `ContraClient` #### Constructor ```typescript import { ContraClient, ContraConfig } from 'contra-kyc' const client = new ContraClient(config: ContraConfig) ``` ```typescript type ContraConfig = { /** Base URL of the Contra API */ baseUrl: string /** x402 payment header — required for paid endpoints like /risk */ paymentHeader?: string } ``` #### Methods ##### `getProfile(address)` Returns the full indexed on-chain profile for an address. ```typescript const profile = await client.getProfile('0xabc...') ``` **Returns:** [`KYCProfile`](#kycprofile) *** ##### `getRisk(address)` — x402 gated AI-powered AML risk assessment. Requires an x402 payment header (see [Payment](#x402-payment)). ```typescript const result = await client.getRisk('0xabc...') console.log(result.assessment.risk_score) // 0–100 console.log(result.assessment.risk_level) // 'low' | 'medium' | 'high' | 'critical' console.log(result.assessment.summary) console.log(result.onchain?.transaction) // tx hash if verdict written on-chain ``` **Returns:** [`RiskResponse`](#riskresponse) *** ##### `getAgent(agentId)` Get an EIP-8004 registered AI agent profile by numeric ID. ```typescript const agent = await client.getAgent(1) ``` **Returns:** [`AgentProfile`](#agentprofile) *** ##### `getAgentsByOwner(address)` Get all agents registered to an owner address. ```typescript const { agents } = await client.getAgentsByOwner('0xabc...') ``` *** ##### `getStatus()` Returns the Substreams sink status — last indexed block and collection counts. ```typescript const status = await client.getStatus() // { last_block: 29541200, total_addresses_indexed: 4821, ... } ``` *** ##### `triggerCRE(address)` Trigger a Chainlink CRE workflow to write the compliance verdict on-chain. ```typescript const result = await client.triggerCRE('0xabc...') ``` *** ##### `faucet(address)` Mint 100 testnet USDC on Base Sepolia (for x402 payment testing). ```typescript const result = await client.faucet('0xabc...') console.log(result.transaction) // tx hash ``` *** ##### `setPaymentHeader(header)` Update the x402 payment header for subsequent requests. ```typescript client.setPaymentHeader(ContraClient.buildPaymentHeader(txHash, payerAddress)) ``` *** ##### `ContraClient.buildPaymentHeader(txHash, payer)` — static Build a base64-encoded x402 payment header from a transaction hash and payer address. ```typescript const header = ContraClient.buildPaymentHeader('0xdeadbeef...', '0xabc...') ``` ### x402 Payment The `/kyc/:address/risk` endpoint is gated behind HTTP 402. To pay: 1. Get test USDC from the faucet 2. Send USDC to the merchant wallet on Base Sepolia 3. Build the payment header from the transaction hash 4. Pass it to the client ```typescript import { ContraClient } from 'contra-kyc' const client = new ContraClient({ baseUrl: 'http://localhost:3000' }) // 1. Get test USDC await client.faucet(myAddress) // 2. Send USDC to merchant wallet (via viem, wagmi, etc.) const txHash = await sendUsdcToMerchant(...) // 3. Build payment header const header = ContraClient.buildPaymentHeader(txHash, myAddress) client.setPaymentHeader(header) // 4. Now the risk endpoint works try { const risk = await client.getRisk('0xabc...') } catch (err) { if (err instanceof ContraPaymentRequiredError) { console.log('Payment requirements:', err.requirements) } } ``` ### Error Handling ```typescript import { ContraClient, ContraPaymentRequiredError, ContraAPIError } from 'contra-kyc' try { const risk = await client.getRisk(address) } catch (err) { if (err instanceof ContraPaymentRequiredError) { // HTTP 402 — payment needed console.log(err.requirements) } else if (err instanceof ContraAPIError) { // Other API error console.log(err.status, err.body) } } ``` ### Types #### `KYCProfile` ```typescript type KYCProfile = { address: string profile: Record summary: { total_eth_transfers: number total_erc20_transfers: number total_nft_transfers: number total_approvals: number total_contract_calls: number total_bridge_events: number total_defi_events: number total_identity_signals: number total_risk_flags: number unique_counterparties: number unique_tokens: number protocols_used: string[] } risk_flags: RiskFlag[] eth_transfers: unknown[] erc20_transfers: unknown[] // ... all indexed event arrays } type RiskFlag = { severity: 'low' | 'medium' | 'high' | 'critical' category: string description: string } ``` #### `RiskResponse` ```typescript type RiskResponse = { address: string assessment: { risk_score: number // 0–100 risk_level: 'low' | 'medium' | 'high' | 'critical' summary: string flags: RiskFlag[] positive_signals: string[] recommendation: string investigation_steps: string[] } onchain?: { status: 'written' | 'skipped' transaction?: string // tx hash if written contract?: string network?: string explorer?: string } } ``` #### `AgentProfile` ```typescript type AgentProfile = { agent_id: number owner: string metadata_uri: string [key: string]: unknown } ``` #### `SinkStatus` ```typescript type SinkStatus = { last_block: number last_updated: string | null total_addresses_indexed: number total_agents_indexed: number } ``` ### MCP Server The API also exposes a Model Context Protocol server at `POST /mcp` (stateless Streamable HTTP). AI agents can connect to it directly for KYC lookups as part of an agentic workflow. ``` POST http://localhost:3000/mcp Content-Type: application/json ``` Compatible with any MCP client (Claude Desktop, custom agents via `@modelcontextprotocol/sdk`).