@toon-protocol/core 1.4.1 → 1.5.0

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/dist/index.js CHANGED
@@ -27,12 +27,12 @@ var JOB_REVIEW_KIND = 31117;
27
27
  var WEB_OF_TRUST_KIND = 30382;
28
28
  var PREFIX_CLAIM_KIND = 10034;
29
29
  var PREFIX_GRANT_KIND = 10037;
30
+ var ILP_ROOT_PREFIX = "g.toon";
30
31
  var BLOB_STORAGE_REQUEST_KIND = 5094;
31
32
  var BLOB_STORAGE_RESULT_KIND = 6094;
32
33
  var PET_INTERACTION_REQUEST_KIND = 5900;
33
34
  var PET_INTERACTION_RESULT_KIND = 6900;
34
35
  var PET_INTERACTION_EVENT_KIND = 14919;
35
- var ILP_ROOT_PREFIX = "g.toon";
36
36
 
37
37
  // src/address/ilp-address-validation.ts
38
38
  var ILP_SEGMENT_PATTERN = /^[a-z0-9-]+$/;
@@ -277,15 +277,121 @@ function validatePrefix(prefix) {
277
277
  return { valid: true };
278
278
  }
279
279
 
280
- // src/events/parsers.ts
280
+ // src/chain/chain-id.ts
281
281
  function validateChainId(chainId) {
282
282
  if (!chainId) return false;
283
283
  const segments = chainId.split(":");
284
284
  if (segments.length < 2 || segments.length > 3) return false;
285
285
  return segments.every((s) => s.length > 0);
286
286
  }
287
+
288
+ // src/events/swap-pair-validation.ts
289
+ var RATE_REGEX = /^(0|[1-9]\d*)(\.\d+)?$/;
290
+ var AMOUNT_REGEX = /^\d+$/;
291
+ var MAX_NUMERIC_STRING_LENGTH = 80;
287
292
  function isObject(value) {
288
- return typeof value === "object" && value !== null;
293
+ return typeof value === "object" && value !== null && !Array.isArray(value);
294
+ }
295
+ function isNonEmptyString(value) {
296
+ return typeof value === "string" && value.length > 0;
297
+ }
298
+ function isNonNegativeInteger(value) {
299
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
300
+ }
301
+ function validateAsset(asset, side) {
302
+ if (!isObject(asset)) {
303
+ return {
304
+ valid: false,
305
+ reason: `${side} must be an object`,
306
+ field: side
307
+ };
308
+ }
309
+ if (!isNonEmptyString(asset.assetCode)) {
310
+ return {
311
+ valid: false,
312
+ reason: `${side}.assetCode must be a non-empty string`,
313
+ field: `${side}.assetCode`
314
+ };
315
+ }
316
+ if (!isNonNegativeInteger(asset.assetScale)) {
317
+ return {
318
+ valid: false,
319
+ reason: `${side}.assetScale must be a non-negative integer`,
320
+ field: `${side}.assetScale`
321
+ };
322
+ }
323
+ if (typeof asset.chain !== "string" || !validateChainId(asset.chain)) {
324
+ return {
325
+ valid: false,
326
+ reason: `${side}.chain must be a valid chain identifier (e.g., "evm:base:8453")`,
327
+ field: `${side}.chain`
328
+ };
329
+ }
330
+ return { valid: true };
331
+ }
332
+ function isValidSwapPair(pair) {
333
+ if (!isObject(pair)) {
334
+ return { valid: false, reason: "pair must be an object", field: "" };
335
+ }
336
+ const fromResult = validateAsset(pair.from, "from");
337
+ if (!fromResult.valid) return fromResult;
338
+ const toResult = validateAsset(pair.to, "to");
339
+ if (!toResult.valid) return toResult;
340
+ if (typeof pair.rate !== "string" || pair.rate.length > MAX_NUMERIC_STRING_LENGTH || !RATE_REGEX.test(pair.rate)) {
341
+ return {
342
+ valid: false,
343
+ reason: `rate must be a non-negative decimal string (no leading zeros, no exponent, max ${MAX_NUMERIC_STRING_LENGTH} chars)`,
344
+ field: "rate"
345
+ };
346
+ }
347
+ if (pair.minAmount !== void 0) {
348
+ if (typeof pair.minAmount !== "string" || pair.minAmount.length > MAX_NUMERIC_STRING_LENGTH || !AMOUNT_REGEX.test(pair.minAmount)) {
349
+ return {
350
+ valid: false,
351
+ reason: `minAmount must be a non-negative integer string (max ${MAX_NUMERIC_STRING_LENGTH} digits)`,
352
+ field: "minAmount"
353
+ };
354
+ }
355
+ }
356
+ if (pair.maxAmount !== void 0) {
357
+ if (typeof pair.maxAmount !== "string" || pair.maxAmount.length > MAX_NUMERIC_STRING_LENGTH || !AMOUNT_REGEX.test(pair.maxAmount)) {
358
+ return {
359
+ valid: false,
360
+ reason: `maxAmount must be a non-negative integer string (max ${MAX_NUMERIC_STRING_LENGTH} digits)`,
361
+ field: "maxAmount"
362
+ };
363
+ }
364
+ }
365
+ if (pair.minAmount !== void 0 && pair.maxAmount !== void 0) {
366
+ if (BigInt(pair.minAmount) > BigInt(pair.maxAmount)) {
367
+ return {
368
+ valid: false,
369
+ reason: "minAmount must not exceed maxAmount",
370
+ field: "minAmount/maxAmount"
371
+ };
372
+ }
373
+ }
374
+ return { valid: true };
375
+ }
376
+ function formatMessage(index, result) {
377
+ return `swapPairs[${index}]: ${result.reason} (field: ${result.field})`;
378
+ }
379
+ function assertSwapPairForBuild(pair, index) {
380
+ const result = isValidSwapPair(pair);
381
+ if (!result.valid) {
382
+ throw new ToonError(formatMessage(index, result), "INVALID_SWAP_PAIR");
383
+ }
384
+ }
385
+ function assertSwapPairForParse(pair, index) {
386
+ const result = isValidSwapPair(pair);
387
+ if (!result.valid) {
388
+ throw new InvalidEventError(formatMessage(index, result));
389
+ }
390
+ }
391
+
392
+ // src/events/parsers.ts
393
+ function isObject2(value) {
394
+ return typeof value === "object" && value !== null && !Array.isArray(value);
289
395
  }
290
396
  function parseIlpPeerInfo(event) {
291
397
  if (event.kind !== ILP_PEER_INFO_KIND) {
@@ -302,12 +408,14 @@ function parseIlpPeerInfo(event) {
302
408
  err instanceof Error ? err : void 0
303
409
  );
304
410
  }
305
- if (!isObject(parsed)) {
411
+ if (!isObject2(parsed)) {
306
412
  throw new InvalidEventError("Event content must be a JSON object");
307
413
  }
308
414
  const {
309
415
  ilpAddress,
310
416
  btpEndpoint,
417
+ httpEndpoint,
418
+ supportsUpgrade,
311
419
  blsHttpEndpoint,
312
420
  settlementEngine,
313
421
  assetCode,
@@ -319,10 +427,14 @@ function parseIlpPeerInfo(event) {
319
427
  "Missing or invalid required field: ilpAddress"
320
428
  );
321
429
  }
322
- if (typeof btpEndpoint !== "string" || btpEndpoint.length === 0) {
323
- throw new InvalidEventError(
324
- "Missing or invalid required field: btpEndpoint"
325
- );
430
+ if (btpEndpoint !== void 0 && typeof btpEndpoint !== "string") {
431
+ throw new InvalidEventError("Invalid field: btpEndpoint must be a string");
432
+ }
433
+ if (httpEndpoint !== void 0 && typeof httpEndpoint !== "string") {
434
+ throw new InvalidEventError("Invalid field: httpEndpoint must be a string");
435
+ }
436
+ if (supportsUpgrade !== void 0 && typeof supportsUpgrade !== "boolean") {
437
+ throw new InvalidEventError("Invalid field: supportsUpgrade must be a boolean");
326
438
  }
327
439
  if (typeof assetCode !== "string" || assetCode.length === 0) {
328
440
  throw new InvalidEventError("Missing or invalid required field: assetCode");
@@ -361,7 +473,7 @@ function parseIlpPeerInfo(event) {
361
473
  }
362
474
  }
363
475
  if (settlementAddresses !== void 0) {
364
- if (!isObject(settlementAddresses)) {
476
+ if (!isObject2(settlementAddresses)) {
365
477
  throw new InvalidEventError("settlementAddresses must be an object");
366
478
  }
367
479
  for (const [key, value] of Object.entries(settlementAddresses)) {
@@ -388,12 +500,12 @@ function parseIlpPeerInfo(event) {
388
500
  }
389
501
  }
390
502
  if (preferredTokens !== void 0) {
391
- if (!isObject(preferredTokens)) {
503
+ if (!isObject2(preferredTokens)) {
392
504
  throw new InvalidEventError("preferredTokens must be an object");
393
505
  }
394
506
  }
395
507
  if (tokenNetworks !== void 0) {
396
- if (!isObject(tokenNetworks)) {
508
+ if (!isObject2(tokenNetworks)) {
397
509
  throw new InvalidEventError("tokenNetworks must be an object");
398
510
  }
399
511
  }
@@ -411,7 +523,7 @@ function parseIlpPeerInfo(event) {
411
523
  const { prefixPricing: rawPrefixPricing } = parsed;
412
524
  let prefixPricing;
413
525
  if (rawPrefixPricing !== void 0) {
414
- if (!isObject(rawPrefixPricing)) {
526
+ if (!isObject2(rawPrefixPricing)) {
415
527
  throw new InvalidEventError("prefixPricing must be an object");
416
528
  }
417
529
  const { basePrice } = rawPrefixPricing;
@@ -422,6 +534,17 @@ function parseIlpPeerInfo(event) {
422
534
  }
423
535
  prefixPricing = { basePrice };
424
536
  }
537
+ const { swapPairs: rawSwapPairs } = parsed;
538
+ let swapPairs;
539
+ if (rawSwapPairs !== void 0) {
540
+ if (!Array.isArray(rawSwapPairs)) {
541
+ throw new InvalidEventError("swapPairs must be an array");
542
+ }
543
+ rawSwapPairs.forEach((pair, index) => {
544
+ assertSwapPairForParse(pair, index);
545
+ });
546
+ swapPairs = rawSwapPairs;
547
+ }
425
548
  let ilpAddresses;
426
549
  if (rawIlpAddresses !== void 0) {
427
550
  if (!Array.isArray(rawIlpAddresses)) {
@@ -445,7 +568,9 @@ function parseIlpPeerInfo(event) {
445
568
  }
446
569
  return {
447
570
  ilpAddress,
448
- btpEndpoint,
571
+ btpEndpoint: typeof btpEndpoint === "string" ? btpEndpoint : "",
572
+ ...httpEndpoint !== void 0 && typeof httpEndpoint === "string" && { httpEndpoint },
573
+ ...supportsUpgrade !== void 0 && typeof supportsUpgrade === "boolean" && { supportsUpgrade },
449
574
  ...blsHttpEndpoint !== void 0 && typeof blsHttpEndpoint === "string" && { blsHttpEndpoint },
450
575
  assetCode,
451
576
  assetScale,
@@ -460,13 +585,30 @@ function parseIlpPeerInfo(event) {
460
585
  },
461
586
  ilpAddresses,
462
587
  feePerByte,
463
- ...prefixPricing !== void 0 && { prefixPricing }
588
+ ...prefixPricing !== void 0 && { prefixPricing },
589
+ ...swapPairs !== void 0 && { swapPairs }
464
590
  };
465
591
  }
466
592
 
467
593
  // src/events/builders.ts
468
594
  import { finalizeEvent } from "nostr-tools/pure";
469
- function buildIlpPeerInfoEvent(info, secretKey) {
595
+
596
+ // src/events/nip40.ts
597
+ var EXPIRATION_TAG = "expiration";
598
+ function getEventExpiration(event) {
599
+ const tag = event.tags.find((t) => t[0] === EXPIRATION_TAG);
600
+ if (!tag || tag[1] === void 0) return void 0;
601
+ const ts = Number(tag[1]);
602
+ if (!Number.isFinite(ts) || ts < 0) return void 0;
603
+ return ts;
604
+ }
605
+ function isEventExpired(event, nowSeconds = Math.floor(Date.now() / 1e3)) {
606
+ const exp = getEventExpiration(event);
607
+ return exp !== void 0 && exp <= nowSeconds;
608
+ }
609
+
610
+ // src/events/builders.ts
611
+ function buildIlpPeerInfoEvent(info, secretKey, options = {}) {
470
612
  if (info.feePerByte !== void 0) {
471
613
  if (typeof info.feePerByte !== "string" || !/^\d+$/.test(info.feePerByte)) {
472
614
  throw new ToonError(
@@ -498,12 +640,31 @@ function buildIlpPeerInfoEvent(info, secretKey) {
498
640
  ilpAddress: primaryAddress
499
641
  };
500
642
  }
643
+ if (info.swapPairs !== void 0) {
644
+ if (!Array.isArray(info.swapPairs)) {
645
+ throw new ToonError(
646
+ "swapPairs must be an array when provided",
647
+ "INVALID_SWAP_PAIR"
648
+ );
649
+ }
650
+ info.swapPairs.forEach((pair, index) => {
651
+ assertSwapPairForBuild(pair, index);
652
+ });
653
+ }
654
+ const createdAt = Math.floor(Date.now() / 1e3);
655
+ const tags = [];
656
+ if (options.ttlSeconds !== void 0 && options.ttlSeconds > 0) {
657
+ tags.push([
658
+ EXPIRATION_TAG,
659
+ String(createdAt + Math.floor(options.ttlSeconds))
660
+ ]);
661
+ }
501
662
  return finalizeEvent(
502
663
  {
503
664
  kind: ILP_PEER_INFO_KIND,
504
665
  content: JSON.stringify(effectiveInfo),
505
- tags: [],
506
- created_at: Math.floor(Date.now() / 1e3)
666
+ tags,
667
+ created_at: createdAt
507
668
  },
508
669
  secretKey
509
670
  );
@@ -1220,7 +1381,8 @@ function buildWorkflowDefinitionEvent(params, secretKey) {
1220
1381
  };
1221
1382
  const contentJson = JSON.stringify(contentBody);
1222
1383
  const tags = [];
1223
- const dTag = params.workflowId ?? `wf-${Date.now()}-${params.steps.length}`;
1384
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
1385
+ const dTag = params.workflowId ?? `wf-${Date.now()}-${params.steps.length}-${randomSuffix}`;
1224
1386
  tags.push(["d", dTag]);
1225
1387
  tags.push(["bid", params.totalBid, "usdc"]);
1226
1388
  return finalizeEvent6(
@@ -2486,6 +2648,12 @@ var SeedRelayDiscovery = class {
2486
2648
  );
2487
2649
  continue;
2488
2650
  }
2651
+ if (isEventExpired(event)) {
2652
+ console.warn(
2653
+ `[SeedRelayDiscovery] Skipping expired kind:10032 announcement: ${event.id}`
2654
+ );
2655
+ continue;
2656
+ }
2489
2657
  const info = parseIlpPeerInfo(event);
2490
2658
  info.pubkey = event.pubkey;
2491
2659
  discoveredPeers.push(info);
@@ -2749,6 +2917,152 @@ function resolveTokenForChain(chain, requesterPreferredTokens, responderPreferre
2749
2917
  return void 0;
2750
2918
  }
2751
2919
 
2920
+ // src/settlement/hashes.ts
2921
+ import { keccak_256 } from "@noble/hashes/sha3.js";
2922
+ import { sha256 } from "@noble/hashes/sha2.js";
2923
+ import {
2924
+ bytesToHex,
2925
+ hexToBytes as nobleHexToBytes
2926
+ } from "@noble/hashes/utils.js";
2927
+ function hexToBytes(hex) {
2928
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
2929
+ if (clean.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(clean)) {
2930
+ throw new Error(`Invalid hex string: ${hex}`);
2931
+ }
2932
+ return nobleHexToBytes(clean);
2933
+ }
2934
+ function bigintToBytes32BE(x) {
2935
+ if (x < 0n) {
2936
+ throw new Error("bigint must be non-negative for balance-proof encoding");
2937
+ }
2938
+ const out = new Uint8Array(32);
2939
+ let v = x;
2940
+ for (let i = 31; i >= 0; i--) {
2941
+ out[i] = Number(v & 0xffn);
2942
+ v >>= 8n;
2943
+ }
2944
+ if (v !== 0n) {
2945
+ throw new Error("bigint exceeds 256 bits");
2946
+ }
2947
+ return out;
2948
+ }
2949
+ function concatBytes(...parts) {
2950
+ let len = 0;
2951
+ for (const p of parts) len += p.length;
2952
+ const out = new Uint8Array(len);
2953
+ let o = 0;
2954
+ for (const p of parts) {
2955
+ out.set(p, o);
2956
+ o += p.length;
2957
+ }
2958
+ return out;
2959
+ }
2960
+ function balanceProofHashEvm(channelIdBytes, cumulativeAmount, nonce, recipientBytes) {
2961
+ return keccak_256(
2962
+ concatBytes(
2963
+ channelIdBytes,
2964
+ bigintToBytes32BE(cumulativeAmount),
2965
+ bigintToBytes32BE(nonce),
2966
+ recipientBytes
2967
+ )
2968
+ );
2969
+ }
2970
+ function balanceProofHashSolana(channelId, cumulativeAmount, nonce, recipient) {
2971
+ return sha256(
2972
+ concatBytes(
2973
+ new TextEncoder().encode(channelId),
2974
+ bigintToBytes32BE(cumulativeAmount),
2975
+ bigintToBytes32BE(nonce),
2976
+ new TextEncoder().encode(recipient)
2977
+ )
2978
+ );
2979
+ }
2980
+ function minaHashToField(s) {
2981
+ const digestHex = bytesToHex(sha256(new TextEncoder().encode(s)));
2982
+ return BigInt("0x" + digestHex.slice(0, 60));
2983
+ }
2984
+ function balanceProofFieldsMina(channelId, cumulativeAmount, nonce, recipient) {
2985
+ return [
2986
+ minaHashToField(channelId),
2987
+ cumulativeAmount,
2988
+ nonce,
2989
+ minaHashToField(recipient)
2990
+ ];
2991
+ }
2992
+
2993
+ // src/settlement/base58.ts
2994
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
2995
+ function base58Encode(bytes) {
2996
+ let zeros = 0;
2997
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++) zeros++;
2998
+ let value = 0n;
2999
+ for (const byte of bytes) {
3000
+ value = value * 256n + BigInt(byte);
3001
+ }
3002
+ let result = "";
3003
+ while (value > 0n) {
3004
+ result = BASE58_ALPHABET[Number(value % 58n)] + result;
3005
+ value = value / 58n;
3006
+ }
3007
+ for (let i = 0; i < zeros; i++) {
3008
+ result = "1" + result;
3009
+ }
3010
+ return result || "1";
3011
+ }
3012
+ function base58Decode(str) {
3013
+ let zeros = 0;
3014
+ for (let i = 0; i < str.length && str[i] === "1"; i++) zeros++;
3015
+ let value = 0n;
3016
+ for (const ch of str) {
3017
+ const idx = BASE58_ALPHABET.indexOf(ch);
3018
+ if (idx === -1) throw new Error(`Invalid base58 character: ${ch}`);
3019
+ value = value * 58n + BigInt(idx);
3020
+ }
3021
+ const hex = value === 0n ? "" : value.toString(16);
3022
+ const hexPadded = hex.length % 2 ? "0" + hex : hex;
3023
+ const rawBytes = [];
3024
+ for (let i = 0; i < hexPadded.length; i += 2) {
3025
+ rawBytes.push(parseInt(hexPadded.slice(i, i + 2), 16));
3026
+ }
3027
+ const result = new Uint8Array(zeros + rawBytes.length);
3028
+ result.set(rawBytes, zeros);
3029
+ return result;
3030
+ }
3031
+
3032
+ // src/settlement/mina-key.ts
3033
+ import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
3034
+ var MINA_PRIVATE_KEY_VERSION = 90;
3035
+ function hexToMinaBase58PrivateKey(privateKey) {
3036
+ if (!/^(0x)?[0-9a-fA-F]{64}$/.test(privateKey)) {
3037
+ return privateKey;
3038
+ }
3039
+ const beScalar = hexToBytes(privateKey);
3040
+ const leScalar = Uint8Array.from(beScalar).reverse();
3041
+ const payload = concatBytes(
3042
+ Uint8Array.from([MINA_PRIVATE_KEY_VERSION, 1]),
3043
+ leScalar
3044
+ );
3045
+ const checksum = sha2562(sha2562(payload)).slice(0, 4);
3046
+ return base58Encode(concatBytes(payload, checksum));
3047
+ }
3048
+ async function deriveMinaPublicKeyBase58(privateKey) {
3049
+ let signerModule = null;
3050
+ try {
3051
+ const specifier = "mina-signer";
3052
+ signerModule = await import(
3053
+ /* @vite-ignore */
3054
+ specifier
3055
+ );
3056
+ } catch {
3057
+ return null;
3058
+ }
3059
+ if (!signerModule) return null;
3060
+ const mod = signerModule;
3061
+ const ClientCtor = mod.default ?? mod;
3062
+ const client = new ClientCtor({ network: "mainnet" });
3063
+ return client.derivePublicKey(hexToMinaBase58PrivateKey(privateKey));
3064
+ }
3065
+
2752
3066
  // src/bootstrap/BootstrapService.ts
2753
3067
  import { SimplePool as SimplePool3 } from "nostr-tools/pool";
2754
3068
  import { getPublicKey as getPublicKey3 } from "nostr-tools/pure";
@@ -4097,6 +4411,32 @@ var AttestationBootstrap = class {
4097
4411
  }
4098
4412
  };
4099
4413
 
4414
+ // src/utils/reject-code.ts
4415
+ var ILP_TO_SEMANTIC = Object.freeze({
4416
+ T00: "internal_error",
4417
+ T04: "insufficient_funds",
4418
+ F00: "invalid_request",
4419
+ // F01 ("Invalid Packet" in ILP terms — emitted by the swap handler for
4420
+ // "Invalid gift wrap" / "Invalid amount") has NO dedicated entry in the
4421
+ // connector's REJECT_CODE_MAP (accepted semantics are: insufficient_funds,
4422
+ // expired, unreachable, invalid_request, invalid_amount,
4423
+ // insufficient_destination_amount, unexpected_payment, application_error,
4424
+ // internal_error, timeout). The closest faithful reason is `invalid_request`,
4425
+ // which the connector re-encodes to wire code F00. We map F01 EXPLICITLY
4426
+ // (rather than relying on the fallback below) so the F01 -> F00 normalization
4427
+ // is intentional and test-pinned, not a silent collapse that misleads callers
4428
+ // into thinking they hit a different failure class. See issue #86.
4429
+ F01: "invalid_request",
4430
+ F02: "unreachable",
4431
+ F03: "invalid_amount",
4432
+ F04: "insufficient_destination_amount",
4433
+ F06: "unexpected_payment",
4434
+ R00: "expired"
4435
+ });
4436
+ function ilpCodeToSemantic(ilpCode) {
4437
+ return ILP_TO_SEMANTIC[ilpCode] ?? "invalid_request";
4438
+ }
4439
+
4100
4440
  // src/compose.ts
4101
4441
  function createToonNode(config) {
4102
4442
  const directIlpClient = createDirectIlpClient(config.connector, {
@@ -4156,7 +4496,7 @@ function createToonNode(config) {
4156
4496
  }
4157
4497
  const adapted = result;
4158
4498
  adapted.rejectReason = {
4159
- code: result.code,
4499
+ code: ilpCodeToSemantic(result.code),
4160
4500
  message: result.message
4161
4501
  };
4162
4502
  return adapted;
@@ -4227,6 +4567,29 @@ var CHAIN_PRESETS = {
4227
4567
  usdcAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
4228
4568
  tokenNetworkAddress: "",
4229
4569
  registryAddress: ""
4570
+ },
4571
+ // Base Sepolia (public testnet) — TOON's deployed public-testnet settlement
4572
+ // environment (source of truth: e2e/testnets.json). USDC is the deployed test
4573
+ // token the TokenNetwork was opened against (NOT Circle's native testnet USDC,
4574
+ // which the channel would not recognise). Registry + TokenNetwork are live, so
4575
+ // this preset is settlement-complete: `--network testnet|devnet` settles here.
4576
+ "base-sepolia": {
4577
+ name: "base-sepolia",
4578
+ chainId: 84532,
4579
+ rpcUrl: "https://sepolia.base.org",
4580
+ usdcAddress: "0xac80670b86db1eeb5c18c82e18a6bda98fcb4504",
4581
+ tokenNetworkAddress: "0x47616F4b9cF4dA25F74FD727Cd85E9CA0C70Ec5C",
4582
+ registryAddress: "0xb9516c6c53c016c43f3671b1e5eb6096c83ec2c7"
4583
+ },
4584
+ // Base mainnet (public). USDC is Circle's native USDC on Base.
4585
+ // TOON TokenNetwork/registry not deployed yet → settlement relay-only.
4586
+ "base-mainnet": {
4587
+ name: "base-mainnet",
4588
+ chainId: 8453,
4589
+ rpcUrl: "https://mainnet.base.org",
4590
+ usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
4591
+ tokenNetworkAddress: "",
4592
+ registryAddress: ""
4230
4593
  }
4231
4594
  };
4232
4595
  function resolveChainConfig(chain) {
@@ -4234,8 +4597,9 @@ function resolveChainConfig(chain) {
4234
4597
  const name = envChain || chain || "anvil";
4235
4598
  const preset = CHAIN_PRESETS[name];
4236
4599
  if (!preset) {
4600
+ const validNames = Object.keys(CHAIN_PRESETS).join(", ");
4237
4601
  throw new ToonError(
4238
- `Unknown chain "${name}". Valid chains: anvil, arbitrum-sepolia, arbitrum-one`,
4602
+ `Unknown chain "${name}". Valid chains: ${validNames}`,
4239
4603
  "INVALID_CHAIN"
4240
4604
  );
4241
4605
  }
@@ -4328,6 +4692,7 @@ function buildEvmProviderEntry(config, keyId) {
4328
4692
  chainId: `evm:${config.chainId}`,
4329
4693
  rpcUrl: config.rpcUrl,
4330
4694
  registryAddress: config.registryAddress,
4695
+ tokenAddress: config.usdcAddress,
4331
4696
  keyId
4332
4697
  };
4333
4698
  }
@@ -4354,6 +4719,251 @@ function buildMinaProviderEntry(config, keyId) {
4354
4719
  };
4355
4720
  }
4356
4721
 
4722
+ // src/chain/network-profile.ts
4723
+ var DEV_EVM_PRESET = "anvil";
4724
+ var DEV_SOLANA = {
4725
+ usdcMint: "6GbdrVghwNKTz9raga7y3Y4qqX5Zgg3AC4d48Kt7C59Q",
4726
+ programId: ""
4727
+ // TOON payment-channel program not deployed on the dev Solana node
4728
+ };
4729
+ var RELAY_ONLY_CHAIN = "none";
4730
+ var EVM_TIER = {
4731
+ mainnet: { primary: "base-mainnet", also: ["arbitrum-one"] },
4732
+ testnet: { primary: "base-sepolia", also: ["arbitrum-sepolia"] },
4733
+ // EVM has no public devnet → the public Sepolia testnets serve the devnet tier.
4734
+ devnet: { primary: "base-sepolia", also: ["arbitrum-sepolia"] }
4735
+ };
4736
+ var SOLANA_DEPLOYED_DEVNET = {
4737
+ rpcUrl: "https://api.devnet.solana.com",
4738
+ cluster: "devnet",
4739
+ usdcMint: "9FtYCXjNiGDn17jSGvZuB5P4dZAKgVxUsDiQpLc8rbWy",
4740
+ programId: "EdJxYPDxGvaJuu57DSUptf4soLv8enpdyQJJhHDLiydG"
4741
+ };
4742
+ var SOLANA_TIER = {
4743
+ mainnet: {
4744
+ rpcUrl: "https://api.mainnet-beta.solana.com",
4745
+ cluster: "mainnet-beta",
4746
+ usdcMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
4747
+ programId: ""
4748
+ // TOON payment-channel program not deployed on mainnet
4749
+ },
4750
+ testnet: SOLANA_DEPLOYED_DEVNET,
4751
+ devnet: SOLANA_DEPLOYED_DEVNET
4752
+ };
4753
+ var MINA_DEPLOYED_DEVNET = {
4754
+ graphqlUrl: "https://api.minascan.io/node/devnet/v1/graphql",
4755
+ network: "devnet",
4756
+ // Reconciled with e2e/testnets.json (#205): the previous address
4757
+ // (B62qjFgX…) is a bare, unfunded zkApp on-chain (balance 0, channel
4758
+ // nonceField 0) — usable for claim issuance only. This is the funded,
4759
+ // on-chain-settling PaymentChannel zkApp (balance ~4 MINA, nonceField 21
4760
+ // — the on-chain settle proven in #217), same VK hash 21482326…
4761
+ zkAppAddress: "B62qrH1As4odHiNyKpTZMHaM6tRs6gi5DJ53efZKQBtbaR5CUctbDs6"
4762
+ };
4763
+ var MINA_TIER = {
4764
+ mainnet: {
4765
+ graphqlUrl: "https://api.minascan.io/node/mainnet/v1/graphql",
4766
+ network: "mainnet",
4767
+ zkAppAddress: ""
4768
+ // TOON payment-channel zkApp not deployed on mainnet
4769
+ },
4770
+ testnet: MINA_DEPLOYED_DEVNET,
4771
+ devnet: MINA_DEPLOYED_DEVNET
4772
+ };
4773
+ function evmSettlementComplete(p) {
4774
+ return p.registryAddress !== "" && p.tokenNetworkAddress !== "";
4775
+ }
4776
+ function resolveNetworkProfile(network, opts = {}) {
4777
+ if (network === "custom") {
4778
+ return resolveCustom(
4779
+ opts.customProviders ?? [],
4780
+ opts.endpoints ?? {},
4781
+ opts.keyId
4782
+ );
4783
+ }
4784
+ const tier = network;
4785
+ const chainProviders = [];
4786
+ const status = {
4787
+ evm: "unconfigured",
4788
+ solana: "unconfigured",
4789
+ mina: "unconfigured"
4790
+ };
4791
+ const primary = CHAIN_PRESETS[EVM_TIER[tier].primary];
4792
+ const nodeEnv = {
4793
+ EVM_CHAIN: primary.name,
4794
+ EVM_RPC_URL: primary.rpcUrl,
4795
+ EVM_CHAIN_ID: String(primary.chainId),
4796
+ EVM_USDC_ADDRESS: primary.usdcAddress
4797
+ };
4798
+ if (opts.keyId) {
4799
+ for (const name of [EVM_TIER[tier].primary, ...EVM_TIER[tier].also]) {
4800
+ const preset = CHAIN_PRESETS[name];
4801
+ if (evmSettlementComplete(preset)) {
4802
+ chainProviders.push(buildEvmProviderEntry(preset, opts.keyId));
4803
+ status.evm = "configured";
4804
+ }
4805
+ }
4806
+ }
4807
+ const sol = SOLANA_TIER[tier];
4808
+ nodeEnv.SOLANA_RPC_URL = sol.rpcUrl;
4809
+ if (sol.usdcMint) nodeEnv.SOLANA_USDC_MINT = sol.usdcMint;
4810
+ if (opts.keyId && sol.programId) {
4811
+ chainProviders.push(
4812
+ buildSolanaProviderEntry(
4813
+ {
4814
+ name: `solana-${sol.cluster}`,
4815
+ chainType: "solana",
4816
+ rpcUrl: sol.rpcUrl,
4817
+ programId: sol.programId,
4818
+ cluster: sol.cluster,
4819
+ ...sol.usdcMint && { tokenMint: sol.usdcMint }
4820
+ },
4821
+ opts.keyId
4822
+ )
4823
+ );
4824
+ status.solana = "configured";
4825
+ }
4826
+ const mina = MINA_TIER[tier];
4827
+ if (opts.keyId && mina.zkAppAddress) {
4828
+ chainProviders.push(
4829
+ buildMinaProviderEntry(
4830
+ {
4831
+ name: `mina-${mina.network}`,
4832
+ chainType: "mina",
4833
+ graphqlUrl: mina.graphqlUrl,
4834
+ zkAppAddress: mina.zkAppAddress,
4835
+ network: mina.network
4836
+ },
4837
+ opts.keyId
4838
+ )
4839
+ );
4840
+ status.mina = "configured";
4841
+ }
4842
+ return { network, chainProviders, nodeEnv, status };
4843
+ }
4844
+ function evmClientId(preset) {
4845
+ const family = preset.name.split("-")[0] ?? preset.name;
4846
+ return `evm:${family}:${preset.chainId}`;
4847
+ }
4848
+ function resolveClientNetwork(network) {
4849
+ const supportedChains = [];
4850
+ const chainRpcUrls = {};
4851
+ const preferredTokens = {};
4852
+ const tokenNetworks = {};
4853
+ const status = {
4854
+ evm: "unconfigured",
4855
+ solana: "unconfigured",
4856
+ mina: "unconfigured"
4857
+ };
4858
+ const evm = CHAIN_PRESETS[EVM_TIER[network].primary];
4859
+ const evmId = evmClientId(evm);
4860
+ supportedChains.push(evmId);
4861
+ chainRpcUrls[evmId] = evm.rpcUrl;
4862
+ if (evm.usdcAddress) preferredTokens[evmId] = evm.usdcAddress;
4863
+ if (evmSettlementComplete(evm)) {
4864
+ tokenNetworks[evmId] = evm.tokenNetworkAddress;
4865
+ status.evm = "configured";
4866
+ }
4867
+ const sol = SOLANA_TIER[network];
4868
+ const solId = `solana:${sol.cluster}`;
4869
+ supportedChains.push(solId);
4870
+ chainRpcUrls[solId] = sol.rpcUrl;
4871
+ if (sol.usdcMint) preferredTokens[solId] = sol.usdcMint;
4872
+ let solanaChannel;
4873
+ if (sol.programId) {
4874
+ solanaChannel = {
4875
+ rpcUrl: sol.rpcUrl,
4876
+ programId: sol.programId,
4877
+ ...sol.usdcMint && { tokenMint: sol.usdcMint }
4878
+ };
4879
+ status.solana = "configured";
4880
+ }
4881
+ const mina = MINA_TIER[network];
4882
+ const minaId = `mina:${mina.network}`;
4883
+ supportedChains.push(minaId);
4884
+ chainRpcUrls[minaId] = mina.graphqlUrl;
4885
+ let minaChannel;
4886
+ if (mina.zkAppAddress) {
4887
+ minaChannel = {
4888
+ graphqlUrl: mina.graphqlUrl,
4889
+ zkAppAddress: mina.zkAppAddress,
4890
+ networkId: mina.network === "mainnet" ? "mainnet" : "devnet"
4891
+ };
4892
+ status.mina = "configured";
4893
+ }
4894
+ return {
4895
+ supportedChains,
4896
+ chainRpcUrls,
4897
+ preferredTokens,
4898
+ tokenNetworks,
4899
+ ...solanaChannel && { solanaChannel },
4900
+ ...minaChannel && { minaChannel },
4901
+ status
4902
+ };
4903
+ }
4904
+ function resolveCustom(providers, endpoints, keyId) {
4905
+ if (providers.length === 0 && (endpoints.evmUrl || endpoints.solUrl)) {
4906
+ return resolveCustomEndpoints(endpoints, keyId);
4907
+ }
4908
+ const status = {
4909
+ evm: "unconfigured",
4910
+ solana: "unconfigured",
4911
+ mina: "unconfigured"
4912
+ };
4913
+ const nodeEnv = {};
4914
+ const evm = providers.find((p) => p.chainType === "evm");
4915
+ if (evm && evm.chainType === "evm") {
4916
+ nodeEnv.EVM_RPC_URL = evm.rpcUrl;
4917
+ nodeEnv.EVM_CHAIN_ID = evm.chainId.replace(/^evm:/, "");
4918
+ nodeEnv.EVM_USDC_ADDRESS = evm.tokenAddress;
4919
+ nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;
4920
+ if (evm.registryAddress) status.evm = "configured";
4921
+ } else {
4922
+ nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;
4923
+ }
4924
+ const sol = providers.find((p) => p.chainType === "solana");
4925
+ if (sol && sol.chainType === "solana") {
4926
+ nodeEnv.SOLANA_RPC_URL = sol.rpcUrl;
4927
+ if (sol.tokenMint) nodeEnv.SOLANA_USDC_MINT = sol.tokenMint;
4928
+ if (sol.programId) status.solana = "configured";
4929
+ }
4930
+ const mina = providers.find((p) => p.chainType === "mina");
4931
+ if (mina && mina.chainType === "mina" && mina.zkAppAddress) {
4932
+ status.mina = "configured";
4933
+ }
4934
+ return { network: "custom", chainProviders: providers, nodeEnv, status };
4935
+ }
4936
+ function resolveCustomEndpoints(endpoints, keyId) {
4937
+ const evm = CHAIN_PRESETS[DEV_EVM_PRESET];
4938
+ const status = {
4939
+ evm: "unconfigured",
4940
+ solana: "unconfigured",
4941
+ mina: "unconfigured"
4942
+ };
4943
+ const chainProviders = [];
4944
+ const nodeEnv = {
4945
+ EVM_CHAIN: DEV_EVM_PRESET,
4946
+ EVM_CHAIN_ID: String(evm.chainId),
4947
+ EVM_USDC_ADDRESS: evm.usdcAddress
4948
+ };
4949
+ if (endpoints.evmUrl) {
4950
+ nodeEnv.EVM_RPC_URL = endpoints.evmUrl;
4951
+ status.evm = "configured";
4952
+ if (keyId) {
4953
+ chainProviders.push(
4954
+ buildEvmProviderEntry({ ...evm, rpcUrl: endpoints.evmUrl }, keyId)
4955
+ );
4956
+ }
4957
+ } else {
4958
+ nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;
4959
+ }
4960
+ if (endpoints.solUrl) {
4961
+ nodeEnv.SOLANA_RPC_URL = endpoints.solUrl;
4962
+ nodeEnv.SOLANA_USDC_MINT = DEV_SOLANA.usdcMint;
4963
+ }
4964
+ return { network: "custom", chainProviders, nodeEnv, status };
4965
+ }
4966
+
4357
4967
  // src/x402/build-ilp-prepare.ts
4358
4968
  function buildIlpPrepare(params) {
4359
4969
  return {
@@ -4500,8 +5110,8 @@ var NixBuilder = class {
4500
5110
  );
4501
5111
  }
4502
5112
  const imageData = await readFile(imagePath);
4503
- const sha256 = createHash("sha256").update(imageData).digest("hex");
4504
- const imageHash = `sha256:${sha256}`;
5113
+ const sha2563 = createHash("sha256").update(imageData).digest("hex");
5114
+ const imageHash = `sha256:${sha2563}`;
4505
5115
  const pcr0 = createHash("sha384").update(imageData).digest("hex");
4506
5116
  const KERNEL_REGION_SIZE = 1024 * 1024;
4507
5117
  const kernelRegion = imageData.subarray(
@@ -4714,9 +5324,11 @@ export {
4714
5324
  BootstrapError,
4715
5325
  BootstrapService,
4716
5326
  CHAIN_PRESETS,
5327
+ EXPIRATION_TAG,
4717
5328
  GenesisPeerLoader,
4718
5329
  ILP_PEER_INFO_KIND,
4719
5330
  ILP_ROOT_PREFIX,
5331
+ ILP_TO_SEMANTIC,
4720
5332
  IMAGE_GENERATION_KIND,
4721
5333
  InvalidEventError,
4722
5334
  JOB_FEEDBACK_KIND,
@@ -4736,6 +5348,7 @@ export {
4736
5348
  PREFIX_GRANT_KIND,
4737
5349
  PcrReproducibilityError,
4738
5350
  PeerDiscoveryError,
5351
+ RELAY_ONLY_CHAIN,
4739
5352
  ReputationScoreCalculator,
4740
5353
  SEED_RELAY_LIST_KIND,
4741
5354
  SERVICE_DISCOVERY_KIND,
@@ -4757,6 +5370,12 @@ export {
4757
5370
  WORKFLOW_CHAIN_KIND,
4758
5371
  analyzeDockerfileForNonDeterminism,
4759
5372
  assignAddressFromHandshake,
5373
+ balanceProofFieldsMina,
5374
+ balanceProofHashEvm,
5375
+ balanceProofHashSolana,
5376
+ base58Decode,
5377
+ base58Encode,
5378
+ bigintToBytes32BE,
4760
5379
  buildAttestationEvent,
4761
5380
  buildBlobStorageRequest,
4762
5381
  buildEip712Domain,
@@ -4780,6 +5399,7 @@ export {
4780
5399
  buildWotDeclarationEvent,
4781
5400
  calculateRouteAmount,
4782
5401
  checkAddressCollision,
5402
+ concatBytes,
4783
5403
  createAgentRuntimeClient,
4784
5404
  createDirectChannelClient,
4785
5405
  createDirectConnectorAdmin,
@@ -4796,13 +5416,20 @@ export {
4796
5416
  decodeEventFromToon,
4797
5417
  deriveChildAddress,
4798
5418
  deriveFromKmsSeed,
5419
+ deriveMinaPublicKeyBase58,
4799
5420
  encodeEventToToon,
4800
5421
  encodeEventToToonString,
4801
5422
  extractPrefixFromHandshake,
5423
+ getEventExpiration,
4802
5424
  hasMinReputation,
4803
5425
  hasRequireAttestation,
5426
+ hexToBytes,
5427
+ hexToMinaBase58PrivateKey,
5428
+ ilpCodeToSemantic,
5429
+ isEventExpired,
4804
5430
  isGenesisNode,
4805
5431
  isValidIlpAddressStructure,
5432
+ minaHashToField,
4806
5433
  negotiateSettlementChain,
4807
5434
  parseAttestation,
4808
5435
  parseBlobStorageRequest,
@@ -4822,7 +5449,9 @@ export {
4822
5449
  publishSeedRelayEntry,
4823
5450
  readDockerfileNix,
4824
5451
  resolveChainConfig,
5452
+ resolveClientNetwork,
4825
5453
  resolveMinaChainConfig,
5454
+ resolveNetworkProfile,
4826
5455
  resolveRouteFees,
4827
5456
  resolveSolanaChainConfig,
4828
5457
  resolveTokenForChain,