@toon-protocol/core 1.4.0 → 1.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/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,7 +408,7 @@ 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 {
@@ -319,10 +425,8 @@ function parseIlpPeerInfo(event) {
319
425
  "Missing or invalid required field: ilpAddress"
320
426
  );
321
427
  }
322
- if (typeof btpEndpoint !== "string" || btpEndpoint.length === 0) {
323
- throw new InvalidEventError(
324
- "Missing or invalid required field: btpEndpoint"
325
- );
428
+ if (btpEndpoint !== void 0 && typeof btpEndpoint !== "string") {
429
+ throw new InvalidEventError("Invalid field: btpEndpoint must be a string");
326
430
  }
327
431
  if (typeof assetCode !== "string" || assetCode.length === 0) {
328
432
  throw new InvalidEventError("Missing or invalid required field: assetCode");
@@ -361,7 +465,7 @@ function parseIlpPeerInfo(event) {
361
465
  }
362
466
  }
363
467
  if (settlementAddresses !== void 0) {
364
- if (!isObject(settlementAddresses)) {
468
+ if (!isObject2(settlementAddresses)) {
365
469
  throw new InvalidEventError("settlementAddresses must be an object");
366
470
  }
367
471
  for (const [key, value] of Object.entries(settlementAddresses)) {
@@ -388,12 +492,12 @@ function parseIlpPeerInfo(event) {
388
492
  }
389
493
  }
390
494
  if (preferredTokens !== void 0) {
391
- if (!isObject(preferredTokens)) {
495
+ if (!isObject2(preferredTokens)) {
392
496
  throw new InvalidEventError("preferredTokens must be an object");
393
497
  }
394
498
  }
395
499
  if (tokenNetworks !== void 0) {
396
- if (!isObject(tokenNetworks)) {
500
+ if (!isObject2(tokenNetworks)) {
397
501
  throw new InvalidEventError("tokenNetworks must be an object");
398
502
  }
399
503
  }
@@ -411,7 +515,7 @@ function parseIlpPeerInfo(event) {
411
515
  const { prefixPricing: rawPrefixPricing } = parsed;
412
516
  let prefixPricing;
413
517
  if (rawPrefixPricing !== void 0) {
414
- if (!isObject(rawPrefixPricing)) {
518
+ if (!isObject2(rawPrefixPricing)) {
415
519
  throw new InvalidEventError("prefixPricing must be an object");
416
520
  }
417
521
  const { basePrice } = rawPrefixPricing;
@@ -422,6 +526,17 @@ function parseIlpPeerInfo(event) {
422
526
  }
423
527
  prefixPricing = { basePrice };
424
528
  }
529
+ const { swapPairs: rawSwapPairs } = parsed;
530
+ let swapPairs;
531
+ if (rawSwapPairs !== void 0) {
532
+ if (!Array.isArray(rawSwapPairs)) {
533
+ throw new InvalidEventError("swapPairs must be an array");
534
+ }
535
+ rawSwapPairs.forEach((pair, index) => {
536
+ assertSwapPairForParse(pair, index);
537
+ });
538
+ swapPairs = rawSwapPairs;
539
+ }
425
540
  let ilpAddresses;
426
541
  if (rawIlpAddresses !== void 0) {
427
542
  if (!Array.isArray(rawIlpAddresses)) {
@@ -445,7 +560,7 @@ function parseIlpPeerInfo(event) {
445
560
  }
446
561
  return {
447
562
  ilpAddress,
448
- btpEndpoint,
563
+ btpEndpoint: typeof btpEndpoint === "string" ? btpEndpoint : "",
449
564
  ...blsHttpEndpoint !== void 0 && typeof blsHttpEndpoint === "string" && { blsHttpEndpoint },
450
565
  assetCode,
451
566
  assetScale,
@@ -460,13 +575,30 @@ function parseIlpPeerInfo(event) {
460
575
  },
461
576
  ilpAddresses,
462
577
  feePerByte,
463
- ...prefixPricing !== void 0 && { prefixPricing }
578
+ ...prefixPricing !== void 0 && { prefixPricing },
579
+ ...swapPairs !== void 0 && { swapPairs }
464
580
  };
465
581
  }
466
582
 
467
583
  // src/events/builders.ts
468
584
  import { finalizeEvent } from "nostr-tools/pure";
469
- function buildIlpPeerInfoEvent(info, secretKey) {
585
+
586
+ // src/events/nip40.ts
587
+ var EXPIRATION_TAG = "expiration";
588
+ function getEventExpiration(event) {
589
+ const tag = event.tags.find((t) => t[0] === EXPIRATION_TAG);
590
+ if (!tag || tag[1] === void 0) return void 0;
591
+ const ts = Number(tag[1]);
592
+ if (!Number.isFinite(ts) || ts < 0) return void 0;
593
+ return ts;
594
+ }
595
+ function isEventExpired(event, nowSeconds = Math.floor(Date.now() / 1e3)) {
596
+ const exp = getEventExpiration(event);
597
+ return exp !== void 0 && exp <= nowSeconds;
598
+ }
599
+
600
+ // src/events/builders.ts
601
+ function buildIlpPeerInfoEvent(info, secretKey, options = {}) {
470
602
  if (info.feePerByte !== void 0) {
471
603
  if (typeof info.feePerByte !== "string" || !/^\d+$/.test(info.feePerByte)) {
472
604
  throw new ToonError(
@@ -498,12 +630,31 @@ function buildIlpPeerInfoEvent(info, secretKey) {
498
630
  ilpAddress: primaryAddress
499
631
  };
500
632
  }
633
+ if (info.swapPairs !== void 0) {
634
+ if (!Array.isArray(info.swapPairs)) {
635
+ throw new ToonError(
636
+ "swapPairs must be an array when provided",
637
+ "INVALID_SWAP_PAIR"
638
+ );
639
+ }
640
+ info.swapPairs.forEach((pair, index) => {
641
+ assertSwapPairForBuild(pair, index);
642
+ });
643
+ }
644
+ const createdAt = Math.floor(Date.now() / 1e3);
645
+ const tags = [];
646
+ if (options.ttlSeconds !== void 0 && options.ttlSeconds > 0) {
647
+ tags.push([
648
+ EXPIRATION_TAG,
649
+ String(createdAt + Math.floor(options.ttlSeconds))
650
+ ]);
651
+ }
501
652
  return finalizeEvent(
502
653
  {
503
654
  kind: ILP_PEER_INFO_KIND,
504
655
  content: JSON.stringify(effectiveInfo),
505
- tags: [],
506
- created_at: Math.floor(Date.now() / 1e3)
656
+ tags,
657
+ created_at: createdAt
507
658
  },
508
659
  secretKey
509
660
  );
@@ -1220,7 +1371,8 @@ function buildWorkflowDefinitionEvent(params, secretKey) {
1220
1371
  };
1221
1372
  const contentJson = JSON.stringify(contentBody);
1222
1373
  const tags = [];
1223
- const dTag = params.workflowId ?? `wf-${Date.now()}-${params.steps.length}`;
1374
+ const randomSuffix = Math.random().toString(36).slice(2, 8);
1375
+ const dTag = params.workflowId ?? `wf-${Date.now()}-${params.steps.length}-${randomSuffix}`;
1224
1376
  tags.push(["d", dTag]);
1225
1377
  tags.push(["bid", params.totalBid, "usdc"]);
1226
1378
  return finalizeEvent6(
@@ -2486,6 +2638,12 @@ var SeedRelayDiscovery = class {
2486
2638
  );
2487
2639
  continue;
2488
2640
  }
2641
+ if (isEventExpired(event)) {
2642
+ console.warn(
2643
+ `[SeedRelayDiscovery] Skipping expired kind:10032 announcement: ${event.id}`
2644
+ );
2645
+ continue;
2646
+ }
2489
2647
  const info = parseIlpPeerInfo(event);
2490
2648
  info.pubkey = event.pubkey;
2491
2649
  discoveredPeers.push(info);
@@ -2749,6 +2907,152 @@ function resolveTokenForChain(chain, requesterPreferredTokens, responderPreferre
2749
2907
  return void 0;
2750
2908
  }
2751
2909
 
2910
+ // src/settlement/hashes.ts
2911
+ import { keccak_256 } from "@noble/hashes/sha3.js";
2912
+ import { sha256 } from "@noble/hashes/sha2.js";
2913
+ import {
2914
+ bytesToHex,
2915
+ hexToBytes as nobleHexToBytes
2916
+ } from "@noble/hashes/utils.js";
2917
+ function hexToBytes(hex) {
2918
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
2919
+ if (clean.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(clean)) {
2920
+ throw new Error(`Invalid hex string: ${hex}`);
2921
+ }
2922
+ return nobleHexToBytes(clean);
2923
+ }
2924
+ function bigintToBytes32BE(x) {
2925
+ if (x < 0n) {
2926
+ throw new Error("bigint must be non-negative for balance-proof encoding");
2927
+ }
2928
+ const out = new Uint8Array(32);
2929
+ let v = x;
2930
+ for (let i = 31; i >= 0; i--) {
2931
+ out[i] = Number(v & 0xffn);
2932
+ v >>= 8n;
2933
+ }
2934
+ if (v !== 0n) {
2935
+ throw new Error("bigint exceeds 256 bits");
2936
+ }
2937
+ return out;
2938
+ }
2939
+ function concatBytes(...parts) {
2940
+ let len = 0;
2941
+ for (const p of parts) len += p.length;
2942
+ const out = new Uint8Array(len);
2943
+ let o = 0;
2944
+ for (const p of parts) {
2945
+ out.set(p, o);
2946
+ o += p.length;
2947
+ }
2948
+ return out;
2949
+ }
2950
+ function balanceProofHashEvm(channelIdBytes, cumulativeAmount, nonce, recipientBytes) {
2951
+ return keccak_256(
2952
+ concatBytes(
2953
+ channelIdBytes,
2954
+ bigintToBytes32BE(cumulativeAmount),
2955
+ bigintToBytes32BE(nonce),
2956
+ recipientBytes
2957
+ )
2958
+ );
2959
+ }
2960
+ function balanceProofHashSolana(channelId, cumulativeAmount, nonce, recipient) {
2961
+ return sha256(
2962
+ concatBytes(
2963
+ new TextEncoder().encode(channelId),
2964
+ bigintToBytes32BE(cumulativeAmount),
2965
+ bigintToBytes32BE(nonce),
2966
+ new TextEncoder().encode(recipient)
2967
+ )
2968
+ );
2969
+ }
2970
+ function minaHashToField(s) {
2971
+ const digestHex = bytesToHex(sha256(new TextEncoder().encode(s)));
2972
+ return BigInt("0x" + digestHex.slice(0, 60));
2973
+ }
2974
+ function balanceProofFieldsMina(channelId, cumulativeAmount, nonce, recipient) {
2975
+ return [
2976
+ minaHashToField(channelId),
2977
+ cumulativeAmount,
2978
+ nonce,
2979
+ minaHashToField(recipient)
2980
+ ];
2981
+ }
2982
+
2983
+ // src/settlement/base58.ts
2984
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
2985
+ function base58Encode(bytes) {
2986
+ let zeros = 0;
2987
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++) zeros++;
2988
+ let value = 0n;
2989
+ for (const byte of bytes) {
2990
+ value = value * 256n + BigInt(byte);
2991
+ }
2992
+ let result = "";
2993
+ while (value > 0n) {
2994
+ result = BASE58_ALPHABET[Number(value % 58n)] + result;
2995
+ value = value / 58n;
2996
+ }
2997
+ for (let i = 0; i < zeros; i++) {
2998
+ result = "1" + result;
2999
+ }
3000
+ return result || "1";
3001
+ }
3002
+ function base58Decode(str) {
3003
+ let zeros = 0;
3004
+ for (let i = 0; i < str.length && str[i] === "1"; i++) zeros++;
3005
+ let value = 0n;
3006
+ for (const ch of str) {
3007
+ const idx = BASE58_ALPHABET.indexOf(ch);
3008
+ if (idx === -1) throw new Error(`Invalid base58 character: ${ch}`);
3009
+ value = value * 58n + BigInt(idx);
3010
+ }
3011
+ const hex = value === 0n ? "" : value.toString(16);
3012
+ const hexPadded = hex.length % 2 ? "0" + hex : hex;
3013
+ const rawBytes = [];
3014
+ for (let i = 0; i < hexPadded.length; i += 2) {
3015
+ rawBytes.push(parseInt(hexPadded.slice(i, i + 2), 16));
3016
+ }
3017
+ const result = new Uint8Array(zeros + rawBytes.length);
3018
+ result.set(rawBytes, zeros);
3019
+ return result;
3020
+ }
3021
+
3022
+ // src/settlement/mina-key.ts
3023
+ import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
3024
+ var MINA_PRIVATE_KEY_VERSION = 90;
3025
+ function hexToMinaBase58PrivateKey(privateKey) {
3026
+ if (!/^(0x)?[0-9a-fA-F]{64}$/.test(privateKey)) {
3027
+ return privateKey;
3028
+ }
3029
+ const beScalar = hexToBytes(privateKey);
3030
+ const leScalar = Uint8Array.from(beScalar).reverse();
3031
+ const payload = concatBytes(
3032
+ Uint8Array.from([MINA_PRIVATE_KEY_VERSION, 1]),
3033
+ leScalar
3034
+ );
3035
+ const checksum = sha2562(sha2562(payload)).slice(0, 4);
3036
+ return base58Encode(concatBytes(payload, checksum));
3037
+ }
3038
+ async function deriveMinaPublicKeyBase58(privateKey) {
3039
+ let signerModule = null;
3040
+ try {
3041
+ const specifier = "mina-signer";
3042
+ signerModule = await import(
3043
+ /* @vite-ignore */
3044
+ specifier
3045
+ );
3046
+ } catch {
3047
+ return null;
3048
+ }
3049
+ if (!signerModule) return null;
3050
+ const mod = signerModule;
3051
+ const ClientCtor = mod.default ?? mod;
3052
+ const client = new ClientCtor({ network: "mainnet" });
3053
+ return client.derivePublicKey(hexToMinaBase58PrivateKey(privateKey));
3054
+ }
3055
+
2752
3056
  // src/bootstrap/BootstrapService.ts
2753
3057
  import { SimplePool as SimplePool3 } from "nostr-tools/pool";
2754
3058
  import { getPublicKey as getPublicKey3 } from "nostr-tools/pure";
@@ -4097,6 +4401,32 @@ var AttestationBootstrap = class {
4097
4401
  }
4098
4402
  };
4099
4403
 
4404
+ // src/utils/reject-code.ts
4405
+ var ILP_TO_SEMANTIC = Object.freeze({
4406
+ T00: "internal_error",
4407
+ T04: "insufficient_funds",
4408
+ F00: "invalid_request",
4409
+ // F01 ("Invalid Packet" in ILP terms — emitted by the swap handler for
4410
+ // "Invalid gift wrap" / "Invalid amount") has NO dedicated entry in the
4411
+ // connector's REJECT_CODE_MAP (accepted semantics are: insufficient_funds,
4412
+ // expired, unreachable, invalid_request, invalid_amount,
4413
+ // insufficient_destination_amount, unexpected_payment, application_error,
4414
+ // internal_error, timeout). The closest faithful reason is `invalid_request`,
4415
+ // which the connector re-encodes to wire code F00. We map F01 EXPLICITLY
4416
+ // (rather than relying on the fallback below) so the F01 -> F00 normalization
4417
+ // is intentional and test-pinned, not a silent collapse that misleads callers
4418
+ // into thinking they hit a different failure class. See issue #86.
4419
+ F01: "invalid_request",
4420
+ F02: "unreachable",
4421
+ F03: "invalid_amount",
4422
+ F04: "insufficient_destination_amount",
4423
+ F06: "unexpected_payment",
4424
+ R00: "expired"
4425
+ });
4426
+ function ilpCodeToSemantic(ilpCode) {
4427
+ return ILP_TO_SEMANTIC[ilpCode] ?? "invalid_request";
4428
+ }
4429
+
4100
4430
  // src/compose.ts
4101
4431
  function createToonNode(config) {
4102
4432
  const directIlpClient = createDirectIlpClient(config.connector, {
@@ -4156,7 +4486,7 @@ function createToonNode(config) {
4156
4486
  }
4157
4487
  const adapted = result;
4158
4488
  adapted.rejectReason = {
4159
- code: result.code,
4489
+ code: ilpCodeToSemantic(result.code),
4160
4490
  message: result.message
4161
4491
  };
4162
4492
  return adapted;
@@ -4227,6 +4557,29 @@ var CHAIN_PRESETS = {
4227
4557
  usdcAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
4228
4558
  tokenNetworkAddress: "",
4229
4559
  registryAddress: ""
4560
+ },
4561
+ // Base Sepolia (public testnet) — TOON's deployed public-testnet settlement
4562
+ // environment (source of truth: e2e/testnets.json). USDC is the deployed test
4563
+ // token the TokenNetwork was opened against (NOT Circle's native testnet USDC,
4564
+ // which the channel would not recognise). Registry + TokenNetwork are live, so
4565
+ // this preset is settlement-complete: `--network testnet|devnet` settles here.
4566
+ "base-sepolia": {
4567
+ name: "base-sepolia",
4568
+ chainId: 84532,
4569
+ rpcUrl: "https://sepolia.base.org",
4570
+ usdcAddress: "0xac80670b86db1eeb5c18c82e18a6bda98fcb4504",
4571
+ tokenNetworkAddress: "0x47616F4b9cF4dA25F74FD727Cd85E9CA0C70Ec5C",
4572
+ registryAddress: "0xb9516c6c53c016c43f3671b1e5eb6096c83ec2c7"
4573
+ },
4574
+ // Base mainnet (public). USDC is Circle's native USDC on Base.
4575
+ // TOON TokenNetwork/registry not deployed yet → settlement relay-only.
4576
+ "base-mainnet": {
4577
+ name: "base-mainnet",
4578
+ chainId: 8453,
4579
+ rpcUrl: "https://mainnet.base.org",
4580
+ usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
4581
+ tokenNetworkAddress: "",
4582
+ registryAddress: ""
4230
4583
  }
4231
4584
  };
4232
4585
  function resolveChainConfig(chain) {
@@ -4234,8 +4587,9 @@ function resolveChainConfig(chain) {
4234
4587
  const name = envChain || chain || "anvil";
4235
4588
  const preset = CHAIN_PRESETS[name];
4236
4589
  if (!preset) {
4590
+ const validNames = Object.keys(CHAIN_PRESETS).join(", ");
4237
4591
  throw new ToonError(
4238
- `Unknown chain "${name}". Valid chains: anvil, arbitrum-sepolia, arbitrum-one`,
4592
+ `Unknown chain "${name}". Valid chains: ${validNames}`,
4239
4593
  "INVALID_CHAIN"
4240
4594
  );
4241
4595
  }
@@ -4328,6 +4682,7 @@ function buildEvmProviderEntry(config, keyId) {
4328
4682
  chainId: `evm:${config.chainId}`,
4329
4683
  rpcUrl: config.rpcUrl,
4330
4684
  registryAddress: config.registryAddress,
4685
+ tokenAddress: config.usdcAddress,
4331
4686
  keyId
4332
4687
  };
4333
4688
  }
@@ -4354,6 +4709,251 @@ function buildMinaProviderEntry(config, keyId) {
4354
4709
  };
4355
4710
  }
4356
4711
 
4712
+ // src/chain/network-profile.ts
4713
+ var DEV_EVM_PRESET = "anvil";
4714
+ var DEV_SOLANA = {
4715
+ usdcMint: "6GbdrVghwNKTz9raga7y3Y4qqX5Zgg3AC4d48Kt7C59Q",
4716
+ programId: ""
4717
+ // TOON payment-channel program not deployed on the dev Solana node
4718
+ };
4719
+ var RELAY_ONLY_CHAIN = "none";
4720
+ var EVM_TIER = {
4721
+ mainnet: { primary: "base-mainnet", also: ["arbitrum-one"] },
4722
+ testnet: { primary: "base-sepolia", also: ["arbitrum-sepolia"] },
4723
+ // EVM has no public devnet → the public Sepolia testnets serve the devnet tier.
4724
+ devnet: { primary: "base-sepolia", also: ["arbitrum-sepolia"] }
4725
+ };
4726
+ var SOLANA_DEPLOYED_DEVNET = {
4727
+ rpcUrl: "https://api.devnet.solana.com",
4728
+ cluster: "devnet",
4729
+ usdcMint: "9FtYCXjNiGDn17jSGvZuB5P4dZAKgVxUsDiQpLc8rbWy",
4730
+ programId: "EdJxYPDxGvaJuu57DSUptf4soLv8enpdyQJJhHDLiydG"
4731
+ };
4732
+ var SOLANA_TIER = {
4733
+ mainnet: {
4734
+ rpcUrl: "https://api.mainnet-beta.solana.com",
4735
+ cluster: "mainnet-beta",
4736
+ usdcMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
4737
+ programId: ""
4738
+ // TOON payment-channel program not deployed on mainnet
4739
+ },
4740
+ testnet: SOLANA_DEPLOYED_DEVNET,
4741
+ devnet: SOLANA_DEPLOYED_DEVNET
4742
+ };
4743
+ var MINA_DEPLOYED_DEVNET = {
4744
+ graphqlUrl: "https://api.minascan.io/node/devnet/v1/graphql",
4745
+ network: "devnet",
4746
+ // Reconciled with e2e/testnets.json (#205): the previous address
4747
+ // (B62qjFgX…) is a bare, unfunded zkApp on-chain (balance 0, channel
4748
+ // nonceField 0) — usable for claim issuance only. This is the funded,
4749
+ // on-chain-settling PaymentChannel zkApp (balance ~4 MINA, nonceField 21
4750
+ // — the on-chain settle proven in #217), same VK hash 21482326…
4751
+ zkAppAddress: "B62qrH1As4odHiNyKpTZMHaM6tRs6gi5DJ53efZKQBtbaR5CUctbDs6"
4752
+ };
4753
+ var MINA_TIER = {
4754
+ mainnet: {
4755
+ graphqlUrl: "https://api.minascan.io/node/mainnet/v1/graphql",
4756
+ network: "mainnet",
4757
+ zkAppAddress: ""
4758
+ // TOON payment-channel zkApp not deployed on mainnet
4759
+ },
4760
+ testnet: MINA_DEPLOYED_DEVNET,
4761
+ devnet: MINA_DEPLOYED_DEVNET
4762
+ };
4763
+ function evmSettlementComplete(p) {
4764
+ return p.registryAddress !== "" && p.tokenNetworkAddress !== "";
4765
+ }
4766
+ function resolveNetworkProfile(network, opts = {}) {
4767
+ if (network === "custom") {
4768
+ return resolveCustom(
4769
+ opts.customProviders ?? [],
4770
+ opts.endpoints ?? {},
4771
+ opts.keyId
4772
+ );
4773
+ }
4774
+ const tier = network;
4775
+ const chainProviders = [];
4776
+ const status = {
4777
+ evm: "unconfigured",
4778
+ solana: "unconfigured",
4779
+ mina: "unconfigured"
4780
+ };
4781
+ const primary = CHAIN_PRESETS[EVM_TIER[tier].primary];
4782
+ const nodeEnv = {
4783
+ EVM_CHAIN: primary.name,
4784
+ EVM_RPC_URL: primary.rpcUrl,
4785
+ EVM_CHAIN_ID: String(primary.chainId),
4786
+ EVM_USDC_ADDRESS: primary.usdcAddress
4787
+ };
4788
+ if (opts.keyId) {
4789
+ for (const name of [EVM_TIER[tier].primary, ...EVM_TIER[tier].also]) {
4790
+ const preset = CHAIN_PRESETS[name];
4791
+ if (evmSettlementComplete(preset)) {
4792
+ chainProviders.push(buildEvmProviderEntry(preset, opts.keyId));
4793
+ status.evm = "configured";
4794
+ }
4795
+ }
4796
+ }
4797
+ const sol = SOLANA_TIER[tier];
4798
+ nodeEnv.SOLANA_RPC_URL = sol.rpcUrl;
4799
+ if (sol.usdcMint) nodeEnv.SOLANA_USDC_MINT = sol.usdcMint;
4800
+ if (opts.keyId && sol.programId) {
4801
+ chainProviders.push(
4802
+ buildSolanaProviderEntry(
4803
+ {
4804
+ name: `solana-${sol.cluster}`,
4805
+ chainType: "solana",
4806
+ rpcUrl: sol.rpcUrl,
4807
+ programId: sol.programId,
4808
+ cluster: sol.cluster,
4809
+ ...sol.usdcMint && { tokenMint: sol.usdcMint }
4810
+ },
4811
+ opts.keyId
4812
+ )
4813
+ );
4814
+ status.solana = "configured";
4815
+ }
4816
+ const mina = MINA_TIER[tier];
4817
+ if (opts.keyId && mina.zkAppAddress) {
4818
+ chainProviders.push(
4819
+ buildMinaProviderEntry(
4820
+ {
4821
+ name: `mina-${mina.network}`,
4822
+ chainType: "mina",
4823
+ graphqlUrl: mina.graphqlUrl,
4824
+ zkAppAddress: mina.zkAppAddress,
4825
+ network: mina.network
4826
+ },
4827
+ opts.keyId
4828
+ )
4829
+ );
4830
+ status.mina = "configured";
4831
+ }
4832
+ return { network, chainProviders, nodeEnv, status };
4833
+ }
4834
+ function evmClientId(preset) {
4835
+ const family = preset.name.split("-")[0] ?? preset.name;
4836
+ return `evm:${family}:${preset.chainId}`;
4837
+ }
4838
+ function resolveClientNetwork(network) {
4839
+ const supportedChains = [];
4840
+ const chainRpcUrls = {};
4841
+ const preferredTokens = {};
4842
+ const tokenNetworks = {};
4843
+ const status = {
4844
+ evm: "unconfigured",
4845
+ solana: "unconfigured",
4846
+ mina: "unconfigured"
4847
+ };
4848
+ const evm = CHAIN_PRESETS[EVM_TIER[network].primary];
4849
+ const evmId = evmClientId(evm);
4850
+ supportedChains.push(evmId);
4851
+ chainRpcUrls[evmId] = evm.rpcUrl;
4852
+ if (evm.usdcAddress) preferredTokens[evmId] = evm.usdcAddress;
4853
+ if (evmSettlementComplete(evm)) {
4854
+ tokenNetworks[evmId] = evm.tokenNetworkAddress;
4855
+ status.evm = "configured";
4856
+ }
4857
+ const sol = SOLANA_TIER[network];
4858
+ const solId = `solana:${sol.cluster}`;
4859
+ supportedChains.push(solId);
4860
+ chainRpcUrls[solId] = sol.rpcUrl;
4861
+ if (sol.usdcMint) preferredTokens[solId] = sol.usdcMint;
4862
+ let solanaChannel;
4863
+ if (sol.programId) {
4864
+ solanaChannel = {
4865
+ rpcUrl: sol.rpcUrl,
4866
+ programId: sol.programId,
4867
+ ...sol.usdcMint && { tokenMint: sol.usdcMint }
4868
+ };
4869
+ status.solana = "configured";
4870
+ }
4871
+ const mina = MINA_TIER[network];
4872
+ const minaId = `mina:${mina.network}`;
4873
+ supportedChains.push(minaId);
4874
+ chainRpcUrls[minaId] = mina.graphqlUrl;
4875
+ let minaChannel;
4876
+ if (mina.zkAppAddress) {
4877
+ minaChannel = {
4878
+ graphqlUrl: mina.graphqlUrl,
4879
+ zkAppAddress: mina.zkAppAddress,
4880
+ networkId: mina.network === "mainnet" ? "mainnet" : "devnet"
4881
+ };
4882
+ status.mina = "configured";
4883
+ }
4884
+ return {
4885
+ supportedChains,
4886
+ chainRpcUrls,
4887
+ preferredTokens,
4888
+ tokenNetworks,
4889
+ ...solanaChannel && { solanaChannel },
4890
+ ...minaChannel && { minaChannel },
4891
+ status
4892
+ };
4893
+ }
4894
+ function resolveCustom(providers, endpoints, keyId) {
4895
+ if (providers.length === 0 && (endpoints.evmUrl || endpoints.solUrl)) {
4896
+ return resolveCustomEndpoints(endpoints, keyId);
4897
+ }
4898
+ const status = {
4899
+ evm: "unconfigured",
4900
+ solana: "unconfigured",
4901
+ mina: "unconfigured"
4902
+ };
4903
+ const nodeEnv = {};
4904
+ const evm = providers.find((p) => p.chainType === "evm");
4905
+ if (evm && evm.chainType === "evm") {
4906
+ nodeEnv.EVM_RPC_URL = evm.rpcUrl;
4907
+ nodeEnv.EVM_CHAIN_ID = evm.chainId.replace(/^evm:/, "");
4908
+ nodeEnv.EVM_USDC_ADDRESS = evm.tokenAddress;
4909
+ nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;
4910
+ if (evm.registryAddress) status.evm = "configured";
4911
+ } else {
4912
+ nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;
4913
+ }
4914
+ const sol = providers.find((p) => p.chainType === "solana");
4915
+ if (sol && sol.chainType === "solana") {
4916
+ nodeEnv.SOLANA_RPC_URL = sol.rpcUrl;
4917
+ if (sol.tokenMint) nodeEnv.SOLANA_USDC_MINT = sol.tokenMint;
4918
+ if (sol.programId) status.solana = "configured";
4919
+ }
4920
+ const mina = providers.find((p) => p.chainType === "mina");
4921
+ if (mina && mina.chainType === "mina" && mina.zkAppAddress) {
4922
+ status.mina = "configured";
4923
+ }
4924
+ return { network: "custom", chainProviders: providers, nodeEnv, status };
4925
+ }
4926
+ function resolveCustomEndpoints(endpoints, keyId) {
4927
+ const evm = CHAIN_PRESETS[DEV_EVM_PRESET];
4928
+ const status = {
4929
+ evm: "unconfigured",
4930
+ solana: "unconfigured",
4931
+ mina: "unconfigured"
4932
+ };
4933
+ const chainProviders = [];
4934
+ const nodeEnv = {
4935
+ EVM_CHAIN: DEV_EVM_PRESET,
4936
+ EVM_CHAIN_ID: String(evm.chainId),
4937
+ EVM_USDC_ADDRESS: evm.usdcAddress
4938
+ };
4939
+ if (endpoints.evmUrl) {
4940
+ nodeEnv.EVM_RPC_URL = endpoints.evmUrl;
4941
+ status.evm = "configured";
4942
+ if (keyId) {
4943
+ chainProviders.push(
4944
+ buildEvmProviderEntry({ ...evm, rpcUrl: endpoints.evmUrl }, keyId)
4945
+ );
4946
+ }
4947
+ } else {
4948
+ nodeEnv.EVM_CHAIN = RELAY_ONLY_CHAIN;
4949
+ }
4950
+ if (endpoints.solUrl) {
4951
+ nodeEnv.SOLANA_RPC_URL = endpoints.solUrl;
4952
+ nodeEnv.SOLANA_USDC_MINT = DEV_SOLANA.usdcMint;
4953
+ }
4954
+ return { network: "custom", chainProviders, nodeEnv, status };
4955
+ }
4956
+
4357
4957
  // src/x402/build-ilp-prepare.ts
4358
4958
  function buildIlpPrepare(params) {
4359
4959
  return {
@@ -4500,8 +5100,8 @@ var NixBuilder = class {
4500
5100
  );
4501
5101
  }
4502
5102
  const imageData = await readFile(imagePath);
4503
- const sha256 = createHash("sha256").update(imageData).digest("hex");
4504
- const imageHash = `sha256:${sha256}`;
5103
+ const sha2563 = createHash("sha256").update(imageData).digest("hex");
5104
+ const imageHash = `sha256:${sha2563}`;
4505
5105
  const pcr0 = createHash("sha384").update(imageData).digest("hex");
4506
5106
  const KERNEL_REGION_SIZE = 1024 * 1024;
4507
5107
  const kernelRegion = imageData.subarray(
@@ -4714,9 +5314,11 @@ export {
4714
5314
  BootstrapError,
4715
5315
  BootstrapService,
4716
5316
  CHAIN_PRESETS,
5317
+ EXPIRATION_TAG,
4717
5318
  GenesisPeerLoader,
4718
5319
  ILP_PEER_INFO_KIND,
4719
5320
  ILP_ROOT_PREFIX,
5321
+ ILP_TO_SEMANTIC,
4720
5322
  IMAGE_GENERATION_KIND,
4721
5323
  InvalidEventError,
4722
5324
  JOB_FEEDBACK_KIND,
@@ -4736,6 +5338,7 @@ export {
4736
5338
  PREFIX_GRANT_KIND,
4737
5339
  PcrReproducibilityError,
4738
5340
  PeerDiscoveryError,
5341
+ RELAY_ONLY_CHAIN,
4739
5342
  ReputationScoreCalculator,
4740
5343
  SEED_RELAY_LIST_KIND,
4741
5344
  SERVICE_DISCOVERY_KIND,
@@ -4757,6 +5360,12 @@ export {
4757
5360
  WORKFLOW_CHAIN_KIND,
4758
5361
  analyzeDockerfileForNonDeterminism,
4759
5362
  assignAddressFromHandshake,
5363
+ balanceProofFieldsMina,
5364
+ balanceProofHashEvm,
5365
+ balanceProofHashSolana,
5366
+ base58Decode,
5367
+ base58Encode,
5368
+ bigintToBytes32BE,
4760
5369
  buildAttestationEvent,
4761
5370
  buildBlobStorageRequest,
4762
5371
  buildEip712Domain,
@@ -4780,6 +5389,7 @@ export {
4780
5389
  buildWotDeclarationEvent,
4781
5390
  calculateRouteAmount,
4782
5391
  checkAddressCollision,
5392
+ concatBytes,
4783
5393
  createAgentRuntimeClient,
4784
5394
  createDirectChannelClient,
4785
5395
  createDirectConnectorAdmin,
@@ -4796,13 +5406,20 @@ export {
4796
5406
  decodeEventFromToon,
4797
5407
  deriveChildAddress,
4798
5408
  deriveFromKmsSeed,
5409
+ deriveMinaPublicKeyBase58,
4799
5410
  encodeEventToToon,
4800
5411
  encodeEventToToonString,
4801
5412
  extractPrefixFromHandshake,
5413
+ getEventExpiration,
4802
5414
  hasMinReputation,
4803
5415
  hasRequireAttestation,
5416
+ hexToBytes,
5417
+ hexToMinaBase58PrivateKey,
5418
+ ilpCodeToSemantic,
5419
+ isEventExpired,
4804
5420
  isGenesisNode,
4805
5421
  isValidIlpAddressStructure,
5422
+ minaHashToField,
4806
5423
  negotiateSettlementChain,
4807
5424
  parseAttestation,
4808
5425
  parseBlobStorageRequest,
@@ -4822,7 +5439,9 @@ export {
4822
5439
  publishSeedRelayEntry,
4823
5440
  readDockerfileNix,
4824
5441
  resolveChainConfig,
5442
+ resolveClientNetwork,
4825
5443
  resolveMinaChainConfig,
5444
+ resolveNetworkProfile,
4826
5445
  resolveRouteFees,
4827
5446
  resolveSolanaChainConfig,
4828
5447
  resolveTokenForChain,