@toon-protocol/client 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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';
3
- import { NostrEvent } from 'nostr-tools/pure';
2
+ import { IlpPeerInfo, NetworkFamilyStatus, IlpSendResult, IlpClient, ConnectorAdminClient, ConnectorChannelClient, OpenChannelParams, OpenChannelResult, ChannelState } from '@toon-protocol/core';
3
+ import { NostrEvent, EventTemplate } 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 */
@@ -79,6 +199,27 @@ interface ToonClientConfig {
79
199
  btpAuthToken?: string;
80
200
  /** Peer ID for BTP connection (used in connector env var BTP_PEER_{ID}_SECRET) */
81
201
  btpPeerId?: string;
202
+ /**
203
+ * ILP-over-HTTP one-shot endpoint of the uplink connector (the `POST /ilp`
204
+ * URL, e.g. "http://localhost:3000/ilp"). When set, the client prefers the
205
+ * stateless {@link HttpIlpClient} transport for one-shot writes instead of
206
+ * opening a persistent BTP WebSocket.
207
+ *
208
+ * This mirrors the `httpEndpoint` field a peer advertises in discovery
209
+ * (`IlpPeerInfo`, toon PR #29). It is surfaced here as explicit config so the
210
+ * default runtime can opt into ILP-over-HTTP before connectors advertise the
211
+ * field on-wire. Leave unset to keep the existing BTP-only behavior (the safe
212
+ * default — no connector advertises an httpEndpoint until the connector + a
213
+ * core release with these fields ship).
214
+ */
215
+ connectorHttpEndpoint?: string;
216
+ /**
217
+ * Whether the uplink connector accepts a BTP `Upgrade` over its HTTP endpoint
218
+ * (mirrors `IlpPeerInfo.supportsUpgrade`, toon PR #29). Only consulted when
219
+ * `connectorHttpEndpoint` is set and a duplex session is required. Defaults to
220
+ * false (no upgrade) when omitted.
221
+ */
222
+ connectorSupportsUpgrade?: boolean;
82
223
  /**
83
224
  * ILP destination address for event publishing.
84
225
  * Defaults to the connector's local address (derived from connectorUrl host).
@@ -94,8 +235,66 @@ interface ToonClientConfig {
94
235
  initialDeposit?: string;
95
236
  /** Challenge period in seconds (default: 86400) */
96
237
  settlementTimeout?: number;
238
+ /**
239
+ * Solana payment-channel parameters for opening a REAL on-chain channel and
240
+ * signing a connector-format Solana balance proof.
241
+ *
242
+ * When present (and the client has a Solana signer — i.e. it was constructed
243
+ * from a `mnemonic`), `ToonClient.start()` wires these into the on-chain
244
+ * channel client so that negotiating a `solana:*` chain opens an on-chain
245
+ * channel at the connector-parity PDA and pays a Solana-denominated claim.
246
+ *
247
+ * The Ed25519 keypair is NOT carried here — it is derived from the same
248
+ * `mnemonic` that produces the Solana signer, so the channel-open key and the
249
+ * claim-signing key are guaranteed identical.
250
+ */
251
+ solanaChannel?: SolanaChannelClientOptions;
252
+ /**
253
+ * Mina payment-channel parameters (graphqlUrl + zkAppAddress). When present
254
+ * (and the client has a Mina signer — i.e. it was constructed from a
255
+ * `mnemonic` AND `mina-signer` is installed), `ToonClient.start()` wires these
256
+ * into the on-chain channel client so negotiating a `mina:*` chain routes
257
+ * through `openMinaChannel` and pays a Mina-denominated claim.
258
+ *
259
+ * The Mina private key is NOT carried here — it is derived from the same
260
+ * `mnemonic` that produces the Mina signer.
261
+ *
262
+ * NOTE (Phase-2 Stage-3 gate): see `MinaChannelClientOptions` — supplying this
263
+ * wires the negotiation path but the resulting claim does not yet satisfy
264
+ * connector 3.9.0's Mina claim contract, so a live loop is claim-validation
265
+ * gated (distinct from the connector #88 on-chain-settle gate).
266
+ */
267
+ minaChannel?: MinaChannelClientOptions;
97
268
  /** File path for persisting payment channel nonce/amount state across restarts */
98
269
  channelStorePath?: string;
270
+ /**
271
+ * Transport configuration for privacy-preserving connections.
272
+ *
273
+ * - `direct` (default): No privacy overlay, connect directly.
274
+ * - `socks5`: Route connections through a SOCKS5 proxy (Node.js only).
275
+ * Requires `socks5h://` scheme for DNS leak prevention.
276
+ * - `gateway`: Route connections through an ator gateway URL (browser-compatible).
277
+ * The gateway proxies through ator server-side.
278
+ */
279
+ transport?: ClientTransportConfig;
280
+ /**
281
+ * Self-managed `anon` (anyone-protocol / ATOR) SOCKS5h proxy (Node.js only).
282
+ *
283
+ * When the `btpUrl` host ends in `.anyone` and NO explicit proxy is configured
284
+ * (`transport.socksProxy` / `transport.type === 'gateway'`) and the
285
+ * `ANYONE_PROXY_URLS` env var is unset, the SDK auto-downloads + spawns its own
286
+ * `anon` daemon, waits for it to bootstrap + bind a loopback SOCKS5 port, and
287
+ * routes BTP/HTTP through it — ZERO manual proxy setup. `client.stop()` tears
288
+ * the daemon down.
289
+ *
290
+ * - `undefined` (default): auto — managed proxy starts for `.anyone` hosts.
291
+ * - `false`: opt out — never auto-start (you must supply your own proxy).
292
+ *
293
+ * Ignored in browser bundles (the node-only daemon module is never loaded).
294
+ */
295
+ managedAnonProxy?: boolean;
296
+ /** Loopback SOCKS port the managed `anon` daemon binds. Default 9050. */
297
+ managedAnonSocksPort?: number;
99
298
  /** Nostr relay URL for peer discovery. Default: 'ws://localhost:7100' */
100
299
  relayUrl?: string;
101
300
  /**
@@ -166,7 +365,149 @@ interface SignedBalanceProof extends BalanceProofParams {
166
365
  tokenNetworkAddress: string;
167
366
  /** ERC-20 token address (e.g. USDC) for self-describing claim verification */
168
367
  tokenAddress?: string;
368
+ /**
369
+ * Counterparty settlement address the balance proof is bound to.
370
+ *
371
+ * Required for Solana/Mina, where the canonical balance-proof message folds
372
+ * the recipient in (`balanceProofHashSolana` / `balanceProofFieldsMina`).
373
+ * Unused for the client's EVM path (EIP-712 `BalanceProof` has no recipient
374
+ * term). Carried here so it flows from signing through to `buildClaimMessage`.
375
+ */
376
+ recipient?: string;
377
+ /**
378
+ * Mina payment-channel claim fields (connector 3.9.0 `MinaClaimMessage`).
379
+ *
380
+ * Populated only by {@link MinaSigner}, which produces the connector's
381
+ * `Poseidon([balA,balB,salt])` balance commitment + a Pallas Schnorr `proof`
382
+ * over `[commitment, Field(nonce), Poseidon(zkApp.x)]` rather than reusing the
383
+ * generic `signature` field. Carried so they flow from signing through to
384
+ * `MinaSigner.buildClaimMessage`. Absent for EVM/Solana.
385
+ */
386
+ mina?: {
387
+ /** `Poseidon([balanceA, balanceB, salt]).toString()`. */
388
+ balanceCommitment: string;
389
+ /** base64-encoded JSON proof `{ commitment, signature: { r, s }, nonce, signerPublicKey }`. */
390
+ proof: string;
391
+ /** Decimal salt string. */
392
+ salt: string;
393
+ /** Mina token id (default `'MINA'`). */
394
+ tokenId: string;
395
+ };
169
396
  }
397
+ /**
398
+ * Transport configuration for privacy-preserving connections.
399
+ *
400
+ * Node.js: Use `socks5` to route WebSocket and HTTP through a SOCKS5 proxy.
401
+ * Browser: Use `gateway` to route through a server-side ator gateway.
402
+ */
403
+ type ClientTransportConfig = {
404
+ type: 'direct';
405
+ } | {
406
+ type: 'socks5';
407
+ /** SOCKS5 proxy URL. MUST use `socks5h://` scheme (DNS leak prevention). */
408
+ socksProxy: string;
409
+ } | {
410
+ type: 'gateway';
411
+ /** Gateway base URL that proxies connections through ator server-side. */
412
+ gatewayUrl: string;
413
+ };
414
+
415
+ /**
416
+ * Client-side helper for kind:5094 Arweave blob storage DVM requests.
417
+ *
418
+ * This composes the three steps a caller previously had to wire by hand:
419
+ * 1. Build the kind:5094 event via `buildBlobStorageRequest()` (@toon-protocol/core).
420
+ * 2. Publish it to the DVM destination over ILP via `ToonClient.publishEvent()`
421
+ * (reusing the existing claim / channel plumbing).
422
+ * 3. Decode the FULFILL `data` field into the Arweave transaction ID.
423
+ *
424
+ * ## FULFILL data contract
425
+ *
426
+ * For a successful single-packet (non-chunked) blob upload, the DVM provider
427
+ * returns the Arweave transaction ID as a **UTF-8 string, base64-encoded** in
428
+ * the ILP FULFILL `data` field (the connector validates that FULFILL data is
429
+ * base64). An Arweave tx ID is a 43-character base64url string (32 raw bytes).
430
+ *
431
+ * So the decode is:
432
+ * `txId = utf8(base64decode(result.data))`
433
+ *
434
+ * See `packages/sdk/src/arweave/arweave-dvm-handler.ts` for the server side and
435
+ * `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts` for the reference
436
+ * end-to-end flow this helper mirrors.
437
+ *
438
+ * Chunked uploads (multi-packet, via `uploadId` / `chunkIndex` / `totalChunks`
439
+ * params) are intentionally out of scope for this single-packet helper — the
440
+ * provider returns `ack:<n>` for intermediate chunks and the tx ID only on the
441
+ * final chunk. Callers needing chunking should drive `publishEvent()` directly
442
+ * (see the chunked case in the reference E2E test). Tracked as a follow-up.
443
+ */
444
+
445
+ /**
446
+ * Parameters for {@link requestBlobStorage}.
447
+ */
448
+ interface RequestBlobStorageParams {
449
+ /** The raw blob data to store on Arweave. */
450
+ blobData: Uint8Array;
451
+ /**
452
+ * MIME type of the blob. Defaults to `'application/octet-stream'`
453
+ * (matching `buildBlobStorageRequest`).
454
+ */
455
+ contentType?: string;
456
+ /**
457
+ * Bid amount in USDC micro-units, as a string (declared in the event's
458
+ * `bid` tag). Defaults to the stringified `ilpAmount` when omitted.
459
+ *
460
+ * At least one of `bid` / `ilpAmount` should be provided so the declared
461
+ * bid and the paid ILP amount agree.
462
+ */
463
+ bid?: string;
464
+ /**
465
+ * ILP destination address of the DVM that performs the upload
466
+ * (e.g. `'g.toon.peer1'`). Falls back to the client's configured
467
+ * `destinationAddress` when omitted.
468
+ */
469
+ destination?: string;
470
+ /**
471
+ * Pre-signed balance proof claim for this packet. When omitted, the
472
+ * client's channel manager auto-opens a channel and auto-signs a claim
473
+ * (same lazy-channel behavior as `publishEvent`).
474
+ */
475
+ claim?: SignedBalanceProof;
476
+ /**
477
+ * Explicit ILP payment amount (bigint micro-units). When omitted,
478
+ * `publishEvent` computes it from the encoded event size. When `bid`
479
+ * is omitted, this value is stringified to populate the event's bid tag.
480
+ */
481
+ ilpAmount?: bigint;
482
+ }
483
+ /**
484
+ * Typed result of {@link requestBlobStorage}.
485
+ */
486
+ interface RequestBlobStorageResult {
487
+ /** Whether the upload succeeded and a tx ID was decoded. */
488
+ success: boolean;
489
+ /** The Arweave transaction ID (43-char base64url) when `success` is true. */
490
+ txId?: string;
491
+ /** The id of the kind:5094 event that was published. */
492
+ eventId?: string;
493
+ /** Error message when `success` is false. */
494
+ error?: string;
495
+ }
496
+ /**
497
+ * Requests permanent Arweave blob storage from a DVM in a single ILP packet.
498
+ *
499
+ * Mirrors the single-packet flow in
500
+ * `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts`: builds a signed
501
+ * kind:5094 event, publishes it through the supplied {@link ToonClient}
502
+ * (reusing its claim/channel plumbing), and decodes the FULFILL `data` field
503
+ * into an Arweave transaction ID.
504
+ *
505
+ * @param client - A started `ToonClient` (call `client.start()` first).
506
+ * @param secretKey - The Nostr secret key used to sign the kind:5094 event.
507
+ * @param params - Blob, content type, bid, destination, and payment options.
508
+ * @returns `{ success, txId?, eventId?, error? }`.
509
+ */
510
+ declare function requestBlobStorage(client: ToonClient, secretKey: Uint8Array, params: RequestBlobStorageParams): Promise<RequestBlobStorageResult>;
170
511
 
171
512
  /**
172
513
  * ToonClient - High-level client for interacting with TOON network.
@@ -209,6 +550,20 @@ declare class ToonClient {
209
550
  private readonly config;
210
551
  private state;
211
552
  private readonly evmSigner?;
553
+ private solanaSigner?;
554
+ /**
555
+ * Ed25519 signing seed (32 bytes) derived from the mnemonic for the Solana
556
+ * identity. Retained so `start()` can inject it into the on-chain channel
557
+ * client's Solana config (same key as `solanaSigner`).
558
+ */
559
+ private solanaSeed?;
560
+ private minaSigner?;
561
+ /**
562
+ * Mina private key (big-endian hex scalar, as `deriveFullIdentity` emits)
563
+ * derived from the mnemonic. Retained so `start()` can inject it into the
564
+ * on-chain channel client's Mina config (same key as `minaSigner`).
565
+ */
566
+ private minaPrivateKey?;
212
567
  private channelManager?;
213
568
  private readonly peerNegotiations;
214
569
  /**
@@ -232,10 +587,58 @@ declare class ToonClient {
232
587
  * Works before start() is called.
233
588
  */
234
589
  getPublicKey(): string;
590
+ /**
591
+ * Sign an unsigned Nostr event template with the client's Nostr secret key,
592
+ * returning a fully-signed event (id + pubkey + sig).
593
+ *
594
+ * This is the key primitive behind the daemon's sign-and-publish path: a UI
595
+ * or agent supplies only `{ kind, content, tags, created_at }` and never holds
596
+ * the private key — signing happens here, inside the key owner.
597
+ */
598
+ signEvent(template: EventTemplate): NostrEvent;
599
+ /**
600
+ * Upload bytes to Arweave via the kind:5094 blob-storage DVM (single-packet),
601
+ * signing the request with this client's Nostr key and paying through its
602
+ * existing channel. Returns the Arweave tx id on success.
603
+ *
604
+ * Backs the daemon's `upload-media` path: the key and claim/channel plumbing
605
+ * stay inside the client; callers pass only the bytes.
606
+ */
607
+ uploadBlob(params: {
608
+ blobData: Uint8Array;
609
+ contentType?: string;
610
+ bid?: string;
611
+ destination?: string;
612
+ ilpAmount?: bigint;
613
+ }): Promise<RequestBlobStorageResult>;
614
+ /**
615
+ * Per-chain settlement readiness for the configured `network` tier, mirroring
616
+ * the townhouse node's status. Returns `undefined` when no named `network` is
617
+ * set (or `network: 'custom'`), since there is no preset tier to report on.
618
+ */
619
+ getNetworkStatus(): NetworkFamilyStatus | undefined;
235
620
  /**
236
621
  * Gets the EVM address derived from the Nostr secret key (or explicit evmPrivateKey override).
237
622
  */
238
623
  getEvmAddress(): string | undefined;
624
+ /**
625
+ * Gets the Solana (base58) address, when the client was constructed from a
626
+ * `mnemonic`. Available only AFTER `start()` (Solana keys are derived
627
+ * asynchronously). Returns undefined otherwise.
628
+ */
629
+ getSolanaAddress(): string | undefined;
630
+ /**
631
+ * Gets the Mina (base58) address, when the client was constructed from a
632
+ * `mnemonic` AND `mina-signer` is installed. Available only AFTER `start()`.
633
+ * Returns undefined otherwise.
634
+ */
635
+ getMinaAddress(): string | undefined;
636
+ /**
637
+ * Derive the Solana/Mina keys from the mnemonic and register their signers on
638
+ * the ChannelManager. Mirrors how the EVM signer is wired, but for the
639
+ * non-secp256k1 chains. Skips any chain whose optional dependency is missing.
640
+ */
641
+ private registerMnemonicChainSigners;
239
642
  /**
240
643
  * Starts the ToonClient.
241
644
  *
@@ -263,7 +666,58 @@ declare class ToonClient {
263
666
  publishEvent(event: NostrEvent, options?: {
264
667
  destination?: string;
265
668
  claim?: SignedBalanceProof;
669
+ ilpAmount?: bigint;
266
670
  }): Promise<PublishEventResult>;
671
+ /**
672
+ * Sends a raw swap ILP packet (Story 12.5) to a Mill peer with an attached
673
+ * balance-proof claim. This is a lower-level surface than `publishEvent`:
674
+ * it forwards the raw `IlpSendResult` so the sender (`streamSwap()`) can
675
+ * decode FULFILL metadata itself.
676
+ *
677
+ * Claim resolution mirrors `publishEvent`:
678
+ * (a) explicit `params.claim` -> use it,
679
+ * (b) `channelManager` present -> auto-open + auto-sign for the peer
680
+ * matching `destination`,
681
+ * (c) neither -> throw MISSING_CLAIM.
682
+ *
683
+ * @throws {ToonClientError} INVALID_STATE / NO_BTP_CLIENT / MISSING_CLAIM
684
+ */
685
+ sendSwapPacket(params: {
686
+ destination: string;
687
+ amount: bigint;
688
+ toonData: Uint8Array;
689
+ timeout?: number;
690
+ claim?: SignedBalanceProof;
691
+ }): Promise<IlpSendResult>;
692
+ /**
693
+ * Build a BTP claim message from a pre-signed balance proof using the
694
+ * CHAIN-APPROPRIATE signer.
695
+ *
696
+ * The explicit-claim path (caller signs the balance proof, then passes
697
+ * `{ claim }`) must wrap the proof with the signer matching the channel's
698
+ * chain. Hardcoding `EvmSigner.buildClaimMessage` here produced an EVM
699
+ * `BTPClaimMessage` for a Solana/Mina balance proof — no `blockchain`
700
+ * discriminator and the base58 channel account placed in the EVM
701
+ * `channelId` field — which the connector's inbound validator classifies
702
+ * as EVM and rejects with F06 (`Invalid channelId format`).
703
+ *
704
+ * When the proof's `channelId` is tracked we use
705
+ * `getSignerForChannel(channelId).buildClaimMessage`, which emits the
706
+ * correct per-chain envelope (e.g. `blockchain:'solana'` + base58
707
+ * `channelAccount`). When it is not tracked we fall back to the EVM signer
708
+ * to preserve prior behavior for lightweight/EVM-only callers.
709
+ *
710
+ * EVM output is byte-identical to the previous hardcoded path (the EVM
711
+ * adapter in `getSignerForChannel` delegates to the same
712
+ * `EvmSigner.buildClaimMessage`).
713
+ */
714
+ private buildClaimMessageForProof;
715
+ /**
716
+ * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.
717
+ * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution
718
+ * to call this helper. Kept duplicated for now to minimize regression risk.
719
+ */
720
+ private resolveClaimForDestination;
267
721
  /**
268
722
  * Signs a balance proof for the given channel with the specified amount.
269
723
  * Delegates to ChannelManager which auto-increments nonce and tracks cumulative amount.
@@ -274,6 +728,22 @@ declare class ToonClient {
274
728
  * @throws {ToonClientError} If no EVM signer configured or channel not tracked
275
729
  */
276
730
  signBalanceProof(channelId: string, amount: bigint): Promise<SignedBalanceProof>;
731
+ /**
732
+ * Eagerly open (or return existing) payment channel for the given destination.
733
+ *
734
+ * Channels are normally opened lazily on the first `publishEvent()` /
735
+ * `sendSwapPacket()` call. This method exposes the lazy-open path so
736
+ * callers (and E2E tests) that need a tracked `channelId` BEFORE publishing
737
+ * can force the open. Idempotent — returns the existing channel ID for the
738
+ * peer if one is already open.
739
+ *
740
+ * @param destination - Optional ILP destination address. Defaults to
741
+ * `config.destinationAddress`.
742
+ * @returns The channel ID of the (now) open channel.
743
+ * @throws {ToonClientError} If client not started, no channel manager
744
+ * configured, or peer negotiation metadata missing.
745
+ */
746
+ openChannel(destination?: string): Promise<string>;
277
747
  /**
278
748
  * Gets list of tracked payment channel IDs.
279
749
  */
@@ -343,6 +813,48 @@ declare class ToonClient {
343
813
  getDiscoveredPeers(): _toon_protocol_core.DiscoveredPeer[];
344
814
  }
345
815
 
816
+ /**
817
+ * Hidden-service hostname validation for the anyone-protocol / ATOR network.
818
+ *
819
+ * The `anon` binary routes hidden-service hostnames under the **`.anyone`** TLD
820
+ * ONLY. A `<host>.anon` name is NOT recognized as a hidden service — anon treats
821
+ * it as a clearnet name and tries to exit-resolve it, which fails
822
+ * (`resolve failed` / `HostUnreachable`). Only `<host>.anyone` triggers anon's
823
+ * `parse_extended_hostname: Anyone dns address lookup` and is validated.
824
+ *
825
+ * Historically the client and the toon-client pod accepted BOTH `.anon` and
826
+ * `.anyone`, so a `.anon` address was silently accepted and then failed deep in
827
+ * the transport with an opaque error. This module makes `.anyone` the single
828
+ * accepted/routable HS TLD and rejects `.anon` up front with an actionable
829
+ * message (see issue #201).
830
+ *
831
+ * This is a pure, browser-safe helper (no Node built-ins) so it can be imported
832
+ * from any transport path.
833
+ */
834
+ /**
835
+ * A `<host>.anyone` hidden-service hostname. The label is base32 (`a-z2-7`),
836
+ * matching the on-wire onion-style address alphabet anon uses.
837
+ */
838
+ declare const HS_HOSTNAME_REGEX: RegExp;
839
+ /** Max length of an HS hostname (defensive bound against pathological input). */
840
+ declare const HS_HOSTNAME_MAX_LENGTH = 80;
841
+ /**
842
+ * Returns true iff `s` is a routable `.anyone` hidden-service hostname.
843
+ * Does NOT accept the legacy `.anon` TLD (see {@link assertRoutableHsHostname}).
844
+ */
845
+ declare function isRoutableHsHostname(s: unknown): s is string;
846
+ /**
847
+ * Validates that `hostname` is a routable `.anyone` hidden-service address.
848
+ *
849
+ * - `<host>.anyone` → returns the hostname unchanged.
850
+ * - `<host>.anon` → throws with an actionable message pointing at `.anyone`
851
+ * (anon does NOT route `.anon`; it would silently fail in the transport).
852
+ * - anything else → throws a generic format error.
853
+ *
854
+ * @throws {Error} if the hostname is not a routable `.anyone` HS address.
855
+ */
856
+ declare function assertRoutableHsHostname(hostname: unknown): string;
857
+
346
858
  /**
347
859
  * Base error class for all TOON client errors.
348
860
  */
@@ -679,6 +1191,8 @@ interface BtpRuntimeClientConfig {
679
1191
  maxRetries?: number;
680
1192
  /** Delay between reconnection attempts in ms (default: 1000) */
681
1193
  retryDelay?: number;
1194
+ /** Custom WebSocket constructor (for SOCKS5 proxy support). */
1195
+ createWebSocket?: (url: string) => WebSocket;
682
1196
  }
683
1197
  /**
684
1198
  * BTP transport implementing IlpClient.
@@ -722,6 +1236,14 @@ declare class BtpRuntimeClient implements IlpClient {
722
1236
  data: string;
723
1237
  timeout?: number;
724
1238
  }, claim: Record<string, unknown>): Promise<IlpSendResult>;
1239
+ /**
1240
+ * Send a standalone `payment-channel-claim` BTP MESSAGE (no ILP packet
1241
+ * attached). The connector's ClaimReceiver consumes this fire-and-forget
1242
+ * to register cumulative claim state independently of the per-packet
1243
+ * forwarding path. Auto-reconnects on connection errors.
1244
+ */
1245
+ sendClaimMessage(claim: Record<string, unknown>): Promise<void>;
1246
+ private _sendClaimMessageOnce;
725
1247
  /**
726
1248
  * Single-attempt ILP packet send. Reconnects if not connected.
727
1249
  */
@@ -732,6 +1254,216 @@ declare class BtpRuntimeClient implements IlpClient {
732
1254
  private _sendIlpPacketWithClaimOnce;
733
1255
  }
734
1256
 
1257
+ /**
1258
+ * ILP-over-HTTP (RFC-0035) transport for the TOON client.
1259
+ *
1260
+ * The connector now serves ILP-over-HTTP on the SAME port as BTP (connector
1261
+ * PR #181). This adapter lets a client do stateless one-shot writes over HTTP
1262
+ * (`POST /ilp`) and upgrade to a duplex BTP session when it needs to receive
1263
+ * server-initiated packets or act as a peer.
1264
+ *
1265
+ * Wire contract (targets connector PR #181's `/ilp`):
1266
+ * - One-shot write: `POST /ilp`
1267
+ * body: OER-encoded ILP PREPARE (`application/octet-stream`)
1268
+ * header: `ILP-Payment-Channel-Claim: base64(JSON of the claim)` — the
1269
+ * SAME claim JSON the BTP path attaches as the
1270
+ * `payment-channel-claim` protocolData entry.
1271
+ * optional: `ILP-Peer-Id` + `Authorization: Bearer <secret>` identity.
1272
+ * response: `200 OK` with an OER FULFILL or REJECT body. HTTP non-2xx is
1273
+ * reserved for TRANSPORT errors (400/401/413/5xx); ILP-level
1274
+ * rejects come back as a 200 + REJECT body.
1275
+ * - Upgrade to BTP: standard HTTP `Upgrade` with `Sec-WebSocket-Protocol: btp`
1276
+ * plus the same `ILP-Peer-Id` + `Authorization` headers. The connector
1277
+ * pre-authenticates the BTP session from those headers (continuity), so
1278
+ * after `101` we send BTP frames WITHOUT a separate in-band auth frame.
1279
+ * Omitting the auth headers falls back to the normal BTP auth-frame flow.
1280
+ *
1281
+ * Reuses `serializeIlpPrepare`/`deserializeIlpPacket` from `btp/protocol.ts` —
1282
+ * the SAME OER codec the BTP path uses. Claim signing/construction is owned by
1283
+ * the caller (BootstrapService); this transport never builds or signs claims.
1284
+ */
1285
+
1286
+ /** Header carrying the base64(JSON) payment-channel claim. */
1287
+ declare const ILP_CLAIM_HEADER = "ILP-Payment-Channel-Claim";
1288
+ /** Header carrying a NIP-59 wrapped (gift-wrapped) claim. */
1289
+ declare const ILP_CLAIM_WRAPPED_HEADER = "ILP-Payment-Channel-Claim-Wrapped";
1290
+ /** Header carrying the peer identity. */
1291
+ declare const ILP_PEER_ID_HEADER = "ILP-Peer-Id";
1292
+ interface HttpIlpClientConfig {
1293
+ /** The peer's `POST /ilp` URL (the `httpEndpoint` from discovery). */
1294
+ httpEndpoint: string;
1295
+ /**
1296
+ * Optional peer identity. With no `peerId`/`authToken` the connector treats
1297
+ * the request as an anonymous no-auth peer (permissionless default) and
1298
+ * derives an ephemeral id from the claim signer.
1299
+ */
1300
+ peerId?: string;
1301
+ /** Bearer secret for `Authorization`. Omit for the no-auth peer path. */
1302
+ authToken?: string;
1303
+ /** Request timeout in milliseconds (default: 30000). */
1304
+ timeout?: number;
1305
+ /** Max retry attempts for transport-level network failures (default: 3). */
1306
+ maxRetries?: number;
1307
+ /** Initial retry delay in milliseconds (default: 1000). */
1308
+ retryDelay?: number;
1309
+ /** Custom fetch implementation (SOCKS5 mode / testing). */
1310
+ httpClient?: typeof fetch;
1311
+ /**
1312
+ * Custom WebSocket constructor for the BTP upgrade path (SOCKS5 mode).
1313
+ * Forwarded to the underlying BtpRuntimeClient.
1314
+ */
1315
+ createWebSocket?: (url: string) => WebSocket;
1316
+ }
1317
+ /**
1318
+ * Stateless ILP-over-HTTP transport implementing `IlpClient`.
1319
+ *
1320
+ * Use this for pure one-shot consumers (publish-and-forget writes). When the
1321
+ * client needs a duplex session — to receive server-initiated packets or to act
1322
+ * as a peer — call {@link upgradeToBtp} to obtain a connected BtpRuntimeClient
1323
+ * that reuses the existing BTP code path.
1324
+ */
1325
+ declare class HttpIlpClient implements IlpClient {
1326
+ private readonly httpEndpoint;
1327
+ private readonly peerId;
1328
+ private readonly authToken;
1329
+ private readonly timeout;
1330
+ private readonly retryConfig;
1331
+ private readonly httpClient;
1332
+ private readonly createWebSocket;
1333
+ constructor(config: HttpIlpClientConfig);
1334
+ /**
1335
+ * Send an ILP PREPARE via `POST /ilp` WITHOUT a claim. The connector accepts
1336
+ * this only on free/zero-amount routes; paid writes must use
1337
+ * {@link sendIlpPacketWithClaim}. Satisfies the IlpClient interface.
1338
+ */
1339
+ sendIlpPacket(params: {
1340
+ destination: string;
1341
+ amount: string;
1342
+ data: string;
1343
+ timeout?: number;
1344
+ }): Promise<IlpSendResult>;
1345
+ /**
1346
+ * Send an ILP PREPARE via `POST /ilp` with the payment-channel claim attached
1347
+ * as the `ILP-Payment-Channel-Claim` header. `claim` is the SAME JSON object
1348
+ * the BTP path attaches as the `payment-channel-claim` protocolData entry —
1349
+ * we base64(JSON.stringify(claim)) it, byte-for-byte identical to BTP.
1350
+ */
1351
+ sendIlpPacketWithClaim(params: {
1352
+ destination: string;
1353
+ amount: string;
1354
+ data: string;
1355
+ timeout?: number;
1356
+ }, claim: unknown): Promise<IlpSendResult>;
1357
+ /**
1358
+ * Upgrade to a duplex BTP session over the SAME endpoint.
1359
+ *
1360
+ * Derives the `ws(s)://` URL from `httpEndpoint`, opens a WebSocket with
1361
+ * `Sec-WebSocket-Protocol: btp` and the same `ILP-Peer-Id` + `Authorization`
1362
+ * headers, and returns a connected {@link BtpRuntimeClient}. When auth headers
1363
+ * are present the connector pre-authenticates the session (no in-band auth
1364
+ * frame); without them the BtpRuntimeClient falls back to the normal BTP
1365
+ * auth-frame flow.
1366
+ *
1367
+ * NOTE: passing per-connection headers + a subprotocol to a WebSocket is
1368
+ * Node-only (the `ws` package). Browsers cannot set arbitrary request headers
1369
+ * on a WebSocket handshake, so a browser consumer must use the gateway
1370
+ * transport or BTP-with-auth-frame instead.
1371
+ */
1372
+ upgradeToBtp(): Promise<BtpRuntimeClient>;
1373
+ private authHeaders;
1374
+ /**
1375
+ * Single attempt: serialize the PREPARE, POST it, and map the response.
1376
+ * @throws {NetworkError} On connection/timeout failures (retried).
1377
+ * @throws {ConnectorError} On non-retryable transport errors (5xx / unexpected).
1378
+ */
1379
+ private postPrepare;
1380
+ /**
1381
+ * Map a `200 OK` body (OER FULFILL/REJECT) to an IlpSendResult; map a non-2xx
1382
+ * to a transport error. Per the wire contract, ILP-level rejects arrive as a
1383
+ * 200 + REJECT body — only HTTP non-2xx means a transport-layer failure.
1384
+ */
1385
+ private mapResponse;
1386
+ private mapTransportError;
1387
+ }
1388
+ /**
1389
+ * Derive the BTP WebSocket URL from a `POST /ilp` HTTP endpoint. The connector
1390
+ * serves BTP on the SAME path, so we only swap the scheme (http→ws, https→wss).
1391
+ */
1392
+ declare function httpEndpointToBtpUrl(httpEndpoint: string): string;
1393
+
1394
+ /**
1395
+ * Transport selection policy for the client ILP layer.
1396
+ *
1397
+ * The connector serves ILP-over-HTTP (`POST /ilp`) and BTP on the SAME port
1398
+ * (connector PR #181). A peer advertises this in discovery via the toon-core
1399
+ * `IlpPeerInfo` fields added in toon PR #29:
1400
+ * - `httpEndpoint?: string` — the `POST /ilp` URL.
1401
+ * - `supportsUpgrade?: boolean` — whether the host accepts the BTP upgrade.
1402
+ *
1403
+ * Those fields may not yet exist on the installed `@toon-protocol/core`
1404
+ * `IlpPeerInfo` type, so we read them defensively here (see `DiscoveredIlpPeer`).
1405
+ *
1406
+ * Policy:
1407
+ * - Pure one-shot consumers (`needsDuplex: false`) prefer HTTP when the peer
1408
+ * advertises an `httpEndpoint` — stateless, no persistent socket.
1409
+ * - Clients that must receive server-initiated packets or act as a peer
1410
+ * (`needsDuplex: true`) use BTP. If the peer only exposes `httpEndpoint`
1411
+ * and `supportsUpgrade` is true, we go HTTP-then-upgrade; otherwise we
1412
+ * connect to the BTP endpoint directly.
1413
+ */
1414
+ /**
1415
+ * The subset of discovery fields this policy reads. Structurally compatible with
1416
+ * core's `IlpPeerInfo`; `httpEndpoint`/`supportsUpgrade` are optional so a
1417
+ * pre-PR-#29 `IlpPeerInfo` can be passed through a cast.
1418
+ */
1419
+ interface DiscoveredIlpPeer {
1420
+ /** BTP WebSocket endpoint (always present on a TOON peer). */
1421
+ btpEndpoint?: string;
1422
+ /** `POST /ilp` URL (toon PR #29). */
1423
+ httpEndpoint?: string;
1424
+ /** Whether the host accepts the BTP upgrade over the HTTP endpoint (toon PR #29). */
1425
+ supportsUpgrade?: boolean;
1426
+ }
1427
+ type IlpTransportChoice =
1428
+ /** Stateless one-shot writes via `POST /ilp`. */
1429
+ {
1430
+ kind: 'http';
1431
+ httpEndpoint: string;
1432
+ canUpgrade: boolean;
1433
+ }
1434
+ /** Duplex BTP session via the WebSocket endpoint. */
1435
+ | {
1436
+ kind: 'btp';
1437
+ btpEndpoint: string;
1438
+ }
1439
+ /**
1440
+ * Open HTTP first (one-shot writes) but upgrade to BTP when duplex is needed.
1441
+ * Only chosen when the peer exposes `httpEndpoint` + `supportsUpgrade` and no
1442
+ * separate `btpEndpoint`.
1443
+ */
1444
+ | {
1445
+ kind: 'http-upgradable';
1446
+ httpEndpoint: string;
1447
+ };
1448
+ interface SelectIlpTransportOptions {
1449
+ /**
1450
+ * Whether the client needs a duplex session (receive server-initiated
1451
+ * packets / act as a peer). Default: false (pure one-shot consumer).
1452
+ */
1453
+ needsDuplex?: boolean;
1454
+ }
1455
+ /**
1456
+ * Read discovery fields defensively from a (possibly pre-PR-#29) peer info
1457
+ * object. Accepts core's `IlpPeerInfo` or any structurally-compatible shape.
1458
+ */
1459
+ declare function readDiscoveredIlpPeer(peer: unknown): DiscoveredIlpPeer;
1460
+ /**
1461
+ * Choose the ILP transport for a discovered peer given the consumer's needs.
1462
+ *
1463
+ * @throws {Error} If the peer advertises no usable endpoint at all.
1464
+ */
1465
+ declare function selectIlpTransport(peer: DiscoveredIlpPeer, options?: SelectIlpTransportOptions): IlpTransportChoice;
1466
+
735
1467
  /**
736
1468
  * Chain-specific metadata (discriminated union).
737
1469
  */
@@ -761,36 +1493,92 @@ interface ChainSigner {
761
1493
  transferredAmount: bigint;
762
1494
  lockedAmount: bigint;
763
1495
  locksRoot: string;
1496
+ /**
1497
+ * Counterparty settlement address the proof is bound to. Required for
1498
+ * Solana/Mina (folded into the canonical balance-proof message); the EVM
1499
+ * adapter ignores it (EIP-712 has no recipient term).
1500
+ */
1501
+ recipient: string;
764
1502
  metadata: ChainMetadata;
1503
+ /**
1504
+ * On-chain channel `depositTotal` (base units). When supplied (>0), a Mina
1505
+ * signer binds the conserved `balanceB = depositTotal − balanceA` commitment
1506
+ * required to settle on a funded zkApp (connector#133); EVM/Solana signers
1507
+ * ignore it. When omitted, a Mina signer self-resolves it from chain if it
1508
+ * was configured with a GraphQL URL (#223), else falls back to the legacy
1509
+ * `balanceB = 0` form.
1510
+ */
1511
+ depositTotal?: bigint;
765
1512
  }): Promise<SignedBalanceProof>;
766
1513
  buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage;
767
1514
  }
768
1515
  type ClaimMessage = EVMClaimMessage | SolanaClaimMessage | MinaClaimMessage;
1516
+ /**
1517
+ * Solana payment-channel claim — wire-compatible with the connector's
1518
+ * `SolanaClaimMessage` (`@toon-protocol/connector` `btp/btp-claim-types.ts`).
1519
+ * Field names match the connector's `validateSolanaClaim` exactly:
1520
+ * `channelAccount` (base58 PDA), `signerPublicKey` (base58), base64 `signature`.
1521
+ */
769
1522
  interface SolanaClaimMessage {
770
1523
  version: '1.0';
771
1524
  blockchain: 'solana';
772
1525
  messageId: string;
773
1526
  timestamp: string;
774
1527
  senderId: string;
775
- channelId: string;
1528
+ /** On-chain PDA account address for the payment channel (base58). */
1529
+ channelAccount: string;
776
1530
  nonce: number;
1531
+ /** Cumulative transferred amount (string for bigint precision). */
777
1532
  transferredAmount: string;
1533
+ /** Ed25519 signature over the 48-byte balance-proof message (base64). */
778
1534
  signature: string;
779
- signerAddress: string;
1535
+ /** Base58-encoded Ed25519 public key of the signer. */
1536
+ signerPublicKey: string;
1537
+ /** Solana program id for the payment-channel program (base58). */
780
1538
  programId: string;
781
1539
  }
1540
+ /**
1541
+ * Mina payment-channel claim — wire-compatible with the connector's
1542
+ * `MinaClaimMessage` (`@toon-protocol/connector` `btp/btp-claim-types.ts`).
1543
+ * Field names + types match `validateMinaClaim` exactly: `zkAppAddress`
1544
+ * (B62-prefixed 55-char base58, the channel id), `tokenId`, `balanceCommitment`
1545
+ * (`Poseidon([balA,balB,salt])` decimal string), integer `nonce`, base64 `proof`,
1546
+ * and `salt`. `transferredAmount`/`balanceB`/`signatureB`/`network` are OPTIONAL
1547
+ * at validation; the apex-as-recipient single-direction claim sends party-A only.
1548
+ */
782
1549
  interface MinaClaimMessage {
783
1550
  version: '1.0';
784
1551
  blockchain: 'mina';
785
1552
  messageId: string;
786
1553
  timestamp: string;
787
1554
  senderId: string;
788
- channelId: string;
789
- nonce: number;
790
- transferredAmount: string;
791
- commitment: string;
792
- signerAddress: string;
1555
+ /** Deployed payment-channel zkApp address (B62 base58) — the channel id. */
793
1556
  zkAppAddress: string;
1557
+ /** Mina token id (default `'MINA'`). */
1558
+ tokenId: string;
1559
+ /** `Poseidon([balanceA, balanceB, salt]).toString()`. */
1560
+ balanceCommitment: string;
1561
+ nonce: number;
1562
+ /** base64-encoded JSON `{ commitment, signature: { r, s }, nonce, signerPublicKey }`. */
1563
+ proof: string;
1564
+ /** Commitment salt (decimal string). */
1565
+ salt: string;
1566
+ /** Cumulative transferred amount (optional; string for bigint precision). */
1567
+ transferredAmount?: string;
1568
+ /**
1569
+ * Signer's Mina public key (B62 base58) — the claiming participant.
1570
+ *
1571
+ * Surfaced top-level (in addition to being embedded in `proof`) so the
1572
+ * connector's `SettlementExecutor` can resolve participant keys for the
1573
+ * on-chain `claimFromChannel` on an externally-opened (inbound) channel. The
1574
+ * connector reads `latestClaim.signerPublicKey` directly (not the proof blob);
1575
+ * without it the Mina SDK's `claimFromChannel` throws `ACCOUNT_NOT_FOUND`
1576
+ * ("Participant keys not found in cache and none were supplied"). The
1577
+ * connector accepts this as an optional `MinaClaimMessage` field.
1578
+ */
1579
+ signerPublicKey?: string;
1580
+ /** Mina network id — defaults to `devnet` connector-side when omitted. */
1581
+ network?: 'mainnet' | 'devnet' | 'berkeley' | 'lightnet';
794
1582
  }
795
1583
 
796
1584
  /**
@@ -862,17 +1650,33 @@ declare class EvmSigner {
862
1650
  }
863
1651
 
864
1652
  /**
865
- * Solana signer for Ed25519 balance proofs.
1653
+ * Solana signer for the connector payment-channel claim path.
866
1654
  *
867
- * Signs channel state using Ed25519 (raw, not EIP-712).
868
- * Dynamically imports @noble/curves to avoid missing-dep errors for non-Solana users.
1655
+ * Signs the connector's on-chain payment-channel balance-proof message — the
1656
+ * raw 48-byte `channel_pda(32) || nonce(8 LE) || transferredAmount(8 LE)` (see
1657
+ * `@toon-protocol/connector` `SolanaPaymentChannelSDK._buildBalanceProofMessage`
1658
+ * + `solana-payment-channel-provider.verifyBalanceProof`). The produced 64-byte
1659
+ * Ed25519 signature verifies on the connector's `verifySolanaClaim` path, which
1660
+ * is what makes a client-issued Solana payment-channel claim (paying the apex
1661
+ * to write) acceptable on connector 3.9.0.
1662
+ *
1663
+ * NOTE: this is a DIFFERENT message from the Mill ↔ sender swap-claim wire
1664
+ * contract (`balanceProofHashSolana`, SDK `verifyEd25519Signature`). The client
1665
+ * here is paying a payment-channel claim to the apex, not issuing a swap claim,
1666
+ * so it must sign the connector's on-chain payment-channel message. `channelId`
1667
+ * MUST be the base58 channel PDA (produced by `OnChainChannelClient.openChannel`).
869
1668
  */
870
1669
  declare class SolanaSigner implements ChainSigner {
871
1670
  readonly chainType: "solana";
1671
+ /** 32-byte Ed25519 seed. */
872
1672
  private readonly privateKey;
873
- private publicKey?;
874
1673
  private pubkeyBase58Cache?;
875
- constructor(privateKey: Uint8Array);
1674
+ /**
1675
+ * @param privateKey - 32-byte Ed25519 seed (e.g. `identity.solana.secretKey.slice(0, 32)`).
1676
+ * @param publicKeyBase58 - Optional base58 public key (e.g. `identity.solana.publicKey`).
1677
+ * When omitted it is derived lazily from `privateKey`.
1678
+ */
1679
+ constructor(privateKey: Uint8Array, publicKeyBase58?: string);
876
1680
  private ensurePublicKey;
877
1681
  get signerIdentifier(): string;
878
1682
  signBalanceProof(params: {
@@ -881,44 +1685,170 @@ declare class SolanaSigner implements ChainSigner {
881
1685
  transferredAmount: bigint;
882
1686
  lockedAmount: bigint;
883
1687
  locksRoot: string;
1688
+ recipient: string;
884
1689
  metadata: ChainMetadata;
885
1690
  }): Promise<SignedBalanceProof>;
886
1691
  buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage;
887
1692
  }
888
1693
 
1694
+ /** Reads a channel zkApp's on-chain `depositTotal` (base units). */
1695
+ type MinaDepositReader = (zkAppAddress: string) => Promise<bigint>;
1696
+ /** Optional `MinaSigner` wiring for on-chain `depositTotal` resolution. */
1697
+ interface MinaSignerOptions {
1698
+ /**
1699
+ * Mina GraphQL URL used to read the channel's on-chain `depositTotal` when a
1700
+ * caller doesn't supply it to `signBalanceProof`. Enables conserved
1701
+ * `balanceB = depositTotal − balanceA` claims (settleable on funded zkApps).
1702
+ */
1703
+ graphqlUrl?: string;
1704
+ /** Inject a deposit reader (tests / custom transport). Overrides `graphqlUrl`. */
1705
+ depositReader?: MinaDepositReader;
1706
+ }
889
1707
  /**
890
- * Mina signer for Poseidon commitment balance proofs.
1708
+ * Mina (Pallas) signer for the connector payment-channel claim path.
1709
+ *
1710
+ * Produces the connector 3.9.0 `MinaClaimMessage` contract — `{ zkAppAddress,
1711
+ * tokenId, balanceCommitment, proof (base64), salt, nonce }` — by reproducing
1712
+ * `MinaPaymentChannelSDK.signBalanceProof` exactly (via
1713
+ * {@link buildMinaPaymentChannelProof}):
1714
+ *
1715
+ * commitment = Poseidon([Field(balanceA), Field(0), Field(salt)])
1716
+ * channelHashField = Poseidon([participantA.x, participantB.x, 0]) (see below)
1717
+ * proof = base64(JSON{ commitment, signature: { r, s }, nonce, signerPublicKey })
891
1718
  *
892
- * Dynamically imports o1js to avoid pulling 50MB into the client bundle
893
- * for non-Mina users.
1719
+ * with the Schnorr signature computed over `[commitment, Field(nonce),
1720
+ * channelHashField]` using the Mina `'devnet'` network id (matching o1js's
1721
+ * hardcoded `Signature.create` prefix). Verified field-by-field against the
1722
+ * connector's o1js `Signature.fromJSON({r,s}).verify` (see the package tests).
1723
+ *
1724
+ * `channelHashField` is the ON-CHAIN participant form
1725
+ * (`Poseidon([client.x, apex.x, 0])`, participantA=client, participantB=apex)
1726
+ * whenever the apex's Mina pubkey is known (the negotiated `recipient`), so the
1727
+ * claim can SETTLE on-chain via the zkApp's `claimFromChannel` (which only
1728
+ * verifies the participant form). When the apex pubkey is unavailable the signer
1729
+ * falls back to the legacy zkApp-x form (`Poseidon([zkApp.x])`); the connector's
1730
+ * off-chain `verifyBalanceProof` accepts EITHER, so off-chain store/FULFILL works
1731
+ * in both cases — only on-chain settle requires the participant form.
1732
+ *
1733
+ * NOTE: this is a DIFFERENT message + format from the Mill ↔ sender swap-claim
1734
+ * wire contract (`balanceProofFieldsMina` in `@toon-protocol/core`, verified by
1735
+ * the SDK's `verifyMinaSignature`). The client here pays a payment-channel claim
1736
+ * to the apex, so it signs the connector's on-chain payment-channel scheme; the
1737
+ * swap-format hash is left untouched (mirrors the Solana #105 separation).
1738
+ *
1739
+ * `channelId` MUST be the deployed payment-channel zkApp B62 address (the same
1740
+ * address the apex's Mina provider resolves on-chain via `getChannelState`),
1741
+ * which is what `OnChainChannelClient.openMinaChannel` returns.
1742
+ *
1743
+ * `mina-signer` is an OPTIONAL dependency: its crypto (Poseidon, Pallas Schnorr,
1744
+ * the base58 signature codec) is loaded dynamically so the client builds and runs
1745
+ * for non-Mina users without it installed, and WITHOUT pulling the o1js WASM
1746
+ * circuit runtime.
894
1747
  */
895
1748
  declare class MinaSigner implements ChainSigner {
896
1749
  readonly chainType: "mina";
897
- private readonly privateKeyBase58;
898
- private publicKeyBase58;
899
- constructor(privateKeyBase58: string);
1750
+ /** Big-endian hex scalar (or already-`EK…` base58) Mina private key. */
1751
+ private readonly privateKey;
1752
+ private publicKeyBase58?;
1753
+ private readonly depositReader?;
1754
+ /** Per-zkApp `depositTotal` cache (deposits are rare; the connector re-reads). */
1755
+ private readonly depositCache;
1756
+ /**
1757
+ * @param privateKey - Mina private key as big-endian hex scalar (the form
1758
+ * `deriveFullIdentity()` emits, `identity.mina.privateKey`) or an `EK…`
1759
+ * base58 key. Converted to the base58check form mina-signer requires.
1760
+ * @param publicKeyBase58 - Optional base58 public key (e.g.
1761
+ * `identity.mina.publicKey`). When omitted it is derived during signing.
1762
+ * @param options - Optional on-chain `depositTotal` resolution (graphqlUrl or
1763
+ * an injected reader) so claims conserve balances on funded zkApps.
1764
+ */
1765
+ constructor(privateKey: string, publicKeyBase58?: string, options?: MinaSignerOptions);
1766
+ /**
1767
+ * Resolve the channel's on-chain `depositTotal`, caching per zkApp. Returns
1768
+ * `undefined` when no reader is configured or the read fails — callers then
1769
+ * fall back to the legacy `balanceB = 0` commitment.
1770
+ */
1771
+ private resolveDepositTotal;
900
1772
  get signerIdentifier(): string;
901
- private ensurePublicKey;
1773
+ /** Derive this signer's B62 public key from its (base58) private key. */
1774
+ private deriveOwnPublicKey;
902
1775
  signBalanceProof(params: {
903
1776
  channelId: string;
904
1777
  nonce: number;
905
1778
  transferredAmount: bigint;
906
1779
  lockedAmount: bigint;
907
1780
  locksRoot: string;
1781
+ recipient: string;
908
1782
  metadata: ChainMetadata;
1783
+ /**
1784
+ * On-chain channel `depositTotal`. When provided (>0), the signed commitment
1785
+ * binds `balanceB = depositTotal − balanceA` (the funder's remaining
1786
+ * balance), matching the connector's claimFromChannel reconstruction
1787
+ * (toon-protocol/connector#133) and the on-chain circuit's
1788
+ * `balanceA + balanceB == depositTotal` invariant. Omitted/0 keeps the
1789
+ * legacy `balanceB = 0` form (off-chain-store-only, non-settleable).
1790
+ */
1791
+ depositTotal?: bigint;
909
1792
  }): Promise<SignedBalanceProof>;
910
1793
  buildClaimMessage(proof: SignedBalanceProof, senderId: string): ClaimMessage;
911
1794
  }
912
1795
 
913
1796
  interface SolanaChannelConfig {
914
1797
  rpcUrl: string;
1798
+ /**
1799
+ * Ed25519 keypair material. Accepts either a 32-byte seed or a 64-byte
1800
+ * `secretKey` (seed || pubkey, as produced by `deriveFullIdentity`). The first
1801
+ * 32 bytes are the signing seed; the public key is derived from it.
1802
+ */
915
1803
  keypair: Uint8Array;
916
1804
  programId: string;
1805
+ /**
1806
+ * SPL token mint (base58) for PDA derivation. Optional — the per-channel
1807
+ * negotiated token (`OpenChannelParams.token`) takes precedence when present.
1808
+ */
1809
+ tokenMint?: string;
1810
+ /**
1811
+ * Challenge-period duration in seconds for `initialize_channel`. Defaults to
1812
+ * `OpenChannelParams.settlementTimeout` or 86400.
1813
+ */
1814
+ challengeDuration?: number;
1815
+ /**
1816
+ * Optional deposit amount (base units, string) + the payer's funded SPL token
1817
+ * account (ATA, base58). When omitted, the channel is opened (initialized)
1818
+ * without an on-chain deposit — the connector accepts the claim on channel
1819
+ * `opened` status + participant membership; deposit is only consumed at
1820
+ * on-chain claim/settle time.
1821
+ */
1822
+ deposit?: {
1823
+ amount: string;
1824
+ payerTokenAccount: string;
1825
+ };
917
1826
  }
918
1827
  interface MinaChannelConfig {
919
1828
  graphqlUrl: string;
920
1829
  privateKey: string;
921
1830
  zkAppAddress: string;
1831
+ /**
1832
+ * Channel settlement timeout in slots for `initializeChannel`. Defaults to
1833
+ * `OpenChannelParams.settlementTimeout` or 86400.
1834
+ */
1835
+ challengeDuration?: number;
1836
+ /**
1837
+ * Mina token id field (decimal string) for `initializeChannel`. Default '1'
1838
+ * (native MINA). The connector reads this only as on-chain channel metadata.
1839
+ */
1840
+ tokenId?: string;
1841
+ /**
1842
+ * Optional on-chain deposit (base units, string) submitted after the channel
1843
+ * is initialized. When omitted, the channel is opened (OPEN state) without a
1844
+ * deposit — the connector accepts the claim on `opened` status; deposit is
1845
+ * only consumed at on-chain settle time.
1846
+ */
1847
+ deposit?: {
1848
+ amount: string;
1849
+ };
1850
+ /** Mina network id for the account/Schnorr prefix. Default 'devnet'. */
1851
+ networkId?: 'devnet' | 'mainnet';
922
1852
  }
923
1853
  interface OnChainChannelClientConfig {
924
1854
  evmSigner: EvmSigner;
@@ -935,10 +1865,29 @@ interface OnChainChannelClientConfig {
935
1865
  declare class OnChainChannelClient implements ConnectorChannelClient {
936
1866
  private readonly evmSigner;
937
1867
  private readonly chainRpcUrls;
938
- private readonly solanaConfig?;
939
- private readonly minaConfig?;
1868
+ private solanaConfig?;
1869
+ private minaConfig?;
940
1870
  private readonly channelContext;
941
1871
  constructor(config: OnChainChannelClientConfig);
1872
+ /**
1873
+ * Late-binds the Solana channel config.
1874
+ *
1875
+ * `ToonClient.start()` derives the Solana Ed25519 keypair from the client's
1876
+ * mnemonic asynchronously (after this client is constructed), so the keypair
1877
+ * is injected here rather than at construction. Same keypair as the
1878
+ * registered Solana signer — guarantees the channel-open key and the
1879
+ * claim-signing key match.
1880
+ */
1881
+ setSolanaConfig(config: SolanaChannelConfig): void;
1882
+ /**
1883
+ * Late-binds the Mina channel config.
1884
+ *
1885
+ * Parallel to `setSolanaConfig`: `ToonClient.start()` derives the Mina private
1886
+ * key from the client's mnemonic asynchronously (after this client is
1887
+ * constructed), so the key is injected here rather than at construction. Same
1888
+ * key as the registered Mina signer.
1889
+ */
1890
+ setMinaConfig(config: MinaChannelConfig): void;
942
1891
  /**
943
1892
  * Parse chain identifier to extract chainId.
944
1893
  * Format: "evm:{network}:{chainId}" e.g., "evm:anvil:31337"
@@ -958,12 +1907,48 @@ declare class OnChainChannelClient implements ConnectorChannelClient {
958
1907
  */
959
1908
  openChannel(params: OpenChannelParams): Promise<OpenChannelResult>;
960
1909
  /**
961
- * Opens a Solana payment channel (PDA creation).
1910
+ * Opens a REAL on-chain Solana payment channel.
1911
+ *
1912
+ * Derives the connector-parity channel PDA
1913
+ * (`[b"channel", min_pubkey, max_pubkey, token_mint]`), submits the
1914
+ * `initialize_channel` instruction (+ optional `deposit`) to the deployed
1915
+ * payment-channel program, and returns the base58 PDA as the channel id. That
1916
+ * PDA is what the claim carries as `channelAccount`, and the on-chain channel
1917
+ * is what the connector's `verifySolanaClaim` reads via
1918
+ * `provider.getChannelState` before accepting the claim.
1919
+ *
1920
+ * Mirrors `openEvmChannel`'s open(+deposit) structure. Idempotent: if the
1921
+ * channel account already exists on-chain, returns its PDA without
1922
+ * re-initializing.
962
1923
  */
963
1924
  private openSolanaChannel;
964
1925
  /**
965
- * Opens a Mina payment channel (zkApp state transition).
966
- * Dynamically imports o1js to avoid bundle bloat.
1926
+ * Opens a REAL on-chain Mina payment channel on the deployed `PaymentChannel`
1927
+ * zkApp.
1928
+ *
1929
+ * The zkApp is deployed out-of-band (the operator/e2e harness deploys it
1930
+ * deterministically and advertises its B62 address). This client then calls
1931
+ * `initializeChannel` on that zkApp so its on-chain `channelState` becomes
1932
+ * `OPEN` — which is what the connector's `MinaPaymentChannelSDK.getChannelState`
1933
+ * reads to return status `'opened'` (claim verification otherwise fails with
1934
+ * `mina_claim_verification_failed`). The deployed zkApp address IS the channel
1935
+ * id: `MinaClaimMessage.zkAppAddress` is both the claim's channel identifier
1936
+ * AND the channel-hash preimage the off-chain proof binds to (see
1937
+ * `mina-payment-channel.ts`), so the channel-open id and the claim's channel id
1938
+ * are guaranteed identical.
1939
+ *
1940
+ * This is the Mina analog of `openSolanaChannel` (connector#105): the client
1941
+ * opens its own per-channel on-chain state (initialize + optional deposit). The
1942
+ * heavyweight o1js + `@toon-protocol/mina-zkapp` proof work is lazily imported
1943
+ * inside `openMinaChannelOnChain` so npm consumers who never open a Mina
1944
+ * channel don't pay the o1js cost.
1945
+ *
1946
+ * Idempotent: if the on-chain channel is already `OPEN`, the opener returns
1947
+ * without re-initializing.
1948
+ *
1949
+ * NOTE: full on-chain Mina SETTLE remains gated by the connector-side
1950
+ * settlement-executor (the same blocker that stops the Solana SETTLE); reaching
1951
+ * `opened` + a stored claim is parity with Solana.
967
1952
  */
968
1953
  private openMinaChannel;
969
1954
  /**
@@ -1062,6 +2047,8 @@ declare class ChannelManager {
1062
2047
  chainId: number;
1063
2048
  tokenNetworkAddress: string;
1064
2049
  tokenAddress?: string;
2050
+ recipient?: string;
2051
+ depositTotal?: bigint;
1065
2052
  }, initialNonce?: number, initialAmount?: bigint): void;
1066
2053
  /**
1067
2054
  * Signs a balance proof for the given channel.
@@ -1093,6 +2080,26 @@ declare class ChannelManager {
1093
2080
  isTracking(channelId: string): boolean;
1094
2081
  }
1095
2082
 
2083
+ /**
2084
+ * Read a Mina payment-channel zkApp's on-chain `depositTotal` via a plain
2085
+ * GraphQL query (no o1js / WASM). Used by {@link MinaSigner} to bind the
2086
+ * conserved `balanceB = depositTotal − balanceA` commitment that a FUNDED zkApp
2087
+ * requires (connector#133); without it the connector's `claimFromChannel`
2088
+ * verification rejects the claim with `F06 - Invalid zk-SNARK proof on claim`.
2089
+ *
2090
+ * The `PaymentChannel` zkApp app-state field order is
2091
+ * `[channelHash, balanceCommitment, nonceField, channelState, depositTotal, …]`
2092
+ * (see `mina-channel-open.ts`), so `depositTotal` is `zkappState[4]`.
2093
+ */
2094
+ /**
2095
+ * Query `account(publicKey).zkappState` and return the channel's `depositTotal`
2096
+ * (base units). Throws when the account/state is unavailable so callers can fall
2097
+ * back to the legacy `balanceB = 0` behavior.
2098
+ *
2099
+ * @param fetchImpl - injectable for tests; defaults to global `fetch`.
2100
+ */
2101
+ declare function readMinaDepositTotal(graphqlUrl: string, zkAppAddress: string, fetchImpl?: typeof fetch): Promise<bigint>;
2102
+
1096
2103
  /**
1097
2104
  * Configuration options for retry behavior with exponential backoff.
1098
2105
  */
@@ -1130,6 +2137,95 @@ interface RetryOptions {
1130
2137
  */
1131
2138
  declare function withRetry<T>(operation: () => Promise<T>, options: RetryOptions): Promise<T>;
1132
2139
 
2140
+ /**
2141
+ * Self-managed `anon` (anyone-protocol / ATOR) SOCKS5h proxy (Node.js only).
2142
+ *
2143
+ * Lets a `@toon-protocol/client` consumer reach a `.anyone` hidden service with
2144
+ * ZERO manual proxy setup: the SDK downloads, verifies, extracts, and spawns its
2145
+ * own `anon` daemon, waits for it to bootstrap + bind a loopback SOCKS5 port, and
2146
+ * hands back a `socks5h://127.0.0.1:<port>` URL. The proven reference is the
2147
+ * server-side pod entrypoint `docker/src/entrypoint-toon-client.ts` (`writeTorrc`,
2148
+ * `spawnAnon`, `waitForAnonSocks`, `tcpProbe`); this module ports that daemon
2149
+ * logic into the client package and adds the binary download + checksum gate so it
2150
+ * works without an OS-level `anon` install.
2151
+ *
2152
+ * BROWSER SAFETY: this module is dynamically imported only from `resolveTransport`
2153
+ * when a managed proxy is actually needed (Node-only path). Every Node built-in is
2154
+ * pulled in lazily via the ESM-safe `require(...)` built off `import.meta.url`
2155
+ * (the same pattern as `socks5.ts`), so a browser bundler that statically analyses
2156
+ * the package never reaches `node:child_process`/`node:fs`/`node:https`/`node:net`.
2157
+ */
2158
+ /**
2159
+ * Pinned `anon` release. "beta" is the channel slug embedded in the per-platform
2160
+ * zip asset names (e.g. `anon-beta-macos-arm64.zip`).
2161
+ */
2162
+ declare const ANON_VERSION = "v0.4.10.0-beta";
2163
+ /**
2164
+ * Per-platform `anon` zip asset descriptor. `sha256` is the pinned checksum of the
2165
+ * release zip. All supported platforms are pinned (issue #204); the type stays
2166
+ * `string | null` and the download gate still defensively refuses a `null` entry,
2167
+ * so adding a new (not-yet-hashed) platform fails closed rather than skipping
2168
+ * verification.
2169
+ */
2170
+ interface AnonAsset {
2171
+ /** Release asset file name, e.g. `anon-beta-macos-arm64.zip`. */
2172
+ assetName: string;
2173
+ /** Pinned sha256 of the zip, or null when not yet pinned (issue #204). */
2174
+ sha256: string | null;
2175
+ }
2176
+ /**
2177
+ * Platform → asset map keyed by `${os.platform()}-${os.arch()}` (Node values).
2178
+ * Only macOS + Linux on x64/arm64 are supported (the `anon` releases that ship a
2179
+ * SOCKS-capable binary). Windows is intentionally absent.
2180
+ *
2181
+ * Pinned checksums (issue #204): all four supported platforms are pinned to the
2182
+ * sha256 of the `v0.4.10.0-beta` release zips (downloaded + hashed; the
2183
+ * darwin-arm64 value matches the previously-verified manual flow).
2184
+ */
2185
+ declare const ANON_ASSETS: Record<string, AnonAsset>;
2186
+ /**
2187
+ * Resolves the `anon` release asset for a platform/arch pair (Node
2188
+ * `os.platform()` / `os.arch()` values).
2189
+ *
2190
+ * @throws If the platform/arch combination has no known `anon` asset.
2191
+ */
2192
+ declare function selectAnonAsset(platform: string, arch: string): AnonAsset;
2193
+ /**
2194
+ * Handle returned by `startManagedAnonProxy`. `socksProxy` is the loopback
2195
+ * `socks5h://` URL to wire into `transport: { type: 'socks5', socksProxy }`.
2196
+ * `stop()` SIGTERMs the daemon and is idempotent.
2197
+ */
2198
+ interface ManagedAnonProxy {
2199
+ socksProxy: string;
2200
+ stop(): Promise<void>;
2201
+ }
2202
+ /**
2203
+ * Options for `startManagedAnonProxy`. All have sensible defaults; tests inject
2204
+ * the deps to avoid real downloads/spawns.
2205
+ */
2206
+ interface StartManagedAnonProxyOptions {
2207
+ /** Cache dir for the binary + torrc + data. Default: {@link defaultCacheDir}. */
2208
+ cacheDir?: string;
2209
+ /** Loopback SOCKS5 port. Default 9050. */
2210
+ socksPort?: number;
2211
+ /** Bootstrap deadline in ms. Default 180_000. */
2212
+ bootstrapTimeoutMs?: number;
2213
+ /** Logger. Default: no-op. */
2214
+ log?: (msg: string) => void;
2215
+ /** os.platform() override (tests). */
2216
+ platform?: string;
2217
+ /** os.arch() override (tests). */
2218
+ arch?: string;
2219
+ }
2220
+ /**
2221
+ * Downloads (if needed) + spawns a managed `anon` daemon and waits for its SOCKS5
2222
+ * port to bind. Returns a {@link ManagedAnonProxy} whose `socksProxy` is ready for
2223
+ * `transport: { type: 'socks5', socksProxy }`.
2224
+ *
2225
+ * @throws If the platform is unsupported, the checksum fails, or anon never binds.
2226
+ */
2227
+ declare function startManagedAnonProxy(options?: StartManagedAnonProxyOptions): Promise<ManagedAnonProxy>;
2228
+
1133
2229
  /**
1134
2230
  * Settlement info produced by buildSettlementInfo().
1135
2231
  * Extends the core SettlementConfig shape with ilpAddress for client use.
@@ -1141,6 +2237,27 @@ interface ClientSettlementInfo {
1141
2237
  preferredTokens?: Record<string, string>;
1142
2238
  tokenNetworks?: Record<string, string>;
1143
2239
  }
2240
+ /**
2241
+ * Applies named-network preset defaults to a client config.
2242
+ *
2243
+ * When `config.network` is set and != `'custom'`, the settlement-related
2244
+ * fields are defaulted from the shared core presets (`resolveClientNetwork`):
2245
+ * RPC/GraphQL URLs, supported chain identifiers, preferred tokens, EVM
2246
+ * TokenNetwork addresses, and the Solana/Mina channel params. Any explicit
2247
+ * per-chain field on `config` OVERRIDES the preset (explicit always wins).
2248
+ *
2249
+ * `'custom'` and the unset case both pass `config` through untouched, keeping
2250
+ * the fully-manual path and full backward compatibility.
2251
+ *
2252
+ * @returns A shallow copy with preset defaults merged in (never mutates input).
2253
+ */
2254
+ declare function applyNetworkPresets(config: ToonClientConfig): ToonClientConfig;
2255
+ /**
2256
+ * Returns per-chain settlement readiness for the configured `network` tier,
2257
+ * mirroring the townhouse node's status. Returns `undefined` when `network` is
2258
+ * unset or `'custom'` (no preset tier to report on).
2259
+ */
2260
+ declare function getNetworkStatus(config: ToonClientConfig): NetworkFamilyStatus | undefined;
1144
2261
  /**
1145
2262
  * Validates ToonClient configuration.
1146
2263
  *
@@ -1153,10 +2270,30 @@ declare function validateConfig(config: ToonClientConfig): void;
1153
2270
  * The resolved config type after defaults are applied.
1154
2271
  * secretKey is guaranteed to be present (auto-generated if omitted).
1155
2272
  */
1156
- type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'evmPrivateKey' | 'supportedChains' | 'settlementAddresses' | 'preferredTokens' | 'tokenNetworks' | 'btpUrl' | 'btpAuthToken' | 'btpPeerId' | 'chainRpcUrls' | 'initialDeposit' | 'settlementTimeout' | 'channelStorePath' | 'knownPeers' | 'destinationAddress'>> & {
2273
+ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'mnemonic' | 'mnemonicAccountIndex' | 'evmPrivateKey' | 'network' | 'supportedChains' | 'settlementAddresses' | 'preferredTokens' | 'tokenNetworks' | 'btpUrl' | 'btpAuthToken' | 'btpPeerId' | 'connectorHttpEndpoint' | 'connectorSupportsUpgrade' | 'chainRpcUrls' | 'initialDeposit' | 'settlementTimeout' | 'solanaChannel' | 'minaChannel' | 'channelStorePath' | 'knownPeers' | 'destinationAddress' | 'transport' | 'managedAnonProxy' | 'managedAnonSocksPort'>> & {
1157
2274
  connector?: unknown;
1158
2275
  /** Always present after applyDefaults() — derived from secretKey if not explicitly provided */
1159
2276
  evmPrivateKey: string | Uint8Array;
2277
+ /**
2278
+ * BIP-39 phrase retained so `ToonClient.start()` can derive the Solana/Mina
2279
+ * keys asynchronously and register the corresponding signers. The Nostr/EVM
2280
+ * keys are already resolved synchronously into `secretKey`/`evmPrivateKey`.
2281
+ */
2282
+ mnemonic?: string;
2283
+ /**
2284
+ * BIP-44 account index for mnemonic-based derivation (defaults to 0).
2285
+ * Retained so `ToonClient.start()` derives the Solana/Mina signers at the
2286
+ * same index as the synchronously-resolved Nostr/EVM keys.
2287
+ */
2288
+ mnemonicAccountIndex?: number;
2289
+ /** Transport privacy config (optional — defaults to direct). */
2290
+ transport?: ClientTransportConfig;
2291
+ /** Named network tier, retained for `getNetworkStatus()`. */
2292
+ network?: ToonClientConfig['network'];
2293
+ /** Self-managed `anon` SOCKS5h proxy opt-out (default auto). */
2294
+ managedAnonProxy?: boolean;
2295
+ /** Loopback SOCKS port for the managed `anon` daemon (default 9050). */
2296
+ managedAnonSocksPort?: number;
1160
2297
  supportedChains?: string[];
1161
2298
  settlementAddresses?: Record<string, string>;
1162
2299
  preferredTokens?: Record<string, string>;
@@ -1164,9 +2301,13 @@ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'evmPrivateK
1164
2301
  btpUrl?: string;
1165
2302
  btpAuthToken?: string;
1166
2303
  btpPeerId?: string;
2304
+ connectorHttpEndpoint?: string;
2305
+ connectorSupportsUpgrade?: boolean;
1167
2306
  chainRpcUrls?: Record<string, string>;
1168
2307
  initialDeposit?: string;
1169
2308
  settlementTimeout?: number;
2309
+ solanaChannel?: ToonClientConfig['solanaChannel'];
2310
+ minaChannel?: ToonClientConfig['minaChannel'];
1170
2311
  channelStorePath?: string;
1171
2312
  knownPeers?: {
1172
2313
  pubkey: string;
@@ -1180,12 +2321,412 @@ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'evmPrivateK
1180
2321
  * Auto-generates a Nostr keypair when secretKey is omitted.
1181
2322
  * Derives btpUrl from connectorUrl when not provided.
1182
2323
  */
1183
- declare function applyDefaults(config: ToonClientConfig): ResolvedConfig;
2324
+ declare function applyDefaults(rawConfig: ToonClientConfig): ResolvedConfig;
1184
2325
  /**
1185
2326
  * Builds SettlementConfig from client config.
1186
2327
  * Returns undefined if no settlement-related config is present.
1187
2328
  */
1188
- declare function buildSettlementInfo(config: ToonClientConfig): ClientSettlementInfo | undefined;
2329
+ declare function buildSettlementInfo(rawConfig: ToonClientConfig): ClientSettlementInfo | undefined;
2330
+
2331
+ /**
2332
+ * Pet DVM Client-Side Types
2333
+ *
2334
+ * Locally defined types for pet DVM interaction utilities.
2335
+ * These mirror server-side types but do NOT import from @toon-protocol/pet-dvm,
2336
+ * @toon-protocol/pet-circuit, or @toon-protocol/memvid-node.
2337
+ *
2338
+ * @module pet/types
2339
+ */
2340
+ /** Plain-number stat values (all clamped to [1, 100]) */
2341
+ interface StatValues {
2342
+ hunger: number;
2343
+ happiness: number;
2344
+ health: number;
2345
+ hygiene: number;
2346
+ energy: number;
2347
+ }
2348
+ /** Metadata for a pet DVM provider discovered via Kind 10035 events */
2349
+ interface PetDvmProvider {
2350
+ /** ILP address of the provider's connector */
2351
+ ilpAddress: string;
2352
+ /** Per-interaction cost from skill.pricing['5900'] */
2353
+ pricing: string;
2354
+ /** Provider's Nostr pubkey (cryptographically bound from event.pubkey) */
2355
+ pubkey: string;
2356
+ /** Feature list from skill.features */
2357
+ features: string[];
2358
+ }
2359
+ /** Parameters for building a Kind 5900 pet interaction request */
2360
+ interface PetInteractionRequestParams {
2361
+ /** Blobbi identifier (non-empty string) */
2362
+ blobbiId: string;
2363
+ /** Action type (0-10, maps to Feed/Play/Clean/etc.) */
2364
+ actionType: number;
2365
+ /** Item identifier (>= 0) */
2366
+ itemId: number;
2367
+ /** Token cost for this interaction (>= 0) */
2368
+ tokenCost: number;
2369
+ /** Whether the pet is currently sleeping */
2370
+ isSleeping: boolean;
2371
+ }
2372
+ /** Unsigned Nostr event structure compatible with nostr-tools finalizeEvent */
2373
+ interface UnsignedNostrEvent {
2374
+ kind: number;
2375
+ created_at: number;
2376
+ tags: string[][];
2377
+ content: string;
2378
+ }
2379
+ /** Parsed result data from Kind 6900 DVM response (base64 JSON in IlpSendResult.data) */
2380
+ interface PetInteractionResultData {
2381
+ /** Current stat values */
2382
+ stats: StatValues;
2383
+ /** Current stage (0=Egg, 1=Baby, 2=Adult) */
2384
+ stage: number;
2385
+ /** Current evolution cycle (>= 0) */
2386
+ cycle: number;
2387
+ /** Unix timestamp of last interaction */
2388
+ lastInteraction: number;
2389
+ /** 64-char hex BLAKE3 hash of brain state */
2390
+ brainHash: string;
2391
+ /** Per-action-type cooldown timestamps */
2392
+ cooldownTimestamps: number[];
2393
+ }
2394
+ /** Stat snapshot used in interaction result content */
2395
+ interface InteractionResultContent {
2396
+ priorStats: StatValues;
2397
+ decayedStats: StatValues;
2398
+ finalStats: StatValues;
2399
+ cycle: number;
2400
+ stage: number;
2401
+ tokenCost: number;
2402
+ }
2403
+ /** Proof status of a Kind 14919 event */
2404
+ type ProofStatus = 'optimistic' | 'proven';
2405
+ /** Parameters for building a Kind 30402 pet-for-sale classified listing */
2406
+ interface PetListingParams {
2407
+ /** Blobbi identifier (non-empty string) — used as the 'd' tag */
2408
+ blobbiId: string;
2409
+ /** Asking price in USDC (> 0) */
2410
+ askPriceUsdc: number;
2411
+ /** 64-char hex lifecycleHash from on-chain PetZkApp state */
2412
+ lifecycleHash: string;
2413
+ /** Cumulative PET tokens spent (numeric string, >= "0") */
2414
+ totalSpent: string;
2415
+ /** Current stage: 0=Egg, 1=Baby, 2=Adult */
2416
+ stage: number;
2417
+ /** Current pet stats */
2418
+ stats: StatValues;
2419
+ /** Seller's Nostr pubkey (64-char hex) */
2420
+ sellerPubkey: string;
2421
+ /** Preferred relay URL for event relay routing */
2422
+ relayUrl: string;
2423
+ /** Listing expiry as unix timestamp */
2424
+ expiresAt: number;
2425
+ }
2426
+ /** A parsed pet-for-sale listing (extends PetListingParams with event metadata) */
2427
+ interface PetListing extends PetListingParams {
2428
+ /** Nostr event ID of the kind:30402 listing event */
2429
+ eventId: string;
2430
+ /** Unix timestamp when the listing event was created */
2431
+ createdAt: number;
2432
+ }
2433
+ /** Filter options for filterPetListings() */
2434
+ interface PetListingFilterOptions {
2435
+ /** Only include listings for pets at or above this stage */
2436
+ minStage?: number;
2437
+ /** Only include listings at or below this USDC price */
2438
+ maxAskPriceUsdc?: number;
2439
+ /** Only include listings where totalSpent >= this value (numeric string comparison) */
2440
+ minTotalSpent?: string;
2441
+ /** Only include listings from this seller pubkey */
2442
+ sellerPubkey?: string;
2443
+ }
2444
+ /** Parameters for building a Kind 5900 pet purchase request (transfer-ownership) */
2445
+ interface PetPurchaseRequestParams {
2446
+ /** Blobbi identifier being purchased */
2447
+ blobbiId: string;
2448
+ /** Nostr event ID of the kind:30402 listing being purchased */
2449
+ listingEventId: string;
2450
+ /** Buyer's Nostr pubkey (64-char hex) */
2451
+ buyerPubkey: string;
2452
+ /** Token cost for the purchase (>= 0) */
2453
+ tokenCost: number;
2454
+ /** Seller's Nostr pubkey — ILP payment routed to this pubkey (64-char hex) */
2455
+ sellerPubkey: string;
2456
+ }
2457
+ /** Parsed data from a Kind 14919 pet interaction event */
2458
+ interface PetInteractionEventData {
2459
+ /** Blobbi identifier from 'd' tag */
2460
+ blobbiId: string;
2461
+ /** Action type from 'action' tag */
2462
+ actionType: number;
2463
+ /** Item identifier from 'item' tag */
2464
+ itemId: number;
2465
+ /** Token cost from 'cost' tag */
2466
+ tokenCost: number;
2467
+ /** Evolution cycle from 'cycle' tag */
2468
+ cycle: number;
2469
+ /** Stage from 'stage' tag */
2470
+ stage: number;
2471
+ /** Brain hash from 'brain_hash' tag */
2472
+ brainHash: string;
2473
+ /** Proof status: 'optimistic' (no proof tag) or 'proven' (has proof + mina_tx tags) */
2474
+ proofStatus: ProofStatus;
2475
+ /** Parsed content JSON (stats before/after) */
2476
+ content: InteractionResultContent | null;
2477
+ /** Base64 proof data (only present when proven) */
2478
+ proof?: string;
2479
+ /** Mina transaction hash (only present when proven) */
2480
+ minaTx?: string;
2481
+ }
2482
+
2483
+ /**
2484
+ * Pet DVM Provider Discovery
2485
+ *
2486
+ * Filters Kind 10035 service discovery events to find providers that
2487
+ * support Pet DVM interactions (Kind 5900).
2488
+ *
2489
+ * @module pet/filterPetDvmProviders
2490
+ */
2491
+
2492
+ /**
2493
+ * Minimal Nostr event shape needed for filtering.
2494
+ * Using a local interface to avoid importing nostr-tools types.
2495
+ */
2496
+ interface NostrEventLike$3 {
2497
+ kind: number;
2498
+ pubkey: string;
2499
+ content: string;
2500
+ tags: string[][];
2501
+ id: string;
2502
+ sig: string;
2503
+ created_at: number;
2504
+ }
2505
+ /**
2506
+ * Filter Kind 10035 service discovery events to find pet DVM providers.
2507
+ *
2508
+ * Accepts raw NostrEvent[] and internally parses content via parseServiceDiscovery.
2509
+ * Filters events where skill.kinds includes 5900 (PET_INTERACTION_REQUEST_KIND).
2510
+ * Returns provider metadata sorted by price ascending (cheapest first).
2511
+ *
2512
+ * Handles missing/malformed skill descriptors gracefully (returns empty array, no throw).
2513
+ *
2514
+ * @param events - Array of raw Nostr events (kind:10035)
2515
+ * @returns Array of PetDvmProvider metadata, sorted by price ascending
2516
+ */
2517
+ declare function filterPetDvmProviders(events: NostrEventLike$3[]): PetDvmProvider[];
2518
+
2519
+ /**
2520
+ * Pet Interaction Request Builder (Kind 5900)
2521
+ *
2522
+ * Builds unsigned Kind 5900 Nostr events for pet DVM interaction requests.
2523
+ * Compatible with nostr-tools/pure finalizeEvent for signing.
2524
+ *
2525
+ * @module pet/buildPetInteractionRequest
2526
+ */
2527
+
2528
+ /**
2529
+ * Build an unsigned Kind 5900 pet interaction request event.
2530
+ *
2531
+ * All tag values are stringified per Nostr protocol convention.
2532
+ * The returned event is compatible with nostr-tools `finalizeEvent`.
2533
+ *
2534
+ * @param params - Typed interaction parameters
2535
+ * @returns Unsigned Nostr event ready for signing
2536
+ * @throws ValidationError for invalid input
2537
+ */
2538
+ declare function buildPetInteractionRequest(params: PetInteractionRequestParams): UnsignedNostrEvent;
2539
+
2540
+ /**
2541
+ * Pet Interaction Result Parser (Kind 6900)
2542
+ *
2543
+ * Decodes base64-encoded JSON from IlpSendResult.data field.
2544
+ * Uses browser-safe atob() -- NOT Node.js Buffer -- for ditto React SPA compatibility.
2545
+ *
2546
+ * @module pet/parsePetInteractionResult
2547
+ */
2548
+
2549
+ /**
2550
+ * Parse base64-encoded JSON result data from a Kind 6900 DVM response.
2551
+ *
2552
+ * Uses atob() for browser compatibility (ditto React SPA).
2553
+ * Returns null for malformed/missing data (no throw).
2554
+ *
2555
+ * Validates:
2556
+ * - brainHash is 64-char hex
2557
+ * - stats has all 5 fields
2558
+ * - cycle >= 0
2559
+ * - stage 0-2
2560
+ *
2561
+ * @param data - Base64-encoded JSON string from IlpSendResult.data
2562
+ * @returns Parsed PetInteractionResultData or null if invalid
2563
+ */
2564
+ declare function parsePetInteractionResult(data: string): PetInteractionResultData | null;
2565
+
2566
+ /**
2567
+ * Pet Interaction Event Parser (Kind 14919)
2568
+ *
2569
+ * Parses Kind 14919 optimistic/proven pet interaction events.
2570
+ * Detects proof status from presence of 'proof' + 'mina_tx' tags.
2571
+ *
2572
+ * @module pet/parsePetInteractionEvent
2573
+ */
2574
+
2575
+ /**
2576
+ * Minimal Nostr event shape needed for parsing.
2577
+ */
2578
+ interface NostrEventLike$2 {
2579
+ kind: number;
2580
+ pubkey: string;
2581
+ content: string;
2582
+ tags: string[][];
2583
+ id: string;
2584
+ sig: string;
2585
+ created_at: number;
2586
+ }
2587
+ /**
2588
+ * Parse a Kind 14919 pet interaction event.
2589
+ *
2590
+ * Extracts all tag values and detects proof status:
2591
+ * - 'optimistic': no 'proof' tag
2592
+ * - 'proven': has 'proof' + 'mina_tx' tags
2593
+ *
2594
+ * Returns null if required tags are missing.
2595
+ *
2596
+ * @param event - A Nostr event (Kind 14919)
2597
+ * @returns Parsed PetInteractionEventData or null if malformed
2598
+ */
2599
+ declare function parsePetInteractionEvent(event: NostrEventLike$2): PetInteractionEventData | null;
2600
+
2601
+ /**
2602
+ * Pet Listing Event Builder (Kind 30402)
2603
+ *
2604
+ * Builds unsigned Kind 30402 (NIP-99 classified listing) Nostr events
2605
+ * for pet-for-sale marketplace listings. Every listing includes a
2606
+ * verified biography attachment (lifecycleHash + totalSpent) so buyers
2607
+ * can verify the listing against on-chain PetZkApp state.
2608
+ *
2609
+ * Browser-compatible — no Node.js-only imports.
2610
+ *
2611
+ * @module pet/buildPetListingEvent
2612
+ */
2613
+
2614
+ /**
2615
+ * Build an unsigned Kind 30402 pet-for-sale classified listing event.
2616
+ *
2617
+ * The listing uses the NIP-99 classified listing format with TOON-specific
2618
+ * extension tags for verified biography (lifecycleHash, totalSpent).
2619
+ * The `d` tag is set to `blobbiId` for stable parameterized replaceability —
2620
+ * republishing with the same `d` tag updates the listing on relays.
2621
+ *
2622
+ * The returned event is compatible with nostr-tools `finalizeEvent`.
2623
+ *
2624
+ * @param params - Typed listing parameters
2625
+ * @returns Unsigned Nostr event ready for signing and publishing
2626
+ */
2627
+ declare function buildPetListingEvent(params: PetListingParams): UnsignedNostrEvent;
2628
+
2629
+ /**
2630
+ * Pet Listing Parser (Kind 30402)
2631
+ *
2632
+ * Parses Kind 30402 (NIP-99 classified listing) Nostr events into
2633
+ * typed PetListing objects. Returns null for invalid or malformed events.
2634
+ *
2635
+ * Browser-compatible — no Node.js-only imports.
2636
+ *
2637
+ * @module pet/parsePetListing
2638
+ */
2639
+
2640
+ /** Minimal Nostr event shape required for parsing */
2641
+ interface NostrEventLike$1 {
2642
+ id: string;
2643
+ kind: number;
2644
+ pubkey: string;
2645
+ tags: string[][];
2646
+ content: string;
2647
+ created_at: number;
2648
+ }
2649
+ /**
2650
+ * Parse a Kind 30402 pet classified listing event into a PetListing.
2651
+ *
2652
+ * Validation rules:
2653
+ * - event.kind must be 30402
2654
+ * - 'd' tag must be present and non-empty
2655
+ * - 'price' tag must be present with a valid positive numeric first element
2656
+ * - 'lifecycle_hash' tag must be a 64-char hex string
2657
+ * - 'total_spent' tag must be a valid non-negative numeric string
2658
+ * - 'stage' tag must be present
2659
+ *
2660
+ * Stats are parsed from content JSON; unparseable content falls back to DEFAULT_STATS.
2661
+ *
2662
+ * @param event - A Nostr event (expected Kind 30402)
2663
+ * @returns Parsed PetListing or null if invalid
2664
+ */
2665
+ declare function parsePetListing(event: NostrEventLike$1): PetListing | null;
2666
+
2667
+ /**
2668
+ * Pet Listing Discovery Filter
2669
+ *
2670
+ * Filters and sorts Kind 30402 pet marketplace listing events into
2671
+ * typed PetListing objects. Handles expiry, stage, price, biography
2672
+ * value, and seller filtering. Results sorted by totalSpent descending
2673
+ * (highest biography value first) to surface the most battle-hardened pets.
2674
+ *
2675
+ * Browser-compatible — no Node.js-only imports.
2676
+ *
2677
+ * @module pet/filterPetListings
2678
+ */
2679
+
2680
+ /** Minimal Nostr event shape accepted by the filter */
2681
+ interface NostrEventLike {
2682
+ id: string;
2683
+ kind: number;
2684
+ pubkey: string;
2685
+ tags: string[][];
2686
+ content: string;
2687
+ created_at: number;
2688
+ }
2689
+ /**
2690
+ * Filter and sort Kind 30402 pet marketplace listing events.
2691
+ *
2692
+ * Parsing is done via parsePetListing — invalid events are silently dropped.
2693
+ * Expired listings (expiration tag < current unix time) are excluded.
2694
+ * Options allow additional filtering by stage, price, biography value, and seller.
2695
+ * Results are sorted by totalSpent descending (highest biography value first).
2696
+ *
2697
+ * @param events - Array of raw Nostr events to filter
2698
+ * @param options - Optional filter criteria
2699
+ * @returns Filtered and sorted array of PetListing objects
2700
+ */
2701
+ declare function filterPetListings(events: NostrEventLike[], options?: PetListingFilterOptions): PetListing[];
2702
+
2703
+ /**
2704
+ * Pet Purchase Request Builder (Kind 5900, action type 9)
2705
+ *
2706
+ * Builds unsigned Kind 5900 Nostr events for pet transfer-ownership
2707
+ * purchase requests. Action type 9 is a reserved slot in the pet DVM
2708
+ * protocol — this event signals purchase intent and routes ILP payment
2709
+ * to the seller. The actual Mina on-chain ownership transfer (PetZkApp
2710
+ * .transferOperator) is handled by downstream stories.
2711
+ *
2712
+ * Browser-compatible — no Node.js-only imports.
2713
+ *
2714
+ * @module pet/buildPetPurchaseRequest
2715
+ */
2716
+
2717
+ /**
2718
+ * Build an unsigned Kind 5900 pet purchase request event.
2719
+ *
2720
+ * Reuses the existing pet interaction event kind (5900) with action type 9
2721
+ * (transfer-ownership). The `listing` tag references the kind:30402 listing
2722
+ * event being purchased. The `p` tag routes ILP payment to the seller.
2723
+ *
2724
+ * The returned event is compatible with nostr-tools `finalizeEvent`.
2725
+ *
2726
+ * @param params - Typed purchase request parameters
2727
+ * @returns Unsigned Nostr event ready for signing and publishing
2728
+ */
2729
+ declare function buildPetPurchaseRequest(params: PetPurchaseRequestParams): UnsignedNostrEvent;
1189
2730
 
1190
2731
  /**
1191
2732
  * Full multi-chain identity derived from a single BIP-39 mnemonic.
@@ -1384,16 +2925,38 @@ declare function generateMnemonic(): string;
1384
2925
  * Validate a BIP-39 mnemonic phrase.
1385
2926
  */
1386
2927
  declare function validateMnemonic(mnemonic: string): boolean;
2928
+ /**
2929
+ * Synchronously derive ONLY the Nostr secp256k1 key (NIP-06) from a mnemonic.
2930
+ *
2931
+ * The EVM key shares this same secp256k1 key. Solana (Ed25519) and Mina
2932
+ * (Pallas) require async dynamic imports — use {@link deriveFullIdentity} for
2933
+ * those. This sync subset exists so `ToonClient`'s synchronous constructor can
2934
+ * resolve the Nostr/EVM identity from a `mnemonic` config field without an
2935
+ * async factory; the client derives Solana/Mina lazily in `start()`.
2936
+ */
2937
+ declare function deriveNostrKeyFromMnemonic(mnemonic: string, accountIndex?: number): {
2938
+ secretKey: Uint8Array;
2939
+ pubkey: string;
2940
+ };
1387
2941
  /**
1388
2942
  * Derive a full multi-chain ToonIdentity from a BIP-39 mnemonic.
1389
2943
  *
2944
+ * All four chains vary by `accountIndex` (default 0), matching the SDK's
2945
+ * {@link https://www.npmjs.com/package/@toon-protocol/sdk `fromMnemonicFull`}
2946
+ * path-per-index scheme so a non-zero index produces the SAME addresses as
2947
+ * `fromMnemonicFull(mnemonic, { accountIndex })`. Index 0 is unchanged from the
2948
+ * historical fixed paths (back-compat).
2949
+ *
1390
2950
  * Chains derived:
1391
- * - Nostr (secp256k1): m/44'/1237'/0'/0/0
2951
+ * - Nostr (secp256k1): m/44'/1237'/0'/0/{accountIndex}
1392
2952
  * - 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
2953
+ * - Solana (Ed25519): m/44'/501'/{accountIndex}'/0' (SLIP-0010)
2954
+ * - Mina (Pallas): m/44'/12586'/{accountIndex}'/0/0
2955
+ *
2956
+ * @param mnemonic - A valid BIP-39 mnemonic (12 or 24 words).
2957
+ * @param accountIndex - BIP-44 account index (default 0).
1395
2958
  */
1396
- declare function deriveFullIdentity(mnemonic: string): Promise<ToonIdentity>;
2959
+ declare function deriveFullIdentity(mnemonic: string, accountIndex?: number): Promise<ToonIdentity>;
1397
2960
  /**
1398
2961
  * Derive a partial identity from an nsec (Nostr-only private key).
1399
2962
  * Nostr + EVM share the same secp256k1 key.
@@ -1441,4 +3004,77 @@ declare function parseBackupPayload(content: string): VaultData;
1441
3004
  */
1442
3005
  declare function isPrfSupported(): boolean;
1443
3006
 
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 };
3007
+ /**
3008
+ * Node-only encrypted mnemonic keystore for @toon-protocol/client.
3009
+ *
3010
+ * Mirrors the Townhouse node wallet crypto (`packages/townhouse/src/wallet/
3011
+ * crypto.ts`): a BIP-39 mnemonic is encrypted at rest with scrypt (KDF) +
3012
+ * AES-256-GCM (authenticated encryption), serialized as JSON, and written to
3013
+ * disk with mode 0o600. Decryption requires the operator password; a wrong
3014
+ * password fails the GCM auth-tag verification and throws.
3015
+ *
3016
+ * This is the Node-side counterpart to the browser Passkey/IndexedDB
3017
+ * `KeyManager`/`KeyVault` flow — it does NOT touch those. It is guarded against
3018
+ * browser bundling: every entry point throws if `node:crypto`/`node:fs` are not
3019
+ * available (e.g. when accidentally imported in a browser bundle).
3020
+ *
3021
+ * @module
3022
+ */
3023
+ /**
3024
+ * Encrypted keystore file format (JSON, all binary fields base64-encoded).
3025
+ * Wire-compatible with Townhouse's `EncryptedWallet`.
3026
+ */
3027
+ interface EncryptedKeystore {
3028
+ /** scrypt salt (base64). */
3029
+ salt: string;
3030
+ /** AES-GCM initialization vector (base64). */
3031
+ iv: string;
3032
+ /** AES-256-GCM ciphertext (base64). */
3033
+ ciphertext: string;
3034
+ /** AES-GCM authentication tag (base64). */
3035
+ tag: string;
3036
+ /** Envelope version for forward-compat (currently 1). */
3037
+ version?: number;
3038
+ }
3039
+ /**
3040
+ * Encrypt a mnemonic with a password using scrypt + AES-256-GCM.
3041
+ * Returns the JSON-serializable encrypted envelope (does NOT write to disk).
3042
+ */
3043
+ declare function encryptMnemonic(mnemonic: string, password: string): EncryptedKeystore;
3044
+ /**
3045
+ * Decrypt an encrypted keystore envelope with a password.
3046
+ * Throws on a wrong password (GCM auth-tag verification failure) or corruption.
3047
+ */
3048
+ declare function decryptMnemonic(encrypted: EncryptedKeystore, password: string): string;
3049
+ /**
3050
+ * Generate a fresh 12-word BIP-39 mnemonic, encrypt it under `password`, and
3051
+ * write the encrypted keystore to `path` with mode 0o600.
3052
+ *
3053
+ * Returns the mnemonic (for one-time display/backup) alongside the encrypted
3054
+ * envelope. The caller is responsible for displaying the mnemonic securely and
3055
+ * NOT persisting it in plaintext.
3056
+ */
3057
+ declare function generateKeystore(path: string, password: string): {
3058
+ mnemonic: string;
3059
+ keystore: EncryptedKeystore;
3060
+ };
3061
+ /**
3062
+ * Import an existing BIP-39 mnemonic (12 or 24 words), encrypt it under
3063
+ * `password`, and write the encrypted keystore to `path` with mode 0o600.
3064
+ *
3065
+ * Throws if the mnemonic is not a valid BIP-39 phrase (wrong checksum/word
3066
+ * count) before any file is written.
3067
+ */
3068
+ declare function importKeystore(path: string, mnemonic: string, password: string): EncryptedKeystore;
3069
+ /**
3070
+ * Load and decrypt a keystore file at `path` with `password`, returning the
3071
+ * plaintext mnemonic. Throws on a wrong password or corruption.
3072
+ */
3073
+ declare function loadKeystore(path: string, password: string): string;
3074
+ /**
3075
+ * Serialize and write an encrypted keystore to disk with mode 0o600
3076
+ * (owner read/write only), mirroring the Townhouse wallet file permissions.
3077
+ */
3078
+ declare function writeKeystoreFile(path: string, keystore: EncryptedKeystore): void;
3079
+
3080
+ 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 DiscoveredIlpPeer, type EVMClaimMessage, type EncryptedKeystore, EvmSigner, HS_HOSTNAME_MAX_LENGTH, HS_HOSTNAME_REGEX, HttpConnectorAdmin, type HttpConnectorAdminConfig, HttpIlpClient, type HttpIlpClientConfig, HttpRuntimeClient, type HttpRuntimeClientConfig, ILP_CLAIM_HEADER, ILP_CLAIM_WRAPPED_HEADER, ILP_PEER_ID_HEADER, type IlpTransportChoice, 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 SelectIlpTransportOptions, 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, httpEndpointToBtpUrl, importKeystore, isPrfSupported, isRoutableHsHostname, loadKeystore, parseBackupPayload, parsePetInteractionEvent, parsePetInteractionResult, parsePetListing, readDiscoveredIlpPeer, readMinaDepositTotal, requestBlobStorage, selectAnonAsset, selectIlpTransport, startManagedAnonProxy, validateConfig, validateMnemonic, withRetry, writeKeystoreFile };