@toon-protocol/client 0.10.0 → 0.12.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,5 +1,5 @@
1
1
  import * as _toon_protocol_core from '@toon-protocol/core';
2
- import { IlpPeerInfo, NetworkFamilyStatus, IlpSendResult, IlpClient, ConnectorAdminClient, ConnectorChannelClient, OpenChannelParams, OpenChannelResult, ChannelState } from '@toon-protocol/core';
2
+ import { IlpPeerInfo, IlpClient, IlpSendResult, NetworkFamilyStatus, ConnectorAdminClient, ConnectorChannelClient, OpenChannelParams, OpenChannelResult, ChannelState } from '@toon-protocol/core';
3
3
  import { NostrEvent, EventTemplate } from 'nostr-tools/pure';
4
4
  import { PrivateKeyAccount } from 'viem/accounts';
5
5
 
@@ -213,6 +213,30 @@ interface ToonClientConfig {
213
213
  * core release with these fields ship).
214
214
  */
215
215
  connectorHttpEndpoint?: string;
216
+ /**
217
+ * Connector-PROXY base URL for the deployed devnet/testnet edge (e.g.
218
+ * `https://proxy.devnet.toonprotocol.dev`). The connector is now a
219
+ * payment-proxy that serves ILP-over-HTTP at `POST /ilp`; setting `proxyUrl`
220
+ * is the high-level way to route paid writes through that proxy WITHOUT a
221
+ * persistent BTP socket.
222
+ *
223
+ * When set, `applyDefaults` derives `connectorHttpEndpoint` from it (appending
224
+ * `/ilp` to the base unless the URL already ends in `/ilp`), so the existing
225
+ * {@link HttpIlpClient} transport selection (`selectIlpTransport`) picks the
226
+ * stateless one-shot HTTP transport for `publishEvent`/`sendSwapPacket`/
227
+ * `sendPayment`. An explicit `connectorHttpEndpoint` always wins over this.
228
+ *
229
+ * The eventual home for these endpoints is a `@toon-protocol/core` devnet
230
+ * preset; until that ships, set this field explicitly.
231
+ */
232
+ proxyUrl?: string;
233
+ /**
234
+ * Faucet base URL for the deployed devnet (e.g.
235
+ * `https://faucet.devnet.toonprotocol.dev`). Consumed by the {@link fundWallet}
236
+ * helper to drip test funds to the client's chain address before publishing.
237
+ * Has no effect on the runtime transport; it is config carried for tooling/e2e.
238
+ */
239
+ faucetUrl?: string;
216
240
  /**
217
241
  * Whether the uplink connector accepts a BTP `Upgrade` over its HTTP endpoint
218
242
  * (mirrors `IlpPeerInfo.supportsUpgrade`, toon PR #29). Only consulted when
@@ -267,34 +291,6 @@ interface ToonClientConfig {
267
291
  minaChannel?: MinaChannelClientOptions;
268
292
  /** File path for persisting payment channel nonce/amount state across restarts */
269
293
  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;
298
294
  /** Nostr relay URL for peer discovery. Default: 'ws://localhost:7100' */
299
295
  relayUrl?: string;
300
296
  /**
@@ -394,23 +390,6 @@ interface SignedBalanceProof extends BalanceProofParams {
394
390
  tokenId: string;
395
391
  };
396
392
  }
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
393
 
415
394
  /**
416
395
  * Client-side helper for kind:5094 Arweave blob storage DVM requests.
@@ -509,6 +488,417 @@ interface RequestBlobStorageResult {
509
488
  */
510
489
  declare function requestBlobStorage(client: ToonClient, secretKey: Uint8Array, params: RequestBlobStorageParams): Promise<RequestBlobStorageResult>;
511
490
 
491
+ interface BtpRuntimeClientConfig {
492
+ btpUrl: string;
493
+ peerId: string;
494
+ authToken: string;
495
+ /** Max reconnection attempts on send failure (default: 3) */
496
+ maxRetries?: number;
497
+ /** Delay between reconnection attempts in ms (default: 1000) */
498
+ retryDelay?: number;
499
+ /** Custom WebSocket constructor (for testing / custom transports). */
500
+ createWebSocket?: (url: string) => WebSocket;
501
+ }
502
+ /**
503
+ * BTP transport implementing IlpClient.
504
+ * Uses IsomorphicBtpClient (browser-native, no Node.js dependencies).
505
+ */
506
+ declare class BtpRuntimeClient implements IlpClient {
507
+ private btpClient;
508
+ private readonly config;
509
+ private _isConnected;
510
+ constructor(config: BtpRuntimeClientConfig);
511
+ /**
512
+ * Connects to the BTP peer via WebSocket.
513
+ */
514
+ connect(): Promise<void>;
515
+ /**
516
+ * Attempts to reconnect by creating a fresh client and connecting.
517
+ */
518
+ reconnect(): Promise<void>;
519
+ /**
520
+ * Disconnects from the BTP peer.
521
+ */
522
+ disconnect(): Promise<void>;
523
+ get isConnected(): boolean;
524
+ /**
525
+ * Sends an ILP packet via BTP with auto-reconnect on connection errors.
526
+ * Satisfies IlpClient interface.
527
+ */
528
+ sendIlpPacket(params: {
529
+ destination: string;
530
+ amount: string;
531
+ data: string;
532
+ timeout?: number;
533
+ }): Promise<IlpSendResult>;
534
+ /**
535
+ * Sends a balance proof claim via BTP protocol data, then sends an ILP packet.
536
+ * Auto-reconnects on connection errors.
537
+ */
538
+ sendIlpPacketWithClaim(params: {
539
+ destination: string;
540
+ amount: string;
541
+ data: string;
542
+ timeout?: number;
543
+ }, claim: Record<string, unknown>): Promise<IlpSendResult>;
544
+ /**
545
+ * Send a standalone `payment-channel-claim` BTP MESSAGE (no ILP packet
546
+ * attached). The connector's ClaimReceiver consumes this fire-and-forget
547
+ * to register cumulative claim state independently of the per-packet
548
+ * forwarding path. Auto-reconnects on connection errors.
549
+ */
550
+ sendClaimMessage(claim: Record<string, unknown>): Promise<void>;
551
+ private _sendClaimMessageOnce;
552
+ /**
553
+ * Single-attempt ILP packet send. Reconnects if not connected.
554
+ */
555
+ private _sendIlpPacketOnce;
556
+ /**
557
+ * Single-attempt claim + ILP packet send. Reconnects if not connected.
558
+ */
559
+ private _sendIlpPacketWithClaimOnce;
560
+ }
561
+
562
+ /**
563
+ * ILP-over-HTTP (RFC-0035) transport for the TOON client.
564
+ *
565
+ * The connector now serves ILP-over-HTTP on the SAME port as BTP (connector
566
+ * PR #181). This adapter lets a client do stateless one-shot writes over HTTP
567
+ * (`POST /ilp`) and upgrade to a duplex BTP session when it needs to receive
568
+ * server-initiated packets or act as a peer.
569
+ *
570
+ * Wire contract (targets connector PR #181's `/ilp`):
571
+ * - One-shot write: `POST /ilp`
572
+ * body: OER-encoded ILP PREPARE (`application/octet-stream`)
573
+ * header: `ILP-Payment-Channel-Claim: base64(JSON of the claim)` — the
574
+ * SAME claim JSON the BTP path attaches as the
575
+ * `payment-channel-claim` protocolData entry.
576
+ * optional: `ILP-Peer-Id` + `Authorization: Bearer <secret>` identity.
577
+ * response: `200 OK` with an OER FULFILL or REJECT body. HTTP non-2xx is
578
+ * reserved for TRANSPORT errors (400/401/413/5xx); ILP-level
579
+ * rejects come back as a 200 + REJECT body.
580
+ * - Upgrade to BTP: standard HTTP `Upgrade` with `Sec-WebSocket-Protocol: btp`
581
+ * plus the same `ILP-Peer-Id` + `Authorization` headers. The connector
582
+ * pre-authenticates the BTP session from those headers (continuity), so
583
+ * after `101` we send BTP frames WITHOUT a separate in-band auth frame.
584
+ * Omitting the auth headers falls back to the normal BTP auth-frame flow.
585
+ *
586
+ * Reuses `serializeIlpPrepare`/`deserializeIlpPacket` from `btp/protocol.ts` —
587
+ * the SAME OER codec the BTP path uses. Claim signing/construction is owned by
588
+ * the caller (BootstrapService); this transport never builds or signs claims.
589
+ */
590
+
591
+ /** Header carrying the base64(JSON) payment-channel claim. */
592
+ declare const ILP_CLAIM_HEADER = "ILP-Payment-Channel-Claim";
593
+ /** Header carrying a NIP-59 wrapped (gift-wrapped) claim. */
594
+ declare const ILP_CLAIM_WRAPPED_HEADER = "ILP-Payment-Channel-Claim-Wrapped";
595
+ /** Header carrying the peer identity. */
596
+ declare const ILP_PEER_ID_HEADER = "ILP-Peer-Id";
597
+ interface HttpIlpClientConfig {
598
+ /** The peer's `POST /ilp` URL (the `httpEndpoint` from discovery). */
599
+ httpEndpoint: string;
600
+ /**
601
+ * Optional peer identity. With no `peerId`/`authToken` the connector treats
602
+ * the request as an anonymous no-auth peer (permissionless default) and
603
+ * derives an ephemeral id from the claim signer.
604
+ */
605
+ peerId?: string;
606
+ /** Bearer secret for `Authorization`. Omit for the no-auth peer path. */
607
+ authToken?: string;
608
+ /** Request timeout in milliseconds (default: 30000). */
609
+ timeout?: number;
610
+ /** Max retry attempts for transport-level network failures (default: 3). */
611
+ maxRetries?: number;
612
+ /** Initial retry delay in milliseconds (default: 1000). */
613
+ retryDelay?: number;
614
+ /** Custom fetch implementation (for testing / custom transports). */
615
+ httpClient?: typeof fetch;
616
+ /**
617
+ * Custom WebSocket constructor for the BTP upgrade path (for testing /
618
+ * custom transports). Forwarded to the underlying BtpRuntimeClient.
619
+ */
620
+ createWebSocket?: (url: string) => WebSocket;
621
+ }
622
+ /**
623
+ * Stateless ILP-over-HTTP transport implementing `IlpClient`.
624
+ *
625
+ * Use this for pure one-shot consumers (publish-and-forget writes). When the
626
+ * client needs a duplex session — to receive server-initiated packets or to act
627
+ * as a peer — call {@link upgradeToBtp} to obtain a connected BtpRuntimeClient
628
+ * that reuses the existing BTP code path.
629
+ */
630
+ declare class HttpIlpClient implements IlpClient {
631
+ private readonly httpEndpoint;
632
+ private readonly peerId;
633
+ private readonly authToken;
634
+ private readonly timeout;
635
+ private readonly retryConfig;
636
+ private readonly httpClient;
637
+ private readonly createWebSocket;
638
+ constructor(config: HttpIlpClientConfig);
639
+ /**
640
+ * Send an ILP PREPARE via `POST /ilp` WITHOUT a claim. The connector accepts
641
+ * this only on free/zero-amount routes; paid writes must use
642
+ * {@link sendIlpPacketWithClaim}. Satisfies the IlpClient interface.
643
+ */
644
+ sendIlpPacket(params: {
645
+ destination: string;
646
+ amount: string;
647
+ data: string;
648
+ timeout?: number;
649
+ }): Promise<IlpSendResult>;
650
+ /**
651
+ * Send an ILP PREPARE via `POST /ilp` with the payment-channel claim attached
652
+ * as the `ILP-Payment-Channel-Claim` header. `claim` is the SAME JSON object
653
+ * the BTP path attaches as the `payment-channel-claim` protocolData entry —
654
+ * we base64(JSON.stringify(claim)) it, byte-for-byte identical to BTP.
655
+ */
656
+ sendIlpPacketWithClaim(params: {
657
+ destination: string;
658
+ amount: string;
659
+ data: string;
660
+ timeout?: number;
661
+ }, claim: unknown): Promise<IlpSendResult>;
662
+ /**
663
+ * Upgrade to a duplex BTP session over the SAME endpoint.
664
+ *
665
+ * Derives the `ws(s)://` URL from `httpEndpoint`, opens a WebSocket with
666
+ * `Sec-WebSocket-Protocol: btp` and the same `ILP-Peer-Id` + `Authorization`
667
+ * headers, and returns a connected {@link BtpRuntimeClient}. When auth headers
668
+ * are present the connector pre-authenticates the session (no in-band auth
669
+ * frame); without them the BtpRuntimeClient falls back to the normal BTP
670
+ * auth-frame flow.
671
+ *
672
+ * NOTE: passing per-connection headers + a subprotocol to a WebSocket is
673
+ * Node-only (the `ws` package). Browsers cannot set arbitrary request headers
674
+ * on a WebSocket handshake, so a browser consumer must use the gateway
675
+ * transport or BTP-with-auth-frame instead.
676
+ */
677
+ upgradeToBtp(): Promise<BtpRuntimeClient>;
678
+ private authHeaders;
679
+ /**
680
+ * Single attempt: serialize the PREPARE, POST it, and map the response.
681
+ * @throws {NetworkError} On connection/timeout failures (retried).
682
+ * @throws {ConnectorError} On non-retryable transport errors (5xx / unexpected).
683
+ */
684
+ private postPrepare;
685
+ /**
686
+ * Map a `200 OK` body (OER FULFILL/REJECT) to an IlpSendResult; map a non-2xx
687
+ * to a transport error. Per the wire contract, ILP-level rejects arrive as a
688
+ * 200 + REJECT body — only HTTP non-2xx means a transport-layer failure.
689
+ */
690
+ private mapResponse;
691
+ private mapTransportError;
692
+ }
693
+ /**
694
+ * Derive the BTP WebSocket URL from a `POST /ilp` HTTP endpoint. The connector
695
+ * serves BTP on the SAME path, so we only swap the scheme (http→ws, https→wss).
696
+ */
697
+ declare function httpEndpointToBtpUrl(httpEndpoint: string): string;
698
+
699
+ /**
700
+ * Payment-aware HTTP fetch over TOON (the "h402" flow).
701
+ *
702
+ * This adapter makes paying for an HTTP resource transparent: it issues an
703
+ * ordinary HTTP request, and when the origin answers `402 Payment Required`
704
+ * with an x402-style challenge that offers a `toon-channel` payment option, it
705
+ * opens/reuses a payment channel, signs a balance-proof claim, and re-sends the
706
+ * SAME HTTP request as a "transparent HTTP-in-ILP" packet to the connector's
707
+ * `POST /ilp` endpoint (via {@link HttpIlpClient}). The connector terminates the
708
+ * payment, forwards the request to the origin, and returns the origin's HTTP
709
+ * response inside the ILP FULFILL `data`. We reconstruct a normal Web `Response`
710
+ * from those bytes — the caller never sees ILP.
711
+ *
712
+ * ─── x402 wire contract (the 402 challenge body) ────────────────────────────
713
+ * The connector side (the 402 greeting + the `accepts` entries) is a separate,
714
+ * NOT-YET-BUILT dependency, so we parse DEFENSIVELY (mirroring
715
+ * `readDiscoveredIlpPeer` in selectIlpTransport.ts): a slightly different
716
+ * connector shape should degrade gracefully (fall back to the vanilla 402)
717
+ * rather than throw.
718
+ *
719
+ * Expected 402 JSON body (x402 v1-ish):
720
+ * ```jsonc
721
+ * {
722
+ * "x402Version": 1,
723
+ * "accepts": [
724
+ * {
725
+ * "scheme": "toon-channel", // REQUIRED — selects the TOON option.
726
+ * "network": "evm:base:8453", // optional — chain key (informational).
727
+ * "destination": "g.toon.apex", // ILP destination address to pay (the
728
+ * // connector route that fronts the URL).
729
+ * "amount": "1000", // price in ILP base units (string|number).
730
+ * "httpEndpoint": "https://apex/ilp", // the connector's POST /ilp URL.
731
+ * "supportsUpgrade": true // optional — host accepts the BTP upgrade.
732
+ * }
733
+ * ]
734
+ * }
735
+ * ```
736
+ *
737
+ * Field aliases read defensively (first present wins):
738
+ * - destination: `destination` | `ilpAddress` | `payTo` | `maxAmountRequired`'s
739
+ * sibling `payTo`. (We do NOT invent a value — a missing destination makes
740
+ * the entry unusable and we fall back to the vanilla 402.)
741
+ * - amount: `amount` | `price` | `maxAmountRequired`.
742
+ * - httpEndpoint:`httpEndpoint` | `ilpEndpoint` | `endpoint`.
743
+ * - upgrade: `supportsUpgrade` | `upgradable`.
744
+ *
745
+ * ─── HTTP-in-ILP framing ────────────────────────────────────────────────────
746
+ * The raw HTTP request/response is serialized as minimal HTTP/1.1 wire bytes
747
+ * (request-line / status-line + headers + CRLFCRLF + body) and carried as the
748
+ * ILP packet `data` (base64). See {@link serializeHttpRequest} /
749
+ * {@link parseHttpResponse}. This keeps the connector free to forward the bytes
750
+ * verbatim and lets us rebuild a standard `Response`.
751
+ *
752
+ * Claim signing/construction is owned by the CALLER (ToonClient wires the live
753
+ * ChannelManager + signer). This adapter never builds or validates claims —
754
+ * payment-claim validation lives ONLY in the connector.
755
+ */
756
+
757
+ /** A single parsed `accepts` entry that offers the `toon-channel` scheme. */
758
+ interface ToonChannelAccept {
759
+ /** Always `'toon-channel'` for a matched entry. */
760
+ scheme: 'toon-channel';
761
+ /** Optional chain key, e.g. `evm:base:8453` — informational. */
762
+ network?: string;
763
+ /** ILP destination address to pay (the connector route fronting the URL). */
764
+ destination: string;
765
+ /** Price in ILP base units. */
766
+ amount: bigint;
767
+ /** The connector's `POST /ilp` URL. */
768
+ httpEndpoint: string;
769
+ /** Whether the host accepts the BTP upgrade over the HTTP endpoint. */
770
+ supportsUpgrade: boolean;
771
+ }
772
+ /** The parsed x402 402 body, with the selected `toon-channel` entry (if any). */
773
+ interface ParsedX402Challenge {
774
+ x402Version?: number;
775
+ /** The first usable `toon-channel` accepts entry, or `undefined`. */
776
+ toonChannel?: ToonChannelAccept;
777
+ }
778
+ /** Options for {@link Http402Client.fetch} / `ToonClient.h402Fetch`. */
779
+ interface H402FetchOptions {
780
+ /** HTTP method. Default `'GET'`. */
781
+ method?: string;
782
+ /** Request headers. */
783
+ headers?: Record<string, string>;
784
+ /** Request body. */
785
+ body?: string | Uint8Array;
786
+ /** Request timeout in milliseconds. */
787
+ timeout?: number;
788
+ /** Optional explicit ILP destination override (else the x402 entry's value). */
789
+ destination?: string;
790
+ }
791
+ /**
792
+ * Caller-supplied hook that signs a balance-proof claim for `(destination,
793
+ * amount)` and returns the chain-appropriate claim message to attach to the ILP
794
+ * PREPARE. ToonClient wires this to its ChannelManager + per-chain signer (the
795
+ * exact same plumbing as `publishEvent`). The returned value is forwarded
796
+ * opaquely as the `ILP-Payment-Channel-Claim` header by {@link HttpIlpClient}.
797
+ */
798
+ type ClaimResolver = (destination: string, amount: bigint) => Promise<unknown>;
799
+ /** Factory for an {@link HttpIlpClient} given a resolved `POST /ilp` endpoint. */
800
+ type HttpIlpClientFactory = (httpEndpoint: string) => HttpIlpClient;
801
+ interface Http402ClientConfig {
802
+ /**
803
+ * Underlying HTTP fetch for the INITIAL (un-paid) request that probes for a
804
+ * 402. Default: global `fetch`.
805
+ */
806
+ fetch?: typeof fetch;
807
+ /**
808
+ * Resolves + signs the payment-channel claim. REQUIRED to pay; if omitted,
809
+ * a 402 with a `toon-channel` offer is surfaced unchanged (vanilla challenge).
810
+ */
811
+ resolveClaim?: ClaimResolver;
812
+ /**
813
+ * Builds the {@link HttpIlpClient} for a resolved endpoint. Default: construct
814
+ * a new `HttpIlpClient({ httpEndpoint })`. Injectable for tests.
815
+ */
816
+ createIlpClient?: HttpIlpClientFactory;
817
+ /**
818
+ * AC4: request a duplex transport for the paid send. When `true` and the
819
+ * toon-channel entry advertises `supportsUpgrade`, {@link selectIlpTransport}
820
+ * returns `http-upgradable` and the send path calls
821
+ * {@link HttpIlpClient.upgradeToBtp} before writing — the wiring for
822
+ * large/streaming responses. Default `false` (stateless one-shot HTTP).
823
+ *
824
+ * NOTE (v1 limitation): even on the upgrade path the actual write is still a
825
+ * one-shot `sendIlpPacketWithClaim`; full duplex body streaming over the BTP
826
+ * session is a documented follow-up. The selection + upgrade CALL PATH is
827
+ * wired and exercised here so the streaming consumer can take over the
828
+ * returned session in a later iteration.
829
+ */
830
+ needsDuplex?: boolean;
831
+ }
832
+ /**
833
+ * Reusable h402 fetch engine. `ToonClient.h402Fetch` is a thin wrapper that
834
+ * constructs this with the live claim/channel plumbing.
835
+ */
836
+ declare class Http402Client {
837
+ private readonly fetchImpl;
838
+ private readonly resolveClaim?;
839
+ private readonly createIlpClient;
840
+ private readonly needsDuplex;
841
+ constructor(config?: Http402ClientConfig);
842
+ /**
843
+ * `fetch()`-like entry point. Issues the request; on `402` parses the x402
844
+ * challenge and — when a usable `toon-channel` offer is present and a claim
845
+ * resolver is configured — pays over TOON and returns the reconstructed
846
+ * `Response`. Otherwise returns the original 402 unchanged (AC5).
847
+ */
848
+ fetch(url: string, opts?: H402FetchOptions): Promise<Response>;
849
+ /**
850
+ * Open/reuse a channel (via the injected claim resolver), serialize the HTTP
851
+ * request into the ILP packet `data`, send it to `POST /ilp` with the claim,
852
+ * and reconstruct the origin `Response` from the FULFILL `data`.
853
+ */
854
+ private payOverToon;
855
+ /**
856
+ * Send the serialized HTTP-in-ILP PREPARE over the selected transport.
857
+ *
858
+ * - `http` / `http-upgradable`: stateless one-shot `POST /ilp` with the claim.
859
+ * - `http-upgradable` additionally exercises {@link HttpIlpClient.upgradeToBtp}
860
+ * for the duplex/streaming path (AC4). v1 still drives the actual write over
861
+ * the one-shot HTTP method even after upgrading — full duplex body streaming
862
+ * is a documented follow-up — but the upgrade call path is wired here.
863
+ * - `btp`: not reachable from h402 (the x402 offer only carries an
864
+ * `httpEndpoint`); guarded for completeness.
865
+ */
866
+ private sendOverChoice;
867
+ }
868
+ /**
869
+ * Parse a 402 `Response` body into a {@link ParsedX402Challenge}, selecting the
870
+ * first usable `toon-channel` entry. Reads every field defensively; a malformed
871
+ * body, a non-JSON body, or an entry missing its `destination`/`httpEndpoint`
872
+ * yields `{ toonChannel: undefined }` so the caller falls back to the vanilla
873
+ * 402 rather than throwing.
874
+ */
875
+ declare function parseX402Challenge(response: Response): Promise<ParsedX402Challenge>;
876
+ /** Pure parser over an already-decoded x402 body (testable without a Response). */
877
+ declare function parseX402Body(body: unknown): ParsedX402Challenge;
878
+ /**
879
+ * Serialize a raw HTTP request to HTTP/1.1 wire bytes:
880
+ * `METHOD path HTTP/1.1\r\n` + `Host:` + headers + `\r\n\r\n` + body.
881
+ *
882
+ * The request-line target is the URL's path+query (origin-form); we add a
883
+ * `Host` header from the URL authority and a `Content-Length` when there's a
884
+ * body, unless the caller already supplied them. Header names are matched
885
+ * case-insensitively so we never duplicate `Host`/`Content-Length`.
886
+ */
887
+ declare function serializeHttpRequest(req: {
888
+ method: string;
889
+ url: string;
890
+ headers?: Record<string, string>;
891
+ body?: string | Uint8Array;
892
+ }): Uint8Array;
893
+ /**
894
+ * Parse HTTP/1.1 wire bytes (status-line + headers + CRLFCRLF + body) into a
895
+ * standard Web `Response`. Used to reconstruct the origin response from the ILP
896
+ * FULFILL `data`.
897
+ *
898
+ * @throws {ConnectorError} If the bytes are not a parseable HTTP/1.1 response.
899
+ */
900
+ declare function parseHttpResponse(bytes: Uint8Array): Response;
901
+
512
902
  /**
513
903
  * ToonClient - High-level client for interacting with TOON network.
514
904
  *
@@ -668,6 +1058,33 @@ declare class ToonClient {
668
1058
  claim?: SignedBalanceProof;
669
1059
  ilpAmount?: bigint;
670
1060
  }): Promise<PublishEventResult>;
1061
+ /**
1062
+ * Payment-aware HTTP fetch over TOON (issue #50). A `fetch()`-like method that
1063
+ * makes paying for an HTTP resource transparent:
1064
+ *
1065
+ * 1. Issues the HTTP request to `url`.
1066
+ * 2. On `402`, parses the x402 `accepts` array and selects the
1067
+ * `toon-channel` entry (see {@link Http402Client} for the wire shape).
1068
+ * 3. Opens/reuses a payment channel for the entry's ILP destination (via
1069
+ * ChannelManager), signs a balance proof for the demanded price, and
1070
+ * re-sends the SAME HTTP request as a transparent HTTP-in-ILP packet to
1071
+ * the connector's `POST /ilp` (via {@link HttpIlpClient}), with the claim
1072
+ * in the `ILP-Payment-Channel-Claim` header.
1073
+ * 4. Reconstructs and returns a standard Web `Response` from the FULFILL
1074
+ * `data`. The caller never sees ILP.
1075
+ *
1076
+ * If the origin offers no `toon-channel` entry, the original `402` Response is
1077
+ * returned unchanged (the caller sees the vanilla x402 challenge).
1078
+ *
1079
+ * The channel/claim plumbing is wired to the live ChannelManager + per-chain
1080
+ * signer via `resolveClaimForDestination` — identical to `publishEvent`. The
1081
+ * `amount` paid comes from the selected x402 entry (the resource's price).
1082
+ *
1083
+ * @throws {ToonClientError} If the client is not started.
1084
+ * @throws {ConnectorError} If the connector rejects the payment or returns no
1085
+ * HTTP payload.
1086
+ */
1087
+ h402Fetch(url: string, opts?: H402FetchOptions): Promise<Response>;
671
1088
  /**
672
1089
  * Sends a raw swap ILP packet (Story 12.5) to a Mill peer with an attached
673
1090
  * balance-proof claim. This is a lower-level surface than `publishEvent`:
@@ -680,7 +1097,7 @@ declare class ToonClient {
680
1097
  * matching `destination`,
681
1098
  * (c) neither -> throw MISSING_CLAIM.
682
1099
  *
683
- * @throws {ToonClientError} INVALID_STATE / NO_BTP_CLIENT / MISSING_CLAIM
1100
+ * @throws {ToonClientError} INVALID_STATE / NO_ILP_TRANSPORT / MISSING_CLAIM
684
1101
  */
685
1102
  sendSwapPacket(params: {
686
1103
  destination: string;
@@ -711,7 +1128,31 @@ declare class ToonClient {
711
1128
  * adapter in `getSignerForChannel` delegates to the same
712
1129
  * `EvmSigner.buildClaimMessage`).
713
1130
  */
714
- private buildClaimMessageForProof;
1131
+ private buildClaimMessageForProof;
1132
+ /**
1133
+ * Resolve the ILP transport for a paid (claim-bearing) write.
1134
+ *
1135
+ * The connector is a payment-proxy: paid writes carry an ILP PREPARE plus the
1136
+ * signed payment-channel claim. Either transport speaks the SAME claim
1137
+ * contract — the BTP `payment-channel-claim` protocolData entry and the
1138
+ * ILP-over-HTTP `ILP-Payment-Channel-Claim` header serialize the same claim
1139
+ * JSON — so we route through whichever transport is ACTIVE rather than
1140
+ * hard-requiring BTP.
1141
+ *
1142
+ * Selection (mirrors `modes/http.ts` runtime-client precedence):
1143
+ * 1. `runtimeClient` when it implements `sendIlpPacketWithClaim` — this is
1144
+ * the HttpIlpClient (proxy `POST /ilp`) when a `proxyUrl`/
1145
+ * `connectorHttpEndpoint` is configured, else the BtpRuntimeClient.
1146
+ * 2. `btpClient` as an explicit fallback (always present when `btpUrl` is set).
1147
+ *
1148
+ * The level-3 `HttpRuntimeClient` (connector-admin HTTP, no `btpUrl` AND no
1149
+ * proxy) does NOT implement `sendIlpPacketWithClaim`; in that case there is no
1150
+ * paid-write transport and we throw a clear, actionable error.
1151
+ *
1152
+ * @throws {ToonClientError} NO_ILP_TRANSPORT when no active transport can send
1153
+ * a packet+claim.
1154
+ */
1155
+ private getClaimTransport;
715
1156
  /**
716
1157
  * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.
717
1158
  * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution
@@ -813,48 +1254,6 @@ declare class ToonClient {
813
1254
  getDiscoveredPeers(): _toon_protocol_core.DiscoveredPeer[];
814
1255
  }
815
1256
 
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
-
858
1257
  /**
859
1258
  * Base error class for all TOON client errors.
860
1259
  */
@@ -1183,214 +1582,6 @@ declare class HttpConnectorAdmin implements ConnectorAdminClient {
1183
1582
  private handleErrorResponse;
1184
1583
  }
1185
1584
 
1186
- interface BtpRuntimeClientConfig {
1187
- btpUrl: string;
1188
- peerId: string;
1189
- authToken: string;
1190
- /** Max reconnection attempts on send failure (default: 3) */
1191
- maxRetries?: number;
1192
- /** Delay between reconnection attempts in ms (default: 1000) */
1193
- retryDelay?: number;
1194
- /** Custom WebSocket constructor (for SOCKS5 proxy support). */
1195
- createWebSocket?: (url: string) => WebSocket;
1196
- }
1197
- /**
1198
- * BTP transport implementing IlpClient.
1199
- * Uses IsomorphicBtpClient (browser-native, no Node.js dependencies).
1200
- */
1201
- declare class BtpRuntimeClient implements IlpClient {
1202
- private btpClient;
1203
- private readonly config;
1204
- private _isConnected;
1205
- constructor(config: BtpRuntimeClientConfig);
1206
- /**
1207
- * Connects to the BTP peer via WebSocket.
1208
- */
1209
- connect(): Promise<void>;
1210
- /**
1211
- * Attempts to reconnect by creating a fresh client and connecting.
1212
- */
1213
- reconnect(): Promise<void>;
1214
- /**
1215
- * Disconnects from the BTP peer.
1216
- */
1217
- disconnect(): Promise<void>;
1218
- get isConnected(): boolean;
1219
- /**
1220
- * Sends an ILP packet via BTP with auto-reconnect on connection errors.
1221
- * Satisfies IlpClient interface.
1222
- */
1223
- sendIlpPacket(params: {
1224
- destination: string;
1225
- amount: string;
1226
- data: string;
1227
- timeout?: number;
1228
- }): Promise<IlpSendResult>;
1229
- /**
1230
- * Sends a balance proof claim via BTP protocol data, then sends an ILP packet.
1231
- * Auto-reconnects on connection errors.
1232
- */
1233
- sendIlpPacketWithClaim(params: {
1234
- destination: string;
1235
- amount: string;
1236
- data: string;
1237
- timeout?: number;
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;
1247
- /**
1248
- * Single-attempt ILP packet send. Reconnects if not connected.
1249
- */
1250
- private _sendIlpPacketOnce;
1251
- /**
1252
- * Single-attempt claim + ILP packet send. Reconnects if not connected.
1253
- */
1254
- private _sendIlpPacketWithClaimOnce;
1255
- }
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
1585
  /**
1395
1586
  * Transport selection policy for the client ILP layer.
1396
1587
  *
@@ -2138,93 +2329,42 @@ interface RetryOptions {
2138
2329
  declare function withRetry<T>(operation: () => Promise<T>, options: RetryOptions): Promise<T>;
2139
2330
 
2140
2331
  /**
2141
- * Self-managed `anon` (anyone-protocol / ATOR) SOCKS5h proxy (Node.js only).
2332
+ * Store-write HTTP envelope for the payment-proxy (HTTP-in-ILP) path.
2142
2333
  *
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.
2334
+ * The deployed connector is a payment-proxy: it terminates a paid write by
2335
+ * decoding the ILP PREPARE `data` as a literal RFC 7230 HTTP/1.1 request and
2336
+ * reverse-proxying it to the relay store's `POST /write` (see the connector's
2337
+ * `HttpProxyHandler.decodeHttpRequest`). The ILP `data` MUST therefore be a
2338
+ * full HTTP request envelope:
2151
2339
  *
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.
2340
+ * POST /write HTTP/1.1\r\n
2341
+ * Host: relay\r\n
2342
+ * Content-Type: application/json\r\n
2343
+ * \r\n
2344
+ * {"event": <signed nostr event object>}
2180
2345
  *
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).
2346
+ * Sending the bare TOON-encoded event (no request-line) makes the proxy reject
2347
+ * with `F01 - Invalid HTTP envelope: malformed request-line`. The relay's
2348
+ * `/write` handler parses the body as JSON and reads `body.event` as a full
2349
+ * signed Nostr event OBJECT (it runs `verifyEvent(event)` + `store(event)`), so
2350
+ * the body carries the event object verbatim — NOT the TOON string.
2189
2351
  *
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.
2352
+ * This helper is the single source of truth for that envelope so the proxy
2353
+ * paid-write path (`ToonClient.publishEvent`) and any future caller stay
2354
+ * byte-compatible with the deployed store. It is isomorphic (Node + browser):
2355
+ * `JSON.stringify` escapes non-ASCII to `\uXXXX`, so the serialized envelope is
2356
+ * pure ASCII and `encodeUtf8` matches the bytes the store expects.
2197
2357
  */
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
- }
2358
+
2220
2359
  /**
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 }`.
2360
+ * Wrap a signed Nostr event in the `POST /write` HTTP envelope the deployed
2361
+ * payment-proxy reverse-proxies to the relay store.
2224
2362
  *
2225
- * @throws If the platform is unsupported, the checksum fails, or anon never binds.
2363
+ * @param event - A finalized (signed) Nostr event passed through to the store
2364
+ * as the JSON `event` field verbatim (the store re-verifies the signature).
2365
+ * @returns The envelope bytes to use as the ILP PREPARE `data`.
2226
2366
  */
2227
- declare function startManagedAnonProxy(options?: StartManagedAnonProxyOptions): Promise<ManagedAnonProxy>;
2367
+ declare function buildStoreWriteEnvelope(event: NostrEvent): Uint8Array;
2228
2368
 
2229
2369
  /**
2230
2370
  * Settlement info produced by buildSettlementInfo().
@@ -2258,6 +2398,16 @@ declare function applyNetworkPresets(config: ToonClientConfig): ToonClientConfig
2258
2398
  * unset or `'custom'` (no preset tier to report on).
2259
2399
  */
2260
2400
  declare function getNetworkStatus(config: ToonClientConfig): NetworkFamilyStatus | undefined;
2401
+ /**
2402
+ * Normalize a connector-proxy base URL into its `POST /ilp` endpoint.
2403
+ *
2404
+ * `https://proxy.devnet.toonprotocol.dev` → `https://proxy.devnet.toonprotocol.dev/ilp`
2405
+ * `https://proxy.devnet.toonprotocol.dev/ilp` → unchanged (idempotent)
2406
+ * `https://proxy.devnet.toonprotocol.dev/` → `https://proxy.devnet.toonprotocol.dev/ilp`
2407
+ *
2408
+ * Returns `undefined` for an empty/falsy input so callers can `??`-chain it.
2409
+ */
2410
+ declare function proxyIlpEndpoint(proxyUrl: string | undefined): string | undefined;
2261
2411
  /**
2262
2412
  * Validates ToonClient configuration.
2263
2413
  *
@@ -2270,7 +2420,7 @@ declare function validateConfig(config: ToonClientConfig): void;
2270
2420
  * The resolved config type after defaults are applied.
2271
2421
  * secretKey is guaranteed to be present (auto-generated if omitted).
2272
2422
  */
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'>> & {
2423
+ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'mnemonic' | 'mnemonicAccountIndex' | 'evmPrivateKey' | 'network' | 'supportedChains' | 'settlementAddresses' | 'preferredTokens' | 'tokenNetworks' | 'btpUrl' | 'btpAuthToken' | 'btpPeerId' | 'connectorHttpEndpoint' | 'proxyUrl' | 'faucetUrl' | 'connectorSupportsUpgrade' | 'chainRpcUrls' | 'initialDeposit' | 'settlementTimeout' | 'solanaChannel' | 'minaChannel' | 'channelStorePath' | 'knownPeers' | 'destinationAddress'>> & {
2274
2424
  connector?: unknown;
2275
2425
  /** Always present after applyDefaults() — derived from secretKey if not explicitly provided */
2276
2426
  evmPrivateKey: string | Uint8Array;
@@ -2286,14 +2436,8 @@ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'mnemonic' |
2286
2436
  * same index as the synchronously-resolved Nostr/EVM keys.
2287
2437
  */
2288
2438
  mnemonicAccountIndex?: number;
2289
- /** Transport privacy config (optional — defaults to direct). */
2290
- transport?: ClientTransportConfig;
2291
2439
  /** Named network tier, retained for `getNetworkStatus()`. */
2292
2440
  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;
2297
2441
  supportedChains?: string[];
2298
2442
  settlementAddresses?: Record<string, string>;
2299
2443
  preferredTokens?: Record<string, string>;
@@ -2302,6 +2446,8 @@ type ResolvedConfig = Required<Omit<ToonClientConfig, 'connector' | 'mnemonic' |
2302
2446
  btpAuthToken?: string;
2303
2447
  btpPeerId?: string;
2304
2448
  connectorHttpEndpoint?: string;
2449
+ proxyUrl?: string;
2450
+ faucetUrl?: string;
2305
2451
  connectorSupportsUpgrade?: boolean;
2306
2452
  chainRpcUrls?: Record<string, string>;
2307
2453
  initialDeposit?: string;
@@ -2728,6 +2874,51 @@ declare function filterPetListings(events: NostrEventLike[], options?: PetListin
2728
2874
  */
2729
2875
  declare function buildPetPurchaseRequest(params: PetPurchaseRequestParams): UnsignedNostrEvent;
2730
2876
 
2877
+ /**
2878
+ * Devnet faucet helper.
2879
+ *
2880
+ * The deployed TOON devnet exposes a faucet that drips test funds to a given
2881
+ * chain address so a client can open payment channels and pay for writes:
2882
+ *
2883
+ * EVM `POST {faucetUrl}/api/request` body `{ address }` → 100 ETH + 10k USDC
2884
+ * Solana `POST {faucetUrl}/api/solana/request` body `{ address }` → SOL + USDC
2885
+ * Mina `POST {faucetUrl}/api/mina/request` body `{ address }` → native MINA only
2886
+ *
2887
+ * Devnet edge (today): `https://faucet.devnet.toonprotocol.dev`.
2888
+ *
2889
+ * EVM is implemented fully. Solana/Mina are deferred to a later milestone (WS3)
2890
+ * and throw a clear error so callers don't silently assume funding happened.
2891
+ */
2892
+ /** Supported faucet chains. */
2893
+ type FaucetChain = 'evm' | 'solana' | 'mina';
2894
+ /** Result of a successful faucet drip. */
2895
+ interface FundWalletResult {
2896
+ /** The chain that was funded. */
2897
+ chain: FaucetChain;
2898
+ /** The funded address (echoed back). */
2899
+ address: string;
2900
+ /** Raw parsed JSON body from the faucet (shape is faucet-defined). */
2901
+ response: unknown;
2902
+ }
2903
+ /** Options for {@link fundWallet}. */
2904
+ interface FundWalletOptions {
2905
+ /** Custom fetch implementation (for testing / custom transports). */
2906
+ fetchImpl?: typeof fetch;
2907
+ /** Request timeout in milliseconds (default: 30000). */
2908
+ timeout?: number;
2909
+ }
2910
+ /**
2911
+ * Drip test funds to `address` on `chain` from the devnet `faucetUrl`.
2912
+ *
2913
+ * @param faucetUrl - Faucet base URL, e.g. `https://faucet.devnet.toonprotocol.dev`.
2914
+ * A trailing `/` is tolerated.
2915
+ * @param address - The chain address to fund (EVM 0x address, Solana base58, etc).
2916
+ * @param chain - `'evm'` (implemented) | `'solana'` | `'mina'` (deferred — throw).
2917
+ * @throws {Error} If `faucetUrl`/`address` is missing, or the chain is deferred.
2918
+ * @throws {NetworkError} On transport failure or a non-2xx faucet response.
2919
+ */
2920
+ declare function fundWallet(faucetUrl: string, address: string, chain: FaucetChain, options?: FundWalletOptions): Promise<FundWalletResult>;
2921
+
2731
2922
  /**
2732
2923
  * Full multi-chain identity derived from a single BIP-39 mnemonic.
2733
2924
  */
@@ -3077,4 +3268,4 @@ declare function loadKeystore(path: string, password: string): string;
3077
3268
  */
3078
3269
  declare function writeKeystoreFile(path: string, keystore: EncryptedKeystore): void;
3079
3270
 
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 };
3271
+ export { type BackupPayload, type BalanceProofParams, BtpRuntimeClient, type BtpRuntimeClientConfig, type ChainMetadata, type ChainSigner, ChannelManager, type ClaimMessage, type ClaimResolver, ConnectorError, type DiscoveredIlpPeer, type EVMClaimMessage, type EncryptedKeystore, EvmSigner, type FaucetChain, type FundWalletOptions, type FundWalletResult, type H402FetchOptions, Http402Client, type Http402ClientConfig, HttpConnectorAdmin, type HttpConnectorAdminConfig, HttpIlpClient, type HttpIlpClientConfig, type HttpIlpClientFactory, HttpRuntimeClient, type HttpRuntimeClientConfig, ILP_CLAIM_HEADER, ILP_CLAIM_WRAPPED_HEADER, ILP_PEER_ID_HEADER, type IlpTransportChoice, type InteractionResultContent, KeyManager, type KeyManagerConfig, type MinaClaimMessage, type MinaDepositReader, MinaSigner, type MinaSignerOptions, NetworkError, OnChainChannelClient, type OnChainChannelClientConfig, type ParsedX402Challenge, 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 StatValues, type ToonChannelAccept, ToonClient, type ToonClientConfig, ToonClientError, type ToonIdentity, type ToonSigners, type ToonStartResult, type UnsignedNostrEvent, ValidationError, type VaultData, applyDefaults, applyNetworkPresets, buildBackupEvent, buildBackupFilter, buildPetInteractionRequest, buildPetListingEvent, buildPetPurchaseRequest, buildSettlementInfo, buildStoreWriteEnvelope, decryptMnemonic, deriveFromNsec, deriveFullIdentity, deriveNostrKeyFromMnemonic, encryptMnemonic, filterPetDvmProviders, filterPetListings, fundWallet, generateKeystore, generateMnemonic, generateRandomIdentity, getNetworkStatus, httpEndpointToBtpUrl, importKeystore, isPrfSupported, loadKeystore, parseBackupPayload, parseHttpResponse, parsePetInteractionEvent, parsePetInteractionResult, parsePetListing, parseX402Body, parseX402Challenge, proxyIlpEndpoint, readDiscoveredIlpPeer, readMinaDepositTotal, requestBlobStorage, selectIlpTransport, serializeHttpRequest, validateConfig, validateMnemonic, withRetry, writeKeystoreFile };