@ar.io/sdk 3.5.0-alpha.1 → 3.5.0-alpha.2

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.
@@ -0,0 +1,81 @@
1
+ import { strict as assert } from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+ import { ARIOToken, mARIOToken } from './token.js';
4
+ describe('ARIOToken', () => {
5
+ it('should throw an error on invalid input', () => {
6
+ assert.throws(() => new ARIOToken(-1));
7
+ });
8
+ it('should round at 6 decimal places', () => {
9
+ const token = new ARIOToken(1.123456789);
10
+ assert.strictEqual(token.valueOf(), 1.123457);
11
+ });
12
+ it('should convert to mARIOToken', () => {
13
+ const token = new ARIOToken(1);
14
+ const mToken = token.toMARIO();
15
+ assert.strictEqual(mToken.valueOf(), 1000000);
16
+ });
17
+ it('should print as a string', () => {
18
+ const token = new ARIOToken(1);
19
+ assert.strictEqual(`${token}`, '1');
20
+ });
21
+ });
22
+ describe('mARIOToken', () => {
23
+ it('should multiply by a number', () => {
24
+ const token = new mARIOToken(1);
25
+ const result = token.multiply(2);
26
+ assert.strictEqual(result.valueOf(), 2);
27
+ });
28
+ it('should multiply by another mARIOToken', () => {
29
+ const token = new mARIOToken(1);
30
+ const result = token.multiply(new mARIOToken(2));
31
+ assert.strictEqual(result.valueOf(), 2);
32
+ });
33
+ it('should divide by a number', () => {
34
+ const token = new mARIOToken(2);
35
+ const result = token.divide(2);
36
+ assert.strictEqual(result.valueOf(), 1);
37
+ });
38
+ it('should divide by another mARIOToken', () => {
39
+ const token = new mARIOToken(2);
40
+ const result = token.divide(new mARIOToken(2));
41
+ assert.strictEqual(result.valueOf(), 1);
42
+ });
43
+ it('should throw an error on division by zero', () => {
44
+ const token = new mARIOToken(2);
45
+ assert.throws(() => token.divide(0));
46
+ });
47
+ it('should round down on multiplication of a number', () => {
48
+ const token = new mARIOToken(1);
49
+ const result = token.multiply(1.5);
50
+ assert.strictEqual(result.valueOf(), 1);
51
+ });
52
+ it('should round down on division with a number', () => {
53
+ const token = new mARIOToken(2);
54
+ const result = token.divide(3);
55
+ assert.strictEqual(result.valueOf(), 0);
56
+ });
57
+ it('should round down on division with another mARIOToken', () => {
58
+ const token = new mARIOToken(2);
59
+ const result = token.divide(new mARIOToken(3));
60
+ assert.strictEqual(result.valueOf(), 0);
61
+ });
62
+ it('should add', () => {
63
+ const token = new mARIOToken(1);
64
+ const result = token.plus(new mARIOToken(1));
65
+ assert.strictEqual(result.valueOf(), 2);
66
+ });
67
+ it('should subtract', () => {
68
+ const token = new mARIOToken(2);
69
+ const result = token.minus(new mARIOToken(1));
70
+ assert.strictEqual(result.valueOf(), 1);
71
+ });
72
+ it('should convert to ARIO', () => {
73
+ const token = new mARIOToken(1000000);
74
+ const result = token.toARIO();
75
+ assert.strictEqual(result.valueOf(), 1);
76
+ });
77
+ it('should print as a string', () => {
78
+ const token = new mARIOToken(1);
79
+ assert.strictEqual(`${token}`, '1');
80
+ });
81
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Sorts ANT records by priority and then lexicographically.
3
+ *
4
+ * Note: javascript guarantees that the order of objects in an object is persistent. Still, adding index to each record is useful for enforcing against undername limits.
5
+ *
6
+ * Reference: https://github.com/ar-io/ar-io-node/blob/e0a9ec56559cad1b3e35d668563871afb8649913/docs/madr/003-arns-undername-limits.md
7
+ *
8
+ * @param antRecords - The ANT records to sort.
9
+ */
10
+ export const sortANTRecords = (antRecords) => {
11
+ const sortedEntries = Object.entries(antRecords).sort(([a, aRecord], [b, bRecord]) => {
12
+ // '@' is the root name and should be resolved first
13
+ if (a === '@') {
14
+ return -1;
15
+ }
16
+ if (b === '@') {
17
+ return 1;
18
+ }
19
+ // if a record has a priority, it should be resolved before any other record without a priority
20
+ if ('priority' in aRecord && !('priority' in bRecord)) {
21
+ return -1;
22
+ }
23
+ if (!('priority' in aRecord) && 'priority' in bRecord) {
24
+ return 1;
25
+ }
26
+ // if both records have a priority, sort by priority and fallback to lexicographic sorting
27
+ if (aRecord.priority !== undefined && bRecord.priority !== undefined) {
28
+ if (aRecord.priority === bRecord.priority) {
29
+ return a.localeCompare(b);
30
+ }
31
+ return aRecord.priority - bRecord.priority;
32
+ }
33
+ // all other records are sorted lexicographically
34
+ return a.localeCompare(b);
35
+ });
36
+ // now that they are sorted, add the index to each record - this is their position in the sorted list and is used to enforce undername limits
37
+ return Object.fromEntries(sortedEntries.map(([a, aRecord], index) => [a, { ...aRecord, index }]));
38
+ };
@@ -0,0 +1,67 @@
1
+ import { strict as assert } from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+ import { sortANTRecords } from './ant.js';
4
+ describe('sortANTRecordsByPriority', () => {
5
+ it('should sort records by priority and then lexicographically', () => {
6
+ const records = {
7
+ undername1: { priority: 1, transactionId: 'test', ttlSeconds: 1 },
8
+ undername2: { priority: 2, transactionId: 'test', ttlSeconds: 1 },
9
+ undername3: { priority: 3, transactionId: 'test', ttlSeconds: 1 }, // colliding priorities default to lexicographic sorting
10
+ undername4: { priority: 3, transactionId: 'test', ttlSeconds: 1 },
11
+ undername5: { priority: 100, transactionId: 'test', ttlSeconds: 1 }, // priority does not represent the index or position of the record, just the order of resolution relative to other records
12
+ noPriority: { transactionId: 'test', ttlSeconds: 1 },
13
+ '@': { transactionId: 'test', ttlSeconds: 1 }, // always first, even if no priority
14
+ };
15
+ const sorted = sortANTRecords(records);
16
+ assert.deepStrictEqual(sorted, {
17
+ '@': { transactionId: 'test', ttlSeconds: 1, index: 0 }, // always first, even if no priority
18
+ undername1: {
19
+ priority: 1,
20
+ transactionId: 'test',
21
+ ttlSeconds: 1,
22
+ index: 1,
23
+ },
24
+ undername2: {
25
+ priority: 2,
26
+ transactionId: 'test',
27
+ ttlSeconds: 1,
28
+ index: 2,
29
+ },
30
+ undername3: {
31
+ priority: 3,
32
+ transactionId: 'test',
33
+ ttlSeconds: 1,
34
+ index: 3,
35
+ },
36
+ undername4: {
37
+ priority: 3,
38
+ transactionId: 'test',
39
+ ttlSeconds: 1,
40
+ index: 4,
41
+ },
42
+ undername5: {
43
+ priority: 100,
44
+ transactionId: 'test',
45
+ ttlSeconds: 1,
46
+ index: 5,
47
+ },
48
+ noPriority: { transactionId: 'test', ttlSeconds: 1, index: 6 },
49
+ });
50
+ });
51
+ it('should always return @ as the first record, regardless of priority', () => {
52
+ const records = {
53
+ '@': { priority: 5, transactionId: 'test', ttlSeconds: 1 }, // priorities set on '@' are ignored, they are always first
54
+ undername1: { priority: 2, transactionId: 'test', ttlSeconds: 1 },
55
+ };
56
+ const sorted = sortANTRecords(records);
57
+ assert.deepStrictEqual(sorted, {
58
+ '@': { priority: 5, transactionId: 'test', ttlSeconds: 1, index: 0 },
59
+ undername1: {
60
+ priority: 2,
61
+ transactionId: 'test',
62
+ ttlSeconds: 1,
63
+ index: 1,
64
+ },
65
+ });
66
+ });
67
+ });
@@ -0,0 +1,70 @@
1
+ import { strict as assert } from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+ import { fromB64Url, getRandomText, toB64Url } from './base64.js';
4
+ describe('b64utils', () => {
5
+ it('should convert various strings to base64url and back', () => {
6
+ const testStrings = [
7
+ 'Hello, World!',
8
+ 'Test123!@#',
9
+ 'Base64URLEncoding',
10
+ 'Special_Chars+/',
11
+ '',
12
+ 'A',
13
+ '1234567890',
14
+ ];
15
+ for (const str of testStrings) {
16
+ const encoded = toB64Url(Buffer.from(str));
17
+ const decoded = fromB64Url(encoded);
18
+ assert.deepStrictEqual(decoded, Buffer.from(str), `Failed for string: ${str}`);
19
+ }
20
+ });
21
+ it('should convert various buffers to base64url and back', () => {
22
+ const testBuffers = [
23
+ Buffer.from('Hello, World!'),
24
+ Buffer.from([0, 1, 2, 3, 4, 5]),
25
+ Buffer.from('Test123!@#'),
26
+ Buffer.from('Base64URLEncoding'),
27
+ Buffer.from('Special_Chars+/'),
28
+ Buffer.alloc(0),
29
+ Buffer.from('A'),
30
+ Buffer.from('1234567890'),
31
+ ];
32
+ for (const buf of testBuffers) {
33
+ const encoded = toB64Url(buf);
34
+ const decoded = fromB64Url(encoded);
35
+ assert.deepStrictEqual(decoded, buf, `Failed for buffer: ${buf.toString()}`);
36
+ }
37
+ });
38
+ it('should handle edge cases for base64url conversion', () => {
39
+ const edgeCases = [
40
+ '',
41
+ 'A',
42
+ 'AA',
43
+ 'AAA',
44
+ '====',
45
+ '===',
46
+ '==',
47
+ '=',
48
+ 'A===',
49
+ 'AA==',
50
+ 'AAA=',
51
+ ];
52
+ for (const testCase of edgeCases) {
53
+ const encoded = toB64Url(Buffer.from(testCase));
54
+ const decoded = Buffer.from(fromB64Url(encoded)).toString();
55
+ assert.strictEqual(decoded, testCase, `Failed for edge case: ${testCase}`);
56
+ }
57
+ });
58
+ it('should generate random text', () => {
59
+ const randomText = getRandomText();
60
+ const randomText2 = getRandomText();
61
+ assert.strictEqual(randomText.length, 32);
62
+ assert.strictEqual(randomText2.length, 32);
63
+ assert.notStrictEqual(randomText, randomText2);
64
+ const smallRandomText = getRandomText(16);
65
+ const smallRandomText2 = getRandomText(16);
66
+ assert.strictEqual(smallRandomText.length, 16);
67
+ assert.strictEqual(smallRandomText2.length, 16);
68
+ assert.notStrictEqual(smallRandomText, smallRandomText2);
69
+ });
70
+ });
@@ -19,3 +19,4 @@ export * from './base64.js';
19
19
  export * from './json.js';
20
20
  export * from './processes.js';
21
21
  export * from './schema.js';
22
+ export * from './ant.js';
@@ -0,0 +1,104 @@
1
+ import { strict as assert } from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+ import { pruneTags } from './arweave.js';
4
+ import { errorMessageFromOutput } from './index.js';
5
+ describe('pruneTags', () => {
6
+ it('should remove tags with undefined values', () => {
7
+ const tags = [
8
+ { name: 'Tag1', value: 'value1' },
9
+ { name: 'Tag2', value: undefined },
10
+ { name: 'Tag3', value: 'value3' },
11
+ { name: 'Tag4', value: undefined },
12
+ ];
13
+ const prunedTags = pruneTags(tags);
14
+ assert.deepEqual(prunedTags, [
15
+ { name: 'Tag1', value: 'value1' },
16
+ { name: 'Tag3', value: 'value3' },
17
+ ]);
18
+ });
19
+ it('should return empty array when all tags have undefined values', () => {
20
+ const tags = [
21
+ { name: 'Tag1', value: undefined },
22
+ { name: 'Tag2', value: undefined },
23
+ ];
24
+ const prunedTags = pruneTags(tags);
25
+ assert.deepEqual(prunedTags, []);
26
+ });
27
+ it('should return same array when no tags have undefined values', () => {
28
+ const tags = [
29
+ { name: 'Tag1', value: 'value1' },
30
+ { name: 'Tag2', value: 'value2' },
31
+ ];
32
+ const prunedTags = pruneTags(tags);
33
+ assert.deepEqual(prunedTags, tags);
34
+ });
35
+ it('should return empty array with no tags', () => {
36
+ const tags = [];
37
+ const prunedTags = pruneTags(tags);
38
+ assert.deepEqual(prunedTags, []);
39
+ });
40
+ });
41
+ describe('errorMessageFromOutput', () => {
42
+ it('should return error message from Error field', () => {
43
+ const output = {
44
+ Error: 'Error message',
45
+ };
46
+ const errorMessage = errorMessageFromOutput(output);
47
+ assert.equal(errorMessage, 'Error message');
48
+ });
49
+ it('should return error message from Error tag', () => {
50
+ const output = {
51
+ Messages: [
52
+ {
53
+ Tags: [{ name: 'Error', value: 'Error message' }],
54
+ },
55
+ ],
56
+ };
57
+ const errorMessage = errorMessageFromOutput(output);
58
+ assert.equal(errorMessage, 'Error message');
59
+ });
60
+ it('should return error message from Error tag if Error field is undefined', () => {
61
+ const output = {
62
+ Messages: [
63
+ {
64
+ Tags: [{ name: 'Error', value: 'Error message' }],
65
+ },
66
+ ],
67
+ };
68
+ const errorMessage = errorMessageFromOutput(output);
69
+ assert.equal(errorMessage, 'Error message');
70
+ });
71
+ it('should return undefined if no error message is present', () => {
72
+ const output = {
73
+ Messages: [
74
+ {
75
+ Tags: [{ name: 'Tag1', value: 'value1' }],
76
+ },
77
+ ],
78
+ };
79
+ const errorMessage = errorMessageFromOutput(output);
80
+ assert.equal(errorMessage, undefined);
81
+ });
82
+ it('should return error message with line number', () => {
83
+ const output = {
84
+ Error: '[string "aos"]:123: Error message',
85
+ };
86
+ const errorMessage = errorMessageFromOutput(output);
87
+ assert.equal(errorMessage, 'Error message (line 123)');
88
+ });
89
+ it('should return error message with line number and remove unicode', () => {
90
+ const output = {
91
+ Error: '[string "aos"]:123: Error message\u001b[0m',
92
+ };
93
+ const errorMessage = errorMessageFromOutput(output);
94
+ assert.equal(errorMessage, 'Error message (line 123)');
95
+ });
96
+ const knownErrorMessages = '\u001b[31mError\u001b[90m handling message with Action = Register\u001b[0m\n\u001b[32m[string ".handlers"]:723: [string "aos"]:128: Already registered\u001b[0m\n\n\u001b[90mstack traceback:\n\t[string ".process"]:871: in function \'.process.handle\'\u001b[0m\n\n\u001b[31merror:\n\u001b[0m[string ".handlers"]:723: [string "aos"]:128: Already registered';
97
+ it('should display a clean error for a known error message', () => {
98
+ const output = {
99
+ Error: knownErrorMessages,
100
+ };
101
+ const errorMessage = errorMessageFromOutput(output);
102
+ assert.equal(errorMessage, 'Already registered (line 128)');
103
+ });
104
+ });
@@ -14,4 +14,4 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  // AUTOMATICALLY GENERATED FILE - DO NOT TOUCH
17
- export const version = '3.5.0-alpha.1';
17
+ export const version = '3.5.0-alpha.2';
@@ -34,23 +34,34 @@ export declare const AntKeywordsSchema: z.ZodArray<z.ZodString, "many">;
34
34
  export declare const AntRecordSchema: z.ZodObject<{
35
35
  transactionId: z.ZodEffects<z.ZodString, string, string>;
36
36
  ttlSeconds: z.ZodNumber;
37
+ priority: z.ZodOptional<z.ZodNumber>;
37
38
  }, "strip", z.ZodTypeAny, {
38
39
  transactionId: string;
39
40
  ttlSeconds: number;
41
+ priority?: number | undefined;
40
42
  }, {
41
43
  transactionId: string;
42
44
  ttlSeconds: number;
45
+ priority?: number | undefined;
43
46
  }>;
44
47
  export type AoANTRecord = z.infer<typeof AntRecordSchema>;
48
+ export type ANTRecords = Record<string, AoANTRecord>;
49
+ export type SortedANTRecord = AoANTRecord & {
50
+ index: number;
51
+ };
52
+ export type SortedANTRecords = Record<string, SortedANTRecord>;
45
53
  export declare const AntRecordsSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
46
54
  transactionId: z.ZodEffects<z.ZodString, string, string>;
47
55
  ttlSeconds: z.ZodNumber;
56
+ priority: z.ZodOptional<z.ZodNumber>;
48
57
  }, "strip", z.ZodTypeAny, {
49
58
  transactionId: string;
50
59
  ttlSeconds: number;
60
+ priority?: number | undefined;
51
61
  }, {
52
62
  transactionId: string;
53
63
  ttlSeconds: number;
64
+ priority?: number | undefined;
54
65
  }>>;
55
66
  export declare const AntControllersSchema: z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">;
56
67
  export declare const AntBalancesSchema: z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodNumber>;
@@ -65,12 +76,15 @@ export declare const AntStateSchema: z.ZodObject<{
65
76
  Records: z.ZodRecord<z.ZodString, z.ZodObject<{
66
77
  transactionId: z.ZodEffects<z.ZodString, string, string>;
67
78
  ttlSeconds: z.ZodNumber;
79
+ priority: z.ZodOptional<z.ZodNumber>;
68
80
  }, "strip", z.ZodTypeAny, {
69
81
  transactionId: string;
70
82
  ttlSeconds: number;
83
+ priority?: number | undefined;
71
84
  }, {
72
85
  transactionId: string;
73
86
  ttlSeconds: number;
87
+ priority?: number | undefined;
74
88
  }>>;
75
89
  Balances: z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodNumber>;
76
90
  Logo: z.ZodEffects<z.ZodString, string, string>;
@@ -87,6 +101,7 @@ export declare const AntStateSchema: z.ZodObject<{
87
101
  Records: Record<string, {
88
102
  transactionId: string;
89
103
  ttlSeconds: number;
104
+ priority?: number | undefined;
90
105
  }>;
91
106
  Balances: Record<string, number>;
92
107
  Logo: string;
@@ -103,6 +118,7 @@ export declare const AntStateSchema: z.ZodObject<{
103
118
  Records: Record<string, {
104
119
  transactionId: string;
105
120
  ttlSeconds: number;
121
+ priority?: number | undefined;
106
122
  }>;
107
123
  Balances: Record<string, number>;
108
124
  Logo: string;
@@ -167,7 +183,7 @@ export interface AoANTRead {
167
183
  getRecord({ undername }: {
168
184
  undername: string;
169
185
  }, opts?: AntReadOptions): Promise<AoANTRecord | undefined>;
170
- getRecords(opts?: AntReadOptions): Promise<Record<string, AoANTRecord>>;
186
+ getRecords(opts?: AntReadOptions): Promise<ANTRecords>;
171
187
  getOwner(opts?: AntReadOptions): Promise<WalletAddress>;
172
188
  getControllers(): Promise<WalletAddress[]>;
173
189
  getTicker(opts?: AntReadOptions): Promise<string>;
@@ -191,6 +207,7 @@ export interface AoANTWrite extends AoANTRead {
191
207
  }>;
192
208
  /** @deprecated Use setUndernameRecord instead for undernames, and setBaseNameRecord instead for the top level name (e.g. "@") */
193
209
  setRecord: AoWriteAction<AoANTSetUndernameRecordParams>;
210
+ /** @deprecated Use removeUndernameRecord instead for undernames */
194
211
  removeRecord: AoWriteAction<{
195
212
  undername: string;
196
213
  }>;
@@ -237,6 +254,7 @@ export interface AoANTWrite extends AoANTRead {
237
254
  export type AoANTSetBaseNameRecordParams = {
238
255
  transactionId: string;
239
256
  ttlSeconds: number;
257
+ priority?: number;
240
258
  };
241
259
  export type AoANTSetUndernameRecordParams = AoANTSetBaseNameRecordParams & {
242
260
  undername: string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
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 { ANTRecords, SortedANTRecords } from '../types/ant.js';
17
+ /**
18
+ * Sorts ANT records by priority and then lexicographically.
19
+ *
20
+ * Note: javascript guarantees that the order of objects in an object is persistent. Still, adding index to each record is useful for enforcing against undername limits.
21
+ *
22
+ * Reference: https://github.com/ar-io/ar-io-node/blob/e0a9ec56559cad1b3e35d668563871afb8649913/docs/madr/003-arns-undername-limits.md
23
+ *
24
+ * @param antRecords - The ANT records to sort.
25
+ */
26
+ export declare const sortANTRecords: (antRecords: ANTRecords) => SortedANTRecords;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -19,3 +19,4 @@ export * from './base64.js';
19
19
  export * from './json.js';
20
20
  export * from './processes.js';
21
21
  export * from './schema.js';
22
+ export * from './ant.js';
@@ -0,0 +1 @@
1
+ export {};
@@ -13,4 +13,4 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- export declare const version = "3.4.1";
16
+ export declare const version = "3.5.0-alpha.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ar.io/sdk",
3
- "version": "3.5.0-alpha.1",
3
+ "version": "3.5.0-alpha.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ar-io/ar-io-sdk.git"
@@ -72,7 +72,7 @@
72
72
  "test:cjs": "yarn build:cjs && yarn link && cd ./tests/e2e/cjs && yarn && yarn test",
73
73
  "test:esm": "yarn build:esm && yarn link && cd ./tests/e2e/esm && yarn && yarn test",
74
74
  "test:web": "yarn build:esm && yarn link && cd ./tests/e2e/web && yarn && yarn test",
75
- "test:unit": "NODE_OPTIONS=\"--import=./register.mjs\" node --test tests/unit/**.test.ts",
75
+ "test:unit": "NODE_OPTIONS=\"--import=./register.mjs\" node --test src/**/**.test.ts",
76
76
  "test:link": "yarn build && yarn link",
77
77
  "test:e2e": "yarn test:cjs && yarn test:esm && yarn test:web",
78
78
  "prepare": "husky install",