openzoo 0.50.15 → 0.50.17
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
|
+
// ON BY DEFAULT — PROVEN ON CHAIN 2026-08-28.
|
|
377
|
+
//
|
|
378
|
+
// It shipped opt-in first because the fallback below covers a missing, slow
|
|
379
|
+
// or exhausted nonce but NOT a malformed nonce transaction: a wrong
|
|
380
|
+
// instruction order or account meta only surfaces at settle time, after the
|
|
381
|
+
// fallback is long past. That risk is now retired by a real settlement —
|
|
382
|
+
// tx 3pJbRH2u…mMWAZ3, instruction 0 `advanceNonceAccount`, err None.
|
|
383
|
+
// OPENZOO_NONCE=0 is the kill switch back to recent blockhashes.
|
|
384
|
+
if (facilitator && process.env.OPENZOO_NONCE !== '0') {
|
|
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.17",
|
|
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",
|