@toon-protocol/client 0.9.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +654 -167
- package/dist/index.js +642 -70
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-WHAEQLIW.js";
|
|
7
7
|
|
|
8
8
|
// src/ToonClient.ts
|
|
9
|
-
import { generateSecretKey as generateSecretKey3, getPublicKey as getPublicKey2 } from "nostr-tools/pure";
|
|
9
|
+
import { generateSecretKey as generateSecretKey3, getPublicKey as getPublicKey2, finalizeEvent } from "nostr-tools/pure";
|
|
10
10
|
|
|
11
11
|
// src/config.ts
|
|
12
12
|
import { generateSecretKey as generateSecretKey2 } from "nostr-tools/pure";
|
|
@@ -21,6 +21,7 @@ var ToonClientError = class extends Error {
|
|
|
21
21
|
this.code = code;
|
|
22
22
|
this.name = "ToonClientError";
|
|
23
23
|
}
|
|
24
|
+
code;
|
|
24
25
|
};
|
|
25
26
|
var NetworkError = class extends ToonClientError {
|
|
26
27
|
constructor(message, cause) {
|
|
@@ -1352,6 +1353,232 @@ var BtpRuntimeClient = class {
|
|
|
1352
1353
|
}
|
|
1353
1354
|
};
|
|
1354
1355
|
|
|
1356
|
+
// src/adapters/HttpIlpClient.ts
|
|
1357
|
+
var ILP_CLAIM_HEADER = "ILP-Payment-Channel-Claim";
|
|
1358
|
+
var ILP_CLAIM_WRAPPED_HEADER = "ILP-Payment-Channel-Claim-Wrapped";
|
|
1359
|
+
var ILP_PEER_ID_HEADER = "ILP-Peer-Id";
|
|
1360
|
+
var HttpIlpClient = class {
|
|
1361
|
+
httpEndpoint;
|
|
1362
|
+
peerId;
|
|
1363
|
+
authToken;
|
|
1364
|
+
timeout;
|
|
1365
|
+
retryConfig;
|
|
1366
|
+
httpClient;
|
|
1367
|
+
createWebSocket;
|
|
1368
|
+
constructor(config) {
|
|
1369
|
+
this.httpEndpoint = config.httpEndpoint;
|
|
1370
|
+
this.peerId = config.peerId;
|
|
1371
|
+
this.authToken = config.authToken;
|
|
1372
|
+
this.timeout = config.timeout ?? 3e4;
|
|
1373
|
+
this.retryConfig = {
|
|
1374
|
+
maxRetries: config.maxRetries ?? 3,
|
|
1375
|
+
retryDelay: config.retryDelay ?? 1e3
|
|
1376
|
+
};
|
|
1377
|
+
this.httpClient = config.httpClient ?? fetch;
|
|
1378
|
+
this.createWebSocket = config.createWebSocket;
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Send an ILP PREPARE via `POST /ilp` WITHOUT a claim. The connector accepts
|
|
1382
|
+
* this only on free/zero-amount routes; paid writes must use
|
|
1383
|
+
* {@link sendIlpPacketWithClaim}. Satisfies the IlpClient interface.
|
|
1384
|
+
*/
|
|
1385
|
+
async sendIlpPacket(params) {
|
|
1386
|
+
return withRetry(() => this.postPrepare(params), {
|
|
1387
|
+
maxRetries: this.retryConfig.maxRetries,
|
|
1388
|
+
retryDelay: this.retryConfig.retryDelay,
|
|
1389
|
+
exponentialBackoff: true,
|
|
1390
|
+
shouldRetry: (error) => error instanceof NetworkError
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Send an ILP PREPARE via `POST /ilp` with the payment-channel claim attached
|
|
1395
|
+
* as the `ILP-Payment-Channel-Claim` header. `claim` is the SAME JSON object
|
|
1396
|
+
* the BTP path attaches as the `payment-channel-claim` protocolData entry —
|
|
1397
|
+
* we base64(JSON.stringify(claim)) it, byte-for-byte identical to BTP.
|
|
1398
|
+
*/
|
|
1399
|
+
async sendIlpPacketWithClaim(params, claim) {
|
|
1400
|
+
return withRetry(() => this.postPrepare(params, claim), {
|
|
1401
|
+
maxRetries: this.retryConfig.maxRetries,
|
|
1402
|
+
retryDelay: this.retryConfig.retryDelay,
|
|
1403
|
+
exponentialBackoff: true,
|
|
1404
|
+
shouldRetry: (error) => error instanceof NetworkError
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Upgrade to a duplex BTP session over the SAME endpoint.
|
|
1409
|
+
*
|
|
1410
|
+
* Derives the `ws(s)://` URL from `httpEndpoint`, opens a WebSocket with
|
|
1411
|
+
* `Sec-WebSocket-Protocol: btp` and the same `ILP-Peer-Id` + `Authorization`
|
|
1412
|
+
* headers, and returns a connected {@link BtpRuntimeClient}. When auth headers
|
|
1413
|
+
* are present the connector pre-authenticates the session (no in-band auth
|
|
1414
|
+
* frame); without them the BtpRuntimeClient falls back to the normal BTP
|
|
1415
|
+
* auth-frame flow.
|
|
1416
|
+
*
|
|
1417
|
+
* NOTE: passing per-connection headers + a subprotocol to a WebSocket is
|
|
1418
|
+
* Node-only (the `ws` package). Browsers cannot set arbitrary request headers
|
|
1419
|
+
* on a WebSocket handshake, so a browser consumer must use the gateway
|
|
1420
|
+
* transport or BTP-with-auth-frame instead.
|
|
1421
|
+
*/
|
|
1422
|
+
async upgradeToBtp() {
|
|
1423
|
+
const btpUrl = httpEndpointToBtpUrl(this.httpEndpoint);
|
|
1424
|
+
const createWebSocket = this.createWebSocket ?? await makeBtpWebSocketFactory(this.authHeaders());
|
|
1425
|
+
const client = new BtpRuntimeClient({
|
|
1426
|
+
btpUrl,
|
|
1427
|
+
// BtpRuntimeClient sends an auth frame using these; when the connector
|
|
1428
|
+
// pre-authenticated via Upgrade headers it accepts the (redundant) frame.
|
|
1429
|
+
peerId: this.peerId ?? "client",
|
|
1430
|
+
authToken: this.authToken ?? "",
|
|
1431
|
+
createWebSocket
|
|
1432
|
+
});
|
|
1433
|
+
await client.connect();
|
|
1434
|
+
return client;
|
|
1435
|
+
}
|
|
1436
|
+
// ─── Private ──────────────────────────────────────────────────────────────
|
|
1437
|
+
authHeaders() {
|
|
1438
|
+
const headers = {};
|
|
1439
|
+
if (this.peerId) headers[ILP_PEER_ID_HEADER] = this.peerId;
|
|
1440
|
+
if (this.authToken) headers["Authorization"] = `Bearer ${this.authToken}`;
|
|
1441
|
+
return headers;
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Single attempt: serialize the PREPARE, POST it, and map the response.
|
|
1445
|
+
* @throws {NetworkError} On connection/timeout failures (retried).
|
|
1446
|
+
* @throws {ConnectorError} On non-retryable transport errors (5xx / unexpected).
|
|
1447
|
+
*/
|
|
1448
|
+
async postPrepare(params, claim) {
|
|
1449
|
+
const requestTimeout = params.timeout ?? this.timeout;
|
|
1450
|
+
const prepare = serializeIlpPrepare({
|
|
1451
|
+
type: ILPPacketType.PREPARE,
|
|
1452
|
+
amount: BigInt(params.amount),
|
|
1453
|
+
destination: params.destination,
|
|
1454
|
+
executionCondition: new Uint8Array(32),
|
|
1455
|
+
expiresAt: new Date(Date.now() + requestTimeout),
|
|
1456
|
+
data: fromBase64(params.data)
|
|
1457
|
+
});
|
|
1458
|
+
const headers = {
|
|
1459
|
+
"Content-Type": "application/octet-stream",
|
|
1460
|
+
...this.authHeaders()
|
|
1461
|
+
};
|
|
1462
|
+
if (claim !== void 0) {
|
|
1463
|
+
headers[ILP_CLAIM_HEADER] = toBase64(
|
|
1464
|
+
encodeUtf8(JSON.stringify(claim))
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
const controller = new AbortController();
|
|
1468
|
+
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
|
1469
|
+
try {
|
|
1470
|
+
const response = await this.httpClient(this.httpEndpoint, {
|
|
1471
|
+
method: "POST",
|
|
1472
|
+
headers,
|
|
1473
|
+
// Copy into a fresh ArrayBuffer so fetch sees a clean body, not a view.
|
|
1474
|
+
body: prepare.slice(),
|
|
1475
|
+
signal: controller.signal
|
|
1476
|
+
});
|
|
1477
|
+
clearTimeout(timeoutId);
|
|
1478
|
+
return await this.mapResponse(response);
|
|
1479
|
+
} catch (error) {
|
|
1480
|
+
clearTimeout(timeoutId);
|
|
1481
|
+
throw this.mapTransportError(error, requestTimeout);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Map a `200 OK` body (OER FULFILL/REJECT) to an IlpSendResult; map a non-2xx
|
|
1486
|
+
* to a transport error. Per the wire contract, ILP-level rejects arrive as a
|
|
1487
|
+
* 200 + REJECT body — only HTTP non-2xx means a transport-layer failure.
|
|
1488
|
+
*/
|
|
1489
|
+
async mapResponse(response) {
|
|
1490
|
+
if (response.ok) {
|
|
1491
|
+
const buf = new Uint8Array(await response.arrayBuffer());
|
|
1492
|
+
if (buf.length === 0) {
|
|
1493
|
+
throw new ConnectorError("Empty 200 body from /ilp (expected OER ILP response)");
|
|
1494
|
+
}
|
|
1495
|
+
const ilp = deserializeIlpPacket(buf);
|
|
1496
|
+
if (ilp.type === ILPPacketType.FULFILL) {
|
|
1497
|
+
return {
|
|
1498
|
+
accepted: true,
|
|
1499
|
+
data: ilp.data.length > 0 ? toBase64(ilp.data) : void 0
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
accepted: false,
|
|
1504
|
+
code: ilp.code,
|
|
1505
|
+
message: ilp.message,
|
|
1506
|
+
data: ilp.data.length > 0 ? toBase64(ilp.data) : void 0
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
const body = await response.text().catch(() => "");
|
|
1510
|
+
const detail = body ? `: ${body}` : "";
|
|
1511
|
+
if (response.status >= 500) {
|
|
1512
|
+
throw new ConnectorError(
|
|
1513
|
+
`Connector transport error (${response.status} ${response.statusText})${detail}`
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
throw new ConnectorError(
|
|
1517
|
+
`ILP-over-HTTP request rejected (${response.status} ${response.statusText})${detail}`
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
mapTransportError(error, requestTimeout) {
|
|
1521
|
+
if (error instanceof ConnectorError || error instanceof NetworkError) {
|
|
1522
|
+
return error;
|
|
1523
|
+
}
|
|
1524
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1525
|
+
return new NetworkError(`Request timeout after ${requestTimeout}ms`, error);
|
|
1526
|
+
}
|
|
1527
|
+
if (error instanceof TypeError && (error.message.includes("fetch failed") || error.message.includes("ECONNREFUSED") || error.message.includes("ECONNRESET") || error.message.includes("ETIMEDOUT") || error.message.includes("network"))) {
|
|
1528
|
+
return new NetworkError(`Network connection failed: ${error.message}`, error);
|
|
1529
|
+
}
|
|
1530
|
+
return new ConnectorError(
|
|
1531
|
+
`Unexpected error during ILP-over-HTTP request: ${error instanceof Error ? error.message : String(error)}`,
|
|
1532
|
+
error instanceof Error ? error : void 0
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
function httpEndpointToBtpUrl(httpEndpoint) {
|
|
1537
|
+
return httpEndpoint.replace(/^https:\/\//i, "wss://").replace(/^http:\/\//i, "ws://");
|
|
1538
|
+
}
|
|
1539
|
+
async function makeBtpWebSocketFactory(headers) {
|
|
1540
|
+
const { createRequire } = await import("module");
|
|
1541
|
+
const require2 = createRequire(import.meta.url);
|
|
1542
|
+
const WS = require2("ws");
|
|
1543
|
+
const ws = WS;
|
|
1544
|
+
const WSClass = typeof ws === "function" ? ws : typeof ws.default === "function" ? ws.default : typeof ws.WebSocket === "function" ? ws.WebSocket : null;
|
|
1545
|
+
if (WSClass === null) {
|
|
1546
|
+
throw new Error(
|
|
1547
|
+
"makeBtpWebSocketFactory: require('ws') did not yield a constructor on .default, .WebSocket, or the module root."
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
return (url) => (
|
|
1551
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1552
|
+
new WSClass(url, "btp", { headers })
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// src/adapters/selectIlpTransport.ts
|
|
1557
|
+
function readDiscoveredIlpPeer(peer) {
|
|
1558
|
+
const p = peer ?? {};
|
|
1559
|
+
return {
|
|
1560
|
+
btpEndpoint: typeof p["btpEndpoint"] === "string" ? p["btpEndpoint"] : void 0,
|
|
1561
|
+
httpEndpoint: typeof p["httpEndpoint"] === "string" ? p["httpEndpoint"] : void 0,
|
|
1562
|
+
supportsUpgrade: typeof p["supportsUpgrade"] === "boolean" ? p["supportsUpgrade"] : void 0
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function selectIlpTransport(peer, options = {}) {
|
|
1566
|
+
const needsDuplex = options.needsDuplex ?? false;
|
|
1567
|
+
const http2 = peer.httpEndpoint?.trim() || void 0;
|
|
1568
|
+
const btp = peer.btpEndpoint?.trim() || void 0;
|
|
1569
|
+
const canUpgrade = peer.supportsUpgrade === true;
|
|
1570
|
+
if (needsDuplex) {
|
|
1571
|
+
if (btp) return { kind: "btp", btpEndpoint: btp };
|
|
1572
|
+
if (http2 && canUpgrade) return { kind: "http-upgradable", httpEndpoint: http2 };
|
|
1573
|
+
throw new Error(
|
|
1574
|
+
"Duplex transport required but peer exposes neither a btpEndpoint nor an upgradable httpEndpoint"
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
if (http2) return { kind: "http", httpEndpoint: http2, canUpgrade };
|
|
1578
|
+
if (btp) return { kind: "btp", btpEndpoint: btp };
|
|
1579
|
+
throw new Error("Peer exposes neither an httpEndpoint nor a btpEndpoint");
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1355
1582
|
// src/channel/OnChainChannelClient.ts
|
|
1356
1583
|
import {
|
|
1357
1584
|
createPublicClient,
|
|
@@ -2548,6 +2775,12 @@ async function initializeHttpMode(config) {
|
|
|
2548
2775
|
const effectiveBtpUrl = transport.btpUrl ?? config.btpUrl;
|
|
2549
2776
|
const effectiveConnectorUrl = transport.connectorUrl ?? config.connectorUrl;
|
|
2550
2777
|
const settlementInfo = buildSettlementInfo(config);
|
|
2778
|
+
const discoveredPeer = readDiscoveredIlpPeer({
|
|
2779
|
+
btpEndpoint: effectiveBtpUrl,
|
|
2780
|
+
httpEndpoint: config.connectorHttpEndpoint,
|
|
2781
|
+
supportsUpgrade: config.connectorSupportsUpgrade
|
|
2782
|
+
});
|
|
2783
|
+
const transportChoice = discoveredPeer.httpEndpoint || discoveredPeer.btpEndpoint ? selectIlpTransport(discoveredPeer, { needsDuplex: false }) : null;
|
|
2551
2784
|
let btpClient = null;
|
|
2552
2785
|
if (effectiveBtpUrl) {
|
|
2553
2786
|
btpClient = new BtpRuntimeClient({
|
|
@@ -2558,7 +2791,20 @@ async function initializeHttpMode(config) {
|
|
|
2558
2791
|
});
|
|
2559
2792
|
await btpClient.connect();
|
|
2560
2793
|
}
|
|
2561
|
-
|
|
2794
|
+
let httpIlpClient = null;
|
|
2795
|
+
if (transportChoice && (transportChoice.kind === "http" || transportChoice.kind === "http-upgradable")) {
|
|
2796
|
+
httpIlpClient = new HttpIlpClient({
|
|
2797
|
+
httpEndpoint: transportChoice.httpEndpoint,
|
|
2798
|
+
...config.btpPeerId !== void 0 ? { peerId: config.btpPeerId } : {},
|
|
2799
|
+
...config.btpAuthToken !== void 0 ? { authToken: config.btpAuthToken } : {},
|
|
2800
|
+
timeout: config.queryTimeout,
|
|
2801
|
+
maxRetries: config.maxRetries,
|
|
2802
|
+
retryDelay: config.retryDelay,
|
|
2803
|
+
...transport.httpClient !== void 0 ? { httpClient: transport.httpClient } : {},
|
|
2804
|
+
...transport.createWebSocket !== void 0 ? { createWebSocket: transport.createWebSocket } : {}
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
const runtimeClient = httpIlpClient ?? btpClient ?? new HttpRuntimeClient({
|
|
2562
2808
|
connectorUrl: effectiveConnectorUrl,
|
|
2563
2809
|
timeout: config.queryTimeout,
|
|
2564
2810
|
maxRetries: config.maxRetries,
|
|
@@ -3304,6 +3550,324 @@ var JsonFileChannelStore = class {
|
|
|
3304
3550
|
}
|
|
3305
3551
|
};
|
|
3306
3552
|
|
|
3553
|
+
// src/blob-storage.ts
|
|
3554
|
+
import { buildBlobStorageRequest } from "@toon-protocol/core";
|
|
3555
|
+
var ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;
|
|
3556
|
+
async function requestBlobStorage(client, secretKey, params) {
|
|
3557
|
+
const bid = params.bid ?? (params.ilpAmount !== void 0 ? String(params.ilpAmount) : void 0);
|
|
3558
|
+
if (bid === void 0 || bid === "") {
|
|
3559
|
+
return {
|
|
3560
|
+
success: false,
|
|
3561
|
+
error: "requestBlobStorage requires a bid (or ilpAmount to derive it)"
|
|
3562
|
+
};
|
|
3563
|
+
}
|
|
3564
|
+
const blobBuffer = Buffer.from(
|
|
3565
|
+
params.blobData.buffer,
|
|
3566
|
+
params.blobData.byteOffset,
|
|
3567
|
+
params.blobData.byteLength
|
|
3568
|
+
);
|
|
3569
|
+
let event;
|
|
3570
|
+
try {
|
|
3571
|
+
event = buildBlobStorageRequest(
|
|
3572
|
+
{
|
|
3573
|
+
blobData: blobBuffer,
|
|
3574
|
+
contentType: params.contentType,
|
|
3575
|
+
bid
|
|
3576
|
+
},
|
|
3577
|
+
secretKey
|
|
3578
|
+
);
|
|
3579
|
+
} catch (error) {
|
|
3580
|
+
return {
|
|
3581
|
+
success: false,
|
|
3582
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3583
|
+
};
|
|
3584
|
+
}
|
|
3585
|
+
const result = await client.publishEvent(event, {
|
|
3586
|
+
destination: params.destination,
|
|
3587
|
+
claim: params.claim,
|
|
3588
|
+
ilpAmount: params.ilpAmount
|
|
3589
|
+
});
|
|
3590
|
+
if (!result.success) {
|
|
3591
|
+
return {
|
|
3592
|
+
success: false,
|
|
3593
|
+
eventId: result.eventId ?? event.id,
|
|
3594
|
+
error: result.error ?? "Blob storage request rejected"
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
if (!result.data) {
|
|
3598
|
+
return {
|
|
3599
|
+
success: false,
|
|
3600
|
+
eventId: event.id,
|
|
3601
|
+
error: "FULFILL contained no data; expected base64-encoded Arweave tx ID"
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
const txId = decodeUtf8(fromBase64(result.data));
|
|
3605
|
+
if (!ARWEAVE_TX_ID_REGEX.test(txId)) {
|
|
3606
|
+
return {
|
|
3607
|
+
success: false,
|
|
3608
|
+
eventId: event.id,
|
|
3609
|
+
error: `Decoded FULFILL data is not a valid Arweave tx ID: "${txId}"`
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
return {
|
|
3613
|
+
success: true,
|
|
3614
|
+
txId,
|
|
3615
|
+
eventId: event.id
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
// src/adapters/Http402Client.ts
|
|
3620
|
+
var Http402Client = class {
|
|
3621
|
+
fetchImpl;
|
|
3622
|
+
resolveClaim;
|
|
3623
|
+
createIlpClient;
|
|
3624
|
+
needsDuplex;
|
|
3625
|
+
constructor(config = {}) {
|
|
3626
|
+
this.fetchImpl = config.fetch ?? fetch;
|
|
3627
|
+
this.resolveClaim = config.resolveClaim;
|
|
3628
|
+
this.createIlpClient = config.createIlpClient ?? ((httpEndpoint) => new HttpIlpClient({ httpEndpoint }));
|
|
3629
|
+
this.needsDuplex = config.needsDuplex ?? false;
|
|
3630
|
+
}
|
|
3631
|
+
/**
|
|
3632
|
+
* `fetch()`-like entry point. Issues the request; on `402` parses the x402
|
|
3633
|
+
* challenge and — when a usable `toon-channel` offer is present and a claim
|
|
3634
|
+
* resolver is configured — pays over TOON and returns the reconstructed
|
|
3635
|
+
* `Response`. Otherwise returns the original 402 unchanged (AC5).
|
|
3636
|
+
*/
|
|
3637
|
+
async fetch(url, opts = {}) {
|
|
3638
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
3639
|
+
const probe = await this.fetchImpl(url, {
|
|
3640
|
+
method,
|
|
3641
|
+
...opts.headers ? { headers: opts.headers } : {},
|
|
3642
|
+
...opts.body !== void 0 ? { body: opts.body } : {},
|
|
3643
|
+
...opts.timeout !== void 0 ? { signal: AbortSignal.timeout(opts.timeout) } : {}
|
|
3644
|
+
});
|
|
3645
|
+
if (probe.status !== 402) return probe;
|
|
3646
|
+
const challenge = await parseX402Challenge(probe.clone());
|
|
3647
|
+
const accept = challenge.toonChannel;
|
|
3648
|
+
if (!accept || !this.resolveClaim) return probe;
|
|
3649
|
+
return this.payOverToon(url, method, opts, accept, this.resolveClaim);
|
|
3650
|
+
}
|
|
3651
|
+
/**
|
|
3652
|
+
* Open/reuse a channel (via the injected claim resolver), serialize the HTTP
|
|
3653
|
+
* request into the ILP packet `data`, send it to `POST /ilp` with the claim,
|
|
3654
|
+
* and reconstruct the origin `Response` from the FULFILL `data`.
|
|
3655
|
+
*/
|
|
3656
|
+
async payOverToon(url, method, opts, accept, resolveClaim) {
|
|
3657
|
+
const destination = opts.destination ?? accept.destination;
|
|
3658
|
+
const claim = await resolveClaim(destination, accept.amount);
|
|
3659
|
+
const requestBytes = serializeHttpRequest({
|
|
3660
|
+
method,
|
|
3661
|
+
url,
|
|
3662
|
+
headers: opts.headers,
|
|
3663
|
+
body: opts.body
|
|
3664
|
+
});
|
|
3665
|
+
const peer = {
|
|
3666
|
+
httpEndpoint: accept.httpEndpoint,
|
|
3667
|
+
supportsUpgrade: accept.supportsUpgrade
|
|
3668
|
+
};
|
|
3669
|
+
const choice = selectIlpTransport(peer, {
|
|
3670
|
+
needsDuplex: this.needsDuplex
|
|
3671
|
+
});
|
|
3672
|
+
const ilpClient = this.createIlpClient(accept.httpEndpoint);
|
|
3673
|
+
const result = await this.sendOverChoice(
|
|
3674
|
+
ilpClient,
|
|
3675
|
+
choice,
|
|
3676
|
+
{
|
|
3677
|
+
destination,
|
|
3678
|
+
amount: String(accept.amount),
|
|
3679
|
+
data: toBase64(requestBytes),
|
|
3680
|
+
...opts.timeout !== void 0 ? { timeout: opts.timeout } : {}
|
|
3681
|
+
},
|
|
3682
|
+
claim
|
|
3683
|
+
);
|
|
3684
|
+
if (!result.accepted) {
|
|
3685
|
+
throw new ConnectorError(
|
|
3686
|
+
`h402 payment rejected by connector: ${result.code ?? "F00"} ${result.message ?? ""}`.trim()
|
|
3687
|
+
);
|
|
3688
|
+
}
|
|
3689
|
+
if (!result.data) {
|
|
3690
|
+
throw new ConnectorError(
|
|
3691
|
+
"h402 FULFILL carried no data (expected an HTTP response payload)"
|
|
3692
|
+
);
|
|
3693
|
+
}
|
|
3694
|
+
return parseHttpResponse(fromBase64(result.data));
|
|
3695
|
+
}
|
|
3696
|
+
/**
|
|
3697
|
+
* Send the serialized HTTP-in-ILP PREPARE over the selected transport.
|
|
3698
|
+
*
|
|
3699
|
+
* - `http` / `http-upgradable`: stateless one-shot `POST /ilp` with the claim.
|
|
3700
|
+
* - `http-upgradable` additionally exercises {@link HttpIlpClient.upgradeToBtp}
|
|
3701
|
+
* for the duplex/streaming path (AC4). v1 still drives the actual write over
|
|
3702
|
+
* the one-shot HTTP method even after upgrading — full duplex body streaming
|
|
3703
|
+
* is a documented follow-up — but the upgrade call path is wired here.
|
|
3704
|
+
* - `btp`: not reachable from h402 (the x402 offer only carries an
|
|
3705
|
+
* `httpEndpoint`); guarded for completeness.
|
|
3706
|
+
*/
|
|
3707
|
+
async sendOverChoice(ilpClient, choice, params, claim) {
|
|
3708
|
+
if (choice.kind === "http-upgradable") {
|
|
3709
|
+
const btp = await ilpClient.upgradeToBtp();
|
|
3710
|
+
try {
|
|
3711
|
+
return await btp.sendIlpPacketWithClaim(
|
|
3712
|
+
params,
|
|
3713
|
+
claim
|
|
3714
|
+
);
|
|
3715
|
+
} finally {
|
|
3716
|
+
await btp.disconnect().catch(() => {
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
if (choice.kind === "btp") {
|
|
3721
|
+
throw new ToonClientError(
|
|
3722
|
+
"h402 offer resolved to a BTP-only transport; the x402 toon-channel entry must advertise an httpEndpoint",
|
|
3723
|
+
"INVALID_STATE"
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3726
|
+
return ilpClient.sendIlpPacketWithClaim(params, claim);
|
|
3727
|
+
}
|
|
3728
|
+
};
|
|
3729
|
+
function readString(obj, keys) {
|
|
3730
|
+
for (const k of keys) {
|
|
3731
|
+
const v = obj[k];
|
|
3732
|
+
if (typeof v === "string" && v.trim().length > 0) return v.trim();
|
|
3733
|
+
}
|
|
3734
|
+
return void 0;
|
|
3735
|
+
}
|
|
3736
|
+
function readAmount(obj, keys) {
|
|
3737
|
+
for (const k of keys) {
|
|
3738
|
+
const v = obj[k];
|
|
3739
|
+
if (typeof v === "bigint") return v;
|
|
3740
|
+
if (typeof v === "number" && Number.isFinite(v)) return BigInt(Math.trunc(v));
|
|
3741
|
+
if (typeof v === "string" && /^\d+$/.test(v.trim())) return BigInt(v.trim());
|
|
3742
|
+
}
|
|
3743
|
+
return void 0;
|
|
3744
|
+
}
|
|
3745
|
+
async function parseX402Challenge(response) {
|
|
3746
|
+
let body;
|
|
3747
|
+
try {
|
|
3748
|
+
body = await response.json();
|
|
3749
|
+
} catch {
|
|
3750
|
+
return {};
|
|
3751
|
+
}
|
|
3752
|
+
return parseX402Body(body);
|
|
3753
|
+
}
|
|
3754
|
+
function parseX402Body(body) {
|
|
3755
|
+
if (typeof body !== "object" || body === null) return {};
|
|
3756
|
+
const b = body;
|
|
3757
|
+
const version = typeof b["x402Version"] === "number" ? b["x402Version"] : void 0;
|
|
3758
|
+
const accepts = Array.isArray(b["accepts"]) ? b["accepts"] : [];
|
|
3759
|
+
for (const raw of accepts) {
|
|
3760
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
3761
|
+
const entry = raw;
|
|
3762
|
+
const scheme = readString(entry, ["scheme"]);
|
|
3763
|
+
if (scheme !== "toon-channel") continue;
|
|
3764
|
+
const destination = readString(entry, [
|
|
3765
|
+
"destination",
|
|
3766
|
+
"ilpAddress",
|
|
3767
|
+
"payTo"
|
|
3768
|
+
]);
|
|
3769
|
+
const httpEndpoint = readString(entry, [
|
|
3770
|
+
"httpEndpoint",
|
|
3771
|
+
"ilpEndpoint",
|
|
3772
|
+
"endpoint"
|
|
3773
|
+
]);
|
|
3774
|
+
const amount = readAmount(entry, ["amount", "price", "maxAmountRequired"]);
|
|
3775
|
+
if (!destination || !httpEndpoint || amount === void 0) continue;
|
|
3776
|
+
const network = readString(entry, ["network", "chain"]);
|
|
3777
|
+
const supportsUpgrade = entry["supportsUpgrade"] === true || entry["upgradable"] === true;
|
|
3778
|
+
return {
|
|
3779
|
+
...version !== void 0 ? { x402Version: version } : {},
|
|
3780
|
+
toonChannel: {
|
|
3781
|
+
scheme: "toon-channel",
|
|
3782
|
+
...network !== void 0 ? { network } : {},
|
|
3783
|
+
destination,
|
|
3784
|
+
amount,
|
|
3785
|
+
httpEndpoint,
|
|
3786
|
+
supportsUpgrade
|
|
3787
|
+
}
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
return version !== void 0 ? { x402Version: version } : {};
|
|
3791
|
+
}
|
|
3792
|
+
var CRLF = "\r\n";
|
|
3793
|
+
function concatHeadAndBody(head, body) {
|
|
3794
|
+
const headBytes = encodeUtf8(head);
|
|
3795
|
+
const out = new Uint8Array(headBytes.length + body.length);
|
|
3796
|
+
out.set(headBytes, 0);
|
|
3797
|
+
out.set(body, headBytes.length);
|
|
3798
|
+
return out;
|
|
3799
|
+
}
|
|
3800
|
+
function bodyToBytes(body) {
|
|
3801
|
+
if (body === void 0) return new Uint8Array(0);
|
|
3802
|
+
return typeof body === "string" ? encodeUtf8(body) : body;
|
|
3803
|
+
}
|
|
3804
|
+
function serializeHttpRequest(req) {
|
|
3805
|
+
const u = new URL(req.url);
|
|
3806
|
+
const target = `${u.pathname}${u.search}` || "/";
|
|
3807
|
+
const bodyBytes = bodyToBytes(req.body);
|
|
3808
|
+
const headers = /* @__PURE__ */ new Map();
|
|
3809
|
+
const put = (name, value) => headers.set(name.toLowerCase(), `${name}: ${value}`);
|
|
3810
|
+
const has = (name) => headers.has(name.toLowerCase());
|
|
3811
|
+
for (const [name, value] of Object.entries(req.headers ?? {})) {
|
|
3812
|
+
put(name, value);
|
|
3813
|
+
}
|
|
3814
|
+
if (!has("host")) put("Host", u.host);
|
|
3815
|
+
if (bodyBytes.length > 0 && !has("content-length")) {
|
|
3816
|
+
put("Content-Length", String(bodyBytes.length));
|
|
3817
|
+
}
|
|
3818
|
+
const lines = [
|
|
3819
|
+
`${req.method.toUpperCase()} ${target} HTTP/1.1`,
|
|
3820
|
+
...headers.values()
|
|
3821
|
+
];
|
|
3822
|
+
const head = lines.join(CRLF) + CRLF + CRLF;
|
|
3823
|
+
return concatHeadAndBody(head, bodyBytes);
|
|
3824
|
+
}
|
|
3825
|
+
function findHeaderEnd(bytes) {
|
|
3826
|
+
for (let i = 0; i + 3 < bytes.length; i++) {
|
|
3827
|
+
if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) {
|
|
3828
|
+
return i + 4;
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
return -1;
|
|
3832
|
+
}
|
|
3833
|
+
function parseHttpResponse(bytes) {
|
|
3834
|
+
const headerEnd = findHeaderEnd(bytes);
|
|
3835
|
+
const headBytes = headerEnd === -1 ? bytes : bytes.subarray(0, headerEnd - 2);
|
|
3836
|
+
const body = headerEnd === -1 ? new Uint8Array(0) : bytes.subarray(headerEnd);
|
|
3837
|
+
const headText = decodeUtf8(headBytes);
|
|
3838
|
+
const lines = headText.split(CRLF).filter((l) => l.length > 0);
|
|
3839
|
+
const statusLine = lines.shift();
|
|
3840
|
+
if (!statusLine) {
|
|
3841
|
+
throw new ConnectorError(
|
|
3842
|
+
"h402 response payload had no HTTP status line"
|
|
3843
|
+
);
|
|
3844
|
+
}
|
|
3845
|
+
const match = /^HTTP\/\d\.\d\s+(\d{3})(?:\s+(.*))?$/.exec(statusLine.trim());
|
|
3846
|
+
if (!match) {
|
|
3847
|
+
throw new ConnectorError(
|
|
3848
|
+
`h402 response payload had a malformed status line: "${statusLine}"`
|
|
3849
|
+
);
|
|
3850
|
+
}
|
|
3851
|
+
const status = parseInt(match[1], 10);
|
|
3852
|
+
const statusText = match[2] ?? "";
|
|
3853
|
+
const headers = new Headers();
|
|
3854
|
+
for (const line of lines) {
|
|
3855
|
+
const idx = line.indexOf(":");
|
|
3856
|
+
if (idx === -1) continue;
|
|
3857
|
+
const name = line.slice(0, idx).trim();
|
|
3858
|
+
const value = line.slice(idx + 1).trim();
|
|
3859
|
+
if (name.length === 0) continue;
|
|
3860
|
+
headers.append(name, value);
|
|
3861
|
+
}
|
|
3862
|
+
const nullBodyStatus = status === 101 || status === 204 || status === 205 || status === 304;
|
|
3863
|
+
const init = { status, headers };
|
|
3864
|
+
if (statusText) init.statusText = statusText;
|
|
3865
|
+
return new Response(
|
|
3866
|
+
nullBodyStatus || body.length === 0 ? null : body.slice(),
|
|
3867
|
+
init
|
|
3868
|
+
);
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3307
3871
|
// src/ToonClient.ts
|
|
3308
3872
|
var ToonClient = class {
|
|
3309
3873
|
config;
|
|
@@ -3355,6 +3919,28 @@ var ToonClient = class {
|
|
|
3355
3919
|
getPublicKey() {
|
|
3356
3920
|
return getPublicKey2(this.config.secretKey);
|
|
3357
3921
|
}
|
|
3922
|
+
/**
|
|
3923
|
+
* Sign an unsigned Nostr event template with the client's Nostr secret key,
|
|
3924
|
+
* returning a fully-signed event (id + pubkey + sig).
|
|
3925
|
+
*
|
|
3926
|
+
* This is the key primitive behind the daemon's sign-and-publish path: a UI
|
|
3927
|
+
* or agent supplies only `{ kind, content, tags, created_at }` and never holds
|
|
3928
|
+
* the private key — signing happens here, inside the key owner.
|
|
3929
|
+
*/
|
|
3930
|
+
signEvent(template) {
|
|
3931
|
+
return finalizeEvent(template, this.config.secretKey);
|
|
3932
|
+
}
|
|
3933
|
+
/**
|
|
3934
|
+
* Upload bytes to Arweave via the kind:5094 blob-storage DVM (single-packet),
|
|
3935
|
+
* signing the request with this client's Nostr key and paying through its
|
|
3936
|
+
* existing channel. Returns the Arweave tx id on success.
|
|
3937
|
+
*
|
|
3938
|
+
* Backs the daemon's `upload-media` path: the key and claim/channel plumbing
|
|
3939
|
+
* stay inside the client; callers pass only the bytes.
|
|
3940
|
+
*/
|
|
3941
|
+
async uploadBlob(params) {
|
|
3942
|
+
return requestBlobStorage(this, this.config.secretKey, params);
|
|
3943
|
+
}
|
|
3358
3944
|
/**
|
|
3359
3945
|
* Per-chain settlement readiness for the configured `network` tier, mirroring
|
|
3360
3946
|
* the townhouse node's status. Returns `undefined` when no named `network` is
|
|
@@ -3637,6 +4223,46 @@ var ToonClient = class {
|
|
|
3637
4223
|
);
|
|
3638
4224
|
}
|
|
3639
4225
|
}
|
|
4226
|
+
/**
|
|
4227
|
+
* Payment-aware HTTP fetch over TOON (issue #50). A `fetch()`-like method that
|
|
4228
|
+
* makes paying for an HTTP resource transparent:
|
|
4229
|
+
*
|
|
4230
|
+
* 1. Issues the HTTP request to `url`.
|
|
4231
|
+
* 2. On `402`, parses the x402 `accepts` array and selects the
|
|
4232
|
+
* `toon-channel` entry (see {@link Http402Client} for the wire shape).
|
|
4233
|
+
* 3. Opens/reuses a payment channel for the entry's ILP destination (via
|
|
4234
|
+
* ChannelManager), signs a balance proof for the demanded price, and
|
|
4235
|
+
* re-sends the SAME HTTP request as a transparent HTTP-in-ILP packet to
|
|
4236
|
+
* the connector's `POST /ilp` (via {@link HttpIlpClient}), with the claim
|
|
4237
|
+
* in the `ILP-Payment-Channel-Claim` header.
|
|
4238
|
+
* 4. Reconstructs and returns a standard Web `Response` from the FULFILL
|
|
4239
|
+
* `data`. The caller never sees ILP.
|
|
4240
|
+
*
|
|
4241
|
+
* If the origin offers no `toon-channel` entry, the original `402` Response is
|
|
4242
|
+
* returned unchanged (the caller sees the vanilla x402 challenge).
|
|
4243
|
+
*
|
|
4244
|
+
* The channel/claim plumbing is wired to the live ChannelManager + per-chain
|
|
4245
|
+
* signer via `resolveClaimForDestination` — identical to `publishEvent`. The
|
|
4246
|
+
* `amount` paid comes from the selected x402 entry (the resource's price).
|
|
4247
|
+
*
|
|
4248
|
+
* @throws {ToonClientError} If the client is not started.
|
|
4249
|
+
* @throws {ConnectorError} If the connector rejects the payment or returns no
|
|
4250
|
+
* HTTP payload.
|
|
4251
|
+
*/
|
|
4252
|
+
async h402Fetch(url, opts) {
|
|
4253
|
+
if (!this.state) {
|
|
4254
|
+
throw new ToonClientError(
|
|
4255
|
+
"Client not started. Call start() first.",
|
|
4256
|
+
"INVALID_STATE"
|
|
4257
|
+
);
|
|
4258
|
+
}
|
|
4259
|
+
const client = new Http402Client({
|
|
4260
|
+
...this.channelManager ? {
|
|
4261
|
+
resolveClaim: (destination, amount) => this.resolveClaimForDestination(destination, amount)
|
|
4262
|
+
} : {}
|
|
4263
|
+
});
|
|
4264
|
+
return client.fetch(url, opts);
|
|
4265
|
+
}
|
|
3640
4266
|
/**
|
|
3641
4267
|
* Sends a raw swap ILP packet (Story 12.5) to a Mill peer with an attached
|
|
3642
4268
|
* balance-proof claim. This is a lower-level surface than `publishEvent`:
|
|
@@ -4735,74 +5361,8 @@ function buildPetPurchaseRequest(params) {
|
|
|
4735
5361
|
};
|
|
4736
5362
|
}
|
|
4737
5363
|
|
|
4738
|
-
// src/blob-storage.ts
|
|
4739
|
-
import { buildBlobStorageRequest } from "@toon-protocol/core";
|
|
4740
|
-
var ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;
|
|
4741
|
-
async function requestBlobStorage(client, secretKey, params) {
|
|
4742
|
-
const bid = params.bid ?? (params.ilpAmount !== void 0 ? String(params.ilpAmount) : void 0);
|
|
4743
|
-
if (bid === void 0 || bid === "") {
|
|
4744
|
-
return {
|
|
4745
|
-
success: false,
|
|
4746
|
-
error: "requestBlobStorage requires a bid (or ilpAmount to derive it)"
|
|
4747
|
-
};
|
|
4748
|
-
}
|
|
4749
|
-
const blobBuffer = Buffer.from(
|
|
4750
|
-
params.blobData.buffer,
|
|
4751
|
-
params.blobData.byteOffset,
|
|
4752
|
-
params.blobData.byteLength
|
|
4753
|
-
);
|
|
4754
|
-
let event;
|
|
4755
|
-
try {
|
|
4756
|
-
event = buildBlobStorageRequest(
|
|
4757
|
-
{
|
|
4758
|
-
blobData: blobBuffer,
|
|
4759
|
-
contentType: params.contentType,
|
|
4760
|
-
bid
|
|
4761
|
-
},
|
|
4762
|
-
secretKey
|
|
4763
|
-
);
|
|
4764
|
-
} catch (error) {
|
|
4765
|
-
return {
|
|
4766
|
-
success: false,
|
|
4767
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4768
|
-
};
|
|
4769
|
-
}
|
|
4770
|
-
const result = await client.publishEvent(event, {
|
|
4771
|
-
destination: params.destination,
|
|
4772
|
-
claim: params.claim,
|
|
4773
|
-
ilpAmount: params.ilpAmount
|
|
4774
|
-
});
|
|
4775
|
-
if (!result.success) {
|
|
4776
|
-
return {
|
|
4777
|
-
success: false,
|
|
4778
|
-
eventId: result.eventId ?? event.id,
|
|
4779
|
-
error: result.error ?? "Blob storage request rejected"
|
|
4780
|
-
};
|
|
4781
|
-
}
|
|
4782
|
-
if (!result.data) {
|
|
4783
|
-
return {
|
|
4784
|
-
success: false,
|
|
4785
|
-
eventId: event.id,
|
|
4786
|
-
error: "FULFILL contained no data; expected base64-encoded Arweave tx ID"
|
|
4787
|
-
};
|
|
4788
|
-
}
|
|
4789
|
-
const txId = decodeUtf8(fromBase64(result.data));
|
|
4790
|
-
if (!ARWEAVE_TX_ID_REGEX.test(txId)) {
|
|
4791
|
-
return {
|
|
4792
|
-
success: false,
|
|
4793
|
-
eventId: event.id,
|
|
4794
|
-
error: `Decoded FULFILL data is not a valid Arweave tx ID: "${txId}"`
|
|
4795
|
-
};
|
|
4796
|
-
}
|
|
4797
|
-
return {
|
|
4798
|
-
success: true,
|
|
4799
|
-
txId,
|
|
4800
|
-
eventId: event.id
|
|
4801
|
-
};
|
|
4802
|
-
}
|
|
4803
|
-
|
|
4804
5364
|
// src/keys/KeyManager.ts
|
|
4805
|
-
import { finalizeEvent } from "nostr-tools/pure";
|
|
5365
|
+
import { finalizeEvent as finalizeEvent2 } from "nostr-tools/pure";
|
|
4806
5366
|
import { nip19 } from "nostr-tools";
|
|
4807
5367
|
|
|
4808
5368
|
// src/keys/PasskeyAuth.ts
|
|
@@ -5588,7 +6148,7 @@ var KeyManager = class {
|
|
|
5588
6148
|
this.vault,
|
|
5589
6149
|
this.identity.nostr.secretKey
|
|
5590
6150
|
);
|
|
5591
|
-
const signedEvent =
|
|
6151
|
+
const signedEvent = finalizeEvent2(
|
|
5592
6152
|
eventTemplate,
|
|
5593
6153
|
this.identity.nostr.secretKey
|
|
5594
6154
|
);
|
|
@@ -5849,8 +6409,13 @@ export {
|
|
|
5849
6409
|
EvmSigner,
|
|
5850
6410
|
HS_HOSTNAME_MAX_LENGTH,
|
|
5851
6411
|
HS_HOSTNAME_REGEX,
|
|
6412
|
+
Http402Client,
|
|
5852
6413
|
HttpConnectorAdmin,
|
|
6414
|
+
HttpIlpClient,
|
|
5853
6415
|
HttpRuntimeClient,
|
|
6416
|
+
ILP_CLAIM_HEADER,
|
|
6417
|
+
ILP_CLAIM_WRAPPED_HEADER,
|
|
6418
|
+
ILP_PEER_ID_HEADER,
|
|
5854
6419
|
KeyManager,
|
|
5855
6420
|
MinaSigner,
|
|
5856
6421
|
NetworkError,
|
|
@@ -5879,17 +6444,24 @@ export {
|
|
|
5879
6444
|
generateMnemonic,
|
|
5880
6445
|
generateRandomIdentity,
|
|
5881
6446
|
getNetworkStatus,
|
|
6447
|
+
httpEndpointToBtpUrl,
|
|
5882
6448
|
importKeystore,
|
|
5883
6449
|
isPrfSupported,
|
|
5884
6450
|
isRoutableHsHostname,
|
|
5885
6451
|
loadKeystore,
|
|
5886
6452
|
parseBackupPayload,
|
|
6453
|
+
parseHttpResponse,
|
|
5887
6454
|
parsePetInteractionEvent,
|
|
5888
6455
|
parsePetInteractionResult,
|
|
5889
6456
|
parsePetListing,
|
|
6457
|
+
parseX402Body,
|
|
6458
|
+
parseX402Challenge,
|
|
6459
|
+
readDiscoveredIlpPeer,
|
|
5890
6460
|
readMinaDepositTotal,
|
|
5891
6461
|
requestBlobStorage,
|
|
5892
6462
|
selectAnonAsset,
|
|
6463
|
+
selectIlpTransport,
|
|
6464
|
+
serializeHttpRequest,
|
|
5893
6465
|
startManagedAnonProxy,
|
|
5894
6466
|
validateConfig,
|
|
5895
6467
|
validateMnemonic,
|