@ardrive/turbo-sdk 1.42.0 → 1.43.0-alpha.2

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.
Files changed (99) hide show
  1. package/lib/cjs/cli/cli.js +97 -0
  2. package/lib/cjs/cli/commands/arns.js +475 -0
  3. package/lib/cjs/cli/commands/freeStatus.js +47 -0
  4. package/lib/cjs/cli/commands/index.js +3 -0
  5. package/lib/cjs/cli/commands/paymentHistory.js +25 -0
  6. package/lib/cjs/cli/options.js +202 -1
  7. package/lib/cjs/common/arnsActions.js +125 -0
  8. package/lib/cjs/common/chunked.js +51 -1
  9. package/lib/cjs/common/folderIndex.js +401 -0
  10. package/lib/cjs/common/http.js +40 -6
  11. package/lib/cjs/common/index.js +7 -0
  12. package/lib/cjs/common/payment.js +548 -2
  13. package/lib/cjs/common/signer.js +27 -6
  14. package/lib/cjs/common/token/index.js +0 -11
  15. package/lib/cjs/common/turbo.js +162 -0
  16. package/lib/cjs/common/upload.js +314 -31
  17. package/lib/cjs/node/folderIndex.js +132 -0
  18. package/lib/cjs/node/index.js +1 -0
  19. package/lib/cjs/node/upload.js +7 -0
  20. package/lib/cjs/types.js +53 -3
  21. package/lib/cjs/utils/common.js +0 -4
  22. package/lib/cjs/utils/errors.js +38 -1
  23. package/lib/cjs/utils/folderIndex.js +88 -0
  24. package/lib/cjs/utils/uuid.js +31 -0
  25. package/lib/cjs/web/signer.js +4 -2
  26. package/lib/esm/cli/cli.js +99 -2
  27. package/lib/esm/cli/commands/arns.js +450 -0
  28. package/lib/esm/cli/commands/freeStatus.js +44 -0
  29. package/lib/esm/cli/commands/index.js +3 -0
  30. package/lib/esm/cli/commands/paymentHistory.js +22 -0
  31. package/lib/esm/cli/options.js +201 -0
  32. package/lib/esm/common/arnsActions.js +114 -0
  33. package/lib/esm/common/chunked.js +51 -1
  34. package/lib/esm/common/folderIndex.js +396 -0
  35. package/lib/esm/common/http.js +39 -5
  36. package/lib/esm/common/index.js +7 -0
  37. package/lib/esm/common/payment.js +548 -2
  38. package/lib/esm/common/signer.js +28 -7
  39. package/lib/esm/common/token/index.js +1 -12
  40. package/lib/esm/common/turbo.js +162 -0
  41. package/lib/esm/common/upload.js +316 -33
  42. package/lib/esm/node/folderIndex.js +129 -0
  43. package/lib/esm/node/index.js +1 -0
  44. package/lib/esm/node/upload.js +7 -0
  45. package/lib/esm/types.js +52 -2
  46. package/lib/esm/utils/common.js +0 -4
  47. package/lib/esm/utils/errors.js +35 -0
  48. package/lib/esm/utils/folderIndex.js +81 -0
  49. package/lib/esm/utils/uuid.js +28 -0
  50. package/lib/esm/web/signer.js +4 -2
  51. package/lib/types/cli/commands/arns.d.ts +147 -0
  52. package/lib/types/cli/commands/arns.d.ts.map +1 -0
  53. package/lib/types/cli/commands/freeStatus.d.ts +3 -0
  54. package/lib/types/cli/commands/freeStatus.d.ts.map +1 -0
  55. package/lib/types/cli/commands/index.d.ts +3 -0
  56. package/lib/types/cli/commands/index.d.ts.map +1 -1
  57. package/lib/types/cli/commands/paymentHistory.d.ts +18 -0
  58. package/lib/types/cli/commands/paymentHistory.d.ts.map +1 -0
  59. package/lib/types/cli/options.d.ts +423 -0
  60. package/lib/types/cli/options.d.ts.map +1 -1
  61. package/lib/types/cli/types.d.ts +78 -0
  62. package/lib/types/cli/types.d.ts.map +1 -1
  63. package/lib/types/common/arnsActions.d.ts +58 -0
  64. package/lib/types/common/arnsActions.d.ts.map +1 -0
  65. package/lib/types/common/chunked.d.ts +47 -2
  66. package/lib/types/common/chunked.d.ts.map +1 -1
  67. package/lib/types/common/folderIndex.d.ts +54 -0
  68. package/lib/types/common/folderIndex.d.ts.map +1 -0
  69. package/lib/types/common/http.d.ts +25 -2
  70. package/lib/types/common/http.d.ts.map +1 -1
  71. package/lib/types/common/index.d.ts +3 -0
  72. package/lib/types/common/index.d.ts.map +1 -1
  73. package/lib/types/common/payment.d.ts +291 -1
  74. package/lib/types/common/payment.d.ts.map +1 -1
  75. package/lib/types/common/signer.d.ts +12 -6
  76. package/lib/types/common/signer.d.ts.map +1 -1
  77. package/lib/types/common/token/index.d.ts.map +1 -1
  78. package/lib/types/common/turbo.d.ts +183 -1
  79. package/lib/types/common/turbo.d.ts.map +1 -1
  80. package/lib/types/common/upload.d.ts +41 -0
  81. package/lib/types/common/upload.d.ts.map +1 -1
  82. package/lib/types/node/folderIndex.d.ts +28 -0
  83. package/lib/types/node/folderIndex.d.ts.map +1 -0
  84. package/lib/types/node/index.d.ts +1 -0
  85. package/lib/types/node/index.d.ts.map +1 -1
  86. package/lib/types/node/upload.d.ts +1 -0
  87. package/lib/types/node/upload.d.ts.map +1 -1
  88. package/lib/types/types.d.ts +694 -3
  89. package/lib/types/types.d.ts.map +1 -1
  90. package/lib/types/utils/common.d.ts.map +1 -1
  91. package/lib/types/utils/errors.d.ts +29 -0
  92. package/lib/types/utils/errors.d.ts.map +1 -1
  93. package/lib/types/utils/folderIndex.d.ts +47 -0
  94. package/lib/types/utils/folderIndex.d.ts.map +1 -0
  95. package/lib/types/utils/uuid.d.ts +7 -0
  96. package/lib/types/utils/uuid.d.ts.map +1 -0
  97. package/lib/types/web/signer.d.ts +1 -1
  98. package/lib/types/web/signer.d.ts.map +1 -1
  99. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.paymentHistory = paymentHistory;
4
+ const utils_js_1 = require("../utils.js");
5
+ async function paymentHistory(options) {
6
+ // Signature-required and self-scoped: a wallet is mandatory (there is no
7
+ // --address form), and the service returns only this signer's own top-ups.
8
+ const turbo = await (0, utils_js_1.turboFromOptions)(options);
9
+ const limit = options.limit !== undefined ? +options.limit : undefined;
10
+ if (limit !== undefined &&
11
+ (!Number.isInteger(limit) || limit < 1 || limit > 100)) {
12
+ throw new Error('--limit must be an integer between 1 and 100');
13
+ }
14
+ const { payments, hasMore, cursor } = await turbo.getPaymentHistory({
15
+ limit,
16
+ cursor: options.cursor,
17
+ });
18
+ // stdout carries only the JSON page and the next-page hint goes to stderr, so
19
+ // `... | jq` works at the default log level. (--debug intentionally routes SDK
20
+ // logs to stdout and will interleave — expected when you ask for debug output.)
21
+ console.log(JSON.stringify({ payments, hasMore, cursor }, null, 2));
22
+ if (hasMore) {
23
+ console.error(`\nMore results available — fetch the next page with --cursor ${cursor}`);
24
+ }
25
+ }
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.listSharesOptions = exports.revokeCreditsOptions = exports.shareCreditsOptions = exports.uploadFileOptions = exports.uploadFolderOptions = exports.uploadOptions = exports.globalOptions = exports.walletOptions = exports.optionMap = void 0;
18
+ exports.arnsActionPriceOptions = exports.removeArNSRecordMetadataOptions = exports.setArNSRecordMetadataOptions = exports.transferArNSRecordOptions = exports.removeArNSControllerOptions = exports.addArNSControllerOptions = exports.removeArNSRecordOptions = exports.setArNSRecordOptions = exports.transferArNSAntOptions = exports.arnsActionStatusOptions = exports.arnsPurchaseStatusOptions = exports.upgradeArNSNameOptions = exports.increaseArNSUndernamesOptions = exports.extendArNSLeaseOptions = exports.buyArNSNameOptions = exports.arnsFiatQuoteOptions = exports.arnsPriceOptions = exports.listSharesOptions = exports.revokeCreditsOptions = exports.shareCreditsOptions = exports.uploadFileOptions = exports.uploadFolderOptions = exports.uploadOptions = exports.globalOptions = exports.walletOptions = exports.optionMap = void 0;
19
19
  exports.optionMap = {
20
20
  token: {
21
21
  alias: '-t, --token <type>',
@@ -189,6 +189,102 @@ exports.optionMap = {
189
189
  alias: '--max-crypto-top-up-value <maxCryptoTopUpValue>',
190
190
  description: 'Maximum crypto top-up value to use for the upload. Defaults to no limit.',
191
191
  },
192
+ // ---- ArNS (paid with Turbo Credits) ----
193
+ arnsName: {
194
+ alias: '--name <name>',
195
+ description: 'ArNS name to price, buy, or manage',
196
+ },
197
+ arnsType: {
198
+ alias: '--type <type>',
199
+ description: "ArNS purchase type for Buy-Name: 'lease' or 'permabuy'",
200
+ },
201
+ arnsYears: {
202
+ alias: '--years <years>',
203
+ description: 'Lease duration in years (Buy-Name lease / Extend-Lease)',
204
+ },
205
+ arnsIncreaseQty: {
206
+ alias: '--increase-qty <qty>',
207
+ description: 'Number of additional undernames (Increase-Undername-Limit)',
208
+ },
209
+ arnsOwnerKey: {
210
+ alias: '--owner-key <base58SolanaSecretKey>',
211
+ description: 'Base58 Solana secret key that OWNS the ANT and signs for it. Separate from the wallet paying in Turbo Credits — the owner needs a key to sign with, not SOL: Turbo pays every fee and rent',
212
+ },
213
+ arnsNonce: {
214
+ alias: '--nonce <nonce>',
215
+ description: 'ArNS purchase nonce to look up the status for',
216
+ },
217
+ arnsAntId: {
218
+ alias: '--ant-id <antId>',
219
+ description: 'ANT (Metaplex Core asset) ID to transfer or manage',
220
+ },
221
+ arnsTarget: {
222
+ alias: '--target <address>',
223
+ description: 'Target Solana pubkey to transfer the ANT to',
224
+ },
225
+ arnsUndername: {
226
+ alias: '--undername <undername>',
227
+ description: "ArNS undername record to set or remove (defaults to '@')",
228
+ },
229
+ arnsTransactionId: {
230
+ alias: '--transaction-id <transactionId>',
231
+ description: 'Arweave transaction ID the ArNS record resolves to',
232
+ },
233
+ arnsTtlSeconds: {
234
+ alias: '--ttl-seconds <ttlSeconds>',
235
+ description: 'TTL in seconds for the ArNS record',
236
+ },
237
+ arnsDisplayName: {
238
+ alias: '--display-name <displayName>',
239
+ description: 'Display name for the record (Set-Record-Metadata)',
240
+ },
241
+ arnsClearDisplayName: {
242
+ alias: '--clear-display-name',
243
+ description: 'Clear the record display name (Set-Record-Metadata)',
244
+ default: false,
245
+ },
246
+ arnsRecordLogo: {
247
+ alias: '--record-logo <transactionId>',
248
+ description: 'Arweave transaction ID for the record logo (Set-Record-Metadata)',
249
+ },
250
+ arnsClearRecordLogo: {
251
+ alias: '--clear-record-logo',
252
+ description: 'Clear the record logo (Set-Record-Metadata)',
253
+ default: false,
254
+ },
255
+ arnsRecordDescription: {
256
+ alias: '--record-description <description>',
257
+ description: 'Description for the record (Set-Record-Metadata)',
258
+ },
259
+ arnsClearRecordDescription: {
260
+ alias: '--clear-record-description',
261
+ description: 'Clear the record description (Set-Record-Metadata)',
262
+ default: false,
263
+ },
264
+ arnsRecordKeywords: {
265
+ alias: '--record-keywords <keywords...>',
266
+ description: 'Keywords for the record (Set-Record-Metadata)',
267
+ type: 'array',
268
+ },
269
+ arnsClearRecordKeywords: {
270
+ alias: '--clear-record-keywords',
271
+ description: 'Clear the record keywords (Set-Record-Metadata)',
272
+ default: false,
273
+ },
274
+ arnsAction: {
275
+ alias: '--action <action>',
276
+ description: 'ArNS action to price: set-record, remove-record, set-record-metadata, ' +
277
+ 'remove-record-metadata, transfer-record, add-controller, remove-controller, or transfer',
278
+ },
279
+ // ---- Payment history ----
280
+ limit: {
281
+ alias: '--limit <limit>',
282
+ description: 'Max number of payment-history rows to return (1-100, default 50)',
283
+ },
284
+ cursor: {
285
+ alias: '--cursor <cursor>',
286
+ description: 'Opaque pagination cursor from a prior payment-history response',
287
+ },
192
288
  };
193
289
  exports.walletOptions = [
194
290
  exports.optionMap.walletFile,
@@ -242,3 +338,108 @@ exports.shareCreditsOptions = [
242
338
  ];
243
339
  exports.revokeCreditsOptions = [...exports.walletOptions, exports.optionMap.address];
244
340
  exports.listSharesOptions = exports.revokeCreditsOptions;
341
+ // ---- ArNS command option sets (token + --payment-url come from globalOptions) ----
342
+ exports.arnsPriceOptions = [
343
+ exports.optionMap.arnsName,
344
+ exports.optionMap.arnsType,
345
+ exports.optionMap.arnsYears,
346
+ exports.optionMap.arnsIncreaseQty,
347
+ ];
348
+ exports.arnsFiatQuoteOptions = [
349
+ exports.optionMap.arnsName,
350
+ exports.optionMap.arnsType,
351
+ exports.optionMap.arnsYears,
352
+ exports.optionMap.arnsIncreaseQty,
353
+ exports.optionMap.address,
354
+ exports.optionMap.currency,
355
+ {
356
+ alias: '--method <method>',
357
+ description: 'Stripe integration mode: payment-intent (default) or checkout-session',
358
+ },
359
+ {
360
+ alias: '--promo-code <promoCode...>',
361
+ description: 'Promo code(s) to apply to the fiat price',
362
+ },
363
+ ];
364
+ exports.buyArNSNameOptions = [
365
+ ...exports.walletOptions,
366
+ exports.optionMap.arnsName,
367
+ exports.optionMap.arnsType,
368
+ exports.optionMap.arnsYears,
369
+ exports.optionMap.arnsOwnerKey,
370
+ exports.optionMap.paidBy,
371
+ ];
372
+ exports.extendArNSLeaseOptions = [
373
+ ...exports.walletOptions,
374
+ exports.optionMap.arnsName,
375
+ exports.optionMap.arnsYears,
376
+ exports.optionMap.paidBy,
377
+ ];
378
+ exports.increaseArNSUndernamesOptions = [
379
+ ...exports.walletOptions,
380
+ exports.optionMap.arnsName,
381
+ exports.optionMap.arnsIncreaseQty,
382
+ exports.optionMap.paidBy,
383
+ ];
384
+ exports.upgradeArNSNameOptions = [
385
+ ...exports.walletOptions,
386
+ exports.optionMap.arnsName,
387
+ exports.optionMap.paidBy,
388
+ ];
389
+ exports.arnsPurchaseStatusOptions = [exports.optionMap.arnsNonce];
390
+ exports.arnsActionStatusOptions = [...exports.walletOptions, exports.optionMap.arnsNonce];
391
+ exports.transferArNSAntOptions = [
392
+ ...exports.walletOptions,
393
+ exports.optionMap.arnsOwnerKey,
394
+ exports.optionMap.arnsAntId,
395
+ exports.optionMap.arnsTarget,
396
+ ];
397
+ exports.setArNSRecordOptions = [
398
+ ...exports.walletOptions,
399
+ exports.optionMap.arnsOwnerKey,
400
+ exports.optionMap.arnsAntId,
401
+ exports.optionMap.arnsUndername,
402
+ exports.optionMap.arnsTransactionId,
403
+ exports.optionMap.arnsTtlSeconds,
404
+ ];
405
+ exports.removeArNSRecordOptions = [
406
+ ...exports.walletOptions,
407
+ exports.optionMap.arnsOwnerKey,
408
+ exports.optionMap.arnsAntId,
409
+ exports.optionMap.arnsUndername,
410
+ ];
411
+ exports.addArNSControllerOptions = [
412
+ ...exports.walletOptions,
413
+ exports.optionMap.arnsOwnerKey,
414
+ exports.optionMap.arnsAntId,
415
+ exports.optionMap.arnsTarget,
416
+ ];
417
+ exports.removeArNSControllerOptions = exports.addArNSControllerOptions;
418
+ exports.transferArNSRecordOptions = [
419
+ ...exports.walletOptions,
420
+ exports.optionMap.arnsOwnerKey,
421
+ exports.optionMap.arnsAntId,
422
+ exports.optionMap.arnsUndername,
423
+ exports.optionMap.arnsTarget,
424
+ ];
425
+ exports.setArNSRecordMetadataOptions = [
426
+ ...exports.walletOptions,
427
+ exports.optionMap.arnsOwnerKey,
428
+ exports.optionMap.arnsAntId,
429
+ exports.optionMap.arnsUndername,
430
+ exports.optionMap.arnsDisplayName,
431
+ exports.optionMap.arnsClearDisplayName,
432
+ exports.optionMap.arnsRecordLogo,
433
+ exports.optionMap.arnsClearRecordLogo,
434
+ exports.optionMap.arnsRecordDescription,
435
+ exports.optionMap.arnsClearRecordDescription,
436
+ exports.optionMap.arnsRecordKeywords,
437
+ exports.optionMap.arnsClearRecordKeywords,
438
+ ];
439
+ exports.removeArNSRecordMetadataOptions = [
440
+ ...exports.walletOptions,
441
+ exports.optionMap.arnsOwnerKey,
442
+ exports.optionMap.arnsAntId,
443
+ exports.optionMap.arnsUndername,
444
+ ];
445
+ exports.arnsActionPriceOptions = [exports.optionMap.arnsAction];
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildArNSCustodyMessage = buildArNSCustodyMessage;
7
+ exports.arNSMetadataField = arNSMetadataField;
8
+ exports.arNSKeywordsField = arNSKeywordsField;
9
+ exports.arNSOwnerProofHeaders = arNSOwnerProofHeaders;
10
+ exports.solanaOwnerSigner = solanaOwnerSigner;
11
+ exports.emptySignatureSlots = emptySignatureSlots;
12
+ /**
13
+ * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
14
+ *
15
+ * Licensed under the Apache License, Version 2.0 (the "License");
16
+ * you may not use this file except in compliance with the License.
17
+ * You may obtain a copy of the License at
18
+ *
19
+ * http://www.apache.org/licenses/LICENSE-2.0
20
+ *
21
+ * Unless required by applicable law or agreed to in writing, software
22
+ * distributed under the License is distributed on an "AS IS" BASIS,
23
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24
+ * See the License for the specific language governing permissions and
25
+ * limitations under the License.
26
+ */
27
+ const web3_js_1 = require("@solana/web3.js");
28
+ const bs58_1 = __importDefault(require("bs58"));
29
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
30
+ const base64_js_1 = require("../utils/base64.js");
31
+ /**
32
+ * Canonical ACTION-BOUND message for the owner proof.
33
+ *
34
+ * MUST match the bundler's `buildArNSCustodyMessage` byte for byte
35
+ * (newline-delimited) or every signature is rejected. The bundler rebuilds this
36
+ * from the request and verifies the signature over `message + nonce`, so a
37
+ * signature captured for one operation cannot authorize a different one, and
38
+ * the nonce is consumed on use so the same one cannot be replayed (e.g. to
39
+ * revert a record to an older value).
40
+ */
41
+ function buildArNSCustodyMessage(action, fields) {
42
+ return ['arns', action, ...fields].join('\n');
43
+ }
44
+ /**
45
+ * A metadata field's ABSENT/EMPTY distinction, as the bundler binds it.
46
+ *
47
+ * `null` (clear the field) must not sign the same message as `''` (set it to
48
+ * empty), or a signature authorizing one would authorize the other. NUL can
49
+ * never appear in a value that reached here — the route rejects control
50
+ * characters — so it is a safe sentinel.
51
+ */
52
+ function arNSMetadataField(value) {
53
+ return value === null || value === undefined ? '\u0000' : value;
54
+ }
55
+ /**
56
+ * Keywords joined on a separator the route rejects INSIDE a keyword, so
57
+ * `['a,b']` and `['a','b']` cannot be re-partitioned into one another with the
58
+ * same signature.
59
+ */
60
+ function arNSKeywordsField(keywords) {
61
+ return keywords === null || keywords === undefined
62
+ ? '\u0000'
63
+ : keywords.join('\u0001');
64
+ }
65
+ /** Solana's signature-type discriminator in Turbo's signed-request scheme. */
66
+ const SOLANA_SIGNATURE_TYPE = 4;
67
+ /**
68
+ * The ANT owner's half of the envelope, in its own `x-owner-*` headers.
69
+ *
70
+ * Two signatures travel on a record action — the PAYER's signed request over
71
+ * `"" + nonce`, and the OWNER's action-bound proof over `message + nonce` —
72
+ * from two different keys, usually on two different chains. They cannot share
73
+ * one header set: whichever verifier ran second would reject a signature that
74
+ * was never meant for it.
75
+ */
76
+ async function arNSOwnerProofHeaders(owner, message, nonce) {
77
+ const address = await owner.getAddress();
78
+ const signature = await owner.signMessage(Uint8Array.from(Buffer.from(message + nonce)));
79
+ return {
80
+ // A Solana address IS the base58-encoded 32-byte ed25519 public key, so
81
+ // decoding the address recovers exactly the bytes the bundler verifies
82
+ // against. Keeps ArNSOwnerSigner minimal — no separate getPublicKey().
83
+ 'x-owner-public-key': (0, base64_js_1.toB64Url)(Buffer.from(bs58_1.default.decode(address))),
84
+ 'x-owner-nonce': nonce,
85
+ 'x-owner-signature': (0, base64_js_1.toB64Url)(Buffer.from(signature)),
86
+ 'x-owner-signature-type': String(SOLANA_SIGNATURE_TYPE),
87
+ };
88
+ }
89
+ /**
90
+ * Build an {@link ArNSOwnerSigner} from a raw Solana secret key.
91
+ *
92
+ * For servers and tests. A browser wallet (Phantom, Solflare, or a console's
93
+ * embedded wallet) should implement the interface directly against its own
94
+ * `signTransaction` / `signMessage` rather than exposing a secret key.
95
+ *
96
+ * Note the owner needs a key to SIGN with, not a funded account: Turbo is the
97
+ * fee payer on every sponsored action, so this wallet's SOL balance can stay
98
+ * at zero for the entire life of the name.
99
+ */
100
+ function solanaOwnerSigner(secretKey) {
101
+ const keypair = web3_js_1.Keypair.fromSecretKey(typeof secretKey === 'string' ? bs58_1.default.decode(secretKey) : secretKey);
102
+ return {
103
+ getAddress: () => keypair.publicKey.toBase58(),
104
+ signTransaction: async (transactionBase64) => {
105
+ const tx = web3_js_1.VersionedTransaction.deserialize(Buffer.from(transactionBase64, 'base64'));
106
+ // Sign the bytes as returned. Turbo's fee-payer signature already covers
107
+ // this exact message; rebuilding it from parts invalidates that and the
108
+ // submission is rejected.
109
+ tx.sign([keypair]);
110
+ return Buffer.from(tx.serialize()).toString('base64');
111
+ },
112
+ signMessage: async (message) => tweetnacl_1.default.sign.detached(message, keypair.secretKey),
113
+ };
114
+ }
115
+ /**
116
+ * Count the signature slots a prepared transaction still has empty.
117
+ *
118
+ * Turbo pre-signs as fee payer and leaves exactly one slot for the owner, so a
119
+ * client can assert that before prompting a wallet — a cheap way to catch a
120
+ * malformed or already-signed transaction before bothering the user.
121
+ */
122
+ function emptySignatureSlots(transactionBase64) {
123
+ const tx = web3_js_1.VersionedTransaction.deserialize(Buffer.from(transactionBase64, 'base64'));
124
+ return tx.signatures.filter((sig) => sig.every((byte) => byte === 0)).length;
125
+ }
@@ -41,7 +41,7 @@ const chunkingHeader = { 'x-chunking-version': '2' };
41
41
  * uploading them in parallel, and emitting progress/error events.
42
42
  */
43
43
  class ChunkedUploader {
44
- constructor({ http, token, maxChunkConcurrency = exports.defaultMaxChunkConcurrency, maxFinalizeMs, chunkByteCount = exports.defaultChunkByteCount, logger = logger_js_1.Logger.default, chunkingMode = 'auto', dataItemByteCount, }) {
44
+ constructor({ http, token, maxChunkConcurrency = exports.defaultMaxChunkConcurrency, maxFinalizeMs, chunkByteCount = exports.defaultChunkByteCount, logger = logger_js_1.Logger.default, chunkingMode = 'auto', dataItemByteCount, x402, x402RefundIdentity, }) {
45
45
  this.assertChunkParams({
46
46
  chunkByteCount,
47
47
  chunkingMode,
@@ -60,6 +60,9 @@ class ChunkedUploader {
60
60
  dataItemByteCount,
61
61
  });
62
62
  this.maxBacklogQueue = this.maxChunkConcurrency * backlogQueueFactor;
63
+ this.x402 = x402;
64
+ this.x402RefundIdentity = x402RefundIdentity;
65
+ this.dataItemByteCount = dataItemByteCount;
63
66
  }
64
67
  shouldChunkUpload({ chunkByteCount, chunkingMode, dataItemByteCount, }) {
65
68
  if (chunkingMode === 'disabled') {
@@ -98,7 +101,54 @@ class ChunkedUploader {
98
101
  /**
99
102
  * Initialize or resume an upload session, returning the upload ID.
100
103
  */
104
+ /**
105
+ * Open a multipart upload paid for with x402.
106
+ *
107
+ * The bundler settles the payment at CREATE, before accepting a single chunk
108
+ * — so an unpaid upload never consumes storage. That is why the size is
109
+ * declared here and the money moves here, not at finalize.
110
+ *
111
+ * The 402 handshake is delegated to `wrapFetchWithPayment`, the same
112
+ * mechanism the single-shot x402 POST already uses, so the signer and the
113
+ * spend cap behave identically on both paths.
114
+ */
115
+ async initPaidUpload(x402) {
116
+ if (this.paidUploadId !== undefined) {
117
+ this.logger.debug('Resuming the already-paid chunked upload', {
118
+ uploadId: this.paidUploadId,
119
+ });
120
+ return this.paidUploadId;
121
+ }
122
+ if (this.x402RefundIdentity === undefined) {
123
+ throw new Error('An x402-paid chunked upload needs a refund identity — it is the Turbo wallet credited if the upload delivers fewer bytes than were paid for.');
124
+ }
125
+ const { address, signatureType } = this.x402RefundIdentity;
126
+ const endpoint = `/chunks/${this.token}/-1/-1?chunkSize=${this.chunkByteCount}` +
127
+ `&totalBytes=${this.dataItemByteCount}` +
128
+ `&address=${encodeURIComponent(address)}` +
129
+ `&signatureType=${signatureType}`;
130
+ this.logger.debug('Opening an x402-paid chunked upload', {
131
+ totalBytes: this.dataItemByteCount,
132
+ });
133
+ const res = await this.http.get({
134
+ endpoint: endpoint,
135
+ headers: chunkingHeader,
136
+ x402Options: x402,
137
+ });
138
+ if (res.chunkSize !== undefined && res.chunkSize !== this.chunkByteCount) {
139
+ this.logger.warn('Chunk size mismatch! Overriding with server value.', {
140
+ clientExpected: this.chunkByteCount,
141
+ serverReturned: res.chunkSize,
142
+ });
143
+ this.chunkByteCount = res.chunkSize;
144
+ }
145
+ this.paidUploadId = res.id;
146
+ return res.id;
147
+ }
101
148
  async initUpload() {
149
+ if (this.x402 !== undefined) {
150
+ return this.initPaidUpload(this.x402);
151
+ }
102
152
  const res = await this.http.get({
103
153
  endpoint: `/chunks/${this.token}/-1/-1?chunkSize=${this.chunkByteCount}`,
104
154
  headers: chunkingHeader,