@toon-protocol/client 0.19.0 → 0.20.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.d.ts CHANGED
@@ -4,7 +4,7 @@ export { UI_RENDERER_KIND, UI_TAG, UiCoordinate, buildUiCoordinate, getUiCoordin
4
4
  import { NostrEvent, EventTemplate } from 'nostr-tools/pure';
5
5
  import { MinaSignerClientLike, SettlementBundle } from '@toon-protocol/sdk';
6
6
  import { AccumulatedClaim } from '@toon-protocol/sdk/swap';
7
- import { PrivateKeyAccount, Hex } from 'viem';
7
+ import { PrivateKeyAccount, Hex, Address } from 'viem';
8
8
  import { PrivateKeyAccount as PrivateKeyAccount$1 } from 'viem/accounts';
9
9
  export { A2uiDecision, ConsentDecision, ConsentRequest, DispatchGuardInfo, DispatchInput, GenerateContext, GeneratedRenderer, GenerativeDecision, GenerativeFallbackOptions, GenerativeFallbackRenderer, GenerativeFallbackResult, GuardedDispatchInput, IntentClassification, KindRegistry, MIME_A2UI, MIME_MCP_APP, McpUiDecision, NativeDecision, PublishBackOptions, RenderBranch, RenderDecision, RenderTrust, RendererGenerator, RendererPin, RendererPinStore, RendererPublisher, RendererSigner, ResolvedCoordinate, SwapApproval, SwapDecision, SwapRejection, SwapRejectionReason, UiResource, VerifyRendererInput, WidgetIntent, buildConsentRequest, buildRendererEventTemplate, classifyIntent, deterministicGenerator, extractUiResource, guardedRenderDispatch, isTrustDowngrade, publishBackCoordinate, renderDeterministicHtml, renderDispatch, resolveRendererMime, resolveUiCoordinate, resolveUiRenderer, verifyRendererTrust } from './render/index.js';
10
10
 
@@ -1262,6 +1262,21 @@ interface BuildSwapSettlementsParams {
1262
1262
  tokenNetworks?: Record<string, string>;
1263
1263
  /** Pre-loaded `mina-signer` client for `mina:*` re-verification. */
1264
1264
  minaSignerClient?: MinaSignerClientLike;
1265
+ /**
1266
+ * Re-verify the stored claim's signature at settle time (defense in depth
1267
+ * over the store file). Default `true` — the published **v2** sdk
1268
+ * (`@toon-protocol/sdk@^3`, connector#324 finding #1) verifies EVM claims
1269
+ * against the **v2** EIP-712 domain-separated balance-proof digest, the SAME
1270
+ * digest the receive-side used (`ingestReceivedClaims`). Reconstructing that
1271
+ * EIP-712 domain needs `chainId` + `verifyingContract`, which this builder
1272
+ * threads into the sdk signer config from `tokenNetworks` (for EVM claims) —
1273
+ * so a claim verified at receipt re-verifies correctly here.
1274
+ *
1275
+ * Set `false` only to skip the settle-time re-verify entirely (e.g. when the
1276
+ * receive-side verify is treated as the sole authoritative gate); the sdk is
1277
+ * then used only to BUILD the settlement calldata.
1278
+ */
1279
+ verifySignatures?: boolean;
1265
1280
  }
1266
1281
  /** Rebuild the sdk `AccumulatedClaim` shape from a persisted entry. */
1267
1282
  declare function entryToAccumulatedClaim(entry: ReceivedClaimEntry): AccumulatedClaim;
@@ -3017,10 +3032,15 @@ declare function readMinaChannelState(graphqlUrl: string, zkAppAddress: string,
3017
3032
  * `swapSignerAddress` (kind:10032 discovery / operator config), the
3018
3033
  * claim's self-reported signer must match (`SWAP_SIGNER_MISMATCH`,
3019
3034
  * sdk 2.x vocabulary).
3020
- * 5. signature — `verifyAccumulatedClaim` (sdk) against the expected signer:
3021
- * the advertised address when given, else the claim's self-reported one
3022
- * (which then still proves the claim is internally consistent and pins
3023
- * the address the settlement tx will verify against on-chain).
3035
+ * 5. signature — against the expected signer (advertised address when given,
3036
+ * else the claim's self-reported one). EVM claims verify against the
3037
+ * **v2 EIP-712 domain-separated** balance-proof digest
3038
+ * (`verifyEvmClaimSignature`, connector#324 finding #1) the digest binds
3039
+ * `chainId` + `verifyingContract`, so `tokenNetworks[chain]` (the
3040
+ * RollingSwapChannel address) and a numeric chain id are REQUIRED (an EVM
3041
+ * claim without them is rejected `MISSING_CHAIN_CONFIG`, fail-closed).
3042
+ * Solana/Mina keep the sdk `verifyAccumulatedClaim` path (their domain is
3043
+ * folded into the message).
3024
3044
  * 6. signer pinning — a claim may not silently rotate the signer of an
3025
3045
  * already-persisted watermark for the same channel.
3026
3046
  * 7. nonce/cumulative monotonicity vs the locally persisted watermark —
@@ -3030,7 +3050,7 @@ declare function readMinaChannelState(graphqlUrl: string, zkAppAddress: string,
3030
3050
  */
3031
3051
 
3032
3052
  /** Why a claim was rejected at receipt time. Codes are stable API. */
3033
- type ReceivedClaimRejectionCode = 'CHAIN_MISMATCH' | 'RECIPIENT_MISMATCH' | 'SWAP_SIGNER_MISMATCH' | 'SIGNER_MISMATCH' | 'SIGNATURE_INVALID' | 'MINA_VERIFICATION_UNSUPPORTED' | 'UNSUPPORTED_CHAIN' | 'NON_MONOTONIC_NONCE' | 'NON_MONOTONIC_CUMULATIVE' | 'CUMULATIVE_SHORTFALL' | 'MALFORMED_METADATA';
3053
+ type ReceivedClaimRejectionCode = 'CHAIN_MISMATCH' | 'RECIPIENT_MISMATCH' | 'SWAP_SIGNER_MISMATCH' | 'SIGNER_MISMATCH' | 'SIGNATURE_INVALID' | 'MINA_VERIFICATION_UNSUPPORTED' | 'UNSUPPORTED_CHAIN' | 'NON_MONOTONIC_NONCE' | 'NON_MONOTONIC_CUMULATIVE' | 'CUMULATIVE_SHORTFALL' | 'MISSING_CHAIN_CONFIG' | 'MALFORMED_METADATA';
3034
3054
  interface ReceivedClaimRejection {
3035
3055
  claim: AccumulatedClaim;
3036
3056
  code: ReceivedClaimRejectionCode;
@@ -3056,6 +3076,21 @@ interface IngestReceivedClaimsParams {
3056
3076
  * never the claim's own.
3057
3077
  */
3058
3078
  expectedSignerAddress?: string;
3079
+ /**
3080
+ * Per-chain settlement contract addresses (the deployed `RollingSwapChannel`
3081
+ * / `verifyingContract`), keyed by the FULL chain key (e.g. `evm:base:8453`).
3082
+ * Matches the daemon config's `tokenNetworks` map and what
3083
+ * `buildSwapSettlements` already threads on the submit side.
3084
+ *
3085
+ * REQUIRED for EVM claims under the v2 EIP-712 digest (connector#324 finding
3086
+ * #1): the claim signature is domain-separated over `(chainId,
3087
+ * verifyingContract)`, so an EVM claim whose chain key lacks a `tokenNetworks`
3088
+ * entry (or a numeric chain id) is rejected `MISSING_CHAIN_CONFIG` — it cannot
3089
+ * be verified fail-closed without the domain. Supplied by the connector/swap
3090
+ * session context (the RollingSwapChannel the client settles against). Unused
3091
+ * for Solana/Mina claims, which fold their domain into the message itself.
3092
+ */
3093
+ tokenNetworks?: Record<string, string>;
3059
3094
  /** Durable watermark store; verified claims are persisted here. */
3060
3095
  store: ReceivedClaimStore;
3061
3096
  /** Pre-loaded `mina-signer` client — required to verify `mina:*` claims. */
@@ -3089,6 +3124,134 @@ declare function hasSettlementMetadata(claim: AccumulatedClaim): claim is Accumu
3089
3124
  */
3090
3125
  declare function ingestReceivedClaims(params: IngestReceivedClaimsParams): IngestReceivedClaimsResult;
3091
3126
 
3127
+ /**
3128
+ * EVM rolling-swap **v2** balance-proof claim digest (EIP-712 domain-separated).
3129
+ *
3130
+ * Refs connector#324 **finding #1** / connector#325 (canonical spec:
3131
+ * `docs/rolling-swap-v2-digest-spec.md`). This is the client's byte-for-byte
3132
+ * conformance anchor for the v2 migration.
3133
+ *
3134
+ * ## Why this exists in the client
3135
+ *
3136
+ * The v1 digest that shipped in `@toon-protocol/core` `balanceProofHashEvm`
3137
+ * (imported transitively by the sdk's `verifyAccumulatedClaim` /
3138
+ * `buildSettlementTx`, which is what this repo used to lean on) was
3139
+ *
3140
+ * keccak256( channelId(32) || cumulativeAmount(32BE)
3141
+ * || nonce(32BE) || recipient(20) )
3142
+ *
3143
+ * — it bound **neither** `chainId` **nor** the settling contract address, so a
3144
+ * signer-signed claim redeemed on one (chain, deployment) could be replayed
3145
+ * verbatim on another for the same tuple (cross-chain / cross-deployment
3146
+ * replay). v2 folds `chainId` **and** `verifyingContract` into the signed
3147
+ * preimage via a standard **EIP-712** typed-data domain, so a signature is
3148
+ * valid on **exactly one `(chainId, contract)` pair**, and the `version="2"`
3149
+ * string makes the cutover fail-closed (a v1 raw-keccak sig can never validate
3150
+ * as v2 and vice-versa).
3151
+ *
3152
+ * ## Lockstep dependency
3153
+ *
3154
+ * The canonical v2 digest util lives in `@toon-protocol/core`
3155
+ * (`balanceProofHashEvm` → EIP-712) + `@toon-protocol/sdk`
3156
+ * (`verifyAccumulatedClaim` / `buildSettlementTx`). Those packages migrate in
3157
+ * lockstep (connector#324 release order: core/sdk → swap → client). Until a v2
3158
+ * core/sdk is published, this module is the client's own v2 digest so the
3159
+ * receive-side verify (see {@link file://./received-claims.ts}) is already
3160
+ * domain-separated and the golden-vector conformance test
3161
+ * (`evm-claim-digest.test.ts`) pins the exact literals from the spec. When the
3162
+ * v2 core/sdk ships, the recompute here should delegate to it (or be asserted
3163
+ * byte-identical to it) — the golden-vector test stays as the shared fixture.
3164
+ *
3165
+ * The signer leg (`swap` `EvmPaymentChannelSigner.signBalanceProof`) MUST also
3166
+ * move to this same EIP-712 preimage and now REQUIRES `chainId` +
3167
+ * `verifyingContract` inputs it did not take before.
3168
+ */
3169
+
3170
+ /** EIP-712 domain `name` for the RollingSwapChannel (spec §2.1). */
3171
+ declare const ROLLING_SWAP_DOMAIN_NAME: "RollingSwapChannel";
3172
+ /** EIP-712 domain `version` — `"2"` is the domain-separated migration. */
3173
+ declare const ROLLING_SWAP_DOMAIN_VERSION: "2";
3174
+ /**
3175
+ * `keccak256("ClaimBalanceProof(bytes32 channelId,uint256 cumulativeAmount,uint256 nonce,address recipient)")`.
3176
+ * Pinned from the spec; asserted in `evm-claim-digest.test.ts`.
3177
+ */
3178
+ declare const CLAIM_TYPEHASH: "0xa0c8262c1a8615f7674d3af796b14d19672d3634f89c6093502ab35c0afe2d91";
3179
+ /**
3180
+ * `keccak256("CooperativeClose(bytes32 channelId,uint256 cumulativeAmount,uint256 nonce)")`.
3181
+ * Pinned from the spec; asserted in `evm-claim-digest.test.ts`.
3182
+ */
3183
+ declare const COOP_CLOSE_TYPEHASH: "0xa5753389755fea51cd5016d7b02b508ac03f2e822d9a7ee345ec45b36574ff9f";
3184
+ /** The two chain-context inputs v2 adds over v1 (the domain binding). */
3185
+ interface EvmClaimDomainContext {
3186
+ /** Settlement chain id (`block.chainid` on-chain). e.g. `8453` for Base. */
3187
+ chainId: number;
3188
+ /** Deployed `RollingSwapChannel` address (`address(this)` / EIP-712 `verifyingContract`). */
3189
+ verifyingContract: string;
3190
+ }
3191
+ /** Fields of the `ClaimBalanceProof` message (the balance-proof claim leg). */
3192
+ interface EvmClaimMessage {
3193
+ /** Channel id, 0x-prefixed 32-byte hex. */
3194
+ channelId: string;
3195
+ /** Cumulative transferred amount (target micro-units). */
3196
+ cumulativeAmount: bigint;
3197
+ /** Monotonic balance-proof nonce. */
3198
+ nonce: bigint;
3199
+ /** Recipient (the claim payout address), 0x-prefixed 20-byte hex. */
3200
+ recipient: string;
3201
+ }
3202
+ /** Fields of the `CooperativeClose` message (recipient close-ack leg). */
3203
+ interface EvmCooperativeCloseMessage {
3204
+ channelId: string;
3205
+ cumulativeAmount: bigint;
3206
+ nonce: bigint;
3207
+ }
3208
+ /**
3209
+ * The v2 EIP-712 claim digest — `keccak256(0x1901 || domainSeparator ||
3210
+ * hashStruct(ClaimBalanceProof))` — that the on-chain `updateBalance` verifier
3211
+ * (and the swap signer) produce. This is what a received claim's signature must
3212
+ * recover against.
3213
+ */
3214
+ declare function evmClaimDigest(ctx: EvmClaimDomainContext, message: EvmClaimMessage): Hex;
3215
+ /**
3216
+ * The v2 EIP-712 cooperative-close digest — same domain as the claim, distinct
3217
+ * type hash — that a recipient's close-ack signature must recover against.
3218
+ */
3219
+ declare function evmCooperativeCloseDigest(ctx: EvmClaimDomainContext, message: EvmCooperativeCloseMessage): Hex;
3220
+ /** A 65-byte `r || s || v` signature, as 0x-hex or raw bytes. */
3221
+ type ClaimSignature = Hex | Uint8Array;
3222
+ /**
3223
+ * Recover the EVM address that signed a v2 claim digest. Uses the same
3224
+ * `@noble/curves` secp256k1 recovery + keccak address derivation as the sdk's
3225
+ * on-chain-matching `recoverEvmSignerAddress`, over the **v2** EIP-712 digest.
3226
+ * Enforces the same 65-byte `r||s||v`, `v ∈ {27,28}` envelope the on-chain OZ
3227
+ * `ECDSA.recover` accepts (fail-closed on a malformed signature).
3228
+ *
3229
+ * @returns the recovered address (lowercase 0x-hex).
3230
+ * @throws if the signature is not 65 bytes or `v` is not 27/28.
3231
+ */
3232
+ declare function recoverEvmClaimSigner(ctx: EvmClaimDomainContext, message: EvmClaimMessage, signature: ClaimSignature): Address;
3233
+ /** Result of {@link verifyEvmClaimSignature}. Mirrors the sdk verify shape. */
3234
+ type EvmClaimVerifyResult = {
3235
+ valid: true;
3236
+ recovered: Address;
3237
+ } | {
3238
+ valid: false;
3239
+ reason: string;
3240
+ };
3241
+ /**
3242
+ * Verify a received EVM balance-proof claim: recover the v2-digest signer and
3243
+ * check it equals `expectedSigner` (case-insensitive, per EVM address rules).
3244
+ *
3245
+ * Result-shaped — never throws; a malformed signature or a mismatch is a
3246
+ * `{ valid: false, reason }` so the receive ladder can bucket it fail-closed.
3247
+ */
3248
+ declare function verifyEvmClaimSignature(params: {
3249
+ ctx: EvmClaimDomainContext;
3250
+ message: EvmClaimMessage;
3251
+ signature: ClaimSignature;
3252
+ expectedSigner: string;
3253
+ }): EvmClaimVerifyResult;
3254
+
3092
3255
  /**
3093
3256
  * Per-packet preimage retention (toon-client#360, rolling-swap epic
3094
3257
  * toon-meta#145 — spec `docs/rolling-swap.md` §3 R1 / §3.2 leg-B reveal).
@@ -4175,4 +4338,4 @@ declare function loadKeystore(path: string, password: string): string;
4175
4338
  */
4176
4339
  declare function writeKeystoreFile(path: string, keystore: EncryptedKeystore): void;
4177
4340
 
4178
- export { type BackupPayload, type BalanceProofParams, BtpRuntimeClient, type BtpRuntimeClientConfig, type BuildSwapSettlementsParams, CONDITION_LENGTH, type ChainMetadata, type ChainSigner, ChannelFundingError, ChannelManager, type ClaimMessage, type ClaimResolver, ConnectorError, type DiscoveredIlpPeer, type EVMClaimMessage, type EncryptedKeystore, EvmSigner, type ExecutionConditionPair, FULFILLMENT_MISMATCH_CODE, FULFILLMENT_MISMATCH_MESSAGE, type FaucetChain, type FundWalletOptions, type FundWalletResult, type H402FetchOptions, Http402Client, type Http402ClientConfig, HttpConnectorAdmin, type HttpConnectorAdminConfig, HttpIlpClient, type HttpIlpClientConfig, type HttpIlpClientFactory, HttpRuntimeClient, type HttpRuntimeClientConfig, ILP_CLAIM_HEADER, ILP_CLAIM_WRAPPED_HEADER, ILP_PEER_ID_HEADER, type IlpSendParams, type IlpSendResultWithFulfillment, type IlpTransportChoice, InMemoryPreimageRetentionStore, InMemoryReceivedClaimStore, type IngestAndRevealParams, type IngestAndRevealResult, type IngestReceivedClaimsParams, type IngestReceivedClaimsResult, JsonFileReceivedClaimStore, KeyManager, type KeyManagerConfig, MINA_CHANNEL_STATE, type MinaChannelStateReader, type MinaClaimMessage, type MinaClaimSubmitArgs, type MinaClaimSubmitter, type MinaCoSignInputs, type MinaCoSignedClaim, type MinaDepositReader, type MinaOnChainChannelState, type MinaSettlementContext, MinaSettlementError, type MinaSettlementErrorCode, type MinaSettlementResult, type MinaSignaturePair, MinaSigner, type MinaSignerOptions, NetworkError, OnChainChannelClient, type OnChainChannelClientConfig, type ParsedFulfillHttp, type ParsedX402Challenge, type PasskeyInfo, type PreimageRetentionStore, type PublishEventResult, type ReceivedClaimEntry, type ReceivedClaimRejection, type ReceivedClaimRejectionCode, type ReceivedClaimStore, type RequestBlobStorageParams, type RequestBlobStorageResult, type RetainedPreimage, type RetryOptions, type RevealDecision, type RevealFn, type RevealResult, type RevealedClaim, type RolledBackClaim, type SelectIlpTransportOptions, type SignedBalanceProof, type SolanaChannelClientOptions, type SolanaClaimMessage, SolanaSigner, type SubmitEvmSettlementParams, type SubmitEvmSettlementResult, type SwapSettlementBuild, type ToonChannelAccept, ToonClient, type ToonClientConfig, ToonClientError, type ToonIdentity, type ToonSigners, type ToonStartResult, ValidationError, type VaultData, type VerifiedReceivedClaim, type WalletBalance, type WalletBalanceSources, type WalletChainBalances, type WalletTokenAmount, applyDefaults, applyNetworkPresets, assertValidCondition, buildBackupEvent, buildBackupFilter, buildMinaCoSignedClaim, buildSettlementInfo, buildStoreWriteEnvelope, buildSwapSettlements, createO1jsMinaClaimSubmitter, decodeEvmSettlementTx, decryptMnemonic, defaultFaucetTimeout, deriveFromNsec, deriveFullIdentity, deriveNostrKeyFromMnemonic, encryptMnemonic, entryToAccumulatedClaim, extractArweaveTxId, fulfillmentMatchesCondition, fundWallet, generateKeystore, generateMnemonic, generateRandomIdentity, getNetworkStatus, hasSettlementMetadata, httpEndpointToBtpUrl, importKeystore, ingestAndReveal, ingestReceivedClaims, isInsufficientGasError, isPrfSupported, isZeroCondition, loadKeystore, mintExecutionCondition, parseBackupPayload, parseEvmChainId, parseFulfillHttp, parseFulfillHttpBytes, parseHttpResponse, parseX402Body, parseX402Challenge, proxyIlpEndpoint, readDiscoveredIlpPeer, readEvmNativeBalance, readEvmTokenBalance, readMinaBalance, readMinaChannelState, readMinaDepositTotal, readSolanaNativeBalance, readSolanaTokenBalance, readWalletBalances, requestBlobStorage, selectIlpTransport, serializeHttpRequest, submitEvmSettlement, submitMinaSettlement, validateConfig, validateMnemonic, withRetry, writeKeystoreFile };
4341
+ export { type BackupPayload, type BalanceProofParams, BtpRuntimeClient, type BtpRuntimeClientConfig, type BuildSwapSettlementsParams, CLAIM_TYPEHASH, CONDITION_LENGTH, COOP_CLOSE_TYPEHASH, type ChainMetadata, type ChainSigner, ChannelFundingError, ChannelManager, type ClaimMessage, type ClaimResolver, type ClaimSignature, ConnectorError, type DiscoveredIlpPeer, type EVMClaimMessage, type EncryptedKeystore, type EvmClaimDomainContext, type EvmClaimMessage, type EvmClaimVerifyResult, type EvmCooperativeCloseMessage, EvmSigner, type ExecutionConditionPair, FULFILLMENT_MISMATCH_CODE, FULFILLMENT_MISMATCH_MESSAGE, type FaucetChain, type FundWalletOptions, type FundWalletResult, type H402FetchOptions, Http402Client, type Http402ClientConfig, HttpConnectorAdmin, type HttpConnectorAdminConfig, HttpIlpClient, type HttpIlpClientConfig, type HttpIlpClientFactory, HttpRuntimeClient, type HttpRuntimeClientConfig, ILP_CLAIM_HEADER, ILP_CLAIM_WRAPPED_HEADER, ILP_PEER_ID_HEADER, type IlpSendParams, type IlpSendResultWithFulfillment, type IlpTransportChoice, InMemoryPreimageRetentionStore, InMemoryReceivedClaimStore, type IngestAndRevealParams, type IngestAndRevealResult, type IngestReceivedClaimsParams, type IngestReceivedClaimsResult, JsonFileReceivedClaimStore, KeyManager, type KeyManagerConfig, MINA_CHANNEL_STATE, type MinaChannelStateReader, type MinaClaimMessage, type MinaClaimSubmitArgs, type MinaClaimSubmitter, type MinaCoSignInputs, type MinaCoSignedClaim, type MinaDepositReader, type MinaOnChainChannelState, type MinaSettlementContext, MinaSettlementError, type MinaSettlementErrorCode, type MinaSettlementResult, type MinaSignaturePair, MinaSigner, type MinaSignerOptions, NetworkError, OnChainChannelClient, type OnChainChannelClientConfig, type ParsedFulfillHttp, type ParsedX402Challenge, type PasskeyInfo, type PreimageRetentionStore, type PublishEventResult, ROLLING_SWAP_DOMAIN_NAME, ROLLING_SWAP_DOMAIN_VERSION, type ReceivedClaimEntry, type ReceivedClaimRejection, type ReceivedClaimRejectionCode, type ReceivedClaimStore, type RequestBlobStorageParams, type RequestBlobStorageResult, type RetainedPreimage, type RetryOptions, type RevealDecision, type RevealFn, type RevealResult, type RevealedClaim, type RolledBackClaim, type SelectIlpTransportOptions, type SignedBalanceProof, type SolanaChannelClientOptions, type SolanaClaimMessage, SolanaSigner, type SubmitEvmSettlementParams, type SubmitEvmSettlementResult, type SwapSettlementBuild, type ToonChannelAccept, ToonClient, type ToonClientConfig, ToonClientError, type ToonIdentity, type ToonSigners, type ToonStartResult, ValidationError, type VaultData, type VerifiedReceivedClaim, type WalletBalance, type WalletBalanceSources, type WalletChainBalances, type WalletTokenAmount, applyDefaults, applyNetworkPresets, assertValidCondition, buildBackupEvent, buildBackupFilter, buildMinaCoSignedClaim, buildSettlementInfo, buildStoreWriteEnvelope, buildSwapSettlements, createO1jsMinaClaimSubmitter, decodeEvmSettlementTx, decryptMnemonic, defaultFaucetTimeout, deriveFromNsec, deriveFullIdentity, deriveNostrKeyFromMnemonic, encryptMnemonic, entryToAccumulatedClaim, evmClaimDigest, evmCooperativeCloseDigest, extractArweaveTxId, fulfillmentMatchesCondition, fundWallet, generateKeystore, generateMnemonic, generateRandomIdentity, getNetworkStatus, hasSettlementMetadata, httpEndpointToBtpUrl, importKeystore, ingestAndReveal, ingestReceivedClaims, isInsufficientGasError, isPrfSupported, isZeroCondition, loadKeystore, mintExecutionCondition, parseBackupPayload, parseEvmChainId, parseFulfillHttp, parseFulfillHttpBytes, parseHttpResponse, parseX402Body, parseX402Challenge, proxyIlpEndpoint, readDiscoveredIlpPeer, readEvmNativeBalance, readEvmTokenBalance, readMinaBalance, readMinaChannelState, readMinaDepositTotal, readSolanaNativeBalance, readSolanaTokenBalance, readWalletBalances, recoverEvmClaimSigner, requestBlobStorage, selectIlpTransport, serializeHttpRequest, submitEvmSettlement, submitMinaSettlement, validateConfig, validateMnemonic, verifyEvmClaimSignature, withRetry, writeKeystoreFile };
package/dist/index.js CHANGED
@@ -1871,6 +1871,17 @@ function deriveChannelPDA(participantA, participantB, tokenMint, programId) {
1871
1871
  const { pda, bump } = findProgramAddress(seeds, program);
1872
1872
  return { pda: base58Encode(pda), bump };
1873
1873
  }
1874
+ function deriveAssociatedTokenAccount(owner, tokenMint) {
1875
+ const TOKEN_PROGRAM_ID2 = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
1876
+ const ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
1877
+ const seeds = [
1878
+ padTo32(base58Decode(owner)),
1879
+ padTo32(base58Decode(TOKEN_PROGRAM_ID2)),
1880
+ padTo32(base58Decode(tokenMint))
1881
+ ];
1882
+ const { pda } = findProgramAddress(seeds, padTo32(base58Decode(ASSOCIATED_TOKEN_PROGRAM_ID)));
1883
+ return base58Encode(pda);
1884
+ }
1874
1885
  function deriveVaultPDA(channelPDA, programId) {
1875
1886
  const channel = padTo32(base58Decode(channelPDA));
1876
1887
  const program = padTo32(base58Decode(programId));
@@ -2586,14 +2597,17 @@ var OnChainChannelClient = class {
2586
2597
  throw new Error("Solana channel config not set \u2014 cannot deposit.");
2587
2598
  }
2588
2599
  const cfg = this.solanaConfig;
2589
- const payerTokenAccount = cfg.deposit?.payerTokenAccount;
2590
- if (!payerTokenAccount) {
2591
- throw new Error(
2592
- "Solana deposit requires solanaConfig.deposit.payerTokenAccount (the funded SPL token account)."
2593
- );
2594
- }
2595
2600
  const payerSeed = cfg.keypair.slice(0, 32);
2596
2601
  const payerPubkey = base58Encode2(new Uint8Array(ed255192.getPublicKey(payerSeed)));
2602
+ let payerTokenAccount = cfg.deposit?.payerTokenAccount;
2603
+ if (!payerTokenAccount) {
2604
+ if (!cfg.tokenMint) {
2605
+ throw new Error(
2606
+ "Solana deposit requires solanaConfig.deposit.payerTokenAccount or solanaConfig.tokenMint to derive the payer ATA."
2607
+ );
2608
+ }
2609
+ payerTokenAccount = deriveAssociatedTokenAccount(payerPubkey, cfg.tokenMint);
2610
+ }
2597
2611
  const { depositTxSignature } = await depositSolanaChannel({
2598
2612
  rpcUrl: cfg.rpcUrl,
2599
2613
  programId: cfg.programId,
@@ -2799,7 +2813,9 @@ var OnChainChannelClient = class {
2799
2813
  );
2800
2814
  const deposit = cfg.deposit ? {
2801
2815
  amount: BigInt(cfg.deposit.amount),
2802
- payerTokenAccount: cfg.deposit.payerTokenAccount
2816
+ // Derive the payer ATA (owner + channel mint) when not supplied — it is
2817
+ // deterministic, so the caller need not thread payerTokenAccount through.
2818
+ payerTokenAccount: cfg.deposit.payerTokenAccount || deriveAssociatedTokenAccount(payerPubkey, tokenMint)
2803
2819
  } : void 0;
2804
2820
  const { channelPDA } = await openSolanaChannel({
2805
2821
  rpcUrl: cfg.rpcUrl,
@@ -4677,6 +4693,7 @@ function buildSwapSettlements(params) {
4677
4693
  claims: [entryToAccumulatedClaim(entry)],
4678
4694
  signers: { [entry.chain]: signer },
4679
4695
  recipients: { [entry.chain]: entry.recipient },
4696
+ ...params.verifySignatures !== void 0 ? { verifySignatures: params.verifySignatures } : {},
4680
4697
  ...params.minaSignerClient ? { minaSignerClient: params.minaSignerClient } : {}
4681
4698
  });
4682
4699
  const firstRejected = result.rejected[0];
@@ -4724,7 +4741,9 @@ function decodeEvmSettlementTx(bundle) {
4724
4741
  const chainIdHex = fields[6];
4725
4742
  const chainId = Number.parseInt(chainIdHex === "0x" ? "0x0" : chainIdHex, 16);
4726
4743
  if (typeof to !== "string" || to.length !== 42) {
4727
- throw new Error(`settlement tx "to" is not a 20-byte address: ${String(to)}`);
4744
+ throw new Error(
4745
+ `settlement tx "to" is not a 20-byte address: ${String(to)}`
4746
+ );
4728
4747
  }
4729
4748
  return { to, data: data === "0x" ? "0x" : data, chainId };
4730
4749
  }
@@ -4741,7 +4760,10 @@ async function submitEvmSettlement(bundle, params) {
4741
4760
  nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
4742
4761
  rpcUrls: { default: { http: [params.rpcUrl] } }
4743
4762
  });
4744
- const publicClient = createPublicClient3({ chain, transport: http3(params.rpcUrl) });
4763
+ const publicClient = createPublicClient3({
4764
+ chain,
4765
+ transport: http3(params.rpcUrl)
4766
+ });
4745
4767
  const [nonce, gasPrice, gas] = await Promise.all([
4746
4768
  publicClient.getTransactionCount({
4747
4769
  address: params.account.address,
@@ -6463,6 +6485,116 @@ function fromJson(entry) {
6463
6485
  import {
6464
6486
  verifyAccumulatedClaim
6465
6487
  } from "@toon-protocol/sdk";
6488
+
6489
+ // src/swap/evm-claim-digest.ts
6490
+ import {
6491
+ hashTypedData
6492
+ } from "viem";
6493
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
6494
+ import { keccak_256 } from "@noble/hashes/sha3.js";
6495
+ import { hexToBytes, bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
6496
+ var ROLLING_SWAP_DOMAIN_NAME = "RollingSwapChannel";
6497
+ var ROLLING_SWAP_DOMAIN_VERSION = "2";
6498
+ var CLAIM_TYPEHASH = "0xa0c8262c1a8615f7674d3af796b14d19672d3634f89c6093502ab35c0afe2d91";
6499
+ var COOP_CLOSE_TYPEHASH = "0xa5753389755fea51cd5016d7b02b508ac03f2e822d9a7ee345ec45b36574ff9f";
6500
+ var CLAIM_TYPES = {
6501
+ ClaimBalanceProof: [
6502
+ { name: "channelId", type: "bytes32" },
6503
+ { name: "cumulativeAmount", type: "uint256" },
6504
+ { name: "nonce", type: "uint256" },
6505
+ { name: "recipient", type: "address" }
6506
+ ]
6507
+ };
6508
+ var COOP_CLOSE_TYPES = {
6509
+ CooperativeClose: [
6510
+ { name: "channelId", type: "bytes32" },
6511
+ { name: "cumulativeAmount", type: "uint256" },
6512
+ { name: "nonce", type: "uint256" }
6513
+ ]
6514
+ };
6515
+ function normalizeAddress(addr) {
6516
+ return addr.toLowerCase();
6517
+ }
6518
+ function domainOf(ctx) {
6519
+ return {
6520
+ name: ROLLING_SWAP_DOMAIN_NAME,
6521
+ version: ROLLING_SWAP_DOMAIN_VERSION,
6522
+ chainId: ctx.chainId,
6523
+ verifyingContract: normalizeAddress(ctx.verifyingContract)
6524
+ };
6525
+ }
6526
+ function evmClaimDigest(ctx, message) {
6527
+ return hashTypedData({
6528
+ domain: domainOf(ctx),
6529
+ types: CLAIM_TYPES,
6530
+ primaryType: "ClaimBalanceProof",
6531
+ message: {
6532
+ channelId: message.channelId,
6533
+ cumulativeAmount: message.cumulativeAmount,
6534
+ nonce: message.nonce,
6535
+ recipient: normalizeAddress(message.recipient)
6536
+ }
6537
+ });
6538
+ }
6539
+ function evmCooperativeCloseDigest(ctx, message) {
6540
+ return hashTypedData({
6541
+ domain: domainOf(ctx),
6542
+ types: COOP_CLOSE_TYPES,
6543
+ primaryType: "CooperativeClose",
6544
+ message: {
6545
+ channelId: message.channelId,
6546
+ cumulativeAmount: message.cumulativeAmount,
6547
+ nonce: message.nonce
6548
+ }
6549
+ });
6550
+ }
6551
+ var SIG_BYTES = 65;
6552
+ function signatureToBytes(sig) {
6553
+ if (typeof sig !== "string") return sig;
6554
+ const hex = sig.startsWith("0x") ? sig.slice(2) : sig;
6555
+ return hexToBytes(hex);
6556
+ }
6557
+ function recoverEvmClaimSigner(ctx, message, signature) {
6558
+ const bytes = signatureToBytes(signature);
6559
+ if (bytes.length !== SIG_BYTES) {
6560
+ throw new Error(
6561
+ `EVM signature must be 65 bytes (r||s||v), got ${bytes.length}`
6562
+ );
6563
+ }
6564
+ const v = bytes[64];
6565
+ if (v !== 27 && v !== 28) {
6566
+ throw new Error(`EVM signature v must be 27 or 28, got ${v}`);
6567
+ }
6568
+ const digest = evmClaimDigest(ctx, message).slice(2);
6569
+ const recovered = secp256k1.Signature.fromBytes(bytes.slice(0, 64), "compact").addRecoveryBit(v - 27).recoverPublicKey(hexToBytes(digest));
6570
+ const uncompressed = recovered.toBytes(false);
6571
+ const addrHash = keccak_256(uncompressed.slice(1));
6572
+ return `0x${bytesToHex2(addrHash.slice(-20))}`;
6573
+ }
6574
+ function verifyEvmClaimSignature(params) {
6575
+ let recovered;
6576
+ try {
6577
+ recovered = recoverEvmClaimSigner(
6578
+ params.ctx,
6579
+ params.message,
6580
+ params.signature
6581
+ );
6582
+ } catch (err) {
6583
+ return {
6584
+ valid: false,
6585
+ reason: `SIGNATURE_INVALID: ${err instanceof Error ? err.message : String(err)}`
6586
+ };
6587
+ }
6588
+ if (recovered.toLowerCase() !== params.expectedSigner.toLowerCase()) {
6589
+ return {
6590
+ valid: false,
6591
+ reason: `SIGNER_MISMATCH: recovered ${recovered}, expected ${params.expectedSigner}`
6592
+ };
6593
+ }
6594
+ return { valid: true, recovered };
6595
+ }
6596
+
6597
+ // src/swap/received-claims.ts
6466
6598
  function hasSettlementMetadata(claim) {
6467
6599
  return claim.channelId !== void 0 && claim.nonce !== void 0 && claim.cumulativeAmount !== void 0 && claim.recipient !== void 0 && claim.swapSignerAddress !== void 0;
6468
6600
  }
@@ -6517,11 +6649,49 @@ function ingestReceivedClaims(params) {
6517
6649
  continue;
6518
6650
  }
6519
6651
  const expectedSigner = params.expectedSignerAddress ?? claim.swapSignerAddress;
6520
- const sig = verifyAccumulatedClaim(
6521
- claim,
6522
- { address: expectedSigner },
6523
- params.minaSignerClient
6524
- );
6652
+ let sig;
6653
+ if (chain.startsWith("evm")) {
6654
+ const chainId = parseEvmChainId2(chain);
6655
+ const verifyingContract = params.tokenNetworks?.[chain];
6656
+ if (chainId === void 0 || !verifyingContract) {
6657
+ reject(
6658
+ claim,
6659
+ "MISSING_CHAIN_CONFIG",
6660
+ `EVM v2 balance-proof verification for ${chain} needs a numeric chain id in the chain key AND tokenNetworks["${chain}"] (the RollingSwapChannel / verifyingContract) to reconstruct the EIP-712 domain; supply it from the connector/swap session context.`
6661
+ );
6662
+ continue;
6663
+ }
6664
+ let msgNonce;
6665
+ let msgCumulative;
6666
+ try {
6667
+ msgNonce = BigInt(claim.nonce);
6668
+ msgCumulative = BigInt(claim.cumulativeAmount);
6669
+ } catch {
6670
+ reject(
6671
+ claim,
6672
+ "MALFORMED_METADATA",
6673
+ `nonce "${claim.nonce}" / cumulativeAmount "${claim.cumulativeAmount}" are not decimal integers`
6674
+ );
6675
+ continue;
6676
+ }
6677
+ sig = verifyEvmClaimSignature({
6678
+ ctx: { chainId, verifyingContract },
6679
+ message: {
6680
+ channelId: claim.channelId,
6681
+ cumulativeAmount: msgCumulative,
6682
+ nonce: msgNonce,
6683
+ recipient: claim.recipient
6684
+ },
6685
+ signature: claim.claimBytes,
6686
+ expectedSigner
6687
+ });
6688
+ } else {
6689
+ sig = verifyAccumulatedClaim(
6690
+ claim,
6691
+ { address: expectedSigner },
6692
+ params.minaSignerClient
6693
+ );
6694
+ }
6525
6695
  if (!sig.valid) {
6526
6696
  reject(claim, signatureRejectionCode(sig.reason), sig.reason);
6527
6697
  continue;
@@ -6889,14 +7059,14 @@ function fromBase642(b64) {
6889
7059
  }
6890
7060
  return bytes;
6891
7061
  }
6892
- function hexToBytes(hex) {
7062
+ function hexToBytes2(hex) {
6893
7063
  const bytes = new Uint8Array(hex.length / 2);
6894
7064
  for (let i = 0; i < hex.length; i += 2) {
6895
7065
  bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
6896
7066
  }
6897
7067
  return bytes;
6898
7068
  }
6899
- function bytesToHex2(bytes) {
7069
+ function bytesToHex3(bytes) {
6900
7070
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
6901
7071
  }
6902
7072
 
@@ -7228,7 +7398,7 @@ var KeyManager = class {
7228
7398
  const mnemonic = generateMnemonic();
7229
7399
  const identity = await deriveFullIdentity(mnemonic);
7230
7400
  const prfSalt = crypto.getRandomValues(new Uint8Array(32));
7231
- const userIdBytes = hexToBytes(identity.nostr.pubkey);
7401
+ const userIdBytes = hexToBytes2(identity.nostr.pubkey);
7232
7402
  const registration = await registerPasskey({
7233
7403
  rpId: this.config.rpId,
7234
7404
  rpName: this.config.rpName,
@@ -7270,7 +7440,7 @@ var KeyManager = class {
7270
7440
  "Passkey did not return a userHandle. Cannot determine Nostr pubkey for recovery."
7271
7441
  );
7272
7442
  }
7273
- const pubkey = bytesToHex2(discovery.userHandle);
7443
+ const pubkey = bytesToHex3(discovery.userHandle);
7274
7444
  const vault = await fetchBackupFromRelays(pubkey, this.config.relayUrls);
7275
7445
  if (!vault) {
7276
7446
  throw new Error(
@@ -7308,7 +7478,7 @@ var KeyManager = class {
7308
7478
  }
7309
7479
  const identity = await deriveFullIdentity(mnemonic);
7310
7480
  const prfSalt = crypto.getRandomValues(new Uint8Array(32));
7311
- const userIdBytes = hexToBytes(identity.nostr.pubkey);
7481
+ const userIdBytes = hexToBytes2(identity.nostr.pubkey);
7312
7482
  const registration = await registerPasskey({
7313
7483
  rpId: this.config.rpId,
7314
7484
  rpName: this.config.rpName,
@@ -7339,7 +7509,7 @@ var KeyManager = class {
7339
7509
  const identity = deriveFromNsec(secretKey);
7340
7510
  if (isPrfSupported()) {
7341
7511
  const prfSalt = crypto.getRandomValues(new Uint8Array(32));
7342
- const userIdBytes = hexToBytes(identity.nostr.pubkey);
7512
+ const userIdBytes = hexToBytes2(identity.nostr.pubkey);
7343
7513
  try {
7344
7514
  const registration = await registerPasskey({
7345
7515
  rpId: this.config.rpId,
@@ -7350,7 +7520,7 @@ var KeyManager = class {
7350
7520
  });
7351
7521
  const kek = await deriveKek(registration.prfOutput);
7352
7522
  const credIdHash = await hashCredentialId(registration.credentialId);
7353
- const hexKey = bytesToHex2(secretKey);
7523
+ const hexKey = bytesToHex3(secretKey);
7354
7524
  this.vault = await createVault(hexKey, kek, credIdHash, prfSalt);
7355
7525
  this.activeCredentialIdHash = credIdHash;
7356
7526
  await this.saveToLocalStorage();
@@ -7369,7 +7539,7 @@ var KeyManager = class {
7369
7539
  throw new Error("No active identity \u2014 call create() or recover() first");
7370
7540
  }
7371
7541
  const prfSalt = crypto.getRandomValues(new Uint8Array(32));
7372
- const userIdBytes = hexToBytes(this.identity.nostr.pubkey);
7542
+ const userIdBytes = hexToBytes2(this.identity.nostr.pubkey);
7373
7543
  const registration = await registerPasskey({
7374
7544
  rpId: this.config.rpId,
7375
7545
  rpName: this.config.rpName,
@@ -7799,7 +7969,9 @@ function writeKeystoreFile(path, keystore) {
7799
7969
  }
7800
7970
  export {
7801
7971
  BtpRuntimeClient,
7972
+ CLAIM_TYPEHASH,
7802
7973
  CONDITION_LENGTH,
7974
+ COOP_CLOSE_TYPEHASH,
7803
7975
  ChannelFundingError,
7804
7976
  ChannelManager,
7805
7977
  ConnectorError,
@@ -7826,6 +7998,8 @@ export {
7826
7998
  MinaSigner,
7827
7999
  NetworkError,
7828
8000
  OnChainChannelClient,
8001
+ ROLLING_SWAP_DOMAIN_NAME,
8002
+ ROLLING_SWAP_DOMAIN_VERSION,
7829
8003
  RendererPinStore,
7830
8004
  SolanaSigner,
7831
8005
  ToonClient,
@@ -7856,6 +8030,8 @@ export {
7856
8030
  deterministicGenerator,
7857
8031
  encryptMnemonic2 as encryptMnemonic,
7858
8032
  entryToAccumulatedClaim,
8033
+ evmClaimDigest,
8034
+ evmCooperativeCloseDigest,
7859
8035
  extractArweaveTxId,
7860
8036
  extractUiResource,
7861
8037
  fulfillmentMatchesCondition,
@@ -7896,6 +8072,7 @@ export {
7896
8072
  readSolanaNativeBalance,
7897
8073
  readSolanaTokenBalance,
7898
8074
  readWalletBalances,
8075
+ recoverEvmClaimSigner,
7899
8076
  renderDeterministicHtml,
7900
8077
  renderDispatch,
7901
8078
  requestBlobStorage,
@@ -7909,6 +8086,7 @@ export {
7909
8086
  submitMinaSettlement,
7910
8087
  validateConfig,
7911
8088
  validateMnemonic,
8089
+ verifyEvmClaimSignature,
7912
8090
  verifyRendererTrust,
7913
8091
  withRetry,
7914
8092
  writeKeystoreFile