@ar.io/sdk 3.1.0-alpha.1 → 3.1.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bundles/web.bundle.min.js +68 -68
  2. package/lib/cjs/cli/cli.js +19 -69
  3. package/lib/cjs/cli/commands/arnsPurchaseCommands.js +167 -0
  4. package/lib/cjs/cli/commands/gatewayWriteCommands.js +8 -4
  5. package/lib/cjs/cli/commands/readCommands.js +1 -22
  6. package/lib/cjs/cli/commands/transfer.js +5 -1
  7. package/lib/cjs/cli/options.js +8 -7
  8. package/lib/cjs/cli/utils.js +39 -7
  9. package/lib/cjs/common/contracts/ao-process.js +34 -16
  10. package/lib/cjs/common/io.js +76 -116
  11. package/lib/cjs/utils/arweave.js +22 -13
  12. package/lib/cjs/version.js +1 -1
  13. package/lib/esm/cli/cli.js +21 -71
  14. package/lib/esm/cli/commands/arnsPurchaseCommands.js +159 -0
  15. package/lib/esm/cli/commands/gatewayWriteCommands.js +6 -2
  16. package/lib/esm/cli/commands/readCommands.js +2 -23
  17. package/lib/esm/cli/commands/transfer.js +6 -2
  18. package/lib/esm/cli/options.js +7 -6
  19. package/lib/esm/cli/utils.js +34 -5
  20. package/lib/esm/common/contracts/ao-process.js +34 -16
  21. package/lib/esm/common/io.js +77 -117
  22. package/lib/esm/utils/arweave.js +21 -11
  23. package/lib/esm/version.js +1 -1
  24. package/lib/types/cli/commands/arnsPurchaseCommands.d.ts +22 -0
  25. package/lib/types/cli/options.d.ts +1 -9
  26. package/lib/types/cli/types.d.ts +3 -5
  27. package/lib/types/cli/utils.d.ts +16 -3
  28. package/lib/types/common/contracts/ao-process.d.ts +1 -0
  29. package/lib/types/common/io.d.ts +14 -43
  30. package/lib/types/types/common.d.ts +1 -0
  31. package/lib/types/types/io.d.ts +15 -9
  32. package/lib/types/utils/arweave.d.ts +6 -18
  33. package/lib/types/version.d.ts +1 -1
  34. package/package.json +2 -2
@@ -0,0 +1,159 @@
1
+ import { assertConfirmationPrompt, assertEnoughBalanceForArNSPurchase, fundFromFromOptions, positiveIntegerFromOptions, recordTypeFromOptions, requiredPositiveIntegerFromOptions, requiredStringFromOptions, writeARIOFromOptions, writeActionTagsFromOptions, } from '../utils.js';
2
+ export async function buyRecordCLICommand(o) {
3
+ const { ario, signerAddress } = writeARIOFromOptions(o);
4
+ const name = requiredStringFromOptions(o, 'name');
5
+ const type = recordTypeFromOptions(o);
6
+ const years = positiveIntegerFromOptions(o, 'years');
7
+ const fundFrom = fundFromFromOptions(o);
8
+ const processId = o.processId;
9
+ if (processId === undefined) {
10
+ // TODO: Spawn ANT process, register it to ANT registry, get process ID
11
+ throw new Error('Process ID must be provided for buy-record');
12
+ }
13
+ if (!o.skipConfirmation) {
14
+ const existingRecord = await ario.getArNSRecord({
15
+ name,
16
+ });
17
+ if (existingRecord !== undefined) {
18
+ throw new Error(`ArNS Record ${name} is already owned`);
19
+ }
20
+ await assertEnoughBalanceForArNSPurchase({
21
+ ario,
22
+ address: signerAddress,
23
+ costDetailsParams: {
24
+ intent: 'Buy-Record',
25
+ type,
26
+ name,
27
+ years,
28
+ fundFrom,
29
+ fromAddress: signerAddress,
30
+ },
31
+ });
32
+ await assertConfirmationPrompt(`Are you sure you want to ${type} the record ${name}?`, o);
33
+ }
34
+ return ario.buyRecord({
35
+ name: requiredStringFromOptions(o, 'name'),
36
+ processId,
37
+ type,
38
+ years,
39
+ fundFrom: fundFromFromOptions(o),
40
+ }, writeActionTagsFromOptions(o));
41
+ }
42
+ export async function upgradeRecordCLICommand(o) {
43
+ const name = requiredStringFromOptions(o, 'name');
44
+ const { ario, signerAddress } = writeARIOFromOptions(o);
45
+ const fundFrom = fundFromFromOptions(o);
46
+ if (!o.skipConfirmation) {
47
+ const existingRecord = await ario.getArNSRecord({
48
+ name,
49
+ });
50
+ if (existingRecord === undefined) {
51
+ throw new Error(`ArNS Record ${name} does not exist`);
52
+ }
53
+ if (existingRecord.type === 'permabuy') {
54
+ throw new Error(`ArNS Record ${name} is already a permabuy`);
55
+ }
56
+ await assertEnoughBalanceForArNSPurchase({
57
+ ario,
58
+ address: signerAddress,
59
+ costDetailsParams: {
60
+ intent: 'Upgrade-Name',
61
+ name,
62
+ fundFrom,
63
+ fromAddress: signerAddress,
64
+ },
65
+ });
66
+ await assertConfirmationPrompt(`Are you sure you want to upgrade the lease of ${name} to a permabuy?`, o);
67
+ }
68
+ return ario.upgradeRecord({
69
+ name,
70
+ fundFrom,
71
+ });
72
+ }
73
+ export async function extendLeaseCLICommand(o) {
74
+ const name = requiredStringFromOptions(o, 'name');
75
+ const years = requiredPositiveIntegerFromOptions(o, 'years');
76
+ const fundFrom = fundFromFromOptions(o);
77
+ const { ario, signerAddress } = writeARIOFromOptions(o);
78
+ if (!o.skipConfirmation) {
79
+ const existingRecord = await ario.getArNSRecord({
80
+ name,
81
+ });
82
+ if (existingRecord === undefined) {
83
+ throw new Error(`ArNS Record ${name} does not exist`);
84
+ }
85
+ if (existingRecord.type === 'permabuy') {
86
+ throw new Error(`ArNS Record ${name} is a permabuy and cannot be extended`);
87
+ }
88
+ await assertEnoughBalanceForArNSPurchase({
89
+ ario: ario,
90
+ address: signerAddress,
91
+ costDetailsParams: {
92
+ intent: 'Extend-Lease',
93
+ name,
94
+ years,
95
+ fundFrom,
96
+ fromAddress: signerAddress,
97
+ },
98
+ });
99
+ await assertConfirmationPrompt(`Are you sure you want to extend the lease of ${name} by ${years}?`, o);
100
+ }
101
+ return ario.extendLease({
102
+ name,
103
+ years,
104
+ }, writeActionTagsFromOptions(o));
105
+ }
106
+ export async function increaseUndernameLimitCLICommand(o) {
107
+ const name = requiredStringFromOptions(o, 'name');
108
+ const increaseCount = requiredPositiveIntegerFromOptions(o, 'increaseCount');
109
+ const fundFrom = fundFromFromOptions(o);
110
+ const { ario, signerAddress } = writeARIOFromOptions(o);
111
+ if (!o.skipConfirmation) {
112
+ const existingRecord = await ario.getArNSRecord({
113
+ name,
114
+ });
115
+ if (existingRecord === undefined) {
116
+ throw new Error(`ArNS Record ${name} does not exist`);
117
+ }
118
+ await assertEnoughBalanceForArNSPurchase({
119
+ ario,
120
+ address: signerAddress,
121
+ costDetailsParams: {
122
+ intent: 'Increase-Undername-Limit',
123
+ name,
124
+ quantity: increaseCount,
125
+ fundFrom,
126
+ fromAddress: signerAddress,
127
+ },
128
+ });
129
+ await assertConfirmationPrompt(`Are you sure you want to increase the undername limit of ${name} by ${increaseCount}?`, o);
130
+ }
131
+ return ario.increaseUndernameLimit({
132
+ name,
133
+ increaseCount,
134
+ }, writeActionTagsFromOptions(o));
135
+ }
136
+ export async function requestPrimaryNameCLICommand(o) {
137
+ const { ario, signerAddress } = writeARIOFromOptions(o);
138
+ const fundFrom = fundFromFromOptions(o);
139
+ const name = requiredStringFromOptions(o, 'name');
140
+ if (!o.skipConfirmation) {
141
+ // TODO: Assert name requested is not already owned?
142
+ // TODO: More assertions?
143
+ await assertEnoughBalanceForArNSPurchase({
144
+ ario,
145
+ address: signerAddress,
146
+ costDetailsParams: {
147
+ intent: 'Primary-Name-Request',
148
+ name,
149
+ fromAddress: signerAddress,
150
+ fundFrom,
151
+ },
152
+ });
153
+ await assertConfirmationPrompt(`Are you sure you want to request the primary name ${name}?`, o);
154
+ }
155
+ return ario.requestPrimaryName({
156
+ name,
157
+ fundFrom,
158
+ }, writeActionTagsFromOptions(o));
159
+ }
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import prompts from 'prompts';
17
17
  import { mARIOToken } from '../../node/index.js';
18
- import { assertConfirmationPrompt, assertEnoughBalance, formatARIOWithCommas, gatewaySettingsFromOptions, redelegateParamsFromOptions, requiredAddressFromOptions, requiredMIOFromOptions as requiredMARIOFromOptions, requiredStringArrayFromOptions, requiredStringFromOptions, requiredTargetAndQuantityFromOptions, stringifyJsonForCLIDisplay, writeARIOFromOptions, writeActionTagsFromOptions, } from '../utils.js';
18
+ import { assertConfirmationPrompt, assertEnoughMARIOBalance, formatARIOWithCommas, gatewaySettingsFromOptions, redelegateParamsFromOptions, requiredAddressFromOptions, requiredMARIOFromOptions, requiredStringArrayFromOptions, requiredStringFromOptions, requiredTargetAndQuantityFromOptions, stringifyJsonForCLIDisplay, writeARIOFromOptions, writeActionTagsFromOptions, } from '../utils.js';
19
19
  export async function joinNetwork(options) {
20
20
  const { ario, signerAddress } = writeARIOFromOptions(options);
21
21
  const mARIOQuantity = requiredMARIOFromOptions(options, 'operatorStake');
@@ -34,7 +34,11 @@ export async function joinNetwork(options) {
34
34
  if (settings.operators.minStake > mARIOQuantity.valueOf()) {
35
35
  throw new Error(`The minimum operator stake is ${formatARIOWithCommas(new mARIOToken(settings.operators.minStake).toARIO())} ARIO. Please provide a higher stake.`);
36
36
  }
37
- await assertEnoughBalance(ario, signerAddress, mARIOQuantity.toARIO());
37
+ await assertEnoughMARIOBalance({
38
+ ario,
39
+ address: signerAddress,
40
+ mARIOQuantity,
41
+ });
38
42
  await assertConfirmationPrompt(`Gateway Settings:\n\n${JSON.stringify(settings, null, 2)}\n\nYou are about to stake ${formatARIOWithCommas(mARIOQuantity.toARIO())} ARIO to join the AR.IO network\nAre you sure?\n`, options);
39
43
  }
40
44
  const result = await ario.joinNetwork(settings, writeActionTagsFromOptions(options));
@@ -1,21 +1,5 @@
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 { fundFromOptions, isValidFundFrom, } from '../../types/io.js';
17
1
  import { mARIOToken } from '../../types/token.js';
18
- import { addressFromOptions, epochInputFromOptions, formatARIOWithCommas, getTokenCostParamsFromOptions, paginationParamsFromOptions, readARIOFromOptions, requiredAddressFromOptions, requiredStringFromOptions, } from '../utils.js';
2
+ import { addressFromOptions, epochInputFromOptions, formatARIOWithCommas, fundFromFromOptions, getTokenCostParamsFromOptions, paginationParamsFromOptions, readARIOFromOptions, requiredAddressFromOptions, requiredStringFromOptions, } from '../utils.js';
19
3
  export async function getGateway(o) {
20
4
  const address = requiredAddressFromOptions(o);
21
5
  const gateway = await readARIOFromOptions(o).getGateway({
@@ -123,14 +107,9 @@ export async function getTokenCost(o) {
123
107
  return output;
124
108
  }
125
109
  export async function getCostDetails(o) {
126
- if (o.fundFrom !== undefined) {
127
- if (!isValidFundFrom(o.fundFrom)) {
128
- throw new Error(`Invalid fund from: ${o.fundFrom}. Please use one of ${fundFromOptions.join(', ')}`);
129
- }
130
- }
131
110
  const costDetails = await readARIOFromOptions(o).getCostDetails({
132
111
  ...getTokenCostParamsFromOptions(o),
133
- fundFrom: o.fundFrom,
112
+ fundFrom: fundFromFromOptions(o),
134
113
  });
135
114
  const output = {
136
115
  ...costDetails,
@@ -1,9 +1,13 @@
1
- import { assertEnoughBalance, confirmationPrompt, formatARIOWithCommas, requiredTargetAndQuantityFromOptions, writeARIOFromOptions, writeActionTagsFromOptions, } from '../utils.js';
1
+ import { assertEnoughMARIOBalance, confirmationPrompt, formatARIOWithCommas, requiredTargetAndQuantityFromOptions, writeARIOFromOptions, writeActionTagsFromOptions, } from '../utils.js';
2
2
  export async function transfer(options) {
3
3
  const { target, arioQuantity } = requiredTargetAndQuantityFromOptions(options);
4
4
  const { ario, signerAddress } = writeARIOFromOptions(options);
5
5
  if (!options.skipConfirmation) {
6
- await assertEnoughBalance(ario, signerAddress, arioQuantity);
6
+ await assertEnoughMARIOBalance({
7
+ ario,
8
+ address: signerAddress,
9
+ mARIOQuantity: arioQuantity.toMARIO(),
10
+ });
7
11
  const confirm = await confirmationPrompt(`Are you sure you want to transfer ${formatARIOWithCommas(arioQuantity)} ARIO to ${target}?`);
8
12
  if (!confirm) {
9
13
  return { message: 'Transfer aborted by user' };
@@ -252,11 +252,13 @@ export const globalOptions = [
252
252
  optionMap.cuUrl,
253
253
  ];
254
254
  export const writeActionOptions = [optionMap.skipConfirmation, optionMap.tags];
255
- export const addressOptions = [optionMap.address];
256
- export const nameOptions = [optionMap.name];
257
- export const initiatorOptions = [optionMap.initiator];
255
+ export const arnsPurchaseOptions = [
256
+ ...writeActionOptions,
257
+ optionMap.name,
258
+ optionMap.fundFrom,
259
+ ];
258
260
  export const epochOptions = [optionMap.epochIndex, optionMap.timestamp];
259
- export const addressAndVaultIdOptions = [...addressOptions, optionMap.vaultId];
261
+ export const addressAndVaultIdOptions = [optionMap.address, optionMap.vaultId];
260
262
  export const nameWriteOptions = [...writeActionOptions, optionMap.name];
261
263
  export const paginationOptions = [
262
264
  optionMap.cursor,
@@ -313,8 +315,7 @@ export const joinNetworkOptions = [
313
315
  optionMap.operatorStake,
314
316
  ];
315
317
  export const buyRecordOptions = [
316
- ...writeActionOptions,
317
- optionMap.name,
318
+ ...arnsPurchaseOptions,
318
319
  optionMap.quantity,
319
320
  optionMap.type,
320
321
  optionMap.years,
@@ -17,7 +17,7 @@ import { connect } from '@permaweb/aoconnect';
17
17
  import { program } from 'commander';
18
18
  import { readFileSync } from 'fs';
19
19
  import prompts from 'prompts';
20
- import { ANT, AOProcess, ARIO, ARIOToken, ARIO_DEVNET_PROCESS_ID, ARIO_TESTNET_PROCESS_ID, ArweaveSigner, Logger, createAoSigner, fromB64Url, initANTStateForAddress, isValidIntent, mARIOToken, sha256B64Url, validIntents, } from '../node/index.js';
20
+ import { ANT, AOProcess, ARIO, ARIOToken, ARIO_DEVNET_PROCESS_ID, ARIO_TESTNET_PROCESS_ID, ArweaveSigner, Logger, createAoSigner, fromB64Url, fundFromOptions, initANTStateForAddress, isValidFundFrom, isValidIntent, mARIOToken, sha256B64Url, validIntents, } from '../node/index.js';
21
21
  import { globalOptions } from './options.js';
22
22
  export function stringifyJsonForCLIDisplay(json) {
23
23
  return JSON.stringify(json, null, 2);
@@ -141,6 +141,9 @@ export function formatARIOWithCommas(value) {
141
141
  }
142
142
  return integerWithCommas + '.' + decimalPart;
143
143
  }
144
+ export function formatMARIOToARIOWithCommas(value) {
145
+ return formatARIOWithCommas(value.toARIO());
146
+ }
144
147
  /** helper to get address from --address option first, then check wallet options */
145
148
  export function addressFromOptions(options) {
146
149
  if (options.address !== undefined) {
@@ -265,16 +268,34 @@ export function recordTypeFromOptions(options) {
265
268
  }
266
269
  return options.type;
267
270
  }
268
- export function requiredMIOFromOptions(options, key) {
271
+ export function requiredMARIOFromOptions(options, key) {
269
272
  if (options[key] === undefined) {
270
273
  throw new Error(`No ${key} provided. Use --${key} denominated in ARIO`);
271
274
  }
272
275
  return new ARIOToken(+options[key]).toMARIO();
273
276
  }
274
- export async function assertEnoughBalance(ario, address, arioQuantity) {
277
+ export async function assertEnoughBalanceForArNSPurchase({ ario, address, costDetailsParams, }) {
278
+ const costDetails = await ario.getCostDetails(costDetailsParams);
279
+ if (costDetails.fundingPlan) {
280
+ if (costDetails.fundingPlan.shortfall > 0) {
281
+ throw new Error(`Insufficient balance for action. Shortfall: ${formatMARIOToARIOWithCommas(new mARIOToken(costDetails.fundingPlan.shortfall))}\n${JSON.stringify(costDetails, null, 2)}`);
282
+ }
283
+ }
284
+ else {
285
+ await assertEnoughMARIOBalance({
286
+ ario,
287
+ address,
288
+ mARIOQuantity: costDetails.tokenCost,
289
+ });
290
+ }
291
+ }
292
+ export async function assertEnoughMARIOBalance({ address, ario, mARIOQuantity, }) {
293
+ if (typeof mARIOQuantity === 'number') {
294
+ mARIOQuantity = new mARIOToken(mARIOQuantity);
295
+ }
275
296
  const balance = await ario.getBalance({ address });
276
- if (balance < arioQuantity.toMARIO().valueOf()) {
277
- throw new Error(`Insufficient ARIO balance for action. Balance available: ${new mARIOToken(balance).toARIO()} ARIO`);
297
+ if (balance < mARIOQuantity.valueOf()) {
298
+ throw new Error(`Insufficient ARIO balance for action. Balance available: ${formatMARIOToARIOWithCommas(new mARIOToken(balance))} ARIO`);
278
299
  }
279
300
  }
280
301
  export async function confirmationPrompt(message) {
@@ -383,3 +404,11 @@ export function getTokenCostParamsFromOptions(o) {
383
404
  fromAddress: addressFromOptions(o),
384
405
  };
385
406
  }
407
+ export function fundFromFromOptions(o) {
408
+ if (o.fundFrom !== undefined) {
409
+ if (!isValidFundFrom(o.fundFrom)) {
410
+ throw new Error(`Invalid fund from: ${o.fundFrom}. Please use one of ${fundFromOptions.join(', ')}`);
411
+ }
412
+ }
413
+ return o.fundFrom ?? 'balance';
414
+ }
@@ -28,6 +28,12 @@ export class AOProcess {
28
28
  this.logger = logger;
29
29
  this.ao = ao;
30
30
  }
31
+ isMessageDataEmpty(messageData) {
32
+ return (messageData === undefined ||
33
+ messageData === 'null' || // This is what the CU returns for 'nil' values that are json.encoded
34
+ messageData === '' ||
35
+ messageData === null);
36
+ }
31
37
  async read({ tags, retries = 3, fromAddress, }) {
32
38
  let attempts = 0;
33
39
  let lastError;
@@ -48,20 +54,18 @@ export class AOProcess {
48
54
  this.logger.debug(`Read interaction result`, {
49
55
  result,
50
56
  });
57
+ const error = errorMessageFromOutput(result);
58
+ if (error !== undefined) {
59
+ throw new Error(error);
60
+ }
51
61
  if (result.Messages === undefined || result.Messages.length === 0) {
52
62
  this.logger.debug(`Process ${this.processId} does not support provided action.`, result, tags);
53
63
  throw new Error(`Process ${this.processId} does not support provided action.`);
54
64
  }
55
- const tagsOutput = result.Messages?.[0]?.Tags;
56
65
  const messageData = result.Messages?.[0]?.Data;
57
- const errorData = result.Error;
58
- const error = errorData || tagsOutput?.find((tag) => tag.name === 'Error')?.value;
59
- if (error) {
60
- throw new Error(`${error}${messageData ? `: ${messageData}` : ''}`);
61
- }
62
- // return empty object if no data is returned
63
- if (messageData === undefined) {
64
- return {};
66
+ // return undefined if no data is returned
67
+ if (this.isMessageDataEmpty(messageData)) {
68
+ return undefined;
65
69
  }
66
70
  const response = safeDecode(result.Messages[0].Data);
67
71
  return response;
@@ -69,7 +73,7 @@ export class AOProcess {
69
73
  catch (e) {
70
74
  attempts++;
71
75
  this.logger.debug(`Read attempt ${attempts} failed`, {
72
- error: e,
76
+ error: e instanceof Error ? e.message : e,
73
77
  tags,
74
78
  });
75
79
  lastError = e;
@@ -116,11 +120,8 @@ export class AOProcess {
116
120
  messageId,
117
121
  processId: this.processId,
118
122
  });
119
- const errorData = output.Error;
120
- const error = errorData ||
121
- output.Messages?.[0]?.Tags?.find((tag) => tag.name === 'Error')
122
- ?.value;
123
- if (error) {
123
+ const error = errorMessageFromOutput(output);
124
+ if (error !== undefined) {
124
125
  throw new WriteInteractionError(error);
125
126
  }
126
127
  // check if there are any Messages in the output
@@ -130,7 +131,7 @@ export class AOProcess {
130
131
  if (output.Messages.length === 0) {
131
132
  throw new Error(`Process ${this.processId} does not support provided action.`);
132
133
  }
133
- if (output.Messages[0].Data === undefined) {
134
+ if (this.isMessageDataEmpty(output.Messages[0].Data)) {
134
135
  return { id: messageId };
135
136
  }
136
137
  const resultData = safeDecode(output.Messages[0].Data);
@@ -167,3 +168,20 @@ export class AOProcess {
167
168
  throw lastError;
168
169
  }
169
170
  }
171
+ function errorMessageFromOutput(output) {
172
+ const errorData = output.Error;
173
+ if (errorData !== undefined) {
174
+ // TODO: Could clean this one up too, current error is verbose, but not always deterministic for parsing
175
+ // Throw the whole raw error if AO process level error
176
+ return errorData;
177
+ }
178
+ const error = output.Messages?.[0]?.Tags?.find((tag) => tag.name === 'Error')?.value;
179
+ if (error !== undefined) {
180
+ // from [string "aos"]:6846: Name is already registered
181
+ const lineNumber = error.match(/\d+/)?.[0];
182
+ const message = error.replace(/\[string "aos"\]:\d+:/, '');
183
+ // to more user friendly: Name is already registered (line 6846)
184
+ return `${message} (line ${lineNumber})`.trim();
185
+ }
186
+ return undefined;
187
+ }