@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/README.md +1 -26
- package/dist/index.d.ts +576 -385
- package/dist/index.js +483 -193
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/dist/anon-proxy-W3KMM7GU.js +0 -23
- package/dist/anon-proxy-W3KMM7GU.js.map +0 -1
- package/dist/chunk-WHAEQLIW.js +0 -276
- package/dist/chunk-WHAEQLIW.js.map +0 -1
- package/dist/gateway-QOK47RKS.js +0 -13
- package/dist/gateway-QOK47RKS.js.map +0 -1
- package/dist/socks5-WTJBYGME.js +0 -136
- package/dist/socks5-WTJBYGME.js.map +0 -1
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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 &&
|
|
384
|
+
if (!btpUrl && connectorUrl && !connectorHttpEndpoint) {
|
|
386
385
|
try {
|
|
387
|
-
const url = new URL(
|
|
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 &&
|
|
393
|
+
if (!destinationAddress && connectorUrl) {
|
|
395
394
|
try {
|
|
396
|
-
const url = new URL(
|
|
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
|
|
420
|
-
|
|
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
|
|
2767
|
-
|
|
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
|
|
|
@@ -3616,6 +3547,258 @@ async function requestBlobStorage(client, secretKey, params) {
|
|
|
3616
3547
|
};
|
|
3617
3548
|
}
|
|
3618
3549
|
|
|
3550
|
+
// src/adapters/Http402Client.ts
|
|
3551
|
+
var Http402Client = class {
|
|
3552
|
+
fetchImpl;
|
|
3553
|
+
resolveClaim;
|
|
3554
|
+
createIlpClient;
|
|
3555
|
+
needsDuplex;
|
|
3556
|
+
constructor(config = {}) {
|
|
3557
|
+
this.fetchImpl = config.fetch ?? fetch;
|
|
3558
|
+
this.resolveClaim = config.resolveClaim;
|
|
3559
|
+
this.createIlpClient = config.createIlpClient ?? ((httpEndpoint) => new HttpIlpClient({ httpEndpoint }));
|
|
3560
|
+
this.needsDuplex = config.needsDuplex ?? false;
|
|
3561
|
+
}
|
|
3562
|
+
/**
|
|
3563
|
+
* `fetch()`-like entry point. Issues the request; on `402` parses the x402
|
|
3564
|
+
* challenge and — when a usable `toon-channel` offer is present and a claim
|
|
3565
|
+
* resolver is configured — pays over TOON and returns the reconstructed
|
|
3566
|
+
* `Response`. Otherwise returns the original 402 unchanged (AC5).
|
|
3567
|
+
*/
|
|
3568
|
+
async fetch(url, opts = {}) {
|
|
3569
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
3570
|
+
const probe = await this.fetchImpl(url, {
|
|
3571
|
+
method,
|
|
3572
|
+
...opts.headers ? { headers: opts.headers } : {},
|
|
3573
|
+
...opts.body !== void 0 ? { body: opts.body } : {},
|
|
3574
|
+
...opts.timeout !== void 0 ? { signal: AbortSignal.timeout(opts.timeout) } : {}
|
|
3575
|
+
});
|
|
3576
|
+
if (probe.status !== 402) return probe;
|
|
3577
|
+
const challenge = await parseX402Challenge(probe.clone());
|
|
3578
|
+
const accept = challenge.toonChannel;
|
|
3579
|
+
if (!accept || !this.resolveClaim) return probe;
|
|
3580
|
+
return this.payOverToon(url, method, opts, accept, this.resolveClaim);
|
|
3581
|
+
}
|
|
3582
|
+
/**
|
|
3583
|
+
* Open/reuse a channel (via the injected claim resolver), serialize the HTTP
|
|
3584
|
+
* request into the ILP packet `data`, send it to `POST /ilp` with the claim,
|
|
3585
|
+
* and reconstruct the origin `Response` from the FULFILL `data`.
|
|
3586
|
+
*/
|
|
3587
|
+
async payOverToon(url, method, opts, accept, resolveClaim) {
|
|
3588
|
+
const destination = opts.destination ?? accept.destination;
|
|
3589
|
+
const claim = await resolveClaim(destination, accept.amount);
|
|
3590
|
+
const requestBytes = serializeHttpRequest({
|
|
3591
|
+
method,
|
|
3592
|
+
url,
|
|
3593
|
+
headers: opts.headers,
|
|
3594
|
+
body: opts.body
|
|
3595
|
+
});
|
|
3596
|
+
const peer = {
|
|
3597
|
+
httpEndpoint: accept.httpEndpoint,
|
|
3598
|
+
supportsUpgrade: accept.supportsUpgrade
|
|
3599
|
+
};
|
|
3600
|
+
const choice = selectIlpTransport(peer, {
|
|
3601
|
+
needsDuplex: this.needsDuplex
|
|
3602
|
+
});
|
|
3603
|
+
const ilpClient = this.createIlpClient(accept.httpEndpoint);
|
|
3604
|
+
const result = await this.sendOverChoice(
|
|
3605
|
+
ilpClient,
|
|
3606
|
+
choice,
|
|
3607
|
+
{
|
|
3608
|
+
destination,
|
|
3609
|
+
amount: String(accept.amount),
|
|
3610
|
+
data: toBase64(requestBytes),
|
|
3611
|
+
...opts.timeout !== void 0 ? { timeout: opts.timeout } : {}
|
|
3612
|
+
},
|
|
3613
|
+
claim
|
|
3614
|
+
);
|
|
3615
|
+
if (!result.accepted) {
|
|
3616
|
+
throw new ConnectorError(
|
|
3617
|
+
`h402 payment rejected by connector: ${result.code ?? "F00"} ${result.message ?? ""}`.trim()
|
|
3618
|
+
);
|
|
3619
|
+
}
|
|
3620
|
+
if (!result.data) {
|
|
3621
|
+
throw new ConnectorError(
|
|
3622
|
+
"h402 FULFILL carried no data (expected an HTTP response payload)"
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
return parseHttpResponse(fromBase64(result.data));
|
|
3626
|
+
}
|
|
3627
|
+
/**
|
|
3628
|
+
* Send the serialized HTTP-in-ILP PREPARE over the selected transport.
|
|
3629
|
+
*
|
|
3630
|
+
* - `http` / `http-upgradable`: stateless one-shot `POST /ilp` with the claim.
|
|
3631
|
+
* - `http-upgradable` additionally exercises {@link HttpIlpClient.upgradeToBtp}
|
|
3632
|
+
* for the duplex/streaming path (AC4). v1 still drives the actual write over
|
|
3633
|
+
* the one-shot HTTP method even after upgrading — full duplex body streaming
|
|
3634
|
+
* is a documented follow-up — but the upgrade call path is wired here.
|
|
3635
|
+
* - `btp`: not reachable from h402 (the x402 offer only carries an
|
|
3636
|
+
* `httpEndpoint`); guarded for completeness.
|
|
3637
|
+
*/
|
|
3638
|
+
async sendOverChoice(ilpClient, choice, params, claim) {
|
|
3639
|
+
if (choice.kind === "http-upgradable") {
|
|
3640
|
+
const btp = await ilpClient.upgradeToBtp();
|
|
3641
|
+
try {
|
|
3642
|
+
return await btp.sendIlpPacketWithClaim(
|
|
3643
|
+
params,
|
|
3644
|
+
claim
|
|
3645
|
+
);
|
|
3646
|
+
} finally {
|
|
3647
|
+
await btp.disconnect().catch(() => {
|
|
3648
|
+
});
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
if (choice.kind === "btp") {
|
|
3652
|
+
throw new ToonClientError(
|
|
3653
|
+
"h402 offer resolved to a BTP-only transport; the x402 toon-channel entry must advertise an httpEndpoint",
|
|
3654
|
+
"INVALID_STATE"
|
|
3655
|
+
);
|
|
3656
|
+
}
|
|
3657
|
+
return ilpClient.sendIlpPacketWithClaim(params, claim);
|
|
3658
|
+
}
|
|
3659
|
+
};
|
|
3660
|
+
function readString(obj, keys) {
|
|
3661
|
+
for (const k of keys) {
|
|
3662
|
+
const v = obj[k];
|
|
3663
|
+
if (typeof v === "string" && v.trim().length > 0) return v.trim();
|
|
3664
|
+
}
|
|
3665
|
+
return void 0;
|
|
3666
|
+
}
|
|
3667
|
+
function readAmount(obj, keys) {
|
|
3668
|
+
for (const k of keys) {
|
|
3669
|
+
const v = obj[k];
|
|
3670
|
+
if (typeof v === "bigint") return v;
|
|
3671
|
+
if (typeof v === "number" && Number.isFinite(v)) return BigInt(Math.trunc(v));
|
|
3672
|
+
if (typeof v === "string" && /^\d+$/.test(v.trim())) return BigInt(v.trim());
|
|
3673
|
+
}
|
|
3674
|
+
return void 0;
|
|
3675
|
+
}
|
|
3676
|
+
async function parseX402Challenge(response) {
|
|
3677
|
+
let body;
|
|
3678
|
+
try {
|
|
3679
|
+
body = await response.json();
|
|
3680
|
+
} catch {
|
|
3681
|
+
return {};
|
|
3682
|
+
}
|
|
3683
|
+
return parseX402Body(body);
|
|
3684
|
+
}
|
|
3685
|
+
function parseX402Body(body) {
|
|
3686
|
+
if (typeof body !== "object" || body === null) return {};
|
|
3687
|
+
const b = body;
|
|
3688
|
+
const version = typeof b["x402Version"] === "number" ? b["x402Version"] : void 0;
|
|
3689
|
+
const accepts = Array.isArray(b["accepts"]) ? b["accepts"] : [];
|
|
3690
|
+
for (const raw of accepts) {
|
|
3691
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
3692
|
+
const entry = raw;
|
|
3693
|
+
const scheme = readString(entry, ["scheme"]);
|
|
3694
|
+
if (scheme !== "toon-channel") continue;
|
|
3695
|
+
const destination = readString(entry, [
|
|
3696
|
+
"destination",
|
|
3697
|
+
"ilpAddress",
|
|
3698
|
+
"payTo"
|
|
3699
|
+
]);
|
|
3700
|
+
const httpEndpoint = readString(entry, [
|
|
3701
|
+
"httpEndpoint",
|
|
3702
|
+
"ilpEndpoint",
|
|
3703
|
+
"endpoint"
|
|
3704
|
+
]);
|
|
3705
|
+
const amount = readAmount(entry, ["amount", "price", "maxAmountRequired"]);
|
|
3706
|
+
if (!destination || !httpEndpoint || amount === void 0) continue;
|
|
3707
|
+
const network = readString(entry, ["network", "chain"]);
|
|
3708
|
+
const supportsUpgrade = entry["supportsUpgrade"] === true || entry["upgradable"] === true;
|
|
3709
|
+
return {
|
|
3710
|
+
...version !== void 0 ? { x402Version: version } : {},
|
|
3711
|
+
toonChannel: {
|
|
3712
|
+
scheme: "toon-channel",
|
|
3713
|
+
...network !== void 0 ? { network } : {},
|
|
3714
|
+
destination,
|
|
3715
|
+
amount,
|
|
3716
|
+
httpEndpoint,
|
|
3717
|
+
supportsUpgrade
|
|
3718
|
+
}
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
return version !== void 0 ? { x402Version: version } : {};
|
|
3722
|
+
}
|
|
3723
|
+
var CRLF = "\r\n";
|
|
3724
|
+
function concatHeadAndBody(head, body) {
|
|
3725
|
+
const headBytes = encodeUtf8(head);
|
|
3726
|
+
const out = new Uint8Array(headBytes.length + body.length);
|
|
3727
|
+
out.set(headBytes, 0);
|
|
3728
|
+
out.set(body, headBytes.length);
|
|
3729
|
+
return out;
|
|
3730
|
+
}
|
|
3731
|
+
function bodyToBytes(body) {
|
|
3732
|
+
if (body === void 0) return new Uint8Array(0);
|
|
3733
|
+
return typeof body === "string" ? encodeUtf8(body) : body;
|
|
3734
|
+
}
|
|
3735
|
+
function serializeHttpRequest(req) {
|
|
3736
|
+
const u = new URL(req.url);
|
|
3737
|
+
const target = `${u.pathname}${u.search}` || "/";
|
|
3738
|
+
const bodyBytes = bodyToBytes(req.body);
|
|
3739
|
+
const headers = /* @__PURE__ */ new Map();
|
|
3740
|
+
const put = (name, value) => headers.set(name.toLowerCase(), `${name}: ${value}`);
|
|
3741
|
+
const has = (name) => headers.has(name.toLowerCase());
|
|
3742
|
+
for (const [name, value] of Object.entries(req.headers ?? {})) {
|
|
3743
|
+
put(name, value);
|
|
3744
|
+
}
|
|
3745
|
+
if (!has("host")) put("Host", u.host);
|
|
3746
|
+
if (bodyBytes.length > 0 && !has("content-length")) {
|
|
3747
|
+
put("Content-Length", String(bodyBytes.length));
|
|
3748
|
+
}
|
|
3749
|
+
const lines = [
|
|
3750
|
+
`${req.method.toUpperCase()} ${target} HTTP/1.1`,
|
|
3751
|
+
...headers.values()
|
|
3752
|
+
];
|
|
3753
|
+
const head = lines.join(CRLF) + CRLF + CRLF;
|
|
3754
|
+
return concatHeadAndBody(head, bodyBytes);
|
|
3755
|
+
}
|
|
3756
|
+
function findHeaderEnd(bytes) {
|
|
3757
|
+
for (let i = 0; i + 3 < bytes.length; i++) {
|
|
3758
|
+
if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) {
|
|
3759
|
+
return i + 4;
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
return -1;
|
|
3763
|
+
}
|
|
3764
|
+
function parseHttpResponse(bytes) {
|
|
3765
|
+
const headerEnd = findHeaderEnd(bytes);
|
|
3766
|
+
const headBytes = headerEnd === -1 ? bytes : bytes.subarray(0, headerEnd - 2);
|
|
3767
|
+
const body = headerEnd === -1 ? new Uint8Array(0) : bytes.subarray(headerEnd);
|
|
3768
|
+
const headText = decodeUtf8(headBytes);
|
|
3769
|
+
const lines = headText.split(CRLF).filter((l) => l.length > 0);
|
|
3770
|
+
const statusLine = lines.shift();
|
|
3771
|
+
if (!statusLine) {
|
|
3772
|
+
throw new ConnectorError(
|
|
3773
|
+
"h402 response payload had no HTTP status line"
|
|
3774
|
+
);
|
|
3775
|
+
}
|
|
3776
|
+
const match = /^HTTP\/\d\.\d\s+(\d{3})(?:\s+(.*))?$/.exec(statusLine.trim());
|
|
3777
|
+
if (!match) {
|
|
3778
|
+
throw new ConnectorError(
|
|
3779
|
+
`h402 response payload had a malformed status line: "${statusLine}"`
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
const status = parseInt(match[1], 10);
|
|
3783
|
+
const statusText = match[2] ?? "";
|
|
3784
|
+
const headers = new Headers();
|
|
3785
|
+
for (const line of lines) {
|
|
3786
|
+
const idx = line.indexOf(":");
|
|
3787
|
+
if (idx === -1) continue;
|
|
3788
|
+
const name = line.slice(0, idx).trim();
|
|
3789
|
+
const value = line.slice(idx + 1).trim();
|
|
3790
|
+
if (name.length === 0) continue;
|
|
3791
|
+
headers.append(name, value);
|
|
3792
|
+
}
|
|
3793
|
+
const nullBodyStatus = status === 101 || status === 204 || status === 205 || status === 304;
|
|
3794
|
+
const init = { status, headers };
|
|
3795
|
+
if (statusText) init.statusText = statusText;
|
|
3796
|
+
return new Response(
|
|
3797
|
+
nullBodyStatus || body.length === 0 ? null : body.slice(),
|
|
3798
|
+
init
|
|
3799
|
+
);
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3619
3802
|
// src/ToonClient.ts
|
|
3620
3803
|
var ToonClient = class {
|
|
3621
3804
|
config;
|
|
@@ -3771,13 +3954,7 @@ var ToonClient = class {
|
|
|
3771
3954
|
}
|
|
3772
3955
|
}
|
|
3773
3956
|
const initialization = await initializeHttpMode(this.config);
|
|
3774
|
-
const {
|
|
3775
|
-
bootstrapService,
|
|
3776
|
-
discoveryTracker,
|
|
3777
|
-
runtimeClient,
|
|
3778
|
-
btpClient,
|
|
3779
|
-
stopManagedProxy
|
|
3780
|
-
} = initialization;
|
|
3957
|
+
const { bootstrapService, discoveryTracker, runtimeClient, btpClient } = initialization;
|
|
3781
3958
|
if (this.channelManager) {
|
|
3782
3959
|
const cm = this.channelManager;
|
|
3783
3960
|
const nostrPubkey = this.getPublicKey();
|
|
@@ -3865,8 +4042,7 @@ var ToonClient = class {
|
|
|
3865
4042
|
discoveryTracker,
|
|
3866
4043
|
runtimeClient,
|
|
3867
4044
|
peersDiscovered: bootstrapResults.length,
|
|
3868
|
-
btpClient: btpClient ?? void 0
|
|
3869
|
-
...stopManagedProxy ? { stopManagedProxy } : {}
|
|
4045
|
+
btpClient: btpClient ?? void 0
|
|
3870
4046
|
};
|
|
3871
4047
|
return {
|
|
3872
4048
|
peersDiscovered: bootstrapResults.length,
|
|
@@ -3902,13 +4078,9 @@ var ToonClient = class {
|
|
|
3902
4078
|
const toonData = this.config.toonEncoder(event);
|
|
3903
4079
|
const basePricePerByte = 10n;
|
|
3904
4080
|
const amount = options?.ilpAmount !== void 0 ? String(options.ilpAmount) : String(BigInt(toonData.length) * basePricePerByte);
|
|
4081
|
+
const writeData = buildStoreWriteEnvelope(event);
|
|
3905
4082
|
const destination = options?.destination ?? this.config.destinationAddress;
|
|
3906
|
-
|
|
3907
|
-
throw new ToonClientError(
|
|
3908
|
-
"BTP client required for publishing. Configure btpUrl.",
|
|
3909
|
-
"NO_BTP_CLIENT"
|
|
3910
|
-
);
|
|
3911
|
-
}
|
|
4083
|
+
const transport = this.getClaimTransport();
|
|
3912
4084
|
let claimMessage;
|
|
3913
4085
|
if (options?.claim) {
|
|
3914
4086
|
claimMessage = this.buildClaimMessageForProof(options.claim);
|
|
@@ -3937,13 +4109,11 @@ var ToonClient = class {
|
|
|
3937
4109
|
"MISSING_CLAIM"
|
|
3938
4110
|
);
|
|
3939
4111
|
}
|
|
3940
|
-
const response = await
|
|
4112
|
+
const response = await transport.sendIlpPacketWithClaim(
|
|
3941
4113
|
{
|
|
3942
4114
|
destination,
|
|
3943
4115
|
amount,
|
|
3944
|
-
data: toBase64(
|
|
3945
|
-
toonData instanceof Uint8Array ? toonData : new Uint8Array(toonData)
|
|
3946
|
-
)
|
|
4116
|
+
data: toBase64(writeData)
|
|
3947
4117
|
},
|
|
3948
4118
|
claimMessage
|
|
3949
4119
|
);
|
|
@@ -3971,6 +4141,46 @@ var ToonClient = class {
|
|
|
3971
4141
|
);
|
|
3972
4142
|
}
|
|
3973
4143
|
}
|
|
4144
|
+
/**
|
|
4145
|
+
* Payment-aware HTTP fetch over TOON (issue #50). A `fetch()`-like method that
|
|
4146
|
+
* makes paying for an HTTP resource transparent:
|
|
4147
|
+
*
|
|
4148
|
+
* 1. Issues the HTTP request to `url`.
|
|
4149
|
+
* 2. On `402`, parses the x402 `accepts` array and selects the
|
|
4150
|
+
* `toon-channel` entry (see {@link Http402Client} for the wire shape).
|
|
4151
|
+
* 3. Opens/reuses a payment channel for the entry's ILP destination (via
|
|
4152
|
+
* ChannelManager), signs a balance proof for the demanded price, and
|
|
4153
|
+
* re-sends the SAME HTTP request as a transparent HTTP-in-ILP packet to
|
|
4154
|
+
* the connector's `POST /ilp` (via {@link HttpIlpClient}), with the claim
|
|
4155
|
+
* in the `ILP-Payment-Channel-Claim` header.
|
|
4156
|
+
* 4. Reconstructs and returns a standard Web `Response` from the FULFILL
|
|
4157
|
+
* `data`. The caller never sees ILP.
|
|
4158
|
+
*
|
|
4159
|
+
* If the origin offers no `toon-channel` entry, the original `402` Response is
|
|
4160
|
+
* returned unchanged (the caller sees the vanilla x402 challenge).
|
|
4161
|
+
*
|
|
4162
|
+
* The channel/claim plumbing is wired to the live ChannelManager + per-chain
|
|
4163
|
+
* signer via `resolveClaimForDestination` — identical to `publishEvent`. The
|
|
4164
|
+
* `amount` paid comes from the selected x402 entry (the resource's price).
|
|
4165
|
+
*
|
|
4166
|
+
* @throws {ToonClientError} If the client is not started.
|
|
4167
|
+
* @throws {ConnectorError} If the connector rejects the payment or returns no
|
|
4168
|
+
* HTTP payload.
|
|
4169
|
+
*/
|
|
4170
|
+
async h402Fetch(url, opts) {
|
|
4171
|
+
if (!this.state) {
|
|
4172
|
+
throw new ToonClientError(
|
|
4173
|
+
"Client not started. Call start() first.",
|
|
4174
|
+
"INVALID_STATE"
|
|
4175
|
+
);
|
|
4176
|
+
}
|
|
4177
|
+
const client = new Http402Client({
|
|
4178
|
+
...this.channelManager ? {
|
|
4179
|
+
resolveClaim: (destination, amount) => this.resolveClaimForDestination(destination, amount)
|
|
4180
|
+
} : {}
|
|
4181
|
+
});
|
|
4182
|
+
return client.fetch(url, opts);
|
|
4183
|
+
}
|
|
3974
4184
|
/**
|
|
3975
4185
|
* Sends a raw swap ILP packet (Story 12.5) to a Mill peer with an attached
|
|
3976
4186
|
* balance-proof claim. This is a lower-level surface than `publishEvent`:
|
|
@@ -3983,7 +4193,7 @@ var ToonClient = class {
|
|
|
3983
4193
|
* matching `destination`,
|
|
3984
4194
|
* (c) neither -> throw MISSING_CLAIM.
|
|
3985
4195
|
*
|
|
3986
|
-
* @throws {ToonClientError} INVALID_STATE /
|
|
4196
|
+
* @throws {ToonClientError} INVALID_STATE / NO_ILP_TRANSPORT / MISSING_CLAIM
|
|
3987
4197
|
*/
|
|
3988
4198
|
async sendSwapPacket(params) {
|
|
3989
4199
|
if (!this.state) {
|
|
@@ -3992,18 +4202,13 @@ var ToonClient = class {
|
|
|
3992
4202
|
"INVALID_STATE"
|
|
3993
4203
|
);
|
|
3994
4204
|
}
|
|
3995
|
-
|
|
3996
|
-
throw new ToonClientError(
|
|
3997
|
-
"BTP client required for sending swap packets. Configure btpUrl.",
|
|
3998
|
-
"NO_BTP_CLIENT"
|
|
3999
|
-
);
|
|
4000
|
-
}
|
|
4205
|
+
const transport = this.getClaimTransport();
|
|
4001
4206
|
const claimMessage = await this.resolveClaimForDestination(
|
|
4002
4207
|
params.destination,
|
|
4003
4208
|
params.amount,
|
|
4004
4209
|
params.claim
|
|
4005
4210
|
);
|
|
4006
|
-
return
|
|
4211
|
+
return transport.sendIlpPacketWithClaim(
|
|
4007
4212
|
{
|
|
4008
4213
|
destination: params.destination,
|
|
4009
4214
|
amount: String(params.amount),
|
|
@@ -4043,6 +4248,51 @@ var ToonClient = class {
|
|
|
4043
4248
|
}
|
|
4044
4249
|
return EvmSigner.buildClaimMessage(claim, this.getPublicKey());
|
|
4045
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
|
+
}
|
|
4046
4296
|
/**
|
|
4047
4297
|
* Shared claim-resolution logic used by `publishEvent` and `sendSwapPacket`.
|
|
4048
4298
|
* TODO(12.5 followup): also factor `publishEvent`'s inline claim resolution
|
|
@@ -4231,14 +4481,9 @@ var ToonClient = class {
|
|
|
4231
4481
|
"MISSING_CLAIM"
|
|
4232
4482
|
);
|
|
4233
4483
|
}
|
|
4234
|
-
|
|
4235
|
-
throw new ToonClientError(
|
|
4236
|
-
"BTP client required for sending payments. Configure btpUrl.",
|
|
4237
|
-
"NO_BTP_CLIENT"
|
|
4238
|
-
);
|
|
4239
|
-
}
|
|
4484
|
+
const transport = this.getClaimTransport();
|
|
4240
4485
|
const claimMessage = this.buildClaimMessageForProof(params.claim);
|
|
4241
|
-
return
|
|
4486
|
+
return transport.sendIlpPacketWithClaim(
|
|
4242
4487
|
ilpParams,
|
|
4243
4488
|
claimMessage
|
|
4244
4489
|
);
|
|
@@ -4256,14 +4501,10 @@ var ToonClient = class {
|
|
|
4256
4501
|
if (!this.state) {
|
|
4257
4502
|
throw new ToonClientError("Client not started", "INVALID_STATE");
|
|
4258
4503
|
}
|
|
4259
|
-
const stopManagedProxy = this.state.stopManagedProxy;
|
|
4260
4504
|
try {
|
|
4261
4505
|
if (this.state.btpClient) {
|
|
4262
4506
|
await this.state.btpClient.disconnect();
|
|
4263
4507
|
}
|
|
4264
|
-
if (stopManagedProxy) {
|
|
4265
|
-
await stopManagedProxy();
|
|
4266
|
-
}
|
|
4267
4508
|
this.state = null;
|
|
4268
4509
|
} catch (error) {
|
|
4269
4510
|
throw new ToonClientError(
|
|
@@ -4311,26 +4552,6 @@ var ToonClient = class {
|
|
|
4311
4552
|
}
|
|
4312
4553
|
};
|
|
4313
4554
|
|
|
4314
|
-
// src/transport/hs-hostname.ts
|
|
4315
|
-
var HS_HOSTNAME_REGEX = /^[a-z2-7]+\.anyone$/;
|
|
4316
|
-
var HS_HOSTNAME_MAX_LENGTH = 80;
|
|
4317
|
-
function isRoutableHsHostname(s) {
|
|
4318
|
-
return typeof s === "string" && s.length <= HS_HOSTNAME_MAX_LENGTH && HS_HOSTNAME_REGEX.test(s);
|
|
4319
|
-
}
|
|
4320
|
-
function assertRoutableHsHostname(hostname) {
|
|
4321
|
-
if (typeof hostname === "string" && /\.anon$/.test(hostname)) {
|
|
4322
|
-
throw new Error(
|
|
4323
|
-
`"${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).`
|
|
4324
|
-
);
|
|
4325
|
-
}
|
|
4326
|
-
if (!isRoutableHsHostname(hostname)) {
|
|
4327
|
-
throw new Error(
|
|
4328
|
-
`Invalid hidden-service hostname: ${JSON.stringify(hostname)}. Expected a base32 .anyone address matching ${HS_HOSTNAME_REGEX}.`
|
|
4329
|
-
);
|
|
4330
|
-
}
|
|
4331
|
-
return hostname;
|
|
4332
|
-
}
|
|
4333
|
-
|
|
4334
4555
|
// src/adapters/HttpConnectorAdmin.ts
|
|
4335
4556
|
var HttpConnectorAdmin = class {
|
|
4336
4557
|
adminUrl;
|
|
@@ -5069,6 +5290,75 @@ function buildPetPurchaseRequest(params) {
|
|
|
5069
5290
|
};
|
|
5070
5291
|
}
|
|
5071
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
|
+
|
|
5072
5362
|
// src/keys/KeyManager.ts
|
|
5073
5363
|
import { finalizeEvent as finalizeEvent2 } from "nostr-tools/pure";
|
|
5074
5364
|
import { nip19 } from "nostr-tools";
|
|
@@ -6109,14 +6399,11 @@ function writeKeystoreFile(path, keystore) {
|
|
|
6109
6399
|
});
|
|
6110
6400
|
}
|
|
6111
6401
|
export {
|
|
6112
|
-
ANON_ASSETS,
|
|
6113
|
-
ANON_VERSION,
|
|
6114
6402
|
BtpRuntimeClient,
|
|
6115
6403
|
ChannelManager,
|
|
6116
6404
|
ConnectorError,
|
|
6117
6405
|
EvmSigner,
|
|
6118
|
-
|
|
6119
|
-
HS_HOSTNAME_REGEX,
|
|
6406
|
+
Http402Client,
|
|
6120
6407
|
HttpConnectorAdmin,
|
|
6121
6408
|
HttpIlpClient,
|
|
6122
6409
|
HttpRuntimeClient,
|
|
@@ -6133,13 +6420,13 @@ export {
|
|
|
6133
6420
|
ValidationError,
|
|
6134
6421
|
applyDefaults,
|
|
6135
6422
|
applyNetworkPresets,
|
|
6136
|
-
assertRoutableHsHostname,
|
|
6137
6423
|
buildBackupEvent,
|
|
6138
6424
|
buildBackupFilter,
|
|
6139
6425
|
buildPetInteractionRequest,
|
|
6140
6426
|
buildPetListingEvent,
|
|
6141
6427
|
buildPetPurchaseRequest,
|
|
6142
6428
|
buildSettlementInfo,
|
|
6429
|
+
buildStoreWriteEnvelope,
|
|
6143
6430
|
decryptMnemonic2 as decryptMnemonic,
|
|
6144
6431
|
deriveFromNsec,
|
|
6145
6432
|
deriveFullIdentity,
|
|
@@ -6147,6 +6434,7 @@ export {
|
|
|
6147
6434
|
encryptMnemonic2 as encryptMnemonic,
|
|
6148
6435
|
filterPetDvmProviders,
|
|
6149
6436
|
filterPetListings,
|
|
6437
|
+
fundWallet,
|
|
6150
6438
|
generateKeystore,
|
|
6151
6439
|
generateMnemonic,
|
|
6152
6440
|
generateRandomIdentity,
|
|
@@ -6154,18 +6442,20 @@ export {
|
|
|
6154
6442
|
httpEndpointToBtpUrl,
|
|
6155
6443
|
importKeystore,
|
|
6156
6444
|
isPrfSupported,
|
|
6157
|
-
isRoutableHsHostname,
|
|
6158
6445
|
loadKeystore,
|
|
6159
6446
|
parseBackupPayload,
|
|
6447
|
+
parseHttpResponse,
|
|
6160
6448
|
parsePetInteractionEvent,
|
|
6161
6449
|
parsePetInteractionResult,
|
|
6162
6450
|
parsePetListing,
|
|
6451
|
+
parseX402Body,
|
|
6452
|
+
parseX402Challenge,
|
|
6453
|
+
proxyIlpEndpoint,
|
|
6163
6454
|
readDiscoveredIlpPeer,
|
|
6164
6455
|
readMinaDepositTotal,
|
|
6165
6456
|
requestBlobStorage,
|
|
6166
|
-
selectAnonAsset,
|
|
6167
6457
|
selectIlpTransport,
|
|
6168
|
-
|
|
6458
|
+
serializeHttpRequest,
|
|
6169
6459
|
validateConfig,
|
|
6170
6460
|
validateMnemonic,
|
|
6171
6461
|
withRetry,
|