@toon-protocol/sdk 0.2.0 → 0.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
@@ -1,3 +1,5 @@
1
+ import "./chunk-UP2VWCW5.js";
2
+
1
3
  // src/identity.ts
2
4
  import {
3
5
  generateMnemonic as _generateMnemonic,
@@ -140,7 +142,6 @@ function toChecksumAddress(addressHex) {
140
142
  }
141
143
 
142
144
  // src/handler-context.ts
143
- import { createHash } from "crypto";
144
145
  function createHandlerContext(options) {
145
146
  let cachedEvent;
146
147
  return {
@@ -166,11 +167,8 @@ function createHandlerContext(options) {
166
167
  return cachedEvent;
167
168
  },
168
169
  accept(metadata) {
169
- const toonBytes = Buffer.from(options.toon, "base64");
170
- const fulfillment = createHash("sha256").update(toonBytes).digest().toString("base64");
171
170
  return {
172
171
  accept: true,
173
- fulfillment,
174
172
  ...metadata ? { metadata } : {}
175
173
  };
176
174
  },
@@ -381,7 +379,15 @@ import {
381
379
  buildIlpPrepare,
382
380
  buildJobFeedbackEvent,
383
381
  buildJobResultEvent,
384
- parseJobResult
382
+ parseJobResult,
383
+ deriveChildAddress,
384
+ checkAddressCollision,
385
+ AddressRegistry,
386
+ ILP_ROOT_PREFIX,
387
+ calculateRouteAmount,
388
+ resolveRouteFees,
389
+ buildPrefixClaimEvent,
390
+ validatePrefix
385
391
  } from "@toon-protocol/core";
386
392
  import {
387
393
  shallowParseToon,
@@ -390,11 +396,21 @@ import {
390
396
  } from "@toon-protocol/core/toon";
391
397
 
392
398
  // src/skill-descriptor.ts
399
+ import { ToonError as ToonError2 } from "@toon-protocol/core";
400
+ var HEX_64_REGEX = /^[0-9a-f]{64}$/;
393
401
  function buildSkillDescriptor(registry, config = {}) {
394
402
  const dvmKinds = registry.getDvmKinds();
395
403
  if (dvmKinds.length === 0) {
396
404
  return void 0;
397
405
  }
406
+ if (config.attestation !== void 0) {
407
+ if (!HEX_64_REGEX.test(config.attestation.eventId)) {
408
+ throw new ToonError2(
409
+ "Skill descriptor attestation eventId must be a 64-character lowercase hex string",
410
+ "DVM_SKILL_INVALID_ATTESTATION_EVENT_ID"
411
+ );
412
+ }
413
+ }
398
414
  const basePricePerByte = config.basePricePerByte ?? 10n;
399
415
  const kindPricing = config.kindPricing ?? {};
400
416
  const pricing = {};
@@ -416,6 +432,15 @@ function buildSkillDescriptor(registry, config = {}) {
416
432
  if (config.models !== void 0) {
417
433
  descriptor.models = config.models;
418
434
  }
435
+ if (config.reputation !== void 0) {
436
+ descriptor.reputation = config.reputation;
437
+ }
438
+ if (config.attestation !== void 0) {
439
+ descriptor.attestation = {
440
+ eventId: config.attestation.eventId,
441
+ enclaveImageHash: config.attestation.enclaveImageHash
442
+ };
443
+ }
419
444
  return descriptor;
420
445
  }
421
446
 
@@ -578,11 +603,54 @@ function createNode(config) {
578
603
  return { accept: false, code: "T00", message: "Internal error" };
579
604
  }
580
605
  };
606
+ let resolvedIlpAddress;
607
+ let resolvedIlpAddresses;
608
+ if (config.upstreamPrefixes !== void 0 && config.upstreamPrefixes.length > 0) {
609
+ if (config.upstreamPrefix !== void 0) {
610
+ console.warn(
611
+ "[toon:warn] Both upstreamPrefixes and upstreamPrefix set; upstreamPrefixes takes priority"
612
+ );
613
+ }
614
+ resolvedIlpAddresses = config.upstreamPrefixes.map(
615
+ (p) => deriveChildAddress(p, pubkey)
616
+ );
617
+ for (let i = 0; i < resolvedIlpAddresses.length; i++) {
618
+ const others = resolvedIlpAddresses.filter((_, j) => j !== i);
619
+ checkAddressCollision(resolvedIlpAddresses[i], others);
620
+ }
621
+ resolvedIlpAddress = resolvedIlpAddresses[0];
622
+ } else if (config.upstreamPrefix !== void 0) {
623
+ resolvedIlpAddress = deriveChildAddress(config.upstreamPrefix, pubkey);
624
+ resolvedIlpAddresses = [resolvedIlpAddress];
625
+ } else if (config.ilpAddress !== void 0) {
626
+ resolvedIlpAddress = config.ilpAddress;
627
+ resolvedIlpAddresses = [resolvedIlpAddress];
628
+ } else {
629
+ resolvedIlpAddress = deriveChildAddress(ILP_ROOT_PREFIX, pubkey);
630
+ resolvedIlpAddresses = [resolvedIlpAddress];
631
+ }
632
+ const addressRegistry = new AddressRegistry();
633
+ if (config.upstreamPrefixes !== void 0 && config.upstreamPrefixes.length > 0) {
634
+ for (let i = 0; i < config.upstreamPrefixes.length; i++) {
635
+ addressRegistry.addAddress(
636
+ config.upstreamPrefixes[i],
637
+ resolvedIlpAddresses[i]
638
+ );
639
+ }
640
+ } else if (config.upstreamPrefix !== void 0) {
641
+ addressRegistry.addAddress(config.upstreamPrefix, resolvedIlpAddress);
642
+ } else if (config.ilpAddress !== void 0) {
643
+ addressRegistry.addAddress("explicit", resolvedIlpAddress);
644
+ } else {
645
+ addressRegistry.addAddress(ILP_ROOT_PREFIX, resolvedIlpAddress);
646
+ }
581
647
  const ilpInfo = {
582
- ilpAddress: config.ilpAddress ?? "g.toon.local",
648
+ ilpAddress: resolvedIlpAddress,
649
+ ilpAddresses: resolvedIlpAddresses,
583
650
  btpEndpoint: config.btpEndpoint ?? "",
584
651
  assetCode: config.assetCode ?? "USD",
585
- assetScale: config.assetScale ?? 6
652
+ assetScale: config.assetScale ?? 6,
653
+ feePerByte: String(config.feePerByte ?? 0n)
586
654
  };
587
655
  let ilpClient;
588
656
  let adminClient;
@@ -667,6 +735,7 @@ function createNode(config) {
667
735
  keyBuffer.fill(0);
668
736
  }
669
737
  const hasSettlementAddresses = chainConfig.registryAddress && chainConfig.tokenNetworkAddress;
738
+ const hasChainProviders = config.chainProviders !== void 0 && config.chainProviders.length > 0;
670
739
  autoCreatedConnector = new ConnectorNodeClass(
671
740
  {
672
741
  nodeId,
@@ -676,7 +745,12 @@ function createNode(config) {
676
745
  peers: [],
677
746
  routes: [],
678
747
  localDelivery: { enabled: false },
679
- ...hasSettlementAddresses && {
748
+ // Multi-chain: use chainProviders when provided
749
+ ...hasChainProviders && {
750
+ chainProviders: config.chainProviders
751
+ },
752
+ // Legacy: fall back to settlementInfra when chainProviders absent
753
+ ...!hasChainProviders && hasSettlementAddresses && {
680
754
  settlementInfra: {
681
755
  enabled: true,
682
756
  rpcUrl: chainConfig.rpcUrl,
@@ -684,7 +758,9 @@ function createNode(config) {
684
758
  tokenAddress: chainConfig.usdcAddress,
685
759
  privateKey: settlementPrivateKey
686
760
  }
687
- }
761
+ },
762
+ // NIP-59 transport privacy
763
+ ...config.nip59 && { nip59: config.nip59 }
688
764
  },
689
765
  connectorLogger
690
766
  );
@@ -721,11 +797,13 @@ function createNode(config) {
721
797
  discoveryTrackerInstance.setChannelClient(toonNode.channelClient);
722
798
  }
723
799
  trackerRef.current = discoveryTrackerInstance;
724
- autoCreatedConnector.addRoute({
725
- prefix: ilpInfo.ilpAddress,
726
- nextHop: nodeId,
727
- priority: 100
728
- });
800
+ for (const addr of ilpInfo.ilpAddresses) {
801
+ autoCreatedConnector.addRoute({
802
+ prefix: addr,
803
+ nextHop: nodeId,
804
+ priority: 100
805
+ });
806
+ }
729
807
  const result = await toonNode.start();
730
808
  return result;
731
809
  };
@@ -842,6 +920,7 @@ function createNode(config) {
842
920
  };
843
921
  }
844
922
  let started = false;
923
+ let lastBootstrapResults = [];
845
924
  const node = {
846
925
  get pubkey() {
847
926
  return pubkey;
@@ -893,6 +972,7 @@ function createNode(config) {
893
972
  try {
894
973
  const result = await doStart();
895
974
  started = true;
975
+ lastBootstrapResults = result.bootstrapResults;
896
976
  return {
897
977
  peerCount: result.peerCount,
898
978
  channelCount: result.channelCount,
@@ -941,7 +1021,35 @@ function createNode(config) {
941
1021
  }
942
1022
  try {
943
1023
  const toonData = encoder(event);
944
- const amount = (config.basePricePerByte ?? 10n) * BigInt(toonData.length);
1024
+ const { hopFees, warnings } = resolveRouteFees({
1025
+ destination: options.destination,
1026
+ ownIlpAddress: ilpInfo.ilpAddress,
1027
+ discoveredPeers: discoveryTrackerInstance.getAllDiscoveredPeers()
1028
+ });
1029
+ for (const warning of warnings) {
1030
+ console.warn(`[publishEvent] ${warning}`);
1031
+ }
1032
+ const destinationAmount = options.amount ?? (config.basePricePerByte ?? 10n) * BigInt(toonData.length);
1033
+ if (options.bid !== void 0 && destinationAmount > options.bid) {
1034
+ throw new NodeError(
1035
+ `Cannot publish: destination amount ${destinationAmount} exceeds bid safety cap ${options.bid}`
1036
+ );
1037
+ }
1038
+ let amount;
1039
+ if (options.amount !== void 0) {
1040
+ const hopFeesTotal = calculateRouteAmount({
1041
+ basePricePerByte: 0n,
1042
+ packetByteLength: toonData.length,
1043
+ hopFees
1044
+ });
1045
+ amount = options.amount + hopFeesTotal;
1046
+ } else {
1047
+ amount = calculateRouteAmount({
1048
+ basePricePerByte: config.basePricePerByte ?? 10n,
1049
+ packetByteLength: toonData.length,
1050
+ hopFees
1051
+ });
1052
+ }
945
1053
  const packet = buildIlpPrepare({
946
1054
  destination: options.destination,
947
1055
  amount,
@@ -952,7 +1060,7 @@ function createNode(config) {
952
1060
  return {
953
1061
  success: true,
954
1062
  eventId: event.id,
955
- fulfillment: result.fulfillment ?? ""
1063
+ ...result.data !== void 0 ? { data: result.data } : {}
956
1064
  };
957
1065
  }
958
1066
  return {
@@ -987,6 +1095,9 @@ function createNode(config) {
987
1095
  return node.publishEvent(resultEvent, options);
988
1096
  },
989
1097
  async settleCompute(resultEvent, providerIlpAddress, options) {
1098
+ console.warn(
1099
+ "[settleCompute] DEPRECATED: Use publishEvent() with { amount } option instead. The prepaid model sends job request + payment in one packet."
1100
+ );
990
1101
  if (!started) {
991
1102
  throw new NodeError(
992
1103
  "Cannot settle compute: node not started. Call start() first."
@@ -1037,26 +1148,944 @@ function createNode(config) {
1037
1148
  amount: computeAmount,
1038
1149
  data: ""
1039
1150
  });
1151
+ },
1152
+ // Task 6.3: Address lifecycle management for multi-peered nodes
1153
+ addUpstreamPeer(upstreamPrefix) {
1154
+ const derivedAddress = deriveChildAddress(upstreamPrefix, pubkey);
1155
+ checkAddressCollision(derivedAddress, addressRegistry.getAddresses());
1156
+ addressRegistry.addAddress(upstreamPrefix, derivedAddress);
1157
+ ilpInfo.ilpAddresses = addressRegistry.getAddresses();
1158
+ ilpInfo.ilpAddress = addressRegistry.getPrimaryAddress() ?? ilpInfo.ilpAddress;
1159
+ if (autoCreatedConnector) {
1160
+ const nodeId = `toon-${pubkey.slice(0, 16)}`;
1161
+ autoCreatedConnector.addRoute({
1162
+ prefix: derivedAddress,
1163
+ nextHop: nodeId,
1164
+ priority: 100
1165
+ });
1166
+ }
1167
+ if (started && lastBootstrapResults.length > 0) {
1168
+ void bootstrapServiceInstance.republish(lastBootstrapResults);
1169
+ }
1170
+ },
1171
+ removeUpstreamPeer(upstreamPrefix) {
1172
+ if (addressRegistry.hasPrefix(upstreamPrefix) && addressRegistry.size <= 1) {
1173
+ throw new NodeError(
1174
+ "Cannot remove last upstream peer: a node must have at least one ILP address"
1175
+ );
1176
+ }
1177
+ const removedAddress = addressRegistry.removeAddress(upstreamPrefix);
1178
+ if (removedAddress === void 0) {
1179
+ return;
1180
+ }
1181
+ ilpInfo.ilpAddresses = addressRegistry.getAddresses();
1182
+ const newPrimary = addressRegistry.getPrimaryAddress();
1183
+ if (newPrimary !== void 0) {
1184
+ ilpInfo.ilpAddress = newPrimary;
1185
+ }
1186
+ if (autoCreatedConnector && autoCreatedConnector.removeRoute) {
1187
+ autoCreatedConnector.removeRoute(removedAddress);
1188
+ }
1189
+ if (started && lastBootstrapResults.length > 0) {
1190
+ void bootstrapServiceInstance.republish(lastBootstrapResults);
1191
+ }
1192
+ },
1193
+ async claimPrefix(prefix, upstreamDestination, options) {
1194
+ if (!started) {
1195
+ throw new NodeError(
1196
+ "Cannot claim prefix: node not started. Call start() first."
1197
+ );
1198
+ }
1199
+ const prefixValidation = validatePrefix(prefix);
1200
+ if (!prefixValidation.valid) {
1201
+ throw new NodeError(
1202
+ `Cannot claim prefix: ${prefixValidation.reason ?? "invalid prefix"}`
1203
+ );
1204
+ }
1205
+ let claimAmount = options?.prefixPrice;
1206
+ if (claimAmount === void 0) {
1207
+ const peers = discoveryTrackerInstance.getAllDiscoveredPeers();
1208
+ const upstreamPeer = peers.find(
1209
+ (p) => p.peerInfo.ilpAddress === upstreamDestination || p.peerInfo.ilpAddresses && p.peerInfo.ilpAddresses.includes(upstreamDestination)
1210
+ );
1211
+ if (upstreamPeer?.peerInfo.prefixPricing?.basePrice) {
1212
+ claimAmount = BigInt(upstreamPeer.peerInfo.prefixPricing.basePrice);
1213
+ } else {
1214
+ throw new NodeError(
1215
+ "Cannot claim prefix: no amount provided and upstream peer prefix pricing not found in discovery"
1216
+ );
1217
+ }
1218
+ }
1219
+ const claimEvent = buildPrefixClaimEvent(
1220
+ { requestedPrefix: prefix },
1221
+ config.secretKey
1222
+ );
1223
+ return node.publishEvent(claimEvent, {
1224
+ destination: upstreamDestination,
1225
+ amount: claimAmount
1226
+ });
1040
1227
  }
1041
1228
  };
1042
1229
  return node;
1043
1230
  }
1231
+
1232
+ // src/workflow-orchestrator.ts
1233
+ import { buildJobRequestEvent, parseJobFeedback } from "@toon-protocol/core";
1234
+ var WorkflowOrchestrator = class {
1235
+ node;
1236
+ options;
1237
+ state = "pending";
1238
+ definition = null;
1239
+ workflowEventId = "0".repeat(64);
1240
+ customerPubkey = "0".repeat(64);
1241
+ currentStepIndex = 0;
1242
+ stepStates = [];
1243
+ timeoutHandle = null;
1244
+ processedResultIds = /* @__PURE__ */ new Set();
1245
+ constructor(node, options) {
1246
+ this.node = node;
1247
+ this.options = {
1248
+ stepTimeoutMs: 3e5,
1249
+ // 5 minutes default
1250
+ ...options
1251
+ };
1252
+ if (options?.workflowEventId) {
1253
+ this.workflowEventId = options.workflowEventId;
1254
+ }
1255
+ if (options?.customerPubkey) {
1256
+ this.customerPubkey = options.customerPubkey;
1257
+ }
1258
+ }
1259
+ /**
1260
+ * Returns the current workflow state.
1261
+ */
1262
+ getState() {
1263
+ return this.state;
1264
+ }
1265
+ /**
1266
+ * Returns per-step state tracking data (for testing/debugging).
1267
+ */
1268
+ getStepStates() {
1269
+ return this.stepStates;
1270
+ }
1271
+ /**
1272
+ * Starts a workflow: parses the definition, creates and publishes
1273
+ * the Kind 5xxx job request for step 1, sets state to step_1_running.
1274
+ */
1275
+ async startWorkflow(definition) {
1276
+ if (this.state !== "pending") {
1277
+ throw new Error(
1278
+ "WorkflowOrchestrator.startWorkflow() called on a non-pending orchestrator. Create a new WorkflowOrchestrator instance for each workflow."
1279
+ );
1280
+ }
1281
+ if (!definition.steps || definition.steps.length === 0) {
1282
+ throw new Error(
1283
+ "WorkflowOrchestrator.startWorkflow() requires at least one step in the definition."
1284
+ );
1285
+ }
1286
+ this.definition = definition;
1287
+ this.stepStates = definition.steps.map((_, index) => ({
1288
+ index,
1289
+ settled: false
1290
+ }));
1291
+ if (this.options.eventStore) {
1292
+ const now = this.options.now ? this.options.now() : Date.now();
1293
+ const syntheticId = `wf${now.toString(16).padStart(16, "0")}${this.node.pubkey.slice(0, 48)}`;
1294
+ const stateEvent = {
1295
+ id: syntheticId.padEnd(64, "0").slice(0, 64),
1296
+ pubkey: this.node.pubkey,
1297
+ created_at: Math.floor(now / 1e3),
1298
+ kind: 10040,
1299
+ content: JSON.stringify({
1300
+ state: "started",
1301
+ definition
1302
+ }),
1303
+ tags: [],
1304
+ sig: "0".repeat(128)
1305
+ };
1306
+ await this.options.eventStore.store(stateEvent);
1307
+ }
1308
+ await this.publishStepRequest(
1309
+ 0,
1310
+ definition.initialInput.data,
1311
+ definition.initialInput.type
1312
+ );
1313
+ this.state = "step_1_running";
1314
+ this.currentStepIndex = 0;
1315
+ this.startStepTimeout();
1316
+ }
1317
+ /**
1318
+ * Handles a Kind 6xxx result event for the current step.
1319
+ *
1320
+ * If the current step matches, extracts the result content and either:
1321
+ * - Advances to the next step (publishes Kind 5xxx for step N+1)
1322
+ * - Marks workflow as completed (if this was the final step)
1323
+ *
1324
+ * Idempotent: re-processing a result for an already-advanced step is a no-op.
1325
+ */
1326
+ async handleStepResult(resultEvent) {
1327
+ if (!this.definition) return;
1328
+ if (resultEvent.kind < 6e3 || resultEvent.kind > 6999) return;
1329
+ if (this.processedResultIds.has(resultEvent.id)) return;
1330
+ const currentStep = this.stepStates[this.currentStepIndex];
1331
+ if (!currentStep) return;
1332
+ if (currentStep.resultEvent) return;
1333
+ if (this.state === "completed" || this.state.endsWith("_failed")) return;
1334
+ this.processedResultIds.add(resultEvent.id);
1335
+ this.clearStepTimeout();
1336
+ currentStep.resultEvent = resultEvent;
1337
+ await this.settleStep(this.currentStepIndex, resultEvent);
1338
+ if (this.options.eventStore) {
1339
+ await this.options.eventStore.store(resultEvent);
1340
+ }
1341
+ const isLastStep = this.currentStepIndex === this.definition.steps.length - 1;
1342
+ if (isLastStep) {
1343
+ this.state = "completed";
1344
+ await this.publishWorkflowNotification(
1345
+ "success",
1346
+ "Workflow completed successfully"
1347
+ );
1348
+ } else {
1349
+ const nextIndex = this.currentStepIndex + 1;
1350
+ const resultContent = resultEvent.content;
1351
+ await this.publishStepRequest(nextIndex, resultContent, "text");
1352
+ this.currentStepIndex = nextIndex;
1353
+ this.state = `step_${nextIndex + 1}_running`;
1354
+ this.startStepTimeout();
1355
+ }
1356
+ }
1357
+ /**
1358
+ * Handles a Kind 7000 feedback event for the current step.
1359
+ *
1360
+ * If the feedback status is 'error', marks the workflow as failed
1361
+ * and notifies the customer.
1362
+ */
1363
+ async handleStepFeedback(feedbackEvent) {
1364
+ if (!this.definition) return;
1365
+ if (this.state === "completed" || this.state.endsWith("_failed")) return;
1366
+ const parsed = parseJobFeedback(feedbackEvent);
1367
+ if (!parsed) return;
1368
+ if (parsed.status === "error") {
1369
+ this.clearStepTimeout();
1370
+ this.state = `step_${this.currentStepIndex + 1}_failed`;
1371
+ if (this.options.eventStore) {
1372
+ await this.options.eventStore.store(feedbackEvent);
1373
+ }
1374
+ await this.publishWorkflowNotification(
1375
+ "error",
1376
+ `Workflow failed at step ${this.currentStepIndex + 1}: ${parsed.content || "Unknown error"}`
1377
+ );
1378
+ }
1379
+ }
1380
+ /**
1381
+ * Cleans up resources (timeout handles).
1382
+ */
1383
+ destroy() {
1384
+ this.clearStepTimeout();
1385
+ }
1386
+ // ---------- Private Methods ----------
1387
+ /**
1388
+ * Publishes a Kind 5xxx job request for the given step.
1389
+ */
1390
+ async publishStepRequest(stepIndex, inputData, inputType) {
1391
+ if (!this.definition) return;
1392
+ const step = this.definition.steps[stepIndex];
1393
+ if (!step) return;
1394
+ const stepBid = this.getStepBid(stepIndex);
1395
+ const jobRequest = buildJobRequestEvent(
1396
+ {
1397
+ kind: step.kind,
1398
+ input: { data: inputData, type: inputType },
1399
+ bid: stepBid,
1400
+ output: "text/plain",
1401
+ content: step.description,
1402
+ targetProvider: step.targetProvider
1403
+ },
1404
+ this.getSecretKey()
1405
+ );
1406
+ const destination = this.options.destination ?? "g.toon.local";
1407
+ await this.node.publishEvent(jobRequest, { destination });
1408
+ const stepState = this.stepStates[stepIndex];
1409
+ if (stepState) {
1410
+ stepState.requestEventId = jobRequest.id;
1411
+ }
1412
+ }
1413
+ /**
1414
+ * Settles compute payment for a completed step.
1415
+ */
1416
+ async settleStep(stepIndex, resultEvent) {
1417
+ if (!this.definition) return;
1418
+ const stepState = this.stepStates[stepIndex];
1419
+ if (!stepState || stepState.settled) return;
1420
+ const step = this.definition.steps[stepIndex];
1421
+ if (!step) return;
1422
+ const providerIlpAddress = this.options.destination ?? "g.toon.provider";
1423
+ try {
1424
+ await this.node.settleCompute(resultEvent, providerIlpAddress, {
1425
+ originalBid: this.getStepBid(stepIndex)
1426
+ });
1427
+ stepState.settled = true;
1428
+ } catch (_err) {
1429
+ }
1430
+ }
1431
+ /**
1432
+ * Returns the bid allocation for a specific step.
1433
+ * Uses explicit bidAllocation if set, otherwise proportional split.
1434
+ */
1435
+ getStepBid(stepIndex) {
1436
+ if (!this.definition) return "0";
1437
+ const step = this.definition.steps[stepIndex];
1438
+ if (!step) return "0";
1439
+ if (step.bidAllocation !== void 0) {
1440
+ return step.bidAllocation;
1441
+ }
1442
+ try {
1443
+ const totalBid = BigInt(this.definition.totalBid);
1444
+ const stepCount = BigInt(this.definition.steps.length);
1445
+ const perStep = totalBid / stepCount;
1446
+ return perStep.toString();
1447
+ } catch {
1448
+ return "0";
1449
+ }
1450
+ }
1451
+ /**
1452
+ * Publishes a Kind 7000 workflow notification to the customer.
1453
+ */
1454
+ async publishWorkflowNotification(status, content) {
1455
+ const destination = this.options.destination ?? "g.toon.local";
1456
+ try {
1457
+ await this.node.publishFeedback(
1458
+ this.workflowEventId,
1459
+ this.customerPubkey,
1460
+ status,
1461
+ content,
1462
+ { destination }
1463
+ );
1464
+ } catch (_err) {
1465
+ }
1466
+ }
1467
+ /**
1468
+ * Starts a timeout timer for the current step.
1469
+ */
1470
+ startStepTimeout() {
1471
+ this.clearStepTimeout();
1472
+ const timeoutMs = this.options.stepTimeoutMs;
1473
+ const timerFn = this.options.setTimer ?? setTimeout;
1474
+ this.timeoutHandle = timerFn(() => {
1475
+ this.state = `step_${this.currentStepIndex + 1}_failed`;
1476
+ void this.publishWorkflowNotification(
1477
+ "error",
1478
+ `Workflow timed out at step ${this.currentStepIndex + 1} after ${timeoutMs}ms`
1479
+ );
1480
+ }, timeoutMs);
1481
+ }
1482
+ /**
1483
+ * Returns the secret key for signing step events.
1484
+ * Falls back to a deterministic key derived from the node pubkey
1485
+ * (for testing -- production should always pass secretKey in options).
1486
+ */
1487
+ getSecretKey() {
1488
+ if (this.options.secretKey) {
1489
+ return this.options.secretKey;
1490
+ }
1491
+ throw new Error(
1492
+ "WorkflowOrchestrator requires secretKey in options to sign step events"
1493
+ );
1494
+ }
1495
+ /**
1496
+ * Clears the current step timeout.
1497
+ */
1498
+ clearStepTimeout() {
1499
+ if (this.timeoutHandle !== null) {
1500
+ const cancelFn = this.options.clearTimer ?? clearTimeout;
1501
+ cancelFn(this.timeoutHandle);
1502
+ this.timeoutHandle = null;
1503
+ }
1504
+ }
1505
+ };
1506
+
1507
+ // src/swarm-coordinator.ts
1508
+ import {
1509
+ ToonError as ToonError3,
1510
+ parseSwarmRequest,
1511
+ parseSwarmSelection
1512
+ } from "@toon-protocol/core";
1513
+ var SwarmCoordinator = class {
1514
+ node;
1515
+ options;
1516
+ state = "collecting";
1517
+ swarmRequestId = "";
1518
+ customerPubkey = "";
1519
+ maxProviders = 0;
1520
+ submissions = [];
1521
+ timeoutHandle = null;
1522
+ started = false;
1523
+ settlementSucceeded = false;
1524
+ submissionIds = /* @__PURE__ */ new Set();
1525
+ constructor(node, options) {
1526
+ this.node = node;
1527
+ this.options = {
1528
+ timeoutMs: 6e5,
1529
+ // 10 minutes default
1530
+ ...options
1531
+ };
1532
+ }
1533
+ /**
1534
+ * Returns the current swarm state.
1535
+ */
1536
+ getState() {
1537
+ return this.state;
1538
+ }
1539
+ /**
1540
+ * Returns whether settlement was successfully completed.
1541
+ * Only meaningful when state is 'settled' (returns true).
1542
+ * If settlement fails, the state remains 'judging' and this returns false.
1543
+ */
1544
+ isSettlementSucceeded() {
1545
+ return this.settlementSucceeded;
1546
+ }
1547
+ /**
1548
+ * Returns the collected eligible submissions.
1549
+ */
1550
+ getSubmissions() {
1551
+ return this.submissions;
1552
+ }
1553
+ /**
1554
+ * Starts a swarm: parses the swarm request, initializes collection,
1555
+ * and starts the timeout timer.
1556
+ */
1557
+ async startSwarm(swarmRequest) {
1558
+ const parsed = parseSwarmRequest(swarmRequest);
1559
+ if (!parsed) {
1560
+ throw new ToonError3(
1561
+ "Invalid swarm request event: missing swarm tag or invalid Kind 5xxx",
1562
+ "DVM_INVALID_KIND"
1563
+ );
1564
+ }
1565
+ this.swarmRequestId = swarmRequest.id;
1566
+ this.customerPubkey = swarmRequest.pubkey;
1567
+ this.maxProviders = parsed.maxProviders;
1568
+ this.state = "collecting";
1569
+ this.submissions = [];
1570
+ this.submissionIds = /* @__PURE__ */ new Set();
1571
+ this.started = true;
1572
+ if (this.options.eventStore) {
1573
+ await this.options.eventStore.store(swarmRequest);
1574
+ }
1575
+ this.startTimeout();
1576
+ }
1577
+ /**
1578
+ * Handles a Kind 6xxx result event (provider submission).
1579
+ *
1580
+ * Validates the `e` tag references the swarm request, adds to the
1581
+ * submissions list, and checks if max providers is reached.
1582
+ *
1583
+ * Late submissions (after timeout or max reached) are ignored.
1584
+ * Submissions before startSwarm() are silently ignored.
1585
+ */
1586
+ async handleSubmission(resultEvent) {
1587
+ if (!this.started) return;
1588
+ if (this.state !== "collecting") return;
1589
+ if (resultEvent.kind < 6e3 || resultEvent.kind > 6999) return;
1590
+ const eTag = resultEvent.tags.find((t) => t[0] === "e");
1591
+ if (!eTag) return;
1592
+ const requestEventId = eTag[1];
1593
+ if (requestEventId === void 0 || requestEventId !== this.swarmRequestId)
1594
+ return;
1595
+ if (this.submissionIds.has(resultEvent.id)) return;
1596
+ this.submissionIds.add(resultEvent.id);
1597
+ this.submissions.push(resultEvent);
1598
+ if (this.options.eventStore) {
1599
+ await this.options.eventStore.store(resultEvent);
1600
+ }
1601
+ if (this.submissions.length >= this.maxProviders) {
1602
+ this.clearTimeout();
1603
+ this.state = "judging";
1604
+ }
1605
+ }
1606
+ /**
1607
+ * Selects the winner and settles compute payment.
1608
+ *
1609
+ * Validates:
1610
+ * - Swarm is in `judging` state (not `collecting`, `settled`, or `failed`)
1611
+ * - Selection references a submission in the collected set
1612
+ * - Swarm has not already been settled (idempotency guard)
1613
+ *
1614
+ * @throws ToonError with code DVM_SWARM_ALREADY_SETTLED if already settled
1615
+ * @throws ToonError with code DVM_SWARM_INVALID_SELECTION if winner not in submissions
1616
+ */
1617
+ async selectWinner(selectionEvent) {
1618
+ if (this.state === "settled") {
1619
+ throw new ToonError3(
1620
+ "Swarm has already been settled; duplicate selection rejected",
1621
+ "DVM_SWARM_ALREADY_SETTLED"
1622
+ );
1623
+ }
1624
+ if (this.state !== "judging") {
1625
+ throw new ToonError3(
1626
+ `Cannot select winner in state '${this.state}'; swarm must be in 'judging' state`,
1627
+ "DVM_SWARM_INVALID_SELECTION"
1628
+ );
1629
+ }
1630
+ if (selectionEvent.pubkey !== this.customerPubkey) {
1631
+ throw new ToonError3(
1632
+ "Selection event pubkey does not match the swarm request customer pubkey",
1633
+ "DVM_SWARM_INVALID_SELECTION"
1634
+ );
1635
+ }
1636
+ const parsed = parseSwarmSelection(selectionEvent);
1637
+ if (!parsed) {
1638
+ throw new ToonError3(
1639
+ "Invalid swarm selection event: missing winner tag or invalid Kind 7000",
1640
+ "DVM_SWARM_INVALID_SELECTION"
1641
+ );
1642
+ }
1643
+ if (parsed.swarmRequestEventId !== this.swarmRequestId) {
1644
+ throw new ToonError3(
1645
+ `Selection event references swarm '${parsed.swarmRequestEventId}' but this swarm is '${this.swarmRequestId}'`,
1646
+ "DVM_SWARM_INVALID_SELECTION"
1647
+ );
1648
+ }
1649
+ const winnerSubmission = this.submissions.find(
1650
+ (s) => s.id === parsed.winnerResultEventId
1651
+ );
1652
+ if (!winnerSubmission) {
1653
+ throw new ToonError3(
1654
+ `Winner result event ID '${parsed.winnerResultEventId}' not found in collected submissions`,
1655
+ "DVM_SWARM_INVALID_SELECTION"
1656
+ );
1657
+ }
1658
+ const providerIlpAddress = this.options.destination ?? "g.toon.provider";
1659
+ try {
1660
+ await this.node.settleCompute(winnerSubmission, providerIlpAddress);
1661
+ this.settlementSucceeded = true;
1662
+ } catch (_err) {
1663
+ this.settlementSucceeded = false;
1664
+ throw new ToonError3(
1665
+ "Settlement failed for winning provider; swarm remains in judging state for retry",
1666
+ "DVM_SWARM_SETTLEMENT_FAILED"
1667
+ );
1668
+ }
1669
+ if (this.options.eventStore) {
1670
+ await this.options.eventStore.store(selectionEvent);
1671
+ }
1672
+ this.state = "settled";
1673
+ }
1674
+ /**
1675
+ * Cleans up resources (timeout handles).
1676
+ */
1677
+ destroy() {
1678
+ this.clearTimeout();
1679
+ }
1680
+ // ---------- Private Methods ----------
1681
+ /**
1682
+ * Starts the collection timeout timer.
1683
+ */
1684
+ startTimeout() {
1685
+ this.clearTimeout();
1686
+ const timeoutMs = this.options.timeoutMs;
1687
+ const timerFn = this.options.setTimer ?? setTimeout;
1688
+ this.timeoutHandle = timerFn(() => {
1689
+ if (this.state !== "collecting") return;
1690
+ if (this.submissions.length === 0) {
1691
+ this.state = "failed";
1692
+ void this.publishNoSubmissionsFeedback();
1693
+ } else {
1694
+ this.state = "judging";
1695
+ }
1696
+ }, timeoutMs);
1697
+ }
1698
+ /**
1699
+ * Publishes a Kind 7000 feedback event indicating no submissions were received.
1700
+ */
1701
+ async publishNoSubmissionsFeedback() {
1702
+ const destination = this.options.destination ?? "g.toon.local";
1703
+ try {
1704
+ await this.node.publishFeedback(
1705
+ this.swarmRequestId,
1706
+ this.customerPubkey,
1707
+ "error",
1708
+ "Swarm timed out with no submissions",
1709
+ { destination }
1710
+ );
1711
+ } catch (_err) {
1712
+ }
1713
+ }
1714
+ /**
1715
+ * Clears the current timeout timer.
1716
+ */
1717
+ clearTimeout() {
1718
+ if (this.timeoutHandle !== null) {
1719
+ const cancelFn = this.options.clearTimer ?? clearTimeout;
1720
+ cancelFn(this.timeoutHandle);
1721
+ this.timeoutHandle = null;
1722
+ }
1723
+ }
1724
+ };
1725
+
1726
+ // src/prefix-claim-handler.ts
1727
+ import {
1728
+ parsePrefixClaimEvent,
1729
+ buildPrefixGrantEvent,
1730
+ validatePrefix as validatePrefix2
1731
+ } from "@toon-protocol/core";
1732
+ function createPrefixClaimHandler(options) {
1733
+ return async (ctx) => {
1734
+ const event = ctx.decode();
1735
+ const claim = parsePrefixClaimEvent(event);
1736
+ if (!claim) {
1737
+ return ctx.reject("F06", "Invalid prefix claim event: malformed content");
1738
+ }
1739
+ const validation = validatePrefix2(claim.requestedPrefix);
1740
+ if (!validation.valid) {
1741
+ return ctx.reject(
1742
+ "F06",
1743
+ `Invalid prefix: ${validation.reason ?? "unknown validation error"}`
1744
+ );
1745
+ }
1746
+ if (ctx.amount < options.prefixPricing.basePrice) {
1747
+ return ctx.reject(
1748
+ "F06",
1749
+ `Insufficient payment: received ${ctx.amount}, required ${options.prefixPricing.basePrice}`
1750
+ );
1751
+ }
1752
+ const claimedPrefixes = options.getClaimedPrefixes();
1753
+ if (claimedPrefixes.has(claim.requestedPrefix)) {
1754
+ return ctx.reject(
1755
+ "F06",
1756
+ `PREFIX_TAKEN: prefix "${claim.requestedPrefix}" is already claimed`
1757
+ );
1758
+ }
1759
+ const claimed = options.claimPrefix(claim.requestedPrefix, event.pubkey);
1760
+ if (!claimed) {
1761
+ return ctx.reject(
1762
+ "F06",
1763
+ `PREFIX_TAKEN: prefix "${claim.requestedPrefix}" is already claimed`
1764
+ );
1765
+ }
1766
+ const prefix = options.ilpAddressPrefix ?? "g.toon";
1767
+ const ilpAddress = `${prefix}.${claim.requestedPrefix}`;
1768
+ const grantEvent = buildPrefixGrantEvent(
1769
+ {
1770
+ grantedPrefix: claim.requestedPrefix,
1771
+ claimerPubkey: event.pubkey,
1772
+ ilpAddress
1773
+ },
1774
+ options.secretKey
1775
+ );
1776
+ await options.publishGrant(grantEvent);
1777
+ return ctx.accept();
1778
+ };
1779
+ }
1780
+
1781
+ // src/arweave/arweave-dvm-handler.ts
1782
+ import { parseBlobStorageRequest } from "@toon-protocol/core";
1783
+ var MIME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9!#$&\-^_.+]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-^_.+]*$/;
1784
+ function sanitizeContentType(contentType) {
1785
+ const base = (contentType.split(";")[0] ?? "").trim();
1786
+ if (base.length > 0 && base.length <= 255 && MIME_REGEX.test(base)) {
1787
+ return base;
1788
+ }
1789
+ return "application/octet-stream";
1790
+ }
1791
+ function createArweaveDvmHandler(config) {
1792
+ const { turboAdapter, chunkManager, arweaveTags } = config;
1793
+ return async (ctx) => {
1794
+ const event = ctx.decode();
1795
+ const parsed = parseBlobStorageRequest(event);
1796
+ if (!parsed) {
1797
+ return {
1798
+ accept: false,
1799
+ code: "F00",
1800
+ message: "Malformed kind:5094 blob storage request"
1801
+ };
1802
+ }
1803
+ const uploadTags = {
1804
+ ...arweaveTags,
1805
+ "Content-Type": sanitizeContentType(parsed.contentType)
1806
+ };
1807
+ if (parsed.uploadId !== void 0 && parsed.chunkIndex !== void 0 && parsed.totalChunks !== void 0) {
1808
+ try {
1809
+ const result = chunkManager.addChunk(
1810
+ parsed.uploadId,
1811
+ parsed.chunkIndex,
1812
+ parsed.totalChunks,
1813
+ parsed.blobData
1814
+ );
1815
+ if (!result.complete) {
1816
+ return {
1817
+ accept: true,
1818
+ data: Buffer.from(`ack:${parsed.chunkIndex}`).toString("base64")
1819
+ };
1820
+ }
1821
+ const assembled = result.assembled ?? Buffer.alloc(0);
1822
+ const { txId } = await turboAdapter.upload(assembled, uploadTags);
1823
+ return {
1824
+ accept: true,
1825
+ data: Buffer.from(txId).toString("base64")
1826
+ };
1827
+ } catch (error) {
1828
+ const internalMsg = error instanceof Error ? error.message : "Unknown chunk error";
1829
+ const safeMsg = internalMsg.includes("Duplicate chunkIndex") ? "Duplicate chunk rejected" : internalMsg.includes("Max active uploads") ? "Server busy, too many concurrent uploads" : internalMsg.includes("exceeds max bytes") ? "Upload size limit exceeded" : "Chunk processing error";
1830
+ return {
1831
+ accept: false,
1832
+ code: "F00",
1833
+ message: safeMsg
1834
+ };
1835
+ }
1836
+ }
1837
+ try {
1838
+ const { txId } = await turboAdapter.upload(parsed.blobData, uploadTags);
1839
+ return {
1840
+ accept: true,
1841
+ data: Buffer.from(txId).toString("base64")
1842
+ };
1843
+ } catch {
1844
+ return {
1845
+ accept: false,
1846
+ code: "T00",
1847
+ message: "Arweave upload failed"
1848
+ };
1849
+ }
1850
+ };
1851
+ }
1852
+
1853
+ // src/arweave/turbo-adapter.ts
1854
+ import { Readable } from "stream";
1855
+ var TurboUploadAdapter = class {
1856
+ turboClient;
1857
+ initialized = false;
1858
+ constructor(turboClient) {
1859
+ if (turboClient) {
1860
+ this.turboClient = turboClient;
1861
+ this.initialized = true;
1862
+ }
1863
+ }
1864
+ async getClient() {
1865
+ if (!this.initialized) {
1866
+ const { TurboFactory } = await import("@ardrive/turbo-sdk/node");
1867
+ const Arweave = (await import("./node-WPA2UDEH.js")).default;
1868
+ const arweave = Arweave.init({});
1869
+ const jwk = await arweave.crypto.generateJWK();
1870
+ this.turboClient = TurboFactory.authenticated({ privateKey: jwk });
1871
+ this.initialized = true;
1872
+ }
1873
+ return this.turboClient;
1874
+ }
1875
+ async upload(data, tags) {
1876
+ const client = await this.getClient();
1877
+ const tagArray = [];
1878
+ if (tags) {
1879
+ for (const [name, value] of Object.entries(tags)) {
1880
+ tagArray.push({ name, value });
1881
+ }
1882
+ }
1883
+ const result = await client.uploadFile({
1884
+ fileStreamFactory: () => Readable.from(data),
1885
+ fileSizeFactory: () => data.length,
1886
+ ...tagArray.length > 0 ? { dataItemOpts: { tags: tagArray } } : {}
1887
+ });
1888
+ return { txId: result.id };
1889
+ }
1890
+ };
1891
+
1892
+ // src/arweave/chunk-manager.ts
1893
+ var ChunkManager = class {
1894
+ uploads = /* @__PURE__ */ new Map();
1895
+ timeoutMs;
1896
+ maxActiveUploads;
1897
+ maxBytesPerUpload;
1898
+ constructor(config = {}) {
1899
+ this.timeoutMs = config.timeoutMs ?? 3e5;
1900
+ this.maxActiveUploads = config.maxActiveUploads ?? 100;
1901
+ this.maxBytesPerUpload = config.maxBytesPerUpload ?? 50 * 1024 * 1024;
1902
+ }
1903
+ /**
1904
+ * Add a chunk for a given uploadId.
1905
+ *
1906
+ * @param uploadId - Unique identifier for the upload session.
1907
+ * @param chunkIndex - Zero-based index of this chunk.
1908
+ * @param totalChunks - Total number of chunks expected.
1909
+ * @param data - The chunk data.
1910
+ * @returns Result indicating completion status or error.
1911
+ * @throws Error if memory cap reached or duplicate chunk.
1912
+ */
1913
+ addChunk(uploadId, chunkIndex, totalChunks, data) {
1914
+ let state = this.uploads.get(uploadId);
1915
+ if (!state) {
1916
+ if (this.uploads.size >= this.maxActiveUploads) {
1917
+ throw new Error(
1918
+ `Max active uploads reached (${this.maxActiveUploads}). Cannot accept new uploadId.`
1919
+ );
1920
+ }
1921
+ const timer = setTimeout(() => {
1922
+ this.cleanup(uploadId);
1923
+ }, this.timeoutMs);
1924
+ state = {
1925
+ chunks: /* @__PURE__ */ new Map(),
1926
+ totalChunks,
1927
+ timer
1928
+ };
1929
+ this.uploads.set(uploadId, state);
1930
+ }
1931
+ if (chunkIndex < 0 || chunkIndex >= state.totalChunks) {
1932
+ throw new Error(
1933
+ `chunkIndex ${chunkIndex} out of bounds for totalChunks ${state.totalChunks} (uploadId ${uploadId})`
1934
+ );
1935
+ }
1936
+ if (state.chunks.has(chunkIndex)) {
1937
+ throw new Error(
1938
+ `Duplicate chunkIndex ${chunkIndex} for uploadId ${uploadId}`
1939
+ );
1940
+ }
1941
+ let currentBytes = 0;
1942
+ for (const chunk of state.chunks.values()) {
1943
+ currentBytes += chunk.length;
1944
+ }
1945
+ if (currentBytes + data.length > this.maxBytesPerUpload) {
1946
+ this.cleanup(uploadId);
1947
+ throw new Error(
1948
+ `Upload ${uploadId} exceeds max bytes per upload (${this.maxBytesPerUpload})`
1949
+ );
1950
+ }
1951
+ state.chunks.set(chunkIndex, data);
1952
+ if (state.chunks.size === state.totalChunks) {
1953
+ const orderedChunks = [];
1954
+ for (let i = 0; i < state.totalChunks; i++) {
1955
+ const chunk = state.chunks.get(i);
1956
+ if (!chunk) {
1957
+ throw new Error(`Missing chunk ${i} for upload`);
1958
+ }
1959
+ orderedChunks.push(chunk);
1960
+ }
1961
+ const assembled = Buffer.concat(orderedChunks);
1962
+ this.cleanup(uploadId);
1963
+ return { complete: true, assembled };
1964
+ }
1965
+ return { complete: false };
1966
+ }
1967
+ /**
1968
+ * Check if an upload is complete (all chunks received).
1969
+ * Returns false if the uploadId is unknown (cleaned up or never started).
1970
+ */
1971
+ isComplete(uploadId) {
1972
+ const state = this.uploads.get(uploadId);
1973
+ if (!state) return false;
1974
+ return state.chunks.size === state.totalChunks;
1975
+ }
1976
+ /**
1977
+ * Clean up state for a given uploadId, clearing the timeout timer.
1978
+ */
1979
+ cleanup(uploadId) {
1980
+ const state = this.uploads.get(uploadId);
1981
+ if (state) {
1982
+ clearTimeout(state.timer);
1983
+ this.uploads.delete(uploadId);
1984
+ }
1985
+ }
1986
+ /**
1987
+ * Clean up all active uploads and their timers.
1988
+ * Call this on node shutdown to prevent timer leaks.
1989
+ */
1990
+ destroyAll() {
1991
+ for (const [, state] of this.uploads) {
1992
+ clearTimeout(state.timer);
1993
+ }
1994
+ this.uploads.clear();
1995
+ }
1996
+ };
1997
+
1998
+ // src/arweave/chunked-upload.ts
1999
+ import { randomUUID } from "crypto";
2000
+ import { buildBlobStorageRequest } from "@toon-protocol/core";
2001
+ async function uploadBlob(node, blob, destination, options) {
2002
+ const contentType = options.contentType ?? "application/octet-stream";
2003
+ const pricePerByte = options.pricePerByte ?? 10n;
2004
+ const amount = BigInt(blob.length) * pricePerByte;
2005
+ const event = buildBlobStorageRequest(
2006
+ {
2007
+ blobData: blob,
2008
+ contentType,
2009
+ bid: amount.toString()
2010
+ },
2011
+ options.secretKey
2012
+ );
2013
+ const result = await node.publishEvent(event, { destination, amount });
2014
+ if (!result.success) {
2015
+ throw new Error(`Blob upload failed: ${result.message ?? "unknown error"}`);
2016
+ }
2017
+ if (result.data) {
2018
+ return Buffer.from(result.data, "base64").toString("utf-8");
2019
+ }
2020
+ return result.eventId;
2021
+ }
2022
+ async function uploadBlobChunked(node, blob, destination, options) {
2023
+ if (blob.length === 0) {
2024
+ throw new Error("Cannot upload empty blob via chunked upload");
2025
+ }
2026
+ const chunkSize = options.chunkSize ?? 5e5;
2027
+ const contentType = options.contentType ?? "application/octet-stream";
2028
+ const pricePerByte = options.pricePerByte ?? 10n;
2029
+ const uploadId = randomUUID();
2030
+ const totalChunks = Math.ceil(blob.length / chunkSize);
2031
+ let lastResult;
2032
+ for (let i = 0; i < totalChunks; i++) {
2033
+ const start = i * chunkSize;
2034
+ const end = Math.min(start + chunkSize, blob.length);
2035
+ const chunkData = blob.subarray(start, end);
2036
+ const amount = BigInt(chunkData.length) * pricePerByte;
2037
+ const event = buildBlobStorageRequest(
2038
+ {
2039
+ blobData: Buffer.from(chunkData),
2040
+ contentType,
2041
+ bid: amount.toString(),
2042
+ params: [
2043
+ { key: "uploadId", value: uploadId },
2044
+ { key: "chunkIndex", value: String(i) },
2045
+ { key: "totalChunks", value: String(totalChunks) }
2046
+ ]
2047
+ },
2048
+ options.secretKey
2049
+ );
2050
+ lastResult = await node.publishEvent(event, { destination, amount });
2051
+ if (!lastResult.success) {
2052
+ throw new Error(
2053
+ `Chunk ${i}/${totalChunks} upload failed: ${lastResult.message ?? "unknown error"}`
2054
+ );
2055
+ }
2056
+ }
2057
+ if (!lastResult) {
2058
+ throw new Error("No chunks were uploaded");
2059
+ }
2060
+ if (lastResult.data) {
2061
+ return Buffer.from(lastResult.data, "base64").toString("utf-8");
2062
+ }
2063
+ return lastResult.eventId;
2064
+ }
1044
2065
  export {
2066
+ ChunkManager,
1045
2067
  HandlerError,
1046
2068
  HandlerRegistry,
1047
2069
  IdentityError,
1048
2070
  NodeError,
1049
2071
  PricingError,
2072
+ SwarmCoordinator,
2073
+ TurboUploadAdapter,
1050
2074
  VerificationError,
2075
+ WorkflowOrchestrator,
1051
2076
  buildSkillDescriptor,
2077
+ createArweaveDvmHandler,
1052
2078
  createEventStorageHandler,
1053
2079
  createHandlerContext,
1054
2080
  createNode,
1055
2081
  createPaymentHandlerBridge,
2082
+ createPrefixClaimHandler,
1056
2083
  createPricingValidator,
1057
2084
  createVerificationPipeline,
1058
2085
  fromMnemonic,
1059
2086
  fromSecretKey,
1060
- generateMnemonic
2087
+ generateMnemonic,
2088
+ uploadBlob,
2089
+ uploadBlobChunked
1061
2090
  };
1062
2091
  //# sourceMappingURL=index.js.map