@toon-protocol/sdk 2.2.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,7 +60,7 @@ const result = await node.start();
60
60
  console.log(`Connected to ${result.peerCount} peers`);
61
61
  ```
62
62
 
63
- > **Easier path:** If you just want to run a relay, use [`@toon-protocol/town`](../town) — it wraps the SDK with sensible defaults and runs out of the box. The SDK is for building custom services where you control the full pipeline.
63
+ > **Easier path:** If you just want to run a relay, use [`@toon-protocol/relay`](https://github.com/toon-protocol/relay) — it wraps the SDK with sensible defaults and runs out of the box. The SDK is for building custom services where you control the full pipeline.
64
64
 
65
65
  ## Where It Sits
66
66
 
@@ -100,17 +100,17 @@ The SDK is the framework layer. You bring handlers; it handles the protocol.
100
100
  | **Self-write bypass** | Events from your own pubkey skip pricing |
101
101
  | **Dev mode** | Skip verification and pricing for testing |
102
102
  | **[TOON](https://github.com/toon-format/toon) encoding** | Events encoded in compact text format, not JSON |
103
- | **Address assignment** | ILP addresses are derived from peering topology — your upstream peer assigns your address automatically based on your pubkey. A node with multiple upstream peers has multiple addresses. See [Protocol — ILP Address Hierarchy](../../docs/protocol.md#ilp-address-hierarchy) |
103
+ | **Address assignment** | ILP addresses are derived from peering topology — your upstream peer assigns your address automatically based on your pubkey. A node with multiple upstream peers has multiple addresses. See [Protocol — ILP Address Hierarchy](https://github.com/toon-protocol/toon-meta/blob/main/docs/protocol.md#ilp-address-hierarchy) |
104
104
  | **Invisible fee calculation** | The SDK computes multi-hop fees internally. Each intermediary advertises a fee-per-byte; the SDK sums them along the route so `publishEvent()` callers pay one total amount |
105
105
 
106
106
  ## Full Documentation
107
107
 
108
- See the [SDK Guide](../../docs/sdk-guide.md) for the complete API reference, handler patterns, verification pipeline details, publishing events, and error handling.
108
+ See the [SDK Guide](https://github.com/toon-protocol/toon-meta/blob/main/docs/sdk-guide.md) for the complete API reference, handler patterns, verification pipeline details, publishing events, and error handling.
109
109
 
110
110
  ## Requirements
111
111
 
112
- - Node.js >= 20
113
- - `@toon-protocol/connector` >= 1.6.0 (peer dependency, optional)
112
+ - Node.js >= 22
113
+ - `@toon-protocol/connector` >= 3.3.3 (peer dependency, optional)
114
114
 
115
115
  ## License
116
116
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ToonError, HandlePacketAcceptResponse, HandlePacketRejectResponse, ReputationScore, SkillDescriptor, EmbeddableConnectorLike, KnownPeer, SettlementConfig, ChainProviderConfigEntry, ConnectorChannelClient, BootstrapEventListener, BootstrapResult, DvmJobStatus, IlpSendResult, ParsedWorkflowDefinition, SwapPair } from '@toon-protocol/core';
2
- export { BootstrapEvent, BootstrapEventListener, HandlePacketAcceptResponse, HandlePacketRejectResponse, SkillDescriptor, balanceProofFieldsMina, balanceProofHashEvm, balanceProofHashSolana, bigintToBytes32BE, concatBytes, hexToBytes, minaHashToField } from '@toon-protocol/core';
2
+ export { BootstrapEvent, BootstrapEventListener, HandlePacketAcceptResponse, HandlePacketRejectResponse, SkillDescriptor, balanceProofFieldsMina, balanceProofHashEvm, balanceProofHashSolana, bigintToBytes32BE, concatBytes, coopCloseHashEvm, eip712DomainSeparatorEvm, hexToBytes, minaHashToField } from '@toon-protocol/core';
3
3
  import { NostrEvent, UnsignedEvent } from 'nostr-tools/pure';
4
4
  import { ToonRoutingMeta } from '@toon-protocol/core/toon';
5
5
  import { TransportConfig } from '@toon-protocol/connector';
@@ -1788,7 +1788,10 @@ interface SwapSignerConfig {
1788
1788
  */
1789
1789
  address: string;
1790
1790
  /**
1791
- * On-chain payment-channel contract address (EVM-only).
1791
+ * On-chain payment-channel contract address (EVM-only). Required for EVM
1792
+ * claims: it is the RLP `to` field AND the EIP-712 `verifyingContract` of the
1793
+ * v2 balance-proof digest (connector#324 finding #1), so it now binds the
1794
+ * signature to this exact deployment.
1792
1795
  */
1793
1796
  contractAddress?: string;
1794
1797
  /**
@@ -1796,7 +1799,9 @@ interface SwapSignerConfig {
1796
1799
  */
1797
1800
  programId?: string;
1798
1801
  /**
1799
- * EVM chain-id (decimal). Required for EVM claims — baked into RLP per EIP-155.
1802
+ * EVM chain-id (decimal). Required for EVM claims — baked into RLP per EIP-155
1803
+ * AND into the EIP-712 domain `chainId` of the v2 balance-proof digest
1804
+ * (connector#324 finding #1), so the signature is valid on this chain only.
1800
1805
  */
1801
1806
  chainId?: number;
1802
1807
  }
package/dist/index.js CHANGED
@@ -2364,9 +2364,9 @@ function validateConfig(config) {
2364
2364
  }
2365
2365
 
2366
2366
  // src/settlement/evm.ts
2367
- import { secp256k1 } from "@noble/curves/secp256k1.js";
2368
2367
  import { keccak_256 } from "@noble/hashes/sha3.js";
2369
2368
  import { bytesToHex } from "@noble/hashes/utils.js";
2369
+ import { recoverEvmSigner } from "@toon-protocol/settlement-digest";
2370
2370
 
2371
2371
  // src/settlement/hashes.ts
2372
2372
  import {
@@ -2374,6 +2374,8 @@ import {
2374
2374
  bigintToBytes32BE,
2375
2375
  concatBytes,
2376
2376
  balanceProofHashEvm,
2377
+ coopCloseHashEvm,
2378
+ eip712DomainSeparatorEvm,
2377
2379
  balanceProofHashSolana,
2378
2380
  minaHashToField,
2379
2381
  balanceProofFieldsMina
@@ -2388,7 +2390,19 @@ var EVM_SETTLEMENT_FUNCTION_SELECTOR = keccak_256(
2388
2390
  var EVM_SETTLEMENT_EVENT_TOPIC = "0x" + bytesToHex(
2389
2391
  keccak_256(new TextEncoder().encode(EVM_SETTLEMENT_EVENT_SIGNATURE))
2390
2392
  );
2391
- function recoverEvmSignerAddress(claim) {
2393
+ function recoverEvmSignerAddress(claim, chainId, verifyingContract) {
2394
+ if (typeof chainId !== "number" || !Number.isInteger(chainId) || chainId <= 0) {
2395
+ throw new SettlementTxError(
2396
+ "INVALID_INPUT",
2397
+ `EVM v2 digest requires a positive integer chainId, got ${chainId}`
2398
+ );
2399
+ }
2400
+ if (!verifyingContract) {
2401
+ throw new SettlementTxError(
2402
+ "INVALID_INPUT",
2403
+ "EVM v2 digest requires verifyingContract (RollingSwapChannel address)"
2404
+ );
2405
+ }
2392
2406
  if (claim.channelId === void 0 || claim.cumulativeAmount === void 0 || claim.nonce === void 0 || claim.recipient === void 0) {
2393
2407
  throw new SettlementTxError(
2394
2408
  "MISSING_SETTLEMENT_METADATA",
@@ -2408,13 +2422,10 @@ function recoverEvmSignerAddress(claim) {
2408
2422
  `EVM signature v must be 27 or 28, got ${v}`
2409
2423
  );
2410
2424
  }
2411
- const recovery = v - 27;
2412
- const compactRS = claim.claimBytes.slice(0, 64);
2413
- let msgHash;
2414
- let uncompressedPubkey;
2415
2425
  try {
2416
2426
  const channelIdBytes = hexToBytes2(claim.channelId);
2417
2427
  const recipientBytes = hexToBytes2(claim.recipient);
2428
+ const verifyingContractBytes = hexToBytes2(verifyingContract);
2418
2429
  if (channelIdBytes.length !== 32) {
2419
2430
  throw new Error(
2420
2431
  `channelId must be 32 bytes (got ${channelIdBytes.length})`
@@ -2425,18 +2436,20 @@ function recoverEvmSignerAddress(claim) {
2425
2436
  `recipient must be 20 bytes (got ${recipientBytes.length})`
2426
2437
  );
2427
2438
  }
2428
- msgHash = balanceProofHashEvm(
2439
+ if (verifyingContractBytes.length !== 20) {
2440
+ throw new Error(
2441
+ `verifyingContract must be 20 bytes (got ${verifyingContractBytes.length})`
2442
+ );
2443
+ }
2444
+ const msgHash = balanceProofHashEvm(
2429
2445
  channelIdBytes,
2430
2446
  BigInt(claim.cumulativeAmount),
2431
2447
  BigInt(claim.nonce),
2432
- recipientBytes
2448
+ recipientBytes,
2449
+ BigInt(chainId),
2450
+ verifyingContractBytes
2433
2451
  );
2434
- const sig = secp256k1.Signature.fromBytes(
2435
- compactRS,
2436
- "compact"
2437
- ).addRecoveryBit(recovery);
2438
- const point = sig.recoverPublicKey(msgHash);
2439
- uncompressedPubkey = point.toBytes(false);
2452
+ return recoverEvmSigner(msgHash, claim.claimBytes);
2440
2453
  } catch (err) {
2441
2454
  throw new SettlementTxError(
2442
2455
  "ENCODING_FAILED",
@@ -2444,8 +2457,6 @@ function recoverEvmSignerAddress(claim) {
2444
2457
  { cause: err }
2445
2458
  );
2446
2459
  }
2447
- const addrHash = keccak_256(uncompressedPubkey.slice(1));
2448
- return "0x" + bytesToHex(addrHash.slice(-20)).toLowerCase();
2449
2460
  }
2450
2461
  function padLeft32(bytes) {
2451
2462
  if (bytes.length > 32) {
@@ -2731,8 +2742,8 @@ function rlpDecodeList(buf) {
2731
2742
  }
2732
2743
  return items;
2733
2744
  }
2734
- function verifyEvmClaimSignature(claim, expectedAddress) {
2735
- const recovered = recoverEvmSignerAddress(claim);
2745
+ function verifyEvmClaimSignature(claim, expectedAddress, chainId, verifyingContract) {
2746
+ const recovered = recoverEvmSignerAddress(claim, chainId, verifyingContract);
2736
2747
  const expected = expectedAddress.toLowerCase();
2737
2748
  return {
2738
2749
  valid: recovered.toLowerCase() === expected,
@@ -3098,7 +3109,9 @@ function buildSettlementTx(params) {
3098
3109
  try {
3099
3110
  const { valid, recovered } = verifyEvmClaimSignature(
3100
3111
  claim,
3101
- signer.address
3112
+ signer.address,
3113
+ signer.chainId,
3114
+ signer.contractAddress
3102
3115
  );
3103
3116
  if (!valid) {
3104
3117
  rejected.push({
@@ -3348,10 +3361,18 @@ function buildSettlementTx(params) {
3348
3361
  function verifyAccumulatedClaim(claim, signer, minaSignerClient) {
3349
3362
  const kind = chainKindOf(claim.pair.to.chain);
3350
3363
  if (kind === "evm") {
3364
+ if (typeof signer.chainId !== "number" || !Number.isInteger(signer.chainId) || signer.chainId <= 0 || !signer.contractAddress) {
3365
+ return {
3366
+ valid: false,
3367
+ reason: "INVALID_INPUT: EVM signer requires chainId (positive integer) + contractAddress for the v2 EIP-712 digest (connector#324 finding #1)"
3368
+ };
3369
+ }
3351
3370
  try {
3352
3371
  const { valid, recovered } = verifyEvmClaimSignature(
3353
3372
  claim,
3354
- signer.address
3373
+ signer.address,
3374
+ signer.chainId,
3375
+ signer.contractAddress
3355
3376
  );
3356
3377
  if (valid) return { valid: true };
3357
3378
  return {
@@ -3441,6 +3462,7 @@ export {
3441
3462
  buildSettlementTx,
3442
3463
  buildSkillDescriptor,
3443
3464
  concatBytes,
3465
+ coopCloseHashEvm,
3444
3466
  createArweaveDvmHandler,
3445
3467
  createEventStorageHandler,
3446
3468
  createHandlerContext,
@@ -3451,6 +3473,7 @@ export {
3451
3473
  createSwapHandler,
3452
3474
  createVerificationPipeline,
3453
3475
  decryptFulfillClaim,
3476
+ eip712DomainSeparatorEvm,
3454
3477
  encodeReceiptSigningPayload,
3455
3478
  encryptFulfillClaim,
3456
3479
  fillEvmSettlementTxGas,