@toon-protocol/core 1.2.0 → 1.3.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
@@ -22,6 +22,257 @@ var TEXT_GENERATION_KIND = 5100;
22
22
  var IMAGE_GENERATION_KIND = 5200;
23
23
  var TEXT_TO_SPEECH_KIND = 5300;
24
24
  var TRANSLATION_KIND = 5302;
25
+ var WORKFLOW_CHAIN_KIND = 10040;
26
+ var JOB_REVIEW_KIND = 31117;
27
+ var WEB_OF_TRUST_KIND = 30382;
28
+ var PREFIX_CLAIM_KIND = 10034;
29
+ var PREFIX_GRANT_KIND = 10037;
30
+ var BLOB_STORAGE_REQUEST_KIND = 5094;
31
+ var BLOB_STORAGE_RESULT_KIND = 6094;
32
+ var ILP_ROOT_PREFIX = "g.toon";
33
+
34
+ // src/address/ilp-address-validation.ts
35
+ var ILP_SEGMENT_PATTERN = /^[a-z0-9-]+$/;
36
+ var MAX_ILP_ADDRESS_LENGTH = 1023;
37
+ function isValidIlpAddressStructure(address) {
38
+ if (!address) return false;
39
+ if (address.length > MAX_ILP_ADDRESS_LENGTH) return false;
40
+ const segments = address.split(".");
41
+ for (const segment of segments) {
42
+ if (segment.length === 0) return false;
43
+ if (!ILP_SEGMENT_PATTERN.test(segment)) return false;
44
+ }
45
+ return true;
46
+ }
47
+ function validateIlpAddress(address) {
48
+ if (address.length > MAX_ILP_ADDRESS_LENGTH) {
49
+ throw new ToonError(
50
+ `Invalid ILP address: exceeds maximum length of ${MAX_ILP_ADDRESS_LENGTH}`,
51
+ "ADDRESS_INVALID_PREFIX"
52
+ );
53
+ }
54
+ const segments = address.split(".");
55
+ for (const segment of segments) {
56
+ if (segment.length === 0) {
57
+ throw new ToonError(
58
+ `Invalid ILP address: empty segment in "${address}"`,
59
+ "ADDRESS_INVALID_PREFIX"
60
+ );
61
+ }
62
+ if (!ILP_SEGMENT_PATTERN.test(segment)) {
63
+ throw new ToonError(
64
+ `Invalid ILP address: segment "${segment}" contains invalid characters`,
65
+ "ADDRESS_INVALID_PREFIX"
66
+ );
67
+ }
68
+ }
69
+ }
70
+
71
+ // src/address/derive-child-address.ts
72
+ var HEX_PATTERN = /^[0-9a-fA-F]+$/;
73
+ var PUBKEY_TRUNCATION_LENGTH = 8;
74
+ var MAX_PUBKEY_LENGTH = 128;
75
+ var MAX_ILP_ADDRESS_LENGTH2 = 1023;
76
+ function deriveChildAddress(parentPrefix, childPubkey) {
77
+ if (!parentPrefix) {
78
+ throw new ToonError(
79
+ "Parent prefix must not be empty",
80
+ "ADDRESS_INVALID_PREFIX"
81
+ );
82
+ }
83
+ validateIlpAddress(parentPrefix);
84
+ if (childPubkey.length < PUBKEY_TRUNCATION_LENGTH) {
85
+ throw new ToonError(
86
+ `Invalid pubkey: must be at least ${PUBKEY_TRUNCATION_LENGTH} hex characters, got ${childPubkey.length}`,
87
+ "ADDRESS_INVALID_PUBKEY"
88
+ );
89
+ }
90
+ if (childPubkey.length > MAX_PUBKEY_LENGTH) {
91
+ throw new ToonError(
92
+ `Invalid pubkey: exceeds maximum length of ${MAX_PUBKEY_LENGTH} characters`,
93
+ "ADDRESS_INVALID_PUBKEY"
94
+ );
95
+ }
96
+ if (!HEX_PATTERN.test(childPubkey)) {
97
+ throw new ToonError(
98
+ `Invalid pubkey: contains non-hex characters`,
99
+ "ADDRESS_INVALID_PUBKEY"
100
+ );
101
+ }
102
+ const childSegment = childPubkey.slice(0, PUBKEY_TRUNCATION_LENGTH).toLowerCase();
103
+ const derivedAddress = `${parentPrefix}.${childSegment}`;
104
+ if (derivedAddress.length > MAX_ILP_ADDRESS_LENGTH2) {
105
+ throw new ToonError(
106
+ `Derived ILP address exceeds maximum length of ${MAX_ILP_ADDRESS_LENGTH2}`,
107
+ "ADDRESS_TOO_LONG"
108
+ );
109
+ }
110
+ return derivedAddress;
111
+ }
112
+
113
+ // src/address/btp-prefix-exchange.ts
114
+ var MAX_PREFIX_LENGTH = 1023;
115
+ function extractPrefixFromHandshake(handshakeData) {
116
+ const prefix = handshakeData["prefix"];
117
+ if (prefix === void 0 || prefix === null || prefix === "") {
118
+ throw new ToonError(
119
+ "BTP handshake response missing required prefix field",
120
+ "ADDRESS_MISSING_PREFIX"
121
+ );
122
+ }
123
+ if (typeof prefix !== "string") {
124
+ throw new ToonError(
125
+ "BTP handshake response missing required prefix field",
126
+ "ADDRESS_MISSING_PREFIX"
127
+ );
128
+ }
129
+ if (prefix.length > MAX_PREFIX_LENGTH) {
130
+ throw new ToonError(
131
+ `BTP handshake prefix exceeds maximum length of ${MAX_PREFIX_LENGTH}`,
132
+ "ADDRESS_INVALID_PREFIX"
133
+ );
134
+ }
135
+ if (!isValidIlpAddressStructure(prefix)) {
136
+ throw new ToonError(
137
+ `BTP handshake prefix is not a valid ILP address: "${prefix}"`,
138
+ "ADDRESS_INVALID_PREFIX"
139
+ );
140
+ }
141
+ return prefix;
142
+ }
143
+ function buildPrefixHandshakeData(ownIlpAddress) {
144
+ if (ownIlpAddress.length > MAX_PREFIX_LENGTH) {
145
+ throw new ToonError(
146
+ `Cannot build handshake data: address exceeds maximum length of ${MAX_PREFIX_LENGTH}`,
147
+ "ADDRESS_INVALID_PREFIX"
148
+ );
149
+ }
150
+ if (!isValidIlpAddressStructure(ownIlpAddress)) {
151
+ throw new ToonError(
152
+ `Cannot build handshake data: "${ownIlpAddress}" is not a valid ILP address`,
153
+ "ADDRESS_INVALID_PREFIX"
154
+ );
155
+ }
156
+ return { prefix: ownIlpAddress };
157
+ }
158
+ function validatePrefixConsistency(handshakePrefix, advertisedPrefix) {
159
+ if (advertisedPrefix === void 0) {
160
+ return;
161
+ }
162
+ if (handshakePrefix !== advertisedPrefix) {
163
+ throw new ToonError(
164
+ `BTP handshake prefix "${handshakePrefix}" does not match upstream kind:10032 advertised address "${advertisedPrefix}"`,
165
+ "ADDRESS_PREFIX_MISMATCH"
166
+ );
167
+ }
168
+ }
169
+ function checkAddressCollision(derivedAddress, knownPeerAddresses) {
170
+ if (knownPeerAddresses.includes(derivedAddress)) {
171
+ throw new ToonError(
172
+ `Derived ILP address "${derivedAddress}" collides with an existing peer address`,
173
+ "ADDRESS_COLLISION"
174
+ );
175
+ }
176
+ }
177
+
178
+ // src/address/address-assignment.ts
179
+ function assignAddressFromHandshake(handshakeData, ownPubkey) {
180
+ const prefix = extractPrefixFromHandshake(handshakeData);
181
+ return deriveChildAddress(prefix, ownPubkey);
182
+ }
183
+ function isGenesisNode(config) {
184
+ return config.ilpAddress === ILP_ROOT_PREFIX;
185
+ }
186
+
187
+ // src/address/address-registry.ts
188
+ var AddressRegistry = class {
189
+ prefixToAddress = /* @__PURE__ */ new Map();
190
+ /**
191
+ * Registers a new upstream prefix -> derived address mapping.
192
+ *
193
+ * @param upstreamPrefix - The upstream peer's ILP address prefix
194
+ * @param derivedAddress - The derived ILP address for this node under that prefix
195
+ */
196
+ addAddress(upstreamPrefix, derivedAddress) {
197
+ this.prefixToAddress.set(upstreamPrefix, derivedAddress);
198
+ }
199
+ /**
200
+ * Removes the mapping for the given upstream prefix.
201
+ *
202
+ * @param upstreamPrefix - The upstream prefix to remove
203
+ * @returns The removed derived address, or `undefined` if the prefix was not found
204
+ */
205
+ removeAddress(upstreamPrefix) {
206
+ const address = this.prefixToAddress.get(upstreamPrefix);
207
+ if (address !== void 0) {
208
+ this.prefixToAddress.delete(upstreamPrefix);
209
+ }
210
+ return address;
211
+ }
212
+ /**
213
+ * Returns all derived addresses in insertion order.
214
+ */
215
+ getAddresses() {
216
+ return [...this.prefixToAddress.values()];
217
+ }
218
+ /**
219
+ * Returns `true` if the given upstream prefix is registered.
220
+ */
221
+ hasPrefix(upstreamPrefix) {
222
+ return this.prefixToAddress.has(upstreamPrefix);
223
+ }
224
+ /**
225
+ * Returns the number of registered addresses.
226
+ */
227
+ get size() {
228
+ return this.prefixToAddress.size;
229
+ }
230
+ /**
231
+ * Returns the primary (first inserted) address.
232
+ *
233
+ * @returns The first address, or `undefined` if the registry is empty
234
+ */
235
+ getPrimaryAddress() {
236
+ const first = this.prefixToAddress.values().next();
237
+ return first.done ? void 0 : first.value;
238
+ }
239
+ };
240
+
241
+ // src/address/prefix-validation.ts
242
+ var RESERVED_WORDS = /* @__PURE__ */ new Set(["toon", "ilp", "local", "peer", "test"]);
243
+ var PREFIX_PATTERN = /^[a-z0-9]+$/;
244
+ var MIN_LENGTH = 2;
245
+ var MAX_LENGTH = 16;
246
+ function validatePrefix(prefix) {
247
+ if (typeof prefix !== "string" || prefix.length === 0) {
248
+ return { valid: false, reason: "Prefix must be a non-empty string" };
249
+ }
250
+ if (prefix.length < MIN_LENGTH) {
251
+ return {
252
+ valid: false,
253
+ reason: `Prefix must have a minimum of ${MIN_LENGTH} characters, got ${prefix.length}`
254
+ };
255
+ }
256
+ if (prefix.length > MAX_LENGTH) {
257
+ return {
258
+ valid: false,
259
+ reason: `Prefix must have a maximum of ${MAX_LENGTH} characters, got ${prefix.length}`
260
+ };
261
+ }
262
+ if (!PREFIX_PATTERN.test(prefix)) {
263
+ return {
264
+ valid: false,
265
+ reason: "Prefix must contain only lowercase alphanumeric characters [a-z0-9]"
266
+ };
267
+ }
268
+ if (RESERVED_WORDS.has(prefix)) {
269
+ return {
270
+ valid: false,
271
+ reason: `Prefix "${prefix}" is a reserved word`
272
+ };
273
+ }
274
+ return { valid: true };
275
+ }
25
276
 
26
277
  // src/events/parsers.ts
27
278
  function validateChainId(chainId) {
@@ -57,7 +308,8 @@ function parseIlpPeerInfo(event) {
57
308
  blsHttpEndpoint,
58
309
  settlementEngine,
59
310
  assetCode,
60
- assetScale
311
+ assetScale,
312
+ ilpAddresses: rawIlpAddresses
61
313
  } = parsed;
62
314
  if (typeof ilpAddress !== "string" || ilpAddress.length === 0) {
63
315
  throw new InvalidEventError(
@@ -142,6 +394,52 @@ function parseIlpPeerInfo(event) {
142
394
  throw new InvalidEventError("tokenNetworks must be an object");
143
395
  }
144
396
  }
397
+ const { feePerByte: rawFeePerByte } = parsed;
398
+ let feePerByte;
399
+ if (rawFeePerByte === void 0) {
400
+ feePerByte = "0";
401
+ } else if (typeof rawFeePerByte !== "string" || !/^\d+$/.test(rawFeePerByte)) {
402
+ throw new InvalidEventError(
403
+ `Invalid feePerByte: "${String(rawFeePerByte)}" must be a non-negative integer string`
404
+ );
405
+ } else {
406
+ feePerByte = rawFeePerByte;
407
+ }
408
+ const { prefixPricing: rawPrefixPricing } = parsed;
409
+ let prefixPricing;
410
+ if (rawPrefixPricing !== void 0) {
411
+ if (!isObject(rawPrefixPricing)) {
412
+ throw new InvalidEventError("prefixPricing must be an object");
413
+ }
414
+ const { basePrice } = rawPrefixPricing;
415
+ if (typeof basePrice !== "string" || !/^\d+$/.test(basePrice)) {
416
+ throw new InvalidEventError(
417
+ `Invalid prefixPricing.basePrice: "${String(basePrice)}" must be a non-negative integer string`
418
+ );
419
+ }
420
+ prefixPricing = { basePrice };
421
+ }
422
+ let ilpAddresses;
423
+ if (rawIlpAddresses !== void 0) {
424
+ if (!Array.isArray(rawIlpAddresses)) {
425
+ throw new InvalidEventError("ilpAddresses must be an array");
426
+ }
427
+ for (const addr of rawIlpAddresses) {
428
+ if (typeof addr !== "string" || addr.length === 0) {
429
+ throw new InvalidEventError(
430
+ "ilpAddresses elements must be non-empty strings"
431
+ );
432
+ }
433
+ if (!isValidIlpAddressStructure(addr)) {
434
+ throw new InvalidEventError(
435
+ `Invalid ILP address in ilpAddresses: "${addr}"`
436
+ );
437
+ }
438
+ }
439
+ ilpAddresses = rawIlpAddresses;
440
+ } else {
441
+ ilpAddresses = [ilpAddress];
442
+ }
145
443
  return {
146
444
  ilpAddress,
147
445
  btpEndpoint,
@@ -156,17 +454,51 @@ function parseIlpPeerInfo(event) {
156
454
  },
157
455
  ...tokenNetworks !== void 0 && {
158
456
  tokenNetworks
159
- }
457
+ },
458
+ ilpAddresses,
459
+ feePerByte,
460
+ ...prefixPricing !== void 0 && { prefixPricing }
160
461
  };
161
462
  }
162
463
 
163
464
  // src/events/builders.ts
164
465
  import { finalizeEvent } from "nostr-tools/pure";
165
466
  function buildIlpPeerInfoEvent(info, secretKey) {
467
+ if (info.feePerByte !== void 0) {
468
+ if (typeof info.feePerByte !== "string" || !/^\d+$/.test(info.feePerByte)) {
469
+ throw new ToonError(
470
+ `Invalid feePerByte: "${String(info.feePerByte)}" must be a non-negative integer string`,
471
+ "INVALID_FEE"
472
+ );
473
+ }
474
+ }
475
+ let effectiveInfo = info;
476
+ if (info.ilpAddresses !== void 0) {
477
+ const addresses = info.ilpAddresses;
478
+ if (addresses.length === 0) {
479
+ throw new ToonError(
480
+ "ilpAddresses must be a non-empty array: a node must have at least one address",
481
+ "ADDRESS_EMPTY_ADDRESSES"
482
+ );
483
+ }
484
+ for (const addr of addresses) {
485
+ if (!isValidIlpAddressStructure(addr)) {
486
+ throw new ToonError(
487
+ `Invalid ILP address in ilpAddresses: "${addr}"`,
488
+ "ADDRESS_INVALID_PREFIX"
489
+ );
490
+ }
491
+ }
492
+ const primaryAddress = addresses[0];
493
+ effectiveInfo = {
494
+ ...info,
495
+ ilpAddress: primaryAddress
496
+ };
497
+ }
166
498
  return finalizeEvent(
167
499
  {
168
500
  kind: ILP_PEER_INFO_KIND,
169
- content: JSON.stringify(info),
501
+ content: JSON.stringify(effectiveInfo),
170
502
  tags: [],
171
503
  created_at: Math.floor(Date.now() / 1e3)
172
504
  },
@@ -376,6 +708,32 @@ function parseServiceDiscovery(event) {
376
708
  return null;
377
709
  skillResult.attestation = attestation;
378
710
  }
711
+ const reputation = skillRecord["reputation"];
712
+ if (reputation !== void 0) {
713
+ if (typeof reputation !== "object" || reputation === null || Array.isArray(reputation))
714
+ return null;
715
+ const repRecord = reputation;
716
+ const repScore = repRecord["score"];
717
+ if (typeof repScore !== "number" || !isFinite(repScore)) return null;
718
+ const signals = repRecord["signals"];
719
+ if (typeof signals !== "object" || signals === null || Array.isArray(signals))
720
+ return null;
721
+ const sigRecord = signals;
722
+ const trustedBy = sigRecord["trustedBy"];
723
+ if (typeof trustedBy !== "number" || !isFinite(trustedBy)) return null;
724
+ const channelVolumeUsdc = sigRecord["channelVolumeUsdc"];
725
+ if (typeof channelVolumeUsdc !== "number" || !isFinite(channelVolumeUsdc))
726
+ return null;
727
+ const jobsCompleted = sigRecord["jobsCompleted"];
728
+ if (typeof jobsCompleted !== "number" || !isFinite(jobsCompleted))
729
+ return null;
730
+ const avgRating = sigRecord["avgRating"];
731
+ if (typeof avgRating !== "number" || !isFinite(avgRating)) return null;
732
+ skillResult.reputation = {
733
+ score: repScore,
734
+ signals: { trustedBy, channelVolumeUsdc, jobsCompleted, avgRating }
735
+ };
736
+ }
379
737
  result.skill = skillResult;
380
738
  }
381
739
  return result;
@@ -577,11 +935,22 @@ function buildJobResultEvent(params, secretKey) {
577
935
  "DVM_MISSING_CONTENT"
578
936
  );
579
937
  }
938
+ if (params.attestationEventId !== void 0) {
939
+ if (!HEX_64_REGEX.test(params.attestationEventId)) {
940
+ throw new ToonError(
941
+ "Job result attestationEventId must be a 64-character lowercase hex string",
942
+ "DVM_INVALID_ATTESTATION_EVENT_ID"
943
+ );
944
+ }
945
+ }
580
946
  const tags = [
581
947
  ["e", params.requestEventId],
582
948
  ["p", params.customerPubkey],
583
949
  ["amount", params.amount, "usdc"]
584
950
  ];
951
+ if (params.attestationEventId !== void 0) {
952
+ tags.push(["attestation", params.attestationEventId]);
953
+ }
585
954
  return finalizeEvent5(
586
955
  {
587
956
  kind: params.kind,
@@ -703,50 +1072,842 @@ function parseJobResult(event) {
703
1072
  return null;
704
1073
  const pTag = event.tags.find((t) => t[0] === "p");
705
1074
  if (!pTag) return null;
706
- const customerPubkey = pTag[1];
707
- if (customerPubkey === void 0 || !HEX_64_REGEX.test(customerPubkey))
1075
+ const customerPubkey = pTag[1];
1076
+ if (customerPubkey === void 0 || !HEX_64_REGEX.test(customerPubkey))
1077
+ return null;
1078
+ const amountTag = event.tags.find((t) => t[0] === "amount");
1079
+ if (!amountTag) return null;
1080
+ const amount = amountTag[1];
1081
+ if (amount === void 0 || amount === "") return null;
1082
+ if (!/^\d+$/.test(amount)) return null;
1083
+ const attestationTag = event.tags.find(
1084
+ (t) => t[0] === "attestation"
1085
+ );
1086
+ const attestationEventId = attestationTag?.[1];
1087
+ const result = {
1088
+ kind: event.kind,
1089
+ requestEventId,
1090
+ customerPubkey,
1091
+ amount,
1092
+ content: event.content
1093
+ };
1094
+ if (attestationEventId !== void 0 && HEX_64_REGEX.test(attestationEventId)) {
1095
+ result.attestationEventId = attestationEventId;
1096
+ }
1097
+ return result;
1098
+ }
1099
+ function parseJobFeedback(event) {
1100
+ if (event.kind !== JOB_FEEDBACK_KIND) {
1101
+ return null;
1102
+ }
1103
+ const eTag = event.tags.find((t) => t[0] === "e");
1104
+ if (!eTag) return null;
1105
+ const requestEventId = eTag[1];
1106
+ if (requestEventId === void 0 || !HEX_64_REGEX.test(requestEventId))
1107
+ return null;
1108
+ const pTag = event.tags.find((t) => t[0] === "p");
1109
+ if (!pTag) return null;
1110
+ const customerPubkey = pTag[1];
1111
+ if (customerPubkey === void 0 || !HEX_64_REGEX.test(customerPubkey))
1112
+ return null;
1113
+ const statusTag = event.tags.find((t) => t[0] === "status");
1114
+ if (!statusTag) return null;
1115
+ const statusValue = statusTag[1];
1116
+ if (statusValue === void 0) return null;
1117
+ if (!VALID_STATUSES.has(statusValue)) {
1118
+ return null;
1119
+ }
1120
+ return {
1121
+ requestEventId,
1122
+ customerPubkey,
1123
+ status: statusValue,
1124
+ content: event.content
1125
+ };
1126
+ }
1127
+
1128
+ // src/events/workflow.ts
1129
+ import { finalizeEvent as finalizeEvent6 } from "nostr-tools/pure";
1130
+ var HEX_64_REGEX2 = /^[0-9a-f]{64}$/;
1131
+ function buildWorkflowDefinitionEvent(params, secretKey) {
1132
+ if (!params.steps || params.steps.length === 0) {
1133
+ throw new ToonError(
1134
+ "Workflow definition must have at least one step",
1135
+ "DVM_WORKFLOW_INVALID_STEPS"
1136
+ );
1137
+ }
1138
+ for (const step of params.steps) {
1139
+ if (step.kind < 5e3 || step.kind > 5999) {
1140
+ throw new ToonError(
1141
+ `Workflow step kind must be in range 5000-5999, got ${step.kind}`,
1142
+ "DVM_WORKFLOW_INVALID_STEPS"
1143
+ );
1144
+ }
1145
+ if (typeof step.description !== "string" || step.description === "") {
1146
+ throw new ToonError(
1147
+ "Workflow step description must be a non-empty string",
1148
+ "DVM_WORKFLOW_INVALID_STEPS"
1149
+ );
1150
+ }
1151
+ if (step.targetProvider !== void 0 && !HEX_64_REGEX2.test(step.targetProvider)) {
1152
+ throw new ToonError(
1153
+ "Workflow step targetProvider must be a 64-character lowercase hex string",
1154
+ "DVM_WORKFLOW_INVALID_PROVIDER"
1155
+ );
1156
+ }
1157
+ }
1158
+ if (typeof params.initialInput.data !== "string") {
1159
+ throw new ToonError(
1160
+ "Workflow definition initial input data must be a string",
1161
+ "DVM_WORKFLOW_MISSING_INPUT"
1162
+ );
1163
+ }
1164
+ if (!params.initialInput.type) {
1165
+ throw new ToonError(
1166
+ "Workflow definition initial input type is required",
1167
+ "DVM_WORKFLOW_MISSING_INPUT"
1168
+ );
1169
+ }
1170
+ if (typeof params.totalBid !== "string" || params.totalBid === "") {
1171
+ throw new ToonError(
1172
+ "Workflow definition totalBid must be a non-empty string (USDC micro-units)",
1173
+ "DVM_WORKFLOW_INVALID_BID"
1174
+ );
1175
+ }
1176
+ const stepsWithAllocation = params.steps.filter(
1177
+ (s) => s.bidAllocation !== void 0
1178
+ );
1179
+ if (stepsWithAllocation.length > 0) {
1180
+ let allocationSum;
1181
+ let totalBidBigInt;
1182
+ try {
1183
+ allocationSum = 0n;
1184
+ for (const step of stepsWithAllocation) {
1185
+ allocationSum += BigInt(step.bidAllocation);
1186
+ }
1187
+ totalBidBigInt = BigInt(params.totalBid);
1188
+ } catch {
1189
+ throw new ToonError(
1190
+ "Workflow bid amounts must be valid numeric strings (USDC micro-units)",
1191
+ "DVM_WORKFLOW_INVALID_BID"
1192
+ );
1193
+ }
1194
+ if (allocationSum > totalBidBigInt) {
1195
+ throw new ToonError(
1196
+ `Sum of step bid allocations (${allocationSum}) exceeds total bid (${totalBidBigInt})`,
1197
+ "DVM_WORKFLOW_BID_OVERFLOW"
1198
+ );
1199
+ }
1200
+ }
1201
+ const contentBody = {
1202
+ steps: params.steps.map((step) => {
1203
+ const s = {
1204
+ kind: step.kind,
1205
+ description: step.description
1206
+ };
1207
+ if (step.targetProvider !== void 0) {
1208
+ s["targetProvider"] = step.targetProvider;
1209
+ }
1210
+ if (step.bidAllocation !== void 0) {
1211
+ s["bidAllocation"] = step.bidAllocation;
1212
+ }
1213
+ return s;
1214
+ }),
1215
+ initialInput: params.initialInput,
1216
+ totalBid: params.totalBid
1217
+ };
1218
+ const contentJson = JSON.stringify(contentBody);
1219
+ const tags = [];
1220
+ const dTag = params.workflowId ?? `wf-${Date.now()}-${params.steps.length}`;
1221
+ tags.push(["d", dTag]);
1222
+ tags.push(["bid", params.totalBid, "usdc"]);
1223
+ return finalizeEvent6(
1224
+ {
1225
+ kind: WORKFLOW_CHAIN_KIND,
1226
+ content: contentJson,
1227
+ tags,
1228
+ created_at: Math.floor(Date.now() / 1e3)
1229
+ },
1230
+ secretKey
1231
+ );
1232
+ }
1233
+ function parseWorkflowDefinition(event) {
1234
+ if (event.kind !== WORKFLOW_CHAIN_KIND) {
1235
+ return null;
1236
+ }
1237
+ let body;
1238
+ try {
1239
+ body = JSON.parse(event.content);
1240
+ } catch {
1241
+ return null;
1242
+ }
1243
+ if (typeof body !== "object" || body === null) {
1244
+ return null;
1245
+ }
1246
+ const obj = body;
1247
+ const rawSteps = obj["steps"];
1248
+ if (!Array.isArray(rawSteps) || rawSteps.length === 0) {
1249
+ return null;
1250
+ }
1251
+ const steps = [];
1252
+ for (const rawStep of rawSteps) {
1253
+ if (typeof rawStep !== "object" || rawStep === null) {
1254
+ return null;
1255
+ }
1256
+ const stepObj = rawStep;
1257
+ const kind = stepObj["kind"];
1258
+ const description = stepObj["description"];
1259
+ if (typeof kind !== "number" || typeof description !== "string") {
1260
+ return null;
1261
+ }
1262
+ if (kind < 5e3 || kind > 5999) {
1263
+ return null;
1264
+ }
1265
+ const step = { kind, description };
1266
+ const targetProvider = stepObj["targetProvider"];
1267
+ if (typeof targetProvider === "string" && targetProvider.length > 0) {
1268
+ step.targetProvider = targetProvider;
1269
+ }
1270
+ const bidAllocation = stepObj["bidAllocation"];
1271
+ if (typeof bidAllocation === "string" && bidAllocation.length > 0) {
1272
+ step.bidAllocation = bidAllocation;
1273
+ }
1274
+ steps.push(step);
1275
+ }
1276
+ const rawInput = obj["initialInput"];
1277
+ if (typeof rawInput !== "object" || rawInput === null) {
1278
+ return null;
1279
+ }
1280
+ const inputObj = rawInput;
1281
+ const inputData = inputObj["data"];
1282
+ const inputType = inputObj["type"];
1283
+ if (typeof inputData !== "string" || typeof inputType !== "string") {
1284
+ return null;
1285
+ }
1286
+ const totalBid = obj["totalBid"];
1287
+ if (typeof totalBid !== "string" || totalBid === "") {
1288
+ return null;
1289
+ }
1290
+ return {
1291
+ steps,
1292
+ initialInput: { data: inputData, type: inputType },
1293
+ totalBid,
1294
+ content: event.content
1295
+ };
1296
+ }
1297
+
1298
+ // src/events/swarm.ts
1299
+ import { finalizeEvent as finalizeEvent7 } from "nostr-tools/pure";
1300
+ var HEX_64_REGEX3 = /^[0-9a-f]{64}$/;
1301
+ function buildSwarmRequestEvent(params, secretKey) {
1302
+ if (!Number.isInteger(params.maxProviders) || params.maxProviders < 1) {
1303
+ throw new ToonError(
1304
+ `Swarm maxProviders must be a positive integer >= 1, got ${params.maxProviders}`,
1305
+ "DVM_SWARM_INVALID_MAX_PROVIDERS"
1306
+ );
1307
+ }
1308
+ const baseEvent = buildJobRequestEvent(params, secretKey);
1309
+ const judge = params.judge ?? "customer";
1310
+ const tags = [
1311
+ ...baseEvent.tags,
1312
+ ["swarm", params.maxProviders.toString()],
1313
+ ["judge", judge]
1314
+ ];
1315
+ return finalizeEvent7(
1316
+ {
1317
+ kind: baseEvent.kind,
1318
+ content: baseEvent.content,
1319
+ tags,
1320
+ created_at: baseEvent.created_at
1321
+ },
1322
+ secretKey
1323
+ );
1324
+ }
1325
+ function buildSwarmSelectionEvent(params, secretKey) {
1326
+ if (!HEX_64_REGEX3.test(params.swarmRequestEventId)) {
1327
+ throw new ToonError(
1328
+ "Swarm selection swarmRequestEventId must be a 64-character lowercase hex string",
1329
+ "DVM_INVALID_EVENT_ID"
1330
+ );
1331
+ }
1332
+ if (!HEX_64_REGEX3.test(params.winnerResultEventId)) {
1333
+ throw new ToonError(
1334
+ "Swarm selection winnerResultEventId must be a 64-character lowercase hex string",
1335
+ "DVM_INVALID_EVENT_ID"
1336
+ );
1337
+ }
1338
+ if (!HEX_64_REGEX3.test(params.customerPubkey)) {
1339
+ throw new ToonError(
1340
+ "Swarm selection customerPubkey must be a 64-character lowercase hex string",
1341
+ "DVM_INVALID_PUBKEY"
1342
+ );
1343
+ }
1344
+ const tags = [
1345
+ ["e", params.swarmRequestEventId],
1346
+ ["p", params.customerPubkey],
1347
+ ["status", "success"],
1348
+ ["winner", params.winnerResultEventId]
1349
+ ];
1350
+ return finalizeEvent7(
1351
+ {
1352
+ kind: JOB_FEEDBACK_KIND,
1353
+ content: "",
1354
+ tags,
1355
+ created_at: Math.floor(Date.now() / 1e3)
1356
+ },
1357
+ secretKey
1358
+ );
1359
+ }
1360
+ function parseSwarmRequest(event) {
1361
+ const base = parseJobRequest(event);
1362
+ if (!base) return null;
1363
+ const swarmTag = event.tags.find((t) => t[0] === "swarm");
1364
+ if (!swarmTag) return null;
1365
+ const maxProvidersStr = swarmTag[1];
1366
+ if (maxProvidersStr === void 0) return null;
1367
+ const maxProviders = parseInt(maxProvidersStr, 10);
1368
+ if (isNaN(maxProviders) || maxProviders < 1) return null;
1369
+ const judgeTag = event.tags.find((t) => t[0] === "judge");
1370
+ const judge = judgeTag?.[1] ?? "customer";
1371
+ return {
1372
+ ...base,
1373
+ maxProviders,
1374
+ judge
1375
+ };
1376
+ }
1377
+ function parseSwarmSelection(event) {
1378
+ if (event.kind !== JOB_FEEDBACK_KIND) return null;
1379
+ const winnerTag = event.tags.find((t) => t[0] === "winner");
1380
+ if (!winnerTag) return null;
1381
+ const winnerResultEventId = winnerTag[1];
1382
+ if (winnerResultEventId === void 0 || !HEX_64_REGEX3.test(winnerResultEventId))
1383
+ return null;
1384
+ const eTag = event.tags.find((t) => t[0] === "e");
1385
+ if (!eTag) return null;
1386
+ const swarmRequestEventId = eTag[1];
1387
+ if (swarmRequestEventId === void 0 || !HEX_64_REGEX3.test(swarmRequestEventId))
1388
+ return null;
1389
+ return {
1390
+ swarmRequestEventId,
1391
+ winnerResultEventId
1392
+ };
1393
+ }
1394
+
1395
+ // src/events/prefix-claim.ts
1396
+ import { finalizeEvent as finalizeEvent8 } from "nostr-tools/pure";
1397
+ function buildPrefixClaimEvent(content, secretKey) {
1398
+ return finalizeEvent8(
1399
+ {
1400
+ kind: PREFIX_CLAIM_KIND,
1401
+ content: JSON.stringify(content),
1402
+ tags: [],
1403
+ created_at: Math.floor(Date.now() / 1e3)
1404
+ },
1405
+ secretKey
1406
+ );
1407
+ }
1408
+ function parsePrefixClaimEvent(event) {
1409
+ if (event.kind !== PREFIX_CLAIM_KIND) {
1410
+ return null;
1411
+ }
1412
+ try {
1413
+ const parsed = JSON.parse(event.content);
1414
+ if (typeof parsed !== "object" || parsed === null) {
1415
+ return null;
1416
+ }
1417
+ const obj = parsed;
1418
+ const requestedPrefix = obj["requestedPrefix"];
1419
+ if (typeof requestedPrefix !== "string" || requestedPrefix.length === 0) {
1420
+ return null;
1421
+ }
1422
+ return { requestedPrefix };
1423
+ } catch {
1424
+ return null;
1425
+ }
1426
+ }
1427
+ function buildPrefixGrantEvent(content, secretKey) {
1428
+ return finalizeEvent8(
1429
+ {
1430
+ kind: PREFIX_GRANT_KIND,
1431
+ content: JSON.stringify(content),
1432
+ tags: [],
1433
+ created_at: Math.floor(Date.now() / 1e3)
1434
+ },
1435
+ secretKey
1436
+ );
1437
+ }
1438
+ function parsePrefixGrantEvent(event) {
1439
+ if (event.kind !== PREFIX_GRANT_KIND) {
1440
+ return null;
1441
+ }
1442
+ try {
1443
+ const parsed = JSON.parse(event.content);
1444
+ if (typeof parsed !== "object" || parsed === null) {
1445
+ return null;
1446
+ }
1447
+ const obj = parsed;
1448
+ const grantedPrefix = obj["grantedPrefix"];
1449
+ const claimerPubkey = obj["claimerPubkey"];
1450
+ const ilpAddress = obj["ilpAddress"];
1451
+ if (typeof grantedPrefix !== "string" || grantedPrefix.length === 0) {
1452
+ return null;
1453
+ }
1454
+ if (typeof claimerPubkey !== "string" || claimerPubkey.length === 0) {
1455
+ return null;
1456
+ }
1457
+ if (typeof ilpAddress !== "string" || ilpAddress.length === 0) {
1458
+ return null;
1459
+ }
1460
+ return {
1461
+ grantedPrefix,
1462
+ claimerPubkey,
1463
+ ilpAddress
1464
+ };
1465
+ } catch {
1466
+ return null;
1467
+ }
1468
+ }
1469
+
1470
+ // src/bootstrap/AttestationVerifier.ts
1471
+ var AttestationState = /* @__PURE__ */ ((AttestationState2) => {
1472
+ AttestationState2["VALID"] = "valid";
1473
+ AttestationState2["STALE"] = "stale";
1474
+ AttestationState2["UNATTESTED"] = "unattested";
1475
+ return AttestationState2;
1476
+ })(AttestationState || {});
1477
+ var DEFAULT_VALIDITY_SECONDS = 300;
1478
+ var DEFAULT_GRACE_SECONDS = 30;
1479
+ var AttestationVerifier = class {
1480
+ knownGoodPcrs;
1481
+ validitySeconds;
1482
+ graceSeconds;
1483
+ constructor(config) {
1484
+ this.knownGoodPcrs = new Map(config.knownGoodPcrs);
1485
+ const validity = config.validitySeconds ?? DEFAULT_VALIDITY_SECONDS;
1486
+ const grace = config.graceSeconds ?? DEFAULT_GRACE_SECONDS;
1487
+ if (!Number.isFinite(validity) || validity < 0) {
1488
+ throw new Error(
1489
+ `validitySeconds must be a non-negative finite number, got ${String(validity)}`
1490
+ );
1491
+ }
1492
+ if (!Number.isFinite(grace) || grace < 0) {
1493
+ throw new Error(
1494
+ `graceSeconds must be a non-negative finite number, got ${String(grace)}`
1495
+ );
1496
+ }
1497
+ this.validitySeconds = validity;
1498
+ this.graceSeconds = grace;
1499
+ }
1500
+ /**
1501
+ * Verifies a TEE attestation's PCR values against the known-good registry.
1502
+ *
1503
+ * All three PCR values (pcr0, pcr1, pcr2) must be present and truthy in
1504
+ * the registry for verification to pass.
1505
+ *
1506
+ * @param attestation - The TEE attestation to verify.
1507
+ * @returns Verification result with `valid: true` or `valid: false` with reason.
1508
+ */
1509
+ verify(attestation) {
1510
+ const pcr0Valid = this.knownGoodPcrs.get(attestation.pcr0) === true;
1511
+ const pcr1Valid = this.knownGoodPcrs.get(attestation.pcr1) === true;
1512
+ const pcr2Valid = this.knownGoodPcrs.get(attestation.pcr2) === true;
1513
+ if (pcr0Valid && pcr1Valid && pcr2Valid) {
1514
+ return { valid: true };
1515
+ }
1516
+ return { valid: false, reason: "PCR mismatch" };
1517
+ }
1518
+ /**
1519
+ * Computes the attestation lifecycle state based on timing.
1520
+ *
1521
+ * Boundary behavior:
1522
+ * - At exactly `attestedAt + validitySeconds`: VALID (inclusive <=)
1523
+ * - At exactly `attestedAt + validitySeconds + graceSeconds`: STALE (inclusive <=)
1524
+ * - After grace expires: UNATTESTED
1525
+ *
1526
+ * @param _attestation - The TEE attestation (unused, reserved for future per-attestation logic).
1527
+ * @param attestedAt - Unix timestamp when the attestation was created.
1528
+ * @param now - Optional current unix timestamp (defaults to real clock).
1529
+ * @returns The current attestation state.
1530
+ */
1531
+ getAttestationState(_attestation, attestedAt, now) {
1532
+ if (!Number.isFinite(attestedAt)) {
1533
+ return "unattested" /* UNATTESTED */;
1534
+ }
1535
+ const currentTime = now ?? Math.floor(Date.now() / 1e3);
1536
+ const validityEnd = attestedAt + this.validitySeconds;
1537
+ const graceEnd = validityEnd + this.graceSeconds;
1538
+ if (currentTime <= validityEnd) {
1539
+ return "valid" /* VALID */;
1540
+ }
1541
+ if (currentTime <= graceEnd) {
1542
+ return "stale" /* STALE */;
1543
+ }
1544
+ return "unattested" /* UNATTESTED */;
1545
+ }
1546
+ /**
1547
+ * Ranks peers by attestation status: attested peers first, then non-attested.
1548
+ *
1549
+ * Preserves relative order within each group (stable sort via filter).
1550
+ * Does NOT mutate the input array -- returns a new sorted array.
1551
+ *
1552
+ * Attestation is a preference, not a requirement. Non-attested peers
1553
+ * remain in the result and are connectable.
1554
+ *
1555
+ * @param peers - Array of peer descriptors to rank.
1556
+ * @returns New array with attested peers first, preserving relative order.
1557
+ */
1558
+ rankPeers(peers) {
1559
+ const attested = peers.filter((p) => p.attested);
1560
+ const nonAttested = peers.filter((p) => !p.attested);
1561
+ return [...attested, ...nonAttested];
1562
+ }
1563
+ };
1564
+
1565
+ // src/events/attested-result-verifier.ts
1566
+ var AttestedResultVerifier = class {
1567
+ attestationVerifier;
1568
+ constructor(options) {
1569
+ this.attestationVerifier = options.attestationVerifier;
1570
+ }
1571
+ /**
1572
+ * Verifies that a Kind 6xxx result was computed in a valid TEE enclave.
1573
+ *
1574
+ * Performs three checks:
1575
+ * (a) Pubkey match: attestationEvent.pubkey === resultEvent.pubkey
1576
+ * (b) PCR validity: AttestationVerifier.verify(parsedAttestation.attestation)
1577
+ * (c) Time validity: attestation was VALID at resultEvent.created_at
1578
+ *
1579
+ * @param resultEvent - The Kind 6xxx result Nostr event.
1580
+ * @param _parsedResult - The parsed job result (reserved for future use).
1581
+ * @param attestationEvent - The kind:10033 attestation Nostr event.
1582
+ * @param parsedAttestation - The parsed attestation data.
1583
+ * @returns Verification result with valid flag, reason, and attestation state.
1584
+ */
1585
+ verifyAttestedResult(resultEvent, _parsedResult, attestationEvent, parsedAttestation) {
1586
+ if (attestationEvent.pubkey !== resultEvent.pubkey) {
1587
+ return { valid: false, reason: "pubkey mismatch" };
1588
+ }
1589
+ const pcrResult = this.attestationVerifier.verify(
1590
+ parsedAttestation.attestation
1591
+ );
1592
+ if (!pcrResult.valid) {
1593
+ return { valid: false, reason: "PCR mismatch" };
1594
+ }
1595
+ const state = this.attestationVerifier.getAttestationState(
1596
+ parsedAttestation.attestation,
1597
+ attestationEvent.created_at,
1598
+ resultEvent.created_at
1599
+ );
1600
+ if (state !== "valid" /* VALID */) {
1601
+ return {
1602
+ valid: false,
1603
+ reason: "attestation expired at result creation time",
1604
+ attestationState: state
1605
+ };
1606
+ }
1607
+ return { valid: true, attestationState: "valid" /* VALID */ };
1608
+ }
1609
+ };
1610
+ function hasRequireAttestation(params) {
1611
+ return params.some(
1612
+ (p) => p.key === "require_attestation" && p.value === "true"
1613
+ );
1614
+ }
1615
+
1616
+ // src/events/arweave-storage.ts
1617
+ import { finalizeEvent as finalizeEvent9 } from "nostr-tools/pure";
1618
+ var BASE64_REGEX2 = /^[A-Za-z0-9+/]*={0,2}$/;
1619
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1620
+ function isValidBase64(str) {
1621
+ if (str.length === 0) return false;
1622
+ if (!BASE64_REGEX2.test(str)) return false;
1623
+ try {
1624
+ Buffer.from(str, "base64");
1625
+ return true;
1626
+ } catch {
1627
+ return false;
1628
+ }
1629
+ }
1630
+ function buildBlobStorageRequest(params, secretKey) {
1631
+ if (!params.blobData || params.blobData.length === 0) {
1632
+ throw new ToonError(
1633
+ "Blob storage request blobData is required and must not be empty",
1634
+ "DVM_MISSING_INPUT"
1635
+ );
1636
+ }
1637
+ if (typeof params.bid !== "string" || params.bid === "") {
1638
+ throw new ToonError(
1639
+ "Blob storage request bid must be a non-empty string (USDC micro-units)",
1640
+ "DVM_INVALID_BID"
1641
+ );
1642
+ }
1643
+ const contentType = params.contentType ?? "application/octet-stream";
1644
+ const base64Blob = params.blobData.toString("base64");
1645
+ const tags = [];
1646
+ tags.push(["i", base64Blob, "blob"]);
1647
+ tags.push(["bid", params.bid, "usdc"]);
1648
+ tags.push(["output", contentType]);
1649
+ if (params.params !== void 0) {
1650
+ for (const p of params.params) {
1651
+ tags.push(["param", p.key, p.value]);
1652
+ }
1653
+ }
1654
+ return finalizeEvent9(
1655
+ {
1656
+ kind: BLOB_STORAGE_REQUEST_KIND,
1657
+ content: "",
1658
+ tags,
1659
+ created_at: Math.floor(Date.now() / 1e3)
1660
+ },
1661
+ secretKey
1662
+ );
1663
+ }
1664
+ function parseBlobStorageRequest(event) {
1665
+ if (event.kind !== BLOB_STORAGE_REQUEST_KIND) {
1666
+ return null;
1667
+ }
1668
+ const iTag = event.tags.find((t) => t[0] === "i");
1669
+ if (!iTag) return null;
1670
+ const base64Data = iTag[1];
1671
+ const inputType = iTag[2];
1672
+ if (base64Data === void 0 || base64Data === "") return null;
1673
+ if (inputType !== "blob") return null;
1674
+ if (!isValidBase64(base64Data)) {
1675
+ return null;
1676
+ }
1677
+ const bidTag = event.tags.find((t) => t[0] === "bid");
1678
+ if (!bidTag) return null;
1679
+ const bidAmount = bidTag[1];
1680
+ if (bidAmount === void 0 || bidAmount === "") return null;
1681
+ const outputTag = event.tags.find((t) => t[0] === "output");
1682
+ const contentType = outputTag?.[1] && outputTag[1] !== "" ? outputTag[1] : "application/octet-stream";
1683
+ const blobData = Buffer.from(base64Data, "base64");
1684
+ const paramTags = event.tags.filter((t) => t[0] === "param");
1685
+ const paramMap = /* @__PURE__ */ new Map();
1686
+ for (const pt of paramTags) {
1687
+ const key = pt[1];
1688
+ const value = pt[2];
1689
+ if (key !== void 0 && value !== void 0) {
1690
+ paramMap.set(key, value);
1691
+ }
1692
+ }
1693
+ const result = {
1694
+ blobData,
1695
+ contentType
1696
+ };
1697
+ const uploadId = paramMap.get("uploadId");
1698
+ if (uploadId !== void 0 && UUID_REGEX.test(uploadId)) {
1699
+ result.uploadId = uploadId;
1700
+ }
1701
+ const chunkIndexStr = paramMap.get("chunkIndex");
1702
+ if (chunkIndexStr !== void 0) {
1703
+ const chunkIndex = parseInt(chunkIndexStr, 10);
1704
+ if (!isNaN(chunkIndex) && chunkIndex >= 0) {
1705
+ result.chunkIndex = chunkIndex;
1706
+ }
1707
+ }
1708
+ const totalChunksStr = paramMap.get("totalChunks");
1709
+ if (totalChunksStr !== void 0) {
1710
+ const totalChunks = parseInt(totalChunksStr, 10);
1711
+ if (!isNaN(totalChunks) && totalChunks > 0) {
1712
+ result.totalChunks = totalChunks;
1713
+ }
1714
+ }
1715
+ return result;
1716
+ }
1717
+
1718
+ // src/events/reputation.ts
1719
+ import { finalizeEvent as finalizeEvent10 } from "nostr-tools/pure";
1720
+ var HEX_64_REGEX4 = /^[0-9a-f]{64}$/;
1721
+ function buildJobReviewEvent(params, secretKey) {
1722
+ if (!HEX_64_REGEX4.test(params.jobRequestEventId)) {
1723
+ throw new ToonError(
1724
+ "Job review jobRequestEventId must be a 64-character lowercase hex string",
1725
+ "REPUTATION_INVALID_JOB_REQUEST_EVENT_ID"
1726
+ );
1727
+ }
1728
+ if (!HEX_64_REGEX4.test(params.targetPubkey)) {
1729
+ throw new ToonError(
1730
+ "Job review targetPubkey must be a 64-character lowercase hex string",
1731
+ "REPUTATION_INVALID_TARGET_PUBKEY"
1732
+ );
1733
+ }
1734
+ if (typeof params.rating !== "number" || !Number.isInteger(params.rating) || params.rating < 1 || params.rating > 5) {
1735
+ throw new ToonError(
1736
+ `Job review rating must be an integer 1-5, got ${String(params.rating)}`,
1737
+ "REPUTATION_INVALID_RATING"
1738
+ );
1739
+ }
1740
+ if (params.role !== "customer" && params.role !== "provider") {
1741
+ throw new ToonError(
1742
+ `Job review role must be 'customer' or 'provider', got '${String(params.role)}'`,
1743
+ "REPUTATION_INVALID_ROLE"
1744
+ );
1745
+ }
1746
+ const tags = [
1747
+ ["d", params.jobRequestEventId],
1748
+ ["p", params.targetPubkey],
1749
+ ["rating", params.rating.toString()],
1750
+ ["role", params.role]
1751
+ ];
1752
+ return finalizeEvent10(
1753
+ {
1754
+ kind: JOB_REVIEW_KIND,
1755
+ content: params.content ?? "",
1756
+ tags,
1757
+ created_at: Math.floor(Date.now() / 1e3)
1758
+ },
1759
+ secretKey
1760
+ );
1761
+ }
1762
+ function parseJobReview(event) {
1763
+ if (event.kind !== JOB_REVIEW_KIND) {
1764
+ return null;
1765
+ }
1766
+ const dTag = event.tags.find((t) => t[0] === "d");
1767
+ if (!dTag) return null;
1768
+ const jobRequestEventId = dTag[1];
1769
+ if (jobRequestEventId === void 0 || !HEX_64_REGEX4.test(jobRequestEventId))
1770
+ return null;
1771
+ const pTag = event.tags.find((t) => t[0] === "p");
1772
+ if (!pTag) return null;
1773
+ const targetPubkey = pTag[1];
1774
+ if (targetPubkey === void 0 || !HEX_64_REGEX4.test(targetPubkey))
708
1775
  return null;
709
- const amountTag = event.tags.find((t) => t[0] === "amount");
710
- if (!amountTag) return null;
711
- const amount = amountTag[1];
712
- if (amount === void 0 || amount === "") return null;
713
- if (!/^\d+$/.test(amount)) return null;
1776
+ const ratingTag = event.tags.find((t) => t[0] === "rating");
1777
+ if (!ratingTag) return null;
1778
+ const ratingStr = ratingTag[1];
1779
+ if (ratingStr === void 0) return null;
1780
+ const rating = Number(ratingStr);
1781
+ if (!Number.isInteger(rating) || rating < 1 || rating > 5) return null;
1782
+ const roleTag = event.tags.find((t) => t[0] === "role");
1783
+ if (!roleTag) return null;
1784
+ const role = roleTag[1];
1785
+ if (role !== "customer" && role !== "provider") return null;
714
1786
  return {
715
- kind: event.kind,
716
- requestEventId,
717
- customerPubkey,
718
- amount,
1787
+ jobRequestEventId,
1788
+ targetPubkey,
1789
+ rating,
1790
+ role,
719
1791
  content: event.content
720
1792
  };
721
1793
  }
722
- function parseJobFeedback(event) {
723
- if (event.kind !== JOB_FEEDBACK_KIND) {
724
- return null;
1794
+ function buildWotDeclarationEvent(params, secretKey) {
1795
+ if (!HEX_64_REGEX4.test(params.targetPubkey)) {
1796
+ throw new ToonError(
1797
+ "WoT declaration targetPubkey must be a 64-character lowercase hex string",
1798
+ "REPUTATION_INVALID_TARGET_PUBKEY"
1799
+ );
725
1800
  }
726
- const eTag = event.tags.find((t) => t[0] === "e");
727
- if (!eTag) return null;
728
- const requestEventId = eTag[1];
729
- if (requestEventId === void 0 || !HEX_64_REGEX.test(requestEventId))
1801
+ const tags = [
1802
+ ["d", params.targetPubkey],
1803
+ ["p", params.targetPubkey]
1804
+ ];
1805
+ return finalizeEvent10(
1806
+ {
1807
+ kind: WEB_OF_TRUST_KIND,
1808
+ content: params.content ?? "",
1809
+ tags,
1810
+ created_at: Math.floor(Date.now() / 1e3)
1811
+ },
1812
+ secretKey
1813
+ );
1814
+ }
1815
+ function parseWotDeclaration(event) {
1816
+ if (event.kind !== WEB_OF_TRUST_KIND) {
730
1817
  return null;
1818
+ }
1819
+ const dTag = event.tags.find((t) => t[0] === "d");
1820
+ if (!dTag) return null;
1821
+ const dValue = dTag[1];
1822
+ if (dValue === void 0) return null;
731
1823
  const pTag = event.tags.find((t) => t[0] === "p");
732
1824
  if (!pTag) return null;
733
- const customerPubkey = pTag[1];
734
- if (customerPubkey === void 0 || !HEX_64_REGEX.test(customerPubkey))
735
- return null;
736
- const statusTag = event.tags.find((t) => t[0] === "status");
737
- if (!statusTag) return null;
738
- const statusValue = statusTag[1];
739
- if (statusValue === void 0) return null;
740
- if (!VALID_STATUSES.has(statusValue)) {
1825
+ const targetPubkey = pTag[1];
1826
+ if (targetPubkey === void 0 || !HEX_64_REGEX4.test(targetPubkey))
741
1827
  return null;
742
- }
1828
+ if (dValue !== targetPubkey) return null;
743
1829
  return {
744
- requestEventId,
745
- customerPubkey,
746
- status: statusValue,
1830
+ targetPubkey,
1831
+ declarerPubkey: event.pubkey,
747
1832
  content: event.content
748
1833
  };
749
1834
  }
1835
+ var ReputationScoreCalculator = class {
1836
+ /**
1837
+ * Computes the composite reputation score from pre-computed signals.
1838
+ *
1839
+ * @param signals - The individual signal values.
1840
+ * @returns The composite score with individual signals.
1841
+ */
1842
+ calculateScore(signals) {
1843
+ const score = signals.trustedBy * 100 + Math.log10(Math.max(1, signals.channelVolumeUsdc)) * 10 + signals.jobsCompleted * 5 + signals.avgRating * 20;
1844
+ if (!isFinite(score)) {
1845
+ return { score: 0, signals };
1846
+ }
1847
+ return { score, signals };
1848
+ }
1849
+ /**
1850
+ * Computes the threshold-filtered trusted_by count from WoT declarations.
1851
+ *
1852
+ * Declarers with non-zero channel volume contribute 1 to the count.
1853
+ * Declarers with zero channel volume contribute 0 (sybil defense).
1854
+ *
1855
+ * @param wotDeclarations - Parsed WoT declarations targeting the provider.
1856
+ * @param getChannelVolume - Callback to look up a declarer's channel volume.
1857
+ * @returns The count of WoT declarations from non-zero-volume declarers.
1858
+ */
1859
+ computeTrustedBy(wotDeclarations, getChannelVolume) {
1860
+ let count = 0;
1861
+ for (const declaration of wotDeclarations) {
1862
+ const volume = getChannelVolume(declaration.declarerPubkey);
1863
+ if (volume > 0) {
1864
+ count += 1;
1865
+ }
1866
+ }
1867
+ return count;
1868
+ }
1869
+ /**
1870
+ * Computes the average rating from verified customer reviews only.
1871
+ *
1872
+ * Reviews are provided as tuples of `{ review, reviewerPubkey }` so the
1873
+ * calculator can filter by the verified customer set. Reviews from pubkeys
1874
+ * NOT in `verifiedCustomerPubkeys` are excluded entirely (customer-gate
1875
+ * sybil defense per E6-R013).
1876
+ *
1877
+ * @param reviews - Parsed job reviews with reviewer pubkeys.
1878
+ * @param verifiedCustomerPubkeys - Set of pubkeys that authored Kind 5xxx requests.
1879
+ * @returns The mean rating from verified reviews, or 0 when no verified reviews exist.
1880
+ */
1881
+ computeAvgRating(reviews, verifiedCustomerPubkeys) {
1882
+ let sum = 0;
1883
+ let count = 0;
1884
+ for (const { review, reviewerPubkey } of reviews) {
1885
+ if (!verifiedCustomerPubkeys.has(reviewerPubkey)) {
1886
+ continue;
1887
+ }
1888
+ if (Number.isInteger(review.rating) && review.rating >= 1 && review.rating <= 5) {
1889
+ sum += review.rating;
1890
+ count += 1;
1891
+ }
1892
+ }
1893
+ return count === 0 ? 0 : sum / count;
1894
+ }
1895
+ };
1896
+ function hasMinReputation(params) {
1897
+ const param = params.find((p) => p.key === "min_reputation");
1898
+ if (param === void 0) {
1899
+ return null;
1900
+ }
1901
+ const trimmed = param.value.trim();
1902
+ const numericValue = Number(trimmed);
1903
+ if (trimmed === "" || isNaN(numericValue) || !isFinite(numericValue)) {
1904
+ throw new ToonError(
1905
+ `min_reputation value must be numeric, got '${param.value}'`,
1906
+ "REPUTATION_INVALID_MIN_REPUTATION"
1907
+ );
1908
+ }
1909
+ return numericValue;
1910
+ }
750
1911
 
751
1912
  // src/discovery/NostrPeerDiscovery.ts
752
1913
  import { SimplePool } from "nostr-tools/pool";
@@ -1456,6 +2617,98 @@ async function publishSeedRelayEntry(config) {
1456
2617
  return { publishedTo, eventId: event.id };
1457
2618
  }
1458
2619
 
2620
+ // src/fee/calculate-route-amount.ts
2621
+ function calculateRouteAmount(params) {
2622
+ const { basePricePerByte, packetByteLength, hopFees } = params;
2623
+ if (packetByteLength < 0) {
2624
+ return 0n;
2625
+ }
2626
+ const bytes = BigInt(packetByteLength);
2627
+ const baseAmount = basePricePerByte * bytes;
2628
+ const intermediaryFees = hopFees.reduce((sum, fee) => sum + fee * bytes, 0n);
2629
+ return baseAmount + intermediaryFees;
2630
+ }
2631
+
2632
+ // src/fee/resolve-route-fees.ts
2633
+ var _cachedPeersRef = null;
2634
+ var _cachedPeerFingerprint = "";
2635
+ var _cachedPeerMap = null;
2636
+ function peerFingerprint(peers) {
2637
+ let fp = `${peers.length}:`;
2638
+ for (const p of peers) {
2639
+ fp += `${p.peerInfo.ilpAddress}=${p.peerInfo.feePerByte};`;
2640
+ }
2641
+ return fp;
2642
+ }
2643
+ function getPeerByAddressMap(discoveredPeers) {
2644
+ const fp = peerFingerprint(discoveredPeers);
2645
+ if (_cachedPeersRef === discoveredPeers && _cachedPeerFingerprint === fp && _cachedPeerMap) {
2646
+ return _cachedPeerMap;
2647
+ }
2648
+ const peerByAddress = /* @__PURE__ */ new Map();
2649
+ for (const peer of discoveredPeers) {
2650
+ peerByAddress.set(peer.peerInfo.ilpAddress, peer);
2651
+ if (peer.peerInfo.ilpAddresses) {
2652
+ for (const addr of peer.peerInfo.ilpAddresses) {
2653
+ peerByAddress.set(addr, peer);
2654
+ }
2655
+ }
2656
+ }
2657
+ _cachedPeersRef = discoveredPeers;
2658
+ _cachedPeerFingerprint = fp;
2659
+ _cachedPeerMap = peerByAddress;
2660
+ return peerByAddress;
2661
+ }
2662
+ function resolveRouteFees(params) {
2663
+ const { destination, ownIlpAddress, discoveredPeers } = params;
2664
+ if (!destination || !destination.trim() || !ownIlpAddress || !ownIlpAddress.trim()) {
2665
+ return { hopFees: [], warnings: [] };
2666
+ }
2667
+ const senderSegments = ownIlpAddress.split(".");
2668
+ const destSegments = destination.split(".");
2669
+ let lcaLength = 0;
2670
+ const minLength = Math.min(senderSegments.length, destSegments.length);
2671
+ for (let i = 0; i < minLength; i++) {
2672
+ if (senderSegments[i] === destSegments[i]) {
2673
+ lcaLength = i + 1;
2674
+ } else {
2675
+ break;
2676
+ }
2677
+ }
2678
+ const intermediaryPrefixes = [];
2679
+ for (let i = lcaLength; i < destSegments.length - 1; i++) {
2680
+ const prefix = destSegments.slice(0, i + 1).join(".");
2681
+ intermediaryPrefixes.push(prefix);
2682
+ }
2683
+ if (intermediaryPrefixes.length === 0) {
2684
+ return { hopFees: [], warnings: [] };
2685
+ }
2686
+ const peerByAddress = getPeerByAddressMap(discoveredPeers);
2687
+ const hopFees = [];
2688
+ const warnings = [];
2689
+ for (const prefix of intermediaryPrefixes) {
2690
+ const peer = peerByAddress.get(prefix);
2691
+ if (peer) {
2692
+ let fee;
2693
+ try {
2694
+ fee = BigInt(peer.peerInfo.feePerByte ?? "0");
2695
+ } catch {
2696
+ fee = 0n;
2697
+ warnings.push(
2698
+ `Invalid feePerByte "${peer.peerInfo.feePerByte}" at ${prefix}: defaulting to 0`
2699
+ );
2700
+ }
2701
+ hopFees.push(fee < 0n ? 0n : fee);
2702
+ } else {
2703
+ hopFees.push(0n);
2704
+ warnings.push(
2705
+ `Unknown intermediary at ${prefix}: defaulting feePerByte to 0`
2706
+ );
2707
+ }
2708
+ }
2709
+ return { hopFees, warnings };
2710
+ }
2711
+
1459
2712
  // src/settlement/settlement.ts
1460
2713
  function negotiateSettlementChain(requesterChains, responderChains, requesterPreferredTokens, responderPreferredTokens) {
1461
2714
  const responderSet = new Set(responderChains);
@@ -1885,7 +3138,7 @@ var BootstrapService = class {
1885
3138
  }
1886
3139
  if (ilpResult.accepted) {
1887
3140
  console.log(
1888
- `[Bootstrap] Announced to ${result.registeredPeerId} via ILP (fulfillment: ${ilpResult.fulfillment}, eventId: ${ilpInfoEvent.id})`
3141
+ `[Bootstrap] Announced to ${result.registeredPeerId} via ILP (eventId: ${ilpInfoEvent.id})`
1889
3142
  );
1890
3143
  this.emit({
1891
3144
  type: "bootstrap:announced",
@@ -2148,6 +3401,56 @@ var BootstrapService = class {
2148
3401
  );
2149
3402
  }
2150
3403
  }
3404
+ /**
3405
+ * Re-advertise own kind:10032 to all previously bootstrapped peers.
3406
+ *
3407
+ * Called after topology changes (addUpstreamPeer/removeUpstreamPeer) to
3408
+ * propagate updated ILP address lists to the network. Uses the same
3409
+ * ILP-first announcement path as the initial Phase 2 bootstrap.
3410
+ *
3411
+ * For non-ILP mode, publishes directly to each peer's relay URL.
3412
+ *
3413
+ * @param results - The bootstrap results from the initial bootstrap() call
3414
+ * @returns Number of peers successfully re-announced to
3415
+ */
3416
+ async republish(results) {
3417
+ if (results.length === 0) {
3418
+ return 0;
3419
+ }
3420
+ let successCount = 0;
3421
+ if (this.ilpClient && this.toonEncoder) {
3422
+ for (const result of results) {
3423
+ try {
3424
+ await this.announceViaIlp(result);
3425
+ successCount++;
3426
+ } catch (error) {
3427
+ const reason = error instanceof Error ? error.message : "Unknown error";
3428
+ this.emit({
3429
+ type: "bootstrap:announce-failed",
3430
+ peerId: result.registeredPeerId,
3431
+ reason: `republish: ${reason}`
3432
+ });
3433
+ console.warn(
3434
+ `[Bootstrap] Republish failed for ${result.registeredPeerId}:`,
3435
+ reason
3436
+ );
3437
+ }
3438
+ }
3439
+ } else {
3440
+ for (const result of results) {
3441
+ try {
3442
+ await this.publishOurInfo(result.knownPeer.relayUrl);
3443
+ successCount++;
3444
+ } catch (error) {
3445
+ console.warn(
3446
+ `[Bootstrap] Republish to ${result.knownPeer.relayUrl} failed:`,
3447
+ error instanceof Error ? error.message : "Unknown error"
3448
+ );
3449
+ }
3450
+ }
3451
+ }
3452
+ return successCount;
3453
+ }
2151
3454
  /**
2152
3455
  * Get our pubkey.
2153
3456
  */
@@ -2197,7 +3500,8 @@ function createDiscoveryTracker(config) {
2197
3500
  });
2198
3501
  }).catch((error) => {
2199
3502
  console.warn(
2200
- `[DiscoveryTracker] Failed to deregister ${peerId}:`,
3503
+ "[DiscoveryTracker] Failed to deregister %s: %s",
3504
+ peerId,
2201
3505
  error instanceof Error ? error.message : "Unknown error"
2202
3506
  );
2203
3507
  });
@@ -2278,7 +3582,8 @@ function createDiscoveryTracker(config) {
2278
3582
  peeredPubkeys.delete(targetPubkey);
2279
3583
  const reason = error instanceof Error ? error.message : "Unknown error";
2280
3584
  console.warn(
2281
- `[DiscoveryTracker] Failed to register ${peerId}:`,
3585
+ "[DiscoveryTracker] Failed to register %s: %s",
3586
+ peerId,
2282
3587
  reason
2283
3588
  );
2284
3589
  emit({
@@ -2342,7 +3647,8 @@ function createDiscoveryTracker(config) {
2342
3647
  } catch (error) {
2343
3648
  const reason = error instanceof Error ? error.message : "Unknown error";
2344
3649
  console.warn(
2345
- `[DiscoveryTracker] Settlement failed for ${peerId}:`,
3650
+ "[DiscoveryTracker] Settlement failed for %s: %s",
3651
+ peerId,
2346
3652
  reason
2347
3653
  );
2348
3654
  emit({
@@ -2358,6 +3664,9 @@ function createDiscoveryTracker(config) {
2358
3664
  (p) => !peeredPubkeys.has(p.pubkey)
2359
3665
  );
2360
3666
  },
3667
+ getAllDiscoveredPeers() {
3668
+ return [...discoveredPeers.values()];
3669
+ },
2361
3670
  isPeered(targetPubkey) {
2362
3671
  return peeredPubkeys.has(targetPubkey);
2363
3672
  },
@@ -2425,7 +3734,6 @@ function createHttpIlpClient(baseUrl) {
2425
3734
  const data = await response.json();
2426
3735
  return {
2427
3736
  accepted: data["accepted"] ?? data["fulfilled"] ?? false,
2428
- fulfillment: data["fulfillment"],
2429
3737
  data: data["data"],
2430
3738
  code: data["code"],
2431
3739
  message: data["message"]
@@ -2437,29 +3745,21 @@ var createHttpRuntimeClient = createHttpIlpClient;
2437
3745
  var createAgentRuntimeClient = createHttpIlpClient;
2438
3746
 
2439
3747
  // src/bootstrap/direct-ilp-client.ts
2440
- import { createHash } from "crypto";
2441
- function createDirectIlpClient(connector, config) {
3748
+ function createDirectIlpClient(connector, _config) {
2442
3749
  return {
2443
3750
  async sendIlpPacket(params) {
2444
3751
  try {
2445
3752
  const amount = BigInt(params.amount);
2446
3753
  const data = Uint8Array.from(Buffer.from(params.data, "base64"));
2447
- let executionCondition;
2448
- if (data.length > 0) {
2449
- const fulfillment = createHash("sha256").update(data).digest();
2450
- executionCondition = createHash("sha256").update(fulfillment).digest();
2451
- }
2452
3754
  const result = await connector.sendPacket({
2453
3755
  destination: params.destination,
2454
3756
  amount,
2455
3757
  data,
2456
- executionCondition,
2457
3758
  expiresAt: new Date(Date.now() + 3e4)
2458
3759
  });
2459
3760
  if (result.type === "fulfill" || result.type === 13) {
2460
3761
  return {
2461
3762
  accepted: true,
2462
- fulfillment: Buffer.from(result.fulfillment).toString("base64"),
2463
3763
  data: result.data ? Buffer.from(result.data).toString("base64") : void 0
2464
3764
  };
2465
3765
  }
@@ -2638,7 +3938,6 @@ function createHttpIlpClient2(connectorUrl) {
2638
3938
  if (result["accept"] || result["accepted"]) {
2639
3939
  return {
2640
3940
  accepted: true,
2641
- fulfillment: result["fulfillment"],
2642
3941
  data: result["data"]
2643
3942
  };
2644
3943
  } else {
@@ -2726,101 +4025,6 @@ function createHttpChannelClient(adminUrl) {
2726
4025
  };
2727
4026
  }
2728
4027
 
2729
- // src/bootstrap/AttestationVerifier.ts
2730
- var AttestationState = /* @__PURE__ */ ((AttestationState2) => {
2731
- AttestationState2["VALID"] = "valid";
2732
- AttestationState2["STALE"] = "stale";
2733
- AttestationState2["UNATTESTED"] = "unattested";
2734
- return AttestationState2;
2735
- })(AttestationState || {});
2736
- var DEFAULT_VALIDITY_SECONDS = 300;
2737
- var DEFAULT_GRACE_SECONDS = 30;
2738
- var AttestationVerifier = class {
2739
- knownGoodPcrs;
2740
- validitySeconds;
2741
- graceSeconds;
2742
- constructor(config) {
2743
- this.knownGoodPcrs = new Map(config.knownGoodPcrs);
2744
- const validity = config.validitySeconds ?? DEFAULT_VALIDITY_SECONDS;
2745
- const grace = config.graceSeconds ?? DEFAULT_GRACE_SECONDS;
2746
- if (!Number.isFinite(validity) || validity < 0) {
2747
- throw new Error(
2748
- `validitySeconds must be a non-negative finite number, got ${String(validity)}`
2749
- );
2750
- }
2751
- if (!Number.isFinite(grace) || grace < 0) {
2752
- throw new Error(
2753
- `graceSeconds must be a non-negative finite number, got ${String(grace)}`
2754
- );
2755
- }
2756
- this.validitySeconds = validity;
2757
- this.graceSeconds = grace;
2758
- }
2759
- /**
2760
- * Verifies a TEE attestation's PCR values against the known-good registry.
2761
- *
2762
- * All three PCR values (pcr0, pcr1, pcr2) must be present and truthy in
2763
- * the registry for verification to pass.
2764
- *
2765
- * @param attestation - The TEE attestation to verify.
2766
- * @returns Verification result with `valid: true` or `valid: false` with reason.
2767
- */
2768
- verify(attestation) {
2769
- const pcr0Valid = this.knownGoodPcrs.get(attestation.pcr0) === true;
2770
- const pcr1Valid = this.knownGoodPcrs.get(attestation.pcr1) === true;
2771
- const pcr2Valid = this.knownGoodPcrs.get(attestation.pcr2) === true;
2772
- if (pcr0Valid && pcr1Valid && pcr2Valid) {
2773
- return { valid: true };
2774
- }
2775
- return { valid: false, reason: "PCR mismatch" };
2776
- }
2777
- /**
2778
- * Computes the attestation lifecycle state based on timing.
2779
- *
2780
- * Boundary behavior:
2781
- * - At exactly `attestedAt + validitySeconds`: VALID (inclusive <=)
2782
- * - At exactly `attestedAt + validitySeconds + graceSeconds`: STALE (inclusive <=)
2783
- * - After grace expires: UNATTESTED
2784
- *
2785
- * @param _attestation - The TEE attestation (unused, reserved for future per-attestation logic).
2786
- * @param attestedAt - Unix timestamp when the attestation was created.
2787
- * @param now - Optional current unix timestamp (defaults to real clock).
2788
- * @returns The current attestation state.
2789
- */
2790
- getAttestationState(_attestation, attestedAt, now) {
2791
- if (!Number.isFinite(attestedAt)) {
2792
- return "unattested" /* UNATTESTED */;
2793
- }
2794
- const currentTime = now ?? Math.floor(Date.now() / 1e3);
2795
- const validityEnd = attestedAt + this.validitySeconds;
2796
- const graceEnd = validityEnd + this.graceSeconds;
2797
- if (currentTime <= validityEnd) {
2798
- return "valid" /* VALID */;
2799
- }
2800
- if (currentTime <= graceEnd) {
2801
- return "stale" /* STALE */;
2802
- }
2803
- return "unattested" /* UNATTESTED */;
2804
- }
2805
- /**
2806
- * Ranks peers by attestation status: attested peers first, then non-attested.
2807
- *
2808
- * Preserves relative order within each group (stable sort via filter).
2809
- * Does NOT mutate the input array -- returns a new sorted array.
2810
- *
2811
- * Attestation is a preference, not a requirement. Non-attested peers
2812
- * remain in the result and are connectable.
2813
- *
2814
- * @param peers - Array of peer descriptors to rank.
2815
- * @returns New array with attested peers first, preserving relative order.
2816
- */
2817
- rankPeers(peers) {
2818
- const attested = peers.filter((p) => p.attested);
2819
- const nonAttested = peers.filter((p) => !p.attested);
2820
- return [...attested, ...nonAttested];
2821
- }
2822
- };
2823
-
2824
4028
  // src/bootstrap/AttestationBootstrap.ts
2825
4029
  var AttestationBootstrap = class {
2826
4030
  config;
@@ -3079,6 +4283,97 @@ function buildEip712Domain(config) {
3079
4283
  verifyingContract: config.tokenNetworkAddress
3080
4284
  };
3081
4285
  }
4286
+ var SOLANA_CHAIN_PRESETS = {
4287
+ "solana-devnet": {
4288
+ name: "solana-devnet",
4289
+ chainType: "solana",
4290
+ rpcUrl: "http://localhost:19899",
4291
+ programId: "",
4292
+ // TBD: set after program deployment
4293
+ cluster: "devnet"
4294
+ }
4295
+ };
4296
+ var MINA_CHAIN_PRESETS = {
4297
+ "mina-devnet": {
4298
+ name: "mina-devnet",
4299
+ chainType: "mina",
4300
+ graphqlUrl: "http://localhost:19085/graphql",
4301
+ zkAppAddress: "",
4302
+ // TBD: set after zkApp deployment
4303
+ network: "devnet"
4304
+ }
4305
+ };
4306
+ function resolveSolanaChainConfig(name) {
4307
+ const preset = SOLANA_CHAIN_PRESETS[name];
4308
+ if (!preset) {
4309
+ const validNames = Object.keys(SOLANA_CHAIN_PRESETS).join(", ");
4310
+ throw new ToonError(
4311
+ `Unknown Solana chain "${name}". Valid Solana chains: ${validNames}`,
4312
+ "INVALID_CHAIN"
4313
+ );
4314
+ }
4315
+ const resolved = { ...preset };
4316
+ const envRpcUrl = process.env["SOLANA_RPC_URL"];
4317
+ if (envRpcUrl) {
4318
+ resolved.rpcUrl = envRpcUrl;
4319
+ }
4320
+ const envProgramId = process.env["SOLANA_PROGRAM_ID"];
4321
+ if (envProgramId) {
4322
+ resolved.programId = envProgramId;
4323
+ }
4324
+ return resolved;
4325
+ }
4326
+ function resolveMinaChainConfig(name) {
4327
+ const preset = MINA_CHAIN_PRESETS[name];
4328
+ if (!preset) {
4329
+ const validNames = Object.keys(MINA_CHAIN_PRESETS).join(", ");
4330
+ throw new ToonError(
4331
+ `Unknown Mina chain "${name}". Valid Mina chains: ${validNames}`,
4332
+ "INVALID_CHAIN"
4333
+ );
4334
+ }
4335
+ const resolved = { ...preset };
4336
+ const envGraphqlUrl = process.env["MINA_GRAPHQL_URL"];
4337
+ if (envGraphqlUrl) {
4338
+ resolved.graphqlUrl = envGraphqlUrl;
4339
+ }
4340
+ const envZkAppAddress = process.env["MINA_ZKAPP_ADDRESS"];
4341
+ if (envZkAppAddress) {
4342
+ resolved.zkAppAddress = envZkAppAddress;
4343
+ }
4344
+ return resolved;
4345
+ }
4346
+ function buildEvmProviderEntry(config, keyId) {
4347
+ return {
4348
+ chainType: "evm",
4349
+ chainId: `evm:${config.chainId}`,
4350
+ rpcUrl: config.rpcUrl,
4351
+ registryAddress: config.registryAddress,
4352
+ keyId
4353
+ };
4354
+ }
4355
+ function buildSolanaProviderEntry(config, keyId) {
4356
+ return {
4357
+ chainType: "solana",
4358
+ chainId: `solana:${config.cluster}`,
4359
+ rpcUrl: config.rpcUrl,
4360
+ programId: config.programId,
4361
+ keyId,
4362
+ cluster: config.cluster,
4363
+ ...config.tokenMint && { tokenMint: config.tokenMint }
4364
+ };
4365
+ }
4366
+ function buildMinaProviderEntry(config, keyId) {
4367
+ return {
4368
+ chainType: "mina",
4369
+ chainId: `mina:${config.network}`,
4370
+ graphqlUrl: config.graphqlUrl,
4371
+ zkAppAddress: config.zkAppAddress,
4372
+ ...keyId && { keyId },
4373
+ ...config.tokenId && { tokenId: config.tokenId },
4374
+ network: config.network
4375
+ };
4376
+ }
3082
4377
 
3083
4378
  // src/x402/build-ilp-prepare.ts
3084
4379
  function buildIlpPrepare(params) {
@@ -3165,7 +4460,7 @@ import { execFile as execFileCb } from "child_process";
3165
4460
  import { readFile, mkdtemp, cp, writeFile, rm } from "fs/promises";
3166
4461
  import { tmpdir } from "os";
3167
4462
  import path from "path";
3168
- import { createHash as createHash2 } from "crypto";
4463
+ import { createHash } from "crypto";
3169
4464
  function execFileAsync(cmd, args, options) {
3170
4465
  return new Promise((resolve, reject) => {
3171
4466
  execFileCb(cmd, args, options, (error, stdout, stderr) => {
@@ -3226,19 +4521,19 @@ var NixBuilder = class {
3226
4521
  );
3227
4522
  }
3228
4523
  const imageData = await readFile(imagePath);
3229
- const sha256 = createHash2("sha256").update(imageData).digest("hex");
4524
+ const sha256 = createHash("sha256").update(imageData).digest("hex");
3230
4525
  const imageHash = `sha256:${sha256}`;
3231
- const pcr0 = createHash2("sha384").update(imageData).digest("hex");
4526
+ const pcr0 = createHash("sha384").update(imageData).digest("hex");
3232
4527
  const KERNEL_REGION_SIZE = 1024 * 1024;
3233
4528
  const kernelRegion = imageData.subarray(
3234
4529
  0,
3235
4530
  Math.min(KERNEL_REGION_SIZE, imageData.length)
3236
4531
  );
3237
- const pcr1 = createHash2("sha384").update(kernelRegion).digest("hex");
4532
+ const pcr1 = createHash("sha384").update(kernelRegion).digest("hex");
3238
4533
  const appRegion = imageData.subarray(
3239
4534
  Math.min(KERNEL_REGION_SIZE, imageData.length)
3240
4535
  );
3241
- const pcr2 = appRegion.length > 0 ? createHash2("sha384").update(appRegion).digest("hex") : createHash2("sha384").update("pcr2:").update(imageData).digest("hex");
4536
+ const pcr2 = appRegion.length > 0 ? createHash("sha384").update(appRegion).digest("hex") : createHash("sha384").update("pcr2:").update(imageData).digest("hex");
3242
4537
  return {
3243
4538
  imageHash,
3244
4539
  pcr0,
@@ -3429,29 +4724,40 @@ function createLogger(config) {
3429
4724
  // src/index.ts
3430
4725
  var VERSION = "0.1.0";
3431
4726
  export {
4727
+ AddressRegistry,
3432
4728
  ArDrivePeerRegistry,
3433
4729
  AttestationBootstrap,
3434
4730
  AttestationState,
3435
4731
  AttestationVerifier,
4732
+ AttestedResultVerifier,
4733
+ BLOB_STORAGE_REQUEST_KIND,
4734
+ BLOB_STORAGE_RESULT_KIND,
3436
4735
  BootstrapError,
3437
4736
  BootstrapService,
3438
4737
  CHAIN_PRESETS,
3439
4738
  GenesisPeerLoader,
3440
4739
  ILP_PEER_INFO_KIND,
4740
+ ILP_ROOT_PREFIX,
3441
4741
  IMAGE_GENERATION_KIND,
3442
4742
  InvalidEventError,
3443
4743
  JOB_FEEDBACK_KIND,
3444
4744
  JOB_REQUEST_KIND_BASE,
3445
4745
  JOB_RESULT_KIND_BASE,
4746
+ JOB_REVIEW_KIND,
3446
4747
  KmsIdentityError,
4748
+ MINA_CHAIN_PRESETS,
3447
4749
  MOCK_USDC_ADDRESS,
3448
4750
  MOCK_USDC_CONFIG,
3449
4751
  NixBuilder,
3450
4752
  NostrPeerDiscovery,
4753
+ PREFIX_CLAIM_KIND,
4754
+ PREFIX_GRANT_KIND,
3451
4755
  PcrReproducibilityError,
3452
4756
  PeerDiscoveryError,
4757
+ ReputationScoreCalculator,
3453
4758
  SEED_RELAY_LIST_KIND,
3454
4759
  SERVICE_DISCOVERY_KIND,
4760
+ SOLANA_CHAIN_PRESETS,
3455
4761
  SeedRelayDiscovery,
3456
4762
  SocialPeerDiscovery,
3457
4763
  TEE_ATTESTATION_KIND,
@@ -3465,16 +4771,33 @@ export {
3465
4771
  USDC_NAME,
3466
4772
  USDC_SYMBOL,
3467
4773
  VERSION,
4774
+ WEB_OF_TRUST_KIND,
4775
+ WORKFLOW_CHAIN_KIND,
3468
4776
  analyzeDockerfileForNonDeterminism,
4777
+ assignAddressFromHandshake,
3469
4778
  buildAttestationEvent,
4779
+ buildBlobStorageRequest,
3470
4780
  buildEip712Domain,
4781
+ buildEvmProviderEntry,
3471
4782
  buildIlpPeerInfoEvent,
3472
4783
  buildIlpPrepare,
3473
4784
  buildJobFeedbackEvent,
3474
4785
  buildJobRequestEvent,
3475
4786
  buildJobResultEvent,
4787
+ buildJobReviewEvent,
4788
+ buildMinaProviderEntry,
4789
+ buildPrefixClaimEvent,
4790
+ buildPrefixGrantEvent,
4791
+ buildPrefixHandshakeData,
3476
4792
  buildSeedRelayListEvent,
3477
4793
  buildServiceDiscoveryEvent,
4794
+ buildSolanaProviderEntry,
4795
+ buildSwarmRequestEvent,
4796
+ buildSwarmSelectionEvent,
4797
+ buildWorkflowDefinitionEvent,
4798
+ buildWotDeclarationEvent,
4799
+ calculateRouteAmount,
4800
+ checkAddressCollision,
3478
4801
  createAgentRuntimeClient,
3479
4802
  createDirectChannelClient,
3480
4803
  createDirectConnectorAdmin,
@@ -3489,23 +4812,43 @@ export {
3489
4812
  createLogger,
3490
4813
  createToonNode,
3491
4814
  decodeEventFromToon,
4815
+ deriveChildAddress,
3492
4816
  deriveFromKmsSeed,
3493
4817
  encodeEventToToon,
3494
4818
  encodeEventToToonString,
4819
+ extractPrefixFromHandshake,
4820
+ hasMinReputation,
4821
+ hasRequireAttestation,
4822
+ isGenesisNode,
4823
+ isValidIlpAddressStructure,
3495
4824
  negotiateSettlementChain,
3496
4825
  parseAttestation,
4826
+ parseBlobStorageRequest,
3497
4827
  parseIlpPeerInfo,
3498
4828
  parseJobFeedback,
3499
4829
  parseJobRequest,
3500
4830
  parseJobResult,
4831
+ parseJobReview,
4832
+ parsePrefixClaimEvent,
4833
+ parsePrefixGrantEvent,
3501
4834
  parseSeedRelayList,
3502
4835
  parseServiceDiscovery,
4836
+ parseSwarmRequest,
4837
+ parseSwarmSelection,
4838
+ parseWorkflowDefinition,
4839
+ parseWotDeclaration,
3503
4840
  publishSeedRelayEntry,
3504
4841
  readDockerfileNix,
3505
4842
  resolveChainConfig,
4843
+ resolveMinaChainConfig,
4844
+ resolveRouteFees,
4845
+ resolveSolanaChainConfig,
3506
4846
  resolveTokenForChain,
3507
4847
  shallowParseToon,
3508
4848
  validateChainId,
4849
+ validateIlpAddress,
4850
+ validatePrefix,
4851
+ validatePrefixConsistency,
3509
4852
  verifyPcrReproducibility
3510
4853
  };
3511
4854
  //# sourceMappingURL=index.js.map