@ardrive/turbo-sdk 1.42.0-alpha.8 → 1.42.0-alpha.9

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.
@@ -31,10 +31,26 @@ exports.removeArNSRecord = removeArNSRecord;
31
31
  * limitations under the License.
32
32
  */
33
33
  const bignumber_js_1 = require("bignumber.js");
34
+ const arnsActions_js_1 = require("../../common/arnsActions.js");
34
35
  const factory_js_1 = require("../../node/factory.js");
35
36
  const errors_js_1 = require("../../utils/errors.js");
36
37
  const constants_js_1 = require("../constants.js");
37
38
  const utils_js_1 = require("../utils.js");
39
+ /**
40
+ * The ANT owner's Solana key.
41
+ *
42
+ * Deliberately separate from the wallet that pays: the payer holds Turbo
43
+ * credits (and may be Arweave or Ethereum), while the owner holds the ANT and
44
+ * must be Solana. The owner needs a key to SIGN with, not a funded account —
45
+ * Turbo is the fee payer on every sponsored action.
46
+ */
47
+ function ownerFromOptions(options) {
48
+ if (options.ownerKey === undefined || options.ownerKey === '') {
49
+ throw new Error('Must provide --owner-key (a base58 Solana secret key) — it owns the ANT and signs for it. ' +
50
+ 'This is separate from the wallet paying in Turbo Credits.');
51
+ }
52
+ return (0, arnsActions_js_1.solanaOwnerSigner)(options.ownerKey);
53
+ }
38
54
  function requiredNameFromOptions(options) {
39
55
  if (options.name === undefined || options.name.length === 0) {
40
56
  throw new Error('Must provide an ArNS --name');
@@ -128,10 +144,12 @@ async function withCreditErrorMapping(fn) {
128
144
  }
129
145
  function logPurchaseResult(action, result) {
130
146
  console.log(JSON.stringify({
131
- message: `${action} submitted!`,
147
+ message: `${action} completed!`,
132
148
  nonce: result.nonce,
133
- arioWriteId: result.arioWriteResult?.id,
134
- purchaseReceipt: result.purchaseReceipt,
149
+ antId: result.antId,
150
+ // Solana transaction id of the on-chain write.
151
+ messageId: result.messageId,
152
+ ...(result.alreadyCompleted === true ? { alreadyCompleted: true } : {}),
135
153
  }, null, 2));
136
154
  console.log(`\nTrack this purchase with:\n turbo arns-purchase-status --nonce ${result.nonce}`);
137
155
  }
@@ -198,20 +216,19 @@ async function buyArNSName(options, turbo) {
198
216
  const name = requiredNameFromOptions(options);
199
217
  const type = typeFromOptions(options.type);
200
218
  const paidBy = paidByFromArNSOptions(options.paidBy);
201
- // `--process-id` is OPTIONAL: omit it to have Turbo custodially provision the
202
- // ANT (Turbo spawns + owns it Model A); supply it to point the name at a
203
- // user-owned ANT (Model B). When omitted, no `processId` is sent.
204
- const processId = options.processId;
219
+ // Every buy mints a fresh ANT straight to `--owner-key`; Turbo never holds
220
+ // it, and there is no bring-your-own-ANT path any more.
221
+ const owner = ownerFromOptions(options);
205
222
  const client = turbo ?? (await (0, utils_js_1.turboFromOptions)(options));
206
223
  const result = await withCreditErrorMapping(() => type === 'lease'
207
224
  ? client.buyArNSName({
208
225
  name,
226
+ owner,
209
227
  type: 'lease',
210
228
  years: positiveIntFromOption(options.years, '--years'),
211
- processId,
212
229
  paidBy,
213
230
  })
214
- : client.buyArNSName({ name, type: 'permabuy', processId, paidBy }));
231
+ : client.buyArNSName({ name, owner, type: 'permabuy', paidBy }));
215
232
  logPurchaseResult('ArNS name purchase', result);
216
233
  }
217
234
  async function extendArNSLease(options, turbo) {
@@ -260,6 +277,7 @@ async function transferArNSAnt(options, turbo) {
260
277
  const client = turbo ?? (await (0, utils_js_1.turboFromOptions)(options));
261
278
  const result = await client.transferArNSAnt({
262
279
  antId: options.antId,
280
+ owner: ownerFromOptions(options),
263
281
  target: options.target,
264
282
  });
265
283
  console.log(JSON.stringify({ message: 'ANT transfer submitted!', ...result }, null, 2));
@@ -275,6 +293,7 @@ async function setArNSRecord(options, turbo) {
275
293
  const client = turbo ?? (await (0, utils_js_1.turboFromOptions)(options));
276
294
  const result = await client.setArNSRecord({
277
295
  antId: options.antId,
296
+ owner: ownerFromOptions(options),
278
297
  undername: options.undername ?? '@',
279
298
  transactionId: options.transactionId,
280
299
  ttlSeconds,
@@ -291,6 +310,7 @@ async function removeArNSRecord(options, turbo) {
291
310
  const client = turbo ?? (await (0, utils_js_1.turboFromOptions)(options));
292
311
  const result = await client.removeArNSRecord({
293
312
  antId: options.antId,
313
+ owner: ownerFromOptions(options),
294
314
  undername: options.undername,
295
315
  });
296
316
  console.log(JSON.stringify({ message: 'ArNS record removed!', ...result }, null, 2));
@@ -206,9 +206,9 @@ exports.optionMap = {
206
206
  alias: '--increase-qty <qty>',
207
207
  description: 'Number of additional undernames (Increase-Undername-Limit)',
208
208
  },
209
- arnsProcessId: {
210
- alias: '--process-id <processId>',
211
- description: 'ANT process ID the ArNS name resolves to (Buy-Name). Optional: omit for Turbo custodial provisioning (Turbo owns the ANT); supply for a user-owned ANT',
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
212
  },
213
213
  arnsNonce: {
214
214
  alias: '--nonce <nonce>',
@@ -302,14 +302,12 @@ exports.arnsPriceOptions = [
302
302
  exports.optionMap.arnsType,
303
303
  exports.optionMap.arnsYears,
304
304
  exports.optionMap.arnsIncreaseQty,
305
- exports.optionMap.arnsProcessId,
306
305
  ];
307
306
  exports.arnsFiatQuoteOptions = [
308
307
  exports.optionMap.arnsName,
309
308
  exports.optionMap.arnsType,
310
309
  exports.optionMap.arnsYears,
311
310
  exports.optionMap.arnsIncreaseQty,
312
- exports.optionMap.arnsProcessId,
313
311
  exports.optionMap.address,
314
312
  exports.optionMap.currency,
315
313
  {
@@ -326,7 +324,7 @@ exports.buyArNSNameOptions = [
326
324
  exports.optionMap.arnsName,
327
325
  exports.optionMap.arnsType,
328
326
  exports.optionMap.arnsYears,
329
- exports.optionMap.arnsProcessId,
327
+ exports.optionMap.arnsOwnerKey,
330
328
  exports.optionMap.paidBy,
331
329
  ];
332
330
  exports.extendArNSLeaseOptions = [
@@ -349,11 +347,13 @@ exports.upgradeArNSNameOptions = [
349
347
  exports.arnsPurchaseStatusOptions = [exports.optionMap.arnsNonce];
350
348
  exports.transferArNSAntOptions = [
351
349
  ...exports.walletOptions,
350
+ exports.optionMap.arnsOwnerKey,
352
351
  exports.optionMap.arnsAntId,
353
352
  exports.optionMap.arnsTarget,
354
353
  ];
355
354
  exports.setArNSRecordOptions = [
356
355
  ...exports.walletOptions,
356
+ exports.optionMap.arnsOwnerKey,
357
357
  exports.optionMap.arnsAntId,
358
358
  exports.optionMap.arnsUndername,
359
359
  exports.optionMap.arnsTransactionId,
@@ -361,6 +361,7 @@ exports.setArNSRecordOptions = [
361
361
  ];
362
362
  exports.removeArNSRecordOptions = [
363
363
  ...exports.walletOptions,
364
+ exports.optionMap.arnsOwnerKey,
364
365
  exports.optionMap.arnsAntId,
365
366
  exports.optionMap.arnsUndername,
366
367
  ];
@@ -0,0 +1,102 @@
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.arNSOwnerProofHeaders = arNSOwnerProofHeaders;
8
+ exports.solanaOwnerSigner = solanaOwnerSigner;
9
+ exports.emptySignatureSlots = emptySignatureSlots;
10
+ /**
11
+ * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
12
+ *
13
+ * Licensed under the Apache License, Version 2.0 (the "License");
14
+ * you may not use this file except in compliance with the License.
15
+ * You may obtain a copy of the License at
16
+ *
17
+ * http://www.apache.org/licenses/LICENSE-2.0
18
+ *
19
+ * Unless required by applicable law or agreed to in writing, software
20
+ * distributed under the License is distributed on an "AS IS" BASIS,
21
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22
+ * See the License for the specific language governing permissions and
23
+ * limitations under the License.
24
+ */
25
+ const web3_js_1 = require("@solana/web3.js");
26
+ const bs58_1 = __importDefault(require("bs58"));
27
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
28
+ const base64_js_1 = require("../utils/base64.js");
29
+ /**
30
+ * Canonical ACTION-BOUND message for the owner proof.
31
+ *
32
+ * MUST match the bundler's `buildArNSCustodyMessage` byte for byte
33
+ * (newline-delimited) or every signature is rejected. The bundler rebuilds this
34
+ * from the request and verifies the signature over `message + nonce`, so a
35
+ * signature captured for one operation cannot authorize a different one, and
36
+ * the nonce is consumed on use so the same one cannot be replayed (e.g. to
37
+ * revert a record to an older value).
38
+ */
39
+ function buildArNSCustodyMessage(action, fields) {
40
+ return ['arns', action, ...fields].join('\n');
41
+ }
42
+ /** Solana's signature-type discriminator in Turbo's signed-request scheme. */
43
+ const SOLANA_SIGNATURE_TYPE = 4;
44
+ /**
45
+ * The ANT owner's half of the envelope, in its own `x-owner-*` headers.
46
+ *
47
+ * Two signatures travel on a record action — the PAYER's signed request over
48
+ * `"" + nonce`, and the OWNER's action-bound proof over `message + nonce` —
49
+ * from two different keys, usually on two different chains. They cannot share
50
+ * one header set: whichever verifier ran second would reject a signature that
51
+ * was never meant for it.
52
+ */
53
+ async function arNSOwnerProofHeaders(owner, message, nonce) {
54
+ const address = await owner.getAddress();
55
+ const signature = await owner.signMessage(Uint8Array.from(Buffer.from(message + nonce)));
56
+ return {
57
+ // A Solana address IS the base58-encoded 32-byte ed25519 public key, so
58
+ // decoding the address recovers exactly the bytes the bundler verifies
59
+ // against. Keeps ArNSOwnerSigner minimal — no separate getPublicKey().
60
+ 'x-owner-public-key': (0, base64_js_1.toB64Url)(Buffer.from(bs58_1.default.decode(address))),
61
+ 'x-owner-nonce': nonce,
62
+ 'x-owner-signature': (0, base64_js_1.toB64Url)(Buffer.from(signature)),
63
+ 'x-owner-signature-type': String(SOLANA_SIGNATURE_TYPE),
64
+ };
65
+ }
66
+ /**
67
+ * Build an {@link ArNSOwnerSigner} from a raw Solana secret key.
68
+ *
69
+ * For servers and tests. A browser wallet (Phantom, Solflare, or a console's
70
+ * embedded wallet) should implement the interface directly against its own
71
+ * `signTransaction` / `signMessage` rather than exposing a secret key.
72
+ *
73
+ * Note the owner needs a key to SIGN with, not a funded account: Turbo is the
74
+ * fee payer on every sponsored action, so this wallet's SOL balance can stay
75
+ * at zero for the entire life of the name.
76
+ */
77
+ function solanaOwnerSigner(secretKey) {
78
+ const keypair = web3_js_1.Keypair.fromSecretKey(typeof secretKey === 'string' ? bs58_1.default.decode(secretKey) : secretKey);
79
+ return {
80
+ getAddress: () => keypair.publicKey.toBase58(),
81
+ signTransaction: async (transactionBase64) => {
82
+ const tx = web3_js_1.VersionedTransaction.deserialize(Buffer.from(transactionBase64, 'base64'));
83
+ // Sign the bytes as returned. Turbo's fee-payer signature already covers
84
+ // this exact message; rebuilding it from parts invalidates that and the
85
+ // submission is rejected.
86
+ tx.sign([keypair]);
87
+ return Buffer.from(tx.serialize()).toString('base64');
88
+ },
89
+ signMessage: async (message) => tweetnacl_1.default.sign.detached(message, keypair.secretKey),
90
+ };
91
+ }
92
+ /**
93
+ * Count the signature slots a prepared transaction still has empty.
94
+ *
95
+ * Turbo pre-signs as fee payer and leaves exactly one slot for the owner, so a
96
+ * client can assert that before prompting a wallet — a cheap way to catch a
97
+ * malformed or already-signed transaction before bothering the user.
98
+ */
99
+ function emptySignatureSlots(transactionBase64) {
100
+ const tx = web3_js_1.VersionedTransaction.deserialize(Buffer.from(transactionBase64, 'base64'));
101
+ return tx.signatures.filter((sig) => sig.every((byte) => byte === 0)).length;
102
+ }
@@ -21,6 +21,7 @@ const types_js_1 = require("../types.js");
21
21
  const common_js_1 = require("../utils/common.js");
22
22
  const errors_js_1 = require("../utils/errors.js");
23
23
  const uuid_js_1 = require("../utils/uuid.js");
24
+ const arnsActions_js_1 = require("./arnsActions.js");
24
25
  const http_js_1 = require("./http.js");
25
26
  const http_js_2 = require("./http.js");
26
27
  const logger_js_1 = require("./logger.js");
@@ -126,9 +127,18 @@ class TurboUnauthenticatedPaymentService {
126
127
  // `async` so a validation failure surfaces as a rejected promise (consistent
127
128
  // with `purchaseArNSName`) rather than a synchronous throw.
128
129
  this.validateArNSPurchaseParams(params);
129
- return this.httpService.get({
130
+ const price = await this.httpService.get({
130
131
  endpoint: `/arns/price/${params.intent.toLowerCase()}/${params.name}${this.buildArNSPurchaseQuery(params)}`,
131
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
+ };
132
142
  }
133
143
  /**
134
144
  * Fail fast (client-side) on malformed ArNS requests so JS callers that bypass
@@ -510,112 +520,212 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
510
520
  * balance. The bundler performs the on-chain ARIO purchase and debits credits;
511
521
  * a `402` (FailedRequestError.status === 402) indicates insufficient credits.
512
522
  */
513
- async purchaseArNSName(params) {
514
- this.validateArNSPurchaseParams(params);
515
- // The bundler requires the signed nonce to be a UUID; it also doubles as
516
- // the idempotency + status-lookup key (`getArNSPurchaseStatus`).
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) {
517
548
  const nonce = (0, uuid_js_1.uuidV4)();
518
- const headers = await this.signer.generateSignedRequestHeaders(nonce);
519
- let response;
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
+ }
520
559
  try {
521
- response = await this.httpService.post({
522
- endpoint: `/arns/purchase/${params.intent.toLowerCase()}/${params.name}${this.buildArNSPurchaseQuery(params)}`,
560
+ return await this.httpService.post({
561
+ endpoint: `/arns/actions/${action}`,
523
562
  headers,
524
- // Params travel in the query string + signed headers; the service reads
525
- // no body, but the HTTP layer requires a `data` field.
526
- data: Buffer.from([]),
527
- // Non-idempotent signed write: the nonce is single-use, so a retried
528
- // (but already-landed) purchase would 4xx as "already exists". Poll
529
- // status by nonce instead of retrying.
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.
530
566
  retry: false,
531
567
  });
532
568
  }
533
569
  catch (error) {
534
- // Surface a credit shortfall as a typed, catchable error so callers can
535
- // prompt a top-up. The `nonce` is the idempotency key: after topping up,
536
- // retry the same purchase (a fresh nonce is fine — the service dedupes by
537
- // the on-chain effect, and a captured nonce lets you poll status).
538
570
  if (error instanceof errors_js_1.FailedRequestError && error.status === 402) {
539
571
  throw new errors_js_1.InsufficientCreditsError(error.message);
540
572
  }
541
573
  throw error;
542
574
  }
543
- // Normalize both nonce fields to the one we signed so callers can poll with
544
- // either `response.nonce` or `response.purchaseReceipt.nonce`.
545
- return {
546
- ...response,
547
- nonce,
548
- purchaseReceipt: { ...response.purchaseReceipt, nonce },
549
- };
550
575
  }
551
- buyArNSName(params) {
552
- return this.purchaseArNSName({
553
- ...params,
554
- intent: 'Buy-Name',
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,
555
593
  });
556
594
  }
557
- extendArNSLease(params) {
558
- return this.purchaseArNSName({ ...params, intent: 'Extend-Lease' });
559
- }
560
- increaseArNSUndernameLimit(params) {
561
- return this.purchaseArNSName({
562
- ...params,
563
- intent: 'Increase-Undername-Limit',
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}`,
564
605
  });
565
606
  }
566
- upgradeArNSName(params) {
567
- return this.purchaseArNSName({ ...params, intent: 'Upgrade-Name' });
568
- }
569
- // ---- ArNS ANT custody: self-custody exit + manage records ----
570
- // Canonical ACTION-BOUND message. MUST match the bundler's
571
- // buildArNSCustodyMessage byte-for-byte (newline-delimited) or every signature
572
- // is rejected. The bundler reconstructs this from the request and verifies the
573
- // signature over `message + nonce`, so a captured signature can't be replayed
574
- // against a different operation/params.
575
- buildArNSCustodyMessage(action, fields) {
576
- return ['arns', action, ...fields].join('\n');
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);
577
630
  }
578
631
  /**
579
- * Self-custody exit: move a Turbo-custodied ANT to a Solana pubkey you control.
580
- * Authenticated with an action-bound, single-use signature.
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.
581
641
  */
582
- async transferArNSAnt({ antId, target, }) {
583
- const nonce = (0, uuid_js_1.uuidV4)();
584
- const headers = await this.signer.generateSignedRequestHeaders(nonce, this.buildArNSCustodyMessage('transfer', [antId, target]));
585
- return this.httpService.post({
586
- endpoint: `/arns/transfer/${antId}?target=${encodeURIComponent(target)}`,
587
- headers,
588
- data: Buffer.from([]),
589
- retry: false, // single-use action-bound nonce; don't re-POST on 5xx
590
- });
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 });
591
662
  }
592
- /** Set a resolution record on a custodied ANT (undername defaults to '@'). */
593
- async setArNSRecord({ antId, undername = '@', transactionId, ttlSeconds, }) {
594
- const nonce = (0, uuid_js_1.uuidV4)();
595
- const headers = await this.signer.generateSignedRequestHeaders(nonce, this.buildArNSCustodyMessage('set-record', [
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', [
596
684
  antId,
597
685
  undername,
598
686
  transactionId,
599
687
  String(ttlSeconds),
600
688
  ]));
601
- const query = `?undername=${encodeURIComponent(undername)}&transactionId=${transactionId}&ttlSeconds=${ttlSeconds}`;
602
- return this.httpService.post({
603
- endpoint: `/arns/manage/${antId}/set-record${query}`,
604
- headers,
605
- data: Buffer.from([]),
606
- retry: false, // single-use action-bound nonce; don't re-POST on 5xx
607
- });
608
689
  }
609
- /** Remove a resolution record (an undername) from a custodied ANT. */
610
- async removeArNSRecord({ antId, undername, }) {
611
- const nonce = (0, uuid_js_1.uuidV4)();
612
- const headers = await this.signer.generateSignedRequestHeaders(nonce, this.buildArNSCustodyMessage('remove-record', [antId, undername]));
613
- return this.httpService.post({
614
- endpoint: `/arns/manage/${antId}/remove-record?undername=${encodeURIComponent(undername)}`,
615
- headers,
616
- data: Buffer.from([]),
617
- retry: false, // single-use action-bound nonce; don't re-POST on 5xx
618
- });
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 });
619
729
  }
620
730
  /**
621
731
  * Defaults to the signer's own address when `userAddress` is omitted