openzoo 0.50.15 → 0.50.16
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/lib/x402.js +55 -6
- package/package.json +1 -1
package/lib/x402.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ComputeBudgetProgram,
|
|
4
4
|
Connection,
|
|
5
5
|
PublicKey,
|
|
6
|
+
SystemProgram,
|
|
6
7
|
Transaction,
|
|
7
8
|
TransactionInstruction,
|
|
8
9
|
} from '@solana/web3.js';
|
|
@@ -271,7 +272,7 @@ export const MEMO_PROGRAM_ID = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqX
|
|
|
271
272
|
* them in one window under 10 workers). The spec now puts uniqueness in the
|
|
272
273
|
* memo, so the CU limit is a plain constant and the nonce is the memo body.
|
|
273
274
|
*/
|
|
274
|
-
export function buildPayment({ accept, decimals, programId, recentBlockhash, keypair }) {
|
|
275
|
+
export function buildPayment({ accept, decimals, programId, recentBlockhash, keypair, nonce = null }) {
|
|
275
276
|
const mint = new PublicKey(accept.asset);
|
|
276
277
|
const payTo = new PublicKey(accept.payTo);
|
|
277
278
|
const feePayer = new PublicKey(accept.extra.feePayer);
|
|
@@ -281,6 +282,25 @@ export function buildPayment({ accept, decimals, programId, recentBlockhash, key
|
|
|
281
282
|
const dest = getAssociatedTokenAddressSync(mint, payTo, true, programId);
|
|
282
283
|
|
|
283
284
|
const tx = new Transaction({ feePayer, recentBlockhash });
|
|
285
|
+
// A DURABLE NONCE, WHEN THE FACILITATOR OFFERS ONE — and `nonceAdvance` MUST
|
|
286
|
+
// be the first instruction, which is why this precedes the compute budget.
|
|
287
|
+
//
|
|
288
|
+
// `recentBlockhash` is inside the signed message and expires after ~150 slots
|
|
289
|
+
// (~60s). x402 signs here and settles over there, so a slow uplink or a large
|
|
290
|
+
// body kills a valid payment and nobody downstream can refresh the hash.
|
|
291
|
+
// MEASURED 2026-08-28: every settle from one client failed for forty minutes
|
|
292
|
+
// with `blockhash … valid=false`, funded fee payer, 526 bytes — nothing wrong
|
|
293
|
+
// with it but age. With a nonce in that slot the transaction never expires.
|
|
294
|
+
//
|
|
295
|
+
// The authority is a dedicated facilitator key, NOT the fee payer:
|
|
296
|
+
// scheme_exact_svm forbids the fee payer from appearing in any instruction's
|
|
297
|
+
// accounts, and an advance names its authority as a signer.
|
|
298
|
+
if (nonce?.account && nonce?.authority) {
|
|
299
|
+
tx.add(SystemProgram.nonceAdvance({
|
|
300
|
+
noncePubkey: new PublicKey(nonce.account),
|
|
301
|
+
authorizedPubkey: new PublicKey(nonce.authority),
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
284
304
|
tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }));
|
|
285
305
|
tx.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1 }));
|
|
286
306
|
tx.add(createTransferCheckedInstruction(
|
|
@@ -294,8 +314,8 @@ export function buildPayment({ accept, decimals, programId, recentBlockhash, key
|
|
|
294
314
|
programId: MEMO_PROGRAM_ID,
|
|
295
315
|
data: Buffer.from(accept.extra?.memo || crypto.randomBytes(16).toString('hex'), 'utf8'),
|
|
296
316
|
}));
|
|
297
|
-
if (tx.instructions.length < 3 || tx.instructions.length >
|
|
298
|
-
throw new Error(`x402 svm payment has ${tx.instructions.length} instructions, outside the required 3..
|
|
317
|
+
if (tx.instructions.length < 3 || tx.instructions.length > 7) {
|
|
318
|
+
throw new Error(`x402 svm payment has ${tx.instructions.length} instructions, outside the required 3..7`);
|
|
299
319
|
}
|
|
300
320
|
// THE FEE PAYER MUST NOT APPEAR IN ANY INSTRUCTION'S ACCOUNTS. It is the
|
|
301
321
|
// facilitator's gas signer; letting it in means their signature can be made
|
|
@@ -345,9 +365,38 @@ export function buildPayment({ accept, decimals, programId, recentBlockhash, key
|
|
|
345
365
|
*/
|
|
346
366
|
export async function buildPaymentOnline(connection, keypair, accept) {
|
|
347
367
|
const { programId, decimals } = await getMintInfo(connection, accept.asset);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
368
|
+
|
|
369
|
+
// PREFER A DURABLE NONCE. The facilitator leases one from a pool; with it in
|
|
370
|
+
// the blockhash slot the signed payment never expires, which removes the
|
|
371
|
+
// whole class of "valid=false, no logs" failures. Best-effort by design: no
|
|
372
|
+
// nonce, a slow facilitator, or an exhausted pool all fall through to a
|
|
373
|
+
// recent blockhash, because a 60-second window beats no payment at all.
|
|
374
|
+
let nonce = null;
|
|
375
|
+
const facilitator = accept?.extra?.facilitator;
|
|
376
|
+
// OPT-IN UNTIL IT HAS SETTLED ONCE FOR REAL.
|
|
377
|
+
//
|
|
378
|
+
// The fallback below covers a missing/slow/exhausted nonce, but NOT a
|
|
379
|
+
// malformed nonce transaction — if the instruction order or the account metas
|
|
380
|
+
// are wrong, the payment fails and the fallback never runs, because we only
|
|
381
|
+
// learn at settle time. This has not yet settled a single real payment, and
|
|
382
|
+
// shipping it on by default would bet every Solana payer on that. Set
|
|
383
|
+
// OPENZOO_NONCE=1 to use it; the default stays on recent blockhashes.
|
|
384
|
+
if (facilitator && process.env.OPENZOO_NONCE === '1') {
|
|
385
|
+
try {
|
|
386
|
+
const r = await fetch(`${facilitator}/nonce?network=${encodeURIComponent(accept.network)}`, {
|
|
387
|
+
signal: AbortSignal.timeout(4000),
|
|
388
|
+
});
|
|
389
|
+
if (r.ok) {
|
|
390
|
+
const j = await r.json();
|
|
391
|
+
if (j?.account && j?.nonce && j?.authority) nonce = j;
|
|
392
|
+
}
|
|
393
|
+
} catch { /* fall through to a blockhash */ }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const recentBlockhash = nonce
|
|
397
|
+
? nonce.nonce
|
|
398
|
+
: (await connection.getLatestBlockhash(process.env.OPENZOO_BLOCKHASH_COMMITMENT || 'finalized')).blockhash;
|
|
399
|
+
return buildPayment({ accept, decimals, programId, recentBlockhash, keypair, nonce });
|
|
351
400
|
}
|
|
352
401
|
|
|
353
402
|
/** Token balance of our ATA for a mint. Returns { raw: bigint, ui: number|null }. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.16",
|
|
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 and Base rails live; Robinhood experimental.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|