@toon-protocol/relay 1.3.1 → 1.3.3

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,16 +1,22 @@
1
1
  import { NostrEvent } from 'nostr-tools/pure';
2
2
  import { Filter } from 'nostr-tools/filter';
3
3
  import { WebSocket } from 'ws';
4
+ import { EmbeddableConnectorLike, SkillDescriptor, BootstrapPhase, ChainPreset, IlpClient } from '@toon-protocol/core';
4
5
  export { ToonDecodeError, ToonEncodeError, decodeEventFromToon, encodeEventToToon } from '@toon-protocol/core';
5
- import { Hono } from 'hono';
6
+ import { Hono, Context } from 'hono';
6
7
  import { SimplePool } from 'nostr-tools/pool';
8
+ import { Handler } from '@toon-protocol/sdk';
9
+ import { PublicClient, WalletClient } from 'viem';
10
+ import { ToonRoutingMeta } from '@toon-protocol/core/toon';
7
11
 
8
12
  /**
9
13
  * Configuration options for the Nostr relay.
10
14
  */
11
- interface RelayConfig {
15
+ interface RelayServerConfig {
12
16
  /** Port to listen on (default: 7000) */
13
17
  port: number;
18
+ /** Host/IP to bind to (default: '0.0.0.0'). Set to '127.0.0.1' for hidden service mode. */
19
+ host?: string;
14
20
  /** Maximum concurrent connections (default: 100) */
15
21
  maxConnections?: number;
16
22
  /** Maximum subscriptions per connection (default: 20) */
@@ -23,7 +29,7 @@ interface RelayConfig {
23
29
  /**
24
30
  * Default relay configuration values.
25
31
  */
26
- declare const DEFAULT_RELAY_CONFIG: Required<RelayConfig>;
32
+ declare const DEFAULT_RELAY_CONFIG: Required<RelayServerConfig>;
27
33
 
28
34
  /**
29
35
  * Interface for event storage backends.
@@ -142,7 +148,7 @@ declare class ConnectionHandler {
142
148
  private eventStore;
143
149
  private subscriptions;
144
150
  private config;
145
- constructor(ws: WebSocket, eventStore: EventStore, config?: Partial<RelayConfig>);
151
+ constructor(ws: WebSocket, eventStore: EventStore, config?: Partial<RelayServerConfig>);
146
152
  /**
147
153
  * Handle an incoming message from the WebSocket.
148
154
  */
@@ -192,7 +198,7 @@ declare class NostrRelayServer {
192
198
  private wss;
193
199
  private handlers;
194
200
  private config;
195
- constructor(config: Partial<RelayConfig> | undefined, eventStore: EventStore);
201
+ constructor(config: Partial<RelayServerConfig> | undefined, eventStore: EventStore);
196
202
  /**
197
203
  * Start the WebSocket server.
198
204
  */
@@ -450,6 +456,912 @@ declare class RelaySubscriber {
450
456
  };
451
457
  }
452
458
 
459
+ /**
460
+ * startRelay() -- Programmatic API for starting a TOON relay node.
461
+ *
462
+ * This module wraps the same SDK components used by docker/src/entrypoint-sdk.ts
463
+ * into a single function call with a typed configuration object. Both
464
+ * `startRelay()` and the Docker entrypoint compose the same pipeline:
465
+ *
466
+ * Identity -> Verification -> Pricing -> HandlerRegistry -> BLS + Relay + Bootstrap
467
+ *
468
+ * The key difference is lifecycle management: the Docker entrypoint uses
469
+ * process-level signals (SIGINT/SIGTERM), while `startRelay()` returns a
470
+ * `RelayInstance` with an explicit `.stop()` method.
471
+ *
472
+ * ## Deployment Modes
473
+ *
474
+ * The town node ALWAYS runs an embedded `ConnectorNode` so that packets
475
+ * destined for its own ILP address can be routed locally (the connector and
476
+ * the BLS handler must share a process for the round-trip to work).
477
+ *
478
+ * - **Standalone embedded** (no `connectorUrl`): A self-routing embedded
479
+ * connector with no upstream peers. Useful for genesis nodes and tests.
480
+ * - **Embedded with parent** (`connectorUrl` set): The embedded connector
481
+ * is configured with `connectorUrl` as a parent BTP peer, plus a self-route
482
+ * for local delivery and a default-route to the parent for everything else.
483
+ * - **Pre-built embedded** (`connector`): Pass a fully constructed
484
+ * `EmbeddableConnectorLike`. The town does not modify it.
485
+ *
486
+ * `connector` and `connectorUrl` are mutually exclusive — provide at most one.
487
+ */
488
+
489
+ /**
490
+ * Configuration for starting a TOON relay node via `startRelay()`.
491
+ *
492
+ * Exactly one of `mnemonic` or `secretKey` must be provided.
493
+ * `connector` and `connectorUrl` are mutually exclusive — provide at most one.
494
+ *
495
+ * - When neither is provided, a standalone embedded `ConnectorNode` is built
496
+ * with only a self-route (no upstream peers).
497
+ * - When `connectorUrl` is set, the embedded connector is configured with
498
+ * that URL as a parent BTP peer plus a default-route to it. `ilpAddress`
499
+ * becomes REQUIRED in this mode and must fall under the parent's prefix
500
+ * (e.g. `g.townhouse.<self>`).
501
+ * - When `connector` is set, the caller-supplied `EmbeddableConnectorLike`
502
+ * is used as-is; town does not configure peers, routes, or settlement on it.
503
+ */
504
+ interface RelayConfig {
505
+ /** 12-word or 24-word BIP-39 mnemonic phrase. */
506
+ mnemonic?: string;
507
+ /** 32-byte secp256k1 secret key. */
508
+ secretKey?: Uint8Array;
509
+ /**
510
+ * Pre-built embedded connector. Mutually exclusive with `connectorUrl`.
511
+ * When provided, town does not modify the connector — peers, routes, and
512
+ * settlement are the caller's responsibility.
513
+ */
514
+ connector?: EmbeddableConnectorLike;
515
+ /**
516
+ * Parent connector BTP URL (e.g. `ws://apex.example:3001`). When set, the
517
+ * embedded connector is built with this URL as a parent peer and a default
518
+ * `g.` route to that peer; `ilpAddress` MUST also be set and fall under the
519
+ * parent's prefix. Mutually exclusive with `connector`.
520
+ */
521
+ connectorUrl?: string;
522
+ /** BTP peer id to use for the parent connector (default: `'apex'`). */
523
+ parentPeerId?: string;
524
+ /** BTP auth token for the parent peer (default: empty string -- no-auth). */
525
+ parentAuthToken?: string;
526
+ /** Stable nodeId for the embedded connector (default: `toon-<pubkeyShort>`). */
527
+ nodeId?: string;
528
+ /** BTP server port for the embedded connector (default: 3000). */
529
+ btpServerPort?: number;
530
+ /**
531
+ * EVM private key for settlement infrastructure on the embedded connector.
532
+ * If not set, the identity's secp256k1 key is reused.
533
+ */
534
+ settlementPrivateKey?: string;
535
+ /**
536
+ * EVM treasury address advertised to the parent connector for the
537
+ * embedded-with-parent peer entry. The apex's PerPacketClaimService uses
538
+ * this as the `peerAddress` when the apex opens a payment channel toward
539
+ * this child. Only meaningful when `connectorUrl` is set. When omitted,
540
+ * the parent peer entry has no `evmAddress` and the apex's channel-open
541
+ * call must supply `peerAddress` explicitly.
542
+ */
543
+ parentEvmAddress?: string;
544
+ /** WebSocket relay port (default: 7100). */
545
+ relayPort?: number;
546
+ /** BLS HTTP server port (default: 3100). */
547
+ blsPort?: number;
548
+ /**
549
+ * ILP address for this node. Default `g.toon.<pubkeyShort>` is used only
550
+ * when no parent connector is configured. When `connectorUrl` is set this
551
+ * field is REQUIRED and must fall under the parent's address prefix.
552
+ */
553
+ ilpAddress?: string;
554
+ /** BTP WebSocket endpoint (default: ws://localhost:3000). */
555
+ btpEndpoint?: string;
556
+ /** Base price per byte in ILP units (default: 10n). */
557
+ basePricePerByte?: bigint;
558
+ /** Routing buffer percentage for x402 multi-hop overhead (default: 10). */
559
+ routingBufferPercent?: number;
560
+ /** Enable x402 /publish endpoint (default: false). */
561
+ x402Enabled?: boolean;
562
+ /** Facilitator EVM address for x402 payments. Defaults to the node's EVM address. */
563
+ facilitatorAddress?: string;
564
+ /** Known peers to bootstrap with. */
565
+ knownPeers?: {
566
+ pubkey: string;
567
+ relayUrl: string;
568
+ btpEndpoint: string;
569
+ }[];
570
+ /** Chain preset name (default: 'anvil'). See resolveChainConfig(). */
571
+ chain?: string;
572
+ /** Chain ID -> RPC URL mapping (e.g., { 'evm:base:31337': 'http://localhost:8545' }). */
573
+ chainRpcUrls?: Record<string, string>;
574
+ /** Chain ID -> TokenNetwork contract address. */
575
+ tokenNetworks?: Record<string, string>;
576
+ /** Chain ID -> preferred token address. */
577
+ preferredTokens?: Record<string, string>;
578
+ /**
579
+ * Chain ID -> settlement (recipient) address advertised in kind:10032.
580
+ *
581
+ * By default every supported chain advertises the identity's EVM address.
582
+ * That is wrong for non-EVM chains (e.g. `solana:devnet`), whose settlement
583
+ * recipient must be a chain-native address (a base58 Solana pubkey). Provide
584
+ * a per-chain override here to advertise a chain-native recipient; chains
585
+ * absent from this map keep the EVM-address default.
586
+ *
587
+ * NOTE (Phase-2 Stage 2 gate): advertising a Solana recipient is necessary
588
+ * but NOT sufficient for a settleable Solana loop — the client must also open
589
+ * a real on-chain Solana payment-channel PDA and sign over that PDA. See the
590
+ * Stage-2 PR description / gate report.
591
+ */
592
+ settlementAddresses?: Record<string, string>;
593
+ /** Data directory path (default: ./data). */
594
+ dataDir?: string;
595
+ /** Enable dev mode (skip verification). Default: false. */
596
+ devMode?: boolean;
597
+ /** Discovery mode: 'seed-list' for production, 'genesis' for dev (default: 'genesis'). */
598
+ discovery?: 'seed-list' | 'genesis';
599
+ /** Public Nostr relay URLs for seed relay discovery (used when discovery: 'seed-list'). */
600
+ seedRelays?: string[];
601
+ /** Whether to publish this node as a seed relay entry (default: false). */
602
+ publishSeedEntry?: boolean;
603
+ /** External WebSocket URL of this relay (required if publishSeedEntry is true). */
604
+ externalRelayUrl?: string;
605
+ /**
606
+ * Ator hidden service configuration for the relay.
607
+ *
608
+ * When enabled, the relay binds to localhost only (ator handles inbound routing)
609
+ * and publishes the `.anon` address in seed relay discovery events.
610
+ *
611
+ * - `enabled: false` (default): Relay binds to `0.0.0.0`, no privacy overlay.
612
+ * - `enabled: true`: Relay binds to `127.0.0.1`, publishes `anonAddress` for discovery.
613
+ */
614
+ ator?: {
615
+ enabled: boolean;
616
+ /** The `.anon` hidden service address for this relay (e.g., "wss://abc123.anon:443"). */
617
+ anonAddress?: string;
618
+ /** SOCKS5 proxy URL for outbound connections (default: "socks5h://127.0.0.1:9050"). */
619
+ socksProxy?: string;
620
+ };
621
+ /**
622
+ * Optional DVM skill descriptor to include in service discovery events.
623
+ * When provided, the service discovery event will include the `skill` field.
624
+ * Typically computed by `node.getSkillDescriptor()` from the SDK.
625
+ */
626
+ skill?: SkillDescriptor;
627
+ /** Enable ArDrive peer lookup (default: false). */
628
+ ardriveEnabled?: boolean;
629
+ /** Public Nostr relay URLs for social discovery. */
630
+ relayUrls?: string[];
631
+ /** Asset code for ILP (default: 'USD'). */
632
+ assetCode?: string;
633
+ /** Asset scale for ILP (default: 6). */
634
+ assetScale?: number;
635
+ /**
636
+ * Fee per event in ILP units (overrides basePricePerByte when set).
637
+ * When provided, sets basePricePerByte to this value. Used by the
638
+ * Townhouse orchestrator via TOON_FEE_PER_EVENT env var.
639
+ */
640
+ feePerEvent?: number;
641
+ /**
642
+ * NIP-40 time-to-live for this node's kind:10032 announcement, in seconds
643
+ * (default 3600). The node re-publishes its announcement at half this
644
+ * interval so a live apex stays fresh while an offline one expires, letting
645
+ * clients skip its unreachable BTP endpoint (issue #261). Set to 0 to disable
646
+ * the expiration tag and the heartbeat (non-expiring announcement). Override
647
+ * via the `TOON_ANNOUNCEMENT_TTL_SECONDS` env var.
648
+ */
649
+ announcementTtlSeconds?: number;
650
+ }
651
+ /**
652
+ * Resolved configuration with all defaults applied. All fields are non-optional
653
+ * (ports, pricing, paths have been filled in).
654
+ */
655
+ interface ResolvedRelayConfig {
656
+ relayPort: number;
657
+ blsPort: number;
658
+ ilpAddress: string;
659
+ btpEndpoint: string;
660
+ /** Stable nodeId of the embedded connector. */
661
+ nodeId: string;
662
+ /** Parent connector URL when peering with one (omitted otherwise). */
663
+ connectorUrl?: string;
664
+ /** Parent BTP peer id (only meaningful when connectorUrl is set). */
665
+ parentPeerId?: string;
666
+ basePricePerByte: bigint;
667
+ routingBufferPercent: number;
668
+ x402Enabled: boolean;
669
+ knownPeers: {
670
+ pubkey: string;
671
+ relayUrl: string;
672
+ btpEndpoint: string;
673
+ }[];
674
+ dataDir: string;
675
+ devMode: boolean;
676
+ ardriveEnabled: boolean;
677
+ relayUrls: string[];
678
+ assetCode: string;
679
+ assetScale: number;
680
+ /** Discovery mode: 'seed-list' for production, 'genesis' for dev. */
681
+ discovery: 'seed-list' | 'genesis';
682
+ /** Public Nostr relay URLs for seed relay discovery. */
683
+ seedRelays: string[];
684
+ /** Whether to publish this node as a seed relay entry. */
685
+ publishSeedEntry: boolean;
686
+ /** External WebSocket URL of this relay (for seed entry publishing). */
687
+ externalRelayUrl?: string;
688
+ /** Chain preset name (e.g., 'anvil', 'arbitrum-one'). */
689
+ chain: string;
690
+ }
691
+ /**
692
+ * A running TOON relay node instance returned by `startRelay()`.
693
+ *
694
+ * Provides lifecycle control (stop), identity info, and bootstrap results.
695
+ */
696
+ interface RelayInstance {
697
+ /** Whether the relay is currently running. */
698
+ isRunning(): boolean;
699
+ /** Gracefully stop the relay and release all resources. */
700
+ stop(): Promise<void>;
701
+ /**
702
+ * Subscribe to a remote Nostr relay. Received events are stored in the
703
+ * Town's EventStore. Returns a handle for lifecycle management.
704
+ *
705
+ * @param relayUrl - WebSocket URL of the relay to subscribe to.
706
+ * @param filter - Nostr filter (kinds, authors, etc.).
707
+ * @returns A RelaySubscription handle.
708
+ * @throws If the town is not running.
709
+ */
710
+ subscribe(relayUrl: string, filter: Filter): RelaySubscription;
711
+ /** The node's Nostr x-only public key (64-char hex). */
712
+ pubkey: string;
713
+ /** The node's EVM address (0x-prefixed). */
714
+ evmAddress: string;
715
+ /** The resolved configuration with all defaults applied. */
716
+ config: ResolvedRelayConfig;
717
+ /** Bootstrap results from the startup phase. */
718
+ bootstrapResult: {
719
+ peerCount: number;
720
+ channelCount: number;
721
+ };
722
+ /** Discovery mode used by this instance. */
723
+ discoveryMode: 'seed-list' | 'genesis';
724
+ }
725
+ /**
726
+ * Handle for managing an outbound subscription to a remote Nostr relay.
727
+ * Returned by `RelayInstance.subscribe()`.
728
+ */
729
+ interface RelaySubscription {
730
+ /** Close the subscription and disconnect from the relay. */
731
+ close(): void;
732
+ /** The relay URL this subscription is connected to. */
733
+ relayUrl: string;
734
+ /** Whether this subscription is still active. */
735
+ isActive(): boolean;
736
+ }
737
+ /**
738
+ * Start a TOON relay node with the given configuration.
739
+ *
740
+ * Composes the full SDK pipeline (identity, verification, pricing, handlers)
741
+ * and starts the relay WebSocket server, BLS HTTP server, bootstrap service,
742
+ * and relay monitor. Returns a `RelayInstance` for lifecycle management.
743
+ *
744
+ * The town node ALWAYS runs an embedded `ConnectorNode`. Three configurations
745
+ * are supported:
746
+ * - No connector args: standalone embedded connector with self-route only.
747
+ * - `connectorUrl`: embedded connector configured with that URL as a parent
748
+ * BTP peer plus a default `g.` route to it. `ilpAddress` is REQUIRED here.
749
+ * - `connector`: pass a pre-built `EmbeddableConnectorLike`; town does not
750
+ * modify it.
751
+ *
752
+ * @param config - Node configuration. One of `mnemonic`/`secretKey` is required;
753
+ * `connector` and `connectorUrl` are mutually exclusive.
754
+ * @returns A running RelayInstance.
755
+ * @throws If both or neither of mnemonic/secretKey are provided.
756
+ * @throws If both connector and connectorUrl are provided.
757
+ * @throws If connectorUrl is set without an explicit ilpAddress.
758
+ *
759
+ * @example
760
+ * ```typescript
761
+ * // Standalone (no parent)
762
+ * const town = await startRelay({ mnemonic: 'abandon ...' });
763
+ *
764
+ * // Embedded with parent
765
+ * const town = await startRelay({
766
+ * mnemonic: 'abandon ...',
767
+ * connectorUrl: 'ws://apex.example:3001',
768
+ * parentPeerId: 'apex',
769
+ * parentAuthToken: '',
770
+ * ilpAddress: 'g.townhouse.alice',
771
+ * });
772
+ * ```
773
+ */
774
+ declare function startRelay(config: RelayConfig): Promise<RelayInstance>;
775
+ /**
776
+ * @deprecated Use {@link startRelay} instead. Retained for backwards
777
+ * compatibility after the town → relay package merge.
778
+ */
779
+ declare const startTown: typeof startRelay;
780
+ /**
781
+ * @deprecated Use {@link RelayConfig} instead.
782
+ */
783
+ type TownConfig = RelayConfig;
784
+ /**
785
+ * @deprecated Use {@link RelayInstance} instead.
786
+ */
787
+ type TownInstance = RelayInstance;
788
+ /**
789
+ * @deprecated Use {@link ResolvedRelayConfig} instead.
790
+ */
791
+ type ResolvedTownConfig = ResolvedRelayConfig;
792
+ /**
793
+ * @deprecated Use {@link RelaySubscription} instead.
794
+ */
795
+ type TownSubscription = RelaySubscription;
796
+
797
+ /**
798
+ * Enriched health response for TOON relay nodes (Story 3.6).
799
+ *
800
+ * Provides a pure function `createHealthResponse()` that builds a comprehensive
801
+ * health JSON object combining static configuration (pricing, chain, version,
802
+ * capabilities) with live runtime state (phase, peerCount, channelCount).
803
+ *
804
+ * The response mirrors kind:10035 service discovery event fields but adds
805
+ * runtime-only fields that cannot be known at event publish time.
806
+ *
807
+ * @module
808
+ */
809
+
810
+ /** TEE attestation state for the health response (enforcement guideline 12). */
811
+ interface TeeHealthInfo {
812
+ /** Whether a valid attestation has been published. */
813
+ attested: boolean;
814
+ /** Enclave type identifier (e.g., 'aws-nitro', 'marlin-oyster'). */
815
+ enclaveType: string;
816
+ /** Unix timestamp of the last attestation event. */
817
+ lastAttestation: number;
818
+ /** Platform Configuration Register 0 (SHA-384 hex, 96 chars). */
819
+ pcr0: string;
820
+ /** Attestation validity state. */
821
+ state: 'valid' | 'stale' | 'unattested';
822
+ }
823
+ /** Configuration for building a health response. */
824
+ interface HealthConfig {
825
+ /** Current bootstrap phase. */
826
+ phase: BootstrapPhase;
827
+ /** Node's Nostr pubkey (64-char hex). */
828
+ pubkey: string;
829
+ /** Node's ILP address. */
830
+ ilpAddress: string;
831
+ /** Number of registered peers. */
832
+ peerCount: number;
833
+ /** Number of discovered (not yet registered) peers. */
834
+ discoveredPeerCount: number;
835
+ /** Number of open payment channels. */
836
+ channelCount: number;
837
+ /**
838
+ * Base price per byte (bigint from config, converted to number via Number()).
839
+ * Values exceeding Number.MAX_SAFE_INTEGER (2^53 - 1) will lose precision.
840
+ */
841
+ basePricePerByte: bigint;
842
+ /** Whether x402 is enabled. */
843
+ x402Enabled: boolean;
844
+ /** Chain preset name. */
845
+ chain: string;
846
+ /**
847
+ * TEE attestation info.
848
+ * Omit entirely when not running in a TEE (enforcement guideline 12).
849
+ */
850
+ tee?: TeeHealthInfo;
851
+ }
852
+ /** The enriched health response shape. */
853
+ interface HealthResponse {
854
+ status: 'healthy';
855
+ phase: BootstrapPhase;
856
+ pubkey: string;
857
+ ilpAddress: string;
858
+ peerCount: number;
859
+ discoveredPeerCount: number;
860
+ channelCount: number;
861
+ pricing: {
862
+ basePricePerByte: number;
863
+ currency: 'USDC';
864
+ };
865
+ x402?: {
866
+ enabled: true;
867
+ endpoint: string;
868
+ };
869
+ /**
870
+ * TEE attestation info. Only present when running in a TEE enclave.
871
+ * Entirely absent when not in TEE (enforcement guideline 12 --
872
+ * never `{ attested: false }`, simply omit the field).
873
+ */
874
+ tee?: TeeHealthInfo;
875
+ capabilities: string[];
876
+ chain: string;
877
+ version: string;
878
+ sdk: true;
879
+ timestamp: number;
880
+ }
881
+ /**
882
+ * Build an enriched health response from the given configuration.
883
+ *
884
+ * This is a pure function -- it takes a config object and returns a response
885
+ * object. No Hono context or HTTP request is needed, making it easy to unit
886
+ * test and reuse across entrypoints.
887
+ *
888
+ * The `x402` field is entirely omitted when x402 is disabled (AC #2).
889
+ * This matches the same omission semantics used in kind:10035 events.
890
+ *
891
+ * @param config - Health configuration with runtime state and static config.
892
+ * @returns The enriched health response object.
893
+ */
894
+ declare function createHealthResponse(config: HealthConfig): HealthResponse;
895
+
896
+ /**
897
+ * Event storage handler for @toon-protocol/relay.
898
+ *
899
+ * Stores incoming Nostr events in the EventStore after decoding from TOON.
900
+ * This is the "default" handler for the relay -- it processes all event kinds
901
+ * except those handled by kind-specific handlers.
902
+ *
903
+ * The handler is intentionally simple (~15 lines of logic). The SDK pipeline
904
+ * handles signature verification, pricing validation, and self-write bypass
905
+ * before the handler is invoked. The handler only needs to:
906
+ * 1. ctx.decode() -- lazy-decode the TOON payload into a NostrEvent
907
+ * 2. eventStore.store(event) -- persist the event
908
+ * 3. ctx.accept({ eventId, storedAt }) -- accept the ILP packet
909
+ */
910
+
911
+ /**
912
+ * Configuration for the event storage handler.
913
+ *
914
+ * Minimal by design -- the handler's only job is decode + store + accept.
915
+ * Pricing, verification, and self-write bypass are SDK pipeline concerns.
916
+ */
917
+ interface EventStorageHandlerConfig {
918
+ /** Event store backend (e.g., SqliteEventStore from @toon-protocol/relay). */
919
+ eventStore: EventStore;
920
+ }
921
+ /**
922
+ * Creates an event storage handler that decodes TOON payloads and stores
923
+ * Nostr events in the configured EventStore.
924
+ *
925
+ * Errors from `ctx.decode()` or `eventStore.store()` are not caught here --
926
+ * they propagate to the SDK's dispatch error boundary, which converts
927
+ * unhandled exceptions to `{ accept: false, code: 'T00', message: 'Internal error' }`.
928
+ *
929
+ * @param config - Handler configuration with the event store backend.
930
+ * @returns A handler function compatible with `node.onDefault(handler)`.
931
+ */
932
+ declare function createEventStorageHandler(config: EventStorageHandlerConfig): Handler;
933
+
934
+ /**
935
+ * EIP-3009 types and constants for the x402 publish endpoint.
936
+ *
937
+ * EIP-3009 (`transferWithAuthorization`) allows gasless USDC transfers:
938
+ * the user signs an off-chain authorization, and the facilitator (node
939
+ * operator) submits it on-chain, paying gas. The user pays only the
940
+ * USDC transfer amount.
941
+ *
942
+ * @module
943
+ */
944
+
945
+ /**
946
+ * EIP-3009 `transferWithAuthorization` signed authorization.
947
+ *
948
+ * The user signs this off-chain (EIP-712 typed data). The facilitator
949
+ * submits the signature on-chain to execute the USDC transfer.
950
+ */
951
+ interface Eip3009Authorization {
952
+ /** Sender's EVM address ('0x...'). */
953
+ from: string;
954
+ /** Recipient's EVM address ('0x...' -- facilitator). */
955
+ to: string;
956
+ /** USDC amount in micro-units (bigint). */
957
+ value: bigint;
958
+ /** Unix timestamp: authorization valid after this time. */
959
+ validAfter: number;
960
+ /** Unix timestamp: authorization expires at this time. */
961
+ validBefore: number;
962
+ /** 32-byte nonce ('0x...' hex string). */
963
+ nonce: string;
964
+ /** ECDSA recovery id (27 or 28). */
965
+ v: number;
966
+ /** ECDSA r component ('0x...' 32 bytes). */
967
+ r: string;
968
+ /** ECDSA s component ('0x...' 32 bytes). */
969
+ s: string;
970
+ }
971
+ /**
972
+ * EIP-712 typed data structure for `transferWithAuthorization`.
973
+ *
974
+ * This is the type definition used for off-chain signature verification
975
+ * and on-chain contract calls.
976
+ *
977
+ * NOTE: The EIP-712 domain for USDC's `transferWithAuthorization` is
978
+ * different from the EIP-712 domain for TokenNetwork's balance proofs.
979
+ * The x402 handler must use the USDC contract's domain.
980
+ */
981
+ declare const EIP_3009_TYPES: {
982
+ readonly TransferWithAuthorization: readonly [{
983
+ readonly name: "from";
984
+ readonly type: "address";
985
+ }, {
986
+ readonly name: "to";
987
+ readonly type: "address";
988
+ }, {
989
+ readonly name: "value";
990
+ readonly type: "uint256";
991
+ }, {
992
+ readonly name: "validAfter";
993
+ readonly type: "uint256";
994
+ }, {
995
+ readonly name: "validBefore";
996
+ readonly type: "uint256";
997
+ }, {
998
+ readonly name: "nonce";
999
+ readonly type: "bytes32";
1000
+ }];
1001
+ };
1002
+ /**
1003
+ * EIP-712 domain separator for USDC's `transferWithAuthorization`.
1004
+ *
1005
+ * Uses the USDC contract's name and version, NOT the TokenNetwork's.
1006
+ */
1007
+ declare const USDC_EIP712_DOMAIN: {
1008
+ readonly name: "USD Coin";
1009
+ readonly version: "2";
1010
+ };
1011
+ /**
1012
+ * Minimal EventStore interface for destination reachability checks.
1013
+ * Uses structural typing to avoid importing @toon-protocol/relay directly.
1014
+ * The query method accepts Filter[] (array) per the relay's EventStore interface.
1015
+ */
1016
+ interface EventStoreLike {
1017
+ query(filters: {
1018
+ kinds?: number[];
1019
+ authors?: string[];
1020
+ }[]): unknown[];
1021
+ }
1022
+ /**
1023
+ * Request body for the x402 `/publish` endpoint.
1024
+ *
1025
+ * The client sends a signed Nostr event and a destination ILP address.
1026
+ * The handler TOON-encodes the event before routing.
1027
+ */
1028
+ interface X402PublishRequest {
1029
+ /** Signed Nostr event. */
1030
+ event: NostrEvent;
1031
+ /** Target ILP address (e.g., "g.toon.target-relay"). */
1032
+ destination: string;
1033
+ }
1034
+ /**
1035
+ * Response body for a successful x402 `/publish` request (HTTP 200).
1036
+ */
1037
+ interface X402PublishResponse {
1038
+ /** Nostr event ID (64-char hex). */
1039
+ eventId: string;
1040
+ /** On-chain settlement transaction hash. */
1041
+ settlementTxHash: string;
1042
+ /** Whether the ILP PREPARE was fulfilled or rejected by the destination. */
1043
+ deliveryStatus: 'fulfilled' | 'rejected';
1044
+ /** Always false -- no refunds on REJECT per protocol design. */
1045
+ refundInitiated: false;
1046
+ }
1047
+ /**
1048
+ * Response body for the 402 pricing negotiation.
1049
+ */
1050
+ interface X402PricingResponse {
1051
+ /** Price in USDC micro-units (as string for BigInt serialization). */
1052
+ amount: string;
1053
+ /** Node operator's EVM address that will receive the USDC. */
1054
+ facilitatorAddress: string;
1055
+ /** Payment network identifier. */
1056
+ paymentNetwork: 'eip-3009';
1057
+ /** EVM chain ID. */
1058
+ chainId: number;
1059
+ /** USDC contract address on this chain. */
1060
+ usdcAddress: string;
1061
+ }
1062
+ /**
1063
+ * Minimal USDC ABI for EIP-3009 operations.
1064
+ *
1065
+ * Includes only the functions needed by the x402 handler:
1066
+ * - `balanceOf`: Read sender's USDC balance (pre-flight check #2)
1067
+ * - `authorizationState`: Check nonce freshness (pre-flight check #3)
1068
+ * - `transferWithAuthorization`: Execute gasless USDC transfer (settlement)
1069
+ */
1070
+ declare const USDC_ABI: readonly [{
1071
+ readonly name: "balanceOf";
1072
+ readonly type: "function";
1073
+ readonly stateMutability: "view";
1074
+ readonly inputs: readonly [{
1075
+ readonly name: "account";
1076
+ readonly type: "address";
1077
+ }];
1078
+ readonly outputs: readonly [{
1079
+ readonly name: "";
1080
+ readonly type: "uint256";
1081
+ }];
1082
+ }, {
1083
+ readonly name: "authorizationState";
1084
+ readonly type: "function";
1085
+ readonly stateMutability: "view";
1086
+ readonly inputs: readonly [{
1087
+ readonly name: "authorizer";
1088
+ readonly type: "address";
1089
+ }, {
1090
+ readonly name: "nonce";
1091
+ readonly type: "bytes32";
1092
+ }];
1093
+ readonly outputs: readonly [{
1094
+ readonly name: "";
1095
+ readonly type: "bool";
1096
+ }];
1097
+ }, {
1098
+ readonly name: "transferWithAuthorization";
1099
+ readonly type: "function";
1100
+ readonly stateMutability: "nonpayable";
1101
+ readonly inputs: readonly [{
1102
+ readonly name: "from";
1103
+ readonly type: "address";
1104
+ }, {
1105
+ readonly name: "to";
1106
+ readonly type: "address";
1107
+ }, {
1108
+ readonly name: "value";
1109
+ readonly type: "uint256";
1110
+ }, {
1111
+ readonly name: "validAfter";
1112
+ readonly type: "uint256";
1113
+ }, {
1114
+ readonly name: "validBefore";
1115
+ readonly type: "uint256";
1116
+ }, {
1117
+ readonly name: "nonce";
1118
+ readonly type: "bytes32";
1119
+ }, {
1120
+ readonly name: "v";
1121
+ readonly type: "uint8";
1122
+ }, {
1123
+ readonly name: "r";
1124
+ readonly type: "bytes32";
1125
+ }, {
1126
+ readonly name: "s";
1127
+ readonly type: "bytes32";
1128
+ }];
1129
+ readonly outputs: readonly [];
1130
+ }];
1131
+
1132
+ /**
1133
+ * Pre-flight validation pipeline for the x402 publish endpoint.
1134
+ *
1135
+ * Implements 6 free checks that run before any on-chain transaction,
1136
+ * preventing gas griefing (E3-R008). All checks are either pure
1137
+ * cryptography or read-only RPC calls (no gas cost).
1138
+ *
1139
+ * Check order (cheapest to most expensive):
1140
+ * 1. EIP-3009 signature verification (off-chain, ~1ms)
1141
+ * 2. USDC balance check (eth_call, ~50ms)
1142
+ * 3. Nonce freshness check (eth_call, ~50ms)
1143
+ * 4. TOON shallow parse (pure computation, ~0.1ms)
1144
+ * 5. Schnorr signature verification (pure crypto, ~2ms)
1145
+ * 6. Destination reachability check (local lookup, ~0.1ms)
1146
+ *
1147
+ * @module
1148
+ */
1149
+
1150
+ /**
1151
+ * Result of running the pre-flight validation pipeline.
1152
+ */
1153
+ interface PreflightResult {
1154
+ /** Whether all checks passed. */
1155
+ passed: boolean;
1156
+ /** Which check failed (only set if passed is false). */
1157
+ failedCheck?: string;
1158
+ /** List of check names that were executed. */
1159
+ checksPerformed: string[];
1160
+ }
1161
+ /**
1162
+ * Callback for Schnorr signature verification.
1163
+ * Returns true if the signature is valid.
1164
+ */
1165
+ type SchnorrVerifyFn = (meta: ToonRoutingMeta) => Promise<boolean>;
1166
+ /**
1167
+ * Configuration for the pre-flight validation pipeline.
1168
+ */
1169
+ interface PreflightConfig {
1170
+ /** Resolved chain configuration. */
1171
+ chainConfig: ChainPreset;
1172
+ /** Base price per byte for pricing validation. */
1173
+ basePricePerByte: bigint;
1174
+ /** This node's Nostr public key. */
1175
+ ownPubkey: string;
1176
+ /** Whether dev mode is enabled (skips Schnorr verification). */
1177
+ devMode: boolean;
1178
+ /** viem public client for read-only contract calls (optional, for testing). */
1179
+ publicClient?: PublicClient;
1180
+ /** EventStore for destination reachability check (optional). */
1181
+ eventStore?: EventStoreLike;
1182
+ /** Schnorr verification callback (optional, uses SDK verification pipeline). */
1183
+ schnorrVerify?: SchnorrVerifyFn;
1184
+ }
1185
+ /**
1186
+ * Run the 6-stage pre-flight validation pipeline.
1187
+ *
1188
+ * All checks are free (no gas cost). If any check fails, execution
1189
+ * stops immediately and no on-chain transaction is attempted.
1190
+ *
1191
+ * @param authorization - EIP-3009 signed authorization from the client.
1192
+ * @param toonData - Base64-encoded TOON payload.
1193
+ * @param destination - Target ILP address.
1194
+ * @param config - Pre-flight configuration.
1195
+ * @returns PreflightResult indicating success or which check failed.
1196
+ */
1197
+ declare function runPreflight(authorization: Eip3009Authorization, toonData: string, destination: string, config: PreflightConfig): Promise<PreflightResult>;
1198
+
1199
+ /**
1200
+ * EIP-3009 on-chain settlement module for the x402 publish endpoint.
1201
+ *
1202
+ * Executes `transferWithAuthorization` on the USDC contract to settle
1203
+ * the gasless USDC transfer from the client to the facilitator (node
1204
+ * operator). The facilitator pays gas; the client pays only USDC.
1205
+ *
1206
+ * Settlement atomicity (E3-R006):
1207
+ * - If settlement fails (revert), no ILP PREPARE is constructed.
1208
+ * - If settlement succeeds but ILP PREPARE is rejected, no refund.
1209
+ *
1210
+ * @module
1211
+ */
1212
+
1213
+ /**
1214
+ * Result of an EIP-3009 settlement attempt.
1215
+ */
1216
+ interface X402SettlementResult {
1217
+ /** Whether the on-chain transaction succeeded. */
1218
+ success: boolean;
1219
+ /** Transaction hash (only set on success). */
1220
+ txHash?: string;
1221
+ /** Error message (only set on failure). */
1222
+ error?: string;
1223
+ }
1224
+ /**
1225
+ * @deprecated Use X402SettlementResult instead.
1226
+ */
1227
+ type SettlementResult = X402SettlementResult;
1228
+ /**
1229
+ * Configuration for the settlement module.
1230
+ *
1231
+ * Named `X402SettlementConfig` to avoid collision with
1232
+ * `SettlementConfig` from `@toon-protocol/core` (bootstrap).
1233
+ */
1234
+ interface X402SettlementConfig {
1235
+ /** Resolved chain configuration. */
1236
+ chainConfig: ChainPreset;
1237
+ /** viem wallet client for the facilitator (submits the tx, pays gas). */
1238
+ walletClient: WalletClient;
1239
+ /** viem public client for waiting on transaction receipts. */
1240
+ publicClient?: PublicClient;
1241
+ }
1242
+ /**
1243
+ * Settle an EIP-3009 `transferWithAuthorization` on-chain.
1244
+ *
1245
+ * Submits the client's signed authorization to the USDC contract.
1246
+ * The facilitator (node operator) pays gas for the transaction.
1247
+ *
1248
+ * @param authorization - Signed EIP-3009 authorization from the client.
1249
+ * @param config - Settlement configuration with wallet client.
1250
+ * @returns SettlementResult indicating success/failure.
1251
+ */
1252
+ /**
1253
+ * @deprecated Use X402SettlementConfig instead.
1254
+ */
1255
+ type SettlementConfig = X402SettlementConfig;
1256
+ declare function settleEip3009(authorization: Eip3009Authorization, config: X402SettlementConfig): Promise<X402SettlementResult>;
1257
+
1258
+ /**
1259
+ * x402 publish handler for the TOON protocol.
1260
+ *
1261
+ * Implements the HTTP-native payment on-ramp via the x402 protocol pattern.
1262
+ * Allows any HTTP client (AI agents, browsers, CLI tools) to publish Nostr
1263
+ * events to the network by paying USDC, without understanding ILP or
1264
+ * running an ILP client.
1265
+ *
1266
+ * Flow:
1267
+ * 1. Client sends request without X-PAYMENT header -> 402 with pricing
1268
+ * 2. Client signs EIP-3009 auth and retries with X-PAYMENT header
1269
+ * 3. Handler runs 6 free pre-flight checks
1270
+ * 4. Handler settles USDC on-chain via transferWithAuthorization
1271
+ * 5. Handler constructs ILP PREPARE via shared buildIlpPrepare()
1272
+ * 6. Handler routes PREPARE through connector
1273
+ * 7. Handler returns 200 with event ID and tx hash
1274
+ *
1275
+ * @module
1276
+ */
1277
+
1278
+ /**
1279
+ * Configuration for the x402 publish handler.
1280
+ */
1281
+ interface X402HandlerConfig {
1282
+ /** Whether x402 is enabled for this node. */
1283
+ x402Enabled: boolean;
1284
+ /** Resolved chain configuration. */
1285
+ chainConfig: ChainPreset;
1286
+ /** Base price per byte in USDC micro-units. */
1287
+ basePricePerByte: bigint;
1288
+ /** Routing buffer percentage for multi-hop overhead (default: 10). */
1289
+ routingBufferPercent: number;
1290
+ /** Facilitator's EVM address (receives USDC payments). */
1291
+ facilitatorAddress: string;
1292
+ /** This node's Nostr public key. */
1293
+ ownPubkey: string;
1294
+ /** Whether dev mode is enabled (skips Schnorr verification). */
1295
+ devMode: boolean;
1296
+ /** ILP client for sending PREPARE packets. */
1297
+ ilpClient?: IlpClient;
1298
+ /** Event store for destination reachability check. */
1299
+ eventStore?: EventStoreLike;
1300
+ /** TOON encoder function (defaults to core's encodeEventToToon). */
1301
+ toonEncoder?: (event: NostrEvent) => Uint8Array;
1302
+ /** viem wallet client for on-chain settlement (facilitator pays gas). */
1303
+ walletClient?: WalletClient;
1304
+ /** viem public client for read-only contract calls. */
1305
+ publicClient?: PublicClient;
1306
+ /** Override settle function (for testing). */
1307
+ settle?: (auth: Eip3009Authorization, config: X402SettlementConfig) => Promise<X402SettlementResult>;
1308
+ /** Override pre-flight function (for testing). */
1309
+ runPreflightFn?: typeof runPreflight;
1310
+ }
1311
+ /**
1312
+ * x402 publish handler instance.
1313
+ */
1314
+ interface X402Handler {
1315
+ /** Handle a /publish request (both 402 pricing and paid publish). */
1316
+ handlePublish: (c: Context) => Promise<Response>;
1317
+ }
1318
+ /**
1319
+ * Create an x402 publish handler.
1320
+ *
1321
+ * Returns a handler that processes both the 402 pricing negotiation
1322
+ * (no X-PAYMENT header) and the paid publish flow (with X-PAYMENT header).
1323
+ *
1324
+ * @param config - Handler configuration.
1325
+ * @returns X402Handler with handlePublish method.
1326
+ */
1327
+ declare function createX402Handler(config: X402HandlerConfig): X402Handler;
1328
+
1329
+ /**
1330
+ * x402 pricing calculator with multi-hop routing buffer.
1331
+ *
1332
+ * Computes the all-in USDC price for publishing a Nostr event via the
1333
+ * x402 HTTP on-ramp. The price includes a configurable routing buffer
1334
+ * (default 10%) to cover multi-hop overhead -- intermediate relays charge
1335
+ * their own per-byte fees.
1336
+ *
1337
+ * @module
1338
+ */
1339
+ /**
1340
+ * Configuration for the x402 pricing calculator.
1341
+ */
1342
+ interface X402PricingConfig {
1343
+ /** Base price per byte in ILP/USDC micro-units (e.g., 10n). */
1344
+ basePricePerByte: bigint;
1345
+ /** Routing buffer percentage (default: 10, meaning 10%). */
1346
+ routingBufferPercent: number;
1347
+ }
1348
+ /**
1349
+ * Calculate the all-in x402 price for a TOON payload.
1350
+ *
1351
+ * Formula:
1352
+ * basePrice = basePricePerByte * toonLength
1353
+ * buffer = basePrice * routingBufferPercent / 100
1354
+ * total = basePrice + buffer
1355
+ *
1356
+ * The routing buffer covers multi-hop overhead. 10% default is a
1357
+ * conservative estimate per Party Mode Decision 8.
1358
+ *
1359
+ * @param config - Pricing configuration with base price and buffer percent.
1360
+ * @param toonLength - Length of the TOON-encoded payload in bytes.
1361
+ * @returns Total price in USDC micro-units.
1362
+ */
1363
+ declare function calculateX402Price(config: X402PricingConfig, toonLength: number): bigint;
1364
+
453
1365
  /**
454
1366
  * @toon-protocol/relay
455
1367
  *
@@ -457,4 +1369,4 @@ declare class RelaySubscriber {
457
1369
  */
458
1370
  declare const VERSION = "0.1.0";
459
1371
 
460
- export { type BlsConfig, BlsError, BusinessLogicServer, ConnectionHandler, DEFAULT_RELAY_CONFIG, type EventStore, type HandlePacketAcceptResponse, type HandlePacketRejectResponse, type HandlePacketRequest, type HandlePacketResponse, ILP_ERROR_CODES, InMemoryEventStore, NostrRelayServer, type PricingConfig, PricingError, PricingService, type RelayConfig, RelayError, RelaySubscriber, type RelaySubscriberConfig, SqliteEventStore, type Subscription, VERSION, isValidPubkey, loadPricingConfigFromEnv, loadPricingConfigFromFile, matchFilter };
1372
+ export { type BlsConfig, BlsError, BusinessLogicServer, ConnectionHandler, DEFAULT_RELAY_CONFIG, EIP_3009_TYPES, type Eip3009Authorization, type EventStorageHandlerConfig, type EventStore, type EventStoreLike, type HandlePacketAcceptResponse, type HandlePacketRejectResponse, type HandlePacketRequest, type HandlePacketResponse, type HealthConfig, type HealthResponse, ILP_ERROR_CODES, InMemoryEventStore, NostrRelayServer, type PreflightConfig, type PreflightResult, type PricingConfig, PricingError, PricingService, type RelayConfig, RelayError, type RelayInstance, type RelayServerConfig, RelaySubscriber, type RelaySubscriberConfig, type RelaySubscription, type ResolvedRelayConfig, type ResolvedTownConfig, type SettlementConfig, type SettlementResult, SqliteEventStore, type Subscription, type TeeHealthInfo, type TownConfig, type TownInstance, type TownSubscription, USDC_ABI, USDC_EIP712_DOMAIN, VERSION, type X402Handler, type X402HandlerConfig, type X402PricingConfig, type X402PricingResponse, type X402PublishRequest, type X402PublishResponse, type X402SettlementConfig, type X402SettlementResult, calculateX402Price, createEventStorageHandler, createHealthResponse, createX402Handler, isValidPubkey, loadPricingConfigFromEnv, loadPricingConfigFromFile, matchFilter, runPreflight, settleEip3009, startRelay, startTown };