Live product: tracecredits.xyz — reputation-weighted BNPL. You connect a wallet, Alex quotes a purchase on /buy, TypeScript policy approves or declines, ETH settles on Base Sepolia (or is simulated when BASE_EXECUTE is off), you repay in installments, and Sibyl’s USER_RELATIONSHIP for that wallet with this agent is what changes the next offer.
This is not the old treasury-agent product. TRACE does not ask the LLM whether you are eligible. lib/bnpl/policy.ts computeApproval sets Approve / Approve with reduced limit / Decline / Ceiling blocked. The model, if XAI_API_KEY is set, only writes reasoning copy.
connect wallet → quote → Alex pays/simulates ETH → you repay → Sibyl remembers → next quote uses that book
- Connect a wallet in the header (
ConnectWallet, injectedwindow.ethereum). The product keys everything to that address. - Request a BNPL purchase on
/buy. The live checkout iscomponents/Desk.tsx(the filename is leftover; the page is BNPL, not the old desk). Default SKU is Notebook Set $12. - Alex quotes.
POST /api/purchase(quote) thenPOST /api/purchasewithaccept: true. Decision engine:lib/bnpl/policy.ts. - Payout. If
BASE_EXECUTE=1(ortrue/yes), the agent sends ETH on Base Sepolia to the user wallet (lib/base/send.ts). Display amounts are USD; conversion usesETH_USD(default 2000). If execute is off, the plan is still stored and the UI labels the payout Simulated on this testnet. - Repay in installments from
/buy. The user sends ETH back to the agent.lib/bnpl/verifyUserRepay.tsmust verify that transfer before Sibyl writes a repayment. No verified hash, no book update. - Memory drives the next terms. After at least one purchase exists on
USER_RELATIONSHIP,selectPolicyInputsdropsONCHAIN_SIGNAL(onchain: null). A whale wallet does not override a book this agent already has.
Verified against app/ and the live shell (components/AppShell.tsx). Product nav: Buy, Demo, Docs, Agent Log, My History (history only when a wallet is connected).
| URL | Code | Purpose |
|---|---|---|
/ |
app/page.tsx |
Landing. Wallet connect, Launch App → /buy, How it Works, live agent capacity. |
/buy |
app/buy/page.tsx → Desk |
Product checkout: quote, confirm, repay. This is the app. |
/history |
app/history/page.tsx → HistoryView |
This connected wallet’s Sibyl book. |
/log |
app/log/page.tsx → AgentLog |
Public agent log (MEMORY_READ → ACP_REQUEST → CREDIT_DECISION → SETTLEMENT). |
/demo |
app/demo/page.tsx → DemoRun |
One-click real BNPL run (agent-controlled demo wallet, no visitor connect). |
/docs |
app/docs/page.tsx → DocsView |
In-app documentation. |
/terms |
app/terms/page.tsx |
Testnet terms stub. |
/privacy |
app/privacy/page.tsx |
Privacy stub (wallet-keyed book, no name/email). |
These still exist in the repo. Do not treat them as TRACE BNPL.
| URL | What it is |
|---|---|
/alex |
Legacy treasury agent UI (components/Alex.tsx). Transfer-style decisions: Proceed / Proceed with flag / Hold for approval / Ceiling blocked (lib/policy/decide.ts, MAX_TX_AMOUNT_USDC). Header on this page still has a “Treasury” link. |
/desk |
Redirects to /alex. |
/lend |
Redirects to /buy. |
/memory |
Redirects to /history. |
/developers |
Redirects to /. |
/agent-log |
Redirects to /log. |
Leftover libraries that are not the live checkout: lib/policy/ (treasury ceilings), lib/lending/ (including MAX_BORROW_AMOUNT / MIN_COLLATERAL_RATIO), app/api/decide, app/api/quote, app/api/borrow, app/api/supply, app/api/preview, and unused components/Buy.tsx (not mounted; /buy renders Desk).
Source of truth: lib/bnpl/policy.ts, lib/bnpl/relationship.ts, lib/bnpl/ceiling.ts, lib/bnpl/solvency.ts.
Hard ceilings, checked before terms. Standing, on-chain history, and the LLM cannot override them:
| Variable | Default | File |
|---|---|---|
MAX_PURCHASE_AMOUNT |
10000 | lib/bnpl/ceiling.ts (DEFAULT_MAX_PURCHASE_AMOUNT) |
MAX_ACTIVE_PLANS |
2 | lib/bnpl/ceiling.ts |
MIN_AGENT_RESERVE |
5 (USD-equivalent) | lib/bnpl/solvency.ts |
Other live BNPL constants (not env):
| Constant | Value | Meaning |
|---|---|---|
DECLINE_STANDING |
0.18 | Below this, Decline |
STARTER_STANDING |
0.38 | Open-plan cap / first-time established standing |
FIRST_CLEAN_LIMIT_MIN / MAX |
40 / 80 | Band after one completed on-time plan |
LIMIT_AT_MID_STANDING |
3000 | Gross limit at standing 0.50 (score 50) |
DEFAULT_LIMIT_CAP |
8 | After any default |
OPEN_PLAN_LIMIT_CAP |
24 | Open plan, no completed on-time |
INSTALLMENT_SPACING_DAYS |
14 | Multi-payment spacing |
SHORT_PLAN_DAYS |
7 | Single-payment due |
selectPolicyInputs:
total_purchases == 0→ primaryONCHAIN_SIGNAL. First-time limits fromonchainBaseline:- thin (age < 7d or < 3 txs): limit $12, 1 installment, standing 0.22
- moderate (age < 90d or < 30 txs): limit $20, 2 installments, standing 0.32
- established: limit $24, 2 installments, standing 0.38
total_purchases > 0→ primaryUSER_RELATIONSHIP,onchain: null. On-chain is never used again for that wallet with this agent.
Trace interest is code: interestRateFromStanding (floor 2%, ceiling 26%). You receive principal; you repay principal + interest.
Legacy treasury-only (not BNPL): MAX_TX_AMOUNT_USDC default 25 in lib/policy/ceiling.ts, used by /alex. MAX_BORROW_AMOUNT / MIN_COLLATERAL_RATIO in .env.example are labeled lending ceilings (legacy desk).
This is the current BNPL loop, not the old treasury transfer demo. Numbers below are produced by liveWalkthrough() in lib/bnpl/walkthrough.ts against the same computeApproval as /buy. SKU: Notebook Set $12.
- Run the real loop without connecting.
/demoRun the demo usesPOST /api/demo/runandDEMO_WALLET_PRIVATE_KEY(server-side only) as the buyer. SamerunAcceptPurchase/runRepayInstallmentas/buy. The run originates two purchases: empty-book, repay, then a second accept on the remembered book. To be the signer yourself, use/buy. - First purchase $12 with an empty book.
- Inputs:
ONCHAIN_SIGNAL. - Thin wallet: Approve, limit $12, 1 payment of $14.52 (21% interest).
- Moderate: Approve, limit $20, 2 payments of $7.08.
- Established: Approve, limit $24, 2 payments of $7.02.
- Confirm. If
BASE_EXECUTE=1, ETH is sent to your wallet; otherwise the UI says simulated. The plan is stored either way. - While the plan is open: standing 0.38, limit capped at $24.
- Inputs:
- Repay from
/buy(or demo step 3). Confirm the ETH transfer to the agent. Sibyl writeson_time/lateonly after verification. - Second purchase, same wallet, new request. Re-quote $12. On
/demothis is a secondrunAcceptPurchase(ETH payout included), not a quote-only step.- Inputs:
USER_RELATIONSHIP. Factor text includesONCHAIN_SIGNAL not used. - After one completed on-time $12: Approve, limit $43.33 (inside the $40–$80 first-clean band), 4 payments of $3.51, standing 0.39.
- Inputs:
- Reset memory (
pnpm memory:resetlocally, or delete in History). Chain is unchanged. - Same wallet, quote $12 again. Empty book. First-time
ONCHAIN_SIGNALterms return ($12 / $20 / $24).
A late completion of that $12 (no default) currently quotes limit $16, 2 installments — worse than the clean $43.33 / 4-pay plan. Any default caps standing at 0.12 and Declines, limit $8.
Locked in lib/bnpl/policy.test.ts. Judge script: docs/DEMO.md.
pnpm memory:reset
pnpm dev # http://localhost:3002Then /demo → Run the demo (no visitor wallet). Or /buy → connect → buy $12 → repay → re-quote.
Prerequisites: Node 20, pnpm (packageManager: pnpm@10.34.5), Python 3.10+ only if you want the local Python Sibyl bridge.
git clone https://github.com/0andadream/Trace.git && cd Trace
python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt # sibyl-memory-client>=0.8.0
pnpm install
cp .env.example .env.local
pnpm test
pnpm wallet:create # writes AGENT_PRIVATE_KEY to .env.local; never committed
pnpm dev # next dev --port 3002 → http://localhost:3002Other scripts: pnpm build / pnpm start (port 3002), pnpm memory:reset, pnpm memory:export, pnpm memory:seed (do not run seed against production Redis), pnpm demo:wallet, pnpm demo:fund, pnpm demo:reset (demo wallet Sibyl book only; not a public HTTP route), pnpm sibyl:health, pnpm acp:job, pnpm acp:request, pnpm mcp.
| Variable | Role |
|---|---|
XAI_API_KEY |
Optional. Alex still decides without it; the model only writes copy. |
SIBYL_PYTHON |
Local Python binary (default .venv/bin/python). |
SIBYL_MEMORY_DB |
Local SQLite path (default .data/sibyl-memory.db). Must not be ~/.sibyl-memory/memory.db. |
SIBYL_TENANT |
Default trace-alex. |
KV_REST_API_URL / KV_REST_API_TOKEN |
Vercel Redis. |
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN |
Same Redis, Upstash names. |
AGENT_PRIVATE_KEY |
Treasury/BNPL signer. From pnpm wallet:create. |
AGENT_ADDRESS |
Published signer if the key is absent. Repo default 0x6F75c81375B43AcE7cE839D6eAc7192e10a4440e. |
DEMO_WALLET_PRIVATE_KEY |
Buyer signer for /demo Run the demo. From pnpm demo:wallet. Server-side only; never sent to the client. Must not be AGENT_PRIVATE_KEY. |
DEMO_WALLET_ADDRESS |
Public demo buyer if the key is absent. Also published in config/demo-wallet.json. |
DEMO_RATE_WINDOW_SEC |
Optional. Per-IP cooldown for POST /api/demo/run. Default 180. |
BASE_PRIVATE_KEY |
Alias written by wallet:create; lib/wallet.ts accepts it if AGENT_PRIVATE_KEY is empty. |
SEPOLIA_RPC_URL / BASE_RPC_URL |
Default https://sepolia.base.org. |
BASE_CHAIN_ID |
84532 Base Sepolia (8453 would be Base mainnet). |
BASE_EXECUTE |
Must be 1 / true / yes to broadcast. Otherwise payouts are simulated. |
VIRTUALS_ACP |
Optional. Default: ACP outbound runs when BASE_EXECUTE=1. Set 0 to skip without faking a job. |
MAX_PURCHASE_AMOUNT |
BNPL hard cap. Default 10000. |
MAX_ACTIVE_PLANS |
BNPL hard cap. Default 2. |
MIN_AGENT_RESERVE |
Agent cannot approve a payout that would leave cash below this. Default 5. |
MOCK_MERCHANT |
Optional merchant address for execute-on payouts. |
ETH_USD |
USD→ETH rate for settlement. Default 2000. |
AGENT_SIMULATED_USDC |
Fallback spendable USD if the chain balance cannot be read. Default 100. |
MAX_TX_AMOUNT_USDC |
Legacy treasury only (/alex, default 25). Not the BNPL purchase cap. |
MAX_BORROW_AMOUNT / MIN_COLLATERAL_RATIO |
Legacy lending/desk. |
pnpm wallet:creategenerates a key into.env.localand will refuse if.gitignoredoes not list.env.local.- Stats can read the published address in
config/agent-wallet.json:0x6F75c81375B43AcE7cE839D6eAc7192e10a4440eonsepolia. - Fund that agent with Base Sepolia ETH. User repayments also go to this address.
pnpm demo:walletthenpnpm demo:fundfor the/demobuyer (separate key, Base Sepolia ETH for gas + the interest gap above the $12 payout).- In the browser, connect your injected wallet on Base Sepolia (chain id 84532) for
/buy. Run the demo does not need a visitor wallet. - Set
BASE_EXECUTE=1on Vercel for live payouts at tracecredits.xyz. If execute is off, quotes and books still work; ETH is not broadcast.
Explorer: agent on Base Sepolia.
Sibyl stores and recalls. It does not compute standing, limits, interest, or lateness. Those stay in TypeScript (relationship.ts, policy.ts, ceiling.ts) so laptop and Vercel use the same numbers. Detail: docs/sibyl-parity.md.
Alex’s book lives on the agent, not on each visitor’s phone. Locally that is the official Python SDK (sibyl-memory-client 0.8+) and a SQLite file at .data/sibyl-memory.db, tenant trace-alex. Vercel has no durable SQLite disk, so production keeps the same entities in Redis with no TTL. The sibyl init plugin DB at ~/.sibyl-memory/memory.db is for Grok/Codex on this laptop. It is not TRACE’s BNPL store. The bridge refuses that path.
What is stored now for the product: USER_RELATIONSHIP per wallet — purchases, installment schedules, outcomes (on_time / late / defaulted / active), quotes, overrides, and a compact snapshot (last_outcome, open_plans, standing, trust_note). Standing and limit are recomputed on read, not trusted as stored fields. ONCHAIN_SIGNAL is fetched fresh and never written as history.
| Path | When | Storage |
|---|---|---|
Python sibyl/bridge.py |
Laptop, if .venv/bin/python exists |
Official sibyl-memory-client 0.8+, SQLite (SIBYL_MEMORY_DB, default .data/sibyl-memory.db) |
Node lib/memory/engine.ts + persist.ts |
Always available; required on Vercel | Redis keys, no TTL on history. File fallback .data/sibyl-memory.json off Vercel |
On Vercel (VERCEL=1), Redis must ping. If it does not, APIs return 503 (Sibyl unavailable) instead of an empty book. Redis keys look like sibyl:{tenant}:rel:{wallet}. Locks use SET NX EX 8 only.
Alex is registered:
- Agent id:
01a05400-aea9-7f70-a67e-f558448e86e3 - Profile: app.virtuals.io/acp/agents/…
- Portal/Privy wallet on the registry (not the TRACE Base signer):
0xf3df4e32fb19dc0456a3e59eddfa0d821e65a2c5
From lib/virtuals/status.ts as shipped:
marketplaceListeneris hardcodedfalse. There is no Virtuals SSE/SDK seller listener in this repo.jobEndpointistrue.POST /api/acp/jobsmaps a job intocomputeApproval(lib/virtuals/incoming.ts). Incoming jobs that hit this HTTP adapter use the same BNPL engine. That is not the same as a live marketplace listener.- Status string when the profile GET succeeds: “Agent registered on Virtuals ACP. Marketplace listener is not connected. TRACE ACP job endpoint uses the existing decision engine.”
Virtuals does not set the limit and does not move user funds. ACP identity is attached to quotes (ACP_REQUEST in the log). VIRTUALS_ACP=0 skips outbound ACP without inventing a job.
User wallet (injected) on /buy
or demo buyer (DEMO_WALLET_PRIVATE_KEY, server-side) on /demo Run the demo
→ POST /api/purchase → lib/bnpl/policy.ts computeApproval
→ Sibyl USER_RELATIONSHIP (Redis on Vercel / SQLite or JSON locally)
→ ETH payout on Base Sepolia if BASE_EXECUTE=1
→ POST /api/repay after verified buyer ETH
→ next quote reads the same book
| Object | Role |
|---|---|
lib/bnpl/policy.ts |
selectPolicyInputs / computeApproval |
lib/bnpl/relationship.ts |
Standing, limit curve, snapshot |
lib/bnpl/ceiling.ts |
MAX_PURCHASE_AMOUNT, MAX_ACTIVE_PLANS |
lib/bnpl/solvency.ts |
MIN_AGENT_RESERVE |
lib/memory/engine.ts |
Sibyl write path |
lib/bnpl/store.ts |
Read path (getRelationship) |
lib/base/send.ts |
Agent ETH payout |
lib/bnpl/verifyUserRepay.ts |
No verified ETH, no Sibyl repay write |
lib/virtuals/identity.ts |
Alex ACP id and profile URL |
pnpm test # policy, memory primacy, repay-after-verify, ACP mapping
npx tsc --noEmitLoad-bearing sequence in lib/bnpl/policy.test.ts: empty book uses on-chain; one on-time $12 improves limit and installment count with ONCHAIN_SIGNAL not used; empty again is first-time.
- Testnet only. No real goods, no mainnet credit.
- Live payouts need
BASE_EXECUTE=1and a funded agent wallet. - Virtuals identity is live; marketplace listening is not.
- First-time users with a fat on-chain wallet still get $12 / $20 / $24 until Sibyl has a purchase.
- Production Sibyl is Redis. Local can be Python+SQLite.
- Do not
pnpm memory:seedagainst production Redis.
Memory — Sibyl. Persistent USER_RELATIONSHIP.
Identity — Virtuals ACP. Alex is a registered agent. Virtuals does not underwrite and does not settle.
Settlement — Base Sepolia. ETH payout and repay.
TRACE, Alex, the credit policy, and the deletion test in this repository are original work.
MIT. See LICENSE.
