openzoo 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/lib/config.js +50 -2
- package/lib/info.js +1 -1
- package/lib/mcp.js +10 -3
- package/lib/proxy.js +14 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -152,8 +152,10 @@ The receipt names which base you got; `extra.directUsd` / `extra.savesVsDirect`
|
|
|
152
152
|
| rail | network | status |
|
|
153
153
|
|---|---|---|
|
|
154
154
|
| **Solana** (default) | `solana:5eykt…` | **live** — Token-2022 `TransferChecked`, partial-signed, gateway pays fees. Tested end-to-end against the production 402. Settlement uses a wrapped settlement mint as internal plumbing; you only ever hold and send USDC or TOKEN. |
|
|
155
|
-
| Base | `eip155:8453` |
|
|
156
|
-
| Robinhood Chain | `eip155:4663` | experimental, behind `OPENZOO_ENABLE_RH=1
|
|
155
|
+
| Base | `eip155:8453` | **offered by the zoo** — standard x402 EIP-3009 `transferWithAuthorization` against native USDC. Fund the wallet's EVM address with USDC on Base; nothing is converted. Settlement from this package is live-untested. |
|
|
156
|
+
| Robinhood Chain | `eip155:4663` | experimental, behind `OPENZOO_ENABLE_RH=1`. The zoo quotes it, but its settlement asset has no conversion path here (conversion is Solana-only), so there is no plain balance you can fund and have the shim spend — use Solana or Base. |
|
|
157
|
+
|
|
158
|
+
`npx openzoo` prints the rails off a live 402 at startup, and the funding line is derived from those rails — so a new chain shows up without this package shipping again.
|
|
157
159
|
|
|
158
160
|
The rail is chosen from the 402's `accepts[]` itself (Solana first). Amounts are always taken as raw units from the 402, and Solana decimals are read from the mint **on-chain** — never hardcoded. (The zoo's own pasted prompt hardcodes `decimals = 6`; that's wrong for 18-decimal mints and this package deliberately does not copy the bug.)
|
|
159
161
|
|
package/lib/config.js
CHANGED
|
@@ -28,9 +28,57 @@ export const FUNDING_ASSETS = [
|
|
|
28
28
|
export const USDC_MINT = FUNDING_ASSETS[0].mint;
|
|
29
29
|
export const TOKEN_MINT = FUNDING_ASSETS[1].mint;
|
|
30
30
|
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The one canonical way to tell a user how to fund the SOLANA rail. Callers
|
|
33
|
+
* pass the Solana address; the rail is named explicitly because the wallet
|
|
34
|
+
* also has an EVM address for the Base / Robinhood rails.
|
|
35
|
+
*/
|
|
32
36
|
export function fundingLine(address) {
|
|
33
|
-
return `send a few cents of USDC (${USDC_MINT}) or TOKEN (${TOKEN_MINT}) to ${address}`;
|
|
37
|
+
return `send a few cents of USDC (${USDC_MINT}) or TOKEN (${TOKEN_MINT}) on Solana to ${address}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What a user funds each rail with — UNDERLYING assets only, and only assets
|
|
42
|
+
* this shim can actually spend from. The settlement mints the 402 quotes are
|
|
43
|
+
* internal plumbing and never appear in user-facing copy.
|
|
44
|
+
*
|
|
45
|
+
* Solana: quoted in settlement mints, converted from plain USDC / TOKEN at
|
|
46
|
+
* payment time (lib/wrap.js).
|
|
47
|
+
* Base: quoted in native USDC — funded and spent as-is, no conversion.
|
|
48
|
+
* Robinhood: quoted in a settlement asset with no conversion path on EVM
|
|
49
|
+
* (wrapping is Solana-only), so there is no plain balance a user can fund
|
|
50
|
+
* and have the shim pay from — hence no assets, and no funding line.
|
|
51
|
+
*/
|
|
52
|
+
export const RAIL_FUNDING = {
|
|
53
|
+
solana: { label: 'Solana', assets: ['USDC', 'TOKEN'] },
|
|
54
|
+
base: { label: 'Base', assets: ['USDC'] },
|
|
55
|
+
robinhood: { label: 'Robinhood Chain', assets: [] },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* "USDC or TOKEN on Solana · USDC on Base" — the funding hint for exactly the
|
|
60
|
+
* rails a live 402 is offering, derived from liveRails().live. Rails with no
|
|
61
|
+
* fundable underlying, and networks we have no funding copy for (an
|
|
62
|
+
* unrecognised chain the zoo starts quoting), are left out rather than guessed
|
|
63
|
+
* at. Returns '' when nothing is fundable.
|
|
64
|
+
*/
|
|
65
|
+
export function railFundingHint(liveRailNames) {
|
|
66
|
+
return (liveRailNames || [])
|
|
67
|
+
.map((rail) => RAIL_FUNDING[rail])
|
|
68
|
+
.filter((spec) => spec?.assets.length)
|
|
69
|
+
.map((spec) => `${spec.assets.join(' or ')} on ${spec.label}`)
|
|
70
|
+
.join(' · ');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Rails the zoo is quoting right now that this shim cannot pay from a plain
|
|
75
|
+
* funded balance — named so a live rail never silently disappears from the
|
|
76
|
+
* funding advice.
|
|
77
|
+
*/
|
|
78
|
+
export function unfundableRails(liveRailNames) {
|
|
79
|
+
return (liveRailNames || [])
|
|
80
|
+
.filter((rail) => RAIL_FUNDING[rail] && !RAIL_FUNDING[rail].assets.length)
|
|
81
|
+
.map((rail) => RAIL_FUNDING[rail].label);
|
|
34
82
|
}
|
|
35
83
|
|
|
36
84
|
/**
|
package/lib/info.js
CHANGED
|
@@ -8,7 +8,7 @@ export function printAddress() {
|
|
|
8
8
|
const { keypair, evmPrivateKey, created, path } = loadOrCreateWallet();
|
|
9
9
|
if (created) console.log(`new burner wallet created at ${path} (chmod 600)`);
|
|
10
10
|
console.log(keypair.publicKey.toBase58());
|
|
11
|
-
console.log(`(evm, for Base/
|
|
11
|
+
console.log(`(evm, for the Base / Robinhood rails: ${privateKeyToAccount(evmPrivateKey).address})`);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export async function printBalance() {
|
package/lib/mcp.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { config, FUNDING_ASSETS } from './config.js';
|
|
5
|
+
import { config, FUNDING_ASSETS, liveRails, railFundingHint } from './config.js';
|
|
6
6
|
import { PayClient, QuoteTooHighError, UnderfundedError } from './pay.js';
|
|
7
7
|
import { tokenBalance } from './x402.js';
|
|
8
8
|
import { askWithContext, contextCacheDisabled, BIND_MIN_CHARS } from './hrr.js';
|
|
@@ -94,10 +94,17 @@ export async function startMcp() {
|
|
|
94
94
|
]);
|
|
95
95
|
const balances = { SOL: lamports / 1e9 };
|
|
96
96
|
FUNDING_ASSETS.forEach((a, i) => { balances[a.symbol] = bals[i].ui ?? 0; });
|
|
97
|
+
// Funding advice follows the rails the zoo is quoting right now, not a
|
|
98
|
+
// hardcoded Solana assumption. Fail soft: the probe is advisory.
|
|
99
|
+
const rails = await liveRails().catch(() => null);
|
|
100
|
+
const hint = rails ? railFundingHint(rails.live) : '';
|
|
97
101
|
return text({
|
|
98
102
|
solanaAddress: client.address,
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
evmAddress: client.evmAddress,
|
|
104
|
+
railsLiveNow: rails?.live ?? null,
|
|
105
|
+
fundWith: hint || `${FUNDING_ASSETS.map((a) => a.symbol).join(' or ')} on Solana`,
|
|
106
|
+
solanaMints: Object.fromEntries(FUNDING_ASSETS.map((a) => [a.symbol, a.mint])),
|
|
107
|
+
fundHint: 'send a few cents of a listed asset to the address for that rail — Solana assets to solanaAddress, Base assets to evmAddress. The shim converts to whatever the 402 quotes, at payment time.',
|
|
101
108
|
balances,
|
|
102
109
|
receipts: client.receipts.map((r) => ({ at: r.at, line: r.line })),
|
|
103
110
|
});
|
package/lib/proxy.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
config, FUNDING_ASSETS, fundingLine, liveRails, railFundingHint, unfundableRails, RAIL_FUNDING,
|
|
5
|
+
} from './config.js';
|
|
4
6
|
import { PayClient, QuoteTooHighError, UnderfundedError } from './pay.js';
|
|
5
7
|
import { tokenBalance } from './x402.js';
|
|
6
8
|
import { bindCorpus, contextCacheDisabled, BIND_MIN_CHARS } from './hrr.js';
|
|
@@ -194,7 +196,17 @@ export async function startProxy({ silent = false } = {}) {
|
|
|
194
196
|
try {
|
|
195
197
|
const rails = await liveRails();
|
|
196
198
|
if (rails) {
|
|
197
|
-
console.log(`rails live now: ${rails.live.join(' · ')}
|
|
199
|
+
console.log(`rails live now: ${rails.live.join(' · ')}`);
|
|
200
|
+
// Funding advice is derived from those rails, never hardcoded — the
|
|
201
|
+
// zoo can add a chain without this package shipping again.
|
|
202
|
+
const hint = railFundingHint(rails.live);
|
|
203
|
+
if (hint) console.log(`fund with: ${hint}`);
|
|
204
|
+
const unfundable = unfundableRails(rails.live);
|
|
205
|
+
if (unfundable.length) {
|
|
206
|
+
const fundable = rails.live.filter((r) => RAIL_FUNDING[r]?.assets.length).map((r) => RAIL_FUNDING[r].label);
|
|
207
|
+
const instead = fundable.length ? `pay from ${fundable.join(' or ')} instead` : 'no fundable rail is offered right now';
|
|
208
|
+
console.log(`note: ${unfundable.join(' / ')} is offered by the zoo but not fundable from a plain balance here — ${instead}.`);
|
|
209
|
+
}
|
|
198
210
|
if (rails.dark.length) {
|
|
199
211
|
const rh = rails.dark.includes('robinhood') ? ' — robinhood also needs OPENZOO_ENABLE_RH=1' : '';
|
|
200
212
|
console.log(`rails implemented but not offered by the zoo right now: ${rails.dark.join(' · ')}${rh}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|