@toon-protocol/sdk 0.1.5 → 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/README.md +6 -4
- package/dist/chunk-UP2VWCW5.js +38 -0
- package/dist/chunk-UP2VWCW5.js.map +1 -0
- package/dist/index.d.ts +667 -11
- package/dist/index.js +1161 -15
- package/dist/index.js.map +1 -1
- package/dist/node-WPA2UDEH.js +8221 -0
- package/dist/node-WPA2UDEH.js.map +1 -0
- package/package.json +7 -4
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,
|
|
@@ -167,7 +169,6 @@ function createHandlerContext(options) {
|
|
|
167
169
|
accept(metadata) {
|
|
168
170
|
return {
|
|
169
171
|
accept: true,
|
|
170
|
-
fulfillment: "default-fulfillment",
|
|
171
172
|
...metadata ? { metadata } : {}
|
|
172
173
|
};
|
|
173
174
|
},
|
|
@@ -378,7 +379,15 @@ import {
|
|
|
378
379
|
buildIlpPrepare,
|
|
379
380
|
buildJobFeedbackEvent,
|
|
380
381
|
buildJobResultEvent,
|
|
381
|
-
parseJobResult
|
|
382
|
+
parseJobResult,
|
|
383
|
+
deriveChildAddress,
|
|
384
|
+
checkAddressCollision,
|
|
385
|
+
AddressRegistry,
|
|
386
|
+
ILP_ROOT_PREFIX,
|
|
387
|
+
calculateRouteAmount,
|
|
388
|
+
resolveRouteFees,
|
|
389
|
+
buildPrefixClaimEvent,
|
|
390
|
+
validatePrefix
|
|
382
391
|
} from "@toon-protocol/core";
|
|
383
392
|
import {
|
|
384
393
|
shallowParseToon,
|
|
@@ -387,11 +396,21 @@ import {
|
|
|
387
396
|
} from "@toon-protocol/core/toon";
|
|
388
397
|
|
|
389
398
|
// src/skill-descriptor.ts
|
|
399
|
+
import { ToonError as ToonError2 } from "@toon-protocol/core";
|
|
400
|
+
var HEX_64_REGEX = /^[0-9a-f]{64}$/;
|
|
390
401
|
function buildSkillDescriptor(registry, config = {}) {
|
|
391
402
|
const dvmKinds = registry.getDvmKinds();
|
|
392
403
|
if (dvmKinds.length === 0) {
|
|
393
404
|
return void 0;
|
|
394
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
|
+
}
|
|
395
414
|
const basePricePerByte = config.basePricePerByte ?? 10n;
|
|
396
415
|
const kindPricing = config.kindPricing ?? {};
|
|
397
416
|
const pricing = {};
|
|
@@ -413,6 +432,15 @@ function buildSkillDescriptor(registry, config = {}) {
|
|
|
413
432
|
if (config.models !== void 0) {
|
|
414
433
|
descriptor.models = config.models;
|
|
415
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
|
+
}
|
|
416
444
|
return descriptor;
|
|
417
445
|
}
|
|
418
446
|
|
|
@@ -426,17 +454,13 @@ function createNode(config) {
|
|
|
426
454
|
"NodeConfig: provide either connector or connectorUrl, not both"
|
|
427
455
|
);
|
|
428
456
|
}
|
|
429
|
-
if (!hasConnector && !hasConnectorUrl) {
|
|
430
|
-
throw new NodeError(
|
|
431
|
-
"NodeConfig: one of connector or connectorUrl is required"
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
457
|
if (hasConnectorUrl && config.handlerPort === void 0) {
|
|
435
458
|
throw new NodeError(
|
|
436
459
|
"NodeConfig: handlerPort is required when using connectorUrl (standalone mode)"
|
|
437
460
|
);
|
|
438
461
|
}
|
|
439
|
-
const
|
|
462
|
+
const autoCreateConnector = !hasConnector && !hasConnectorUrl;
|
|
463
|
+
const embeddedMode = hasConnector || autoCreateConnector;
|
|
440
464
|
const chainConfig = resolveChainConfig(config.chain);
|
|
441
465
|
let effectiveSettlementInfo = config.settlementInfo;
|
|
442
466
|
if (!effectiveSettlementInfo) {
|
|
@@ -579,11 +603,54 @@ function createNode(config) {
|
|
|
579
603
|
return { accept: false, code: "T00", message: "Internal error" };
|
|
580
604
|
}
|
|
581
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
|
+
}
|
|
582
647
|
const ilpInfo = {
|
|
583
|
-
ilpAddress:
|
|
648
|
+
ilpAddress: resolvedIlpAddress,
|
|
649
|
+
ilpAddresses: resolvedIlpAddresses,
|
|
584
650
|
btpEndpoint: config.btpEndpoint ?? "",
|
|
585
651
|
assetCode: config.assetCode ?? "USD",
|
|
586
|
-
assetScale: config.assetScale ?? 6
|
|
652
|
+
assetScale: config.assetScale ?? 6,
|
|
653
|
+
feePerByte: String(config.feePerByte ?? 0n)
|
|
587
654
|
};
|
|
588
655
|
let ilpClient;
|
|
589
656
|
let adminClient;
|
|
@@ -593,7 +660,8 @@ function createNode(config) {
|
|
|
593
660
|
let httpServer = null;
|
|
594
661
|
let doStart;
|
|
595
662
|
let doStop;
|
|
596
|
-
|
|
663
|
+
let autoCreatedConnector = null;
|
|
664
|
+
if (embeddedMode && hasConnector) {
|
|
597
665
|
const toonNode = createToonNode({
|
|
598
666
|
connector: config.connector,
|
|
599
667
|
handlePacket: pipelinedHandler,
|
|
@@ -618,6 +686,133 @@ function createNode(config) {
|
|
|
618
686
|
trackerRef.current = discoveryTrackerInstance;
|
|
619
687
|
doStart = async () => toonNode.start();
|
|
620
688
|
doStop = async () => toonNode.stop();
|
|
689
|
+
} else if (autoCreateConnector) {
|
|
690
|
+
bootstrapServiceInstance = new BootstrapService(
|
|
691
|
+
{
|
|
692
|
+
knownPeers: config.knownPeers ?? [],
|
|
693
|
+
ardriveEnabled: config.ardriveEnabled ?? false,
|
|
694
|
+
defaultRelayUrl: config.relayUrl ?? "",
|
|
695
|
+
settlementInfo: effectiveSettlementInfo,
|
|
696
|
+
ownIlpAddress: ilpInfo.ilpAddress,
|
|
697
|
+
toonEncoder: encoder,
|
|
698
|
+
toonDecoder: decoder,
|
|
699
|
+
basePricePerByte: config.basePricePerByte ?? 10n
|
|
700
|
+
},
|
|
701
|
+
config.secretKey,
|
|
702
|
+
ilpInfo
|
|
703
|
+
);
|
|
704
|
+
discoveryTrackerInstance = createDiscoveryTracker({
|
|
705
|
+
secretKey: config.secretKey,
|
|
706
|
+
settlementInfo: effectiveSettlementInfo
|
|
707
|
+
});
|
|
708
|
+
ilpClient = {
|
|
709
|
+
sendIlpPacket: () => Promise.reject(new NodeError("Node not started. Call start() first."))
|
|
710
|
+
};
|
|
711
|
+
adminClient = {
|
|
712
|
+
addPeer: () => Promise.resolve(),
|
|
713
|
+
removePeer: () => Promise.resolve()
|
|
714
|
+
};
|
|
715
|
+
trackerRef.current = discoveryTrackerInstance;
|
|
716
|
+
doStart = async () => {
|
|
717
|
+
let ConnectorNodeClass;
|
|
718
|
+
let createConnectorLogger;
|
|
719
|
+
try {
|
|
720
|
+
const mod = await import("@toon-protocol/connector");
|
|
721
|
+
ConnectorNodeClass = mod.ConnectorNode;
|
|
722
|
+
createConnectorLogger = mod.createLogger;
|
|
723
|
+
} catch {
|
|
724
|
+
throw new NodeError(
|
|
725
|
+
"Auto-create connector requires @toon-protocol/connector as a peer dependency. Install it with: pnpm add @toon-protocol/connector"
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
const nodeId = `toon-${pubkey.slice(0, 16)}`;
|
|
729
|
+
const btpServerPort = config.btpServerPort ?? 3e3;
|
|
730
|
+
const connectorLogger = createConnectorLogger(nodeId, "warn");
|
|
731
|
+
let settlementPrivateKey = config.settlementPrivateKey;
|
|
732
|
+
if (!settlementPrivateKey) {
|
|
733
|
+
const keyBuffer = Buffer.from(config.secretKey);
|
|
734
|
+
settlementPrivateKey = `0x${keyBuffer.toString("hex")}`;
|
|
735
|
+
keyBuffer.fill(0);
|
|
736
|
+
}
|
|
737
|
+
const hasSettlementAddresses = chainConfig.registryAddress && chainConfig.tokenNetworkAddress;
|
|
738
|
+
const hasChainProviders = config.chainProviders !== void 0 && config.chainProviders.length > 0;
|
|
739
|
+
autoCreatedConnector = new ConnectorNodeClass(
|
|
740
|
+
{
|
|
741
|
+
nodeId,
|
|
742
|
+
btpServerPort,
|
|
743
|
+
environment: "development",
|
|
744
|
+
deploymentMode: "embedded",
|
|
745
|
+
peers: [],
|
|
746
|
+
routes: [],
|
|
747
|
+
localDelivery: { enabled: false },
|
|
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 && {
|
|
754
|
+
settlementInfra: {
|
|
755
|
+
enabled: true,
|
|
756
|
+
rpcUrl: chainConfig.rpcUrl,
|
|
757
|
+
registryAddress: chainConfig.registryAddress,
|
|
758
|
+
tokenAddress: chainConfig.usdcAddress,
|
|
759
|
+
privateKey: settlementPrivateKey
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
// NIP-59 transport privacy
|
|
763
|
+
...config.nip59 && { nip59: config.nip59 }
|
|
764
|
+
},
|
|
765
|
+
connectorLogger
|
|
766
|
+
);
|
|
767
|
+
await autoCreatedConnector.start();
|
|
768
|
+
const connector = autoCreatedConnector;
|
|
769
|
+
const toonNode = createToonNode({
|
|
770
|
+
connector,
|
|
771
|
+
handlePacket: pipelinedHandler,
|
|
772
|
+
secretKey: config.secretKey,
|
|
773
|
+
ilpInfo,
|
|
774
|
+
toonEncoder: encoder,
|
|
775
|
+
toonDecoder: decoder,
|
|
776
|
+
relayUrl: config.relayUrl,
|
|
777
|
+
knownPeers: config.knownPeers,
|
|
778
|
+
settlementInfo: effectiveSettlementInfo,
|
|
779
|
+
basePricePerByte: config.basePricePerByte,
|
|
780
|
+
ardriveEnabled: config.ardriveEnabled
|
|
781
|
+
});
|
|
782
|
+
ilpClient = toonNode.ilpClient;
|
|
783
|
+
channelClient = toonNode.channelClient;
|
|
784
|
+
bootstrapServiceInstance.setIlpClient(toonNode.ilpClient);
|
|
785
|
+
bootstrapServiceInstance.setConnectorAdmin({
|
|
786
|
+
addPeer: () => Promise.resolve(),
|
|
787
|
+
removePeer: () => Promise.resolve()
|
|
788
|
+
});
|
|
789
|
+
if (toonNode.channelClient) {
|
|
790
|
+
bootstrapServiceInstance.setChannelClient(toonNode.channelClient);
|
|
791
|
+
}
|
|
792
|
+
discoveryTrackerInstance.setConnectorAdmin({
|
|
793
|
+
addPeer: () => Promise.resolve(),
|
|
794
|
+
removePeer: () => Promise.resolve()
|
|
795
|
+
});
|
|
796
|
+
if (toonNode.channelClient) {
|
|
797
|
+
discoveryTrackerInstance.setChannelClient(toonNode.channelClient);
|
|
798
|
+
}
|
|
799
|
+
trackerRef.current = discoveryTrackerInstance;
|
|
800
|
+
for (const addr of ilpInfo.ilpAddresses) {
|
|
801
|
+
autoCreatedConnector.addRoute({
|
|
802
|
+
prefix: addr,
|
|
803
|
+
nextHop: nodeId,
|
|
804
|
+
priority: 100
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
const result = await toonNode.start();
|
|
808
|
+
return result;
|
|
809
|
+
};
|
|
810
|
+
doStop = async () => {
|
|
811
|
+
if (autoCreatedConnector) {
|
|
812
|
+
await autoCreatedConnector.stop();
|
|
813
|
+
autoCreatedConnector = null;
|
|
814
|
+
}
|
|
815
|
+
};
|
|
621
816
|
} else {
|
|
622
817
|
const connectorUrl = config.connectorUrl;
|
|
623
818
|
const handlerPort = config.handlerPort;
|
|
@@ -725,6 +920,7 @@ function createNode(config) {
|
|
|
725
920
|
};
|
|
726
921
|
}
|
|
727
922
|
let started = false;
|
|
923
|
+
let lastBootstrapResults = [];
|
|
728
924
|
const node = {
|
|
729
925
|
get pubkey() {
|
|
730
926
|
return pubkey;
|
|
@@ -733,7 +929,7 @@ function createNode(config) {
|
|
|
733
929
|
return evmAddress;
|
|
734
930
|
},
|
|
735
931
|
get connector() {
|
|
736
|
-
return config.connector ?? null;
|
|
932
|
+
return config.connector ?? autoCreatedConnector ?? null;
|
|
737
933
|
},
|
|
738
934
|
get channelClient() {
|
|
739
935
|
return channelClient;
|
|
@@ -776,6 +972,7 @@ function createNode(config) {
|
|
|
776
972
|
try {
|
|
777
973
|
const result = await doStart();
|
|
778
974
|
started = true;
|
|
975
|
+
lastBootstrapResults = result.bootstrapResults;
|
|
779
976
|
return {
|
|
780
977
|
peerCount: result.peerCount,
|
|
781
978
|
channelCount: result.channelCount,
|
|
@@ -824,7 +1021,35 @@ function createNode(config) {
|
|
|
824
1021
|
}
|
|
825
1022
|
try {
|
|
826
1023
|
const toonData = encoder(event);
|
|
827
|
-
const
|
|
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
|
+
}
|
|
828
1053
|
const packet = buildIlpPrepare({
|
|
829
1054
|
destination: options.destination,
|
|
830
1055
|
amount,
|
|
@@ -835,7 +1060,7 @@ function createNode(config) {
|
|
|
835
1060
|
return {
|
|
836
1061
|
success: true,
|
|
837
1062
|
eventId: event.id,
|
|
838
|
-
|
|
1063
|
+
...result.data !== void 0 ? { data: result.data } : {}
|
|
839
1064
|
};
|
|
840
1065
|
}
|
|
841
1066
|
return {
|
|
@@ -870,6 +1095,9 @@ function createNode(config) {
|
|
|
870
1095
|
return node.publishEvent(resultEvent, options);
|
|
871
1096
|
},
|
|
872
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
|
+
);
|
|
873
1101
|
if (!started) {
|
|
874
1102
|
throw new NodeError(
|
|
875
1103
|
"Cannot settle compute: node not started. Call start() first."
|
|
@@ -920,26 +1148,944 @@ function createNode(config) {
|
|
|
920
1148
|
amount: computeAmount,
|
|
921
1149
|
data: ""
|
|
922
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
|
+
});
|
|
923
1227
|
}
|
|
924
1228
|
};
|
|
925
1229
|
return node;
|
|
926
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
|
+
}
|
|
927
2065
|
export {
|
|
2066
|
+
ChunkManager,
|
|
928
2067
|
HandlerError,
|
|
929
2068
|
HandlerRegistry,
|
|
930
2069
|
IdentityError,
|
|
931
2070
|
NodeError,
|
|
932
2071
|
PricingError,
|
|
2072
|
+
SwarmCoordinator,
|
|
2073
|
+
TurboUploadAdapter,
|
|
933
2074
|
VerificationError,
|
|
2075
|
+
WorkflowOrchestrator,
|
|
934
2076
|
buildSkillDescriptor,
|
|
2077
|
+
createArweaveDvmHandler,
|
|
935
2078
|
createEventStorageHandler,
|
|
936
2079
|
createHandlerContext,
|
|
937
2080
|
createNode,
|
|
938
2081
|
createPaymentHandlerBridge,
|
|
2082
|
+
createPrefixClaimHandler,
|
|
939
2083
|
createPricingValidator,
|
|
940
2084
|
createVerificationPipeline,
|
|
941
2085
|
fromMnemonic,
|
|
942
2086
|
fromSecretKey,
|
|
943
|
-
generateMnemonic
|
|
2087
|
+
generateMnemonic,
|
|
2088
|
+
uploadBlob,
|
|
2089
|
+
uploadBlobChunked
|
|
944
2090
|
};
|
|
945
2091
|
//# sourceMappingURL=index.js.map
|