@ardrive/turbo-sdk 1.42.0-alpha.7 → 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.
@@ -69,6 +69,19 @@ class TurboUnauthenticatedClient {
69
69
  getArNSPurchaseStatus(p) {
70
70
  return this.paymentService.getArNSPurchaseStatus(p);
71
71
  }
72
+ /**
73
+ * Returns the ArNS names a wallet owns or controls via Turbo's custodial
74
+ * ArNS-with-credits feature. `custodial: true` on a returned name means
75
+ * Turbo still holds/manages its ANT (transfer/manage routes apply);
76
+ * `custodial: false` means self-custody (or an already-completed exit) and
77
+ * is informational only.
78
+ *
79
+ * To read a name's current records or lease/expiration state, use
80
+ * `@ar.io/sdk` directly against the returned `antId`.
81
+ */
82
+ getArNSNames(address) {
83
+ return this.paymentService.getArNSNames(address);
84
+ }
72
85
  /**
73
86
  * Quote a fiat (Stripe) ArNS purchase — buy a name with a credit card in one
74
87
  * step, no Turbo Credits top-up in between. Complete the returned
@@ -201,16 +214,22 @@ class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
201
214
  getPaymentHistory(params) {
202
215
  return this.paymentService.getPaymentHistory(params);
203
216
  }
204
- /**
205
- * Buys, extends, or upgrades an ArNS name, paying with the signer's Turbo
206
- * credit balance. Poll {@link getArNSPurchaseStatus} with the returned nonce.
207
- */
208
- purchaseArNSName(params) {
209
- return this.paymentService.purchaseArNSName(params);
217
+ // ===== ArNS actions (sponsored) =====
218
+ //
219
+ // Thin delegations. The two-shape branch and the owner-proof construction
220
+ // live in the payment service; see its comments for why callers must branch
221
+ // on `status` rather than on which action they asked for.
222
+ /** Create an action. Debits credits HERE — capture the nonce before signing. */
223
+ createArNSAction(action, params, ownerProof) {
224
+ return this.paymentService.createArNSAction(action, params, ownerProof);
210
225
  }
211
- /** Buys a new ArNS name (lease or permabuy). */
212
- buyArNSName(params) {
213
- return this.paymentService.buyArNSName(params);
226
+ /** Submit the owner-signed transaction (FULL serialized tx, base64). */
227
+ signArNSAction(nonce, signedTransaction) {
228
+ return this.paymentService.signArNSAction(nonce, signedTransaction);
229
+ }
230
+ /** Status by nonce — open, needs no signature. Use it to resume. */
231
+ getArNSActionStatus(nonce) {
232
+ return this.paymentService.getArNSActionStatus(nonce);
214
233
  }
215
234
  /**
216
235
  * Quote a fiat (Stripe) ArNS purchase for this signer's wallet — a credit-card
@@ -224,27 +243,53 @@ class TurboAuthenticatedClient extends TurboUnauthenticatedClient {
224
243
  getArNSFiatPurchaseQuote(params) {
225
244
  return this.paymentService.getArNSFiatPurchaseQuote(params);
226
245
  }
227
- /** Extends the lease on an existing ArNS name. */
246
+ /**
247
+ * Buy a name. The ANT is minted straight to `owner`; Turbo never holds it.
248
+ * The only action that always needs the owner's signature — once, ever.
249
+ */
250
+ buyArNSName(params) {
251
+ return this.paymentService.buyArNSName(params);
252
+ }
253
+ /** Extend a lease. No owner signature needed. */
228
254
  extendArNSLease(params) {
229
255
  return this.paymentService.extendArNSLease(params);
230
256
  }
231
- /** Increases the undername limit on an existing ArNS name. */
257
+ /** Raise the undername limit. No owner signature needed. */
232
258
  increaseArNSUndernameLimit(params) {
233
259
  return this.paymentService.increaseArNSUndernameLimit(params);
234
260
  }
235
- /** Upgrades an ArNS lease to a permabuy. */
261
+ /** Upgrade a lease to a permanent name. No owner signature needed. */
236
262
  upgradeArNSName(params) {
237
263
  return this.paymentService.upgradeArNSName(params);
238
264
  }
239
- transferArNSAnt(params) {
240
- return this.paymentService.transferArNSAnt(params);
241
- }
265
+ /** Point a name (or undername) at data. Free; handles both shapes. */
242
266
  setArNSRecord(params) {
243
267
  return this.paymentService.setArNSRecord(params);
244
268
  }
269
+ /** Remove a record. Free; handles both shapes. */
245
270
  removeArNSRecord(params) {
246
271
  return this.paymentService.removeArNSRecord(params);
247
272
  }
273
+ /** Grant controller rights — omit `target` for Turbo itself. Free. */
274
+ addArNSController(params) {
275
+ return this.paymentService.addArNSController(params);
276
+ }
277
+ /** Revoke controller rights. Always available, always free. */
278
+ removeArNSController(params) {
279
+ return this.paymentService.removeArNSController(params);
280
+ }
281
+ /** Hand the ANT to a new owner. Irreversible. Free. */
282
+ transferArNSAnt(params) {
283
+ return this.paymentService.transferArNSAnt(params);
284
+ }
285
+ /**
286
+ * Returns the ArNS names owned or controlled by the connected signer's
287
+ * wallet (or the given `userAddress`) via Turbo's custodial
288
+ * ArNS-with-credits feature.
289
+ */
290
+ getArNSNames(userAddress) {
291
+ return this.paymentService.getArNSNames(userAddress);
292
+ }
248
293
  /**
249
294
  * Returns a list of all credit share approvals for the user.
250
295
  */
package/lib/cjs/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.arNSFiatPurchaseMethods = exports.arNSPurchaseIntents = exports.validChunkingModes = exports.isJWK = exports.isWebUploadFolderParams = exports.isNodeUploadFolderParams = exports.multipartFinalizedStatus = exports.multipartFailedStatus = exports.multipartPendingStatus = exports.X402Funding = exports.OnDemandFunding = exports.ExistingBalanceFunding = exports.supportedEvmSignerTokens = exports.tokenTypes = exports.fiatCurrencyTypes = void 0;
3
+ exports.arNSFiatPurchaseMethods = exports.arNSActions = exports.arNSPurchaseIntents = exports.validChunkingModes = exports.isJWK = exports.isWebUploadFolderParams = exports.isNodeUploadFolderParams = exports.multipartFinalizedStatus = exports.multipartFailedStatus = exports.multipartPendingStatus = exports.X402Funding = exports.OnDemandFunding = exports.ExistingBalanceFunding = exports.supportedEvmSignerTokens = exports.tokenTypes = exports.fiatCurrencyTypes = void 0;
4
4
  exports.isCurrency = isCurrency;
5
5
  exports.isKyvePrivateKey = isKyvePrivateKey;
6
6
  exports.isEthPrivateKey = isEthPrivateKey;
@@ -113,6 +113,25 @@ exports.arNSPurchaseIntents = [
113
113
  'Increase-Undername-Limit',
114
114
  'Upgrade-Name',
115
115
  ];
116
+ // ===== ArNS actions (sponsored — the current bundler surface) =====
117
+ /**
118
+ * The nine sponsored ArNS actions.
119
+ *
120
+ * NOT included, because the bundler does not sponsor them: `primary-name`,
121
+ * `release-name`, `reassign`, and ANT metadata (name/description/keywords/logo).
122
+ * Those stay on the direct-signer path and cost the user SOL.
123
+ */
124
+ exports.arNSActions = [
125
+ 'buy-name',
126
+ 'extend-lease',
127
+ 'upgrade-name',
128
+ 'increase-undername-limit',
129
+ 'set-record',
130
+ 'remove-record',
131
+ 'add-controller',
132
+ 'remove-controller',
133
+ 'transfer',
134
+ ];
116
135
  // ===== ArNS purchases paid with fiat (Stripe) — no Turbo Credits in between ====
117
136
  /**
118
137
  * Stripe integration mode for a fiat ArNS purchase quote.
@@ -14,10 +14,26 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { BigNumber } from 'bignumber.js';
17
+ import { solanaOwnerSigner } from '../../common/arnsActions.js';
17
18
  import { TurboFactory } from '../../node/factory.js';
18
19
  import { FiatPaymentsDisabledError, InsufficientCreditsError, } from '../../utils/errors.js';
19
20
  import { wincPerCredit } from '../constants.js';
20
21
  import { configFromOptions, turboFromOptions } from '../utils.js';
22
+ /**
23
+ * The ANT owner's Solana key.
24
+ *
25
+ * Deliberately separate from the wallet that pays: the payer holds Turbo
26
+ * credits (and may be Arweave or Ethereum), while the owner holds the ANT and
27
+ * must be Solana. The owner needs a key to SIGN with, not a funded account —
28
+ * Turbo is the fee payer on every sponsored action.
29
+ */
30
+ function ownerFromOptions(options) {
31
+ if (options.ownerKey === undefined || options.ownerKey === '') {
32
+ throw new Error('Must provide --owner-key (a base58 Solana secret key) — it owns the ANT and signs for it. ' +
33
+ 'This is separate from the wallet paying in Turbo Credits.');
34
+ }
35
+ return solanaOwnerSigner(options.ownerKey);
36
+ }
21
37
  export function requiredNameFromOptions(options) {
22
38
  if (options.name === undefined || options.name.length === 0) {
23
39
  throw new Error('Must provide an ArNS --name');
@@ -111,10 +127,12 @@ async function withCreditErrorMapping(fn) {
111
127
  }
112
128
  function logPurchaseResult(action, result) {
113
129
  console.log(JSON.stringify({
114
- message: `${action} submitted!`,
130
+ message: `${action} completed!`,
115
131
  nonce: result.nonce,
116
- arioWriteId: result.arioWriteResult?.id,
117
- purchaseReceipt: result.purchaseReceipt,
132
+ antId: result.antId,
133
+ // Solana transaction id of the on-chain write.
134
+ messageId: result.messageId,
135
+ ...(result.alreadyCompleted === true ? { alreadyCompleted: true } : {}),
118
136
  }, null, 2));
119
137
  console.log(`\nTrack this purchase with:\n turbo arns-purchase-status --nonce ${result.nonce}`);
120
138
  }
@@ -132,6 +150,14 @@ export async function arnsPrice(options, turbo) {
132
150
  }
133
151
  export async function arnsFiatQuote(options, turbo) {
134
152
  const params = arnsPriceParamsFromOptions(options);
153
+ // `arnsPriceParamsFromOptions` substitutes PRICING_PLACEHOLDER_PROCESS_ID for
154
+ // an omitted Buy-Name `processId`, which is fine for a price lookup but NOT
155
+ // here: a quote records a real purchase, and that placeholder is not a valid
156
+ // ANT. Send `processId` only when the caller actually supplied one — omitting
157
+ // it is what tells Turbo to custodially provision the ANT.
158
+ if (options.processId === undefined) {
159
+ delete params.processId;
160
+ }
135
161
  const address = options.address;
136
162
  if (address === undefined) {
137
163
  throw new Error('A destination --address is required for a fiat ArNS quote.');
@@ -173,20 +199,19 @@ export async function buyArNSName(options, turbo) {
173
199
  const name = requiredNameFromOptions(options);
174
200
  const type = typeFromOptions(options.type);
175
201
  const paidBy = paidByFromArNSOptions(options.paidBy);
176
- // `--process-id` is OPTIONAL: omit it to have Turbo custodially provision the
177
- // ANT (Turbo spawns + owns it — Model A); supply it to point the name at a
178
- // user-owned ANT (Model B). When omitted, no `processId` is sent.
179
- const processId = options.processId;
202
+ // Every buy mints a fresh ANT straight to `--owner-key`; Turbo never holds
203
+ // it, and there is no bring-your-own-ANT path any more.
204
+ const owner = ownerFromOptions(options);
180
205
  const client = turbo ?? (await turboFromOptions(options));
181
206
  const result = await withCreditErrorMapping(() => type === 'lease'
182
207
  ? client.buyArNSName({
183
208
  name,
209
+ owner,
184
210
  type: 'lease',
185
211
  years: positiveIntFromOption(options.years, '--years'),
186
- processId,
187
212
  paidBy,
188
213
  })
189
- : client.buyArNSName({ name, type: 'permabuy', processId, paidBy }));
214
+ : client.buyArNSName({ name, owner, type: 'permabuy', paidBy }));
190
215
  logPurchaseResult('ArNS name purchase', result);
191
216
  }
192
217
  export async function extendArNSLease(options, turbo) {
@@ -235,6 +260,7 @@ export async function transferArNSAnt(options, turbo) {
235
260
  const client = turbo ?? (await turboFromOptions(options));
236
261
  const result = await client.transferArNSAnt({
237
262
  antId: options.antId,
263
+ owner: ownerFromOptions(options),
238
264
  target: options.target,
239
265
  });
240
266
  console.log(JSON.stringify({ message: 'ANT transfer submitted!', ...result }, null, 2));
@@ -250,6 +276,7 @@ export async function setArNSRecord(options, turbo) {
250
276
  const client = turbo ?? (await turboFromOptions(options));
251
277
  const result = await client.setArNSRecord({
252
278
  antId: options.antId,
279
+ owner: ownerFromOptions(options),
253
280
  undername: options.undername ?? '@',
254
281
  transactionId: options.transactionId,
255
282
  ttlSeconds,
@@ -266,6 +293,7 @@ export async function removeArNSRecord(options, turbo) {
266
293
  const client = turbo ?? (await turboFromOptions(options));
267
294
  const result = await client.removeArNSRecord({
268
295
  antId: options.antId,
296
+ owner: ownerFromOptions(options),
269
297
  undername: options.undername,
270
298
  });
271
299
  console.log(JSON.stringify({ message: 'ArNS record removed!', ...result }, null, 2));
@@ -203,9 +203,9 @@ export const optionMap = {
203
203
  alias: '--increase-qty <qty>',
204
204
  description: 'Number of additional undernames (Increase-Undername-Limit)',
205
205
  },
206
- arnsProcessId: {
207
- alias: '--process-id <processId>',
208
- 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',
206
+ arnsOwnerKey: {
207
+ alias: '--owner-key <base58SolanaSecretKey>',
208
+ 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',
209
209
  },
210
210
  arnsNonce: {
211
211
  alias: '--nonce <nonce>',
@@ -299,14 +299,12 @@ export const arnsPriceOptions = [
299
299
  optionMap.arnsType,
300
300
  optionMap.arnsYears,
301
301
  optionMap.arnsIncreaseQty,
302
- optionMap.arnsProcessId,
303
302
  ];
304
303
  export const arnsFiatQuoteOptions = [
305
304
  optionMap.arnsName,
306
305
  optionMap.arnsType,
307
306
  optionMap.arnsYears,
308
307
  optionMap.arnsIncreaseQty,
309
- optionMap.arnsProcessId,
310
308
  optionMap.address,
311
309
  optionMap.currency,
312
310
  {
@@ -323,7 +321,7 @@ export const buyArNSNameOptions = [
323
321
  optionMap.arnsName,
324
322
  optionMap.arnsType,
325
323
  optionMap.arnsYears,
326
- optionMap.arnsProcessId,
324
+ optionMap.arnsOwnerKey,
327
325
  optionMap.paidBy,
328
326
  ];
329
327
  export const extendArNSLeaseOptions = [
@@ -346,11 +344,13 @@ export const upgradeArNSNameOptions = [
346
344
  export const arnsPurchaseStatusOptions = [optionMap.arnsNonce];
347
345
  export const transferArNSAntOptions = [
348
346
  ...walletOptions,
347
+ optionMap.arnsOwnerKey,
349
348
  optionMap.arnsAntId,
350
349
  optionMap.arnsTarget,
351
350
  ];
352
351
  export const setArNSRecordOptions = [
353
352
  ...walletOptions,
353
+ optionMap.arnsOwnerKey,
354
354
  optionMap.arnsAntId,
355
355
  optionMap.arnsUndername,
356
356
  optionMap.arnsTransactionId,
@@ -358,6 +358,7 @@ export const setArNSRecordOptions = [
358
358
  ];
359
359
  export const removeArNSRecordOptions = [
360
360
  ...walletOptions,
361
+ optionMap.arnsOwnerKey,
361
362
  optionMap.arnsAntId,
362
363
  optionMap.arnsUndername,
363
364
  ];
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Copyright (C) 2022-2024 Permanent Data Solutions, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { Keypair, VersionedTransaction } from '@solana/web3.js';
17
+ import bs58 from 'bs58';
18
+ import nacl from 'tweetnacl';
19
+ import { toB64Url } from '../utils/base64.js';
20
+ /**
21
+ * Canonical ACTION-BOUND message for the owner proof.
22
+ *
23
+ * MUST match the bundler's `buildArNSCustodyMessage` byte for byte
24
+ * (newline-delimited) or every signature is rejected. The bundler rebuilds this
25
+ * from the request and verifies the signature over `message + nonce`, so a
26
+ * signature captured for one operation cannot authorize a different one, and
27
+ * the nonce is consumed on use so the same one cannot be replayed (e.g. to
28
+ * revert a record to an older value).
29
+ */
30
+ export function buildArNSCustodyMessage(action, fields) {
31
+ return ['arns', action, ...fields].join('\n');
32
+ }
33
+ /** Solana's signature-type discriminator in Turbo's signed-request scheme. */
34
+ const SOLANA_SIGNATURE_TYPE = 4;
35
+ /**
36
+ * The ANT owner's half of the envelope, in its own `x-owner-*` headers.
37
+ *
38
+ * Two signatures travel on a record action — the PAYER's signed request over
39
+ * `"" + nonce`, and the OWNER's action-bound proof over `message + nonce` —
40
+ * from two different keys, usually on two different chains. They cannot share
41
+ * one header set: whichever verifier ran second would reject a signature that
42
+ * was never meant for it.
43
+ */
44
+ export async function arNSOwnerProofHeaders(owner, message, nonce) {
45
+ const address = await owner.getAddress();
46
+ const signature = await owner.signMessage(Uint8Array.from(Buffer.from(message + nonce)));
47
+ return {
48
+ // A Solana address IS the base58-encoded 32-byte ed25519 public key, so
49
+ // decoding the address recovers exactly the bytes the bundler verifies
50
+ // against. Keeps ArNSOwnerSigner minimal — no separate getPublicKey().
51
+ 'x-owner-public-key': toB64Url(Buffer.from(bs58.decode(address))),
52
+ 'x-owner-nonce': nonce,
53
+ 'x-owner-signature': toB64Url(Buffer.from(signature)),
54
+ 'x-owner-signature-type': String(SOLANA_SIGNATURE_TYPE),
55
+ };
56
+ }
57
+ /**
58
+ * Build an {@link ArNSOwnerSigner} from a raw Solana secret key.
59
+ *
60
+ * For servers and tests. A browser wallet (Phantom, Solflare, or a console's
61
+ * embedded wallet) should implement the interface directly against its own
62
+ * `signTransaction` / `signMessage` rather than exposing a secret key.
63
+ *
64
+ * Note the owner needs a key to SIGN with, not a funded account: Turbo is the
65
+ * fee payer on every sponsored action, so this wallet's SOL balance can stay
66
+ * at zero for the entire life of the name.
67
+ */
68
+ export function solanaOwnerSigner(secretKey) {
69
+ const keypair = Keypair.fromSecretKey(typeof secretKey === 'string' ? bs58.decode(secretKey) : secretKey);
70
+ return {
71
+ getAddress: () => keypair.publicKey.toBase58(),
72
+ signTransaction: async (transactionBase64) => {
73
+ const tx = VersionedTransaction.deserialize(Buffer.from(transactionBase64, 'base64'));
74
+ // Sign the bytes as returned. Turbo's fee-payer signature already covers
75
+ // this exact message; rebuilding it from parts invalidates that and the
76
+ // submission is rejected.
77
+ tx.sign([keypair]);
78
+ return Buffer.from(tx.serialize()).toString('base64');
79
+ },
80
+ signMessage: async (message) => nacl.sign.detached(message, keypair.secretKey),
81
+ };
82
+ }
83
+ /**
84
+ * Count the signature slots a prepared transaction still has empty.
85
+ *
86
+ * Turbo pre-signs as fee payer and leaves exactly one slot for the owner, so a
87
+ * client can assert that before prompting a wallet — a cheap way to catch a
88
+ * malformed or already-signed transaction before bothering the user.
89
+ */
90
+ export function emptySignatureSlots(transactionBase64) {
91
+ const tx = VersionedTransaction.deserialize(Buffer.from(transactionBase64, 'base64'));
92
+ return tx.signatures.filter((sig) => sig.every((byte) => byte === 0)).length;
93
+ }