@toon-protocol/client 0.14.0 → 0.14.1
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 +73 -7
- package/dist/index.js +104 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -404,13 +404,21 @@ interface SignedBalanceProof extends BalanceProofParams {
|
|
|
404
404
|
*
|
|
405
405
|
* ## FULFILL data contract
|
|
406
406
|
*
|
|
407
|
-
*
|
|
408
|
-
* returns the
|
|
409
|
-
*
|
|
410
|
-
*
|
|
407
|
+
* The deployed connector is a payment-proxy (HTTP-in-ILP): a successful blob
|
|
408
|
+
* upload returns the DVM's verbatim **HTTP/1.1 response message** in the ILP
|
|
409
|
+
* FULFILL `data` field. For a single-packet (non-chunked) upload the body is a
|
|
410
|
+
* JSON object:
|
|
411
411
|
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
412
|
+
* HTTP/1.1 200 OK\r\n
|
|
413
|
+
* content-length: 189\r\n
|
|
414
|
+
* \r\n
|
|
415
|
+
* {"accept":true,"txId":"<43-char base64url>","data":"<base64 of txId>",...}
|
|
416
|
+
*
|
|
417
|
+
* We parse the HTTP envelope, fail on a non-2xx status (or `accept:false`), and
|
|
418
|
+
* read the Arweave tx ID from `txId` (falling back to base64-decoding `data`).
|
|
419
|
+
* An Arweave tx ID is a 43-character base64url string (32 raw bytes). A legacy
|
|
420
|
+
* fallback still accepts a bare `base64(utf8(txId))` FULFILL (no HTTP envelope)
|
|
421
|
+
* so non-proxy providers do not regress. See {@link extractArweaveTxId}.
|
|
414
422
|
*
|
|
415
423
|
* See `packages/sdk/src/arweave/arweave-dvm-handler.ts` for the server side and
|
|
416
424
|
* `packages/client/tests/e2e/docker-arweave-dvm-e2e.test.ts` for the reference
|
|
@@ -2368,6 +2376,64 @@ declare function withRetry<T>(operation: () => Promise<T>, options: RetryOptions
|
|
|
2368
2376
|
*/
|
|
2369
2377
|
declare function buildStoreWriteEnvelope(event: NostrEvent): Uint8Array;
|
|
2370
2378
|
|
|
2379
|
+
/**
|
|
2380
|
+
* Shared parser for the HTTP-over-ILP response carried in an ILP **FULFILL**
|
|
2381
|
+
* packet's `data` field.
|
|
2382
|
+
*
|
|
2383
|
+
* The deployed connector is a payment-proxy (HTTP-in-ILP): a paid write/upload
|
|
2384
|
+
* is reverse-proxied to the relay/DVM origin and the origin's reply is returned
|
|
2385
|
+
* **verbatim as a full HTTP/1.1 response message** inside the FULFILL `data`:
|
|
2386
|
+
*
|
|
2387
|
+
* HTTP/1.1 200 OK\r\n
|
|
2388
|
+
* content-length: 189\r\n
|
|
2389
|
+
* \r\n
|
|
2390
|
+
* {"accept":true,"txId":"4QcRav...","data":"<base64-txid>",...}
|
|
2391
|
+
*
|
|
2392
|
+
* Callers (`ToonClient.publishEvent`, `requestBlobStorage`) previously treated
|
|
2393
|
+
* this `data` as opaque success bytes — `publishEvent` reported success on ANY
|
|
2394
|
+
* FULFILL even when the embedded HTTP status was `404 Not Found`, and
|
|
2395
|
+
* `requestBlobStorage` base64-decoded the WHOLE response as if it were the bare
|
|
2396
|
+
* Arweave tx id. This module makes the HTTP envelope first-class so both paths
|
|
2397
|
+
* can read the real status and body.
|
|
2398
|
+
*
|
|
2399
|
+
* The full Web-`Response` reconstruction used by the h402 fetch path lives in
|
|
2400
|
+
* `adapters/Http402Client.ts` (`parseHttpResponse`); this is a smaller,
|
|
2401
|
+
* dependency-free helper that returns the status code + raw body string, which
|
|
2402
|
+
* is all the publish/upload paths need.
|
|
2403
|
+
*
|
|
2404
|
+
* DEFENSIVE: not every FULFILL is HTTP-enveloped (e.g. Mill-swap raw-TOON
|
|
2405
|
+
* FULFILLs go through `sendSwapPacket`, not these paths). If the decoded data
|
|
2406
|
+
* does not begin with an `HTTP/<v>` status line, `isHttp` is `false` and the
|
|
2407
|
+
* caller should fall back to its prior (non-HTTP) interpretation rather than
|
|
2408
|
+
* fail. This keeps non-HTTP FULFILLs from regressing.
|
|
2409
|
+
*/
|
|
2410
|
+
/** Result of parsing FULFILL `data` as an HTTP/1.1 response. */
|
|
2411
|
+
interface ParsedFulfillHttp {
|
|
2412
|
+
/** Whether the data looked like an HTTP/1.1 response (status line present). */
|
|
2413
|
+
isHttp: boolean;
|
|
2414
|
+
/** HTTP status code (e.g. 200, 404). Only meaningful when `isHttp` is true. */
|
|
2415
|
+
status: number;
|
|
2416
|
+
/** Reason phrase from the status line (may be empty). */
|
|
2417
|
+
statusText: string;
|
|
2418
|
+
/** Decoded response body as a UTF-8 string (empty when none). */
|
|
2419
|
+
body: string;
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Parse FULFILL `data` bytes as an HTTP/1.1 response.
|
|
2423
|
+
*
|
|
2424
|
+
* Returns `{ isHttp: false, ... }` (without throwing) when the payload does not
|
|
2425
|
+
* start with an `HTTP/<v>` status line, so callers can fall back to their
|
|
2426
|
+
* legacy non-HTTP interpretation. When it IS an HTTP response, the status code
|
|
2427
|
+
* and body are extracted; a present-but-malformed status line yields
|
|
2428
|
+
* `isHttp: false` as well (treated as non-HTTP rather than throwing).
|
|
2429
|
+
*/
|
|
2430
|
+
declare function parseFulfillHttpBytes(bytes: Uint8Array): ParsedFulfillHttp;
|
|
2431
|
+
/**
|
|
2432
|
+
* Convenience wrapper: decode a base64 FULFILL `data` string (the shape carried
|
|
2433
|
+
* on `IlpSendResult.data`) and parse it as an HTTP/1.1 response.
|
|
2434
|
+
*/
|
|
2435
|
+
declare function parseFulfillHttp(base64Data: string): ParsedFulfillHttp;
|
|
2436
|
+
|
|
2371
2437
|
/**
|
|
2372
2438
|
* Settlement info produced by buildSettlementInfo().
|
|
2373
2439
|
* Extends the core SettlementConfig shape with ilpAddress for client use.
|
|
@@ -3270,4 +3336,4 @@ declare function loadKeystore(path: string, password: string): string;
|
|
|
3270
3336
|
*/
|
|
3271
3337
|
declare function writeKeystoreFile(path: string, keystore: EncryptedKeystore): void;
|
|
3272
3338
|
|
|
3273
|
-
export { type BackupPayload, type BalanceProofParams, BtpRuntimeClient, type BtpRuntimeClientConfig, type ChainMetadata, type ChainSigner, ChannelManager, type ClaimMessage, type ClaimResolver, ConnectorError, type DiscoveredIlpPeer, type EVMClaimMessage, type EncryptedKeystore, EvmSigner, type FaucetChain, type FundWalletOptions, type FundWalletResult, type H402FetchOptions, Http402Client, type Http402ClientConfig, HttpConnectorAdmin, type HttpConnectorAdminConfig, HttpIlpClient, type HttpIlpClientConfig, type HttpIlpClientFactory, HttpRuntimeClient, type HttpRuntimeClientConfig, ILP_CLAIM_HEADER, ILP_CLAIM_WRAPPED_HEADER, ILP_PEER_ID_HEADER, type IlpTransportChoice, type InteractionResultContent, KeyManager, type KeyManagerConfig, type MinaClaimMessage, type MinaDepositReader, MinaSigner, type MinaSignerOptions, NetworkError, OnChainChannelClient, type OnChainChannelClientConfig, type ParsedX402Challenge, type PasskeyInfo, type PetDvmProvider, type PetInteractionEventData, type PetInteractionRequestParams, type PetInteractionResultData, type PetListing, type PetListingFilterOptions, type PetListingParams, type PetPurchaseRequestParams, type ProofStatus, type PublishEventResult, type RequestBlobStorageParams, type RequestBlobStorageResult, type RetryOptions, type SelectIlpTransportOptions, type SignedBalanceProof, type SolanaChannelClientOptions, type SolanaClaimMessage, SolanaSigner, type StatValues, type ToonChannelAccept, ToonClient, type ToonClientConfig, ToonClientError, type ToonIdentity, type ToonSigners, type ToonStartResult, type UnsignedNostrEvent, ValidationError, type VaultData, applyDefaults, applyNetworkPresets, buildBackupEvent, buildBackupFilter, buildPetInteractionRequest, buildPetListingEvent, buildPetPurchaseRequest, buildSettlementInfo, buildStoreWriteEnvelope, decryptMnemonic, deriveFromNsec, deriveFullIdentity, deriveNostrKeyFromMnemonic, encryptMnemonic, filterPetDvmProviders, filterPetListings, fundWallet, generateKeystore, generateMnemonic, generateRandomIdentity, getNetworkStatus, httpEndpointToBtpUrl, importKeystore, isPrfSupported, loadKeystore, parseBackupPayload, parseHttpResponse, parsePetInteractionEvent, parsePetInteractionResult, parsePetListing, parseX402Body, parseX402Challenge, proxyIlpEndpoint, readDiscoveredIlpPeer, readMinaDepositTotal, requestBlobStorage, selectIlpTransport, serializeHttpRequest, validateConfig, validateMnemonic, withRetry, writeKeystoreFile };
|
|
3339
|
+
export { type BackupPayload, type BalanceProofParams, BtpRuntimeClient, type BtpRuntimeClientConfig, type ChainMetadata, type ChainSigner, ChannelManager, type ClaimMessage, type ClaimResolver, ConnectorError, type DiscoveredIlpPeer, type EVMClaimMessage, type EncryptedKeystore, EvmSigner, type FaucetChain, type FundWalletOptions, type FundWalletResult, type H402FetchOptions, Http402Client, type Http402ClientConfig, HttpConnectorAdmin, type HttpConnectorAdminConfig, HttpIlpClient, type HttpIlpClientConfig, type HttpIlpClientFactory, HttpRuntimeClient, type HttpRuntimeClientConfig, ILP_CLAIM_HEADER, ILP_CLAIM_WRAPPED_HEADER, ILP_PEER_ID_HEADER, type IlpTransportChoice, type InteractionResultContent, KeyManager, type KeyManagerConfig, type MinaClaimMessage, type MinaDepositReader, MinaSigner, type MinaSignerOptions, NetworkError, OnChainChannelClient, type OnChainChannelClientConfig, type ParsedFulfillHttp, type ParsedX402Challenge, type PasskeyInfo, type PetDvmProvider, type PetInteractionEventData, type PetInteractionRequestParams, type PetInteractionResultData, type PetListing, type PetListingFilterOptions, type PetListingParams, type PetPurchaseRequestParams, type ProofStatus, type PublishEventResult, type RequestBlobStorageParams, type RequestBlobStorageResult, type RetryOptions, type SelectIlpTransportOptions, type SignedBalanceProof, type SolanaChannelClientOptions, type SolanaClaimMessage, SolanaSigner, type StatValues, type ToonChannelAccept, ToonClient, type ToonClientConfig, ToonClientError, type ToonIdentity, type ToonSigners, type ToonStartResult, type UnsignedNostrEvent, ValidationError, type VaultData, applyDefaults, applyNetworkPresets, buildBackupEvent, buildBackupFilter, buildPetInteractionRequest, buildPetListingEvent, buildPetPurchaseRequest, buildSettlementInfo, buildStoreWriteEnvelope, decryptMnemonic, deriveFromNsec, deriveFullIdentity, deriveNostrKeyFromMnemonic, encryptMnemonic, filterPetDvmProviders, filterPetListings, fundWallet, generateKeystore, generateMnemonic, generateRandomIdentity, getNetworkStatus, httpEndpointToBtpUrl, importKeystore, isPrfSupported, loadKeystore, parseBackupPayload, parseFulfillHttp, parseFulfillHttpBytes, parseHttpResponse, parsePetInteractionEvent, parsePetInteractionResult, parsePetListing, parseX402Body, parseX402Challenge, proxyIlpEndpoint, readDiscoveredIlpPeer, readMinaDepositTotal, requestBlobStorage, selectIlpTransport, serializeHttpRequest, validateConfig, validateMnemonic, withRetry, writeKeystoreFile };
|
package/dist/index.js
CHANGED
|
@@ -518,6 +518,44 @@ function buildStoreWriteEnvelope(event) {
|
|
|
518
518
|
return encodeUtf8(head + "\r\n\r\n" + body);
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
+
// src/utils/fulfill-http.ts
|
|
522
|
+
var CRLF = "\r\n";
|
|
523
|
+
function findHeaderEnd(bytes) {
|
|
524
|
+
for (let i = 0; i + 3 < bytes.length; i++) {
|
|
525
|
+
if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) {
|
|
526
|
+
return i + 4;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return -1;
|
|
530
|
+
}
|
|
531
|
+
function parseFulfillHttpBytes(bytes) {
|
|
532
|
+
const notHttp = {
|
|
533
|
+
isHttp: false,
|
|
534
|
+
status: 0,
|
|
535
|
+
statusText: "",
|
|
536
|
+
body: ""
|
|
537
|
+
};
|
|
538
|
+
const headerEnd = findHeaderEnd(bytes);
|
|
539
|
+
const headBytes = headerEnd === -1 ? bytes : bytes.subarray(0, headerEnd - 2);
|
|
540
|
+
const bodyBytes = headerEnd === -1 ? new Uint8Array(0) : bytes.subarray(headerEnd);
|
|
541
|
+
const headText = decodeUtf8(headBytes);
|
|
542
|
+
const lines = headText.split(CRLF).filter((l) => l.length > 0);
|
|
543
|
+
const statusLine = lines.shift();
|
|
544
|
+
if (!statusLine) return notHttp;
|
|
545
|
+
if (!statusLine.trimStart().startsWith("HTTP/")) return notHttp;
|
|
546
|
+
const match = /^HTTP\/\d\.\d\s+(\d{3})(?:\s+(.*))?$/.exec(statusLine.trim());
|
|
547
|
+
if (!match) return notHttp;
|
|
548
|
+
return {
|
|
549
|
+
isHttp: true,
|
|
550
|
+
status: parseInt(match[1], 10),
|
|
551
|
+
statusText: match[2] ?? "",
|
|
552
|
+
body: decodeUtf8(bodyBytes)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function parseFulfillHttp(base64Data) {
|
|
556
|
+
return parseFulfillHttpBytes(fromBase64(base64Data));
|
|
557
|
+
}
|
|
558
|
+
|
|
521
559
|
// src/modes/http.ts
|
|
522
560
|
import { BootstrapService, createDiscoveryTracker } from "@toon-protocol/core";
|
|
523
561
|
|
|
@@ -3557,15 +3595,17 @@ async function requestBlobStorage(client, secretKey, params) {
|
|
|
3557
3595
|
return {
|
|
3558
3596
|
success: false,
|
|
3559
3597
|
eventId: event.id,
|
|
3560
|
-
error: "FULFILL contained no data; expected
|
|
3598
|
+
error: "FULFILL contained no data; expected an HTTP response with the Arweave tx ID"
|
|
3561
3599
|
};
|
|
3562
3600
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
3601
|
+
let txId;
|
|
3602
|
+
try {
|
|
3603
|
+
txId = extractArweaveTxId(result.data);
|
|
3604
|
+
} catch (error) {
|
|
3565
3605
|
return {
|
|
3566
3606
|
success: false,
|
|
3567
3607
|
eventId: event.id,
|
|
3568
|
-
error:
|
|
3608
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3569
3609
|
};
|
|
3570
3610
|
}
|
|
3571
3611
|
return {
|
|
@@ -3574,6 +3614,49 @@ async function requestBlobStorage(client, secretKey, params) {
|
|
|
3574
3614
|
eventId: event.id
|
|
3575
3615
|
};
|
|
3576
3616
|
}
|
|
3617
|
+
function extractArweaveTxId(base64Data) {
|
|
3618
|
+
const http2 = parseFulfillHttp(base64Data);
|
|
3619
|
+
if (!http2.isHttp) {
|
|
3620
|
+
const legacy = decodeUtf8(fromBase64(base64Data));
|
|
3621
|
+
if (!ARWEAVE_TX_ID_REGEX.test(legacy)) {
|
|
3622
|
+
throw new Error(
|
|
3623
|
+
`Decoded FULFILL data is not a valid Arweave tx ID: "${legacy}"`
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3626
|
+
return legacy;
|
|
3627
|
+
}
|
|
3628
|
+
if (http2.status < 200 || http2.status >= 300) {
|
|
3629
|
+
const detail = http2.body ? ` - ${http2.body}` : "";
|
|
3630
|
+
throw new Error(
|
|
3631
|
+
`Blob upload failed: DVM returned HTTP ${http2.status} ${http2.statusText}`.trimEnd() + detail
|
|
3632
|
+
);
|
|
3633
|
+
}
|
|
3634
|
+
let parsed;
|
|
3635
|
+
try {
|
|
3636
|
+
parsed = JSON.parse(http2.body);
|
|
3637
|
+
} catch {
|
|
3638
|
+
throw new Error(
|
|
3639
|
+
`Blob upload response body was not valid JSON: "${http2.body}"`
|
|
3640
|
+
);
|
|
3641
|
+
}
|
|
3642
|
+
const body = parsed;
|
|
3643
|
+
if (body.accept === false) {
|
|
3644
|
+
const reason = typeof body.error === "string" ? `: ${body.error}` : "";
|
|
3645
|
+
throw new Error(`Blob upload rejected by DVM (accept:false)${reason}`);
|
|
3646
|
+
}
|
|
3647
|
+
if (typeof body.txId === "string" && ARWEAVE_TX_ID_REGEX.test(body.txId)) {
|
|
3648
|
+
return body.txId;
|
|
3649
|
+
}
|
|
3650
|
+
if (typeof body.data === "string" && body.data.length > 0) {
|
|
3651
|
+
const decoded = decodeUtf8(fromBase64(body.data));
|
|
3652
|
+
if (ARWEAVE_TX_ID_REGEX.test(decoded)) {
|
|
3653
|
+
return decoded;
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
throw new Error(
|
|
3657
|
+
`Blob upload response did not contain a valid Arweave tx ID: "${http2.body}"`
|
|
3658
|
+
);
|
|
3659
|
+
}
|
|
3577
3660
|
|
|
3578
3661
|
// src/adapters/Http402Client.ts
|
|
3579
3662
|
var Http402Client = class {
|
|
@@ -3748,7 +3831,7 @@ function parseX402Body(body) {
|
|
|
3748
3831
|
}
|
|
3749
3832
|
return version !== void 0 ? { x402Version: version } : {};
|
|
3750
3833
|
}
|
|
3751
|
-
var
|
|
3834
|
+
var CRLF2 = "\r\n";
|
|
3752
3835
|
function concatHeadAndBody(head, body) {
|
|
3753
3836
|
const headBytes = encodeUtf8(head);
|
|
3754
3837
|
const out = new Uint8Array(headBytes.length + body.length);
|
|
@@ -3778,10 +3861,10 @@ function serializeHttpRequest(req) {
|
|
|
3778
3861
|
`${req.method.toUpperCase()} ${target} HTTP/1.1`,
|
|
3779
3862
|
...headers.values()
|
|
3780
3863
|
];
|
|
3781
|
-
const head = lines.join(
|
|
3864
|
+
const head = lines.join(CRLF2) + CRLF2 + CRLF2;
|
|
3782
3865
|
return concatHeadAndBody(head, bodyBytes);
|
|
3783
3866
|
}
|
|
3784
|
-
function
|
|
3867
|
+
function findHeaderEnd2(bytes) {
|
|
3785
3868
|
for (let i = 0; i + 3 < bytes.length; i++) {
|
|
3786
3869
|
if (bytes[i] === 13 && bytes[i + 1] === 10 && bytes[i + 2] === 13 && bytes[i + 3] === 10) {
|
|
3787
3870
|
return i + 4;
|
|
@@ -3790,11 +3873,11 @@ function findHeaderEnd(bytes) {
|
|
|
3790
3873
|
return -1;
|
|
3791
3874
|
}
|
|
3792
3875
|
function parseHttpResponse(bytes) {
|
|
3793
|
-
const headerEnd =
|
|
3876
|
+
const headerEnd = findHeaderEnd2(bytes);
|
|
3794
3877
|
const headBytes = headerEnd === -1 ? bytes : bytes.subarray(0, headerEnd - 2);
|
|
3795
3878
|
const body = headerEnd === -1 ? new Uint8Array(0) : bytes.subarray(headerEnd);
|
|
3796
3879
|
const headText = decodeUtf8(headBytes);
|
|
3797
|
-
const lines = headText.split(
|
|
3880
|
+
const lines = headText.split(CRLF2).filter((l) => l.length > 0);
|
|
3798
3881
|
const statusLine = lines.shift();
|
|
3799
3882
|
if (!statusLine) {
|
|
3800
3883
|
throw new ConnectorError(
|
|
@@ -4151,6 +4234,16 @@ var ToonClient = class {
|
|
|
4151
4234
|
error: `Event rejected: ${response.code} - ${response.message}`
|
|
4152
4235
|
};
|
|
4153
4236
|
}
|
|
4237
|
+
if (response.data) {
|
|
4238
|
+
const httpResult = parseFulfillHttp(response.data);
|
|
4239
|
+
if (httpResult.isHttp && (httpResult.status < 200 || httpResult.status >= 300)) {
|
|
4240
|
+
const detail = httpResult.body ? ` - ${httpResult.body}` : "";
|
|
4241
|
+
return {
|
|
4242
|
+
success: false,
|
|
4243
|
+
error: `Write failed: relay returned HTTP ${httpResult.status} ${httpResult.statusText}`.trimEnd() + detail
|
|
4244
|
+
};
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4154
4247
|
return {
|
|
4155
4248
|
success: true,
|
|
4156
4249
|
eventId: event.id,
|
|
@@ -6483,6 +6576,8 @@ export {
|
|
|
6483
6576
|
isTrustDowngrade,
|
|
6484
6577
|
loadKeystore,
|
|
6485
6578
|
parseBackupPayload,
|
|
6579
|
+
parseFulfillHttp,
|
|
6580
|
+
parseFulfillHttpBytes,
|
|
6486
6581
|
parseHttpResponse,
|
|
6487
6582
|
parsePetInteractionEvent,
|
|
6488
6583
|
parsePetInteractionResult,
|