@toon-protocol/client 0.9.0 → 0.9.2

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
@@ -1,8 +1,75 @@
1
1
  import * as _toon_protocol_core from '@toon-protocol/core';
2
- import { IlpPeerInfo, IlpSendResult, IlpClient, ConnectorAdminClient, ConnectorChannelClient, OpenChannelParams, OpenChannelResult, ChannelState } from '@toon-protocol/core';
2
+ import { IlpPeerInfo, NetworkFamilyStatus, IlpSendResult, IlpClient, ConnectorAdminClient, ConnectorChannelClient, OpenChannelParams, OpenChannelResult, ChannelState } from '@toon-protocol/core';
3
3
  import { NostrEvent } from 'nostr-tools/pure';
4
4
  import { PrivateKeyAccount } from 'viem/accounts';
5
5
 
6
+ /**
7
+ * Solana payment-channel parameters supplied via `ToonClientConfig.solanaChannel`.
8
+ *
9
+ * Mirrors the `SolanaChannelConfig` consumed by `OnChainChannelClient`, minus
10
+ * the Ed25519 keypair (derived from the client's `mnemonic`, see
11
+ * `ToonClientConfig.solanaChannel`).
12
+ */
13
+ interface SolanaChannelClientOptions {
14
+ /** Solana JSON-RPC URL used to open the channel + read PDA state. */
15
+ rpcUrl: string;
16
+ /** Deployed payment-channel program id (base58). */
17
+ programId: string;
18
+ /**
19
+ * Default SPL token mint (base58) for PDA derivation. The per-channel
20
+ * negotiated token (the peer's preferred token) takes precedence when present.
21
+ */
22
+ tokenMint?: string;
23
+ /** Challenge-period duration (seconds) for `initialize_channel`. */
24
+ challengeDuration?: number;
25
+ /**
26
+ * Optional on-chain deposit when opening the channel: `amount` in base units
27
+ * (string) drawn from `payerTokenAccount` (the client's funded SPL token
28
+ * account / ATA, base58). When omitted, the channel is opened without a
29
+ * deposit (connector accepts on `opened` status + participant membership).
30
+ */
31
+ deposit?: {
32
+ amount: string;
33
+ payerTokenAccount: string;
34
+ };
35
+ }
36
+ /**
37
+ * Mina payment-channel parameters supplied via `ToonClientConfig.minaChannel`.
38
+ *
39
+ * Mirrors the `MinaChannelConfig` consumed by `OnChainChannelClient`, minus the
40
+ * Mina private key (derived from the client's `mnemonic`, the same key that
41
+ * produces the registered Mina signer — so the channel-open key and the
42
+ * claim-signing key are guaranteed identical).
43
+ *
44
+ * ────────────────────────────────────────────────────────────────────────────
45
+ * PHASE-2 STAGE-3: the client's Mina claim now matches connector 3.9.0's
46
+ * `MinaClaimMessage` contract — `{ zkAppAddress, tokenId, balanceCommitment,
47
+ * proof (base64), salt, nonce }`, with the proof a Pallas Schnorr signature over
48
+ * the connector's `Poseidon([balA,balB,salt]) / Poseidon(zkApp.x)` commitment
49
+ * (verified field-by-field against the connector's o1js verify). A
50
+ * Mina-denominated paid publish is ACCEPTED at `validateClaimMessage` and the
51
+ * apex FULFILLs to town. On-chain SETTLE remains gated for non-EVM dynamic
52
+ * hidden-service peers by connector#88 (`No chain configured for peer`).
53
+ * `zkAppAddress` must be a REAL deployed payment-channel zkApp the apex's Mina
54
+ * provider can resolve on-chain (the e2e harness deploys it deterministically).
55
+ * ────────────────────────────────────────────────────────────────────────────
56
+ */
57
+ interface MinaChannelClientOptions {
58
+ /** Mina GraphQL URL used to open the channel + read zkApp state. */
59
+ graphqlUrl: string;
60
+ /** Deployed payment-channel zkApp address (B62 base58). */
61
+ zkAppAddress: string;
62
+ /** Channel settlement timeout in slots for `initializeChannel` (default 86400). */
63
+ challengeDuration?: number;
64
+ /** Mina token id field (decimal string) for `initializeChannel` (default '1'). */
65
+ tokenId?: string;
66
+ /** Optional on-chain deposit (base units, string) after the channel opens. */
67
+ deposit?: {
68
+ amount: string;
69
+ };
70
+ /** Mina network id for the account/Schnorr prefix (default 'devnet'). */
71
+ networkId?: 'devnet' | 'mainnet';
72
+ }
6
73
  /**
7
74
  * Configuration for ToonClient.
8
75
  *
@@ -43,9 +110,41 @@ interface ToonClientConfig {
43
110
  * Reserved for future implementation.
44
111
  */
45
112
  connector?: unknown;
113
+ /**
114
+ * BIP-39 mnemonic phrase to derive a full multi-chain identity from.
115
+ *
116
+ * When provided, the client derives the Nostr (NIP-06) + EVM keys
117
+ * synchronously at construction and the Solana (Ed25519) + Mina (Pallas) keys
118
+ * lazily in `start()`, registering per-chain signers so balance-proof claims
119
+ * can be settled on any of those chains. This is the recommended way to use
120
+ * non-EVM settlement (the raw `secretKey` path is secp256k1-only).
121
+ *
122
+ * Cannot be combined with `secretKey` (ambiguous Nostr identity). May be
123
+ * combined with `evmPrivateKey` to use a separate EVM key (e.g. hardware
124
+ * wallet) while still deriving Solana/Mina from the phrase.
125
+ *
126
+ * SECURITY: JavaScript strings are immutable and cannot be zeroed from
127
+ * memory — the phrase may persist in the heap until GC. Prefer
128
+ * passing a pre-derived `secretKey`/identity in high-security contexts.
129
+ */
130
+ mnemonic?: string;
131
+ /**
132
+ * BIP-44 account index used when deriving the multi-chain identity from
133
+ * `mnemonic`. Defaults to 0 (back-compat). A non-zero index derives a
134
+ * dedicated wallet from a shared mnemonic, producing the SAME addresses as
135
+ * the SDK's `fromMnemonicFull(mnemonic, { accountIndex })`:
136
+ * - Nostr (secp256k1): m/44'/1237'/0'/0/{index}
137
+ * - EVM (secp256k1): same key as Nostr
138
+ * - Solana (Ed25519): m/44'/501'/{index}'/0' (SLIP-0010)
139
+ * - Mina (Pallas): m/44'/12586'/{index}'/0/0
140
+ *
141
+ * Ignored unless `mnemonic` is provided.
142
+ */
143
+ mnemonicAccountIndex?: number;
46
144
  /**
47
145
  * 32-byte Nostr private key (hex or Uint8Array).
48
- * Optional — if omitted, a keypair is auto-generated in applyDefaults().
146
+ * Optional — if omitted, a keypair is auto-generated in applyDefaults()
147
+ * (or derived from `mnemonic` when that is provided instead).
49
148
  */
50
149
  secretKey?: Uint8Array;
51
150
  /** ILP peer information for this client */
@@ -65,6 +164,27 @@ interface ToonClientConfig {
65
164
  * (e.g., hardware wallet, custodial key, or legacy key separation).
66
165
  */
67
166
  evmPrivateKey?: string | Uint8Array;
167
+ /**
168
+ * Named network tier. When set (and != `'custom'`), the client defaults all
169
+ * settlement-related config — RPC/GraphQL URLs, supported chain identifiers,
170
+ * preferred tokens, EVM TokenNetwork addresses, and the Solana/Mina channel
171
+ * params (programId / zkApp) — from the shared core presets
172
+ * (`resolveClientNetwork`), so the caller no longer hand-wires every address.
173
+ * This is the client-side mirror of the townhouse node's `network` selector;
174
+ * both resolve the SAME deployed contracts.
175
+ *
176
+ * Precedence: any explicit per-chain field (`supportedChains`,
177
+ * `chainRpcUrls`, `settlementAddresses`, `preferredTokens`, `tokenNetworks`,
178
+ * `solanaChannel`, `minaChannel`) you also pass OVERRIDES the preset for that
179
+ * field. `'custom'` keeps the fully-manual path (no preset defaults). When
180
+ * unset, behaviour is unchanged (fully backward compatible).
181
+ *
182
+ * - `mainnet` — Base mainnet + public Solana/Mina (TOON contracts not yet
183
+ * deployed → settlement unconfigured, relay-only).
184
+ * - `testnet` / `devnet` — Base Sepolia + Solana/Mina devnet with the LIVE
185
+ * deployed TOON settlement contracts.
186
+ */
187
+ network?: 'mainnet' | 'testnet' | 'devnet' | 'custom';
68
188
  /** Supported settlement chain identifiers (e.g., ["evm:anvil:31337"]) */
69
189
  supportedChains?: string[];
70
190
  /** Maps chain identifier to EVM settlement address */
@@ -94,8 +214,66 @@ interface ToonClientConfig {
94
214
  initialDeposit?: string;
95
215
  /** Challenge period in seconds (default: 86400) */
96
216
  settlementTimeout?: number;
217
+ /**
218
+ * Solana payment-channel parameters for opening a REAL on-chain channel and
219
+ * signing a connector-format Solana balance proof.
220
+ *
221
+ * When present (and the client has a Solana signer — i.e. it was constructed
222
+ * from a `mnemonic`), `ToonClient.start()` wires these into the on-chain
223
+ * channel client so that negotiating a `solana:*` chain opens an on-chain
224
+ * channel at the connector-parity PDA and pays a Solana-denominated claim.
225
+ *
226
+ * The Ed25519 keypair is NOT carried here — it is derived from the same
227
+ * `mnemonic` that produces the Solana signer, so the channel-open key and the
228
+ * claim-signing key are guaranteed identical.
229
+ */
230
+ solanaChannel?: SolanaChannelClientOptions;
231
+ /**
232
+ * Mina payment-channel parameters (graphqlUrl + zkAppAddress). When present
233
+ * (and the client has a Mina signer — i.e. it was constructed from a
234
+ * `mnemonic` AND `mina-signer` is installed), `ToonClient.start()` wires these
235
+ * into the on-chain channel client so negotiating a `mina:*` chain routes
236
+ * through `openMinaChannel` and pays a Mina-denominated claim.
237
+ *
238
+ * The Mina private key is NOT carried here — it is derived from the same
239
+ * `mnemonic` that produces the Mina signer.
240
+ *
241
+ * NOTE (Phase-2 Stage-3 gate): see `MinaChannelClientOptions` — supplying this
242
+ * wires the negotiation path but the resulting claim does not yet satisfy
243
+ * connector 3.9.0's Mina claim contract, so a live loop is claim-validation
244
+ * gated (distinct from the connector #88 on-chain-settle gate).
245
+ */
246
+ minaChannel?: MinaChannelClientOptions;
97
247
  /** File path for persisting payment channel nonce/amount state across restarts */
98
248
  channelStorePath?: string;
249
+ /**
250
+ * Transport configuration for privacy-preserving connections.
251
+ *
252
+ * - `direct` (default): No privacy overlay, connect directly.
253
+ * - `socks5`: Route connections through a SOCKS5 proxy (Node.js only).
254
+ * Requires `socks5h://` scheme for DNS leak prevention.
255
+ * - `gateway`: Route connections through an ator gateway URL (browser-compatible).
256
+ * The gateway proxies through ator server-side.
257
+ */
258
+ transport?: ClientTransportConfig;
259
+ /**
260
+ * Self-managed `anon` (anyone-protocol / ATOR) SOCKS5h proxy (Node.js only).
261
+ *
262
+ * When the `btpUrl` host ends in `.anyone` and NO explicit proxy is configured
263
+ * (`transport.socksProxy` / `transport.type === 'gateway'`) and the
264
+ * `ANYONE_PROXY_URLS` env var is unset, the SDK auto-downloads + spawns its own
265
+ * `anon` daemon, waits for it to bootstrap + bind a loopback SOCKS5 port, and
266
+ * routes BTP/HTTP through it — ZERO manual proxy setup. `client.stop()` tears
267
+ * the daemon down.
268
+ *
269
+ * - `undefined` (default): auto — managed proxy starts for `.anyone` hosts.
270
+ * - `false`: opt out — never auto-start (you must supply your own proxy).
271
+ *
272
+ * Ignored in browser bundles (the node-only daemon module is never loaded).
273
+ */
274
+ managedAnonProxy?: boolean;
275
+ /** Loopback SOCKS port the managed `anon` daemon binds. Default 9050. */
276
+ managedAnonSocksPort?: number;
99
277
  /** Nostr relay URL for peer discovery. Default: 'ws://localhost:7100' */
100
278
  relayUrl?: string;
101
279
  /**
@@ -166,7 +344,52 @@ interface SignedBalanceProof extends BalanceProofParams {
166
344
  tokenNetworkAddress: string;
167
345
  /** ERC-20 token address (e.g. USDC) for self-describing claim verification */
168
346
  tokenAddress?: string;
347
+ /**
348
+ * Counterparty settlement address the balance proof is bound to.
349
+ *
350
+ * Required for Solana/Mina, where the canonical balance-proof message folds
351
+ * the recipient in (`balanceProofHashSolana` / `balanceProofFieldsMina`).
352
+ * Unused for the client's EVM path (EIP-712 `BalanceProof` has no recipient
353
+ * term). Carried here so it flows from signing through to `buildClaimMessage`.
354
+ */
355
+ recipient?: string;
356
+ /**
357
+ * Mina payment-channel claim fields (connector 3.9.0 `MinaClaimMessage`).
358
+ *
359
+ * Populated only by {@link MinaSigner}, which produces the connector's
360
+ * `Poseidon([balA,balB,salt])` balance commitment + a Pallas Schnorr `proof`
361
+ * over `[commitment, Field(nonce), Poseidon(zkApp.x)]` rather than reusing the
362
+ * generic `signature` field. Carried so they flow from signing through to
363
+ * `MinaSigner.buildClaimMessage`. Absent for EVM/Solana.
364
+ */
365
+ mina?: {
366
+ /** `Poseidon([balanceA, balanceB, salt]).toString()`. */
367
+ balanceCommitment: string;
368
+ /** base64-encoded JSON proof `{ commitment, signature: { r, s }, nonce, signerPublicKey }`. */
369
+ proof: string;
370
+ /** Decimal salt string. */
371
+ salt: string;
372
+ /** Mina token id (default `'MINA'`). */
373
+ tokenId: string;
374
+ };
169
375
  }
376
+ /**
377
+ * Transport configuration for privacy-preserving connections.
378
+ *
379
+ * Node.js: Use `socks5` to route WebSocket and HTTP through a SOCKS5 proxy.
380
+ * Browser: Use `gateway` to route through a server-side ator gateway.
381
+ */
382
+ type ClientTransportConfig = {
383
+ type: 'direct';
384
+ } | {
385
+ type: 'socks5';
386
+ /** SOCKS5 proxy URL. MUST use `socks5h://` scheme (DNS leak prevention). */
387
+ socksProxy: string;
388
+ } | {
389
+ type: 'gateway';
390
+ /** Gateway base URL that proxies connections through ator server-side. */
391
+ gatewayUrl: string;
392
+ };
170
393
 
171
394
  /**
172
395
  * ToonClient - High-level client for interacting with TOON network.
@@ -209,6 +432,20 @@ declare class ToonClient {
209
432
  private readonly config;
210
433
  private state;
211
434
  private readonly evmSigner?;
435
+ private solanaSigner?;
436
+ /**
437
+ * Ed25519 signing seed (32 bytes) derived from the mnemonic for the Solana
438
+ * identity. Retained so `start()` can inject it into the on-chain channel
439
+ * client's Solana config (same key as `solanaSigner`).
440
+ */
441
+ private solanaSeed?;
442
+ private minaSigner?;
443
+ /**
444
+ * Mina private key (big-endian hex scalar, as `deriveFullIdentity` emits)
445
+ * derived from the mnemonic. Retained so `start()` can inject it into the
446
+ * on-chain channel client's Mina config (same key as `minaSigner`).
447
+ */
448
+ private minaPrivateKey?;
212
449
  private channelManager?;
213
450
  private readonly peerNegotiations;
214
451
  /**
@@ -232,10 +469,34 @@ declare class ToonClient {
232
469
  * Works before start() is called.
233
470
  */
234
471
  getPublicKey(): string;
472
+ /**
473
+ * Per-chain settlement readiness for the configured `network` tier, mirroring
474
+ * the townhouse node's status. Returns `undefined` when no named `network` is
475
+ * set (or `network: 'custom'`), since there is no preset tier to report on.
476
+ */
477
+ getNetworkStatus(): NetworkFamilyStatus | undefined;
235
478
  /**
236
479
  * Gets the EVM address derived from the Nostr secret key (or explicit evmPrivateKey override).
237
480
  */
238
481
  getEvmAddress(): string | undefined;
482
+ /**
483
+ * Gets the Solana (base58) address, when the client was constructed from a
484
+ * `mnemonic`. Available only AFTER `start()` (Solana keys are derived
485
+ * asynchronously). Returns undefined otherwise.
486
+ */
487
+ getSolanaAddress(): string | undefined;
488
+ /**
489
+ * Gets the Mina (base58) address, when the client was constructed from a
490
+ * `mnemonic` AND `mina-signer` is installed. Available only AFTER `start()`.
491
+ * Returns undefined otherwise.
492
+ */
493
+ getMinaAddress(): string | undefined;
494
+ /**
495
+ * Derive the Solana/Mina keys from the mnemonic and register their signers on
496
+ * the ChannelManager. Mirrors how the EVM signer is wired, but for the
497
+ * non-secp256k1 chains. Skips any chain whose optional dependency is missing.
498
+ */
499
+ private registerMnemonicChainSigners;
239
500
  /**
240
501
  * Starts the ToonClient.
241
502
  *
@@ -263,7 +524,58 @@ declare class ToonClient {
263
524
  publishEvent(event: NostrEvent, options?: {
264
525
  destination?: string;
265
526
  claim?: SignedBalanceProof;
527
+ ilpAmount?: bigint;
266
528
  }): Promise<PublishEventResult>;
529
+ /**
530
+ * Sends a raw swap ILP packet (Story 12.5) to a Mill peer with an attached
531
+ * balance-proof claim. This is a lower-level surface than `publishEvent`:
532
+ * it forwards the raw `IlpSendResult` so the sender (`streamSwap()`) can
533
+ * decode FULFILL metadata itself.
534
+ *
535
+ * Claim resolution mirrors `publishEvent`:
536
+ * (a) explicit `params.claim` -> use it,
537
+ * (b) `channelManager` present -> auto-open + auto-sign for the peer
538
+ * matching `destination`,
539
+ * (c) neither -> throw MISSING_CLAIM.
540
+ *
541
+ * @throws {ToonClientError} INVALID_STATE / NO_BTP_CLIENT / MISSING_CLAIM
542
+ */
543
+ sendSwapPacket(params: {
544
+ destination: string;
545
+ amount: bigint;
546
+ toonData: Uint8Array;
547
+ timeout?: number;
548
+ claim?: SignedBalanceProof;
549
+ }): Promise<IlpSendResult>;
550
+ /**
551
+ * Build a BTP claim message from a pre-signed balance proof using the
552
+ * CHAIN-APPROPRIATE signer.
553
+ *
554
+ * The explicit-claim path (caller signs the balance proof, then passes
555
+ * `{ claim }`) must wrap the proof with the signer matching the channel's
556
+ * chain. Hardcoding `EvmSigner.buildClaimMessage` here produced an EVM
557
+ * `BTPClaimMessage` for a Solana/Mina balance proof — no `blockchain`
558
+ * discriminator and the base58 channel account placed in the EVM
559
+ * `channelId` field — which the connector's inbound validator classifies
560
+ * as EVM and rejects with F06 (`Invalid channelId format`).
561
+ *
562
+ * When the proof's `channelId` is tracked we use
563
+ * `getSignerForChannel(channelId).buildClaimMessage`, which emits the
564
+ * correct per-chain envelope (e.g. `blockchain:'solana'` + base58
565
+ * `channelAccount`). When it is not tracked we fall back to the EVM signer
566
+ * to preserve prior behavior for lightweight/EVM-only callers.
567
+ *
568
+ * EVM output is byte-identical to the previous hardcoded path (the EVM
569
+ * adapter in `getSignerForChannel` delegates to the same
570
+ * `EvmSigner.buildClaimMessage`).
571
+ */
572
+ private buildClaimMessageForProof;
573
+ /**
574
+ * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.
575
+ * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution
576
+ * to call this helper. Kept duplicated for now to minimize regression risk.
577
+ */
578
+ private resolveClaimForDestination;
267
579
  /**
268
580
  * Signs a balance proof for the given channel with the specified amount.
269
581
  * Delegates to ChannelManager which auto-increments nonce and tracks cumulative amount.
@@ -274,6 +586,22 @@ declare class ToonClient {
274
586
  * @throws {ToonClientError} If no EVM signer configured or channel not tracked
275
587
  */
276
588
  signBalanceProof(channelId: string, amount: bigint): Promise<SignedBalanceProof>;
589
+ /**
590
+ * Eagerly open (or return existing) payment channel for the given destination.
591
+ *
592
+ * Channels are normally opened lazily on the first `publishEvent()` /
593
+ * `sendSwapPacket()` call. This method exposes the lazy-open path so
594
+ * callers (and E2E tests) that need a tracked `channelId` BEFORE publishing
595
+ * can force the open. Idempotent — returns the existing channel ID for the
596
+ * peer if one is already open.
597
+ *
598
+ * @param destination - Optional ILP destination address. Defaults to
599
+ * `config.destinationAddress`.
600
+ * @returns The channel ID of the (now) open channel.
601
+ * @throws {ToonClientError} If client not started, no channel manager
602
+ * configured, or peer negotiation metadata missing.
603
+ */
604
+ openChannel(destination?: string): Promise<string>;
277
605
  /**
278
606
  * Gets list of tracked payment channel IDs.
279
607
  */
@@ -343,6 +671,48 @@ declare class ToonClient {
343
671
  getDiscoveredPeers(): _toon_protocol_core.DiscoveredPeer[];
344
672
  }
345
673
 
674
+ /**
675
+ * Hidden-service hostname validation for the anyone-protocol / ATOR network.
676
+ *
677
+ * The `anon` binary routes hidden-service hostnames under the **`.anyone`** TLD
678
+ * ONLY. A `<host>.anon` name is NOT recognized as a hidden service — anon treats
679
+ * it as a clearnet name and tries to exit-resolve it, which fails
680
+ * (`resolve failed` / `HostUnreachable`). Only `<host>.anyone` triggers anon's
681
+ * `parse_extended_hostname: Anyone dns address lookup` and is validated.
682
+ *
683
+ * Historically the client and the toon-client pod accepted BOTH `.anon` and
684
+ * `.anyone`, so a `.anon` address was silently accepted and then failed deep in
685
+ * the transport with an opaque error. This module makes `.anyone` the single
686
+ * accepted/routable HS TLD and rejects `.anon` up front with an actionable
687
+ * message (see issue #201).
688
+ *
689
+ * This is a pure, browser-safe helper (no Node built-ins) so it can be imported
690
+ * from any transport path.
691
+ */
692
+ /**
693
+ * A `<host>.anyone` hidden-service hostname. The label is base32 (`a-z2-7`),
694
+ * matching the on-wire onion-style address alphabet anon uses.
695
+ */
696
+ declare const HS_HOSTNAME_REGEX: RegExp;
697
+ /** Max length of an HS hostname (defensive bound against pathological input). */
698
+ declare const HS_HOSTNAME_MAX_LENGTH = 80;
699
+ /**
700
+ * Returns true iff `s` is a routable `.anyone` hidden-service hostname.
701
+ * Does NOT accept the legacy `.anon` TLD (see {@link assertRoutableHsHostname}).
702
+ */
703
+ declare function isRoutableHsHostname(s: unknown): s is string;
704
+ /**
705
+ * Validates that `hostname` is a routable `.anyone` hidden-service address.
706
+ *
707
+ * - `<host>.anyone` → returns the hostname unchanged.
708
+ * - `<host>.anon` → throws with an actionable message pointing at `.anyone`
709
+ * (anon does NOT route `.anon`; it would silently fail in the transport).
710
+ * - anything else → throws a generic format error.
711
+ *
712
+ * @throws {Error} if the hostname is not a routable `.anyone` HS address.
713
+ */
714
+ declare function assertRoutableHsHostname(hostname: unknown): string;
715
+
346
716
  /**
347
717
  * Base error class for all TOON client errors.
348
718
  */
@@ -679,6 +1049,8 @@ interface BtpRuntimeClientConfig {
679
1049
  maxRetries?: number;
680
1050
  /** Delay between reconnection attempts in ms (default: 1000) */
681
1051
  retryDelay?: number;
1052
+ /** Custom WebSocket constructor (for SOCKS5 proxy support). */
1053
+ createWebSocket?: (url: string) => WebSocket;
682
1054
  }
683
1055
  /**
684
1056
  * BTP transport implementing IlpClient.
@@ -722,6 +1094,14 @@ declare class BtpRuntimeClient implements IlpClient {
722
1094
  data: string;
723
1095
  timeout?: number;
724
1096
  }, claim: Record<string, unknown>): Promise<IlpSendResult>;
1097
+ /**
1098
+ * Send a standalone `payment-channel-claim` BTP MESSAGE (no ILP packet
1099
+ * attached). The connector's ClaimReceiver consumes this fire-and-forget
1100
+ * to register cumulative claim state independently of the per-packet
1101
+ * forwarding path. Auto-reconnects on connection errors.
1102
+ */
1103
+ sendClaimMessage(claim: Record<string, unknown>): Promise<void>;
1104
+ private _sendClaimMessageOnce;
725
1105
  /**
726
1106
  * Single-attempt ILP packet send. Reconnects if not connected.
727
1107
  */
@@ -761,36 +1141,92 @@ interface ChainSigner {
761
1141
  transferredAmount: bigint;
762
1142
  lockedAmount: bigint;
763
1143
  locksRoot: string;
1144
+ /**
1145
+ * Counterparty settlement address the proof is bound to. Required for
1146
+ * Solana/Mina (folded into the canonical balance-proof message); the EVM
1147
+ * adapter ignores it (EIP-712 has no recipient term).
1148
+ */
1149
+ recipient: string;
764
1150
  metadata: ChainMetadata;
1151
+ /**
1152
+ * On-chain channel `depositTotal` (base units). When supplied (>0), a Mina
1153
+ * signer binds the conserved `balanceB = depositTotal − balanceA` commitment
1154
+ * required to settle on a funded zkApp (connector#133); EVM/Solana signers
1155
+ * ignore it. When omitted, a Mina signer self-resolves it from chain if it
1156
+ * was configured with a GraphQL URL (#223), else falls back to the legacy
1157
+ * `balanceB = 0` form.
1158
+ */
1159
+ depositTotal?: bigint;
765
1160
  }): Promise<SignedBalanceProof>;
766
1161
  buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage;
767
1162
  }
768
1163
  type ClaimMessage = EVMClaimMessage | SolanaClaimMessage | MinaClaimMessage;
1164
+ /**
1165
+ * Solana payment-channel claim — wire-compatible with the connector's
1166
+ * `SolanaClaimMessage` (`@toon-protocol/connector` `btp/btp-claim-types.ts`).
1167
+ * Field names match the connector's `validateSolanaClaim` exactly:
1168
+ * `channelAccount` (base58 PDA), `signerPublicKey` (base58), base64 `signature`.
1169
+ */
769
1170
  interface SolanaClaimMessage {
770
1171
  version: '1.0';
771
1172
  blockchain: 'solana';
772
1173
  messageId: string;
773
1174
  timestamp: string;
774
1175
  senderId: string;
775
- channelId: string;
1176
+ /** On-chain PDA account address for the payment channel (base58). */
1177
+ channelAccount: string;
776
1178
  nonce: number;
1179
+ /** Cumulative transferred amount (string for bigint precision). */
777
1180
  transferredAmount: string;
1181
+ /** Ed25519 signature over the 48-byte balance-proof message (base64). */
778
1182
  signature: string;
779
- signerAddress: string;
1183
+ /** Base58-encoded Ed25519 public key of the signer. */
1184
+ signerPublicKey: string;
1185
+ /** Solana program id for the payment-channel program (base58). */
780
1186
  programId: string;
781
1187
  }
1188
+ /**
1189
+ * Mina payment-channel claim — wire-compatible with the connector's
1190
+ * `MinaClaimMessage` (`@toon-protocol/connector` `btp/btp-claim-types.ts`).
1191
+ * Field names + types match `validateMinaClaim` exactly: `zkAppAddress`
1192
+ * (B62-prefixed 55-char base58, the channel id), `tokenId`, `balanceCommitment`
1193
+ * (`Poseidon([balA,balB,salt])` decimal string), integer `nonce`, base64 `proof`,
1194
+ * and `salt`. `transferredAmount`/`balanceB`/`signatureB`/`network` are OPTIONAL
1195
+ * at validation; the apex-as-recipient single-direction claim sends party-A only.
1196
+ */
782
1197
  interface MinaClaimMessage {
783
1198
  version: '1.0';
784
1199
  blockchain: 'mina';
785
1200
  messageId: string;
786
1201
  timestamp: string;
787
1202
  senderId: string;
788
- channelId: string;
789
- nonce: number;
790
- transferredAmount: string;
791
- commitment: string;
792
- signerAddress: string;
1203
+ /** Deployed payment-channel zkApp address (B62 base58) — the channel id. */
793
1204
  zkAppAddress: string;
1205
+ /** Mina token id (default `'MINA'`). */
1206
+ tokenId: string;
1207
+ /** `Poseidon([balanceA, balanceB, salt]).toString()`. */
1208
+ balanceCommitment: string;
1209
+ nonce: number;
1210
+ /** base64-encoded JSON `{ commitment, signature: { r, s }, nonce, signerPublicKey }`. */
1211
+ proof: string;
1212
+ /** Commitment salt (decimal string). */
1213
+ salt: string;
1214
+ /** Cumulative transferred amount (optional; string for bigint precision). */
1215
+ transferredAmount?: string;
1216
+ /**
1217
+ * Signer's Mina public key (B62 base58) — the claiming participant.
1218
+ *
1219
+ * Surfaced top-level (in addition to being embedded in `proof`) so the
1220
+ * connector's `SettlementExecutor` can resolve participant keys for the
1221
+ * on-chain `claimFromChannel` on an externally-opened (inbound) channel. The
1222
+ * connector reads `latestClaim.signerPublicKey` directly (not the proof blob);
1223
+ * without it the Mina SDK's `claimFromChannel` throws `ACCOUNT_NOT_FOUND`
1224
+ * ("Participant keys not found in cache and none were supplied"). The
1225
+ * connector accepts this as an optional `MinaClaimMessage` field.
1226
+ */
1227
+ signerPublicKey?: string;
1228
+ /** Mina network id — defaults to `devnet` connector-side when omitted. */
1229
+ network?: 'mainnet' | 'devnet' | 'berkeley' | 'lightnet';
794
1230
  }
795
1231
 
796
1232
  /**
@@ -862,17 +1298,33 @@ declare class EvmSigner {
862
1298
  }
863
1299
 
864
1300
  /**
865
- * Solana signer for Ed25519 balance proofs.
1301
+ * Solana signer for the connector payment-channel claim path.
866
1302
  *
867
- * Signs channel state using Ed25519 (raw, not EIP-712).
868
- * Dynamically imports @noble/curves to avoid missing-dep errors for non-Solana users.
1303
+ * Signs the connector's on-chain payment-channel balance-proof message — the
1304
+ * raw 48-byte `channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)` (see
1305
+ * `@toon-protocol/connector` `SolanaPaymentChannelSDK._buildBalanceProofMessage`
1306
+ * + `solana-payment-channel-provider.verifyBalanceProof`). The produced 64-byte
1307
+ * Ed25519 signature verifies on the connector's `verifySolanaClaim` path, which
1308
+ * is what makes a client-issued Solana payment-channel claim (paying the apex
1309
+ * to write) acceptable on connector 3.9.0.
1310
+ *
1311
+ * NOTE: this is a DIFFERENT message from the Mill ↔ sender swap-claim wire
1312
+ * contract (`balanceProofHashSolana`, SDK `verifyEd25519Signature`). The client
1313
+ * here is paying a payment-channel claim to the apex, not issuing a swap claim,
1314
+ * so it must sign the connector's on-chain payment-channel message. `channelId`
1315
+ * MUST be the base58 channel PDA (produced by `OnChainChannelClient.openChannel`).
869
1316
  */
870
1317
  declare class SolanaSigner implements ChainSigner {
871
1318
  readonly chainType: "solana";
1319
+ /** 32-byte Ed25519 seed. */
872
1320
  private readonly privateKey;
873
- private publicKey?;
874
1321
  private pubkeyBase58Cache?;
875
- constructor(privateKey: Uint8Array);
1322
+ /**
1323
+ * @param privateKey - 32-byte Ed25519 seed (e.g. `identity.solana.secretKey.slice(0, 32)`).
1324
+ * @param publicKeyBase58 - Optional base58 public key (e.g. `identity.solana.publicKey`).
1325
+ * When omitted it is derived lazily from `privateKey`.
1326
+ */
1327
+ constructor(privateKey: Uint8Array, publicKeyBase58?: string);
876
1328
  private ensurePublicKey;
877
1329
  get signerIdentifier(): string;
878
1330
  signBalanceProof(params: {
@@ -881,44 +1333,170 @@ declare class SolanaSigner implements ChainSigner {
881
1333
  transferredAmount: bigint;
882
1334
  lockedAmount: bigint;
883
1335
  locksRoot: string;
1336
+ recipient: string;
884
1337
  metadata: ChainMetadata;
885
1338
  }): Promise<SignedBalanceProof>;
886
1339
  buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage;
887
1340
  }
888
1341
 
1342
+ /** Reads a channel zkApp's on-chain `depositTotal` (base units). */
1343
+ type MinaDepositReader = (zkAppAddress: string) => Promise<bigint>;
1344
+ /** Optional `MinaSigner` wiring for on-chain `depositTotal` resolution. */
1345
+ interface MinaSignerOptions {
1346
+ /**
1347
+ * Mina GraphQL URL used to read the channel's on-chain `depositTotal` when a
1348
+ * caller doesn't supply it to `signBalanceProof`. Enables conserved
1349
+ * `balanceB = depositTotal − balanceA` claims (settleable on funded zkApps).
1350
+ */
1351
+ graphqlUrl?: string;
1352
+ /** Inject a deposit reader (tests / custom transport). Overrides `graphqlUrl`. */
1353
+ depositReader?: MinaDepositReader;
1354
+ }
889
1355
  /**
890
- * Mina signer for Poseidon commitment balance proofs.
1356
+ * Mina (Pallas) signer for the connector payment-channel claim path.
1357
+ *
1358
+ * Produces the connector 3.9.0 `MinaClaimMessage` contract — `{ zkAppAddress,
1359
+ * tokenId, balanceCommitment, proof (base64), salt, nonce }` — by reproducing
1360
+ * `MinaPaymentChannelSDK.signBalanceProof` exactly (via
1361
+ * {@link buildMinaPaymentChannelProof}):
1362
+ *
1363
+ * commitment = Poseidon([Field(balanceA), Field(0), Field(salt)])
1364
+ * channelHashField = Poseidon([participantA.x, participantB.x, 0]) (see below)
1365
+ * proof = base64(JSON{ commitment, signature: { r, s }, nonce, signerPublicKey })
1366
+ *
1367
+ * with the Schnorr signature computed over `[commitment, Field(nonce),
1368
+ * channelHashField]` using the Mina `'devnet'` network id (matching o1js's
1369
+ * hardcoded `Signature.create` prefix). Verified field-by-field against the
1370
+ * connector's o1js `Signature.fromJSON({r,s}).verify` (see the package tests).
1371
+ *
1372
+ * `channelHashField` is the ON-CHAIN participant form
1373
+ * (`Poseidon([client.x, apex.x, 0])`, participantA=client, participantB=apex)
1374
+ * whenever the apex's Mina pubkey is known (the negotiated `recipient`), so the
1375
+ * claim can SETTLE on-chain via the zkApp's `claimFromChannel` (which only
1376
+ * verifies the participant form). When the apex pubkey is unavailable the signer
1377
+ * falls back to the legacy zkApp-x form (`Poseidon([zkApp.x])`); the connector's
1378
+ * off-chain `verifyBalanceProof` accepts EITHER, so off-chain store/FULFILL works
1379
+ * in both cases — only on-chain settle requires the participant form.
1380
+ *
1381
+ * NOTE: this is a DIFFERENT message + format from the Mill ↔ sender swap-claim
1382
+ * wire contract (`balanceProofFieldsMina` in `@toon-protocol/core`, verified by
1383
+ * the SDK's `verifyMinaSignature`). The client here pays a payment-channel claim
1384
+ * to the apex, so it signs the connector's on-chain payment-channel scheme; the
1385
+ * swap-format hash is left untouched (mirrors the Solana #105 separation).
891
1386
  *
892
- * Dynamically imports o1js to avoid pulling 50MB into the client bundle
893
- * for non-Mina users.
1387
+ * `channelId` MUST be the deployed payment-channel zkApp B62 address (the same
1388
+ * address the apex's Mina provider resolves on-chain via `getChannelState`),
1389
+ * which is what `OnChainChannelClient.openMinaChannel` returns.
1390
+ *
1391
+ * `mina-signer` is an OPTIONAL dependency: its crypto (Poseidon, Pallas Schnorr,
1392
+ * the base58 signature codec) is loaded dynamically so the client builds and runs
1393
+ * for non-Mina users without it installed, and WITHOUT pulling the o1js WASM
1394
+ * circuit runtime.
894
1395
  */
895
1396
  declare class MinaSigner implements ChainSigner {
896
1397
  readonly chainType: "mina";
897
- private readonly privateKeyBase58;
898
- private publicKeyBase58;
899
- constructor(privateKeyBase58: string);
1398
+ /** Big-endian hex scalar (or already-`EK…` base58) Mina private key. */
1399
+ private readonly privateKey;
1400
+ private publicKeyBase58?;
1401
+ private readonly depositReader?;
1402
+ /** Per-zkApp `depositTotal` cache (deposits are rare; the connector re-reads). */
1403
+ private readonly depositCache;
1404
+ /**
1405
+ * @param privateKey - Mina private key as big-endian hex scalar (the form
1406
+ * `deriveFullIdentity()` emits, `identity.mina.privateKey`) or an `EK…`
1407
+ * base58 key. Converted to the base58check form mina-signer requires.
1408
+ * @param publicKeyBase58 - Optional base58 public key (e.g.
1409
+ * `identity.mina.publicKey`). When omitted it is derived during signing.
1410
+ * @param options - Optional on-chain `depositTotal` resolution (graphqlUrl or
1411
+ * an injected reader) so claims conserve balances on funded zkApps.
1412
+ */
1413
+ constructor(privateKey: string, publicKeyBase58?: string, options?: MinaSignerOptions);
1414
+ /**
1415
+ * Resolve the channel's on-chain `depositTotal`, caching per zkApp. Returns
1416
+ * `undefined` when no reader is configured or the read fails — callers then
1417
+ * fall back to the legacy `balanceB = 0` commitment.
1418
+ */
1419
+ private resolveDepositTotal;
900
1420
  get signerIdentifier(): string;
901
- private ensurePublicKey;
1421
+ /** Derive this signer's B62 public key from its (base58) private key. */
1422
+ private deriveOwnPublicKey;
902
1423
  signBalanceProof(params: {
903
1424
  channelId: string;
904
1425
  nonce: number;
905
1426
  transferredAmount: bigint;
906
1427
  lockedAmount: bigint;
907
1428
  locksRoot: string;
1429
+ recipient: string;
908
1430
  metadata: ChainMetadata;
1431
+ /**
1432
+ * On-chain channel `depositTotal`. When provided (>0), the signed commitment
1433
+ * binds `balanceB = depositTotal − balanceA` (the funder's remaining
1434
+ * balance), matching the connector's claimFromChannel reconstruction
1435
+ * (toon-protocol/connector#133) and the on-chain circuit's
1436
+ * `balanceA + balanceB == depositTotal` invariant. Omitted/0 keeps the
1437
+ * legacy `balanceB = 0` form (off-chain-store-only, non-settleable).
1438
+ */
1439
+ depositTotal?: bigint;
909
1440
  }): Promise<SignedBalanceProof>;
910
1441
  buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage;
911
1442
  }
912
1443
 
913
1444
  interface SolanaChannelConfig {
914
1445
  rpcUrl: string;
1446
+ /**
1447
+ * Ed25519 keypair material. Accepts either a 32-byte seed or a 64-byte
1448
+ * `secretKey` (seed || pubkey, as produced by `deriveFullIdentity`). The first
1449
+ * 32 bytes are the signing seed; the public key is derived from it.
1450
+ */
915
1451
  keypair: Uint8Array;
916
1452
  programId: string;
1453
+ /**
1454
+ * SPL token mint (base58) for PDA derivation. Optional — the per-channel
1455
+ * negotiated token (`OpenChannelParams.token`) takes precedence when present.
1456
+ */
1457
+ tokenMint?: string;
1458
+ /**
1459
+ * Challenge-period duration in seconds for `initialize_channel`. Defaults to
1460
+ * `OpenChannelParams.settlementTimeout` or 86400.
1461
+ */
1462
+ challengeDuration?: number;
1463
+ /**
1464
+ * Optional deposit amount (base units, string) + the payer's funded SPL token
1465
+ * account (ATA, base58). When omitted, the channel is opened (initialized)
1466
+ * without an on-chain deposit — the connector accepts the claim on channel
1467
+ * `opened` status + participant membership; deposit is only consumed at
1468
+ * on-chain claim/settle time.
1469
+ */
1470
+ deposit?: {
1471
+ amount: string;
1472
+ payerTokenAccount: string;
1473
+ };
917
1474
  }
918
1475
  interface MinaChannelConfig {
919
1476
  graphqlUrl: string;
920
1477
  privateKey: string;
921
1478
  zkAppAddress: string;
1479
+ /**
1480
+ * Channel settlement timeout in slots for `initializeChannel`. Defaults to
1481
+ * `OpenChannelParams.settlementTimeout` or 86400.
1482
+ */
1483
+ challengeDuration?: number;
1484
+ /**
1485
+ * Mina token id field (decimal string) for `initializeChannel`. Default '1'
1486
+ * (native MINA). The connector reads this only as on-chain channel metadata.
1487
+ */
1488
+ tokenId?: string;
1489
+ /**
1490
+ * Optional on-chain deposit (base units, string) submitted after the channel
1491
+ * is initialized. When omitted, the channel is opened (OPEN state) without a
1492
+ * deposit — the connector accepts the claim on `opened` status; deposit is
1493
+ * only consumed at on-chain settle time.
1494
+ */
1495
+ deposit?: {
1496
+ amount: string;
1497
+ };
1498
+ /** Mina network id for the account/Schnorr prefix. Default 'devnet'. */
1499
+ networkId?: 'devnet' | 'mainnet';
922
1500
  }
923
1501
  interface OnChainChannelClientConfig {
924
1502
  evmSigner: EvmSigner;
@@ -935,10 +1513,29 @@ interface OnChainChannelClientConfig {
935
1513
  declare class OnChainChannelClient implements ConnectorChannelClient {
936
1514
  private readonly evmSigner;
937
1515
  private readonly chainRpcUrls;
938
- private readonly solanaConfig?;
939
- private readonly minaConfig?;
1516
+ private solanaConfig?;
1517
+ private minaConfig?;
940
1518
  private readonly channelContext;
941
1519
  constructor(config: OnChainChannelClientConfig);
1520
+ /**
1521
+ * Late-binds the Solana channel config.
1522
+ *
1523
+ * `ToonClient.start()` derives the Solana Ed25519 keypair from the client's
1524
+ * mnemonic asynchronously (after this client is constructed), so the keypair
1525
+ * is injected here rather than at construction. Same keypair as the
1526
+ * registered Solana signer — guarantees the channel-open key and the
1527
+ * claim-signing key match.
1528
+ */
1529
+ setSolanaConfig(config: SolanaChannelConfig): void;
1530
+ /**
1531
+ * Late-binds the Mina channel config.
1532
+ *
1533
+ * Parallel to `setSolanaConfig`: `ToonClient.start()` derives the Mina private
1534
+ * key from the client's mnemonic asynchronously (after this client is
1535
+ * constructed), so the key is injected here rather than at construction. Same
1536
+ * key as the registered Mina signer.
1537
+ */
1538
+ setMinaConfig(config: MinaChannelConfig): void;
942
1539
  /**
943
1540
  * Parse chain identifier to extract chainId.
944
1541
  * Format: "evm:{network}:{chainId}" e.g., "evm:anvil:31337"
@@ -958,12 +1555,48 @@ declare class OnChainChannelClient implements ConnectorChannelClient {
958
1555
  */
959
1556
  openChannel(params: OpenChannelParams): Promise<OpenChannelResult>;
960
1557
  /**
961
- * Opens a Solana payment channel (PDA creation).
1558
+ * Opens a REAL on-chain Solana payment channel.
1559
+ *
1560
+ * Derives the connector-parity channel PDA
1561
+ * (`[b"channel", min_pubkey, max_pubkey, token_mint]`), submits the
1562
+ * `initialize_channel` instruction (+ optional `deposit`) to the deployed
1563
+ * payment-channel program, and returns the base58 PDA as the channel id. That
1564
+ * PDA is what the claim carries as `channelAccount`, and the on-chain channel
1565
+ * is what the connector's `verifySolanaClaim` reads via
1566
+ * `provider.getChannelState` before accepting the claim.
1567
+ *
1568
+ * Mirrors `openEvmChannel`'s open(+deposit) structure. Idempotent: if the
1569
+ * channel account already exists on-chain, returns its PDA without
1570
+ * re-initializing.
962
1571
  */
963
1572
  private openSolanaChannel;
964
1573
  /**
965
- * Opens a Mina payment channel (zkApp state transition).
966
- * Dynamically imports o1js to avoid bundle bloat.
1574
+ * Opens a REAL on-chain Mina payment channel on the deployed `PaymentChannel`
1575
+ * zkApp.
1576
+ *
1577
+ * The zkApp is deployed out-of-band (the operator/e2e harness deploys it
1578
+ * deterministically and advertises its B62 address). This client then calls
1579
+ * `initializeChannel` on that zkApp so its on-chain `channelState` becomes
1580
+ * `OPEN` — which is what the connector's `MinaPaymentChannelSDK.getChannelState`
1581
+ * reads to return status `'opened'` (claim verification otherwise fails with
1582
+ * `mina_claim_verification_failed`). The deployed zkApp address IS the channel
1583
+ * id: `MinaClaimMessage.zkAppAddress` is both the claim's channel identifier
1584
+ * AND the channel-hash preimage the off-chain proof binds to (see
1585
+ * `mina-payment-channel.ts`), so the channel-open id and the claim's channel id
1586
+ * are guaranteed identical.
1587
+ *
1588
+ * This is the Mina analog of `openSolanaChannel` (connector#105): the client
1589
+ * opens its own per-channel on-chain state (initialize + optional deposit). The
1590
+ * heavyweight o1js + `@toon-protocol/mina-zkapp` proof work is lazily imported
1591
+ * inside `openMinaChannelOnChain` so npm consumers who never open a Mina
1592
+ * channel don't pay the o1js cost.
1593
+ *
1594
+ * Idempotent: if the on-chain channel is already `OPEN`, the opener returns
1595
+ * without re-initializing.
1596
+ *
1597
+ * NOTE: full on-chain Mina SETTLE remains gated by the connector-side
1598
+ * settlement-executor (the same blocker that stops the Solana SETTLE); reaching
1599
+ * `opened` + a stored claim is parity with Solana.
967
1600
  */
968
1601
  private openMinaChannel;
969
1602
  /**
@@ -1062,6 +1695,8 @@ declare class ChannelManager {
1062
1695
  chainId: number;
1063
1696
  tokenNetworkAddress: string;
1064
1697
  tokenAddress?: string;
1698
+ recipient?: string;
1699
+ depositTotal?: bigint;
1065
1700
  }, initialNonce?: number, initialAmount?: bigint): void;
1066
1701
  /**
1067
1702
  * Signs a balance proof for the given channel.
@@ -1093,6 +1728,26 @@ declare class ChannelManager {
1093
1728
  isTracking(channelId: string): boolean;
1094
1729
  }
1095
1730
 
1731
+ /**
1732
+ * Read a Mina payment-channel zkApp's on-chain `depositTotal` via a plain
1733
+ * GraphQL query (no o1js / WASM). Used by {@link MinaSigner} to bind the
1734
+ * conserved `balanceB = depositTotal − balanceA` commitment that a FUNDED zkApp
1735
+ * requires (connector#133); without it the connector's `claimFromChannel`
1736
+ * verification rejects the claim with `F06 - Invalid zk-SNARK proof on claim`.
1737
+ *
1738
+ * The `PaymentChannel` zkApp app-state field order is
1739
+ * `[channelHash, balanceCommitment, nonceField, channelState, depositTotal, …]`
1740
+ * (see `mina-channel-open.ts`), so `depositTotal` is `zkappState[4]`.
1741
+ */
1742
+ /**
1743
+ * Query `account(publicKey).zkappState` and return the channel's `depositTotal`
1744
+ * (base units). Throws when the account/state is unavailable so callers can fall
1745
+ * back to the legacy `balanceB = 0` behavior.
1746
+ *
1747
+ * @param fetchImpl - injectable for tests; defaults to global `fetch`.
1748
+ */
1749
+ declare function readMinaDepositTotal(graphqlUrl: string, zkAppAddress: string, fetchImpl?: typeof fetch): Promise<bigint>;
1750
+
1096
1751
  /**
1097
1752
  * Configuration options for retry behavior with exponential backoff.
1098
1753
  */
@@ -1130,6 +1785,95 @@ interface RetryOptions {
1130
1785
  */
1131
1786
  declare function withRetry<T>(operation: () => Promise<T>, options: RetryOptions): Promise<T>;
1132
1787
 
1788
+ /**
1789
+ * Self-managed `anon` (anyone-protocol / ATOR) SOCKS5h proxy (Node.js only).
1790
+ *
1791
+ * Lets a `@toon-protocol/client` consumer reach a `.anyone` hidden service with
1792
+ * ZERO manual proxy setup: the SDK downloads, verifies, extracts, and spawns its
1793
+ * own `anon` daemon, waits for it to bootstrap + bind a loopback SOCKS5 port, and
1794
+ * hands back a `socks5h://127.0.0.1:<port>` URL. The proven reference is the
1795
+ * server-side pod entrypoint `docker/src/entrypoint-toon-client.ts` (`writeTorrc`,
1796
+ * `spawnAnon`, `waitForAnonSocks`, `tcpProbe`); this module ports that daemon
1797
+ * logic into the client package and adds the binary download + checksum gate so it
1798
+ * works without an OS-level `anon` install.
1799
+ *
1800
+ * BROWSER SAFETY: this module is dynamically imported only from `resolveTransport`
1801
+ * when a managed proxy is actually needed (Node-only path). Every Node built-in is
1802
+ * pulled in lazily via the ESM-safe `require(...)` built off `import.meta.url`
1803
+ * (the same pattern as `socks5.ts`), so a browser bundler that statically analyses
1804
+ * the package never reaches `node:child_process`/`node:fs`/`node:https`/`node:net`.
1805
+ */
1806
+ /**
1807
+ * Pinned `anon` release. "beta" is the channel slug embedded in the per-platform
1808
+ * zip asset names (e.g. `anon-beta-macos-arm64.zip`).
1809
+ */
1810
+ declare const ANON_VERSION = "v0.4.10.0-beta";
1811
+ /**
1812
+ * Per-platform `anon` zip asset descriptor. `sha256` is the pinned checksum of the
1813
+ * release zip. All supported platforms are pinned (issue #204); the type stays
1814
+ * `string | null` and the download gate still defensively refuses a `null` entry,
1815
+ * so adding a new (not-yet-hashed) platform fails closed rather than skipping
1816
+ * verification.
1817
+ */
1818
+ interface AnonAsset {
1819
+ /** Release asset file name, e.g. `anon-beta-macos-arm64.zip`. */
1820
+ assetName: string;
1821
+ /** Pinned sha256 of the zip, or null when not yet pinned (issue #204). */
1822
+ sha256: string | null;
1823
+ }
1824
+ /**
1825
+ * Platform → asset map keyed by `${os.platform()}-${os.arch()}` (Node values).
1826
+ * Only macOS + Linux on x64/arm64 are supported (the `anon` releases that ship a
1827
+ * SOCKS-capable binary). Windows is intentionally absent.
1828
+ *
1829
+ * Pinned checksums (issue #204): all four supported platforms are pinned to the
1830
+ * sha256 of the `v0.4.10.0-beta` release zips (downloaded + hashed; the
1831
+ * darwin-arm64 value matches the previously-verified manual flow).
1832
+ */
1833
+ declare const ANON_ASSETS: Record<string, AnonAsset>;
1834
+ /**
1835
+ * Resolves the `anon` release asset for a platform/arch pair (Node
1836
+ * `os.platform()` / `os.arch()` values).
1837
+ *
1838
+ * @throws If the platform/arch combination has no known `anon` asset.
1839
+ */
1840
+ declare function selectAnonAsset(platform: string, arch: string): AnonAsset;
1841
+ /**
1842
+ * Handle returned by `startManagedAnonProxy`. `socksProxy` is the loopback
1843
+ * `socks5h://` URL to wire into `transport: { type: 'socks5', socksProxy }`.
1844
+ * `stop()` SIGTERMs the daemon and is idempotent.
1845
+ */
1846
+ interface ManagedAnonProxy {
1847
+ socksProxy: string;
1848
+ stop(): Promise<void>;
1849
+ }
1850
+ /**
1851
+ * Options for `startManagedAnonProxy`. All have sensible defaults; tests inject
1852
+ * the deps to avoid real downloads/spawns.
1853
+ */
1854
+ interface StartManagedAnonProxyOptions {
1855
+ /** Cache dir for the binary + torrc + data. Default: {@link defaultCacheDir}. */
1856
+ cacheDir?: string;
1857
+ /** Loopback SOCKS5 port. Default 9050. */
1858
+ socksPort?: number;
1859
+ /** Bootstrap deadline in ms. Default 180_000. */
1860
+ bootstrapTimeoutMs?: number;
1861
+ /** Logger. Default: no-op. */
1862
+ log?: (msg: string) => void;
1863
+ /** os.platform() override (tests). */
1864
+ platform?: string;
1865
+ /** os.arch() override (tests). */
1866
+ arch?: string;
1867
+ }
1868
+ /**
1869
+ * Downloads (if needed) + spawns a managed `anon` daemon and waits for its SOCKS5
1870
+ * port to bind. Returns a {@link ManagedAnonProxy} whose `socksProxy` is ready for
1871
+ * `transport: { type: 'socks5', socksProxy }`.
1872
+ *
1873
+ * @throws If the platform is unsupported, the checksum fails, or anon never binds.
1874
+ */
1875
+ declare function startManagedAnonProxy(options?: StartManagedAnonProxyOptions): Promise<ManagedAnonProxy>;
1876
+
1133
1877
  /**
1134
1878
  * Settlement info produced by buildSettlementInfo().
1135
1879
  * Extends the core SettlementConfig shape with ilpAddress for client use.
@@ -1141,6 +1885,27 @@ interface ClientSettlementInfo {
1141
1885
  preferredTokens?: Record<string, string>;
1142
1886
  tokenNetworks?: Record<string, string>;
1143
1887
  }
1888
+ /**
1889
+ * Applies named-network preset defaults to a client config.
1890
+ *
1891
+ * When `config.network` is set and != `'custom'`, the settlement-related
1892
+ * fields are defaulted from the shared core presets (`resolveClientNetwork`):
1893
+ * RPC/GraphQL URLs, supported chain identifiers, preferred tokens, EVM
1894
+ * TokenNetwork addresses, and the Solana/Mina channel params. Any explicit
1895
+ * per-chain field on `config` OVERRIDES the preset (explicit always wins).
1896
+ *
1897
+ * `'custom'` and the unset case both pass `config` through untouched, keeping
1898
+ * the fully-manual path and full backward compatibility.
1899
+ *
1900
+ * @returns A shallow copy with preset defaults merged in (never mutates input).
1901
+ */
1902
+ declare function applyNetworkPresets(config: ToonClientConfig): ToonClientConfig;
1903
+ /**
1904
+ * Returns per-chain settlement readiness for the configured `network` tier,
1905
+ * mirroring the townhouse node's status. Returns `undefined` when `network` is
1906
+ * unset or `'custom'` (no preset tier to report on).
1907
+ */
1908
+ declare function getNetworkStatus(config: ToonClientConfig): NetworkFamilyStatus | undefined;
1144
1909
  /**
1145
1910
  * Validates ToonClient configuration.
1146
1911
  *
@@ -1153,10 +1918,30 @@ declare function validateConfig(config: ToonClientConfig): void;
1153
1918
  * The resolved config type after defaults are applied.
1154
1919
  * secretKey is guaranteed to be present (auto-generated if omitted).
1155
1920
  */
1156
- type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'evmPrivateKey' | 'supportedChains' | 'settlementAddresses' | 'preferredTokens' | 'tokenNetworks' | 'btpUrl' | 'btpAuthToken' | 'btpPeerId' | 'chainRpcUrls' | 'initialDeposit' | 'settlementTimeout' | 'channelStorePath' | 'knownPeers' | 'destinationAddress'>> & {
1921
+ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'mnemonic' | 'mnemonicAccountIndex' | 'evmPrivateKey' | 'network' | 'supportedChains' | 'settlementAddresses' | 'preferredTokens' | 'tokenNetworks' | 'btpUrl' | 'btpAuthToken' | 'btpPeerId' | 'chainRpcUrls' | 'initialDeposit' | 'settlementTimeout' | 'solanaChannel' | 'minaChannel' | 'channelStorePath' | 'knownPeers' | 'destinationAddress' | 'transport' | 'managedAnonProxy' | 'managedAnonSocksPort'>> & {
1157
1922
  connector?: unknown;
1158
1923
  /** Always present after applyDefaults() — derived from secretKey if not explicitly provided */
1159
1924
  evmPrivateKey: string | Uint8Array;
1925
+ /**
1926
+ * BIP-39 phrase retained so `ToonClient.start()` can derive the Solana/Mina
1927
+ * keys asynchronously and register the corresponding signers. The Nostr/EVM
1928
+ * keys are already resolved synchronously into `secretKey`/`evmPrivateKey`.
1929
+ */
1930
+ mnemonic?: string;
1931
+ /**
1932
+ * BIP-44 account index for mnemonic-based derivation (defaults to 0).
1933
+ * Retained so `ToonClient.start()` derives the Solana/Mina signers at the
1934
+ * same index as the synchronously-resolved Nostr/EVM keys.
1935
+ */
1936
+ mnemonicAccountIndex?: number;
1937
+ /** Transport privacy config (optional — defaults to direct). */
1938
+ transport?: ClientTransportConfig;
1939
+ /** Named network tier, retained for `getNetworkStatus()`. */
1940
+ network?: ToonClientConfig['network'];
1941
+ /** Self-managed `anon` SOCKS5h proxy opt-out (default auto). */
1942
+ managedAnonProxy?: boolean;
1943
+ /** Loopback SOCKS port for the managed `anon` daemon (default 9050). */
1944
+ managedAnonSocksPort?: number;
1160
1945
  supportedChains?: string[];
1161
1946
  settlementAddresses?: Record<string, string>;
1162
1947
  preferredTokens?: Record<string, string>;
@@ -1167,6 +1952,8 @@ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'evmPrivateK
1167
1952
  chainRpcUrls?: Record<string, string>;
1168
1953
  initialDeposit?: string;
1169
1954
  settlementTimeout?: number;
1955
+ solanaChannel?: ToonClientConfig['solanaChannel'];
1956
+ minaChannel?: ToonClientConfig['minaChannel'];
1170
1957
  channelStorePath?: string;
1171
1958
  knownPeers?: {
1172
1959
  pubkey: string;
@@ -1180,12 +1967,509 @@ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'evmPrivateK
1180
1967
  * Auto-generates a Nostr keypair when secretKey is omitted.
1181
1968
  * Derives btpUrl from connectorUrl when not provided.
1182
1969
  */
1183
- declare function applyDefaults(config: ToonClientConfig): ResolvedConfig;
1970
+ declare function applyDefaults(rawConfig: ToonClientConfig): ResolvedConfig;
1184
1971
  /**
1185
1972
  * Builds SettlementConfig from client config.
1186
1973
  * Returns undefined if no settlement-related config is present.
1187
1974
  */
1188
- declare function buildSettlementInfo(config: ToonClientConfig): ClientSettlementInfo | undefined;
1975
+ declare function buildSettlementInfo(rawConfig: ToonClientConfig): ClientSettlementInfo | undefined;
1976
+
1977
+ /**
1978
+ * Pet DVM Client-Side Types
1979
+ *
1980
+ * Locally defined types for pet DVM interaction utilities.
1981
+ * These mirror server-side types but do NOT import from @toon-protocol/pet-dvm,
1982
+ * @toon-protocol/pet-circuit, or @toon-protocol/memvid-node.
1983
+ *
1984
+ * @module pet/types
1985
+ */
1986
+ /** Plain-number stat values (all clamped to [1, 100]) */
1987
+ interface StatValues {
1988
+ hunger: number;
1989
+ happiness: number;
1990
+ health: number;
1991
+ hygiene: number;
1992
+ energy: number;
1993
+ }
1994
+ /** Metadata for a pet DVM provider discovered via Kind 10035 events */
1995
+ interface PetDvmProvider {
1996
+ /** ILP address of the provider's connector */
1997
+ ilpAddress: string;
1998
+ /** Per-interaction cost from skill.pricing['5900'] */
1999
+ pricing: string;
2000
+ /** Provider's Nostr pubkey (cryptographically bound from event.pubkey) */
2001
+ pubkey: string;
2002
+ /** Feature list from skill.features */
2003
+ features: string[];
2004
+ }
2005
+ /** Parameters for building a Kind 5900 pet interaction request */
2006
+ interface PetInteractionRequestParams {
2007
+ /** Blobbi identifier (non-empty string) */
2008
+ blobbiId: string;
2009
+ /** Action type (0-10, maps to Feed/Play/Clean/etc.) */
2010
+ actionType: number;
2011
+ /** Item identifier (>= 0) */
2012
+ itemId: number;
2013
+ /** Token cost for this interaction (>= 0) */
2014
+ tokenCost: number;
2015
+ /** Whether the pet is currently sleeping */
2016
+ isSleeping: boolean;
2017
+ }
2018
+ /** Unsigned Nostr event structure compatible with nostr-tools finalizeEvent */
2019
+ interface UnsignedNostrEvent {
2020
+ kind: number;
2021
+ created_at: number;
2022
+ tags: string[][];
2023
+ content: string;
2024
+ }
2025
+ /** Parsed result data from Kind 6900 DVM response (base64 JSON in IlpSendResult.data) */
2026
+ interface PetInteractionResultData {
2027
+ /** Current stat values */
2028
+ stats: StatValues;
2029
+ /** Current stage (0=Egg, 1=Baby, 2=Adult) */
2030
+ stage: number;
2031
+ /** Current evolution cycle (>= 0) */
2032
+ cycle: number;
2033
+ /** Unix timestamp of last interaction */
2034
+ lastInteraction: number;
2035
+ /** 64-char hex BLAKE3 hash of brain state */
2036
+ brainHash: string;
2037
+ /** Per-action-type cooldown timestamps */
2038
+ cooldownTimestamps: number[];
2039
+ }
2040
+ /** Stat snapshot used in interaction result content */
2041
+ interface InteractionResultContent {
2042
+ priorStats: StatValues;
2043
+ decayedStats: StatValues;
2044
+ finalStats: StatValues;
2045
+ cycle: number;
2046
+ stage: number;
2047
+ tokenCost: number;
2048
+ }
2049
+ /** Proof status of a Kind 14919 event */
2050
+ type ProofStatus = 'optimistic' | 'proven';
2051
+ /** Parameters for building a Kind 30402 pet-for-sale classified listing */
2052
+ interface PetListingParams {
2053
+ /** Blobbi identifier (non-empty string) — used as the 'd' tag */
2054
+ blobbiId: string;
2055
+ /** Asking price in USDC (> 0) */
2056
+ askPriceUsdc: number;
2057
+ /** 64-char hex lifecycleHash from on-chain PetZkApp state */
2058
+ lifecycleHash: string;
2059
+ /** Cumulative PET tokens spent (numeric string, >= "0") */
2060
+ totalSpent: string;
2061
+ /** Current stage: 0=Egg, 1=Baby, 2=Adult */
2062
+ stage: number;
2063
+ /** Current pet stats */
2064
+ stats: StatValues;
2065
+ /** Seller's Nostr pubkey (64-char hex) */
2066
+ sellerPubkey: string;
2067
+ /** Preferred relay URL for event relay routing */
2068
+ relayUrl: string;
2069
+ /** Listing expiry as unix timestamp */
2070
+ expiresAt: number;
2071
+ }
2072
+ /** A parsed pet-for-sale listing (extends PetListingParams with event metadata) */
2073
+ interface PetListing extends PetListingParams {
2074
+ /** Nostr event ID of the kind:30402 listing event */
2075
+ eventId: string;
2076
+ /** Unix timestamp when the listing event was created */
2077
+ createdAt: number;
2078
+ }
2079
+ /** Filter options for filterPetListings() */
2080
+ interface PetListingFilterOptions {
2081
+ /** Only include listings for pets at or above this stage */
2082
+ minStage?: number;
2083
+ /** Only include listings at or below this USDC price */
2084
+ maxAskPriceUsdc?: number;
2085
+ /** Only include listings where totalSpent >= this value (numeric string comparison) */
2086
+ minTotalSpent?: string;
2087
+ /** Only include listings from this seller pubkey */
2088
+ sellerPubkey?: string;
2089
+ }
2090
+ /** Parameters for building a Kind 5900 pet purchase request (transfer-ownership) */
2091
+ interface PetPurchaseRequestParams {
2092
+ /** Blobbi identifier being purchased */
2093
+ blobbiId: string;
2094
+ /** Nostr event ID of the kind:30402 listing being purchased */
2095
+ listingEventId: string;
2096
+ /** Buyer's Nostr pubkey (64-char hex) */
2097
+ buyerPubkey: string;
2098
+ /** Token cost for the purchase (>= 0) */
2099
+ tokenCost: number;
2100
+ /** Seller's Nostr pubkey — ILP payment routed to this pubkey (64-char hex) */
2101
+ sellerPubkey: string;
2102
+ }
2103
+ /** Parsed data from a Kind 14919 pet interaction event */
2104
+ interface PetInteractionEventData {
2105
+ /** Blobbi identifier from 'd' tag */
2106
+ blobbiId: string;
2107
+ /** Action type from 'action' tag */
2108
+ actionType: number;
2109
+ /** Item identifier from 'item' tag */
2110
+ itemId: number;
2111
+ /** Token cost from 'cost' tag */
2112
+ tokenCost: number;
2113
+ /** Evolution cycle from 'cycle' tag */
2114
+ cycle: number;
2115
+ /** Stage from 'stage' tag */
2116
+ stage: number;
2117
+ /** Brain hash from 'brain_hash' tag */
2118
+ brainHash: string;
2119
+ /** Proof status: 'optimistic' (no proof tag) or 'proven' (has proof + mina_tx tags) */
2120
+ proofStatus: ProofStatus;
2121
+ /** Parsed content JSON (stats before/after) */
2122
+ content: InteractionResultContent | null;
2123
+ /** Base64 proof data (only present when proven) */
2124
+ proof?: string;
2125
+ /** Mina transaction hash (only present when proven) */
2126
+ minaTx?: string;
2127
+ }
2128
+
2129
+ /**
2130
+ * Pet DVM Provider Discovery
2131
+ *
2132
+ * Filters Kind 10035 service discovery events to find providers that
2133
+ * support Pet DVM interactions (Kind 5900).
2134
+ *
2135
+ * @module pet/filterPetDvmProviders
2136
+ */
2137
+
2138
+ /**
2139
+ * Minimal Nostr event shape needed for filtering.
2140
+ * Using a local interface to avoid importing nostr-tools types.
2141
+ */
2142
+ interface NostrEventLike$3 {
2143
+ kind: number;
2144
+ pubkey: string;
2145
+ content: string;
2146
+ tags: string[][];
2147
+ id: string;
2148
+ sig: string;
2149
+ created_at: number;
2150
+ }
2151
+ /**
2152
+ * Filter Kind 10035 service discovery events to find pet DVM providers.
2153
+ *
2154
+ * Accepts raw NostrEvent[] and internally parses content via parseServiceDiscovery.
2155
+ * Filters events where skill.kinds includes 5900 (PET_INTERACTION_REQUEST_KIND).
2156
+ * Returns provider metadata sorted by price ascending (cheapest first).
2157
+ *
2158
+ * Handles missing/malformed skill descriptors gracefully (returns empty array, no throw).
2159
+ *
2160
+ * @param events - Array of raw Nostr events (kind:10035)
2161
+ * @returns Array of PetDvmProvider metadata, sorted by price ascending
2162
+ */
2163
+ declare function filterPetDvmProviders(events: NostrEventLike$3[]): PetDvmProvider[];
2164
+
2165
+ /**
2166
+ * Pet Interaction Request Builder (Kind 5900)
2167
+ *
2168
+ * Builds unsigned Kind 5900 Nostr events for pet DVM interaction requests.
2169
+ * Compatible with nostr-tools/pure finalizeEvent for signing.
2170
+ *
2171
+ * @module pet/buildPetInteractionRequest
2172
+ */
2173
+
2174
+ /**
2175
+ * Build an unsigned Kind 5900 pet interaction request event.
2176
+ *
2177
+ * All tag values are stringified per Nostr protocol convention.
2178
+ * The returned event is compatible with nostr-tools `finalizeEvent`.
2179
+ *
2180
+ * @param params - Typed interaction parameters
2181
+ * @returns Unsigned Nostr event ready for signing
2182
+ * @throws ValidationError for invalid input
2183
+ */
2184
+ declare function buildPetInteractionRequest(params: PetInteractionRequestParams): UnsignedNostrEvent;
2185
+
2186
+ /**
2187
+ * Pet Interaction Result Parser (Kind 6900)
2188
+ *
2189
+ * Decodes base64-encoded JSON from IlpSendResult.data field.
2190
+ * Uses browser-safe atob() -- NOT Node.js Buffer -- for ditto React SPA compatibility.
2191
+ *
2192
+ * @module pet/parsePetInteractionResult
2193
+ */
2194
+
2195
+ /**
2196
+ * Parse base64-encoded JSON result data from a Kind 6900 DVM response.
2197
+ *
2198
+ * Uses atob() for browser compatibility (ditto React SPA).
2199
+ * Returns null for malformed/missing data (no throw).
2200
+ *
2201
+ * Validates:
2202
+ * - brainHash is 64-char hex
2203
+ * - stats has all 5 fields
2204
+ * - cycle >= 0
2205
+ * - stage 0-2
2206
+ *
2207
+ * @param data - Base64-encoded JSON string from IlpSendResult.data
2208
+ * @returns Parsed PetInteractionResultData or null if invalid
2209
+ */
2210
+ declare function parsePetInteractionResult(data: string): PetInteractionResultData | null;
2211
+
2212
+ /**
2213
+ * Pet Interaction Event Parser (Kind 14919)
2214
+ *
2215
+ * Parses Kind 14919 optimistic/proven pet interaction events.
2216
+ * Detects proof status from presence of 'proof' + 'mina_tx' tags.
2217
+ *
2218
+ * @module pet/parsePetInteractionEvent
2219
+ */
2220
+
2221
+ /**
2222
+ * Minimal Nostr event shape needed for parsing.
2223
+ */
2224
+ interface NostrEventLike$2 {
2225
+ kind: number;
2226
+ pubkey: string;
2227
+ content: string;
2228
+ tags: string[][];
2229
+ id: string;
2230
+ sig: string;
2231
+ created_at: number;
2232
+ }
2233
+ /**
2234
+ * Parse a Kind 14919 pet interaction event.
2235
+ *
2236
+ * Extracts all tag values and detects proof status:
2237
+ * - 'optimistic': no 'proof' tag
2238
+ * - 'proven': has 'proof' + 'mina_tx' tags
2239
+ *
2240
+ * Returns null if required tags are missing.
2241
+ *
2242
+ * @param event - A Nostr event (Kind 14919)
2243
+ * @returns Parsed PetInteractionEventData or null if malformed
2244
+ */
2245
+ declare function parsePetInteractionEvent(event: NostrEventLike$2): PetInteractionEventData | null;
2246
+
2247
+ /**
2248
+ * Pet Listing Event Builder (Kind 30402)
2249
+ *
2250
+ * Builds unsigned Kind 30402 (NIP-99 classified listing) Nostr events
2251
+ * for pet-for-sale marketplace listings. Every listing includes a
2252
+ * verified biography attachment (lifecycleHash + totalSpent) so buyers
2253
+ * can verify the listing against on-chain PetZkApp state.
2254
+ *
2255
+ * Browser-compatible — no Node.js-only imports.
2256
+ *
2257
+ * @module pet/buildPetListingEvent
2258
+ */
2259
+
2260
+ /**
2261
+ * Build an unsigned Kind 30402 pet-for-sale classified listing event.
2262
+ *
2263
+ * The listing uses the NIP-99 classified listing format with TOON-specific
2264
+ * extension tags for verified biography (lifecycleHash, totalSpent).
2265
+ * The `d` tag is set to `blobbiId` for stable parameterized replaceability —
2266
+ * republishing with the same `d` tag updates the listing on relays.
2267
+ *
2268
+ * The returned event is compatible with nostr-tools `finalizeEvent`.
2269
+ *
2270
+ * @param params - Typed listing parameters
2271
+ * @returns Unsigned Nostr event ready for signing and publishing
2272
+ */
2273
+ declare function buildPetListingEvent(params: PetListingParams): UnsignedNostrEvent;
2274
+
2275
+ /**
2276
+ * Pet Listing Parser (Kind 30402)
2277
+ *
2278
+ * Parses Kind 30402 (NIP-99 classified listing) Nostr events into
2279
+ * typed PetListing objects. Returns null for invalid or malformed events.
2280
+ *
2281
+ * Browser-compatible — no Node.js-only imports.
2282
+ *
2283
+ * @module pet/parsePetListing
2284
+ */
2285
+
2286
+ /** Minimal Nostr event shape required for parsing */
2287
+ interface NostrEventLike$1 {
2288
+ id: string;
2289
+ kind: number;
2290
+ pubkey: string;
2291
+ tags: string[][];
2292
+ content: string;
2293
+ created_at: number;
2294
+ }
2295
+ /**
2296
+ * Parse a Kind 30402 pet classified listing event into a PetListing.
2297
+ *
2298
+ * Validation rules:
2299
+ * - event.kind must be 30402
2300
+ * - 'd' tag must be present and non-empty
2301
+ * - 'price' tag must be present with a valid positive numeric first element
2302
+ * - 'lifecycle_hash' tag must be a 64-char hex string
2303
+ * - 'total_spent' tag must be a valid non-negative numeric string
2304
+ * - 'stage' tag must be present
2305
+ *
2306
+ * Stats are parsed from content JSON; unparseable content falls back to DEFAULT_STATS.
2307
+ *
2308
+ * @param event - A Nostr event (expected Kind 30402)
2309
+ * @returns Parsed PetListing or null if invalid
2310
+ */
2311
+ declare function parsePetListing(event: NostrEventLike$1): PetListing | null;
2312
+
2313
+ /**
2314
+ * Pet Listing Discovery Filter
2315
+ *
2316
+ * Filters and sorts Kind 30402 pet marketplace listing events into
2317
+ * typed PetListing objects. Handles expiry, stage, price, biography
2318
+ * value, and seller filtering. Results sorted by totalSpent descending
2319
+ * (highest biography value first) to surface the most battle-hardened pets.
2320
+ *
2321
+ * Browser-compatible — no Node.js-only imports.
2322
+ *
2323
+ * @module pet/filterPetListings
2324
+ */
2325
+
2326
+ /** Minimal Nostr event shape accepted by the filter */
2327
+ interface NostrEventLike {
2328
+ id: string;
2329
+ kind: number;
2330
+ pubkey: string;
2331
+ tags: string[][];
2332
+ content: string;
2333
+ created_at: number;
2334
+ }
2335
+ /**
2336
+ * Filter and sort Kind 30402 pet marketplace listing events.
2337
+ *
2338
+ * Parsing is done via parsePetListing — invalid events are silently dropped.
2339
+ * Expired listings (expiration tag < current unix time) are excluded.
2340
+ * Options allow additional filtering by stage, price, biography value, and seller.
2341
+ * Results are sorted by totalSpent descending (highest biography value first).
2342
+ *
2343
+ * @param events - Array of raw Nostr events to filter
2344
+ * @param options - Optional filter criteria
2345
+ * @returns Filtered and sorted array of PetListing objects
2346
+ */
2347
+ declare function filterPetListings(events: NostrEventLike[], options?: PetListingFilterOptions): PetListing[];
2348
+
2349
+ /**
2350
+ * Pet Purchase Request Builder (Kind 5900, action type 9)
2351
+ *
2352
+ * Builds unsigned Kind 5900 Nostr events for pet transfer-ownership
2353
+ * purchase requests. Action type 9 is a reserved slot in the pet DVM
2354
+ * protocol — this event signals purchase intent and routes ILP payment
2355
+ * to the seller. The actual Mina on-chain ownership transfer (PetZkApp
2356
+ * .transferOperator) is handled by downstream stories.
2357
+ *
2358
+ * Browser-compatible — no Node.js-only imports.
2359
+ *
2360
+ * @module pet/buildPetPurchaseRequest
2361
+ */
2362
+
2363
+ /**
2364
+ * Build an unsigned Kind 5900 pet purchase request event.
2365
+ *
2366
+ * Reuses the existing pet interaction event kind (5900) with action type 9
2367
+ * (transfer-ownership). The `listing` tag references the kind:30402 listing
2368
+ * event being purchased. The `p` tag routes ILP payment to the seller.
2369
+ *
2370
+ * The returned event is compatible with nostr-tools `finalizeEvent`.
2371
+ *
2372
+ * @param params - Typed purchase request parameters
2373
+ * @returns Unsigned Nostr event ready for signing and publishing
2374
+ */
2375
+ declare function buildPetPurchaseRequest(params: PetPurchaseRequestParams): UnsignedNostrEvent;
2376
+
2377
+ /**
2378
+ * Client-side helper for kind:5094 Arweave blob storage DVM requests.
2379
+ *
2380
+ * This composes the three steps a caller previously had to wire by hand:
2381
+ * 1. Build the kind:5094 event via `buildBlobStorageRequest()` (@toon-protocol/core).
2382
+ * 2. Publish it to the DVM destination over ILP via `ToonClient.publishEvent()`
2383
+ * (reusing the existing claim / channel plumbing).
2384
+ * 3. Decode the FULFILL `data` field into the Arweave transaction ID.
2385
+ *
2386
+ * ## FULFILL data contract
2387
+ *
2388
+ * For a successful single-packet (non-chunked) blob upload, the DVM provider
2389
+ * returns the Arweave transaction ID as a **UTF-8 string, base64-encoded** in
2390
+ * the ILP FULFILL `data` field (the connector validates that FULFILL data is
2391
+ * base64). An Arweave tx ID is a 43-character base64url string (32 raw bytes).
2392
+ *
2393
+ * So the decode is:
2394
+ * `txId = utf8(base64decode(result.data))`
2395
+ *
2396
+ * See `packages/sdk/src/arweave/arweave-dvm-handler.ts` for the server side and
2397
+ * `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts` for the reference
2398
+ * end-to-end flow this helper mirrors.
2399
+ *
2400
+ * Chunked uploads (multi-packet, via `uploadId` / `chunkIndex` / `totalChunks`
2401
+ * params) are intentionally out of scope for this single-packet helper — the
2402
+ * provider returns `ack:<n>` for intermediate chunks and the tx ID only on the
2403
+ * final chunk. Callers needing chunking should drive `publishEvent()` directly
2404
+ * (see the chunked case in the reference E2E test). Tracked as a follow-up.
2405
+ */
2406
+
2407
+ /**
2408
+ * Parameters for {@link requestBlobStorage}.
2409
+ */
2410
+ interface RequestBlobStorageParams {
2411
+ /** The raw blob data to store on Arweave. */
2412
+ blobData: Uint8Array;
2413
+ /**
2414
+ * MIME type of the blob. Defaults to `'application/octet-stream'`
2415
+ * (matching `buildBlobStorageRequest`).
2416
+ */
2417
+ contentType?: string;
2418
+ /**
2419
+ * Bid amount in USDC micro-units, as a string (declared in the event's
2420
+ * `bid` tag). Defaults to the stringified `ilpAmount` when omitted.
2421
+ *
2422
+ * At least one of `bid` / `ilpAmount` should be provided so the declared
2423
+ * bid and the paid ILP amount agree.
2424
+ */
2425
+ bid?: string;
2426
+ /**
2427
+ * ILP destination address of the DVM that performs the upload
2428
+ * (e.g. `'g.toon.peer1'`). Falls back to the client's configured
2429
+ * `destinationAddress` when omitted.
2430
+ */
2431
+ destination?: string;
2432
+ /**
2433
+ * Pre-signed balance proof claim for this packet. When omitted, the
2434
+ * client's channel manager auto-opens a channel and auto-signs a claim
2435
+ * (same lazy-channel behavior as `publishEvent`).
2436
+ */
2437
+ claim?: SignedBalanceProof;
2438
+ /**
2439
+ * Explicit ILP payment amount (bigint micro-units). When omitted,
2440
+ * `publishEvent` computes it from the encoded event size. When `bid`
2441
+ * is omitted, this value is stringified to populate the event's bid tag.
2442
+ */
2443
+ ilpAmount?: bigint;
2444
+ }
2445
+ /**
2446
+ * Typed result of {@link requestBlobStorage}.
2447
+ */
2448
+ interface RequestBlobStorageResult {
2449
+ /** Whether the upload succeeded and a tx ID was decoded. */
2450
+ success: boolean;
2451
+ /** The Arweave transaction ID (43-char base64url) when `success` is true. */
2452
+ txId?: string;
2453
+ /** The id of the kind:5094 event that was published. */
2454
+ eventId?: string;
2455
+ /** Error message when `success` is false. */
2456
+ error?: string;
2457
+ }
2458
+ /**
2459
+ * Requests permanent Arweave blob storage from a DVM in a single ILP packet.
2460
+ *
2461
+ * Mirrors the single-packet flow in
2462
+ * `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts`: builds a signed
2463
+ * kind:5094 event, publishes it through the supplied {@link ToonClient}
2464
+ * (reusing its claim/channel plumbing), and decodes the FULFILL `data` field
2465
+ * into an Arweave transaction ID.
2466
+ *
2467
+ * @param client - A started `ToonClient` (call `client.start()` first).
2468
+ * @param secretKey - The Nostr secret key used to sign the kind:5094 event.
2469
+ * @param params - Blob, content type, bid, destination, and payment options.
2470
+ * @returns `{ success, txId?, eventId?, error? }`.
2471
+ */
2472
+ declare function requestBlobStorage(client: ToonClient, secretKey: Uint8Array, params: RequestBlobStorageParams): Promise<RequestBlobStorageResult>;
1189
2473
 
1190
2474
  /**
1191
2475
  * Full multi-chain identity derived from a single BIP-39 mnemonic.
@@ -1384,16 +2668,38 @@ declare function generateMnemonic(): string;
1384
2668
  * Validate a BIP-39 mnemonic phrase.
1385
2669
  */
1386
2670
  declare function validateMnemonic(mnemonic: string): boolean;
2671
+ /**
2672
+ * Synchronously derive ONLY the Nostr secp256k1 key (NIP-06) from a mnemonic.
2673
+ *
2674
+ * The EVM key shares this same secp256k1 key. Solana (Ed25519) and Mina
2675
+ * (Pallas) require async dynamic imports — use {@link deriveFullIdentity} for
2676
+ * those. This sync subset exists so `ToonClient`'s synchronous constructor can
2677
+ * resolve the Nostr/EVM identity from a `mnemonic` config field without an
2678
+ * async factory; the client derives Solana/Mina lazily in `start()`.
2679
+ */
2680
+ declare function deriveNostrKeyFromMnemonic(mnemonic: string, accountIndex?: number): {
2681
+ secretKey: Uint8Array;
2682
+ pubkey: string;
2683
+ };
1387
2684
  /**
1388
2685
  * Derive a full multi-chain ToonIdentity from a BIP-39 mnemonic.
1389
2686
  *
2687
+ * All four chains vary by `accountIndex` (default 0), matching the SDK's
2688
+ * {@link https://www.npmjs.com/package/@toon-protocol/sdk `fromMnemonicFull`}
2689
+ * path-per-index scheme so a non-zero index produces the SAME addresses as
2690
+ * `fromMnemonicFull(mnemonic, { accountIndex })`. Index 0 is unchanged from the
2691
+ * historical fixed paths (back-compat).
2692
+ *
1390
2693
  * Chains derived:
1391
- * - Nostr (secp256k1): m/44'/1237'/0'/0/0
2694
+ * - Nostr (secp256k1): m/44'/1237'/0'/0/{accountIndex}
1392
2695
  * - EVM (secp256k1): same key as Nostr
1393
- * - Solana (Ed25519): m/44'/501'/0'/0' (SLIP-0010)
1394
- * - Mina (Pallas): m/44'/12586'/0'/0/0
2696
+ * - Solana (Ed25519): m/44'/501'/{accountIndex}'/0' (SLIP-0010)
2697
+ * - Mina (Pallas): m/44'/12586'/{accountIndex}'/0/0
2698
+ *
2699
+ * @param mnemonic - A valid BIP-39 mnemonic (12 or 24 words).
2700
+ * @param accountIndex - BIP-44 account index (default 0).
1395
2701
  */
1396
- declare function deriveFullIdentity(mnemonic: string): Promise<ToonIdentity>;
2702
+ declare function deriveFullIdentity(mnemonic: string, accountIndex?: number): Promise<ToonIdentity>;
1397
2703
  /**
1398
2704
  * Derive a partial identity from an nsec (Nostr-only private key).
1399
2705
  * Nostr + EVM share the same secp256k1 key.
@@ -1441,4 +2747,77 @@ declare function parseBackupPayload(content: string): VaultData;
1441
2747
  */
1442
2748
  declare function isPrfSupported(): boolean;
1443
2749
 
1444
- export { type BackupPayload, type BalanceProofParams, BtpRuntimeClient, type BtpRuntimeClientConfig, ChannelManager, ConnectorError, type EVMClaimMessage, EvmSigner, HttpConnectorAdmin, type HttpConnectorAdminConfig, HttpRuntimeClient, type HttpRuntimeClientConfig, KeyManager, type KeyManagerConfig, NetworkError, OnChainChannelClient, type OnChainChannelClientConfig, type PasskeyInfo, type PublishEventResult, type RetryOptions, type SignedBalanceProof, ToonClient, type ToonClientConfig, ToonClientError, type ToonIdentity, type ToonSigners, type ToonStartResult, ValidationError, type VaultData, applyDefaults, buildBackupEvent, buildBackupFilter, buildSettlementInfo, deriveFromNsec, deriveFullIdentity, generateMnemonic, generateRandomIdentity, isPrfSupported, parseBackupPayload, validateConfig, validateMnemonic, withRetry };
2750
+ /**
2751
+ * Node-only encrypted mnemonic keystore for @toon-protocol/client.
2752
+ *
2753
+ * Mirrors the Townhouse node wallet crypto (`packages/townhouse/src/wallet/
2754
+ * crypto.ts`): a BIP-39 mnemonic is encrypted at rest with scrypt (KDF) +
2755
+ * AES-256-GCM (authenticated encryption), serialized as JSON, and written to
2756
+ * disk with mode 0o600. Decryption requires the operator password; a wrong
2757
+ * password fails the GCM auth-tag verification and throws.
2758
+ *
2759
+ * This is the Node-side counterpart to the browser Passkey/IndexedDB
2760
+ * `KeyManager`/`KeyVault` flow — it does NOT touch those. It is guarded against
2761
+ * browser bundling: every entry point throws if `node:crypto`/`node:fs` are not
2762
+ * available (e.g. when accidentally imported in a browser bundle).
2763
+ *
2764
+ * @module
2765
+ */
2766
+ /**
2767
+ * Encrypted keystore file format (JSON, all binary fields base64-encoded).
2768
+ * Wire-compatible with Townhouse's `EncryptedWallet`.
2769
+ */
2770
+ interface EncryptedKeystore {
2771
+ /** scrypt salt (base64). */
2772
+ salt: string;
2773
+ /** AES-GCM initialization vector (base64). */
2774
+ iv: string;
2775
+ /** AES-256-GCM ciphertext (base64). */
2776
+ ciphertext: string;
2777
+ /** AES-GCM authentication tag (base64). */
2778
+ tag: string;
2779
+ /** Envelope version for forward-compat (currently 1). */
2780
+ version?: number;
2781
+ }
2782
+ /**
2783
+ * Encrypt a mnemonic with a password using scrypt + AES-256-GCM.
2784
+ * Returns the JSON-serializable encrypted envelope (does NOT write to disk).
2785
+ */
2786
+ declare function encryptMnemonic(mnemonic: string, password: string): EncryptedKeystore;
2787
+ /**
2788
+ * Decrypt an encrypted keystore envelope with a password.
2789
+ * Throws on a wrong password (GCM auth-tag verification failure) or corruption.
2790
+ */
2791
+ declare function decryptMnemonic(encrypted: EncryptedKeystore, password: string): string;
2792
+ /**
2793
+ * Generate a fresh 12-word BIP-39 mnemonic, encrypt it under `password`, and
2794
+ * write the encrypted keystore to `path` with mode 0o600.
2795
+ *
2796
+ * Returns the mnemonic (for one-time display/backup) alongside the encrypted
2797
+ * envelope. The caller is responsible for displaying the mnemonic securely and
2798
+ * NOT persisting it in plaintext.
2799
+ */
2800
+ declare function generateKeystore(path: string, password: string): {
2801
+ mnemonic: string;
2802
+ keystore: EncryptedKeystore;
2803
+ };
2804
+ /**
2805
+ * Import an existing BIP-39 mnemonic (12 or 24 words), encrypt it under
2806
+ * `password`, and write the encrypted keystore to `path` with mode 0o600.
2807
+ *
2808
+ * Throws if the mnemonic is not a valid BIP-39 phrase (wrong checksum/word
2809
+ * count) before any file is written.
2810
+ */
2811
+ declare function importKeystore(path: string, mnemonic: string, password: string): EncryptedKeystore;
2812
+ /**
2813
+ * Load and decrypt a keystore file at `path` with `password`, returning the
2814
+ * plaintext mnemonic. Throws on a wrong password or corruption.
2815
+ */
2816
+ declare function loadKeystore(path: string, password: string): string;
2817
+ /**
2818
+ * Serialize and write an encrypted keystore to disk with mode 0o600
2819
+ * (owner read/write only), mirroring the Townhouse wallet file permissions.
2820
+ */
2821
+ declare function writeKeystoreFile(path: string, keystore: EncryptedKeystore): void;
2822
+
2823
+ export { ANON_ASSETS, ANON_VERSION, type AnonAsset, type BackupPayload, type BalanceProofParams, BtpRuntimeClient, type BtpRuntimeClientConfig, type ChainMetadata, type ChainSigner, ChannelManager, type ClaimMessage, type ClientTransportConfig, ConnectorError, type EVMClaimMessage, type EncryptedKeystore, EvmSigner, HS_HOSTNAME_MAX_LENGTH, HS_HOSTNAME_REGEX, HttpConnectorAdmin, type HttpConnectorAdminConfig, HttpRuntimeClient, type HttpRuntimeClientConfig, type InteractionResultContent, KeyManager, type KeyManagerConfig, type ManagedAnonProxy, type MinaClaimMessage, type MinaDepositReader, MinaSigner, type MinaSignerOptions, NetworkError, OnChainChannelClient, type OnChainChannelClientConfig, type PasskeyInfo, type PetDvmProvider, type PetInteractionEventData, type PetInteractionRequestParams, type PetInteractionResultData, type PetListing, type PetListingFilterOptions, type PetListingParams, type PetPurchaseRequestParams, type ProofStatus, type PublishEventResult, type RequestBlobStorageParams, type RequestBlobStorageResult, type RetryOptions, type SignedBalanceProof, type SolanaChannelClientOptions, type SolanaClaimMessage, SolanaSigner, type StartManagedAnonProxyOptions, type StatValues, ToonClient, type ToonClientConfig, ToonClientError, type ToonIdentity, type ToonSigners, type ToonStartResult, type UnsignedNostrEvent, ValidationError, type VaultData, applyDefaults, applyNetworkPresets, assertRoutableHsHostname, buildBackupEvent, buildBackupFilter, buildPetInteractionRequest, buildPetListingEvent, buildPetPurchaseRequest, buildSettlementInfo, decryptMnemonic, deriveFromNsec, deriveFullIdentity, deriveNostrKeyFromMnemonic, encryptMnemonic, filterPetDvmProviders, filterPetListings, generateKeystore, generateMnemonic, generateRandomIdentity, getNetworkStatus, importKeystore, isPrfSupported, isRoutableHsHostname, loadKeystore, parseBackupPayload, parsePetInteractionEvent, parsePetInteractionResult, parsePetListing, readMinaDepositTotal, requestBlobStorage, selectAnonAsset, startManagedAnonProxy, validateConfig, validateMnemonic, withRetry, writeKeystoreFile };