@ardrive/turbo-sdk 1.41.3 → 1.42.0-alpha.10

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 (97) hide show
  1. package/lib/cjs/cli/cli.js +61 -0
  2. package/lib/cjs/cli/commands/arns.js +317 -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 +124 -11
  7. package/lib/cjs/cli/utils.js +3 -19
  8. package/lib/cjs/common/arnsActions.js +102 -0
  9. package/lib/cjs/common/factory.js +1 -3
  10. package/lib/cjs/common/http.js +20 -5
  11. package/lib/cjs/common/index.js +6 -0
  12. package/lib/cjs/common/payment.js +482 -3
  13. package/lib/cjs/common/signer.js +27 -6
  14. package/lib/cjs/common/token/ario.js +104 -83
  15. package/lib/cjs/common/token/index.js +0 -11
  16. package/lib/cjs/common/token/solana.js +1 -0
  17. package/lib/cjs/common/turbo.js +131 -0
  18. package/lib/cjs/common/upload.js +3 -2
  19. package/lib/cjs/node/factory.js +1 -3
  20. package/lib/cjs/types.js +44 -3
  21. package/lib/cjs/utils/common.js +5 -21
  22. package/lib/cjs/utils/errors.js +38 -1
  23. package/lib/cjs/utils/uuid.js +31 -0
  24. package/lib/cjs/web/factory.js +1 -3
  25. package/lib/cjs/web/signer.js +4 -2
  26. package/lib/esm/cli/cli.js +63 -2
  27. package/lib/esm/cli/commands/arns.js +300 -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 +123 -10
  32. package/lib/esm/cli/utils.js +4 -20
  33. package/lib/esm/common/arnsActions.js +93 -0
  34. package/lib/esm/common/factory.js +1 -3
  35. package/lib/esm/common/http.js +19 -4
  36. package/lib/esm/common/index.js +6 -0
  37. package/lib/esm/common/payment.js +482 -3
  38. package/lib/esm/common/signer.js +28 -7
  39. package/lib/esm/common/token/ario.js +102 -84
  40. package/lib/esm/common/token/index.js +1 -12
  41. package/lib/esm/common/token/solana.js +1 -0
  42. package/lib/esm/common/turbo.js +131 -0
  43. package/lib/esm/common/upload.js +4 -3
  44. package/lib/esm/node/factory.js +1 -3
  45. package/lib/esm/types.js +43 -2
  46. package/lib/esm/utils/common.js +4 -20
  47. package/lib/esm/utils/errors.js +35 -0
  48. package/lib/esm/utils/uuid.js +28 -0
  49. package/lib/esm/web/factory.js +1 -3
  50. package/lib/esm/web/signer.js +4 -2
  51. package/lib/types/cli/commands/arns.d.ts +86 -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 +238 -10
  60. package/lib/types/cli/options.d.ts.map +1 -1
  61. package/lib/types/cli/types.d.ts +41 -2
  62. package/lib/types/cli/types.d.ts.map +1 -1
  63. package/lib/types/cli/utils.d.ts.map +1 -1
  64. package/lib/types/common/arnsActions.d.ts +43 -0
  65. package/lib/types/common/arnsActions.d.ts.map +1 -0
  66. package/lib/types/common/factory.d.ts +1 -1
  67. package/lib/types/common/factory.d.ts.map +1 -1
  68. package/lib/types/common/http.d.ts +14 -1
  69. package/lib/types/common/http.d.ts.map +1 -1
  70. package/lib/types/common/index.d.ts +2 -0
  71. package/lib/types/common/index.d.ts.map +1 -1
  72. package/lib/types/common/payment.d.ts +230 -1
  73. package/lib/types/common/payment.d.ts.map +1 -1
  74. package/lib/types/common/signer.d.ts +12 -6
  75. package/lib/types/common/signer.d.ts.map +1 -1
  76. package/lib/types/common/token/ario.d.ts +13 -9
  77. package/lib/types/common/token/ario.d.ts.map +1 -1
  78. package/lib/types/common/token/index.d.ts.map +1 -1
  79. package/lib/types/common/token/solana.d.ts.map +1 -1
  80. package/lib/types/common/turbo.d.ts +140 -1
  81. package/lib/types/common/turbo.d.ts.map +1 -1
  82. package/lib/types/common/upload.d.ts.map +1 -1
  83. package/lib/types/node/factory.d.ts +1 -1
  84. package/lib/types/node/factory.d.ts.map +1 -1
  85. package/lib/types/types.d.ts +483 -7
  86. package/lib/types/types.d.ts.map +1 -1
  87. package/lib/types/utils/common.d.ts +0 -12
  88. package/lib/types/utils/common.d.ts.map +1 -1
  89. package/lib/types/utils/errors.d.ts +29 -0
  90. package/lib/types/utils/errors.d.ts.map +1 -1
  91. package/lib/types/utils/uuid.d.ts +7 -0
  92. package/lib/types/utils/uuid.d.ts.map +1 -0
  93. package/lib/types/web/factory.d.ts +1 -1
  94. package/lib/types/web/factory.d.ts.map +1 -1
  95. package/lib/types/web/signer.d.ts +1 -1
  96. package/lib/types/web/signer.d.ts.map +1 -1
  97. package/package.json +3 -2
@@ -17,7 +17,11 @@ exports.TurboAuthenticatedPaymentService = exports.TurboUnauthenticatedPaymentSe
17
17
  * limitations under the License.
18
18
  */
19
19
  const bignumber_js_1 = require("bignumber.js");
20
+ const types_js_1 = require("../types.js");
20
21
  const common_js_1 = require("../utils/common.js");
22
+ const errors_js_1 = require("../utils/errors.js");
23
+ const uuid_js_1 = require("../utils/uuid.js");
24
+ const arnsActions_js_1 = require("./arnsActions.js");
21
25
  const http_js_1 = require("./http.js");
22
26
  const http_js_2 = require("./http.js");
23
27
  const logger_js_1 = require("./logger.js");
@@ -33,6 +37,7 @@ class TurboUnauthenticatedPaymentService {
33
37
  logger: this.logger,
34
38
  });
35
39
  this.token = token;
40
+ this.url = url;
36
41
  }
37
42
  async getBalance(address) {
38
43
  const balance = await this.httpService.get({
@@ -49,6 +54,31 @@ class TurboUnauthenticatedPaymentService {
49
54
  receivedApprovals: [],
50
55
  };
51
56
  }
57
+ async getFreeStatus(address) {
58
+ const status = await this.httpService.get({
59
+ endpoint: `/account/free?address=${address}`,
60
+ allowedStatuses: [200, 404],
61
+ });
62
+ // Normalize: preserve a legitimate `0` (free tier off) or `null` (unlimited),
63
+ // and coerce a missing field (e.g. a 404 body) to `null`.
64
+ return { bytesRemaining: status?.bytesRemaining ?? null };
65
+ }
66
+ /**
67
+ * Returns the ArNS names a wallet owns or controls -- both custodial names
68
+ * bought via Turbo's ArNS-with-credits feature (Turbo may spawn and hold
69
+ * the ANT on the caller's behalf, depending on the buy) and self-custody
70
+ * names, in one list. See `TurboArNSName` for field semantics, including
71
+ * the `custodial` flag distinguishing the two.
72
+ *
73
+ * To read a name's current records or lease/expiration state, use
74
+ * `@ar.io/sdk` directly against the returned `antId` -- it talks to the
75
+ * chain directly and needs no round-trip through this SDK/backend.
76
+ */
77
+ getArNSNames(address) {
78
+ return this.httpService.get({
79
+ endpoint: `/arns/my-names/${encodeURIComponent(address)}`,
80
+ });
81
+ }
52
82
  getFiatRates() {
53
83
  return this.httpService.get({
54
84
  endpoint: '/rates',
@@ -93,6 +123,188 @@ class TurboUnauthenticatedPaymentService {
93
123
  equivalentWincTokenAmount: actualPaymentAmount.toString(),
94
124
  };
95
125
  }
126
+ async getArNSPriceForName(params) {
127
+ // `async` so a validation failure surfaces as a rejected promise (consistent
128
+ // with `purchaseArNSName`) rather than a synchronous throw.
129
+ this.validateArNSPurchaseParams(params);
130
+ const price = await this.httpService.get({
131
+ endpoint: `/arns/price/${params.intent.toLowerCase()}/${params.name}${this.buildArNSPurchaseQuery(params)}`,
132
+ });
133
+ // Normalize the figure to charge into ONE field. `winc` is the name only
134
+ // and excludes the ANT spawn surcharge — for a Buy-Name that surcharge can
135
+ // exceed the name's own price, so a caller reading `winc` silently
136
+ // under-quotes every purchase. Surfacing `wincTotal` makes the correct
137
+ // field the obvious one.
138
+ return {
139
+ ...price,
140
+ wincTotal: price.wincTotalWithAntSpawn ?? price.winc,
141
+ };
142
+ }
143
+ /**
144
+ * Fail fast (client-side) on malformed ArNS requests so JS callers that bypass
145
+ * the compile-time intent unions get a clear `ProvidedInputError` instead of an
146
+ * opaque service 4xx. Enforces the required fields per intent:
147
+ * - `Buy-Name`: `type` ('lease' | 'permabuy'); leases also need `years`.
148
+ * `processId` is OPTIONAL — omit it to have the bundler custodially
149
+ * provision the ANT (Turbo owns it), supply it for a user-owned ANT.
150
+ * - `Extend-Lease`: positive `years`
151
+ * - `Increase-Undername-Limit`: positive `increaseQty`
152
+ * - `Upgrade-Name`: just `name`
153
+ */
154
+ validateArNSPurchaseParams(params) {
155
+ const p = params;
156
+ if (!types_js_1.arNSPurchaseIntents.includes(p.intent)) {
157
+ throw new errors_js_1.ProvidedInputError(`Invalid ArNS intent '${p.intent}'. Expected one of: ${types_js_1.arNSPurchaseIntents.join(', ')}.`);
158
+ }
159
+ if (typeof p.name !== 'string' || p.name.length === 0) {
160
+ throw new errors_js_1.ProvidedInputError('An ArNS `name` is required.');
161
+ }
162
+ const isPositiveNumber = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
163
+ switch (p.intent) {
164
+ case 'Buy-Name':
165
+ if (p.type !== 'lease' && p.type !== 'permabuy') {
166
+ throw new errors_js_1.ProvidedInputError("Buy-Name requires a `type` of 'lease' or 'permabuy'.");
167
+ }
168
+ // `processId` is optional for Buy-Name: omitting it drives the
169
+ // bundler's custodial provisioning path (Turbo spawns + owns the ANT).
170
+ // If supplied it must be a non-empty string (user-owned ANT).
171
+ if (p.processId !== undefined &&
172
+ (typeof p.processId !== 'string' || p.processId.length === 0)) {
173
+ throw new errors_js_1.ProvidedInputError('Buy-Name `processId`, when provided, must be a non-empty string (the ANT the name resolves to).');
174
+ }
175
+ if (p.type === 'lease' && !isPositiveNumber(p.years)) {
176
+ throw new errors_js_1.ProvidedInputError('A lease `Buy-Name` requires a positive `years`.');
177
+ }
178
+ break;
179
+ case 'Extend-Lease':
180
+ if (!isPositiveNumber(p.years)) {
181
+ throw new errors_js_1.ProvidedInputError('Extend-Lease requires a positive `years`.');
182
+ }
183
+ break;
184
+ case 'Increase-Undername-Limit':
185
+ if (!isPositiveNumber(p.increaseQty)) {
186
+ throw new errors_js_1.ProvidedInputError('Increase-Undername-Limit requires a positive `increaseQty`.');
187
+ }
188
+ break;
189
+ case 'Upgrade-Name':
190
+ break;
191
+ }
192
+ }
193
+ getArNSPurchaseStatus({ nonce, }) {
194
+ return this.httpService.get({
195
+ endpoint: `/arns/purchase/${nonce}`,
196
+ });
197
+ }
198
+ buildArNSPurchaseQuery(input) {
199
+ // The intent-specific union members each carry only their own fields; read
200
+ // them through a single widened view rather than narrowing per intent.
201
+ const { type, years, increaseQty, processId, paidBy } = input;
202
+ const params = new URLSearchParams();
203
+ if (type !== undefined)
204
+ params.set('type', type);
205
+ if (years !== undefined)
206
+ params.set('years', `${years}`);
207
+ if (increaseQty !== undefined)
208
+ params.set('increaseQty', `${increaseQty}`);
209
+ if (processId !== undefined)
210
+ params.set('processId', processId);
211
+ if (paidBy !== undefined) {
212
+ for (const payer of Array.isArray(paidBy) ? paidBy : [paidBy]) {
213
+ params.append('paidBy', payer);
214
+ }
215
+ }
216
+ const query = params.toString();
217
+ return query.length > 0 ? `?${query}` : '';
218
+ }
219
+ /**
220
+ * Quote a fiat (Stripe) ArNS purchase — buy a name with a credit card in one
221
+ * step, with no Turbo Credits top-up in between.
222
+ *
223
+ * Returns the recorded `purchaseQuote` (its `nonce` is what
224
+ * `getArNSPurchaseStatus` polls) plus the Stripe `paymentSession` to complete
225
+ * payment with. For `payment-intent`, confirm client-side with
226
+ * `stripe.confirmCardPayment(paymentSession.client_secret, ...)`, then poll
227
+ * the nonce until the purchase reports success or failure.
228
+ *
229
+ * Throws {@link FiatPaymentsDisabledError} when the service has Stripe turned
230
+ * off (normal in the testnet sandbox) so callers can fall back to the
231
+ * credit-paid path without string-matching a generic 503.
232
+ */
233
+ async getArNSFiatPurchaseQuote(params) {
234
+ this.validateArNSPurchaseParams(params);
235
+ const { address, currency, method = 'payment-intent', promoCodes = [], } = params;
236
+ if (typeof address !== 'string' || address.length === 0) {
237
+ throw new errors_js_1.ProvidedInputError('A destination `address` is required for a fiat ArNS purchase quote.');
238
+ }
239
+ if (!(0, types_js_1.isCurrency)(currency)) {
240
+ throw new errors_js_1.ProvidedInputError(`Invalid currency '${currency}'. Supported: ${types_js_1.fiatCurrencyTypes.join(', ')}`);
241
+ }
242
+ // Every interpolated segment is encoded. Five user-controlled values land in
243
+ // the path here, and an unencoded one (e.g. a name or address containing
244
+ // `../`) would silently retarget the request at another route.
245
+ const segments = [
246
+ method,
247
+ address,
248
+ currency,
249
+ params.intent,
250
+ params.name,
251
+ ].map((segment) => encodeURIComponent(segment));
252
+ const query = this.buildArNSFiatQuoteQuery(params, promoCodes);
253
+ try {
254
+ return await this.httpService.get({
255
+ endpoint: `/arns/quote/${segments.join('/')}${query}`,
256
+ });
257
+ }
258
+ catch (error) {
259
+ // The service returns 503 both for "Stripe is disabled" and for internal
260
+ // errors, so the body is what disambiguates them.
261
+ if (error instanceof errors_js_1.FailedRequestError &&
262
+ error.status === 503 &&
263
+ /Fiat \(Stripe\).*disabled/i.test(error.message)) {
264
+ throw new errors_js_1.FiatPaymentsDisabledError(error.message);
265
+ }
266
+ throw error;
267
+ }
268
+ }
269
+ /**
270
+ * Query string for a fiat quote. Distinct from `buildArNSPurchaseQuery`
271
+ * because this route takes `uiMode` + its paired URLs and has no `paidBy`
272
+ * (fiat has no delegated payer), and because promo codes must be REPEATED
273
+ * params here: the service reads them with `parseQueryParams`, which treats a
274
+ * comma-joined string as one code rather than several.
275
+ */
276
+ buildArNSFiatQuoteQuery(params, promoCodes) {
277
+ const { type, years, increaseQty, processId } = params;
278
+ const search = new URLSearchParams();
279
+ if (type !== undefined)
280
+ search.set('type', type);
281
+ if (years !== undefined)
282
+ search.set('years', `${years}`);
283
+ if (increaseQty !== undefined)
284
+ search.set('increaseQty', `${increaseQty}`);
285
+ if (processId !== undefined)
286
+ search.set('processId', processId);
287
+ const uiMode = params.uiMode;
288
+ if (uiMode !== undefined)
289
+ search.set('uiMode', uiMode);
290
+ if (uiMode === 'embedded') {
291
+ const { returnUrl } = params;
292
+ if (returnUrl !== undefined)
293
+ search.set('returnUrl', returnUrl);
294
+ }
295
+ else {
296
+ const { successUrl, cancelUrl } = params;
297
+ if (successUrl !== undefined)
298
+ search.set('successUrl', successUrl);
299
+ if (cancelUrl !== undefined)
300
+ search.set('cancelUrl', cancelUrl);
301
+ }
302
+ for (const code of promoCodes) {
303
+ search.append('promoCode', code);
304
+ }
305
+ const query = search.toString();
306
+ return query.length > 0 ? `?${query}` : '';
307
+ }
96
308
  appendPromoCodesToQuery(promoCodes) {
97
309
  const promoCodesQuery = promoCodes.join(',');
98
310
  return promoCodesQuery ? `promoCode=${promoCodesQuery}` : '';
@@ -144,6 +356,10 @@ class TurboUnauthenticatedPaymentService {
144
356
  return this.getCheckout(params);
145
357
  }
146
358
  async submitFundTransaction({ txId, }) {
359
+ this.logger.debug('Submitting fund transaction to Turbo...', {
360
+ txId,
361
+ url: this.url,
362
+ });
147
363
  const response = await this.httpService.post({
148
364
  endpoint: `/account/balance/${this.token}`,
149
365
  data: Buffer.from(JSON.stringify({ tx_id: txId })),
@@ -254,6 +470,272 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
254
470
  userAddress ??= await this.signer.getNativeAddress();
255
471
  return super.getBalance(userAddress);
256
472
  }
473
+ /**
474
+ * Quote a fiat (Stripe) ArNS purchase. `address` defaults to this signer's
475
+ * native address — the wallet that will own the name — so the common case
476
+ * needs no address at all. Pass one explicitly to buy on another wallet's
477
+ * behalf; the route takes the destination as a path param and requires no
478
+ * signature, which is why it is available unauthenticated too.
479
+ */
480
+ async getArNSFiatPurchaseQuote(params) {
481
+ const address = params.address ?? (await this.signer.getNativeAddress());
482
+ return super.getArNSFiatPurchaseQuote({
483
+ ...params,
484
+ address,
485
+ });
486
+ }
487
+ async getFreeStatus(userAddress) {
488
+ userAddress ??= await this.signer.getNativeAddress();
489
+ return super.getFreeStatus(userAddress);
490
+ }
491
+ /**
492
+ * The signer's OWN completed top-up history (crypto + fiat), merged newest
493
+ * first and keyset-paginated. This is a SIGNED GET: unlike `getBalance` /
494
+ * `getFreeStatus` (which name a wallet by `?address=`), payment history is
495
+ * self-scoped and returns only the rows belonging to the signing wallet — the
496
+ * service reads the address from the signature, never a query param.
497
+ *
498
+ * We sign the bare nonce (no action-binding of `limit`/`cursor`) to match the
499
+ * service's `verifySignature` middleware; the pagination params ride in the
500
+ * query string. Pass `cursor` from a prior response to fetch the next page.
501
+ */
502
+ async getPaymentHistory({ limit, cursor, } = {}) {
503
+ const headers = await this.signer.generateSignedRequestHeaders();
504
+ const query = new URLSearchParams();
505
+ if (limit !== undefined) {
506
+ query.set('limit', `${limit}`);
507
+ }
508
+ if (cursor !== undefined) {
509
+ query.set('cursor', cursor);
510
+ }
511
+ const queryString = query.toString();
512
+ return this.httpService.get({
513
+ endpoint: `/account/payments${queryString ? `?${queryString}` : ''}`,
514
+ headers,
515
+ allowedStatuses: [200],
516
+ });
517
+ }
518
+ /**
519
+ * Buy / extend / upgrade an ArNS name, paying with the signer's Turbo credit
520
+ * balance. The bundler performs the on-chain ARIO purchase and debits credits;
521
+ * a `402` (FailedRequestError.status === 402) indicates insufficient credits.
522
+ */
523
+ // ===== ArNS actions — the sponsored surface =====
524
+ //
525
+ // Every ArNS operation is an ACTION, and an action has exactly one of two
526
+ // shapes, chosen by the SERVER rather than the caller: either Turbo already
527
+ // holds the authority (`completed`), or the ANT owner must sign a transaction
528
+ // Turbo has already fee-payer-signed (`awaiting-signature`).
529
+ //
530
+ // The shape is not stable per action, which is why callers must branch on
531
+ // `status` and never on which action they asked for: `set-record` completes
532
+ // alone while Turbo is a controller, and degrades to `awaiting-signature`
533
+ // the moment the customer revokes Turbo.
534
+ //
535
+ // This replaced `/arns/purchase/{intent}/{name}`, `/arns/transfer/{antId}`
536
+ // and `/arns/manage/*`, which were deleted along with Turbo-custodial ANTs.
537
+ // Turbo now takes custody of nothing: every ANT is minted straight to the
538
+ // customer.
539
+ /**
540
+ * Create an action. Returns `completed` or `awaiting-signature`.
541
+ *
542
+ * Credits are debited HERE, not at `/sign`. Capture the returned `nonce`
543
+ * before prompting for a signature: it is the idempotency key, and polling
544
+ * it is how you resume. Never re-create an action to "retry" — that debits
545
+ * a second time. An abandoned action is refunded automatically.
546
+ */
547
+ async createArNSAction(action, params = {}, ownerProof) {
548
+ const nonce = (0, uuid_js_1.uuidV4)();
549
+ const headers = {
550
+ ...(await this.signer.generateSignedRequestHeaders(nonce)),
551
+ 'content-type': 'application/json',
552
+ };
553
+ // Record actions carry a SECOND signature, from the ANT owner's Solana key
554
+ // over a different message. It travels in its own `x-owner-*` headers
555
+ // because two signatures cannot share one header set.
556
+ if (ownerProof !== undefined) {
557
+ Object.assign(headers, await (0, arnsActions_js_1.arNSOwnerProofHeaders)(ownerProof.owner, ownerProof.message, (0, uuid_js_1.uuidV4)()));
558
+ }
559
+ try {
560
+ return await this.httpService.post({
561
+ endpoint: `/arns/actions/${action}`,
562
+ headers,
563
+ data: Buffer.from(JSON.stringify(params)),
564
+ // Non-idempotent signed write that has already debited. A blind retry
565
+ // risks paying twice for one name; poll the nonce instead.
566
+ retry: false,
567
+ });
568
+ }
569
+ catch (error) {
570
+ if (error instanceof errors_js_1.FailedRequestError && error.status === 402) {
571
+ throw new errors_js_1.InsufficientCreditsError(error.message);
572
+ }
573
+ throw error;
574
+ }
575
+ }
576
+ /**
577
+ * Submit the owner-signed transaction for an `awaiting-signature` action.
578
+ *
579
+ * `signedTransaction` is the FULL serialized transaction, base64 — not just
580
+ * the signature. Replaying a completed action returns `alreadyCompleted:
581
+ * true` rather than buying twice, so this is safe to call again if a
582
+ * response is lost.
583
+ */
584
+ async signArNSAction(nonce, signedTransaction) {
585
+ return this.httpService.post({
586
+ endpoint: `/arns/actions/${nonce}/sign`,
587
+ headers: {
588
+ ...(await this.signer.generateSignedRequestHeaders((0, uuid_js_1.uuidV4)())),
589
+ 'content-type': 'application/json',
590
+ },
591
+ data: Buffer.from(JSON.stringify({ transaction: signedTransaction })),
592
+ retry: false,
593
+ });
594
+ }
595
+ /**
596
+ * Status of an action by nonce. Open — no signature required — so it works
597
+ * from a status page or callback handler that never holds the payer's key.
598
+ *
599
+ * Terminal success carries `messageId`; terminal failure carries
600
+ * `failedDate`.
601
+ */
602
+ async getArNSActionStatus(nonce) {
603
+ return this.httpService.get({
604
+ endpoint: `/arns/actions/${nonce}`,
605
+ });
606
+ }
607
+ /**
608
+ * Run an action to a terminal state, signing if the server asks for it.
609
+ *
610
+ * This is the two-shape branch, once, in one place — so callers cannot
611
+ * hardcode which actions need a signature and break when a customer
612
+ * exercises ownership.
613
+ */
614
+ async completeArNSAction(action, params, owner, opts = {}, ownerProofMessage) {
615
+ const created = await this.createArNSAction(action, params, owner !== undefined && ownerProofMessage !== undefined
616
+ ? { owner, message: ownerProofMessage }
617
+ : undefined);
618
+ // Fires before any wallet prompt: the action is already debited, so the
619
+ // caller needs the nonce persisted even if the user walks away here.
620
+ await opts.onNonce?.(created.nonce);
621
+ if (created.status === 'completed')
622
+ return created;
623
+ if (owner === undefined) {
624
+ throw new Error(`ArNS action "${action}" requires the ANT owner's signature, but no owner signer was provided. ` +
625
+ `Pass \`owner\`, or drive createArNSAction/signArNSAction yourself. ` +
626
+ `Nonce ${created.nonce} is already debited — poll it rather than re-creating.`);
627
+ }
628
+ const signed = await owner.signTransaction(created.transaction);
629
+ return this.signArNSAction(created.nonce, signed);
630
+ }
631
+ /**
632
+ * Buy a name. The ANT is minted straight to `owner` — Turbo never holds it.
633
+ *
634
+ * This is the ONLY action that always needs the owner's signature:
635
+ * `ario_ant::initialize` is the one instruction in the whole lifecycle that
636
+ * requires the ANT owner's key. The customer signs once, here, and never
637
+ * again unless they change controllers or transfer the name.
638
+ *
639
+ * The owner needs a Solana key to sign with, NOT a funded one — Turbo pays
640
+ * every lamport of fee and rent.
641
+ */
642
+ async buyArNSName({ name, owner, type = 'lease', years, paidBy, onNonce, }) {
643
+ return this.completeArNSAction('buy-name', {
644
+ name,
645
+ ownerAddress: await owner.getAddress(),
646
+ type,
647
+ ...(years !== undefined ? { years } : {}),
648
+ ...(paidBy !== undefined ? { paidBy } : {}),
649
+ }, owner, { onNonce });
650
+ }
651
+ /** Extend a lease. Permissionless on chain — no owner signature needed. */
652
+ async extendArNSLease({ name, years, paidBy, onNonce, }) {
653
+ return this.completeArNSAction('extend-lease', { name, years, ...(paidBy !== undefined ? { paidBy } : {}) }, undefined, { onNonce });
654
+ }
655
+ /** Upgrade a lease to a permanent name. No owner signature needed. */
656
+ async upgradeArNSName({ name, paidBy, onNonce, }) {
657
+ return this.completeArNSAction('upgrade-name', { name, ...(paidBy !== undefined ? { paidBy } : {}) }, undefined, { onNonce });
658
+ }
659
+ /** Raise the undername limit. No owner signature needed. */
660
+ async increaseArNSUndernameLimit({ name, increaseQty, paidBy, onNonce, }) {
661
+ return this.completeArNSAction('increase-undername-limit', { name, increaseQty, ...(paidBy !== undefined ? { paidBy } : {}) }, undefined, { onNonce });
662
+ }
663
+ /**
664
+ * Point a name (or undername) at an Arweave transaction.
665
+ *
666
+ * Free — Turbo sponsors the Solana fee. Completes in one call while Turbo is
667
+ * a controller of the ANT, and returns `awaiting-signature` once the customer
668
+ * has revoked Turbo, at which point `owner` signs it themselves. Both paths
669
+ * are handled here.
670
+ *
671
+ * The owner proof is required EITHER WAY: Turbo is directing its own
672
+ * controller authority over an asset someone else owns, so nothing on chain
673
+ * records the owner's consent and we demand it. It is a MESSAGE signature,
674
+ * not a transaction — cheap and offline, but still a wallet prompt.
675
+ */
676
+ async setArNSRecord({ antId, owner, transactionId, undername = '@', ttlSeconds = 3600, onNonce, }) {
677
+ return this.completeArNSAction('set-record', {
678
+ antId,
679
+ ownerAddress: await owner.getAddress(),
680
+ transactionId,
681
+ undername,
682
+ ttlSeconds,
683
+ }, owner, { onNonce }, (0, arnsActions_js_1.buildArNSCustodyMessage)('set-record', [
684
+ antId,
685
+ undername,
686
+ transactionId,
687
+ String(ttlSeconds),
688
+ ]));
689
+ }
690
+ /** Remove a record (an undername). Free; same two-shape rules as setArNSRecord. */
691
+ async removeArNSRecord({ antId, owner, undername, onNonce, }) {
692
+ return this.completeArNSAction('remove-record', { antId, ownerAddress: await owner.getAddress(), undername }, owner, { onNonce }, (0, arnsActions_js_1.buildArNSCustodyMessage)('remove-record', [antId, undername]));
693
+ }
694
+ /**
695
+ * Grant controller rights on the ANT. Omit `target` for Turbo itself, which
696
+ * is what makes `setArNSRecord` a single call.
697
+ *
698
+ * Owner-signed: changing an ANT's access control is an owner-only
699
+ * instruction. Free to the customer — Turbo funds the ACL page growth.
700
+ */
701
+ async addArNSController({ antId, owner, target, onNonce, }) {
702
+ return this.completeArNSAction('add-controller', {
703
+ antId,
704
+ ownerAddress: await owner.getAddress(),
705
+ ...(target !== undefined ? { target } : {}),
706
+ }, owner, { onNonce });
707
+ }
708
+ /**
709
+ * Revoke controller rights — the escape hatch that keeps "Turbo is not a
710
+ * custodian" honest.
711
+ *
712
+ * Always available, always free, and needs nothing from Turbo but the fee.
713
+ * After revoking, `setArNSRecord` keeps working: it simply starts returning
714
+ * `awaiting-signature` so the owner signs their own record writes.
715
+ */
716
+ async removeArNSController({ antId, owner, target, onNonce, }) {
717
+ return this.completeArNSAction('remove-controller', {
718
+ antId,
719
+ ownerAddress: await owner.getAddress(),
720
+ ...(target !== undefined ? { target } : {}),
721
+ }, owner, { onNonce });
722
+ }
723
+ /**
724
+ * Hand the ANT to a new owner. Irreversible: after this lands, `owner` no
725
+ * longer controls the name. Owner-signed, and sponsored like the rest.
726
+ */
727
+ async transferArNSAnt({ antId, owner, target, onNonce, }) {
728
+ return this.completeArNSAction('transfer', { antId, ownerAddress: await owner.getAddress(), target }, owner, { onNonce });
729
+ }
730
+ /**
731
+ * Defaults to the signer's own address when `userAddress` is omitted
732
+ * (`null`/`undefined`). Passing `''` does NOT trigger this default --
733
+ * mirrors `getBalance`'s existing behavior above.
734
+ */
735
+ async getArNSNames(userAddress) {
736
+ userAddress ??= await this.signer.getNativeAddress();
737
+ return super.getArNSNames(userAddress);
738
+ }
257
739
  async getCreditShareApprovals({ userAddress, }) {
258
740
  userAddress ??= await this.signer.getNativeAddress();
259
741
  return super.getCreditShareApprovals({ userAddress });
@@ -282,9 +764,6 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
282
764
  if (!this.tokenTools) {
283
765
  throw new Error(`Token type not supported for crypto fund ${this.token}`);
284
766
  }
285
- if (this.token === 'ario') {
286
- throw new Error(`Top up with tokens is not supported for ARIO until after SOL migration is complete`);
287
- }
288
767
  if (turboCreditDestinationAddress !== undefined) {
289
768
  if ((0, common_js_1.isAnyValidUserAddress)(turboCreditDestinationAddress) === false) {
290
769
  throw new Error(`Invalid turboCreditDestinationAddress provided: ${turboCreditDestinationAddress}`);
@@ -60,7 +60,6 @@ class TurboDataItemAbstractSigner {
60
60
  case 'base-eth':
61
61
  case 'usdc':
62
62
  case 'base-usdc':
63
- case 'base-ario':
64
63
  case 'polygon-usdc':
65
64
  return (0, ethers_2.computeAddress)((0, signing_key_1.computePublicKey)((0, base64_js_1.fromB64Url)(owner)));
66
65
  case 'kyve':
@@ -73,15 +72,27 @@ class TurboDataItemAbstractSigner {
73
72
  return (0, base64_js_1.ownerToAddress)(owner);
74
73
  }
75
74
  }
76
- async generateSignedRequestHeaders() {
77
- const nonce = (0, crypto_2.randomBytes)(16).toString('hex');
78
- const buffer = Buffer.from(nonce);
75
+ async generateSignedRequestHeaders(
76
+ // Callers may supply the nonce (e.g. a UUID required by some routes); the
77
+ // nonce round-trips to the service in `x-nonce` unchanged.
78
+ nonce = (0, crypto_2.randomBytes)(16).toString('hex'),
79
+ // Optional ACTION-BINDING data prepended to the nonce for SIGNING only (not
80
+ // sent): the service reconstructs the same string from the request and
81
+ // verifies the signature over `additionalData + nonce`. This binds the
82
+ // signature to a specific operation + params so it can't be replayed against
83
+ // a different request. Omitted → signs the bare nonce (unchanged behavior).
84
+ additionalData) {
85
+ const buffer = Buffer.from((additionalData ?? '') + nonce);
79
86
  const signature = await this.signer.sign(Uint8Array.from(buffer));
80
87
  const publicKey = (0, base64_js_1.toB64Url)(this.signer.publicKey);
81
88
  return {
82
89
  'x-public-key': publicKey,
83
90
  'x-nonce': nonce,
84
91
  'x-signature': (0, base64_js_1.toB64Url)(Buffer.from(signature)),
92
+ // Advertise the signature scheme so the service verifies with the right
93
+ // algorithm. Absent this, the server defaults to Arweave and every
94
+ // non-Arweave signed request (Ethereum, Solana, …) fails verification.
95
+ 'x-signature-type': this.signer.signatureType.toString(),
85
96
  };
86
97
  }
87
98
  async getPublicKey() {
@@ -160,13 +171,23 @@ class TurboDataItemAbstractSigner {
160
171
  }
161
172
  }
162
173
  exports.TurboDataItemAbstractSigner = TurboDataItemAbstractSigner;
174
+ /**
175
+ * Builds the wallet client x402-fetch signs payment authorizations with.
176
+ *
177
+ * The chain matters because `wrapFetchWithPayment` maps `walletClient.chain.id`
178
+ * to a network name and prefers the matching entry in the service's `accepts`
179
+ * list. The upload service advertises Base mainnet (`base`), and x402 support
180
+ * here is limited to `base-usdc`, so mainnet is the correct default. Callers
181
+ * needing another network can supply their own signer via
182
+ * `X402Funding({ signer })`.
183
+ */
163
184
  async function makeX402Signer(arbundlesSigner) {
164
185
  // Node: our SDK uses EthereumSigner with a raw private key
165
186
  if (arbundlesSigner instanceof arbundles_1.EthereumSigner) {
166
187
  return (0, viem_1.createWalletClient)({
167
188
  account: (0, accounts_1.privateKeyToAccount)(('0x' +
168
189
  Buffer.from(arbundlesSigner.key).toString('hex'))),
169
- chain: chains_1.baseSepolia,
190
+ chain: chains_1.base,
170
191
  transport: (0, viem_1.http)(),
171
192
  });
172
193
  }
@@ -185,7 +206,7 @@ async function makeX402Signer(arbundlesSigner) {
185
206
  const account = accounts[0];
186
207
  return (0, viem_1.createWalletClient)({
187
208
  account,
188
- chain: chains_1.baseSepolia,
209
+ chain: chains_1.base,
189
210
  transport: (0, viem_1.custom)(provider),
190
211
  });
191
212
  }