@swapkit/helpers 4.5.7 → 4.5.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.
@@ -0,0 +1,302 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ getAsymmetricAssetShare,
5
+ getAsymmetricAssetWithdrawAmount,
6
+ getAsymmetricRuneShare,
7
+ getAsymmetricRuneWithdrawAmount,
8
+ getEstimatedPoolShare,
9
+ getLiquiditySlippage,
10
+ getSymmetricPoolShare,
11
+ getSymmetricWithdraw,
12
+ } from "../liquidity";
13
+
14
+ describe("getAsymmetricRuneShare", () => {
15
+ test("calculates rune share for typical pool position", () => {
16
+ const result = getAsymmetricRuneShare({
17
+ liquidityUnits: "100000000",
18
+ poolUnits: "1000000000",
19
+ runeDepth: "500000000000",
20
+ });
21
+ expect(result.getValue("number")).toBeGreaterThan(0);
22
+ });
23
+
24
+ test("returns zero rune share for zero liquidity units", () => {
25
+ const result = getAsymmetricRuneShare({ liquidityUnits: "0", poolUnits: "1000000000", runeDepth: "500000000000" });
26
+ expect(result.getValue("number")).toBe(0);
27
+ });
28
+
29
+ test("returns full amount when owning all pool units", () => {
30
+ const result = getAsymmetricRuneShare({
31
+ liquidityUnits: "1000000000",
32
+ poolUnits: "1000000000",
33
+ runeDepth: "500000000000",
34
+ });
35
+ expect(result.getValue("string")).toBe("5000");
36
+ });
37
+ });
38
+
39
+ describe("getAsymmetricAssetShare", () => {
40
+ test("calculates asset share for typical pool position", () => {
41
+ const result = getAsymmetricAssetShare({
42
+ assetDepth: "200000000000",
43
+ liquidityUnits: "100000000",
44
+ poolUnits: "1000000000",
45
+ });
46
+ expect(result.getValue("number")).toBeGreaterThan(0);
47
+ });
48
+
49
+ test("returns zero asset share for zero liquidity units", () => {
50
+ const result = getAsymmetricAssetShare({
51
+ assetDepth: "200000000000",
52
+ liquidityUnits: "0",
53
+ poolUnits: "1000000000",
54
+ });
55
+ expect(result.getValue("number")).toBe(0);
56
+ });
57
+ });
58
+
59
+ describe("getAsymmetricRuneWithdrawAmount", () => {
60
+ test("calculates withdrawal amount for 50% withdrawal", () => {
61
+ const result = getAsymmetricRuneWithdrawAmount({
62
+ liquidityUnits: "100000000",
63
+ percent: 0.5,
64
+ poolUnits: "1000000000",
65
+ runeDepth: "500000000000",
66
+ });
67
+ expect(result.getValue("number")).toBeGreaterThan(0);
68
+ });
69
+
70
+ test("calculates full withdrawal for 100%", () => {
71
+ const full = getAsymmetricRuneWithdrawAmount({
72
+ liquidityUnits: "100000000",
73
+ percent: 1,
74
+ poolUnits: "1000000000",
75
+ runeDepth: "500000000000",
76
+ });
77
+ const half = getAsymmetricRuneWithdrawAmount({
78
+ liquidityUnits: "100000000",
79
+ percent: 0.5,
80
+ poolUnits: "1000000000",
81
+ runeDepth: "500000000000",
82
+ });
83
+ expect(full.getValue("number")).toBeCloseTo(half.getValue("number") * 2, 5);
84
+ });
85
+ });
86
+
87
+ describe("getAsymmetricAssetWithdrawAmount", () => {
88
+ test("calculates withdrawal for given percentage", () => {
89
+ const result = getAsymmetricAssetWithdrawAmount({
90
+ assetDepth: "200000000000",
91
+ liquidityUnits: "100000000",
92
+ percent: 0.25,
93
+ poolUnits: "1000000000",
94
+ });
95
+ expect(result.getValue("number")).toBeGreaterThan(0);
96
+ });
97
+ });
98
+
99
+ describe("getSymmetricPoolShare", () => {
100
+ test("returns both rune and asset amounts", () => {
101
+ const result = getSymmetricPoolShare({
102
+ assetDepth: "200000000000",
103
+ liquidityUnits: "100000000",
104
+ poolUnits: "1000000000",
105
+ runeDepth: "500000000000",
106
+ });
107
+ expect(result.runeAmount.getValue("number")).toBeGreaterThan(0);
108
+ expect(result.assetAmount.getValue("number")).toBeGreaterThan(0);
109
+ });
110
+
111
+ test("proportionally distributes based on liquidity units", () => {
112
+ const result10 = getSymmetricPoolShare({
113
+ assetDepth: "1000000000000",
114
+ liquidityUnits: "100000000",
115
+ poolUnits: "1000000000",
116
+ runeDepth: "1000000000000",
117
+ });
118
+ const result20 = getSymmetricPoolShare({
119
+ assetDepth: "1000000000000",
120
+ liquidityUnits: "200000000",
121
+ poolUnits: "1000000000",
122
+ runeDepth: "1000000000000",
123
+ });
124
+ expect(result20.runeAmount.getValue("number")).toBeCloseTo(result10.runeAmount.getValue("number") * 2, 5);
125
+ });
126
+ });
127
+
128
+ describe("getSymmetricWithdraw", () => {
129
+ test("applies percentage to both amounts", () => {
130
+ const result = getSymmetricWithdraw({
131
+ assetDepth: "200000000000",
132
+ liquidityUnits: "100000000",
133
+ percent: 0.5,
134
+ poolUnits: "1000000000",
135
+ runeDepth: "500000000000",
136
+ });
137
+ expect(result.runeAmount?.getValue("number")).toBeGreaterThan(0);
138
+ expect(result.assetAmount?.getValue("number")).toBeGreaterThan(0);
139
+ });
140
+
141
+ test("full withdrawal equals pool share", () => {
142
+ const params = {
143
+ assetDepth: "200000000000",
144
+ liquidityUnits: "100000000",
145
+ poolUnits: "1000000000",
146
+ runeDepth: "500000000000",
147
+ };
148
+ const share = getSymmetricPoolShare(params);
149
+ const withdraw = getSymmetricWithdraw({ ...params, percent: 1 });
150
+ expect(withdraw.runeAmount?.getValue("string")).toBe(share.runeAmount.getValue("string"));
151
+ });
152
+ });
153
+
154
+ describe("getEstimatedPoolShare", () => {
155
+ test("estimates pool share after adding liquidity", () => {
156
+ // Returns base value representation (with 8 decimals internally)
157
+ // For ~4.76% share, base value is 4761905 (0.04761905 * 10^8)
158
+ const result = getEstimatedPoolShare({
159
+ assetAmount: "10000000000",
160
+ assetDepth: "200000000000",
161
+ liquidityUnits: "0",
162
+ poolUnits: "1000000000",
163
+ runeAmount: "25000000000",
164
+ runeDepth: "500000000000",
165
+ });
166
+ expect(result).toBeGreaterThan(0);
167
+ // Base value for percentage < 100% is < 10^8
168
+ expect(result).toBeLessThan(100_000_000);
169
+ });
170
+
171
+ test("returns 0 when adding zero liquidity", () => {
172
+ const result = getEstimatedPoolShare({
173
+ assetAmount: "0",
174
+ assetDepth: "200000000000",
175
+ liquidityUnits: "0",
176
+ poolUnits: "1000000000",
177
+ runeAmount: "0",
178
+ runeDepth: "500000000000",
179
+ });
180
+ expect(result).toBe(0);
181
+ });
182
+ });
183
+
184
+ describe("getLiquiditySlippage", () => {
185
+ test("returns 0 when any amount is zero", () => {
186
+ expect(
187
+ getLiquiditySlippage({ assetAmount: "0", assetDepth: "200000000000", runeAmount: "100", runeDepth: "500" }),
188
+ ).toBe(0);
189
+ expect(getLiquiditySlippage({ assetAmount: "100", assetDepth: "0", runeAmount: "100", runeDepth: "500" })).toBe(0);
190
+ expect(getLiquiditySlippage({ assetAmount: "100", assetDepth: "200", runeAmount: "0", runeDepth: "500" })).toBe(0);
191
+ expect(getLiquiditySlippage({ assetAmount: "100", assetDepth: "200", runeAmount: "100", runeDepth: "0" })).toBe(0);
192
+ });
193
+
194
+ test("calculates slippage for balanced add", () => {
195
+ const result = getLiquiditySlippage({
196
+ assetAmount: "100000000",
197
+ assetDepth: "200000000000",
198
+ runeAmount: "250000000",
199
+ runeDepth: "500000000000",
200
+ });
201
+ expect(result).toBeGreaterThanOrEqual(0);
202
+ expect(result).toBeLessThan(1);
203
+ });
204
+
205
+ test("returns absolute value (no negatives)", () => {
206
+ const result = getLiquiditySlippage({
207
+ assetAmount: "1000000000",
208
+ assetDepth: "200000000000",
209
+ runeAmount: "100000000",
210
+ runeDepth: "500000000000",
211
+ });
212
+ expect(result).toBeGreaterThanOrEqual(0);
213
+ });
214
+ });
215
+
216
+ describe("edge cases", () => {
217
+ test("handles very large numbers without overflow", () => {
218
+ const result = getAsymmetricRuneShare({
219
+ liquidityUnits: "999999999999999999",
220
+ poolUnits: "9999999999999999999",
221
+ runeDepth: "99999999999999999999",
222
+ });
223
+ expect(result.getValue("number")).toBeGreaterThan(0);
224
+ });
225
+
226
+ test("getSymmetricPoolShare with equal depths", () => {
227
+ const result = getSymmetricPoolShare({
228
+ assetDepth: "1000000000000",
229
+ liquidityUnits: "500000000",
230
+ poolUnits: "1000000000",
231
+ runeDepth: "1000000000000",
232
+ });
233
+ // 50% of pool should get 50% of both depths
234
+ expect(result.runeAmount.getValue("string")).toBe(result.assetAmount.getValue("string"));
235
+ });
236
+
237
+ test("getEstimatedPoolShare with existing liquidity units", () => {
238
+ const result = getEstimatedPoolShare({
239
+ assetAmount: "10000000000",
240
+ assetDepth: "200000000000",
241
+ liquidityUnits: "50000000", // User already has some units
242
+ poolUnits: "1000000000",
243
+ runeAmount: "25000000000",
244
+ runeDepth: "500000000000",
245
+ });
246
+ expect(result).toBeGreaterThan(0);
247
+ });
248
+
249
+ test("handles single-sided liquidity add (only rune)", () => {
250
+ const result = getEstimatedPoolShare({
251
+ assetAmount: "0",
252
+ assetDepth: "200000000000",
253
+ liquidityUnits: "0",
254
+ poolUnits: "1000000000",
255
+ runeAmount: "25000000000",
256
+ runeDepth: "500000000000",
257
+ });
258
+ expect(result).toBeGreaterThan(0);
259
+ });
260
+
261
+ test("handles single-sided liquidity add (only asset)", () => {
262
+ const result = getEstimatedPoolShare({
263
+ assetAmount: "10000000000",
264
+ assetDepth: "200000000000",
265
+ liquidityUnits: "0",
266
+ poolUnits: "1000000000",
267
+ runeAmount: "0",
268
+ runeDepth: "500000000000",
269
+ });
270
+ expect(result).toBeGreaterThan(0);
271
+ });
272
+
273
+ test("getAsymmetricRuneWithdrawAmount with 0% withdrawal returns 0", () => {
274
+ const result = getAsymmetricRuneWithdrawAmount({
275
+ liquidityUnits: "100000000",
276
+ percent: 0,
277
+ poolUnits: "1000000000",
278
+ runeDepth: "500000000000",
279
+ });
280
+ expect(result.getValue("number")).toBe(0);
281
+ });
282
+
283
+ test("getLiquiditySlippage higher slippage for unbalanced add", () => {
284
+ // Balanced add (proportional to pool)
285
+ const balanced = getLiquiditySlippage({
286
+ assetAmount: "100000000",
287
+ assetDepth: "200000000000",
288
+ runeAmount: "250000000",
289
+ runeDepth: "500000000000",
290
+ });
291
+
292
+ // Unbalanced add (too much rune)
293
+ const unbalanced = getLiquiditySlippage({
294
+ assetAmount: "100000000",
295
+ assetDepth: "200000000000",
296
+ runeAmount: "1000000000", // Much higher rune
297
+ runeDepth: "500000000000",
298
+ });
299
+
300
+ expect(unbalanced).toBeGreaterThan(balanced);
301
+ });
302
+ });
@@ -25,7 +25,7 @@ describe("getMemoForLeaveAndBond", () => {
25
25
  });
26
26
 
27
27
  describe("getMemoForNameRegister", () => {
28
- test("returns correct memo for single side", () => {
28
+ test("returns correct memo for name registration", () => {
29
29
  const result = getMemoForNameRegister({
30
30
  address: "0xaasd123",
31
31
  chain: Chain.Ethereum,
@@ -37,7 +37,7 @@ describe("getMemoForNameRegister", () => {
37
37
  });
38
38
 
39
39
  describe("getMemoForNamePreferredAssetRegister", () => {
40
- test("returns correct memo for single side", () => {
40
+ test("returns correct memo for preferred asset registration", () => {
41
41
  const result = getMemoForNamePreferredAssetRegister({
42
42
  asset: "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48",
43
43
  chain: Chain.Ethereum,
@@ -52,7 +52,7 @@ describe("getMemoForNamePreferredAssetRegister", () => {
52
52
  });
53
53
 
54
54
  describe("getMemoForDeposit", () => {
55
- test("returns correct memo for single side", () => {
55
+ test("returns correct memo for deposit", () => {
56
56
  const result = getMemoForDeposit({ chain: Chain.Ethereum, symbol: "ETH" });
57
57
  expect(result).toBe("+:ETH.ETH");
58
58
  });
@@ -69,21 +69,21 @@ describe("getMemoForDeposit", () => {
69
69
  });
70
70
 
71
71
  describe("getMemoForWithdraw", () => {
72
- test("returns correct memo for single side", () => {
72
+ test("returns correct memo for withdraw", () => {
73
73
  const result = getMemoForWithdraw({ basisPoints: 100, chain: Chain.Ethereum, symbol: "ETH", ticker: "ETH" });
74
74
  expect(result).toBe("-:ETH.ETH:100");
75
75
  });
76
76
  });
77
77
 
78
78
  describe("getMemoForRunePoolDeposit", () => {
79
- test("returns correct memo for single side", () => {
79
+ test("returns correct memo for rune pool deposit", () => {
80
80
  const result = getMemoForRunePoolDeposit();
81
81
  expect(result).toBe("POOL+");
82
82
  });
83
83
  });
84
84
 
85
85
  describe("getMemoForRunePoolWithdraw", () => {
86
- test("returns correct memo for single side", () => {
86
+ test("returns correct memo for rune pool withdraw", () => {
87
87
  const result = getMemoForRunePoolWithdraw({ basisPoints: 500 });
88
88
  expect(result).toBe("POOL-:500");
89
89
  });
@@ -2,37 +2,119 @@ import { describe, expect, test } from "bun:test";
2
2
  import { Chain } from "@swapkit/types";
3
3
 
4
4
  import { findAssetBy } from "../asset";
5
- import { getTHORNameCost } from "../others";
5
+ import { getChainIdentifier, getMAYANameCost, getTHORNameCost, warnOnce, wrapWithThrow } from "../others";
6
6
 
7
7
  describe("getTHORNameCost", () => {
8
- describe("for correct values", () => {
9
- const costCases = [
10
- [1, 11],
11
- [2, 12],
12
- [3, 13],
13
- [10, 20],
14
- ];
15
-
16
- for (const [years = 0, expected = 10] of costCases) {
17
- test(`returns correct ${expected} cost for ${years} years`, () => {
18
- const result = getTHORNameCost(years);
19
- expect(result).toBe(expected);
20
- });
21
- }
8
+ test("returns 10 + numberOfYears", () => {
9
+ expect(getTHORNameCost(0)).toBe(10);
10
+ expect(getTHORNameCost(1)).toBe(11);
11
+ expect(getTHORNameCost(5)).toBe(15);
12
+ expect(getTHORNameCost(10)).toBe(20);
22
13
  });
23
14
 
24
- test("throws an error for negative years", () => {
15
+ test("throws for negative years in THORName cost", () => {
25
16
  expect(() => getTHORNameCost(-1)).toThrow("helpers_invalid_number_of_years");
17
+ expect(() => getTHORNameCost(-100)).toThrow("helpers_invalid_number_of_years");
18
+ });
19
+ });
20
+
21
+ describe("getMAYANameCost", () => {
22
+ test("returns 10 + (numberOfYears * 1.0512)", () => {
23
+ expect(getMAYANameCost(0)).toBe(10);
24
+ expect(getMAYANameCost(1)).toBe(11.0512);
25
+ expect(getMAYANameCost(5)).toBeCloseTo(15.256, 3);
26
+ expect(getMAYANameCost(10)).toBeCloseTo(20.512, 3);
27
+ });
28
+
29
+ test("throws for negative years in MAYAName cost", () => {
30
+ expect(() => getMAYANameCost(-1)).toThrow("helpers_invalid_number_of_years");
31
+ });
32
+ });
33
+
34
+ describe("getChainIdentifier", () => {
35
+ test("returns CHAIN.RUNE for THORChain", () => {
36
+ expect(getChainIdentifier(Chain.THORChain)).toBe("THOR.RUNE");
37
+ });
38
+
39
+ test("returns CHAIN.ATOM for Cosmos", () => {
40
+ expect(getChainIdentifier(Chain.Cosmos)).toBe("GAIA.ATOM");
41
+ });
42
+
43
+ test("returns CHAIN only for BinanceSmartChain", () => {
44
+ expect(getChainIdentifier(Chain.BinanceSmartChain)).toBe("BSC");
45
+ });
46
+
47
+ test("returns CHAIN.CHAIN for other chains", () => {
48
+ expect(getChainIdentifier(Chain.Bitcoin)).toBe("BTC.BTC");
49
+ expect(getChainIdentifier(Chain.Ethereum)).toBe("ETH.ETH");
50
+ expect(getChainIdentifier(Chain.Avalanche)).toBe("AVAX.AVAX");
51
+ });
52
+ });
53
+
54
+ describe("wrapWithThrow", () => {
55
+ test("returns function result on success", () => {
56
+ expect(wrapWithThrow(() => 42)).toBe(42);
57
+ expect(wrapWithThrow(() => "hello")).toBe("hello");
58
+ expect(wrapWithThrow(() => ({ a: 1 }))).toEqual({ a: 1 });
59
+ });
60
+
61
+ test("returns undefined on error without errorKey", () => {
62
+ expect(
63
+ wrapWithThrow(() => {
64
+ throw new Error("test");
65
+ }),
66
+ ).toBeUndefined();
67
+ });
68
+
69
+ test("throws SwapKitError when errorKey provided", () => {
70
+ expect(() =>
71
+ wrapWithThrow(() => {
72
+ throw new Error("test");
73
+ }, "helpers_invalid_identifier"),
74
+ ).toThrow("helpers_invalid_identifier");
75
+ });
76
+ });
77
+
78
+ describe("warnOnce", () => {
79
+ // Note: warnOnce skips console.warn in test environment (NODE_ENV=test)
80
+ // We test the behavior by calling the function multiple times and observing
81
+ // that the function doesn't throw and handles repeated calls gracefully
82
+
83
+ test("does not throw when condition is false", () => {
84
+ expect(() => warnOnce({ condition: false, id: "test_false_condition", warning: "should not warn" })).not.toThrow();
85
+ });
86
+
87
+ test("does not throw when condition is true", () => {
88
+ const uniqueId = `test_warns_${Date.now()}`;
89
+ expect(() => warnOnce({ condition: true, id: uniqueId, warning: "test warning message" })).not.toThrow();
90
+ });
91
+
92
+ test("handles repeated calls with same id without throwing", () => {
93
+ const uniqueId = `test_once_${Date.now()}`;
94
+ expect(() => {
95
+ warnOnce({ condition: true, id: uniqueId, warning: "first warn" });
96
+ warnOnce({ condition: true, id: uniqueId, warning: "second warn" });
97
+ warnOnce({ condition: true, id: uniqueId, warning: "third warn" });
98
+ }).not.toThrow();
99
+ });
100
+
101
+ test("handles multiple different ids without throwing", () => {
102
+ const uniqueId1 = `test_different_1_${Date.now()}`;
103
+ const uniqueId2 = `test_different_2_${Date.now()}`;
104
+ expect(() => {
105
+ warnOnce({ condition: true, id: uniqueId1, warning: "warn 1" });
106
+ warnOnce({ condition: true, id: uniqueId2, warning: "warn 2" });
107
+ }).not.toThrow();
26
108
  });
27
109
  });
28
110
 
29
111
  describe("getAssetBy", () => {
30
- test("find asset by identifier", async () => {
112
+ test("find ETH asset by identifier", async () => {
31
113
  const assetByIdentifier = await findAssetBy({ identifier: "ETH.ETH" });
32
114
  expect(assetByIdentifier).toBe("ETH.ETH");
33
115
  });
34
116
 
35
- test("find asset by chain and contract", async () => {
117
+ test("find ETH token by chain and contract", async () => {
36
118
  const assetByChainAndContract = await findAssetBy({
37
119
  chain: Chain.Ethereum,
38
120
  contract: "0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48",
@@ -48,12 +130,12 @@ describe("getAssetBy", () => {
48
130
  });
49
131
 
50
132
  describe(Chain.Radix, () => {
51
- test("find asset by identifier", async () => {
133
+ test("find Radix XRD by identifier", async () => {
52
134
  const assetByChainAndContract = await findAssetBy({ identifier: "XRD.XRD" });
53
135
  expect(assetByChainAndContract?.toUpperCase()).toBe("XRD.XRD".toUpperCase());
54
136
  });
55
137
 
56
- test("find asset by chain and contract", async () => {
138
+ test("find Radix token by chain and contract", async () => {
57
139
  const assetByChainAndContract = await findAssetBy({
58
140
  chain: Chain.Radix,
59
141
  contract: "resource_rdx1t580qxc7upat7lww4l2c4jckacafjeudxj5wpjrrct0p3e82sq4y75",
@@ -65,12 +147,12 @@ describe("getAssetBy", () => {
65
147
  });
66
148
 
67
149
  describe(Chain.Solana, () => {
68
- test("find asset by identifier", async () => {
150
+ test("find Solana SOL by identifier", async () => {
69
151
  const assetByChainAndContract = await findAssetBy({ identifier: "SOL.SOL" });
70
152
  expect(assetByChainAndContract?.toUpperCase()).toBe("SOL.SOL".toUpperCase());
71
153
  });
72
154
 
73
- test("find asset by chain and contract", async () => {
155
+ test("find Solana token by chain and contract", async () => {
74
156
  const assetByChainAndContract = await findAssetBy({
75
157
  chain: Chain.Solana,
76
158
  contract: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
@@ -1,24 +1,84 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { validateTNS } from "../validators";
2
+ import { validateIdentifier, validateTNS } from "../validators";
3
3
 
4
4
  describe("validateTNS", () => {
5
- const casesWithExpectation: [string, boolean][] = [
6
- ["validname", true],
7
- ["valid-name", true],
8
- ["valid_name", true],
9
- ["valid+name", true],
10
- ["name_with_numbers123", true],
11
- ["UPPER_CASE", true],
12
- ["toolongname123456789012345678901", false],
13
- ["invalid@name", false],
14
- ["invalid!name", false],
15
- ["invalid#name", false],
16
- ];
17
-
18
- for (const [name, expected] of casesWithExpectation) {
19
- test(`returns ${expected} for THORName "${name}"`, () => {
20
- const result = validateTNS(name);
21
- expect(result).toBe(expected);
22
- });
23
- }
5
+ test("valid names with alphanumeric and allowed special chars", () => {
6
+ expect(validateTNS("validname")).toBe(true);
7
+ expect(validateTNS("valid-name")).toBe(true);
8
+ expect(validateTNS("valid_name")).toBe(true);
9
+ expect(validateTNS("valid+name")).toBe(true);
10
+ expect(validateTNS("name123")).toBe(true);
11
+ expect(validateTNS("UPPERCASE")).toBe(true);
12
+ expect(validateTNS("MixedCase123")).toBe(true);
13
+ });
14
+
15
+ test("invalid names exceeding 30 characters", () => {
16
+ expect(validateTNS("toolongname123456789012345678901")).toBe(false);
17
+ expect(validateTNS("a".repeat(31))).toBe(false);
18
+ });
19
+
20
+ test("invalid names with disallowed characters", () => {
21
+ expect(validateTNS("invalid@name")).toBe(false);
22
+ expect(validateTNS("invalid!name")).toBe(false);
23
+ expect(validateTNS("invalid#name")).toBe(false);
24
+ expect(validateTNS("invalid$name")).toBe(false);
25
+ expect(validateTNS("invalid%name")).toBe(false);
26
+ expect(validateTNS("name with space")).toBe(false);
27
+ });
28
+
29
+ test("edge cases", () => {
30
+ expect(validateTNS("a")).toBe(true);
31
+ expect(validateTNS("a".repeat(30))).toBe(true);
32
+ expect(validateTNS("")).toBe(false);
33
+ });
34
+ });
35
+
36
+ describe("validateIdentifier", () => {
37
+ test("valid chain.ticker format", () => {
38
+ expect(validateIdentifier("BTC.BTC")).toBe(true);
39
+ expect(validateIdentifier("ETH.ETH")).toBe(true);
40
+ expect(validateIdentifier("AVAX.USDC-0x123")).toBe(true);
41
+ expect(validateIdentifier("eth.eth")).toBe(true);
42
+ });
43
+
44
+ test("valid synth format with /", () => {
45
+ expect(validateIdentifier("ETH/ETH")).toBe(true);
46
+ expect(validateIdentifier("BTC/BTC")).toBe(true);
47
+ expect(validateIdentifier("THOR.ETH/ETH")).toBe(true);
48
+ });
49
+
50
+ test("valid trade asset format with ~", () => {
51
+ expect(validateIdentifier("THOR.ETH~ETH")).toBe(true);
52
+ expect(validateIdentifier("THOR.BTC~BTC")).toBe(true);
53
+ expect(validateIdentifier("THOR.ETH~USDC-0xa5f2211b9b8170f694421f2046281775e8468044")).toBe(true);
54
+ });
55
+
56
+ test("valid NEAR address formats", () => {
57
+ expect(validateIdentifier("NEAR.wNEAR-wrap.near")).toBe(true);
58
+ expect(validateIdentifier("NEAR.USDC-17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1")).toBe(true);
59
+ expect(validateIdentifier("NEAR.ETH-eth.bridge.near")).toBe(true);
60
+ });
61
+
62
+ test("valid Maya synth format", () => {
63
+ expect(validateIdentifier("MAYA.ETH/ETH")).toBe(true);
64
+ expect(validateIdentifier("MAYA.BTC/BTC")).toBe(true);
65
+ });
66
+
67
+ test("throws for invalid chain", () => {
68
+ expect(() => validateIdentifier("INVALID.TOKEN")).toThrow("helpers_invalid_identifier");
69
+ expect(() => validateIdentifier("XXX.YYY")).toThrow("helpers_invalid_identifier");
70
+ });
71
+
72
+ test("throws for empty or malformed identifier", () => {
73
+ expect(() => validateIdentifier("")).toThrow("helpers_invalid_identifier");
74
+ expect(() => validateIdentifier("NOCHAIN")).toThrow("helpers_invalid_identifier");
75
+ });
76
+
77
+ test("accepts identifier with only chain (validates chain exists)", () => {
78
+ // The validateIdentifier function only checks if the chain part is valid
79
+ // It doesn't enforce the full <Chain>.<Ticker> format
80
+ expect(validateIdentifier("ETH")).toBe(true);
81
+ expect(validateIdentifier("ETH.")).toBe(true);
82
+ expect(validateIdentifier("BTC")).toBe(true);
83
+ });
24
84
  });