@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.
- package/lib/cjs/cli/commands/arns.js +29 -9
- package/lib/cjs/cli/options.js +7 -6
- package/lib/cjs/common/arnsActions.js +102 -0
- package/lib/cjs/common/payment.js +190 -80
- package/lib/cjs/common/turbo.js +40 -16
- package/lib/cjs/types.js +20 -1
- package/lib/esm/cli/commands/arns.js +29 -9
- package/lib/esm/cli/options.js +7 -6
- package/lib/esm/common/arnsActions.js +93 -0
- package/lib/esm/common/payment.js +190 -80
- package/lib/esm/common/turbo.js +40 -16
- package/lib/esm/types.js +19 -0
- package/lib/types/cli/commands/arns.d.ts +16 -27
- package/lib/types/cli/commands/arns.d.ts.map +1 -1
- package/lib/types/cli/options.d.ts +14 -11
- package/lib/types/cli/options.d.ts.map +1 -1
- package/lib/types/cli/types.d.ts +8 -4
- package/lib/types/cli/types.d.ts.map +1 -1
- package/lib/types/common/arnsActions.d.ts +43 -0
- package/lib/types/common/arnsActions.d.ts.map +1 -0
- package/lib/types/common/payment.d.ts +135 -33
- package/lib/types/common/payment.d.ts.map +1 -1
- package/lib/types/common/turbo.d.ts +74 -36
- package/lib/types/common/turbo.d.ts.map +1 -1
- package/lib/types/types.d.ts +153 -28
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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}
|
|
147
|
+
message: `${action} completed!`,
|
|
132
148
|
nonce: result.nonce,
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
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',
|
|
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));
|
package/lib/cjs/cli/options.js
CHANGED
|
@@ -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
|
-
|
|
210
|
-
alias: '--
|
|
211
|
-
description: '
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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 =
|
|
519
|
-
|
|
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
|
-
|
|
522
|
-
endpoint: `/arns/
|
|
560
|
+
return await this.httpService.post({
|
|
561
|
+
endpoint: `/arns/actions/${action}`,
|
|
523
562
|
headers,
|
|
524
|
-
|
|
525
|
-
//
|
|
526
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
*
|
|
580
|
-
*
|
|
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
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
/**
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
|
610
|
-
async removeArNSRecord({ antId, undername, }) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|