@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
@@ -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");
@@ -37,7 +41,7 @@ class TurboUnauthenticatedPaymentService {
37
41
  }
38
42
  async getBalance(address) {
39
43
  const balance = await this.httpService.get({
40
- endpoint: `/account/balance/${this.token}?address=${address}`,
44
+ endpoint: `/account/balance/${this.token}?address=${encodeURIComponent(address)}`,
41
45
  allowedStatuses: [200, 404],
42
46
  });
43
47
  return balance.winc
@@ -50,6 +54,31 @@ class TurboUnauthenticatedPaymentService {
50
54
  receivedApprovals: [],
51
55
  };
52
56
  }
57
+ async getFreeStatus(address) {
58
+ const status = await this.httpService.get({
59
+ endpoint: `/account/free?address=${encodeURIComponent(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
+ }
53
82
  getFiatRates() {
54
83
  return this.httpService.get({
55
84
  endpoint: '/rates',
@@ -94,6 +123,206 @@ class TurboUnauthenticatedPaymentService {
94
123
  equivalentWincTokenAmount: actualPaymentAmount.toString(),
95
124
  };
96
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()}/${encodeURIComponent(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
+ * Preview the flat credits margin one of the eight non-purchase actions
145
+ * (everything except Buy-Name/Extend-Lease/Upgrade-Name/
146
+ * Increase-Undername-Limit) will debit, without creating it. Those four
147
+ * purchase actions are priced by `getArNSPriceForName` instead — their
148
+ * cost is dominated by the ARIO purchase, not this margin, so this route
149
+ * rejects them.
150
+ *
151
+ * No signature required: this mirrors `getArNSPriceForName`'s read-only,
152
+ * unauthenticated shape rather than `getArNSActionStatus`'s (which happens
153
+ * to live on the authenticated client today despite needing no signature
154
+ * either).
155
+ */
156
+ async getArNSActionPrice(action) {
157
+ return this.httpService.get({
158
+ endpoint: `/arns/actions/${action}/price`,
159
+ });
160
+ }
161
+ /**
162
+ * Fail fast (client-side) on malformed ArNS requests so JS callers that bypass
163
+ * the compile-time intent unions get a clear `ProvidedInputError` instead of an
164
+ * opaque service 4xx. Enforces the required fields per intent:
165
+ * - `Buy-Name`: `type` ('lease' | 'permabuy'); leases also need `years`.
166
+ * `processId` is OPTIONAL — omit it to have the bundler custodially
167
+ * provision the ANT (Turbo owns it), supply it for a user-owned ANT.
168
+ * - `Extend-Lease`: positive `years`
169
+ * - `Increase-Undername-Limit`: positive `increaseQty`
170
+ * - `Upgrade-Name`: just `name`
171
+ */
172
+ validateArNSPurchaseParams(params) {
173
+ const p = params;
174
+ if (!types_js_1.arNSPurchaseIntents.includes(p.intent)) {
175
+ throw new errors_js_1.ProvidedInputError(`Invalid ArNS intent '${p.intent}'. Expected one of: ${types_js_1.arNSPurchaseIntents.join(', ')}.`);
176
+ }
177
+ if (typeof p.name !== 'string' || p.name.length === 0) {
178
+ throw new errors_js_1.ProvidedInputError('An ArNS `name` is required.');
179
+ }
180
+ const isPositiveNumber = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
181
+ switch (p.intent) {
182
+ case 'Buy-Name':
183
+ if (p.type !== 'lease' && p.type !== 'permabuy') {
184
+ throw new errors_js_1.ProvidedInputError("Buy-Name requires a `type` of 'lease' or 'permabuy'.");
185
+ }
186
+ // `processId` is optional for Buy-Name: omitting it drives the
187
+ // bundler's custodial provisioning path (Turbo spawns + owns the ANT).
188
+ // If supplied it must be a non-empty string (user-owned ANT).
189
+ if (p.processId !== undefined &&
190
+ (typeof p.processId !== 'string' || p.processId.length === 0)) {
191
+ throw new errors_js_1.ProvidedInputError('Buy-Name `processId`, when provided, must be a non-empty string (the ANT the name resolves to).');
192
+ }
193
+ if (p.type === 'lease' && !isPositiveNumber(p.years)) {
194
+ throw new errors_js_1.ProvidedInputError('A lease `Buy-Name` requires a positive `years`.');
195
+ }
196
+ break;
197
+ case 'Extend-Lease':
198
+ if (!isPositiveNumber(p.years)) {
199
+ throw new errors_js_1.ProvidedInputError('Extend-Lease requires a positive `years`.');
200
+ }
201
+ break;
202
+ case 'Increase-Undername-Limit':
203
+ if (!isPositiveNumber(p.increaseQty)) {
204
+ throw new errors_js_1.ProvidedInputError('Increase-Undername-Limit requires a positive `increaseQty`.');
205
+ }
206
+ break;
207
+ case 'Upgrade-Name':
208
+ break;
209
+ }
210
+ }
211
+ getArNSPurchaseStatus({ nonce, }) {
212
+ return this.httpService.get({
213
+ endpoint: `/arns/purchase/${encodeURIComponent(nonce)}`,
214
+ });
215
+ }
216
+ buildArNSPurchaseQuery(input) {
217
+ // The intent-specific union members each carry only their own fields; read
218
+ // them through a single widened view rather than narrowing per intent.
219
+ const { type, years, increaseQty, processId, paidBy } = input;
220
+ const params = new URLSearchParams();
221
+ if (type !== undefined)
222
+ params.set('type', type);
223
+ if (years !== undefined)
224
+ params.set('years', `${years}`);
225
+ if (increaseQty !== undefined)
226
+ params.set('increaseQty', `${increaseQty}`);
227
+ if (processId !== undefined)
228
+ params.set('processId', processId);
229
+ if (paidBy !== undefined) {
230
+ for (const payer of Array.isArray(paidBy) ? paidBy : [paidBy]) {
231
+ params.append('paidBy', payer);
232
+ }
233
+ }
234
+ const query = params.toString();
235
+ return query.length > 0 ? `?${query}` : '';
236
+ }
237
+ /**
238
+ * Quote a fiat (Stripe) ArNS purchase — buy a name with a credit card in one
239
+ * step, with no Turbo Credits top-up in between.
240
+ *
241
+ * Returns the recorded `purchaseQuote` (its `nonce` is what
242
+ * `getArNSPurchaseStatus` polls) plus the Stripe `paymentSession` to complete
243
+ * payment with. For `payment-intent`, confirm client-side with
244
+ * `stripe.confirmCardPayment(paymentSession.client_secret, ...)`, then poll
245
+ * the nonce until the purchase reports success or failure.
246
+ *
247
+ * Throws {@link FiatPaymentsDisabledError} when the service has Stripe turned
248
+ * off (normal in the testnet sandbox) so callers can fall back to the
249
+ * credit-paid path without string-matching a generic 503.
250
+ */
251
+ async getArNSFiatPurchaseQuote(params) {
252
+ this.validateArNSPurchaseParams(params);
253
+ const { address, currency, method = 'payment-intent', promoCodes = [], } = params;
254
+ if (typeof address !== 'string' || address.length === 0) {
255
+ throw new errors_js_1.ProvidedInputError('A destination `address` is required for a fiat ArNS purchase quote.');
256
+ }
257
+ if (!(0, types_js_1.isCurrency)(currency)) {
258
+ throw new errors_js_1.ProvidedInputError(`Invalid currency '${currency}'. Supported: ${types_js_1.fiatCurrencyTypes.join(', ')}`);
259
+ }
260
+ // Every interpolated segment is encoded. Five user-controlled values land in
261
+ // the path here, and an unencoded one (e.g. a name or address containing
262
+ // `../`) would silently retarget the request at another route.
263
+ const segments = [
264
+ method,
265
+ address,
266
+ currency,
267
+ params.intent,
268
+ params.name,
269
+ ].map((segment) => encodeURIComponent(segment));
270
+ const query = this.buildArNSFiatQuoteQuery(params, promoCodes);
271
+ try {
272
+ return await this.httpService.get({
273
+ endpoint: `/arns/quote/${segments.join('/')}${query}`,
274
+ });
275
+ }
276
+ catch (error) {
277
+ // The service returns 503 both for "Stripe is disabled" and for internal
278
+ // errors, so the body is what disambiguates them.
279
+ if (error instanceof errors_js_1.FailedRequestError &&
280
+ error.status === 503 &&
281
+ /Fiat \(Stripe\).*disabled/i.test(error.message)) {
282
+ throw new errors_js_1.FiatPaymentsDisabledError(error.message);
283
+ }
284
+ throw error;
285
+ }
286
+ }
287
+ /**
288
+ * Query string for a fiat quote. Distinct from `buildArNSPurchaseQuery`
289
+ * because this route takes `uiMode` + its paired URLs and has no `paidBy`
290
+ * (fiat has no delegated payer), and because promo codes must be REPEATED
291
+ * params here: the service reads them with `parseQueryParams`, which treats a
292
+ * comma-joined string as one code rather than several.
293
+ */
294
+ buildArNSFiatQuoteQuery(params, promoCodes) {
295
+ const { type, years, increaseQty, processId } = params;
296
+ const search = new URLSearchParams();
297
+ if (type !== undefined)
298
+ search.set('type', type);
299
+ if (years !== undefined)
300
+ search.set('years', `${years}`);
301
+ if (increaseQty !== undefined)
302
+ search.set('increaseQty', `${increaseQty}`);
303
+ if (processId !== undefined)
304
+ search.set('processId', processId);
305
+ const uiMode = params.uiMode;
306
+ if (uiMode !== undefined)
307
+ search.set('uiMode', uiMode);
308
+ if (uiMode === 'embedded') {
309
+ const { returnUrl } = params;
310
+ if (returnUrl !== undefined)
311
+ search.set('returnUrl', returnUrl);
312
+ }
313
+ else {
314
+ const { successUrl, cancelUrl } = params;
315
+ if (successUrl !== undefined)
316
+ search.set('successUrl', successUrl);
317
+ if (cancelUrl !== undefined)
318
+ search.set('cancelUrl', cancelUrl);
319
+ }
320
+ for (const code of promoCodes) {
321
+ search.append('promoCode', code);
322
+ }
323
+ const query = search.toString();
324
+ return query.length > 0 ? `?${query}` : '';
325
+ }
97
326
  appendPromoCodesToQuery(promoCodes) {
98
327
  const promoCodesQuery = promoCodes.join(',');
99
328
  return promoCodesQuery ? `promoCode=${promoCodesQuery}` : '';
@@ -194,7 +423,7 @@ class TurboUnauthenticatedPaymentService {
194
423
  }
195
424
  async getCreditShareApprovals({ userAddress, }) {
196
425
  const response = await this.httpService.get({
197
- endpoint: `/account/approvals/get?userAddress=${userAddress}`,
426
+ endpoint: `/account/approvals/get?userAddress=${encodeURIComponent(userAddress)}`,
198
427
  allowedStatuses: [200, 404],
199
428
  });
200
429
  if (response?.givenApprovals === undefined &&
@@ -259,6 +488,323 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
259
488
  userAddress ??= await this.signer.getNativeAddress();
260
489
  return super.getBalance(userAddress);
261
490
  }
491
+ /**
492
+ * Quote a fiat (Stripe) ArNS purchase. `address` defaults to this signer's
493
+ * native address — the wallet that will own the name — so the common case
494
+ * needs no address at all. Pass one explicitly to buy on another wallet's
495
+ * behalf; the route takes the destination as a path param and requires no
496
+ * signature, which is why it is available unauthenticated too.
497
+ */
498
+ async getArNSFiatPurchaseQuote(params) {
499
+ const address = params.address ?? (await this.signer.getNativeAddress());
500
+ return super.getArNSFiatPurchaseQuote({
501
+ ...params,
502
+ address,
503
+ });
504
+ }
505
+ async getFreeStatus(userAddress) {
506
+ userAddress ??= await this.signer.getNativeAddress();
507
+ return super.getFreeStatus(userAddress);
508
+ }
509
+ /**
510
+ * The signer's OWN completed top-up history (crypto + fiat), merged newest
511
+ * first and keyset-paginated. This is a SIGNED GET: unlike `getBalance` /
512
+ * `getFreeStatus` (which name a wallet by `?address=`), payment history is
513
+ * self-scoped and returns only the rows belonging to the signing wallet — the
514
+ * service reads the address from the signature, never a query param.
515
+ *
516
+ * We sign the bare nonce (no action-binding of `limit`/`cursor`) to match the
517
+ * service's `verifySignature` middleware; the pagination params ride in the
518
+ * query string. Pass `cursor` from a prior response to fetch the next page.
519
+ */
520
+ async getPaymentHistory({ limit, cursor, } = {}) {
521
+ const headers = await this.signer.generateSignedRequestHeaders();
522
+ const query = new URLSearchParams();
523
+ if (limit !== undefined) {
524
+ query.set('limit', `${limit}`);
525
+ }
526
+ if (cursor !== undefined) {
527
+ query.set('cursor', cursor);
528
+ }
529
+ const queryString = query.toString();
530
+ return this.httpService.get({
531
+ endpoint: `/account/payments${queryString ? `?${queryString}` : ''}`,
532
+ headers,
533
+ allowedStatuses: [200],
534
+ });
535
+ }
536
+ /**
537
+ * Buy / extend / upgrade an ArNS name, paying with the signer's Turbo credit
538
+ * balance. The bundler performs the on-chain ARIO purchase and debits credits;
539
+ * a `402` (FailedRequestError.status === 402) indicates insufficient credits.
540
+ */
541
+ // ===== ArNS actions — the sponsored surface =====
542
+ //
543
+ // Every ArNS operation is an ACTION, and an action has exactly one of two
544
+ // shapes, chosen by the SERVER rather than the caller: either Turbo already
545
+ // holds the authority (`completed`), or the ANT owner must sign a transaction
546
+ // Turbo has already fee-payer-signed (`awaiting-signature`).
547
+ //
548
+ // The shape is not stable per action, which is why callers must branch on
549
+ // `status` and never on which action they asked for: `set-record` completes
550
+ // alone while Turbo is a controller, and degrades to `awaiting-signature`
551
+ // the moment the customer revokes Turbo.
552
+ //
553
+ // This replaced `/arns/purchase/{intent}/{name}`, `/arns/transfer/{antId}`
554
+ // and `/arns/manage/*`, which were deleted along with Turbo-custodial ANTs.
555
+ // Turbo now takes custody of nothing: every ANT is minted straight to the
556
+ // customer.
557
+ /**
558
+ * Create an action. Returns `completed` or `awaiting-signature`.
559
+ *
560
+ * Credits are debited HERE, not at `/sign`. Capture the returned `nonce`
561
+ * before prompting for a signature: it is the idempotency key, and polling
562
+ * it is how you resume. Never re-create an action to "retry" — that debits
563
+ * a second time. An abandoned action is refunded automatically.
564
+ */
565
+ async createArNSAction(action, params = {}, ownerProof) {
566
+ const nonce = (0, uuid_js_1.uuidV4)();
567
+ const headers = {
568
+ ...(await this.signer.generateSignedRequestHeaders(nonce)),
569
+ 'content-type': 'application/json',
570
+ };
571
+ // Record actions carry a SECOND signature, from the ANT owner's Solana key
572
+ // over a different message. It travels in its own `x-owner-*` headers
573
+ // because two signatures cannot share one header set.
574
+ if (ownerProof !== undefined) {
575
+ Object.assign(headers, await (0, arnsActions_js_1.arNSOwnerProofHeaders)(ownerProof.owner, ownerProof.message, (0, uuid_js_1.uuidV4)()));
576
+ }
577
+ try {
578
+ return await this.httpService.post({
579
+ endpoint: `/arns/actions/${action}`,
580
+ headers,
581
+ data: Buffer.from(JSON.stringify(params)),
582
+ // Non-idempotent signed write that has already debited. A blind retry
583
+ // risks paying twice for one name; poll the nonce instead.
584
+ retry: false,
585
+ });
586
+ }
587
+ catch (error) {
588
+ if (error instanceof errors_js_1.FailedRequestError && error.status === 402) {
589
+ throw new errors_js_1.InsufficientCreditsError(error.message);
590
+ }
591
+ throw error;
592
+ }
593
+ }
594
+ /**
595
+ * Submit the owner-signed transaction for an `awaiting-signature` action.
596
+ *
597
+ * `signedTransaction` is the FULL serialized transaction, base64 — not just
598
+ * the signature. Replaying a completed action returns `alreadyCompleted:
599
+ * true` rather than buying twice, so this is safe to call again if a
600
+ * response is lost.
601
+ */
602
+ async signArNSAction(nonce, signedTransaction) {
603
+ return this.httpService.post({
604
+ endpoint: `/arns/actions/${nonce}/sign`,
605
+ headers: {
606
+ ...(await this.signer.generateSignedRequestHeaders((0, uuid_js_1.uuidV4)())),
607
+ 'content-type': 'application/json',
608
+ },
609
+ data: Buffer.from(JSON.stringify({ transaction: signedTransaction })),
610
+ retry: false,
611
+ });
612
+ }
613
+ /**
614
+ * Status of an action by nonce. Open — no signature required — so it works
615
+ * from a status page or callback handler that never holds the payer's key.
616
+ *
617
+ * Terminal success carries `messageId`; terminal failure carries
618
+ * `failedDate`.
619
+ */
620
+ async getArNSActionStatus(nonce) {
621
+ return this.httpService.get({
622
+ endpoint: `/arns/actions/${nonce}`,
623
+ });
624
+ }
625
+ /**
626
+ * Run an action to a terminal state, signing if the server asks for it.
627
+ *
628
+ * This is the two-shape branch, once, in one place — so callers cannot
629
+ * hardcode which actions need a signature and break when a customer
630
+ * exercises ownership.
631
+ */
632
+ async completeArNSAction(action, params, owner, opts = {}, ownerProofMessage) {
633
+ const created = await this.createArNSAction(action, params, owner !== undefined && ownerProofMessage !== undefined
634
+ ? { owner, message: ownerProofMessage }
635
+ : undefined);
636
+ // Fires before any wallet prompt: the action is already debited, so the
637
+ // caller needs the nonce persisted even if the user walks away here.
638
+ await opts.onNonce?.(created.nonce);
639
+ if (created.status === 'completed')
640
+ return created;
641
+ if (owner === undefined) {
642
+ throw new Error(`ArNS action "${action}" requires the ANT owner's signature, but no owner signer was provided. ` +
643
+ `Pass \`owner\`, or drive createArNSAction/signArNSAction yourself. ` +
644
+ `Nonce ${created.nonce} is already debited — poll it rather than re-creating.`);
645
+ }
646
+ const signed = await owner.signTransaction(created.transaction);
647
+ return this.signArNSAction(created.nonce, signed);
648
+ }
649
+ /**
650
+ * Buy a name. The ANT is minted straight to `owner` — Turbo never holds it.
651
+ *
652
+ * This is the ONLY action that always needs the owner's signature:
653
+ * `ario_ant::initialize` is the one instruction in the whole lifecycle that
654
+ * requires the ANT owner's key. The customer signs once, here, and never
655
+ * again unless they change controllers or transfer the name.
656
+ *
657
+ * The owner needs a Solana key to sign with, NOT a funded one — Turbo pays
658
+ * every lamport of fee and rent.
659
+ */
660
+ async buyArNSName({ name, owner, type = 'lease', years, paidBy, onNonce, }) {
661
+ return this.completeArNSAction('buy-name', {
662
+ name,
663
+ ownerAddress: await owner.getAddress(),
664
+ type,
665
+ ...(years !== undefined ? { years } : {}),
666
+ ...(paidBy !== undefined ? { paidBy } : {}),
667
+ }, owner, { onNonce });
668
+ }
669
+ /** Extend a lease. Permissionless on chain — no owner signature needed. */
670
+ async extendArNSLease({ name, years, paidBy, onNonce, }) {
671
+ return this.completeArNSAction('extend-lease', { name, years, ...(paidBy !== undefined ? { paidBy } : {}) }, undefined, { onNonce });
672
+ }
673
+ /** Upgrade a lease to a permanent name. No owner signature needed. */
674
+ async upgradeArNSName({ name, paidBy, onNonce, }) {
675
+ return this.completeArNSAction('upgrade-name', { name, ...(paidBy !== undefined ? { paidBy } : {}) }, undefined, { onNonce });
676
+ }
677
+ /** Raise the undername limit. No owner signature needed. */
678
+ async increaseArNSUndernameLimit({ name, increaseQty, paidBy, onNonce, }) {
679
+ return this.completeArNSAction('increase-undername-limit', { name, increaseQty, ...(paidBy !== undefined ? { paidBy } : {}) }, undefined, { onNonce });
680
+ }
681
+ /**
682
+ * Point a name (or undername) at an Arweave transaction.
683
+ *
684
+ * Costs a small credit margin — never SOL, which Turbo sponsors. Completes in one call while Turbo is
685
+ * a controller of the ANT, and returns `awaiting-signature` once the customer
686
+ * has revoked Turbo, at which point `owner` signs it themselves. Both paths
687
+ * are handled here.
688
+ *
689
+ * The owner proof is required EITHER WAY: Turbo is directing its own
690
+ * controller authority over an asset someone else owns, so nothing on chain
691
+ * records the owner's consent and we demand it. It is a MESSAGE signature,
692
+ * not a transaction — cheap and offline, but still a wallet prompt.
693
+ */
694
+ async setArNSRecord({ antId, owner, transactionId, undername = '@', ttlSeconds = 3600, onNonce, }) {
695
+ return this.completeArNSAction('set-record', {
696
+ antId,
697
+ ownerAddress: await owner.getAddress(),
698
+ transactionId,
699
+ undername,
700
+ ttlSeconds,
701
+ }, owner, { onNonce }, (0, arnsActions_js_1.buildArNSCustodyMessage)('set-record', [
702
+ antId,
703
+ undername,
704
+ transactionId,
705
+ String(ttlSeconds),
706
+ ]));
707
+ }
708
+ /** Remove a record (an undername). Costs credits, never SOL. */
709
+ async removeArNSRecord({ antId, owner, undername, onNonce, }) {
710
+ return this.completeArNSAction('remove-record', { antId, ownerAddress: await owner.getAddress(), undername }, owner, { onNonce }, (0, arnsActions_js_1.buildArNSCustodyMessage)('remove-record', [antId, undername]));
711
+ }
712
+ /**
713
+ * Edit a RECORD's metadata — its display name, logo, description, keywords.
714
+ *
715
+ * Costs a small credit margin (never SOL), and is owner-or-controller on
716
+ * chain, so it behaves exactly like
717
+ * {@link setArNSRecord}: Turbo-alone while it is a controller, owner-signed
718
+ * after a revoke.
719
+ *
720
+ * Fields are TRI-STATE. Omit one to leave it unchanged; pass `null` to clear
721
+ * it. Those are bound distinctly by the owner proof, so "clear the
722
+ * description" and "set it to empty" are different authorizations.
723
+ *
724
+ * Note this is RECORD metadata. ANT-level metadata (the ANT's own name,
725
+ * ticker, description, keywords, logo) is NOT sponsored and stays on the
726
+ * direct-signer path via `@ar.io/sdk`.
727
+ */
728
+ async setArNSRecordMetadata({ antId, owner, undername = '@', displayName, recordLogo, recordDescription, recordKeywords, onNonce, }) {
729
+ return this.completeArNSAction('set-record-metadata', {
730
+ antId,
731
+ ownerAddress: await owner.getAddress(),
732
+ undername,
733
+ // Sent explicitly, including `null`, so the server sees the same
734
+ // tri-state the proof was signed over.
735
+ ...(displayName !== undefined ? { displayName } : {}),
736
+ ...(recordLogo !== undefined ? { recordLogo } : {}),
737
+ ...(recordDescription !== undefined ? { recordDescription } : {}),
738
+ ...(recordKeywords !== undefined ? { recordKeywords } : {}),
739
+ }, owner, { onNonce }, (0, arnsActions_js_1.buildArNSCustodyMessage)('set-record-metadata', [
740
+ antId,
741
+ undername,
742
+ (0, arnsActions_js_1.arNSMetadataField)(displayName),
743
+ (0, arnsActions_js_1.arNSMetadataField)(recordLogo),
744
+ (0, arnsActions_js_1.arNSMetadataField)(recordDescription),
745
+ (0, arnsActions_js_1.arNSKeywordsField)(recordKeywords),
746
+ ]));
747
+ }
748
+ /** Clear a record's metadata. Costs credits, never SOL; same two-shape rules. */
749
+ async removeArNSRecordMetadata({ antId, owner, undername, onNonce, }) {
750
+ return this.completeArNSAction('remove-record-metadata', { antId, ownerAddress: await owner.getAddress(), undername }, owner, { onNonce }, (0, arnsActions_js_1.buildArNSCustodyMessage)('remove-record-metadata', [antId, undername]));
751
+ }
752
+ /**
753
+ * Hand ONE record to another address.
754
+ *
755
+ * Distinct from {@link transferArNSAnt}, which hands over the whole ANT and
756
+ * every record on it. Confusing the two gives away far more than intended.
757
+ */
758
+ async transferArNSRecord({ antId, owner, undername, target, onNonce, }) {
759
+ return this.completeArNSAction('transfer-record', { antId, ownerAddress: await owner.getAddress(), undername, target }, owner, { onNonce }, (0, arnsActions_js_1.buildArNSCustodyMessage)('transfer-record', [antId, undername, target]));
760
+ }
761
+ /**
762
+ * Grant controller rights on the ANT. Omit `target` for Turbo itself, which
763
+ * is what makes `setArNSRecord` a single call.
764
+ *
765
+ * Owner-signed: changing an ANT's access control is an owner-only
766
+ * instruction. Costs a small credit margin; Turbo funds the ACL page growth
767
+ * in SOL.
768
+ */
769
+ async addArNSController({ antId, owner, target, onNonce, }) {
770
+ return this.completeArNSAction('add-controller', {
771
+ antId,
772
+ ownerAddress: await owner.getAddress(),
773
+ ...(target !== undefined ? { target } : {}),
774
+ }, owner, { onNonce });
775
+ }
776
+ /**
777
+ * Revoke controller rights — the escape hatch that keeps "Turbo is not a
778
+ * custodian" honest.
779
+ *
780
+ * Always available, and needs nothing from Turbo but the fee. Costs a small
781
+ * credit margin rather than SOL.
782
+ * After revoking, `setArNSRecord` keeps working: it simply starts returning
783
+ * `awaiting-signature` so the owner signs their own record writes.
784
+ */
785
+ async removeArNSController({ antId, owner, target, onNonce, }) {
786
+ return this.completeArNSAction('remove-controller', {
787
+ antId,
788
+ ownerAddress: await owner.getAddress(),
789
+ ...(target !== undefined ? { target } : {}),
790
+ }, owner, { onNonce });
791
+ }
792
+ /**
793
+ * Hand the ANT to a new owner. Irreversible: after this lands, `owner` no
794
+ * longer controls the name. Owner-signed, and sponsored like the rest.
795
+ */
796
+ async transferArNSAnt({ antId, owner, target, onNonce, }) {
797
+ return this.completeArNSAction('transfer', { antId, ownerAddress: await owner.getAddress(), target }, owner, { onNonce });
798
+ }
799
+ /**
800
+ * Defaults to the signer's own address when `userAddress` is omitted
801
+ * (`null`/`undefined`). Passing `''` does NOT trigger this default --
802
+ * mirrors `getBalance`'s existing behavior above.
803
+ */
804
+ async getArNSNames(userAddress) {
805
+ userAddress ??= await this.signer.getNativeAddress();
806
+ return super.getArNSNames(userAddress);
807
+ }
262
808
  async getCreditShareApprovals({ userAddress, }) {
263
809
  userAddress ??= await this.signer.getNativeAddress();
264
810
  return super.getCreditShareApprovals({ userAddress });
@@ -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
  }