Running the demo
The reference demo wraps Gemini 2.5 Flash with TAP and renders a live React dashboard while a session streams. End-to-end you'll see: token-by-token cost accrual, signed commitments arriving, an evaluator trigger, and an on-chain refund of the unused deposit.
Prerequisites
- Solana CLI, with
~/.config/solana/id.jsonfunded on devnet. - Two additional keypairs for producer + consumer at
~/.config/solana/{producer,consumer}.json, both holding USDC on devnet (faucet: https://faucet.circle.com). - Gemini API key — set
GEMINI_API_KEY=.... - Anchor 0.32+, Node 20+, Python 3.12+.
One-time setup
git clone https://github.com/HonestFreak/TAP
cd tap
# Python SDK
pip install -e sdk/python
pip install -r demo/requirements.txt # uvicorn, fastapi, httpx
# Frontend
(cd demo/frontend && npm install)
# Anchor program — already deployed to devnet at
# 2tqofcitv1LHFGCLCmR9Kyke6TmArQwpHSinWWtmCje9.
# To redeploy a local build: anchor deploy --provider.cluster devnet
Wire up the env
export GEMINI_API_KEY=...
export TAP_PRODUCER_KEYPAIR=~/.config/solana/producer.json
export TAP_CONSUMER_KEYPAIR=~/.config/solana/consumer.json
export TAP_PRODUCER_PUBKEY=$(solana-keygen pubkey ~/.config/solana/producer.json)
export TAP_PRODUCER_USDC=$(spl-token address --token <USDC_MINT> --owner $TAP_PRODUCER_PUBKEY --verbose | grep "Associated Token Address" | awk '{print $NF}')
export TAP_RPC=https://api.devnet.solana.com
export TAP_NETWORK=solana-devnet
Run
Three terminals:
# Terminal 1 — producer (FastAPI on :8000)
uvicorn demo.producer:app --host 0.0.0.0 --port 8000
# Terminal 2 — consumer runner backend (FastAPI on :8001)
uvicorn demo.runner:app --port 8001
# Terminal 3 — React dashboard (Vite on :5173)
cd demo/frontend && npm run dev
Open http://localhost:5173. Type a prompt, click Run, watch the
meter tick up token-by-token. Toggle "Enforce JSON schema" off and
ask for prose to see the no-halt path; toggle it on and watch the
evaluator halt mid-stream.
What's happening under the hood
- The frontend POSTs
{prompt, deposit_micro, enforce_schema}torunner.py. runner.pycallsTapConsumer.open_session(...):- POST the prompt to
producer.py(no payment) → 402 withprepaid_inputquote. - Re-tokenize locally; verify match.
- Build + sign
open_channelSolana tx; submit via x402.
- POST the prompt to
- The producer wraps Gemini's output stream with
TapProducer's meter and emits SSE. TapConsumersigns commits every K tokens; the React side renders each as a timeline event.- When the evaluator halts (or the stream completes), the producer
submits
settleon-chain. After the dispute window,closemoves USDC.
Quick smoke test (no chain involvement)
To verify the wire format without funding accounts:
# Boot just the producer
GEMINI_API_KEY=placeholder \
TAP_PRODUCER_KEYPAIR=~/.config/solana/producer.json \
TAP_PRODUCER_USDC=<any base58 pubkey> \
uvicorn demo.producer:app --port 8000
# In another shell — hit the prompt-bound 402 endpoint
curl -s -X POST http://localhost:8000/v1/messages \
-H 'Content-Type: application/json' \
-d '{"messages":[{"role":"user","content":"hello world"}]}' \
-D - -o /dev/null \
| grep -i x-payment-requirements \
| sed 's/x-payment-requirements: //' \
| python3 -c "import sys,base64,json; print(json.dumps(json.loads(base64.b64decode(sys.stdin.read().strip())), indent=2))"
You should see input_token_count and prepaid_input populated based
on the prompt you sent.