@toon-protocol/sdk 0.4.0 → 0.5.1

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,145 +1,36 @@
1
- import "./chunk-UP2VWCW5.js";
2
-
3
- // src/identity.ts
4
1
  import {
5
- generateMnemonic as _generateMnemonic,
6
- validateMnemonic,
7
- mnemonicToSeedSync
8
- } from "@scure/bip39";
9
- import { wordlist } from "@scure/bip39/wordlists/english.js";
10
- import { HDKey } from "@scure/bip32";
11
- import { getPublicKey } from "nostr-tools/pure";
12
- import { secp256k1 } from "@noble/curves/secp256k1.js";
13
- import { keccak_256 } from "@noble/hashes/sha3.js";
14
- import { bytesToHex } from "@noble/hashes/utils.js";
15
-
16
- // src/errors.ts
17
- import { ToonError } from "@toon-protocol/core";
18
- var IdentityError = class extends ToonError {
19
- constructor(message, cause) {
20
- super(message, "IDENTITY_ERROR", cause);
21
- this.name = "IdentityError";
22
- }
23
- };
24
- var NodeError = class extends ToonError {
25
- constructor(message, cause) {
26
- super(message, "NODE_ERROR", cause);
27
- this.name = "NodeError";
28
- }
29
- };
30
- var HandlerError = class extends ToonError {
31
- constructor(message, cause) {
32
- super(message, "HANDLER_ERROR", cause);
33
- this.name = "HandlerError";
34
- }
35
- };
36
- var VerificationError = class extends ToonError {
37
- constructor(message, cause) {
38
- super(message, "VERIFICATION_ERROR", cause);
39
- this.name = "VerificationError";
40
- }
41
- };
42
- var PricingError = class extends ToonError {
43
- constructor(message, cause) {
44
- super(message, "PRICING_ERROR", cause);
45
- this.name = "PricingError";
46
- }
47
- };
48
-
49
- // src/identity.ts
50
- function generateMnemonic() {
51
- return _generateMnemonic(wordlist, 128);
52
- }
53
- function fromMnemonic(mnemonic, options) {
54
- if (!validateMnemonic(mnemonic, wordlist)) {
55
- throw new IdentityError(
56
- `Invalid BIP-39 mnemonic: the provided words do not form a valid mnemonic phrase`
57
- );
58
- }
59
- const accountIndex = options?.accountIndex ?? 0;
60
- if (!Number.isInteger(accountIndex) || accountIndex < 0 || accountIndex > MAX_BIP32_INDEX) {
61
- throw new IdentityError(
62
- `Invalid accountIndex: expected a non-negative integer (0 to ${MAX_BIP32_INDEX}), got ${String(accountIndex)}`
63
- );
64
- }
65
- const path = `m/44'/1237'/0'/0/${accountIndex}`;
66
- let seed;
67
- try {
68
- seed = mnemonicToSeedSync(mnemonic);
69
- const hdKey = HDKey.fromMasterSeed(seed).derive(path);
70
- if (!hdKey.privateKey) {
71
- throw new IdentityError(`Failed to derive private key at path ${path}`);
72
- }
73
- const secretKey = hdKey.privateKey;
74
- return deriveIdentity(secretKey);
75
- } catch (error) {
76
- if (error instanceof IdentityError) {
77
- throw error;
78
- }
79
- throw new IdentityError(
80
- `Key derivation failed at path ${path}: ${error instanceof Error ? error.message : String(error)}`,
81
- error instanceof Error ? error : void 0
82
- );
83
- } finally {
84
- if (seed) {
85
- seed.fill(0);
86
- }
87
- }
88
- }
89
- function fromSecretKey(secretKey) {
90
- if (!(secretKey instanceof Uint8Array)) {
91
- throw new IdentityError(
92
- `Invalid secret key: expected Uint8Array, got ${secretKey === null ? "null" : typeof secretKey}`
93
- );
94
- }
95
- if (secretKey.length !== 32) {
96
- throw new IdentityError(
97
- `Invalid secret key: expected 32 bytes, got ${secretKey.length} bytes`
98
- );
99
- }
100
- try {
101
- return deriveIdentity(secretKey);
102
- } catch (error) {
103
- if (error instanceof IdentityError) {
104
- throw error;
105
- }
106
- throw new IdentityError(
107
- `Invalid secret key: ${error instanceof Error ? error.message : String(error)}`,
108
- error instanceof Error ? error : void 0
109
- );
110
- }
111
- }
112
- var MAX_BIP32_INDEX = 2147483647;
113
- function deriveIdentity(secretKey) {
114
- const pubkey = getPublicKey(secretKey);
115
- const evmAddress = computeEvmAddress(secretKey);
116
- return { secretKey: new Uint8Array(secretKey), pubkey, evmAddress };
117
- }
118
- function computeEvmAddress(secretKey) {
119
- const uncompressedPubkey = secp256k1.getPublicKey(secretKey, false);
120
- const pubkeyWithoutPrefix = uncompressedPubkey.slice(1);
121
- const hash = keccak_256(pubkeyWithoutPrefix);
122
- const addressBytes = hash.slice(-20);
123
- const addressHex = bytesToHex(addressBytes);
124
- return toChecksumAddress(addressHex);
125
- }
126
- function toChecksumAddress(addressHex) {
127
- const lower = addressHex.toLowerCase();
128
- const hash = bytesToHex(keccak_256(new TextEncoder().encode(lower)));
129
- let checksummed = "0x";
130
- for (let i = 0; i < 40; i++) {
131
- const char = lower[i];
132
- const hashChar = hash[i];
133
- if (char === void 0 || hashChar === void 0) {
134
- throw new IdentityError(
135
- `Unexpected undefined at index ${i} during checksum computation`
136
- );
137
- }
138
- const hashNibble = parseInt(hashChar, 16);
139
- checksummed += hashNibble >= 8 ? char.toUpperCase() : char;
140
- }
141
- return checksummed;
142
- }
2
+ GiftWrapError,
3
+ HandlerError,
4
+ IdentityError,
5
+ NodeError,
6
+ PricingError,
7
+ SWAP_HANDLER_REJECT_CODES,
8
+ SWAP_HANDLER_REJECT_MESSAGES,
9
+ SettlementTxError,
10
+ StreamSwapError,
11
+ SwapHandlerError,
12
+ VerificationError,
13
+ __testing,
14
+ applyRate,
15
+ base58Decode,
16
+ base58Encode,
17
+ createSwapHandler,
18
+ decryptFulfillClaim,
19
+ encryptFulfillClaim,
20
+ findSwapPair,
21
+ fromMnemonic,
22
+ fromMnemonicFull,
23
+ fromSecretKey,
24
+ generateMnemonic,
25
+ generateSolanaKeypair,
26
+ streamSwap,
27
+ streamSwapControlled,
28
+ unwrapSwapPacket,
29
+ unwrapSwapPacketFromToon,
30
+ wrapSwapPacket,
31
+ wrapSwapPacketToToon
32
+ } from "./chunk-XTHUXP63.js";
33
+ import "./chunk-UP2VWCW5.js";
143
34
 
144
35
  // src/handler-context.ts
145
36
  function createHandlerContext(options) {
@@ -208,6 +99,13 @@ var HandlerRegistry = class {
208
99
  getRegisteredKinds() {
209
100
  return [...this.handlers.keys()].sort((a, b) => a - b);
210
101
  }
102
+ /**
103
+ * Returns the handler registered for `kind`, or `undefined` if none.
104
+ * Added for Story 12.7 AC-10 (handler-registration verification).
105
+ */
106
+ get(kind) {
107
+ return this.handlers.get(kind);
108
+ }
211
109
  /**
212
110
  * Returns registered kinds in the DVM request range (5000-5999), sorted ascending.
213
111
  * Uses JOB_REQUEST_KIND_BASE (5000) as the range start.
@@ -387,7 +285,8 @@ import {
387
285
  calculateRouteAmount,
388
286
  resolveRouteFees,
389
287
  buildPrefixClaimEvent,
390
- validatePrefix
288
+ validatePrefix,
289
+ ilpCodeToSemantic
391
290
  } from "@toon-protocol/core";
392
291
  import {
393
292
  shallowParseToon,
@@ -396,7 +295,7 @@ import {
396
295
  } from "@toon-protocol/core/toon";
397
296
 
398
297
  // src/skill-descriptor.ts
399
- import { ToonError as ToonError2 } from "@toon-protocol/core";
298
+ import { ToonError } from "@toon-protocol/core";
400
299
  var HEX_64_REGEX = /^[0-9a-f]{64}$/;
401
300
  function buildSkillDescriptor(registry, config = {}) {
402
301
  const dvmKinds = registry.getDvmKinds();
@@ -405,7 +304,7 @@ function buildSkillDescriptor(registry, config = {}) {
405
304
  }
406
305
  if (config.attestation !== void 0) {
407
306
  if (!HEX_64_REGEX.test(config.attestation.eventId)) {
408
- throw new ToonError2(
307
+ throw new ToonError(
409
308
  "Skill descriptor attestation eventId must be a 64-character lowercase hex string",
410
309
  "DVM_SKILL_INVALID_ATTESTATION_EVENT_ID"
411
310
  );
@@ -596,6 +495,27 @@ function createNode(config) {
596
495
  trackerRef.current.processEvent(decoded);
597
496
  }
598
497
  }
498
+ if (result.accept && result.metadata && !result.data) {
499
+ try {
500
+ const metadataJson = JSON.stringify(result.metadata);
501
+ result.data = Buffer.from(
502
+ metadataJson,
503
+ "utf8"
504
+ ).toString("base64");
505
+ } catch (err) {
506
+ console.error(
507
+ "Failed to serialize handler metadata to FULFILL data:",
508
+ err instanceof Error ? err.message : err
509
+ );
510
+ }
511
+ }
512
+ if (!result.accept && !result.rejectReason && result.code) {
513
+ const ilpCode = result.code;
514
+ result.rejectReason = {
515
+ code: ilpCodeToSemantic(ilpCode),
516
+ message: result.message ?? "Payment rejected"
517
+ };
518
+ }
599
519
  return result;
600
520
  } catch (err) {
601
521
  const errMsg = err instanceof Error ? err.message : "Unknown error";
@@ -736,32 +656,36 @@ function createNode(config) {
736
656
  }
737
657
  const hasSettlementAddresses = chainConfig.registryAddress && chainConfig.tokenNetworkAddress;
738
658
  const hasChainProviders = config.chainProviders !== void 0 && config.chainProviders.length > 0;
739
- autoCreatedConnector = new ConnectorNodeClass(
659
+ const effectiveChainProviders = hasChainProviders ? config.chainProviders : hasSettlementAddresses ? [
740
660
  {
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
- },
661
+ chainType: "evm",
662
+ chainId: `evm:${chainConfig.chainId ?? "31337"}`,
663
+ rpcUrl: chainConfig.rpcUrl,
664
+ registryAddress: chainConfig.registryAddress,
665
+ tokenAddress: chainConfig.usdcAddress,
666
+ privateKey: settlementPrivateKey
667
+ }
668
+ ] : void 0;
669
+ const connectorConfig = {
670
+ nodeId,
671
+ btpServerPort,
672
+ environment: "development",
673
+ deploymentMode: "embedded",
674
+ peers: [],
675
+ routes: [],
676
+ localDelivery: { enabled: false }
677
+ };
678
+ if (effectiveChainProviders) {
679
+ connectorConfig.chainProviders = effectiveChainProviders;
680
+ }
681
+ if (config.transport) {
682
+ connectorConfig.transport = config.transport;
683
+ }
684
+ if (config.nip59) {
685
+ connectorConfig.nip59 = config.nip59;
686
+ }
687
+ autoCreatedConnector = new ConnectorNodeClass(
688
+ connectorConfig,
765
689
  connectorLogger
766
690
  );
767
691
  await autoCreatedConnector.start();
@@ -1506,7 +1430,7 @@ var WorkflowOrchestrator = class {
1506
1430
 
1507
1431
  // src/swarm-coordinator.ts
1508
1432
  import {
1509
- ToonError as ToonError3,
1433
+ ToonError as ToonError2,
1510
1434
  parseSwarmRequest,
1511
1435
  parseSwarmSelection
1512
1436
  } from "@toon-protocol/core";
@@ -1557,7 +1481,7 @@ var SwarmCoordinator = class {
1557
1481
  async startSwarm(swarmRequest) {
1558
1482
  const parsed = parseSwarmRequest(swarmRequest);
1559
1483
  if (!parsed) {
1560
- throw new ToonError3(
1484
+ throw new ToonError2(
1561
1485
  "Invalid swarm request event: missing swarm tag or invalid Kind 5xxx",
1562
1486
  "DVM_INVALID_KIND"
1563
1487
  );
@@ -1616,32 +1540,32 @@ var SwarmCoordinator = class {
1616
1540
  */
1617
1541
  async selectWinner(selectionEvent) {
1618
1542
  if (this.state === "settled") {
1619
- throw new ToonError3(
1543
+ throw new ToonError2(
1620
1544
  "Swarm has already been settled; duplicate selection rejected",
1621
1545
  "DVM_SWARM_ALREADY_SETTLED"
1622
1546
  );
1623
1547
  }
1624
1548
  if (this.state !== "judging") {
1625
- throw new ToonError3(
1549
+ throw new ToonError2(
1626
1550
  `Cannot select winner in state '${this.state}'; swarm must be in 'judging' state`,
1627
1551
  "DVM_SWARM_INVALID_SELECTION"
1628
1552
  );
1629
1553
  }
1630
1554
  if (selectionEvent.pubkey !== this.customerPubkey) {
1631
- throw new ToonError3(
1555
+ throw new ToonError2(
1632
1556
  "Selection event pubkey does not match the swarm request customer pubkey",
1633
1557
  "DVM_SWARM_INVALID_SELECTION"
1634
1558
  );
1635
1559
  }
1636
1560
  const parsed = parseSwarmSelection(selectionEvent);
1637
1561
  if (!parsed) {
1638
- throw new ToonError3(
1562
+ throw new ToonError2(
1639
1563
  "Invalid swarm selection event: missing winner tag or invalid Kind 7000",
1640
1564
  "DVM_SWARM_INVALID_SELECTION"
1641
1565
  );
1642
1566
  }
1643
1567
  if (parsed.swarmRequestEventId !== this.swarmRequestId) {
1644
- throw new ToonError3(
1568
+ throw new ToonError2(
1645
1569
  `Selection event references swarm '${parsed.swarmRequestEventId}' but this swarm is '${this.swarmRequestId}'`,
1646
1570
  "DVM_SWARM_INVALID_SELECTION"
1647
1571
  );
@@ -1650,7 +1574,7 @@ var SwarmCoordinator = class {
1650
1574
  (s) => s.id === parsed.winnerResultEventId
1651
1575
  );
1652
1576
  if (!winnerSubmission) {
1653
- throw new ToonError3(
1577
+ throw new ToonError2(
1654
1578
  `Winner result event ID '${parsed.winnerResultEventId}' not found in collected submissions`,
1655
1579
  "DVM_SWARM_INVALID_SELECTION"
1656
1580
  );
@@ -1661,7 +1585,7 @@ var SwarmCoordinator = class {
1661
1585
  this.settlementSucceeded = true;
1662
1586
  } catch (_err) {
1663
1587
  this.settlementSucceeded = false;
1664
- throw new ToonError3(
1588
+ throw new ToonError2(
1665
1589
  "Settlement failed for winning provider; swarm remains in judging state for retry",
1666
1590
  "DVM_SWARM_SETTLEMENT_FAILED"
1667
1591
  );
@@ -1825,6 +1749,10 @@ function createArweaveDvmHandler(config) {
1825
1749
  data: Buffer.from(txId).toString("base64")
1826
1750
  };
1827
1751
  } catch (error) {
1752
+ console.error(
1753
+ "[ArweaveDvmHandler] Chunked upload failed:",
1754
+ error instanceof Error ? error.stack ?? error.message : error
1755
+ );
1828
1756
  const internalMsg = error instanceof Error ? error.message : "Unknown chunk error";
1829
1757
  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
1758
  return {
@@ -1840,7 +1768,11 @@ function createArweaveDvmHandler(config) {
1840
1768
  accept: true,
1841
1769
  data: Buffer.from(txId).toString("base64")
1842
1770
  };
1843
- } catch {
1771
+ } catch (error) {
1772
+ const cause = error instanceof Error ? error.stack ?? error.message : String(error);
1773
+ console.error(
1774
+ `[ArweaveDvmHandler] Arweave upload failed (${parsed.blobData.length} bytes). Cause: ${cause}`
1775
+ );
1844
1776
  return {
1845
1777
  accept: false,
1846
1778
  code: "T00",
@@ -2062,18 +1994,1076 @@ async function uploadBlobChunked(node, blob, destination, options) {
2062
1994
  }
2063
1995
  return lastResult.eventId;
2064
1996
  }
1997
+
1998
+ // src/settlement/evm.ts
1999
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
2000
+ import { keccak_256 } from "@noble/hashes/sha3.js";
2001
+ import { bytesToHex } from "@noble/hashes/utils.js";
2002
+
2003
+ // src/settlement/hashes.ts
2004
+ import {
2005
+ hexToBytes as hexToBytes2,
2006
+ bigintToBytes32BE,
2007
+ concatBytes,
2008
+ balanceProofHashEvm,
2009
+ balanceProofHashSolana,
2010
+ minaHashToField,
2011
+ balanceProofFieldsMina
2012
+ } from "@toon-protocol/core";
2013
+
2014
+ // src/settlement/evm.ts
2015
+ var EVM_SETTLEMENT_FUNCTION_SIGNATURE = "updateBalance(bytes32,uint256,uint256,address,bytes)";
2016
+ var EVM_SETTLEMENT_EVENT_SIGNATURE = "SettlementSucceeded(bytes32,uint256,uint256,address)";
2017
+ var EVM_SETTLEMENT_FUNCTION_SELECTOR = keccak_256(
2018
+ new TextEncoder().encode(EVM_SETTLEMENT_FUNCTION_SIGNATURE)
2019
+ ).slice(0, 4);
2020
+ var EVM_SETTLEMENT_EVENT_TOPIC = "0x" + bytesToHex(
2021
+ keccak_256(new TextEncoder().encode(EVM_SETTLEMENT_EVENT_SIGNATURE))
2022
+ );
2023
+ function recoverEvmSignerAddress(claim) {
2024
+ if (claim.channelId === void 0 || claim.cumulativeAmount === void 0 || claim.nonce === void 0 || claim.recipient === void 0) {
2025
+ throw new SettlementTxError(
2026
+ "MISSING_SETTLEMENT_METADATA",
2027
+ "Claim missing channelId/cumulativeAmount/nonce/recipient for EVM signer recovery"
2028
+ );
2029
+ }
2030
+ if (claim.claimBytes.length !== 65) {
2031
+ throw new SettlementTxError(
2032
+ "INVALID_SIGNATURE_LENGTH",
2033
+ `EVM signature must be 65 bytes (r||s||v), got ${claim.claimBytes.length}`
2034
+ );
2035
+ }
2036
+ const v = claim.claimBytes[64];
2037
+ if (v !== 27 && v !== 28) {
2038
+ throw new SettlementTxError(
2039
+ "INVALID_SIGNATURE_V",
2040
+ `EVM signature v must be 27 or 28, got ${v}`
2041
+ );
2042
+ }
2043
+ const recovery = v - 27;
2044
+ const compactRS = claim.claimBytes.slice(0, 64);
2045
+ let msgHash;
2046
+ let uncompressedPubkey;
2047
+ try {
2048
+ const channelIdBytes = hexToBytes2(claim.channelId);
2049
+ const recipientBytes = hexToBytes2(claim.recipient);
2050
+ if (channelIdBytes.length !== 32) {
2051
+ throw new Error(
2052
+ `channelId must be 32 bytes (got ${channelIdBytes.length})`
2053
+ );
2054
+ }
2055
+ if (recipientBytes.length !== 20) {
2056
+ throw new Error(
2057
+ `recipient must be 20 bytes (got ${recipientBytes.length})`
2058
+ );
2059
+ }
2060
+ msgHash = balanceProofHashEvm(
2061
+ channelIdBytes,
2062
+ BigInt(claim.cumulativeAmount),
2063
+ BigInt(claim.nonce),
2064
+ recipientBytes
2065
+ );
2066
+ const sig = secp256k1.Signature.fromBytes(
2067
+ compactRS,
2068
+ "compact"
2069
+ ).addRecoveryBit(recovery);
2070
+ const point = sig.recoverPublicKey(msgHash);
2071
+ uncompressedPubkey = point.toBytes(false);
2072
+ } catch (err) {
2073
+ throw new SettlementTxError(
2074
+ "ENCODING_FAILED",
2075
+ `EVM signer recovery failed: ${err instanceof Error ? err.message : String(err)}`,
2076
+ { cause: err }
2077
+ );
2078
+ }
2079
+ const addrHash = keccak_256(uncompressedPubkey.slice(1));
2080
+ return "0x" + bytesToHex(addrHash.slice(-20)).toLowerCase();
2081
+ }
2082
+ function padLeft32(bytes) {
2083
+ if (bytes.length > 32) {
2084
+ throw new SettlementTxError(
2085
+ "ENCODING_FAILED",
2086
+ `cannot pad bytes of length ${bytes.length} to 32`
2087
+ );
2088
+ }
2089
+ const out = new Uint8Array(32);
2090
+ out.set(bytes, 32 - bytes.length);
2091
+ return out;
2092
+ }
2093
+ function padRight32(bytes) {
2094
+ const padded = Math.ceil(bytes.length / 32) * 32;
2095
+ const out = new Uint8Array(padded);
2096
+ out.set(bytes, 0);
2097
+ return out;
2098
+ }
2099
+ function encodeUpdateBalanceCallData(channelIdBytes, cumulativeAmount, nonce, recipientBytes, signature) {
2100
+ if (channelIdBytes.length !== 32) {
2101
+ throw new SettlementTxError(
2102
+ "ENCODING_FAILED",
2103
+ `channelId must be 32 bytes (got ${channelIdBytes.length})`
2104
+ );
2105
+ }
2106
+ if (recipientBytes.length !== 20) {
2107
+ throw new SettlementTxError(
2108
+ "ENCODING_FAILED",
2109
+ `recipient must be 20 bytes (got ${recipientBytes.length})`
2110
+ );
2111
+ }
2112
+ const channelIdWord = channelIdBytes;
2113
+ const cumulativeWord = bigintToBytes32BE(cumulativeAmount);
2114
+ const nonceWord = bigintToBytes32BE(nonce);
2115
+ const recipientWord = padLeft32(recipientBytes);
2116
+ const offsetWord = bigintToBytes32BE(160n);
2117
+ const sigLenWord = bigintToBytes32BE(BigInt(signature.length));
2118
+ const sigPadded = padRight32(signature);
2119
+ return concatBytes(
2120
+ EVM_SETTLEMENT_FUNCTION_SELECTOR,
2121
+ channelIdWord,
2122
+ cumulativeWord,
2123
+ nonceWord,
2124
+ recipientWord,
2125
+ offsetWord,
2126
+ sigLenWord,
2127
+ sigPadded
2128
+ );
2129
+ }
2130
+ function rlpEncodeBytes(bytes) {
2131
+ if (bytes.length === 1 && bytes[0] !== void 0 && bytes[0] < 128) {
2132
+ return bytes;
2133
+ }
2134
+ if (bytes.length < 56) {
2135
+ return concatBytes(new Uint8Array([128 + bytes.length]), bytes);
2136
+ }
2137
+ const lenBytes = bigintToMinimalBytes(BigInt(bytes.length));
2138
+ return concatBytes(new Uint8Array([183 + lenBytes.length]), lenBytes, bytes);
2139
+ }
2140
+ function rlpEncodeList(items) {
2141
+ const payload = concatBytes(...items);
2142
+ if (payload.length < 56) {
2143
+ return concatBytes(new Uint8Array([192 + payload.length]), payload);
2144
+ }
2145
+ const lenBytes = bigintToMinimalBytes(BigInt(payload.length));
2146
+ return concatBytes(
2147
+ new Uint8Array([247 + lenBytes.length]),
2148
+ lenBytes,
2149
+ payload
2150
+ );
2151
+ }
2152
+ function bigintToMinimalBytes(x) {
2153
+ if (x < 0n) {
2154
+ throw new SettlementTxError(
2155
+ "ENCODING_FAILED",
2156
+ "bigintToMinimalBytes: negative input"
2157
+ );
2158
+ }
2159
+ if (x === 0n) return new Uint8Array(0);
2160
+ let hex = x.toString(16);
2161
+ if (hex.length % 2) hex = "0" + hex;
2162
+ const out = new Uint8Array(hex.length / 2);
2163
+ for (let i = 0; i < out.length; i++) {
2164
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
2165
+ }
2166
+ return out;
2167
+ }
2168
+ function rlpEncodeUint(x) {
2169
+ return rlpEncodeBytes(bigintToMinimalBytes(x));
2170
+ }
2171
+ function rlpEncodeUnsignedTx(params) {
2172
+ return rlpEncodeList([
2173
+ rlpEncodeUint(params.nonce),
2174
+ rlpEncodeUint(params.gasPrice),
2175
+ rlpEncodeUint(params.gasLimit),
2176
+ rlpEncodeBytes(params.to),
2177
+ rlpEncodeUint(params.value),
2178
+ rlpEncodeBytes(params.data),
2179
+ rlpEncodeUint(params.chainId),
2180
+ rlpEncodeUint(0n),
2181
+ rlpEncodeUint(0n)
2182
+ ]);
2183
+ }
2184
+ function buildEvmSettlementTx(winner, signer, recipient, selectedClaimIndex, claimsMerged) {
2185
+ if (winner.channelId === void 0 || winner.cumulativeAmount === void 0 || winner.nonce === void 0 || winner.recipient === void 0 || winner.millSignerAddress === void 0) {
2186
+ throw new SettlementTxError(
2187
+ "MISSING_SETTLEMENT_METADATA",
2188
+ "EVM winner claim missing settlement-context fields"
2189
+ );
2190
+ }
2191
+ if (!signer.contractAddress) {
2192
+ throw new SettlementTxError(
2193
+ "INVALID_INPUT",
2194
+ `EVM MillSignerConfig.contractAddress is required for chain ${winner.pair.to.chain}`
2195
+ );
2196
+ }
2197
+ if (typeof signer.chainId !== "number" || !Number.isInteger(signer.chainId) || signer.chainId <= 0) {
2198
+ throw new SettlementTxError(
2199
+ "INVALID_INPUT",
2200
+ `EVM MillSignerConfig.chainId must be a positive integer, got ${signer.chainId}`
2201
+ );
2202
+ }
2203
+ const channelIdBytes = hexToBytes2(winner.channelId);
2204
+ const recipientBytes = hexToBytes2(recipient);
2205
+ const contractBytes = hexToBytes2(signer.contractAddress);
2206
+ const calldata = encodeUpdateBalanceCallData(
2207
+ channelIdBytes,
2208
+ BigInt(winner.cumulativeAmount),
2209
+ BigInt(winner.nonce),
2210
+ recipientBytes,
2211
+ winner.claimBytes
2212
+ );
2213
+ const unsignedTxBytes = rlpEncodeUnsignedTx({
2214
+ nonce: 0n,
2215
+ gasPrice: 0n,
2216
+ gasLimit: 0n,
2217
+ to: contractBytes,
2218
+ value: 0n,
2219
+ data: calldata,
2220
+ chainId: BigInt(signer.chainId)
2221
+ });
2222
+ return {
2223
+ chain: winner.pair.to.chain,
2224
+ chainKind: "evm",
2225
+ channelId: winner.channelId,
2226
+ cumulativeAmount: winner.cumulativeAmount,
2227
+ nonce: winner.nonce,
2228
+ recipient,
2229
+ millSignerAddress: winner.millSignerAddress,
2230
+ unsignedTxBytes,
2231
+ expectedEventSignature: EVM_SETTLEMENT_EVENT_TOPIC,
2232
+ claimsMerged,
2233
+ selectedClaimIndex,
2234
+ sourceChain: winner.pair.from.chain,
2235
+ sourceAssetCode: winner.pair.from.assetCode
2236
+ };
2237
+ }
2238
+ function fillEvmSettlementTxGas(bundle, gas, signer) {
2239
+ if (bundle.chainKind !== "evm") {
2240
+ throw new SettlementTxError(
2241
+ "UNSUPPORTED_CHAIN",
2242
+ `fillEvmSettlementTxGas requires chainKind=evm, got ${bundle.chainKind}`
2243
+ );
2244
+ }
2245
+ if (!signer.contractAddress) {
2246
+ throw new SettlementTxError(
2247
+ "INVALID_INPUT",
2248
+ "EVM MillSignerConfig.contractAddress is required for gas-fill"
2249
+ );
2250
+ }
2251
+ if (typeof signer.chainId !== "number" || signer.chainId <= 0) {
2252
+ throw new SettlementTxError(
2253
+ "INVALID_INPUT",
2254
+ "EVM MillSignerConfig.chainId must be a positive integer"
2255
+ );
2256
+ }
2257
+ const channelIdBytes = hexToBytes2(bundle.channelId);
2258
+ const recipientBytes = hexToBytes2(bundle.recipient);
2259
+ const contractBytes = hexToBytes2(signer.contractAddress);
2260
+ const sig = extractSignatureFromBundle(bundle);
2261
+ const calldata = encodeUpdateBalanceCallData(
2262
+ channelIdBytes,
2263
+ BigInt(bundle.cumulativeAmount),
2264
+ BigInt(bundle.nonce),
2265
+ recipientBytes,
2266
+ sig
2267
+ );
2268
+ return rlpEncodeUnsignedTx({
2269
+ nonce: gas.nonce,
2270
+ gasPrice: gas.gasPrice,
2271
+ gasLimit: gas.gasLimit,
2272
+ to: contractBytes,
2273
+ value: 0n,
2274
+ data: calldata,
2275
+ chainId: BigInt(signer.chainId)
2276
+ });
2277
+ }
2278
+ function extractSignatureFromBundle(bundle) {
2279
+ const tx = bundle.unsignedTxBytes;
2280
+ const list = rlpDecodeList(tx);
2281
+ if (list.length < 6) {
2282
+ throw new SettlementTxError(
2283
+ "ENCODING_FAILED",
2284
+ "unsignedTxBytes is not a 9-element RLP list"
2285
+ );
2286
+ }
2287
+ const data = list[5];
2288
+ if (!data) {
2289
+ throw new SettlementTxError(
2290
+ "ENCODING_FAILED",
2291
+ "unsignedTxBytes missing data field"
2292
+ );
2293
+ }
2294
+ const sigLenOffset = 4 + 5 * 32;
2295
+ if (data.length < sigLenOffset + 32) {
2296
+ throw new SettlementTxError(
2297
+ "ENCODING_FAILED",
2298
+ "calldata too short to contain signature length word"
2299
+ );
2300
+ }
2301
+ const sigLenBytes = data.slice(sigLenOffset, sigLenOffset + 32);
2302
+ let sigLen = 0n;
2303
+ for (const b of sigLenBytes) sigLen = sigLen << 8n | BigInt(b);
2304
+ const sigStart = sigLenOffset + 32;
2305
+ const sigEnd = sigStart + Number(sigLen);
2306
+ if (sigEnd > data.length) {
2307
+ throw new SettlementTxError(
2308
+ "ENCODING_FAILED",
2309
+ "calldata truncated before end of signature bytes"
2310
+ );
2311
+ }
2312
+ return data.slice(sigStart, sigEnd);
2313
+ }
2314
+ function rlpDecodeList(buf) {
2315
+ if (buf.length === 0) {
2316
+ throw new SettlementTxError("ENCODING_FAILED", "empty RLP input");
2317
+ }
2318
+ const first = buf[0] ?? 0;
2319
+ let offset;
2320
+ let listEnd;
2321
+ if (first >= 192 && first <= 247) {
2322
+ offset = 1;
2323
+ listEnd = 1 + (first - 192);
2324
+ } else if (first >= 248 && first <= 255) {
2325
+ const lenOfLen = first - 247;
2326
+ let listLen = 0;
2327
+ for (let i = 0; i < lenOfLen; i++) {
2328
+ listLen = listLen << 8 | (buf[1 + i] ?? 0);
2329
+ }
2330
+ offset = 1 + lenOfLen;
2331
+ listEnd = offset + listLen;
2332
+ } else {
2333
+ throw new SettlementTxError(
2334
+ "ENCODING_FAILED",
2335
+ `rlpDecodeList: input is not a list (first byte 0x${first.toString(16)})`
2336
+ );
2337
+ }
2338
+ const items = [];
2339
+ let p = offset;
2340
+ while (p < listEnd) {
2341
+ const b = buf[p] ?? 0;
2342
+ if (b < 128) {
2343
+ items.push(buf.slice(p, p + 1));
2344
+ p += 1;
2345
+ } else if (b <= 183) {
2346
+ const len = b - 128;
2347
+ items.push(buf.slice(p + 1, p + 1 + len));
2348
+ p += 1 + len;
2349
+ } else if (b <= 191) {
2350
+ const lenOfLen = b - 183;
2351
+ let len = 0;
2352
+ for (let i = 0; i < lenOfLen; i++) {
2353
+ len = len << 8 | (buf[p + 1 + i] ?? 0);
2354
+ }
2355
+ items.push(buf.slice(p + 1 + lenOfLen, p + 1 + lenOfLen + len));
2356
+ p += 1 + lenOfLen + len;
2357
+ } else {
2358
+ throw new SettlementTxError(
2359
+ "ENCODING_FAILED",
2360
+ "rlpDecodeList: nested list not supported in minimal decoder"
2361
+ );
2362
+ }
2363
+ }
2364
+ return items;
2365
+ }
2366
+ function verifyEvmClaimSignature(claim, expectedAddress) {
2367
+ const recovered = recoverEvmSignerAddress(claim);
2368
+ const expected = expectedAddress.toLowerCase();
2369
+ return {
2370
+ valid: recovered.toLowerCase() === expected,
2371
+ recovered
2372
+ };
2373
+ }
2374
+
2375
+ // src/settlement/mina.ts
2376
+ var MINA_NETWORK = "mainnet";
2377
+ async function loadMinaSignerClient() {
2378
+ try {
2379
+ const lib = await import("mina-signer");
2380
+ const Ctor = "default" in lib ? lib.default : lib;
2381
+ return new Ctor({ network: MINA_NETWORK });
2382
+ } catch {
2383
+ return void 0;
2384
+ }
2385
+ }
2386
+ function verifyMinaSignature(claim, expectedSignerAddress, client) {
2387
+ if (claim.channelId === void 0 || claim.cumulativeAmount === void 0 || claim.nonce === void 0 || claim.recipient === void 0) {
2388
+ throw new SettlementTxError(
2389
+ "MISSING_SETTLEMENT_METADATA",
2390
+ "Claim missing channelId/cumulativeAmount/nonce/recipient for Mina signature verify"
2391
+ );
2392
+ }
2393
+ if (claim.claimBytes.length === 0) {
2394
+ throw new SettlementTxError(
2395
+ "INVALID_SIGNATURE_LENGTH",
2396
+ "Mina claimBytes is empty (expected UTF-8 base58 signature string)"
2397
+ );
2398
+ }
2399
+ const signature = new TextDecoder().decode(claim.claimBytes);
2400
+ const fields = balanceProofFieldsMina(
2401
+ claim.channelId,
2402
+ BigInt(claim.cumulativeAmount),
2403
+ BigInt(claim.nonce),
2404
+ claim.recipient
2405
+ );
2406
+ try {
2407
+ return client.verifyFields({
2408
+ data: fields,
2409
+ signature,
2410
+ publicKey: expectedSignerAddress
2411
+ });
2412
+ } catch {
2413
+ return false;
2414
+ }
2415
+ }
2416
+ function buildMinaSettlementTx(winner, signer, recipient, selectedClaimIndex, claimsMerged) {
2417
+ if (winner.channelId === void 0 || winner.cumulativeAmount === void 0 || winner.nonce === void 0 || winner.recipient === void 0 || winner.millSignerAddress === void 0) {
2418
+ throw new SettlementTxError(
2419
+ "MISSING_SETTLEMENT_METADATA",
2420
+ "Mina winner claim missing settlement-context fields"
2421
+ );
2422
+ }
2423
+ if (!signer.address) {
2424
+ throw new SettlementTxError(
2425
+ "INVALID_INPUT",
2426
+ `Mina MillSignerConfig.address is required for chain ${winner.pair.to.chain}`
2427
+ );
2428
+ }
2429
+ if (winner.claimBytes.length === 0) {
2430
+ throw new SettlementTxError(
2431
+ "INVALID_SIGNATURE_LENGTH",
2432
+ "Mina winner claimBytes is empty (expected UTF-8 base58 signature string)"
2433
+ );
2434
+ }
2435
+ const unsignedTxBytes = winner.claimBytes;
2436
+ return {
2437
+ chain: winner.pair.to.chain,
2438
+ chainKind: "mina",
2439
+ channelId: winner.channelId,
2440
+ cumulativeAmount: winner.cumulativeAmount,
2441
+ nonce: winner.nonce,
2442
+ recipient,
2443
+ millSignerAddress: winner.millSignerAddress,
2444
+ unsignedTxBytes,
2445
+ claimsMerged,
2446
+ selectedClaimIndex,
2447
+ sourceChain: winner.pair.from.chain,
2448
+ sourceAssetCode: winner.pair.from.assetCode
2449
+ };
2450
+ }
2451
+
2452
+ // src/settlement/solana.ts
2453
+ import { ed25519 } from "@noble/curves/ed25519.js";
2454
+ import { sha256 } from "@noble/hashes/sha2.js";
2455
+ function verifyEd25519Signature(claim, expectedSignerAddress) {
2456
+ if (claim.channelId === void 0 || claim.cumulativeAmount === void 0 || claim.nonce === void 0 || claim.recipient === void 0) {
2457
+ throw new SettlementTxError(
2458
+ "MISSING_SETTLEMENT_METADATA",
2459
+ "Claim missing channelId/cumulativeAmount/nonce/recipient for Solana signature verify"
2460
+ );
2461
+ }
2462
+ if (claim.claimBytes.length !== 64) {
2463
+ throw new SettlementTxError(
2464
+ "INVALID_SIGNATURE_LENGTH",
2465
+ `Solana signature must be 64 bytes, got ${claim.claimBytes.length}`
2466
+ );
2467
+ }
2468
+ const msgHash = balanceProofHashSolana(
2469
+ claim.channelId,
2470
+ BigInt(claim.cumulativeAmount),
2471
+ BigInt(claim.nonce),
2472
+ claim.recipient
2473
+ );
2474
+ let pubkeyBytes;
2475
+ try {
2476
+ pubkeyBytes = base58Decode(expectedSignerAddress);
2477
+ } catch (err) {
2478
+ throw new SettlementTxError(
2479
+ "INVALID_INPUT",
2480
+ `Solana expected signer address is not valid base58: ${expectedSignerAddress}`,
2481
+ { cause: err }
2482
+ );
2483
+ }
2484
+ if (pubkeyBytes.length !== 32) {
2485
+ throw new SettlementTxError(
2486
+ "INVALID_INPUT",
2487
+ `Solana expected signer pubkey must be 32 bytes, got ${pubkeyBytes.length}`
2488
+ );
2489
+ }
2490
+ try {
2491
+ return ed25519.verify(claim.claimBytes, msgHash, pubkeyBytes);
2492
+ } catch {
2493
+ return false;
2494
+ }
2495
+ }
2496
+ var SOLANA_UPDATE_BALANCE_DISCRIMINATOR = sha256(
2497
+ new TextEncoder().encode("global:update_balance")
2498
+ ).slice(0, 8);
2499
+ function bigintToBytes8LE(x) {
2500
+ if (x < 0n) {
2501
+ throw new SettlementTxError(
2502
+ "ENCODING_FAILED",
2503
+ "bigintToBytes8LE: negative input"
2504
+ );
2505
+ }
2506
+ if (x > 0xffffffffffffffffn) {
2507
+ throw new SettlementTxError(
2508
+ "ENCODING_FAILED",
2509
+ "bigintToBytes8LE: value exceeds 64 bits"
2510
+ );
2511
+ }
2512
+ const out = new Uint8Array(8);
2513
+ let v = x;
2514
+ for (let i = 0; i < 8; i++) {
2515
+ out[i] = Number(v & 0xffn);
2516
+ v >>= 8n;
2517
+ }
2518
+ return out;
2519
+ }
2520
+ function buildSolanaSettlementTx(winner, signer, recipient, selectedClaimIndex, claimsMerged) {
2521
+ if (winner.channelId === void 0 || winner.cumulativeAmount === void 0 || winner.nonce === void 0 || winner.recipient === void 0 || winner.millSignerAddress === void 0) {
2522
+ throw new SettlementTxError(
2523
+ "MISSING_SETTLEMENT_METADATA",
2524
+ "Solana winner claim missing settlement-context fields"
2525
+ );
2526
+ }
2527
+ if (!signer.programId) {
2528
+ throw new SettlementTxError(
2529
+ "INVALID_INPUT",
2530
+ `Solana MillSignerConfig.programId is required for chain ${winner.pair.to.chain}`
2531
+ );
2532
+ }
2533
+ let programIdBytes;
2534
+ let recipientBytes;
2535
+ let millBytes;
2536
+ let channelIdBytes;
2537
+ try {
2538
+ programIdBytes = base58Decode(signer.programId);
2539
+ recipientBytes = base58Decode(recipient);
2540
+ millBytes = base58Decode(winner.millSignerAddress);
2541
+ channelIdBytes = base58Decode(winner.channelId);
2542
+ } catch (err) {
2543
+ throw new SettlementTxError(
2544
+ "ENCODING_FAILED",
2545
+ `Solana settlement tx: base58 decode failed (${err instanceof Error ? err.message : String(err)})`,
2546
+ { cause: err }
2547
+ );
2548
+ }
2549
+ if (programIdBytes.length !== 32) {
2550
+ throw new SettlementTxError(
2551
+ "INVALID_INPUT",
2552
+ `Solana programId must be 32 bytes, got ${programIdBytes.length}`
2553
+ );
2554
+ }
2555
+ if (recipientBytes.length !== 32) {
2556
+ throw new SettlementTxError(
2557
+ "INVALID_INPUT",
2558
+ `Solana recipient must decode to 32 bytes, got ${recipientBytes.length}`
2559
+ );
2560
+ }
2561
+ if (millBytes.length !== 32) {
2562
+ throw new SettlementTxError(
2563
+ "INVALID_INPUT",
2564
+ `Solana millSignerAddress must decode to 32 bytes, got ${millBytes.length}`
2565
+ );
2566
+ }
2567
+ if (channelIdBytes.length !== 32) {
2568
+ throw new SettlementTxError(
2569
+ "INVALID_INPUT",
2570
+ `Solana channelId must decode to 32 bytes, got ${channelIdBytes.length}`
2571
+ );
2572
+ }
2573
+ const instructionData = concatBytes(
2574
+ SOLANA_UPDATE_BALANCE_DISCRIMINATOR,
2575
+ bigintToBytes8LE(BigInt(winner.cumulativeAmount)),
2576
+ bigintToBytes8LE(BigInt(winner.nonce)),
2577
+ winner.claimBytes
2578
+ );
2579
+ const accounts = [recipientBytes, millBytes, channelIdBytes, programIdBytes];
2580
+ const header = new Uint8Array([1, 0, 1]);
2581
+ const accountsCountByte = new Uint8Array([accounts.length]);
2582
+ const recentBlockhash = new Uint8Array(32);
2583
+ const instructionsCountByte = new Uint8Array([1]);
2584
+ const programIdIndex = new Uint8Array([3]);
2585
+ const instrAccountsLen = new Uint8Array([3]);
2586
+ const instrAccountIndices = new Uint8Array([0, 1, 2]);
2587
+ if (instructionData.length >= 128) {
2588
+ throw new SettlementTxError(
2589
+ "ENCODING_FAILED",
2590
+ `Solana instruction data too large for simple compact-u16 encoding: ${instructionData.length}`
2591
+ );
2592
+ }
2593
+ const instrDataLen = new Uint8Array([instructionData.length]);
2594
+ const instruction = concatBytes(
2595
+ programIdIndex,
2596
+ instrAccountsLen,
2597
+ instrAccountIndices,
2598
+ instrDataLen,
2599
+ instructionData
2600
+ );
2601
+ const unsignedTxBytes = concatBytes(
2602
+ header,
2603
+ accountsCountByte,
2604
+ ...accounts,
2605
+ recentBlockhash,
2606
+ instructionsCountByte,
2607
+ instruction
2608
+ );
2609
+ return {
2610
+ chain: winner.pair.to.chain,
2611
+ chainKind: "solana",
2612
+ channelId: winner.channelId,
2613
+ cumulativeAmount: winner.cumulativeAmount,
2614
+ nonce: winner.nonce,
2615
+ recipient,
2616
+ millSignerAddress: winner.millSignerAddress,
2617
+ unsignedTxBytes,
2618
+ claimsMerged,
2619
+ selectedClaimIndex,
2620
+ sourceChain: winner.pair.from.chain,
2621
+ sourceAssetCode: winner.pair.from.assetCode
2622
+ };
2623
+ }
2624
+
2625
+ // src/settlement/build-settlement-tx.ts
2626
+ var EVM_ADDRESS_REGEX = /^0x[0-9a-f]{40}$/;
2627
+ function chainKindOf(chain) {
2628
+ if (chain.startsWith("evm:")) return "evm";
2629
+ if (chain.startsWith("solana:")) return "solana";
2630
+ if (chain.startsWith("mina:")) return "mina";
2631
+ return "unknown";
2632
+ }
2633
+ function buildSettlementTx(params) {
2634
+ if (!Array.isArray(params.claims) || params.claims.length === 0) {
2635
+ throw new SettlementTxError(
2636
+ "INVALID_INPUT",
2637
+ "claims array is empty or not an array"
2638
+ );
2639
+ }
2640
+ if (!params.signers || typeof params.signers !== "object") {
2641
+ throw new SettlementTxError("INVALID_INPUT", "signers map is required");
2642
+ }
2643
+ if (!params.recipients || typeof params.recipients !== "object") {
2644
+ throw new SettlementTxError("INVALID_INPUT", "recipients map is required");
2645
+ }
2646
+ const logger = params.logger;
2647
+ const verifySignatures = params.verifySignatures ?? true;
2648
+ for (let i = 0; i < params.claims.length; i++) {
2649
+ const c = params.claims[i];
2650
+ if (c === void 0) continue;
2651
+ if (c.channelId === void 0 || c.nonce === void 0 || c.cumulativeAmount === void 0 || c.recipient === void 0 || c.millSignerAddress === void 0) {
2652
+ throw new SettlementTxError(
2653
+ "MISSING_SETTLEMENT_METADATA",
2654
+ `claims[${i}] missing one or more of { channelId, nonce, cumulativeAmount, recipient, millSignerAddress }`
2655
+ );
2656
+ }
2657
+ }
2658
+ const distinctChains = /* @__PURE__ */ new Set();
2659
+ for (const c of params.claims) distinctChains.add(c.pair.to.chain);
2660
+ for (const chain of distinctChains) {
2661
+ const signer = params.signers[chain];
2662
+ if (!signer) {
2663
+ throw new SettlementTxError(
2664
+ "UNSUPPORTED_CHAIN",
2665
+ `signers map missing entry for chain ${chain}`
2666
+ );
2667
+ }
2668
+ if (!(chain in params.recipients)) {
2669
+ throw new SettlementTxError(
2670
+ "MISSING_RECIPIENT",
2671
+ `recipients map missing entry for chain ${chain}`
2672
+ );
2673
+ }
2674
+ const kind = chainKindOf(chain);
2675
+ if (kind === "evm") {
2676
+ if (!signer.contractAddress || !EVM_ADDRESS_REGEX.test(signer.contractAddress)) {
2677
+ throw new SettlementTxError(
2678
+ "INVALID_INPUT",
2679
+ `EVM signers[${chain}].contractAddress must match 0x + 40 lowercase hex`
2680
+ );
2681
+ }
2682
+ if (typeof signer.chainId !== "number" || !Number.isInteger(signer.chainId) || signer.chainId <= 0) {
2683
+ throw new SettlementTxError(
2684
+ "INVALID_INPUT",
2685
+ `EVM signers[${chain}].chainId must be a positive integer`
2686
+ );
2687
+ }
2688
+ } else if (kind === "solana") {
2689
+ if (!signer.programId || signer.programId.length === 0) {
2690
+ throw new SettlementTxError(
2691
+ "INVALID_INPUT",
2692
+ `Solana signers[${chain}].programId is required`
2693
+ );
2694
+ }
2695
+ let programIdLen;
2696
+ try {
2697
+ programIdLen = base58Decode(signer.programId).length;
2698
+ } catch (err) {
2699
+ throw new SettlementTxError(
2700
+ "INVALID_INPUT",
2701
+ `Solana signers[${chain}].programId is not valid base58`,
2702
+ { cause: err }
2703
+ );
2704
+ }
2705
+ if (programIdLen !== 32) {
2706
+ throw new SettlementTxError(
2707
+ "INVALID_INPUT",
2708
+ `Solana signers[${chain}].programId must decode to 32 bytes (got ${programIdLen})`
2709
+ );
2710
+ }
2711
+ }
2712
+ }
2713
+ const rejected = [];
2714
+ const survivors = [];
2715
+ for (const claim of params.claims) {
2716
+ if (!verifySignatures) {
2717
+ survivors.push(claim);
2718
+ continue;
2719
+ }
2720
+ const chain = claim.pair.to.chain;
2721
+ const signer = params.signers[chain];
2722
+ if (!signer) {
2723
+ throw new SettlementTxError(
2724
+ "UNSUPPORTED_CHAIN",
2725
+ `signers map missing entry for chain ${chain} (unreachable \u2014 validated)`
2726
+ );
2727
+ }
2728
+ const kind = chainKindOf(chain);
2729
+ if (kind === "evm") {
2730
+ try {
2731
+ const { valid, recovered } = verifyEvmClaimSignature(
2732
+ claim,
2733
+ signer.address
2734
+ );
2735
+ if (!valid) {
2736
+ rejected.push({
2737
+ claim,
2738
+ reason: "SIGNER_MISMATCH",
2739
+ details: `recovered=${recovered} expected=${signer.address.toLowerCase()}`
2740
+ });
2741
+ continue;
2742
+ }
2743
+ survivors.push(claim);
2744
+ } catch (err) {
2745
+ rejected.push({
2746
+ claim,
2747
+ reason: "SIGNATURE_INVALID",
2748
+ details: err instanceof Error ? err.message : String(err)
2749
+ });
2750
+ }
2751
+ } else if (kind === "solana") {
2752
+ try {
2753
+ const ok = verifyEd25519Signature(claim, signer.address);
2754
+ if (!ok) {
2755
+ rejected.push({
2756
+ claim,
2757
+ reason: "SIGNER_MISMATCH",
2758
+ details: `ed25519.verify returned false against ${signer.address}`
2759
+ });
2760
+ continue;
2761
+ }
2762
+ survivors.push(claim);
2763
+ } catch (err) {
2764
+ rejected.push({
2765
+ claim,
2766
+ reason: "SIGNATURE_INVALID",
2767
+ details: err instanceof Error ? err.message : String(err)
2768
+ });
2769
+ }
2770
+ } else if (kind === "mina") {
2771
+ if (!params.minaSignerClient) {
2772
+ rejected.push({
2773
+ claim,
2774
+ reason: "MINA_VERIFICATION_UNSUPPORTED",
2775
+ details: "minaSignerClient not provided \u2014 load mina-signer via loadMinaSignerClient() and pass it in params.minaSignerClient to verify mina:* claims"
2776
+ });
2777
+ continue;
2778
+ }
2779
+ try {
2780
+ const ok = verifyMinaSignature(
2781
+ claim,
2782
+ signer.address,
2783
+ params.minaSignerClient
2784
+ );
2785
+ if (!ok) {
2786
+ rejected.push({
2787
+ claim,
2788
+ reason: "SIGNER_MISMATCH",
2789
+ details: `mina-signer verifyFields returned false against ${signer.address}`
2790
+ });
2791
+ continue;
2792
+ }
2793
+ survivors.push(claim);
2794
+ } catch (err) {
2795
+ rejected.push({
2796
+ claim,
2797
+ reason: "SIGNATURE_INVALID",
2798
+ details: err instanceof Error ? err.message : String(err)
2799
+ });
2800
+ }
2801
+ } else {
2802
+ throw new SettlementTxError(
2803
+ "UNSUPPORTED_CHAIN",
2804
+ `Unknown chain kind for ${chain}`
2805
+ );
2806
+ }
2807
+ }
2808
+ logger?.debug?.({
2809
+ event: "build_settlement_tx.verified",
2810
+ survivorCount: survivors.length,
2811
+ rejectedCount: rejected.length
2812
+ });
2813
+ const groups = /* @__PURE__ */ new Map();
2814
+ const originalIndex = /* @__PURE__ */ new Map();
2815
+ for (let i = 0; i < params.claims.length; i++) {
2816
+ const c = params.claims[i];
2817
+ if (c !== void 0) originalIndex.set(c, i);
2818
+ }
2819
+ for (const claim of survivors) {
2820
+ const chain = claim.pair.to.chain;
2821
+ const channelId = claim.channelId;
2822
+ if (channelId === void 0) {
2823
+ throw new SettlementTxError(
2824
+ "MISSING_SETTLEMENT_METADATA",
2825
+ "claim.channelId undefined after validation (unreachable)"
2826
+ );
2827
+ }
2828
+ const key = `${chain}::${channelId}`;
2829
+ let g = groups.get(key);
2830
+ if (!g) {
2831
+ g = { chain, channelId, claims: [] };
2832
+ groups.set(key, g);
2833
+ }
2834
+ const idx = originalIndex.get(claim);
2835
+ g.claims.push({ claim, originalIndex: idx ?? -1 });
2836
+ }
2837
+ const superseded = [];
2838
+ const bundles = [];
2839
+ for (const g of groups.values()) {
2840
+ if (g.claims.length === 0) continue;
2841
+ const first = g.claims[0];
2842
+ if (!first) continue;
2843
+ const firstRecipient = first.claim.recipient;
2844
+ const firstMillSigner = first.claim.millSignerAddress;
2845
+ if (firstRecipient === void 0 || firstMillSigner === void 0) {
2846
+ throw new SettlementTxError(
2847
+ "MISSING_SETTLEMENT_METADATA",
2848
+ "winner claim missing recipient/millSignerAddress (unreachable after validation)"
2849
+ );
2850
+ }
2851
+ for (let i = 1; i < g.claims.length; i++) {
2852
+ const entry = g.claims[i];
2853
+ if (!entry) continue;
2854
+ const c = entry.claim;
2855
+ if (c.recipient !== firstRecipient) {
2856
+ throw new SettlementTxError(
2857
+ "RECIPIENT_MISMATCH",
2858
+ `claims in channel ${g.channelId} disagree on recipient: ${firstRecipient} vs ${String(c.recipient)} (claim indices ${first.originalIndex}, ${entry.originalIndex})`
2859
+ );
2860
+ }
2861
+ if (c.millSignerAddress !== firstMillSigner) {
2862
+ throw new SettlementTxError(
2863
+ "MILL_SIGNER_MISMATCH",
2864
+ `claims in channel ${g.channelId} disagree on millSignerAddress: ${firstMillSigner} vs ${String(c.millSignerAddress)}`
2865
+ );
2866
+ }
2867
+ }
2868
+ const nonceSeen = /* @__PURE__ */ new Set();
2869
+ for (const entry of g.claims) {
2870
+ const nonceStr = entry.claim.nonce;
2871
+ if (nonceStr === void 0) {
2872
+ throw new SettlementTxError(
2873
+ "MISSING_SETTLEMENT_METADATA",
2874
+ "claim.nonce undefined (unreachable after validation)"
2875
+ );
2876
+ }
2877
+ if (nonceSeen.has(nonceStr)) {
2878
+ throw new SettlementTxError(
2879
+ "DUPLICATE_NONCE",
2880
+ `channel ${g.channelId} has two claims with nonce ${nonceStr}`
2881
+ );
2882
+ }
2883
+ nonceSeen.add(nonceStr);
2884
+ }
2885
+ const sorted = [...g.claims].sort((a, b) => {
2886
+ const an = BigInt(a.claim.nonce ?? "0");
2887
+ const bn = BigInt(b.claim.nonce ?? "0");
2888
+ return an < bn ? -1 : an > bn ? 1 : 0;
2889
+ });
2890
+ for (let i = 1; i < sorted.length; i++) {
2891
+ const prevE = sorted[i - 1];
2892
+ const currE = sorted[i];
2893
+ if (!prevE || !currE) continue;
2894
+ const prev = BigInt(prevE.claim.cumulativeAmount ?? "0");
2895
+ const curr = BigInt(currE.claim.cumulativeAmount ?? "0");
2896
+ if (curr < prev) {
2897
+ throw new SettlementTxError(
2898
+ "NON_MONOTONIC_CUMULATIVE",
2899
+ `channel ${g.channelId} nonce ${String(currE.claim.nonce)} has cumulativeAmount ${curr} < previous nonce ${String(prevE.claim.nonce)} cumulativeAmount ${prev}`
2900
+ );
2901
+ }
2902
+ }
2903
+ const winnerEntry = sorted[sorted.length - 1];
2904
+ if (!winnerEntry) continue;
2905
+ const winner = winnerEntry.claim;
2906
+ if (params.includeSuperseded) {
2907
+ for (let i = 0; i < sorted.length - 1; i++) {
2908
+ const e = sorted[i];
2909
+ if (e) superseded.push(e.claim);
2910
+ }
2911
+ }
2912
+ const signer = params.signers[g.chain];
2913
+ const recipient = params.recipients[g.chain];
2914
+ if (!signer || recipient === void 0) {
2915
+ throw new SettlementTxError(
2916
+ "UNSUPPORTED_CHAIN",
2917
+ `signers/recipients missing entry for ${g.chain} (unreachable after validation)`
2918
+ );
2919
+ }
2920
+ const kind = chainKindOf(g.chain);
2921
+ let bundle;
2922
+ if (kind === "evm") {
2923
+ if (recipient.toLowerCase() !== firstRecipient.toLowerCase()) {
2924
+ throw new SettlementTxError(
2925
+ "RECIPIENT_MISMATCH",
2926
+ `recipients[${g.chain}] (${recipient}) does not match Mill-reported recipient (${firstRecipient})`
2927
+ );
2928
+ }
2929
+ bundle = buildEvmSettlementTx(
2930
+ winner,
2931
+ signer,
2932
+ recipient.toLowerCase(),
2933
+ winnerEntry.originalIndex,
2934
+ g.claims.length
2935
+ );
2936
+ } else if (kind === "solana") {
2937
+ if (recipient !== firstRecipient) {
2938
+ throw new SettlementTxError(
2939
+ "RECIPIENT_MISMATCH",
2940
+ `recipients[${g.chain}] (${recipient}) does not match Mill-reported recipient (${firstRecipient})`
2941
+ );
2942
+ }
2943
+ bundle = buildSolanaSettlementTx(
2944
+ winner,
2945
+ signer,
2946
+ recipient,
2947
+ winnerEntry.originalIndex,
2948
+ g.claims.length
2949
+ );
2950
+ } else if (kind === "mina") {
2951
+ if (recipient !== firstRecipient) {
2952
+ throw new SettlementTxError(
2953
+ "RECIPIENT_MISMATCH",
2954
+ `recipients[${g.chain}] (${recipient}) does not match Mill-reported recipient (${firstRecipient})`
2955
+ );
2956
+ }
2957
+ bundle = buildMinaSettlementTx(
2958
+ winner,
2959
+ signer,
2960
+ recipient,
2961
+ winnerEntry.originalIndex,
2962
+ g.claims.length
2963
+ );
2964
+ } else {
2965
+ throw new SettlementTxError(
2966
+ "UNSUPPORTED_CHAIN",
2967
+ `Unknown chain kind for ${g.chain}`
2968
+ );
2969
+ }
2970
+ bundles.push(bundle);
2971
+ }
2972
+ logger?.info?.({
2973
+ event: "build_settlement_tx.complete",
2974
+ bundleCount: bundles.length,
2975
+ rejectedCount: rejected.length,
2976
+ supersededCount: superseded.length
2977
+ });
2978
+ return { bundles, rejected, superseded };
2979
+ }
2980
+ function verifyAccumulatedClaim(claim, signer, minaSignerClient) {
2981
+ const kind = chainKindOf(claim.pair.to.chain);
2982
+ if (kind === "evm") {
2983
+ try {
2984
+ const { valid, recovered } = verifyEvmClaimSignature(
2985
+ claim,
2986
+ signer.address
2987
+ );
2988
+ if (valid) return { valid: true };
2989
+ return {
2990
+ valid: false,
2991
+ reason: `SIGNER_MISMATCH: recovered=${recovered} expected=${signer.address.toLowerCase()}`
2992
+ };
2993
+ } catch (err) {
2994
+ return {
2995
+ valid: false,
2996
+ reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`
2997
+ };
2998
+ }
2999
+ }
3000
+ if (kind === "solana") {
3001
+ try {
3002
+ const ok = verifyEd25519Signature(claim, signer.address);
3003
+ return ok ? { valid: true } : {
3004
+ valid: false,
3005
+ reason: "SIGNER_MISMATCH: ed25519.verify returned false"
3006
+ };
3007
+ } catch (err) {
3008
+ return {
3009
+ valid: false,
3010
+ reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`
3011
+ };
3012
+ }
3013
+ }
3014
+ if (kind === "mina") {
3015
+ if (!minaSignerClient) {
3016
+ return {
3017
+ valid: false,
3018
+ reason: "MINA_VERIFICATION_UNSUPPORTED: pass a mina-signer Client (loadMinaSignerClient()) as minaSignerClient to verify mina:* claims"
3019
+ };
3020
+ }
3021
+ try {
3022
+ const ok = verifyMinaSignature(claim, signer.address, minaSignerClient);
3023
+ return ok ? { valid: true } : {
3024
+ valid: false,
3025
+ reason: "SIGNER_MISMATCH: mina-signer verifyFields returned false"
3026
+ };
3027
+ } catch (err) {
3028
+ return {
3029
+ valid: false,
3030
+ reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`
3031
+ };
3032
+ }
3033
+ }
3034
+ return {
3035
+ valid: false,
3036
+ reason: `UNSUPPORTED_CHAIN: ${claim.pair.to.chain}`
3037
+ };
3038
+ }
2065
3039
  export {
2066
3040
  ChunkManager,
3041
+ GiftWrapError,
2067
3042
  HandlerError,
2068
3043
  HandlerRegistry,
2069
3044
  IdentityError,
2070
3045
  NodeError,
2071
3046
  PricingError,
3047
+ SWAP_HANDLER_REJECT_CODES,
3048
+ SWAP_HANDLER_REJECT_MESSAGES,
3049
+ SettlementTxError,
3050
+ StreamSwapError,
3051
+ SwapHandlerError,
2072
3052
  SwarmCoordinator,
2073
3053
  TurboUploadAdapter,
2074
3054
  VerificationError,
2075
3055
  WorkflowOrchestrator,
3056
+ __testing as __streamSwapTesting,
3057
+ applyRate,
3058
+ balanceProofFieldsMina,
3059
+ balanceProofHashEvm,
3060
+ balanceProofHashSolana,
3061
+ base58Decode,
3062
+ base58Encode,
3063
+ bigintToBytes32BE,
3064
+ buildSettlementTx,
2076
3065
  buildSkillDescriptor,
3066
+ concatBytes,
2077
3067
  createArweaveDvmHandler,
2078
3068
  createEventStorageHandler,
2079
3069
  createHandlerContext,
@@ -2081,11 +3071,30 @@ export {
2081
3071
  createPaymentHandlerBridge,
2082
3072
  createPrefixClaimHandler,
2083
3073
  createPricingValidator,
3074
+ createSwapHandler,
2084
3075
  createVerificationPipeline,
3076
+ decryptFulfillClaim,
3077
+ encryptFulfillClaim,
3078
+ fillEvmSettlementTxGas,
3079
+ findSwapPair,
2085
3080
  fromMnemonic,
3081
+ fromMnemonicFull,
2086
3082
  fromSecretKey,
2087
3083
  generateMnemonic,
3084
+ generateSolanaKeypair,
3085
+ hexToBytes2 as hexToBytes,
3086
+ loadMinaSignerClient,
3087
+ minaHashToField,
3088
+ streamSwap,
3089
+ streamSwapControlled,
3090
+ unwrapSwapPacket,
3091
+ unwrapSwapPacketFromToon,
2088
3092
  uploadBlob,
2089
- uploadBlobChunked
3093
+ uploadBlobChunked,
3094
+ verifyAccumulatedClaim,
3095
+ verifyEd25519Signature,
3096
+ verifyMinaSignature,
3097
+ wrapSwapPacket,
3098
+ wrapSwapPacketToToon
2090
3099
  };
2091
3100
  //# sourceMappingURL=index.js.map