Ecommerce Template
Modern ecommerce template built with Next.js 16, React 19, Drizzle, Better Auth, Supabase, and Stripe.
Asistente de IA para hacer el súper en Argentina. Pedí lo que necesitás, compará precios, armá el carrito y pagá con tarjeta o USDC.
Translation pending
CommerceTypeScript · PythonGitHub @tote-hqLatest connection check failedOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗An AI agent that does your Argentine supermarket shop, paid in USDC on Solana, with the money held in an on-chain escrow until the agent proves it could buy the basket.
You ask for a basket in plain Spanish. The agent searches a real store's live catalogue, compares real prices and builds a real cart. You pay in USDC from an email login, without a seed phrase or gas. The USDC is locked in an escrow program on Solana. A sandboxed browser agent then logs into your own account at the store and buys that basket with our card. If the store places the order, the escrow settles. If anything else happens, the escrow refunds you, and nobody has to approve that by hand.
changuito is what an Argentine calls a shopping trolley.
| Hackathon | Crypto World's Fair (Solana Foundation × Colosseum) — github.com/tote-hq/solana |
| Sibling repo | github.com/tote-hq/stellar, the Stellar build. Both share history up to 419304d; shared code is carried across by labeled PRs (see .github/workflows/sync.yml) |
| Live app | solana-changuito.vercel.app, the official Solana deployment for now (app.changuito.me is the earlier Stellar build) |
| Network | Solana devnet only: our own mock USDC mint, faucet in the app |
| Reviewing this? | docs/judges.md: a 15-minute path through the repo, with claims mapped to evidence |
| Videos | Pitch: link pending · Demo: link pending (sources in creatives/) |
| License | MIT |
The first customer is someone who holds dollars, and has a family in Argentina that needs groceries. The emigrant in Madrid, the freelancer paid in USDC, the expat whose parents live in Rosario. Today they send money and then make a phone call about what to buy. With changuito they say "armá la compra de la semana para mi vieja, sin TACC, hasta $80.000" and pay. The agent does the shop at the supermarket nearest the delivery address.
That person has three things in common:
The second customer, later, is an Argentine already paid in stablecoins who wants the week's shop done without opening four supermarket apps. The first customer is narrower and needs the product more, so it is the one we build for now.
Because the card was never the product. A crypto card lets you pay a supermarket. It does not build the basket, compare four chains' prices, notice that the 1.5 L costs less than two 750 ml bottles, or handle a substitution when something is out of stock. Choosing what to buy takes 40 minutes; paying takes about 40 seconds. changuito does the 40 minutes.
The card also gives the payer nothing to hold onto. Once you have paid, a failed delivery means you dispute it with the issuer. With changuito the funds are in a program account that can only go to one of two places: the treasury, once the basket is proven at checkout, or back to the buyer. That rule is enforced by the program, not by us (see On chain).
And for the first customer above, a local card is not available at all.
flowchart TD
A["#quot;armá un desayuno por menos de $10.000#quot;"] --> B["Agent searches Día over MCP<br/>live catalogue, live prices"]
B --> C["A real cart, drawn as a card<br/>with the store's own cart link"]
C --> D["Pagar → email login with Privy<br/><i>embedded Solana wallet, no seed phrase</i>"]
D --> E["Lock USDC in escrow<br/><i>fee paid by the resolver</i>"]
E --> F["Sandbox agent buys the basket in your Día account<br/>login → cart by SKU → envío → pay by card"]
F -->|order placed| G["settle: vault → treasury<br/>¡Compra completada! + Día order number"]
F -->|declined / failed / lost| H["refund: vault → buyer"]
stateDiagram-v2
[*] --> Open: open() — buyer signs, USDC buyer → vault
Open --> Settled: settle(basket_hash, receipt_hash)<br/>resolver only, hash must match
Open --> Refunded: refund()<br/>resolver any time, or buyer after deadline
Settled --> [*]
Refunded --> [*]
The buyer never depends on the operator to get their money back. If our server
disappears, refund() opens to the buyer themselves once the deadline passes.
Full detail in docs/solana.md.
| Address (Solscan, devnet) | |
|---|---|
Escrow program changuito_escrow (Anchor 1.2) | BFa1gZL9…vLz9d |
Config PDA ["config"]: resolver, treasury, mint. Set once; the program has no setter | BCcorb5C…BPh |
| Mock USDC (6 decimals, minted by the in-app faucet) | BZ6CHGyR…LG85 |
| Resolver: settles, refunds, mints faucet USDC | 5zeMdzZC…hUD |
| Treasury: receives settled baskets | 9TNtBk4R…ctc |
What each piece does:
anchor/programs/changuito_escrow).
open: the buyer signs. It creates an order PDA ["order", id] and a vault token account ["vault", id], stores the basket's SHA-256 and a deadline, and moves the USDC in. It rejects a zero amount, a timeout outside the 5-minute to 30-day range, and a reused order id.settle: callable by the resolver only. The order must be open and the basket hash must match. It pays the treasury, records a receipt hash, and closes the vault (the rent goes back to the buyer).refund: the resolver can call it at any time; the buyer can call it after the deadline.getProgramAccounts query filtered by buyer. There is no orders table to drift from the money./api/checkout/open builds the open transaction with the resolver as fee payer, the wallet signs it as the buyer, and the server checks the bytes are exactly the ones it built, adds the resolver's signature and sends it. The shopper needs no SOL for fees.These are ledger entries, not screenshots:
| Transaction | |
|---|---|
| program deploy | 4rNtLVRC… |
initialize | 3ZQa77Kf… |
| app → faucet | 36qKsuaG… |
app → open (5.06 USDC for a $6.150 ARS basket) | 5aShGUf3… |
app → settle (vault → treasury) | 32qUpErt… |
app → open (failure path) | 2vaNKpBu… |
app → refund (vault → buyer) | 244i1Squ… |
The "app →" rows were produced by scripts/devnet-e2e.mts,
which drives the real API routes (faucet → quote → open → start → status) against
the dev server, with the sandbox in mock mode. Reproduce it:
# terminal 1
SOLANA_RESOLVER_SECRET="$(cat ~/.config/solana/changuito/resolver.json)" npm run dev
# terminal 2
node --experimental-strip-types --no-warnings scripts/devnet-e2e.mts
The dev server needs the resolver key to settle and refund. Without it you can still read everything above on Solscan.
flowchart TB
subgraph B["BROWSER"]
CH["Chat<br/><i>SSE transcript, product grids, cart card</i>"]
CM["CheckoutModal<br/><i>login · lock · shopping · done</i>"]
PV["Privy<br/><i>email / Google login · embedded wallet · sign only</i>"]
end
subgraph N["NEXT.JS (Node runtime)"]
CHAT["/api/chat<br/><i>Claude tool loop, streamed</i>"]
MCP["MCP client ⇄ supermarket MCP server<br/><i>InMemoryTransport</i>"]
Q["/api/checkout/quote · open · start · status"]
F["/api/faucet · /api/balance"]
end
subgraph S["RAILWAY"]
SB["services/sandbox<br/><i>FastAPI + Playwright + Jev<br/>one job at a time</i>"]
end
subgraph X["OUTSIDE"]
VTEX["Día (VTEX): catalogue, cart"]
SOL["Solana devnet<br/>escrow program · mock USDC"]
PG[("Supabase Postgres<br/>turns, quotes, quotas · chat archive")]
end
CH --> CHAT --> MCP --> VTEX
CHAT --> PG
Q --> PG
CH --> CM
CM --> PV
CM --> Q
Q -->|"open (buyer-signed, resolver pays fee)"| SOL
Q -->|"verify order PDA"| SOL
Q -->|"POST /jobs, poll"| SB --> VTEX
Q -->|"settle / refund (resolver)"| SOL
F --> SOL
Trust boundaries:
open.initialize, and only for the basket hash the buyer signed; or refund to the buyer. It also pays the fee for each open, and co-signs only the exact message the server built for that quote./api/checkout/start reads the order PDA from chain. Buyer, status, amount and basket hash must all match the quote. The browser's claim that it paid is never trusted.The sandbox (services/sandbox, docs/sandbox.md) is a Playwright browser driven by Jev. Jev is a model that answers typed questions (pick one of these elements, yes/no, a score) and never writes free text. The harness does the clicking.
More: docs/architecture.md.
Read this before the demo, not after it.
compra from dolarapi, so the treasury absorbs the ~1.6% compra/venta spread. Prices can still move between quote and purchase.CHK0082 (reCAPTCHA token required) before the card is evaluated, so no order is placed yet and every run refunds. Details in docs/sandbox.md.scripts/devnet-e2e.mts against a local Docker sandbox and Día (2026-10-04, the operator's real account): resolver-paid open, login, empty cart, 2/2 items added, Envío programado chosen in the delivery modal, /checkout/#/payment reached in 214 seconds, then settle. Earlier runs looped in that modal: it now needs the Envío programado radio before Confirmar enables, and the harness had collapsed both delivery radios into one option. That fix is ours, not upstream's (UPSTREAM.md). Search is by name, so a loose match can stand in for the requested product (in that run "Galletitas de avena 250g" became Galletitas Mantequitas 250 g). Larger baskets have not been run live.SANDBOX_URL (locally or deployed), a mock job (switched on with npm run config -- set sandbox_mock true) steps through the same phases in about 25 seconds (sandbox_mock_fail makes it fail, to show the refund).open fee, about 0.00001 SOL each, so it has to be kept topped up with devnet SOL. Abuse is bounded: a quote needs a signed-in session, and the resolver co-signs only the one message built for that quote.The next test is the narrow one: ten diaspora households paying for one weekly shop for family in Argentina, with the treasury paying the store. That answers the only question that matters at this stage: will someone abroad trust an agent with their parents' groceries if the money is escrowed?
The basket moves through three hands: buyer → escrow → treasury → store. Margin is made in the middle, and the costs are on the last hop.
| Hypothesis to test | |
|---|---|
| Revenue: service fee | A flat percentage on the basket, shown at quote time. A shopper abroad compares it with the cost of a remittance plus a phone call, not with a free supermarket app |
| Revenue: FX | The quote converts at a published exchange rate (belo's USDC quote), with envío as its own line. A spread on that rate is a lever |
| Cost: paying the store | The treasury pays Día's checkout with a card funded from settled USDC. Card issuance and interchange is the main variable cost, and the reason the fee cannot be zero |
| Cost: the agent | One Claude conversation per basket, plus one sandbox run. Measured per basket, both are well under a typical delivery fee |
| Risk held | None for the shopper, by construction. Funds are locked before work starts and leave only by settle (basket proven) or refund. The treasury carries the gap between settle and the store's card charge |
The escrow is what lets this run without a credit line. We never pay a store before the buyer's money is locked against that exact basket.
From Colosseum's archive (via Colosseum Copilot):
What none of them does is hold the shopper's money against a hash of the basket while an agent proves it can be bought.
The market is narrow on purpose. Each step reuses what the last one built:
packages/mcp already speaks Carrefour, Disco and Jumbo (search and cart). The sandbox is the per-store piece: one navigation profile per checkout.Stated plainly, so nobody has to work it out from the commit history:
packages/mcp (supermercado-mcp: VTEX search and cart tools for Día, Carrefour, Disco and Jumbo) predates the hackathon. It was vendored into this repo when the repo started on 2026-09-12.services/sandbox comes from raptor0929/jev-dia-arg, credited in place. What is new here is the job server (server.py), the run_job API, the deploy files, and a fix for Día's delivery-type radios in sandbox.py and agent.py.open fees paid by the resolver, replacing Pollar;/api/checkout/*, CheckoutModal), replacing the iframe and card flow;Requires Node ≥ 22.12 (.nvmrc). The program is already deployed to devnet,
so you only need Rust and Anchor to change it.
git clone https://github.com/tote-hq/solana && cd solana
npm install
cp apps/web/.env.example apps/web/.env.local
npm run dev # http://localhost:3124
| Variable | Needed for |
|---|---|
ANTHROPIC_API_KEY | the agent (search, compare, cart). Enough on its own to try the chat |
NEXT_PUBLIC_PRIVY_APP_ID + PRIVY_APP_SECRET | login and wallet; the secret is needed server-side to look up the user's Solana wallet. The Privy app needs email and Google login, Solana embedded wallets created on login, and http://localhost:3124 as an allowed origin. Gas sponsorship is not used |
SOLANA_RESOLVER_SECRET | faucet, open fees, settle, refund. The resolver keypair (JSON byte array or base58) |
CHG_SESSION_SECRET | the session cookie. Required in production; a dev default is used locally |
SANDBOX_URL / SANDBOX_TOKEN | the real sandbox. Unset → mock job |
ARS_PER_USD | optional; pins the rate for a reproducible quote |
SOLANA_RPC_URL | optional; defaults to public devnet |
DATABASE_URL | Postgres (Supabase). Required in production: history, quotes and the chat/faucet quotas live there and the quotas fail closed without it. Unset locally, an in-process Map stands in |
The annotated list is in apps/web/.env.example. Deploying
(Vercel for the app, Railway for the sandbox) is covered in DEPLOY.md.
npm test -w @changuito/web
npm run typecheck -w @changuito/web
npm run build
Some web tests still pin behaviour from the Stellar version. They are listed, not edited, in the commit that made the port. See CLAUDE.md for why the tests are treated as a fixed point.
apps/web/ the shopper: chat, checkout, API routes
apps/landing/ marketing site
anchor/programs/ changuito_escrow: open / settle / refund
services/sandbox/ Jev + Playwright checkout agent, FastAPI job server (Railway)
packages/mcp/ supermarket MCP server for four VTEX chains (prior work)
packages/trust/ footer trust copy shared by both apps
scripts/ solana-deploy.sh, solana-init.mts, devnet-e2e.mts
supabase/migrations/ chat archive schema
creatives/ Remotion sources for the pitch videos
deployments.json what is deployed on devnet, and the evidence txs
| docs/judges.md | reviewing in 15 minutes: claims and the evidence for each |
| docs/architecture.md | system, trust boundaries, quote → open → job → settle/refund |
| docs/solana.md | program accounts, instructions, auth rules, Privy, faucet |
| docs/sandbox.md | the job API and what the sandbox will and will not do |
| docs/flows.md | a chat turn and a checkout, end to end |
| docs/tech-stack.md | dependencies and environment |
| DEPLOY.md | devnet program, Vercel, Railway |
| CLAUDE.md | how the agent's behaviour was arrived at, and what not to undo |
57NLdwkH…UgFi) as upgrade authority, so "no setter" is a property of today's code, not a guarantee. Freezing it (solana program set-upgrade-authority --final) is a one-line step before anything holds real money./api/checkout/start after open landed leaves the order waiting for that deadline.