@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.
- package/lib/cjs/cli/commands/arns.js +37 -9
- package/lib/cjs/cli/options.js +7 -6
- package/lib/cjs/common/arnsActions.js +102 -0
- package/lib/cjs/common/payment.js +215 -80
- package/lib/cjs/common/turbo.js +60 -15
- package/lib/cjs/types.js +20 -1
- package/lib/esm/cli/commands/arns.js +37 -9
- package/lib/esm/cli/options.js +7 -6
- package/lib/esm/common/arnsActions.js +93 -0
- package/lib/esm/common/payment.js +215 -80
- package/lib/esm/common/turbo.js +60 -15
- 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 +153 -33
- package/lib/types/common/payment.d.ts.map +1 -1
- package/lib/types/common/turbo.d.ts +91 -36
- package/lib/types/common/turbo.d.ts.map +1 -1
- package/lib/types/types.d.ts +202 -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
|
}
|
|
@@ -149,6 +167,14 @@ async function arnsPrice(options, turbo) {
|
|
|
149
167
|
}
|
|
150
168
|
async function arnsFiatQuote(options, turbo) {
|
|
151
169
|
const params = arnsPriceParamsFromOptions(options);
|
|
170
|
+
// `arnsPriceParamsFromOptions` substitutes PRICING_PLACEHOLDER_PROCESS_ID for
|
|
171
|
+
// an omitted Buy-Name `processId`, which is fine for a price lookup but NOT
|
|
172
|
+
// here: a quote records a real purchase, and that placeholder is not a valid
|
|
173
|
+
// ANT. Send `processId` only when the caller actually supplied one — omitting
|
|
174
|
+
// it is what tells Turbo to custodially provision the ANT.
|
|
175
|
+
if (options.processId === undefined) {
|
|
176
|
+
delete params.processId;
|
|
177
|
+
}
|
|
152
178
|
const address = options.address;
|
|
153
179
|
if (address === undefined) {
|
|
154
180
|
throw new Error('A destination --address is required for a fiat ArNS quote.');
|
|
@@ -190,20 +216,19 @@ async function buyArNSName(options, turbo) {
|
|
|
190
216
|
const name = requiredNameFromOptions(options);
|
|
191
217
|
const type = typeFromOptions(options.type);
|
|
192
218
|
const paidBy = paidByFromArNSOptions(options.paidBy);
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
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);
|
|
197
222
|
const client = turbo ?? (await (0, utils_js_1.turboFromOptions)(options));
|
|
198
223
|
const result = await withCreditErrorMapping(() => type === 'lease'
|
|
199
224
|
? client.buyArNSName({
|
|
200
225
|
name,
|
|
226
|
+
owner,
|
|
201
227
|
type: 'lease',
|
|
202
228
|
years: positiveIntFromOption(options.years, '--years'),
|
|
203
|
-
processId,
|
|
204
229
|
paidBy,
|
|
205
230
|
})
|
|
206
|
-
: client.buyArNSName({ name, type: 'permabuy',
|
|
231
|
+
: client.buyArNSName({ name, owner, type: 'permabuy', paidBy }));
|
|
207
232
|
logPurchaseResult('ArNS name purchase', result);
|
|
208
233
|
}
|
|
209
234
|
async function extendArNSLease(options, turbo) {
|
|
@@ -252,6 +277,7 @@ async function transferArNSAnt(options, turbo) {
|
|
|
252
277
|
const client = turbo ?? (await (0, utils_js_1.turboFromOptions)(options));
|
|
253
278
|
const result = await client.transferArNSAnt({
|
|
254
279
|
antId: options.antId,
|
|
280
|
+
owner: ownerFromOptions(options),
|
|
255
281
|
target: options.target,
|
|
256
282
|
});
|
|
257
283
|
console.log(JSON.stringify({ message: 'ANT transfer submitted!', ...result }, null, 2));
|
|
@@ -267,6 +293,7 @@ async function setArNSRecord(options, turbo) {
|
|
|
267
293
|
const client = turbo ?? (await (0, utils_js_1.turboFromOptions)(options));
|
|
268
294
|
const result = await client.setArNSRecord({
|
|
269
295
|
antId: options.antId,
|
|
296
|
+
owner: ownerFromOptions(options),
|
|
270
297
|
undername: options.undername ?? '@',
|
|
271
298
|
transactionId: options.transactionId,
|
|
272
299
|
ttlSeconds,
|
|
@@ -283,6 +310,7 @@ async function removeArNSRecord(options, turbo) {
|
|
|
283
310
|
const client = turbo ?? (await (0, utils_js_1.turboFromOptions)(options));
|
|
284
311
|
const result = await client.removeArNSRecord({
|
|
285
312
|
antId: options.antId,
|
|
313
|
+
owner: ownerFromOptions(options),
|
|
286
314
|
undername: options.undername,
|
|
287
315
|
});
|
|
288
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");
|
|
@@ -62,6 +63,22 @@ class TurboUnauthenticatedPaymentService {
|
|
|
62
63
|
// and coerce a missing field (e.g. a 404 body) to `null`.
|
|
63
64
|
return { bytesRemaining: status?.bytesRemaining ?? null };
|
|
64
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
|
+
}
|
|
65
82
|
getFiatRates() {
|
|
66
83
|
return this.httpService.get({
|
|
67
84
|
endpoint: '/rates',
|
|
@@ -110,9 +127,18 @@ class TurboUnauthenticatedPaymentService {
|
|
|
110
127
|
// `async` so a validation failure surfaces as a rejected promise (consistent
|
|
111
128
|
// with `purchaseArNSName`) rather than a synchronous throw.
|
|
112
129
|
this.validateArNSPurchaseParams(params);
|
|
113
|
-
|
|
130
|
+
const price = await this.httpService.get({
|
|
114
131
|
endpoint: `/arns/price/${params.intent.toLowerCase()}/${params.name}${this.buildArNSPurchaseQuery(params)}`,
|
|
115
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
|
+
};
|
|
116
142
|
}
|
|
117
143
|
/**
|
|
118
144
|
* Fail fast (client-side) on malformed ArNS requests so JS callers that bypass
|
|
@@ -494,112 +520,221 @@ class TurboAuthenticatedPaymentService extends TurboUnauthenticatedPaymentServic
|
|
|
494
520
|
* balance. The bundler performs the on-chain ARIO purchase and debits credits;
|
|
495
521
|
* a `402` (FailedRequestError.status === 402) indicates insufficient credits.
|
|
496
522
|
*/
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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) {
|
|
501
548
|
const nonce = (0, uuid_js_1.uuidV4)();
|
|
502
|
-
const headers =
|
|
503
|
-
|
|
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
|
+
}
|
|
504
559
|
try {
|
|
505
|
-
|
|
506
|
-
endpoint: `/arns/
|
|
560
|
+
return await this.httpService.post({
|
|
561
|
+
endpoint: `/arns/actions/${action}`,
|
|
507
562
|
headers,
|
|
508
|
-
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
// Non-idempotent signed write: the nonce is single-use, so a retried
|
|
512
|
-
// (but already-landed) purchase would 4xx as "already exists". Poll
|
|
513
|
-
// 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.
|
|
514
566
|
retry: false,
|
|
515
567
|
});
|
|
516
568
|
}
|
|
517
569
|
catch (error) {
|
|
518
|
-
// Surface a credit shortfall as a typed, catchable error so callers can
|
|
519
|
-
// prompt a top-up. The `nonce` is the idempotency key: after topping up,
|
|
520
|
-
// retry the same purchase (a fresh nonce is fine — the service dedupes by
|
|
521
|
-
// the on-chain effect, and a captured nonce lets you poll status).
|
|
522
570
|
if (error instanceof errors_js_1.FailedRequestError && error.status === 402) {
|
|
523
571
|
throw new errors_js_1.InsufficientCreditsError(error.message);
|
|
524
572
|
}
|
|
525
573
|
throw error;
|
|
526
574
|
}
|
|
527
|
-
// Normalize both nonce fields to the one we signed so callers can poll with
|
|
528
|
-
// either `response.nonce` or `response.purchaseReceipt.nonce`.
|
|
529
|
-
return {
|
|
530
|
-
...response,
|
|
531
|
-
nonce,
|
|
532
|
-
purchaseReceipt: { ...response.purchaseReceipt, nonce },
|
|
533
|
-
};
|
|
534
575
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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,
|
|
539
593
|
});
|
|
540
594
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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}`,
|
|
548
605
|
});
|
|
549
606
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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);
|
|
561
630
|
}
|
|
562
631
|
/**
|
|
563
|
-
*
|
|
564
|
-
*
|
|
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.
|
|
565
641
|
*/
|
|
566
|
-
async
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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 });
|
|
575
662
|
}
|
|
576
|
-
/**
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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', [
|
|
580
684
|
antId,
|
|
581
685
|
undername,
|
|
582
686
|
transactionId,
|
|
583
687
|
String(ttlSeconds),
|
|
584
688
|
]));
|
|
585
|
-
const query = `?undername=${encodeURIComponent(undername)}&transactionId=${transactionId}&ttlSeconds=${ttlSeconds}`;
|
|
586
|
-
return this.httpService.post({
|
|
587
|
-
endpoint: `/arns/manage/${antId}/set-record${query}`,
|
|
588
|
-
headers,
|
|
589
|
-
data: Buffer.from([]),
|
|
590
|
-
retry: false, // single-use action-bound nonce; don't re-POST on 5xx
|
|
591
|
-
});
|
|
592
689
|
}
|
|
593
|
-
/** Remove a
|
|
594
|
-
async removeArNSRecord({ antId, undername, }) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
690
|
+
/** Remove a record (an undername). Free; same two-shape rules as setArNSRecord. */
|
|
691
|
+
async removeArNSRecord({ antId, owner, undername, onNonce, }) {
|
|
692
|
+
return this.completeArNSAction('remove-record', { antId, ownerAddress: await owner.getAddress(), undername }, owner, { onNonce }, (0, arnsActions_js_1.buildArNSCustodyMessage)('remove-record', [antId, undername]));
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Grant controller rights on the ANT. Omit `target` for Turbo itself, which
|
|
696
|
+
* is what makes `setArNSRecord` a single call.
|
|
697
|
+
*
|
|
698
|
+
* Owner-signed: changing an ANT's access control is an owner-only
|
|
699
|
+
* instruction. Free to the customer — Turbo funds the ACL page growth.
|
|
700
|
+
*/
|
|
701
|
+
async addArNSController({ antId, owner, target, onNonce, }) {
|
|
702
|
+
return this.completeArNSAction('add-controller', {
|
|
703
|
+
antId,
|
|
704
|
+
ownerAddress: await owner.getAddress(),
|
|
705
|
+
...(target !== undefined ? { target } : {}),
|
|
706
|
+
}, owner, { onNonce });
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Revoke controller rights — the escape hatch that keeps "Turbo is not a
|
|
710
|
+
* custodian" honest.
|
|
711
|
+
*
|
|
712
|
+
* Always available, always free, and needs nothing from Turbo but the fee.
|
|
713
|
+
* After revoking, `setArNSRecord` keeps working: it simply starts returning
|
|
714
|
+
* `awaiting-signature` so the owner signs their own record writes.
|
|
715
|
+
*/
|
|
716
|
+
async removeArNSController({ antId, owner, target, onNonce, }) {
|
|
717
|
+
return this.completeArNSAction('remove-controller', {
|
|
718
|
+
antId,
|
|
719
|
+
ownerAddress: await owner.getAddress(),
|
|
720
|
+
...(target !== undefined ? { target } : {}),
|
|
721
|
+
}, owner, { onNonce });
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Hand the ANT to a new owner. Irreversible: after this lands, `owner` no
|
|
725
|
+
* longer controls the name. Owner-signed, and sponsored like the rest.
|
|
726
|
+
*/
|
|
727
|
+
async transferArNSAnt({ antId, owner, target, onNonce, }) {
|
|
728
|
+
return this.completeArNSAction('transfer', { antId, ownerAddress: await owner.getAddress(), target }, owner, { onNonce });
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Defaults to the signer's own address when `userAddress` is omitted
|
|
732
|
+
* (`null`/`undefined`). Passing `''` does NOT trigger this default --
|
|
733
|
+
* mirrors `getBalance`'s existing behavior above.
|
|
734
|
+
*/
|
|
735
|
+
async getArNSNames(userAddress) {
|
|
736
|
+
userAddress ??= await this.signer.getNativeAddress();
|
|
737
|
+
return super.getArNSNames(userAddress);
|
|
603
738
|
}
|
|
604
739
|
async getCreditShareApprovals({ userAddress, }) {
|
|
605
740
|
userAddress ??= await this.signer.getNativeAddress();
|