Skip to content

Commit f41de07

Browse files
committed
Add read-only Solana agent tools
1 parent 6ac7297 commit f41de07

21 files changed

Lines changed: 1118 additions & 18 deletions

File tree

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ jobs:
4747
- run: cd packages/payment-runtime && npm run typecheck
4848
- run: cd packages/payment-runtime && npm test
4949

50+
solana-agent-tools:
51+
name: Solana agent tools (read-only SAK plugin + example smoke)
52+
runs-on: ubuntu-latest
53+
steps:
54+
- uses: actions/checkout@v5
55+
- uses: actions/setup-node@v5
56+
with:
57+
node-version: 22
58+
- name: Build the agent runtime - local file deps need dist
59+
run: cd packages/agent-runtime && npm install && npm run build
60+
- name: Read-only tools package (typecheck + tests + build)
61+
run: cd packages/solana-agent-tools && npm install && npm run typecheck && npm test && npm run build
62+
- name: Solana Agent Kit example (typecheck + deterministic smoke)
63+
run: cd examples/agent-economy/solana-agent-kit && npm install && npm run typecheck && npm run smoke
64+
5065
txodds:
5166
name: TxODDS World Cup Oracle (typecheck + tests)
5267
runs-on: ubuntu-latest

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ verdict, Explorer-linked txs) and feeds a **reputation score** buyers weigh on t
7373
| **Solana Pay** (reference-bound transfers) | [`packages/agent-runtime/src/solana`](packages/agent-runtime/src/solana) | hand-roll payment URLs + verification |
7474
| **Escrow** (deposit → release / refund) | [`examples/txodds/escrow`](examples/txodds/escrow) | write, audit, and deploy an Anchor program |
7575
| **Payment rails** (one interface; 2 live, 6 scaffolds) | [`packages/payment-runtime`](packages/payment-runtime) | re-plumb payments per provider (see its README's honest status table) |
76+
| **Read-only Solana tools** (SAK-style context plugin) | [`packages/solana-agent-tools`](packages/solana-agent-tools) | give agents balances/prices without handing them signing authority |
7677
| **Harness adapters** (Claude Code / any CLI sells) | [`packages/harness-runtime`](packages/harness-runtime) | bridge external agent harnesses yourself |
7778
| **Verifier gate** (release only on a pass) | [`coral-agents/verifier-agent`](coral-agents/verifier-agent) | build independent delivery checks |
7879
| **Policy choke point** (caps, bindings, gates) | [`packages/agent-runtime/src/policy`](packages/agent-runtime/src/policy) | scatter ad-hoc safety checks |
@@ -90,6 +91,9 @@ Everything runs on **devnet** — free play money, real on-chain settlement. Key
9091
| **A funded devnet wallet** | the buyer signs the escrow deposit → release | generated in step 1; fund at[faucet.solana.com](https://faucet.solana.com) |
9192
| **Docker** *(market only)* | coral-server coordinates the multi-agent round | [docker.com](https://www.docker.com) — the single-agent demo needs none |
9293

94+
The optional Solana Agent Kit example uses `solana-agent-kit@2.x`, which currently needs **Node 22+**.
95+
The core runtime, rails, and other examples keep the repo-wide **Node 20+** floor.
96+
9397
## Quick start
9498

9599
### 1. Fork & set up (once)
@@ -159,6 +163,8 @@ One `npm run` per example, from the repo root. **Each command installs that exam
159163
| `npm run agent-economy:bridge` | the **human checkout** bridge (HTTP + React) | Docker + `docker compose up -d coral` |
160164
| `npm run agent-economy:quickstart` | the **bare 402** pay-per-call seller (no Docker, no CoralOS) | LLM key + funded wallet |
161165
| `npm run agent-economy:quickstart:buyer` | the quickstart **buyer** (run in a 2nd terminal) | the quickstart server running |
166+
| `npm run agent-economy:solana-tools` | optional **Solana Agent Kit read-only context** demo | Node 22+; no private key needed |
167+
| `npm run agent-economy:solana-tools:smoke` | deterministic mock smoke for the SAK tools example | Node 22+ |
162168
| `npm run agent-economy:web` | the agent-economy **3-tab dashboard** | — (talks to the bridge) |
163169
| `npm run desk` | **TxODDS Agent Desk** in a browser (:3030) - run ledger, proof receipts, board actions | the txodds proxy (`npm run dev` or `npm --prefix examples/txodds run proxy`) |
164170
| `npm run desk:app` | the same desk as a **Tauri desktop app** | Rust/Tauri prerequisites + the txodds proxy |
@@ -217,6 +223,11 @@ SDK**. One interface (`quote`/`run`) so a seller can be a prompt (`node-llm`), *
217223
The agent keeps the wallet and the escrow checks; the harness only produces hash-bound artifacts —
218224
**harness processes never hold keys**.
219225

226+
Optional companion: [`packages/solana-agent-tools`](packages/solana-agent-tools) exposes read-only
227+
Solana context tools for richer agents and a Solana Agent Kit-compatible plugin. It can read balances,
228+
SPL token accounts, Jupiter prices, and Pyth Hermes prices, plus simulate a transfer intent through
229+
`policy.enforce()`. It cannot swap, bridge, launch tokens, sign, or broadcast.
230+
220231
### The escrow contract — the settlement spine
221232

222233
The only Rust in the kit: **two** deployed devnet programs, **called** (not forked) by the agents' TS
@@ -243,12 +254,13 @@ deployed ids with no local build. **Devnet only** — never put a funded mainnet
243254
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
244255
| `examples/txodds/` | **the default demo** — the World Cup oracle. `agent/` (`service.ts` = the `deliverService()` fork point; `edge.ts` = its transform; escrow/arbiter clients), `server/` (proxy + mint), `web/` (React board), `research/` (the odds-move **event watcher** feeding the research round), `escrow/` (the two Anchor programs) |
245256
| `examples/marketplace/` | **the full market** — a buyer + competing sellers in one CoralOS session, in three rounds: `start.ts` (classic), `freelancer.ts` (harness sellers + **verifier-gated** release), `research.ts` (event-driven — odds moves trigger the WANTs); `feed/` (rounds + the **run ledger** `runs/`, `/api/runs`, `/api/reputation`, disk replay), `web/` (React visualizer). Needs Docker |
246-
| `examples/agent-economy/` | **three front doors** on CoralOS — autonomous (agent→agent), a human checkout bridge, and a bare 402 pay-per-call quickstart |
257+
| `examples/agent-economy/` | **three front doors** on CoralOS — autonomous (agent→agent), a human checkout bridge, and a bare 402 pay-per-call quickstart; plus an optional Solana Agent Kit read-only tools example |
247258
| `examples/txodds-agent-desk/` | the **operator console** — a Tauri desktop shell over the proxy + run ledger + proof receipts (`npm run desk` for the browser version, no Rust needed) |
248259
| `coral-agents/` | the agents coral-server launches per session —`buyer-agent`, `seller-agent` (+ personas incl. `seller-claude`), `verifier-agent` (the release gate), `broker` (swarm reseller), `echo-agent`, `user_proxy` |
249260
| `packages/agent-runtime/` | the runtime —`llm/`, `solana/`, `coral/`, `market/`, `ledger/`, `policy/` |
250261
| `packages/harness-runtime/` | the **harness adapter SDK**`node-llm` / `claude-code` / any CLI as market sellers |
251262
| `packages/payment-runtime/` | the **payment rail router** — Solana Pay + escrow live; Pay.sh / x402 / USDC / allowance / embedded / payout as typed scaffolds (status table in its README) |
263+
| `packages/solana-agent-tools/` | the optional **read-only Solana agent tools** package — wallet/token reads, Jupiter/Pyth prices, and transfer-intent simulation for SAK-style agents; no fund-moving actions in v1 |
252264
| `scripts/` | `txodds.js` (`npm run dev`), `setup.js` (devnet wallets), `provision-swarm.js` (broker + seller wallets for the swarm demo) |
253265
| `docker-compose.yml` | coral-server (the MCP coordinator) for the market |
254266

docs/PRODUCTION_READINESS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ These are the user-visible flows that must stay healthy:
1818
| Coral marketplace | `npm run marketplace`; feed folds Coral session state into rounds, the run ledger, reputation, threads, and proof receipts. |
1919
| Marketplace visualizer | `examples/marketplace/web` unit tests and Playwright e2e against recorded Coral state. |
2020
| Payment rail procurement | `@pay/payment-runtime` tests plus `PAYMENT_*` folding into `proofReceipts` and `proof_receipts.json`. |
21+
| Read-only Solana agent tools | `@pay/solana-agent-tools` tests plus the Solana Agent Kit example smoke in mock mode. |
2122
| Agent desk | `npm run desk` for browser mode; CI parses UI JS and Tauri configs. |
2223
| Agent economy front doors | Typecheck/build smoke for autonomous, bridge, quickstart, and web dashboard. |
2324
| Escrow programs | CI `cargo check --workspace` on escrow + arbiter. |
@@ -48,6 +49,7 @@ The GitHub Actions workflow blocks PRs/pushes on:
4849
- `packages/agent-runtime`: typecheck, tests.
4950
- `packages/harness-runtime`: runtime build, typecheck, tests.
5051
- `packages/payment-runtime`: runtime build, typecheck, tests.
52+
- `packages/solana-agent-tools`: runtime build, typecheck, tests, package build, and Node 22 SAK example smoke.
5153
- `examples/txodds`: typecheck, tests.
5254
- `examples/marketplace`: launcher typecheck, feed typecheck/tests, web typecheck/tests, Playwright e2e.
5355
- `examples/agent-economy`: autonomous/quickstart/bridge typechecks and web production build smoke.

examples/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ World Cup service is only the default demo.
2222

2323
- **[agent-economy/](agent-economy/README.md)****three front doors** on CoralOS: autonomous
2424
(agent→agent), a human checkout (Phantom/Solflare wallet), and a bare 402 pay-per-call quickstart. All
25-
settle in devnet SOL. Needs Docker.
25+
settle in devnet SOL. Also includes an optional Node 22 Solana Agent Kit read-only tools example
26+
(`solana-agent-kit/`) for wallet/token/price context without signing authority. Needs Docker for the
27+
CoralOS front doors.
2628

2729
- **[txodds-agent-desk/](txodds-agent-desk/README.md)** - the operator console: a Tauri v2 shell
2830
and no-build browser UI over the existing TxODDS proxy, run ledger, proof receipts, settlement

examples/agent-economy/README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,22 @@ are thin manifests reusing the seller image. See [`coral-agents/broker/README.md
108108
[`quickstart/`](quickstart/) is the same pay-per-call loop as two bare-metal Node processes over
109109
plain HTTP `402` — no Docker, no CoralOS. The fastest way to understand the payment cycle.
110110

111+
## Optional Solana Agent Kit context tools
112+
113+
[`solana-agent-kit/`](solana-agent-kit/) loads `@pay/solana-agent-tools` as a Solana Agent
114+
Kit-compatible plugin. It lets agents read wallet balances, SPL token balances, Jupiter prices, and
115+
Pyth Hermes prices, and dry-run a transfer intent through `policy.enforce()` with `executable: false`.
116+
It intentionally has no swaps, bridges, token launches, live transfers, or signing wallet. Run it with
117+
Node 22:
118+
119+
```sh
120+
cd examples/agent-economy/solana-agent-kit
121+
npm install
122+
npm run smoke
123+
```
124+
125+
From the repo root, the same deterministic check is `npm run agent-economy:solana-tools:smoke`.
126+
111127
## Want trustless settlement? — the escrow
112128

113129
The base loop is *pay-first* (the buyer trusts the seller to deliver).
@@ -155,4 +171,4 @@ ledger** feeding seller reputation — lives in [`../marketplace`](../marketplac
155171
(`npm start` / `npm run freelancer` / `npm run research`). Same protocol, same escrow, same
156172
CoralOS bus; the front doors here plug into those rounds unchanged.
157173

158-
Devnet only. Never put a funded mainnet keypair in `.env`.
174+
Devnet only. Never put a funded mainnet keypair in `.env`.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Solana Agent Kit read-only tools
2+
3+
This optional example loads `@pay/solana-agent-tools` into `solana-agent-kit` as a small allowlisted
4+
plugin. It gives richer agents chain context without making Solana Agent Kit the money-moving authority.
5+
6+
## What it exposes
7+
8+
| Action | Tool | Moves funds? |
9+
|---|---|---|
10+
| `solana.read_wallet_balance` | `readWalletBalance` | No |
11+
| `solana.read_token_balances` | `readTokenBalances` | No |
12+
| `solana.fetch_token_price` | `fetchTokenPrice` | No |
13+
| `solana.fetch_pyth_price` | `fetchPythPrice` | No |
14+
| `solana.simulate_transfer_intent` | `simulateTransferIntent` | No |
15+
16+
The wallet passed to SAK is read-only: every signing method throws. The transfer intent action runs
17+
the repo policy gate and returns an instruction-shaped explanation with `executable: false`.
18+
19+
## Run it
20+
21+
`solana-agent-kit@2.x` currently requires Node 22, so this example is intentionally isolated from the
22+
Node 20 runtime packages.
23+
24+
```sh
25+
npm install
26+
npm run smoke # deterministic mock mode, used by CI
27+
npm run demo # live devnet balance read + Jupiter/Pyth price reads
28+
```
29+
30+
For live reads, set `WALLET` to a devnet address. No private key is needed:
31+
32+
```ini
33+
WALLET=11111111111111111111111111111111
34+
SOLANA_RPC_URL=https://api.devnet.solana.com
35+
JUPITER_API_KEY=... # optional, used when Jupiter requires one
36+
```
37+
38+
## Future fund-moving actions
39+
40+
Do not add swaps, bridges, token launches, or live transfers here. A future SAK action that can move
41+
funds must be a separate reviewed surface and pass through the repo's existing chain:
42+
43+
`policy.enforce()` -> devnet guard -> simulation -> explicit approval -> ledger proof receipt -> UI transaction surface.
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { PublicKey } from '@solana/web3.js'
2+
import {
3+
SolanaAgentKit,
4+
executeAction,
5+
type Action,
6+
type Plugin,
7+
} from 'solana-agent-kit'
8+
import {
9+
SOL_MINT,
10+
TOKEN_PROGRAM_ID,
11+
createReadOnlyWallet,
12+
createReadonlySolanaPlugin,
13+
createSolanaAgentTools,
14+
type ReadonlySolanaConnection,
15+
} from '@pay/solana-agent-tools'
16+
17+
const DEVNET_RPC = 'https://api.devnet.solana.com'
18+
const DEMO_WALLET = process.env.WALLET ?? '11111111111111111111111111111111'
19+
const PYTH_SOL_USD = 'ef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d'
20+
const useMock = process.argv.includes('--mock')
21+
22+
function json(body: unknown): Response {
23+
return new Response(JSON.stringify(body), {
24+
status: 200,
25+
headers: { 'content-type': 'application/json' },
26+
})
27+
}
28+
29+
function mockConnection(): ReadonlySolanaConnection {
30+
return {
31+
async getBalance(pubkey) {
32+
if (pubkey.toBase58() !== DEMO_WALLET) throw new Error(`unexpected balance lookup ${pubkey.toBase58()}`)
33+
return 1_230_000_000
34+
},
35+
async getParsedTokenAccountsByOwner(owner, filter) {
36+
if (filter.programId.toBase58() !== TOKEN_PROGRAM_ID.toBase58()) {
37+
throw new Error(`unexpected token program ${filter.programId.toBase58()}`)
38+
}
39+
return {
40+
value: [{
41+
pubkey: new PublicKey(SOL_MINT),
42+
account: {
43+
data: {
44+
parsed: {
45+
info: {
46+
mint: SOL_MINT,
47+
owner: owner.toBase58(),
48+
tokenAmount: {
49+
amount: '42000000',
50+
decimals: 9,
51+
uiAmount: 0.042,
52+
uiAmountString: '0.042',
53+
},
54+
},
55+
},
56+
},
57+
},
58+
}],
59+
}
60+
},
61+
}
62+
}
63+
64+
const mockFetch: typeof fetch = async (input) => {
65+
const href = input instanceof Request ? input.url : String(input)
66+
if (href.includes('price/v3')) {
67+
return json({ [SOL_MINT]: { usdPrice: 123.45, decimals: 9, blockId: 42 } })
68+
}
69+
if (href.includes('hermes.pyth.network') || href.includes('/updates/price/latest')) {
70+
return json({
71+
parsed: [{
72+
id: PYTH_SOL_USD,
73+
price: { price: '12345000000', conf: '1000000', expo: -8, publish_time: 1710000000 },
74+
}],
75+
})
76+
}
77+
return new Response(`unexpected mock fetch ${href}`, { status: 404 })
78+
}
79+
80+
function getAction(agent: SolanaAgentKit, name: string): Action {
81+
const action = agent.actions.find((candidate) => candidate.name === name)
82+
if (!action) throw new Error(`missing action ${name}`)
83+
return action
84+
}
85+
86+
function summarizeTokenBalances(result: Record<string, unknown>): Record<string, unknown> {
87+
const balances = Array.isArray(result.balances) ? result.balances : []
88+
return {
89+
...result,
90+
balanceCount: balances.length,
91+
balances: balances.slice(0, 5),
92+
truncated: balances.length > 5,
93+
}
94+
}
95+
96+
const tools = createSolanaAgentTools(useMock
97+
? { connection: mockConnection(), fetch: mockFetch }
98+
: { rpcUrl: process.env.SOLANA_RPC_URL ?? DEVNET_RPC })
99+
100+
const agent = new SolanaAgentKit(
101+
createReadOnlyWallet(DEMO_WALLET),
102+
process.env.SOLANA_RPC_URL ?? DEVNET_RPC,
103+
{ signOnly: true },
104+
).use(createReadonlySolanaPlugin(tools) as unknown as Plugin)
105+
106+
const balance = await executeAction(
107+
getAction(agent, 'solana.read_wallet_balance'),
108+
agent,
109+
{ address: DEMO_WALLET },
110+
)
111+
const tokenBalances = await executeAction(
112+
getAction(agent, 'solana.read_token_balances'),
113+
agent,
114+
{ owner: DEMO_WALLET },
115+
)
116+
const jupiterPrice = await executeAction(
117+
getAction(agent, 'solana.fetch_token_price'),
118+
agent,
119+
{ id: 'SOL' },
120+
)
121+
const pythPrice = await executeAction(
122+
getAction(agent, 'solana.fetch_pyth_price'),
123+
agent,
124+
{ priceFeedId: PYTH_SOL_USD },
125+
)
126+
const transferIntent = await executeAction(
127+
getAction(agent, 'solana.simulate_transfer_intent'),
128+
agent,
129+
{
130+
service: 'sak.readonly-demo',
131+
buyer: DEMO_WALLET,
132+
recipient: DEMO_WALLET,
133+
amountSol: 0.0001,
134+
awardedPriceSol: 0.0001,
135+
policy: {
136+
maxSolPerRound: 0.001,
137+
maxSolPerSession: 0.01,
138+
allowedServices: ['sak.readonly-demo'],
139+
expectedPayout: DEMO_WALLET,
140+
},
141+
},
142+
)
143+
144+
const decision = {
145+
mode: useMock ? 'mock-smoke' : 'live-read-devnet',
146+
plugin: 'pay-readonly-solana-tools',
147+
agentActions: agent.actions.map((action) => action.name),
148+
observations: {
149+
balance,
150+
tokenBalances: summarizeTokenBalances(tokenBalances),
151+
jupiterPrice,
152+
pythPrice,
153+
},
154+
decision: {
155+
summary: 'Agent has enough read-only context to reason, but not enough authority to move funds.',
156+
nextStep: 'Escalate any real payment to the repo payment rail flow: policy, approval, ledger receipt, UI surface.',
157+
transferIntent,
158+
},
159+
}
160+
161+
if ((transferIntent as { executable?: unknown }).executable !== false) {
162+
throw new Error('read-only transfer intent unexpectedly became executable')
163+
}
164+
165+
console.log(JSON.stringify(decision, null, 2))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "agent-economy-solana-agent-kit",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"engines": { "node": ">=22" },
7+
"scripts": {
8+
"demo": "tsx demo.ts",
9+
"smoke": "tsx demo.ts --mock",
10+
"typecheck": "tsc --noEmit"
11+
},
12+
"dependencies": {
13+
"@pay/solana-agent-tools": "file:../../../packages/solana-agent-tools",
14+
"@solana/web3.js": "^1.98.0",
15+
"solana-agent-kit": "^2.0.10"
16+
},
17+
"devDependencies": {
18+
"@types/node": "^22.0.0",
19+
"tsx": "^4.19.2",
20+
"typescript": "^5.7.2"
21+
}
22+
}

0 commit comments

Comments
 (0)