@toon-protocol/client 0.11.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.js CHANGED
@@ -1,10 +1,3 @@
1
- import {
2
- ANON_ASSETS,
3
- ANON_VERSION,
4
- selectAnonAsset,
5
- startManagedAnonProxy
6
- } from "./chunk-WHAEQLIW.js";
7
-
8
1
  // src/ToonClient.ts
9
2
  import { generateSecretKey as generateSecretKey3, getPublicKey as getPublicKey2, finalizeEvent } from "nostr-tools/pure";
10
3
 
@@ -263,26 +256,49 @@ function getNetworkStatus(config) {
263
256
  if (!network || network === "custom") return void 0;
264
257
  return resolveClientNetwork(network).status;
265
258
  }
259
+ function proxyIlpEndpoint(proxyUrl) {
260
+ if (!proxyUrl) return void 0;
261
+ const trimmed = proxyUrl.replace(/\/+$/, "");
262
+ return /\/ilp$/i.test(trimmed) ? trimmed : `${trimmed}/ilp`;
263
+ }
266
264
  function validateConfig(config) {
267
265
  if (config.connector !== void 0) {
268
266
  throw new ValidationError(
269
267
  "Embedded mode not yet implemented in ToonClient. Use connectorUrl for HTTP mode."
270
268
  );
271
269
  }
272
- if (!config.connectorUrl) {
270
+ if (!config.connectorUrl && !config.proxyUrl) {
273
271
  throw new ValidationError(
274
- 'connectorUrl is required for HTTP mode. Example: "http://localhost:8080"'
272
+ 'connectorUrl (or proxyUrl) is required for HTTP mode. Example: "http://localhost:8080"'
275
273
  );
276
274
  }
277
- try {
278
- const url = new URL(config.connectorUrl);
279
- if (!url.protocol.startsWith("http")) {
280
- throw new Error("Must be HTTP or HTTPS");
275
+ if (config.connectorUrl) {
276
+ try {
277
+ const url = new URL(config.connectorUrl);
278
+ if (!url.protocol.startsWith("http")) {
279
+ throw new Error("Must be HTTP or HTTPS");
280
+ }
281
+ } catch (error) {
282
+ throw new ValidationError(
283
+ `Invalid connectorUrl: must be a valid HTTP/HTTPS URL (e.g., "http://localhost:8080"). Error: ${error instanceof Error ? error.message : String(error)}`
284
+ );
285
+ }
286
+ }
287
+ for (const [field, value] of [
288
+ ["proxyUrl", config.proxyUrl],
289
+ ["faucetUrl", config.faucetUrl]
290
+ ]) {
291
+ if (value === void 0) continue;
292
+ try {
293
+ const url = new URL(value);
294
+ if (!url.protocol.startsWith("http")) {
295
+ throw new Error("Must be HTTP or HTTPS");
296
+ }
297
+ } catch (error) {
298
+ throw new ValidationError(
299
+ `Invalid ${field}: must be a valid HTTP/HTTPS URL. Error: ${error instanceof Error ? error.message : String(error)}`
300
+ );
281
301
  }
282
- } catch (error) {
283
- throw new ValidationError(
284
- `Invalid connectorUrl: must be a valid HTTP/HTTPS URL (e.g., "http://localhost:8080"). Error: ${error instanceof Error ? error.message : String(error)}`
285
- );
286
302
  }
287
303
  if (config.secretKey !== void 0) {
288
304
  if (!config.secretKey || config.secretKey.length !== 32) {
@@ -346,25 +362,6 @@ function validateConfig(config) {
346
362
  );
347
363
  }
348
364
  }
349
- if (config.transport) {
350
- if (config.transport.type === "socks5") {
351
- if (!config.transport.socksProxy?.startsWith("socks5h://")) {
352
- throw new ValidationError(
353
- 'transport.socksProxy must use socks5h:// scheme to prevent DNS leaks. The "h" suffix ensures .anyone hostnames are resolved by the proxy, not locally.'
354
- );
355
- }
356
- } else if (config.transport.type === "gateway") {
357
- if (!config.transport.gatewayUrl) {
358
- throw new ValidationError(
359
- "transport.gatewayUrl is required for gateway transport"
360
- );
361
- }
362
- } else if (config.transport.type !== "direct") {
363
- throw new ValidationError(
364
- `Unknown transport type: "${config.transport.type}"`
365
- );
366
- }
367
- }
368
365
  if (config.chainRpcUrls && config.supportedChains) {
369
366
  for (const chain of Object.keys(config.chainRpcUrls)) {
370
367
  if (!config.supportedChains.includes(chain)) {
@@ -381,19 +378,21 @@ function applyDefaults(rawConfig) {
381
378
  config.mnemonic,
382
379
  config.mnemonicAccountIndex ?? 0
383
380
  ).secretKey : generateSecretKey2());
381
+ const connectorHttpEndpoint = config.connectorHttpEndpoint ?? proxyIlpEndpoint(config.proxyUrl);
382
+ const connectorUrl = config.connectorUrl ?? config.proxyUrl?.replace(/\/+$/, "");
384
383
  let btpUrl = config.btpUrl;
385
- if (!btpUrl && config.connectorUrl) {
384
+ if (!btpUrl && connectorUrl && !connectorHttpEndpoint) {
386
385
  try {
387
- const url = new URL(config.connectorUrl);
386
+ const url = new URL(connectorUrl);
388
387
  const wsProtocol = url.protocol === "https:" ? "wss:" : "ws:";
389
388
  btpUrl = `${wsProtocol}//${url.hostname}:3000`;
390
389
  } catch {
391
390
  }
392
391
  }
393
392
  let destinationAddress = config.destinationAddress;
394
- if (!destinationAddress && config.connectorUrl) {
393
+ if (!destinationAddress && connectorUrl) {
395
394
  try {
396
- const url = new URL(config.connectorUrl);
395
+ const url = new URL(connectorUrl);
397
396
  if (url.hostname === "localhost" || url.hostname === "127.0.0.1") {
398
397
  if (url.port === "8080") {
399
398
  destinationAddress = "g.toon.genesis";
@@ -416,8 +415,10 @@ function applyDefaults(rawConfig) {
416
415
  ...config,
417
416
  secretKey,
418
417
  evmPrivateKey,
419
- connectorUrl: config.connectorUrl,
420
- // Already validated as required
418
+ // Satisfied by connectorUrl OR proxyUrl (validateConfig enforced one of them).
419
+ connectorUrl,
420
+ // Surface the derived `POST /ilp` endpoint so HTTP mode selects HttpIlpClient.
421
+ ...connectorHttpEndpoint ? { connectorHttpEndpoint } : {},
421
422
  relayUrl: config.relayUrl ?? "ws://localhost:7100",
422
423
  queryTimeout: config.queryTimeout ?? 3e4,
423
424
  maxRetries: config.maxRetries ?? 3,
@@ -480,6 +481,15 @@ function isBase64(str) {
480
481
  return /^[A-Za-z0-9+/]*={0,2}$/.test(str);
481
482
  }
482
483
 
484
+ // src/utils/store-envelope.ts
485
+ var REQUEST_LINE = "POST /write HTTP/1.1";
486
+ var HEADERS = ["Host: relay", "Content-Type: application/json"];
487
+ function buildStoreWriteEnvelope(event) {
488
+ const body = JSON.stringify({ event });
489
+ const head = [REQUEST_LINE, ...HEADERS].join("\r\n");
490
+ return encodeUtf8(head + "\r\n\r\n" + body);
491
+ }
492
+
483
493
  // src/modes/http.ts
484
494
  import { BootstrapService, createDiscoveryTracker } from "@toon-protocol/core";
485
495
 
@@ -2698,82 +2708,10 @@ var EvmSigner = class {
2698
2708
  }
2699
2709
  };
2700
2710
 
2701
- // src/transport/index.ts
2702
- function isAnyoneHost(url) {
2703
- if (!url) return false;
2704
- try {
2705
- const withScheme = /:\/\//.test(url) ? url : `ws://${url}`;
2706
- const host = new URL(withScheme).hostname.toLowerCase();
2707
- return host.endsWith(".anyone");
2708
- } catch {
2709
- return false;
2710
- }
2711
- }
2712
- async function resolveTransport(transport, originalBtpUrl, originalConnectorUrl, managedProxyOptions) {
2713
- const hasExplicitProxy = !!transport && (transport.type === "socks5" || transport.type === "gateway");
2714
- const envProxy = process.env["ANYONE_PROXY_URLS"];
2715
- if (!hasExplicitProxy && managedProxyOptions?.managedAnonProxy !== false && !envProxy && isAnyoneHost(originalBtpUrl)) {
2716
- const { startManagedAnonProxy: startManagedAnonProxy2 } = await import("./anon-proxy-W3KMM7GU.js");
2717
- const { createSocks5WebSocketFactory, createSocks5Fetch } = await import("./socks5-WTJBYGME.js");
2718
- const proxy = await startManagedAnonProxy2({
2719
- ...managedProxyOptions?.managedAnonSocksPort !== void 0 ? { socksPort: managedProxyOptions.managedAnonSocksPort } : {}
2720
- });
2721
- try {
2722
- return {
2723
- createWebSocket: createSocks5WebSocketFactory(proxy.socksProxy),
2724
- httpClient: createSocks5Fetch(proxy.socksProxy),
2725
- stopManagedProxy: proxy.stop
2726
- };
2727
- } catch (err) {
2728
- await proxy.stop();
2729
- throw err;
2730
- }
2731
- }
2732
- if (!transport || transport.type === "direct") {
2733
- return {};
2734
- }
2735
- if (transport.type === "socks5") {
2736
- const {
2737
- createSocks5WebSocketFactory,
2738
- createSocks5Fetch,
2739
- probeSocks5Proxy
2740
- } = await import("./socks5-WTJBYGME.js");
2741
- await probeSocks5Proxy(transport.socksProxy);
2742
- return {
2743
- createWebSocket: createSocks5WebSocketFactory(transport.socksProxy),
2744
- httpClient: createSocks5Fetch(transport.socksProxy)
2745
- };
2746
- }
2747
- if (transport.type === "gateway") {
2748
- const { rewriteUrlsForGateway } = await import("./gateway-QOK47RKS.js");
2749
- const rewritten = rewriteUrlsForGateway(
2750
- transport.gatewayUrl,
2751
- originalBtpUrl,
2752
- originalConnectorUrl
2753
- );
2754
- return {
2755
- btpUrl: rewritten.btpUrl,
2756
- connectorUrl: rewritten.connectorUrl
2757
- };
2758
- }
2759
- throw new Error(
2760
- `Unknown transport type: "${transport.type}"`
2761
- );
2762
- }
2763
-
2764
2711
  // src/modes/http.ts
2765
2712
  async function initializeHttpMode(config) {
2766
- const transport = await resolveTransport(
2767
- config.transport,
2768
- config.btpUrl,
2769
- config.connectorUrl,
2770
- {
2771
- ...config.managedAnonProxy !== void 0 ? { managedAnonProxy: config.managedAnonProxy } : {},
2772
- ...config.managedAnonSocksPort !== void 0 ? { managedAnonSocksPort: config.managedAnonSocksPort } : {}
2773
- }
2774
- );
2775
- const effectiveBtpUrl = transport.btpUrl ?? config.btpUrl;
2776
- const effectiveConnectorUrl = transport.connectorUrl ?? config.connectorUrl;
2713
+ const effectiveBtpUrl = config.btpUrl;
2714
+ const effectiveConnectorUrl = config.connectorUrl;
2777
2715
  const settlementInfo = buildSettlementInfo(config);
2778
2716
  const discoveredPeer = readDiscoveredIlpPeer({
2779
2717
  btpEndpoint: effectiveBtpUrl,
@@ -2786,8 +2724,7 @@ async function initializeHttpMode(config) {
2786
2724
  btpClient = new BtpRuntimeClient({
2787
2725
  btpUrl: effectiveBtpUrl,
2788
2726
  peerId: config.btpPeerId ?? `client`,
2789
- authToken: config.btpAuthToken ?? "",
2790
- createWebSocket: transport.createWebSocket
2727
+ authToken: config.btpAuthToken ?? ""
2791
2728
  });
2792
2729
  await btpClient.connect();
2793
2730
  }
@@ -2799,17 +2736,14 @@ async function initializeHttpMode(config) {
2799
2736
  ...config.btpAuthToken !== void 0 ? { authToken: config.btpAuthToken } : {},
2800
2737
  timeout: config.queryTimeout,
2801
2738
  maxRetries: config.maxRetries,
2802
- retryDelay: config.retryDelay,
2803
- ...transport.httpClient !== void 0 ? { httpClient: transport.httpClient } : {},
2804
- ...transport.createWebSocket !== void 0 ? { createWebSocket: transport.createWebSocket } : {}
2739
+ retryDelay: config.retryDelay
2805
2740
  });
2806
2741
  }
2807
2742
  const runtimeClient = httpIlpClient ?? btpClient ?? new HttpRuntimeClient({
2808
2743
  connectorUrl: effectiveConnectorUrl,
2809
2744
  timeout: config.queryTimeout,
2810
2745
  maxRetries: config.maxRetries,
2811
- retryDelay: config.retryDelay,
2812
- httpClient: transport.httpClient
2746
+ retryDelay: config.retryDelay
2813
2747
  });
2814
2748
  let onChainChannelClient = null;
2815
2749
  if (config.chainRpcUrls) {
@@ -2854,10 +2788,7 @@ async function initializeHttpMode(config) {
2854
2788
  runtimeClient,
2855
2789
  adminClient: null,
2856
2790
  btpClient,
2857
- onChainChannelClient,
2858
- // Teardown handle for a managed `anon` proxy this init STARTED (undefined
2859
- // for explicit-proxy/direct/gateway). ToonClient.stop() invokes it.
2860
- stopManagedProxy: transport.stopManagedProxy
2791
+ onChainChannelClient
2861
2792
  };
2862
2793
  }
2863
2794
 
@@ -4023,13 +3954,7 @@ var ToonClient = class {
4023
3954
  }
4024
3955
  }
4025
3956
  const initialization = await initializeHttpMode(this.config);
4026
- const {
4027
- bootstrapService,
4028
- discoveryTracker,
4029
- runtimeClient,
4030
- btpClient,
4031
- stopManagedProxy
4032
- } = initialization;
3957
+ const { bootstrapService, discoveryTracker, runtimeClient, btpClient } = initialization;
4033
3958
  if (this.channelManager) {
4034
3959
  const cm = this.channelManager;
4035
3960
  const nostrPubkey = this.getPublicKey();
@@ -4117,8 +4042,7 @@ var ToonClient = class {
4117
4042
  discoveryTracker,
4118
4043
  runtimeClient,
4119
4044
  peersDiscovered: bootstrapResults.length,
4120
- btpClient: btpClient ?? void 0,
4121
- ...stopManagedProxy ? { stopManagedProxy } : {}
4045
+ btpClient: btpClient ?? void 0
4122
4046
  };
4123
4047
  return {
4124
4048
  peersDiscovered: bootstrapResults.length,
@@ -4154,13 +4078,9 @@ var ToonClient = class {
4154
4078
  const toonData = this.config.toonEncoder(event);
4155
4079
  const basePricePerByte = 10n;
4156
4080
  const amount = options?.ilpAmount !== void 0 ? String(options.ilpAmount) : String(BigInt(toonData.length) * basePricePerByte);
4081
+ const writeData = buildStoreWriteEnvelope(event);
4157
4082
  const destination = options?.destination ?? this.config.destinationAddress;
4158
- if (!this.state.btpClient) {
4159
- throw new ToonClientError(
4160
- "BTP client required for publishing. Configure btpUrl.",
4161
- "NO_BTP_CLIENT"
4162
- );
4163
- }
4083
+ const transport = this.getClaimTransport();
4164
4084
  let claimMessage;
4165
4085
  if (options?.claim) {
4166
4086
  claimMessage = this.buildClaimMessageForProof(options.claim);
@@ -4189,13 +4109,11 @@ var ToonClient = class {
4189
4109
  "MISSING_CLAIM"
4190
4110
  );
4191
4111
  }
4192
- const response = await this.state.btpClient.sendIlpPacketWithClaim(
4112
+ const response = await transport.sendIlpPacketWithClaim(
4193
4113
  {
4194
4114
  destination,
4195
4115
  amount,
4196
- data: toBase64(
4197
- toonData instanceof Uint8Array ? toonData : new Uint8Array(toonData)
4198
- )
4116
+ data: toBase64(writeData)
4199
4117
  },
4200
4118
  claimMessage
4201
4119
  );
@@ -4275,7 +4193,7 @@ var ToonClient = class {
4275
4193
  * matching `destination`,
4276
4194
  * (c) neither -> throw MISSING_CLAIM.
4277
4195
  *
4278
- * @throws {ToonClientError} INVALID_STATE / NO_BTP_CLIENT / MISSING_CLAIM
4196
+ * @throws {ToonClientError} INVALID_STATE / NO_ILP_TRANSPORT / MISSING_CLAIM
4279
4197
  */
4280
4198
  async sendSwapPacket(params) {
4281
4199
  if (!this.state) {
@@ -4284,18 +4202,13 @@ var ToonClient = class {
4284
4202
  "INVALID_STATE"
4285
4203
  );
4286
4204
  }
4287
- if (!this.state.btpClient) {
4288
- throw new ToonClientError(
4289
- "BTP client required for sending swap packets. Configure btpUrl.",
4290
- "NO_BTP_CLIENT"
4291
- );
4292
- }
4205
+ const transport = this.getClaimTransport();
4293
4206
  const claimMessage = await this.resolveClaimForDestination(
4294
4207
  params.destination,
4295
4208
  params.amount,
4296
4209
  params.claim
4297
4210
  );
4298
- return this.state.btpClient.sendIlpPacketWithClaim(
4211
+ return transport.sendIlpPacketWithClaim(
4299
4212
  {
4300
4213
  destination: params.destination,
4301
4214
  amount: String(params.amount),
@@ -4335,6 +4248,51 @@ var ToonClient = class {
4335
4248
  }
4336
4249
  return EvmSigner.buildClaimMessage(claim, this.getPublicKey());
4337
4250
  }
4251
+ /**
4252
+ * Resolve the ILP transport for a paid (claim-bearing) write.
4253
+ *
4254
+ * The connector is a payment-proxy: paid writes carry an ILP PREPARE plus the
4255
+ * signed payment-channel claim. Either transport speaks the SAME claim
4256
+ * contract — the BTP `payment-channel-claim` protocolData entry and the
4257
+ * ILP-over-HTTP `ILP-Payment-Channel-Claim` header serialize the same claim
4258
+ * JSON — so we route through whichever transport is ACTIVE rather than
4259
+ * hard-requiring BTP.
4260
+ *
4261
+ * Selection (mirrors `modes/http.ts` runtime-client precedence):
4262
+ * 1. `runtimeClient` when it implements `sendIlpPacketWithClaim` — this is
4263
+ * the HttpIlpClient (proxy `POST /ilp`) when a `proxyUrl`/
4264
+ * `connectorHttpEndpoint` is configured, else the BtpRuntimeClient.
4265
+ * 2. `btpClient` as an explicit fallback (always present when `btpUrl` is set).
4266
+ *
4267
+ * The level-3 `HttpRuntimeClient` (connector-admin HTTP, no `btpUrl` AND no
4268
+ * proxy) does NOT implement `sendIlpPacketWithClaim`; in that case there is no
4269
+ * paid-write transport and we throw a clear, actionable error.
4270
+ *
4271
+ * @throws {ToonClientError} NO_ILP_TRANSPORT when no active transport can send
4272
+ * a packet+claim.
4273
+ */
4274
+ getClaimTransport() {
4275
+ const state = this.state;
4276
+ if (!state) {
4277
+ throw new ToonClientError(
4278
+ "Client not started. Call start() first.",
4279
+ "INVALID_STATE"
4280
+ );
4281
+ }
4282
+ const candidates = [
4283
+ state.runtimeClient,
4284
+ state.btpClient
4285
+ ];
4286
+ for (const candidate of candidates) {
4287
+ if (candidate && typeof candidate.sendIlpPacketWithClaim === "function") {
4288
+ return candidate;
4289
+ }
4290
+ }
4291
+ throw new ToonClientError(
4292
+ "No ILP transport for paid writes. Configure `proxyUrl`/`connectorHttpEndpoint` (route through the connector proxy over ILP-over-HTTP) or `btpUrl` (BTP socket).",
4293
+ "NO_ILP_TRANSPORT"
4294
+ );
4295
+ }
4338
4296
  /**
4339
4297
  * Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.
4340
4298
  * TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution
@@ -4523,14 +4481,9 @@ var ToonClient = class {
4523
4481
  "MISSING_CLAIM"
4524
4482
  );
4525
4483
  }
4526
- if (!this.state.btpClient) {
4527
- throw new ToonClientError(
4528
- "BTP client required for sending payments. Configure btpUrl.",
4529
- "NO_BTP_CLIENT"
4530
- );
4531
- }
4484
+ const transport = this.getClaimTransport();
4532
4485
  const claimMessage = this.buildClaimMessageForProof(params.claim);
4533
- return this.state.btpClient.sendIlpPacketWithClaim(
4486
+ return transport.sendIlpPacketWithClaim(
4534
4487
  ilpParams,
4535
4488
  claimMessage
4536
4489
  );
@@ -4548,14 +4501,10 @@ var ToonClient = class {
4548
4501
  if (!this.state) {
4549
4502
  throw new ToonClientError("Client not started", "INVALID_STATE");
4550
4503
  }
4551
- const stopManagedProxy = this.state.stopManagedProxy;
4552
4504
  try {
4553
4505
  if (this.state.btpClient) {
4554
4506
  await this.state.btpClient.disconnect();
4555
4507
  }
4556
- if (stopManagedProxy) {
4557
- await stopManagedProxy();
4558
- }
4559
4508
  this.state = null;
4560
4509
  } catch (error) {
4561
4510
  throw new ToonClientError(
@@ -4603,26 +4552,6 @@ var ToonClient = class {
4603
4552
  }
4604
4553
  };
4605
4554
 
4606
- // src/transport/hs-hostname.ts
4607
- var HS_HOSTNAME_REGEX = /^[a-z2-7]+\.anyone$/;
4608
- var HS_HOSTNAME_MAX_LENGTH = 80;
4609
- function isRoutableHsHostname(s) {
4610
- return typeof s === "string" && s.length <= HS_HOSTNAME_MAX_LENGTH && HS_HOSTNAME_REGEX.test(s);
4611
- }
4612
- function assertRoutableHsHostname(hostname) {
4613
- if (typeof hostname === "string" && /\.anon$/.test(hostname)) {
4614
- throw new Error(
4615
- `"${hostname}" is not a routable hidden-service address; use the .anyone TLD (e.g. "${hostname.replace(/\.anon$/, ".anyone")}"). The anon daemon only resolves hidden services under .anyone \u2014 a .anon name is treated as a clearnet address and fails (HostUnreachable).`
4616
- );
4617
- }
4618
- if (!isRoutableHsHostname(hostname)) {
4619
- throw new Error(
4620
- `Invalid hidden-service hostname: ${JSON.stringify(hostname)}. Expected a base32 .anyone address matching ${HS_HOSTNAME_REGEX}.`
4621
- );
4622
- }
4623
- return hostname;
4624
- }
4625
-
4626
4555
  // src/adapters/HttpConnectorAdmin.ts
4627
4556
  var HttpConnectorAdmin = class {
4628
4557
  adminUrl;
@@ -5361,6 +5290,75 @@ function buildPetPurchaseRequest(params) {
5361
5290
  };
5362
5291
  }
5363
5292
 
5293
+ // src/faucet.ts
5294
+ function faucetPath(chain) {
5295
+ switch (chain) {
5296
+ case "evm":
5297
+ return "/api/request";
5298
+ case "solana":
5299
+ return "/api/solana/request";
5300
+ case "mina":
5301
+ return "/api/mina/request";
5302
+ }
5303
+ }
5304
+ async function fundWallet(faucetUrl, address, chain, options = {}) {
5305
+ if (!faucetUrl) {
5306
+ throw new Error("fundWallet: faucetUrl is required");
5307
+ }
5308
+ if (!address) {
5309
+ throw new Error("fundWallet: address is required");
5310
+ }
5311
+ if (chain === "solana" || chain === "mina") {
5312
+ throw new Error(
5313
+ `fundWallet: ${chain} faucet funding is deferred (WS3) \u2014 not yet implemented`
5314
+ );
5315
+ }
5316
+ const base = faucetUrl.replace(/\/+$/, "");
5317
+ const url = `${base}${faucetPath(chain)}`;
5318
+ const fetchImpl = options.fetchImpl ?? fetch;
5319
+ const timeout = options.timeout ?? 3e4;
5320
+ const controller = new AbortController();
5321
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
5322
+ let response;
5323
+ try {
5324
+ response = await fetchImpl(url, {
5325
+ method: "POST",
5326
+ headers: { "Content-Type": "application/json" },
5327
+ body: JSON.stringify({ address }),
5328
+ signal: controller.signal
5329
+ });
5330
+ } catch (error) {
5331
+ if (error instanceof Error && error.name === "AbortError") {
5332
+ throw new NetworkError(
5333
+ `Faucet request timed out after ${timeout}ms (${url})`,
5334
+ error
5335
+ );
5336
+ }
5337
+ throw new NetworkError(
5338
+ `Faucet request failed (${url}): ${error instanceof Error ? error.message : String(error)}`,
5339
+ error instanceof Error ? error : void 0
5340
+ );
5341
+ } finally {
5342
+ clearTimeout(timeoutId);
5343
+ }
5344
+ if (!response.ok) {
5345
+ const detail = await response.text().catch(() => "");
5346
+ throw new NetworkError(
5347
+ `Faucet responded ${response.status} ${response.statusText}${detail ? `: ${detail}` : ""} (${url})`
5348
+ );
5349
+ }
5350
+ const body = await response.text().catch(() => "");
5351
+ let parsed = body;
5352
+ if (body) {
5353
+ try {
5354
+ parsed = JSON.parse(body);
5355
+ } catch {
5356
+ parsed = body;
5357
+ }
5358
+ }
5359
+ return { chain, address, response: parsed };
5360
+ }
5361
+
5364
5362
  // src/keys/KeyManager.ts
5365
5363
  import { finalizeEvent as finalizeEvent2 } from "nostr-tools/pure";
5366
5364
  import { nip19 } from "nostr-tools";
@@ -6401,14 +6399,10 @@ function writeKeystoreFile(path, keystore) {
6401
6399
  });
6402
6400
  }
6403
6401
  export {
6404
- ANON_ASSETS,
6405
- ANON_VERSION,
6406
6402
  BtpRuntimeClient,
6407
6403
  ChannelManager,
6408
6404
  ConnectorError,
6409
6405
  EvmSigner,
6410
- HS_HOSTNAME_MAX_LENGTH,
6411
- HS_HOSTNAME_REGEX,
6412
6406
  Http402Client,
6413
6407
  HttpConnectorAdmin,
6414
6408
  HttpIlpClient,
@@ -6426,13 +6420,13 @@ export {
6426
6420
  ValidationError,
6427
6421
  applyDefaults,
6428
6422
  applyNetworkPresets,
6429
- assertRoutableHsHostname,
6430
6423
  buildBackupEvent,
6431
6424
  buildBackupFilter,
6432
6425
  buildPetInteractionRequest,
6433
6426
  buildPetListingEvent,
6434
6427
  buildPetPurchaseRequest,
6435
6428
  buildSettlementInfo,
6429
+ buildStoreWriteEnvelope,
6436
6430
  decryptMnemonic2 as decryptMnemonic,
6437
6431
  deriveFromNsec,
6438
6432
  deriveFullIdentity,
@@ -6440,6 +6434,7 @@ export {
6440
6434
  encryptMnemonic2 as encryptMnemonic,
6441
6435
  filterPetDvmProviders,
6442
6436
  filterPetListings,
6437
+ fundWallet,
6443
6438
  generateKeystore,
6444
6439
  generateMnemonic,
6445
6440
  generateRandomIdentity,
@@ -6447,7 +6442,6 @@ export {
6447
6442
  httpEndpointToBtpUrl,
6448
6443
  importKeystore,
6449
6444
  isPrfSupported,
6450
- isRoutableHsHostname,
6451
6445
  loadKeystore,
6452
6446
  parseBackupPayload,
6453
6447
  parseHttpResponse,
@@ -6456,13 +6450,12 @@ export {
6456
6450
  parsePetListing,
6457
6451
  parseX402Body,
6458
6452
  parseX402Challenge,
6453
+ proxyIlpEndpoint,
6459
6454
  readDiscoveredIlpPeer,
6460
6455
  readMinaDepositTotal,
6461
6456
  requestBlobStorage,
6462
- selectAnonAsset,
6463
6457
  selectIlpTransport,
6464
6458
  serializeHttpRequest,
6465
- startManagedAnonProxy,
6466
6459
  validateConfig,
6467
6460
  validateMnemonic,
6468
6461
  withRetry,