@toon-protocol/sdk 0.5.0 → 0.6.0

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