@toon-protocol/sdk 0.5.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,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";
@@ -857,32 +656,36 @@ function createNode(config) {
857
656
  }
858
657
  const hasSettlementAddresses = chainConfig.registryAddress && chainConfig.tokenNetworkAddress;
859
658
  const hasChainProviders = config.chainProviders !== void 0 && config.chainProviders.length > 0;
860
- autoCreatedConnector = new ConnectorNodeClass(
659
+ const effectiveChainProviders = hasChainProviders ? config.chainProviders : hasSettlementAddresses ? [
861
660
  {
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
- },
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,
886
689
  connectorLogger
887
690
  );
888
691
  await autoCreatedConnector.start();
@@ -1627,7 +1430,7 @@ var WorkflowOrchestrator = class {
1627
1430
 
1628
1431
  // src/swarm-coordinator.ts
1629
1432
  import {
1630
- ToonError as ToonError3,
1433
+ ToonError as ToonError2,
1631
1434
  parseSwarmRequest,
1632
1435
  parseSwarmSelection
1633
1436
  } from "@toon-protocol/core";
@@ -1678,7 +1481,7 @@ var SwarmCoordinator = class {
1678
1481
  async startSwarm(swarmRequest) {
1679
1482
  const parsed = parseSwarmRequest(swarmRequest);
1680
1483
  if (!parsed) {
1681
- throw new ToonError3(
1484
+ throw new ToonError2(
1682
1485
  "Invalid swarm request event: missing swarm tag or invalid Kind 5xxx",
1683
1486
  "DVM_INVALID_KIND"
1684
1487
  );
@@ -1737,32 +1540,32 @@ var SwarmCoordinator = class {
1737
1540
  */
1738
1541
  async selectWinner(selectionEvent) {
1739
1542
  if (this.state === "settled") {
1740
- throw new ToonError3(
1543
+ throw new ToonError2(
1741
1544
  "Swarm has already been settled; duplicate selection rejected",
1742
1545
  "DVM_SWARM_ALREADY_SETTLED"
1743
1546
  );
1744
1547
  }
1745
1548
  if (this.state !== "judging") {
1746
- throw new ToonError3(
1549
+ throw new ToonError2(
1747
1550
  `Cannot select winner in state '${this.state}'; swarm must be in 'judging' state`,
1748
1551
  "DVM_SWARM_INVALID_SELECTION"
1749
1552
  );
1750
1553
  }
1751
1554
  if (selectionEvent.pubkey !== this.customerPubkey) {
1752
- throw new ToonError3(
1555
+ throw new ToonError2(
1753
1556
  "Selection event pubkey does not match the swarm request customer pubkey",
1754
1557
  "DVM_SWARM_INVALID_SELECTION"
1755
1558
  );
1756
1559
  }
1757
1560
  const parsed = parseSwarmSelection(selectionEvent);
1758
1561
  if (!parsed) {
1759
- throw new ToonError3(
1562
+ throw new ToonError2(
1760
1563
  "Invalid swarm selection event: missing winner tag or invalid Kind 7000",
1761
1564
  "DVM_SWARM_INVALID_SELECTION"
1762
1565
  );
1763
1566
  }
1764
1567
  if (parsed.swarmRequestEventId !== this.swarmRequestId) {
1765
- throw new ToonError3(
1568
+ throw new ToonError2(
1766
1569
  `Selection event references swarm '${parsed.swarmRequestEventId}' but this swarm is '${this.swarmRequestId}'`,
1767
1570
  "DVM_SWARM_INVALID_SELECTION"
1768
1571
  );
@@ -1771,7 +1574,7 @@ var SwarmCoordinator = class {
1771
1574
  (s) => s.id === parsed.winnerResultEventId
1772
1575
  );
1773
1576
  if (!winnerSubmission) {
1774
- throw new ToonError3(
1577
+ throw new ToonError2(
1775
1578
  `Winner result event ID '${parsed.winnerResultEventId}' not found in collected submissions`,
1776
1579
  "DVM_SWARM_INVALID_SELECTION"
1777
1580
  );
@@ -1782,7 +1585,7 @@ var SwarmCoordinator = class {
1782
1585
  this.settlementSucceeded = true;
1783
1586
  } catch (_err) {
1784
1587
  this.settlementSucceeded = false;
1785
- throw new ToonError3(
1588
+ throw new ToonError2(
1786
1589
  "Settlement failed for winning provider; swarm remains in judging state for retry",
1787
1590
  "DVM_SWARM_SETTLEMENT_FAILED"
1788
1591
  );
@@ -1946,6 +1749,10 @@ function createArweaveDvmHandler(config) {
1946
1749
  data: Buffer.from(txId).toString("base64")
1947
1750
  };
1948
1751
  } catch (error) {
1752
+ console.error(
1753
+ "[ArweaveDvmHandler] Chunked upload failed:",
1754
+ error instanceof Error ? error.stack ?? error.message : error
1755
+ );
1949
1756
  const internalMsg = error instanceof Error ? error.message : "Unknown chunk error";
1950
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";
1951
1758
  return {
@@ -1961,7 +1768,11 @@ function createArweaveDvmHandler(config) {
1961
1768
  accept: true,
1962
1769
  data: Buffer.from(txId).toString("base64")
1963
1770
  };
1964
- } 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
+ );
1965
1776
  return {
1966
1777
  accept: false,
1967
1778
  code: "T00",
@@ -2183,20 +1994,1076 @@ async function uploadBlobChunked(node, blob, destination, options) {
2183
1994
  }
2184
1995
  return lastResult.eventId;
2185
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
+ }
2186
3039
  export {
2187
3040
  ChunkManager,
3041
+ GiftWrapError,
2188
3042
  HandlerError,
2189
3043
  HandlerRegistry,
2190
3044
  IdentityError,
2191
3045
  NodeError,
2192
3046
  PricingError,
3047
+ SWAP_HANDLER_REJECT_CODES,
3048
+ SWAP_HANDLER_REJECT_MESSAGES,
3049
+ SettlementTxError,
3050
+ StreamSwapError,
3051
+ SwapHandlerError,
2193
3052
  SwarmCoordinator,
2194
3053
  TurboUploadAdapter,
2195
3054
  VerificationError,
2196
3055
  WorkflowOrchestrator,
3056
+ __testing as __streamSwapTesting,
3057
+ applyRate,
3058
+ balanceProofFieldsMina,
3059
+ balanceProofHashEvm,
3060
+ balanceProofHashSolana,
2197
3061
  base58Decode,
2198
3062
  base58Encode,
3063
+ bigintToBytes32BE,
3064
+ buildSettlementTx,
2199
3065
  buildSkillDescriptor,
3066
+ concatBytes,
2200
3067
  createArweaveDvmHandler,
2201
3068
  createEventStorageHandler,
2202
3069
  createHandlerContext,
@@ -2204,13 +3071,30 @@ export {
2204
3071
  createPaymentHandlerBridge,
2205
3072
  createPrefixClaimHandler,
2206
3073
  createPricingValidator,
3074
+ createSwapHandler,
2207
3075
  createVerificationPipeline,
3076
+ decryptFulfillClaim,
3077
+ encryptFulfillClaim,
3078
+ fillEvmSettlementTxGas,
3079
+ findSwapPair,
2208
3080
  fromMnemonic,
2209
3081
  fromMnemonicFull,
2210
3082
  fromSecretKey,
2211
3083
  generateMnemonic,
2212
3084
  generateSolanaKeypair,
3085
+ hexToBytes2 as hexToBytes,
3086
+ loadMinaSignerClient,
3087
+ minaHashToField,
3088
+ streamSwap,
3089
+ streamSwapControlled,
3090
+ unwrapSwapPacket,
3091
+ unwrapSwapPacketFromToon,
2213
3092
  uploadBlob,
2214
- uploadBlobChunked
3093
+ uploadBlobChunked,
3094
+ verifyAccumulatedClaim,
3095
+ verifyEd25519Signature,
3096
+ verifyMinaSignature,
3097
+ wrapSwapPacket,
3098
+ wrapSwapPacketToToon
2215
3099
  };
2216
3100
  //# sourceMappingURL=index.js.map