Skip to content

Commit 3bd5ae8

Browse files
authored
Merge pull request #42 from macalinao/igm/token-math
feat(token-utils): add tmath module for TokenAmount arithmetic operations
2 parents d6da5f0 + 5ff365a commit 3bd5ae8

File tree

6 files changed

+432
-2
lines changed

6 files changed

+432
-2
lines changed

.changeset/cold-clowns-happen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@macalinao/token-utils": patch
3+
---
4+
5+
Add tmath module

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@
2323
"source.fixAll.eslint": "explicit",
2424
"source.fixAll.biome": "explicit",
2525
"source.action.organizeImports.biome": "explicit"
26-
}
26+
},
27+
"cSpell.words": ["dnum", "tmath", "TKNA", "TKNB"]
2728
}

packages/token-utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ export * from "./create-token-info.js";
33
export * from "./format-token-amount.js";
44
export * from "./native-sol.js";
55
export * from "./parse-token-amount.js";
6+
export * as tmath from "./tmath.js";
67
export * from "./token-amount-to-bigint.js";
78
export * from "./types.js";
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
import { describe, expect, it } from "bun:test";
2+
import { address } from "@solana/kit";
3+
import * as tmath from "./tmath.js";
4+
import type { TokenAmount } from "./types.js";
5+
6+
describe("tmath", () => {
7+
const mockTokenA = {
8+
mint: address("TokenA1111111111111111111111111111111111111"),
9+
name: "Token A",
10+
symbol: "TKNA",
11+
decimals: 9 as const,
12+
};
13+
14+
const mockTokenB = {
15+
mint: address("TokenB2222222222222222222222222222222222222"),
16+
name: "Token B",
17+
symbol: "TKNB",
18+
decimals: 6 as const,
19+
};
20+
21+
const mockTokenASameDecimals = {
22+
mint: address("TokenA1111111111111111111111111111111111111"),
23+
name: "Token A",
24+
symbol: "TKNA",
25+
decimals: 6 as const,
26+
};
27+
28+
describe("add", () => {
29+
it("should add two token amounts with the same token", () => {
30+
const a: TokenAmount = {
31+
token: mockTokenA,
32+
amount: [1000000000n, 9],
33+
};
34+
const b: TokenAmount = {
35+
token: mockTokenA,
36+
amount: [2000000000n, 9],
37+
};
38+
39+
const result = tmath.add(a, b);
40+
41+
expect(result.token).toBe(mockTokenA);
42+
expect(result.amount[0]).toBe(3000000000n);
43+
expect(result.amount[1]).toBe(9);
44+
});
45+
46+
it("should throw error when adding tokens with different mints", () => {
47+
const a: TokenAmount = {
48+
token: mockTokenA,
49+
amount: [1000000000n, 9],
50+
};
51+
const b: TokenAmount = {
52+
token: mockTokenB,
53+
amount: [2000000n, 6],
54+
};
55+
56+
expect(() => tmath.add(a, b)).toThrow(
57+
"Token mint mismatch: TokenA1111111111111111111111111111111111111 !== TokenB2222222222222222222222222222222222222",
58+
);
59+
});
60+
61+
it("should throw error when adding tokens with different decimals", () => {
62+
const a: TokenAmount = {
63+
token: mockTokenA,
64+
amount: [1000000000n, 9],
65+
};
66+
const b: TokenAmount = {
67+
token: mockTokenASameDecimals,
68+
amount: [2000000n, 6],
69+
};
70+
71+
expect(() => tmath.add(a, b)).toThrow("Token decimals mismatch: 9 !== 6");
72+
});
73+
74+
it("should handle adding zero amounts", () => {
75+
const a: TokenAmount = {
76+
token: mockTokenA,
77+
amount: [1000000000n, 9],
78+
};
79+
const b: TokenAmount = {
80+
token: mockTokenA,
81+
amount: [0n, 9],
82+
};
83+
84+
const result = tmath.add(a, b);
85+
86+
expect(result.amount[0]).toBe(1000000000n);
87+
expect(result.amount[1]).toBe(9);
88+
});
89+
});
90+
91+
describe("sub", () => {
92+
it("should subtract two token amounts with the same token", () => {
93+
const a: TokenAmount = {
94+
token: mockTokenA,
95+
amount: [5000000000n, 9],
96+
};
97+
const b: TokenAmount = {
98+
token: mockTokenA,
99+
amount: [2000000000n, 9],
100+
};
101+
102+
const result = tmath.sub(a, b);
103+
104+
expect(result.token).toBe(mockTokenA);
105+
expect(result.amount[0]).toBe(3000000000n);
106+
expect(result.amount[1]).toBe(9);
107+
});
108+
109+
it("should throw error when subtracting tokens with different mints", () => {
110+
const a: TokenAmount = {
111+
token: mockTokenA,
112+
amount: [5000000000n, 9],
113+
};
114+
const b: TokenAmount = {
115+
token: mockTokenB,
116+
amount: [2000000n, 6],
117+
};
118+
119+
expect(() => tmath.sub(a, b)).toThrow(
120+
"Token mint mismatch: TokenA1111111111111111111111111111111111111 !== TokenB2222222222222222222222222222222222222",
121+
);
122+
});
123+
124+
it("should handle subtracting to zero", () => {
125+
const a: TokenAmount = {
126+
token: mockTokenA,
127+
amount: [1000000000n, 9],
128+
};
129+
const b: TokenAmount = {
130+
token: mockTokenA,
131+
amount: [1000000000n, 9],
132+
};
133+
134+
const result = tmath.sub(a, b);
135+
136+
expect(result.amount[0]).toBe(0n);
137+
expect(result.amount[1]).toBe(9);
138+
});
139+
140+
it("should handle negative results", () => {
141+
const a: TokenAmount = {
142+
token: mockTokenA,
143+
amount: [1000000000n, 9],
144+
};
145+
const b: TokenAmount = {
146+
token: mockTokenA,
147+
amount: [2000000000n, 9],
148+
};
149+
150+
const result = tmath.sub(a, b);
151+
152+
expect(result.amount[0]).toBe(-1000000000n);
153+
expect(result.amount[1]).toBe(9);
154+
});
155+
});
156+
157+
describe("mul", () => {
158+
it("should multiply two token amounts with the same token", () => {
159+
const a: TokenAmount = {
160+
token: mockTokenA,
161+
amount: [2000000000n, 9],
162+
};
163+
const b: TokenAmount = {
164+
token: mockTokenA,
165+
amount: [3000000000n, 9],
166+
};
167+
168+
const result = tmath.mul(a, b);
169+
170+
expect(result.token).toBe(mockTokenA);
171+
// 2 * 3 = 6
172+
expect(result.amount[0]).toBe(6000000000n);
173+
expect(result.amount[1]).toBe(9);
174+
});
175+
176+
it("should throw error when multiplying tokens with different mints", () => {
177+
const a: TokenAmount = {
178+
token: mockTokenA,
179+
amount: [2000000000n, 9],
180+
};
181+
const b: TokenAmount = {
182+
token: mockTokenB,
183+
amount: [3000000n, 6],
184+
};
185+
186+
expect(() => tmath.mul(a, b)).toThrow(
187+
"Token mint mismatch: TokenA1111111111111111111111111111111111111 !== TokenB2222222222222222222222222222222222222",
188+
);
189+
});
190+
191+
it("should handle multiplication by zero", () => {
192+
const a: TokenAmount = {
193+
token: mockTokenA,
194+
amount: [5000000000n, 9],
195+
};
196+
const b: TokenAmount = {
197+
token: mockTokenA,
198+
amount: [0n, 9],
199+
};
200+
201+
const result = tmath.mul(a, b);
202+
203+
expect(result.amount[0]).toBe(0n);
204+
expect(result.amount[1]).toBe(9);
205+
});
206+
207+
it("should handle multiplication by one", () => {
208+
const a: TokenAmount = {
209+
token: mockTokenA,
210+
amount: [5000000000n, 9],
211+
};
212+
const b: TokenAmount = {
213+
token: mockTokenA,
214+
amount: [1000000000n, 9], // 1.0 with 9 decimals
215+
};
216+
217+
const result = tmath.mul(a, b);
218+
219+
expect(result.amount[0]).toBe(5000000000n);
220+
expect(result.amount[1]).toBe(9);
221+
});
222+
});
223+
224+
describe("div", () => {
225+
it("should divide two token amounts with the same token", () => {
226+
const a: TokenAmount = {
227+
token: mockTokenA,
228+
amount: [6000000000n, 9],
229+
};
230+
const b: TokenAmount = {
231+
token: mockTokenA,
232+
amount: [2000000000n, 9],
233+
};
234+
235+
const result = tmath.div(a, b);
236+
237+
expect(result.token).toBe(mockTokenA);
238+
// 6 / 2 = 3
239+
expect(result.amount[0]).toBe(3000000000n);
240+
expect(result.amount[1]).toBe(9);
241+
});
242+
243+
it("should throw error when dividing tokens with different mints", () => {
244+
const a: TokenAmount = {
245+
token: mockTokenA,
246+
amount: [6000000000n, 9],
247+
};
248+
const b: TokenAmount = {
249+
token: mockTokenB,
250+
amount: [2000000n, 6],
251+
};
252+
253+
expect(() => tmath.div(a, b)).toThrow(
254+
"Token mint mismatch: TokenA1111111111111111111111111111111111111 !== TokenB2222222222222222222222222222222222222",
255+
);
256+
});
257+
258+
it("should handle division by one", () => {
259+
const a: TokenAmount = {
260+
token: mockTokenA,
261+
amount: [5000000000n, 9],
262+
};
263+
const b: TokenAmount = {
264+
token: mockTokenA,
265+
amount: [1000000000n, 9], // 1.0 with 9 decimals
266+
};
267+
268+
const result = tmath.div(a, b);
269+
270+
expect(result.amount[0]).toBe(5000000000n);
271+
expect(result.amount[1]).toBe(9);
272+
});
273+
274+
it("should handle division resulting in fractions", () => {
275+
const a: TokenAmount = {
276+
token: mockTokenA,
277+
amount: [5000000000n, 9],
278+
};
279+
const b: TokenAmount = {
280+
token: mockTokenA,
281+
amount: [2000000000n, 9],
282+
};
283+
284+
const result = tmath.div(a, b);
285+
286+
// 5 / 2 = 2.5
287+
expect(result.amount[0]).toBe(2500000000n);
288+
expect(result.amount[1]).toBe(9);
289+
});
290+
291+
it("should handle division of zero", () => {
292+
const a: TokenAmount = {
293+
token: mockTokenA,
294+
amount: [0n, 9],
295+
};
296+
const b: TokenAmount = {
297+
token: mockTokenA,
298+
amount: [5000000000n, 9],
299+
};
300+
301+
const result = tmath.div(a, b);
302+
303+
expect(result.amount[0]).toBe(0n);
304+
expect(result.amount[1]).toBe(9);
305+
});
306+
});
307+
308+
describe("edge cases", () => {
309+
it("should maintain precision with large numbers", () => {
310+
const a: TokenAmount = {
311+
token: mockTokenA,
312+
amount: [999999999999999999n, 9],
313+
};
314+
const b: TokenAmount = {
315+
token: mockTokenA,
316+
amount: [1n, 9],
317+
};
318+
319+
const result = tmath.add(a, b);
320+
321+
expect(result.amount[0]).toBe(1000000000000000000n);
322+
expect(result.amount[1]).toBe(9);
323+
});
324+
325+
it("should handle operations with different decimal representations of same value", () => {
326+
const a: TokenAmount = {
327+
token: mockTokenA,
328+
amount: [1500000000n, 9], // 1.5
329+
};
330+
const b: TokenAmount = {
331+
token: mockTokenA,
332+
amount: [500000000n, 9], // 0.5
333+
};
334+
335+
const addResult = tmath.add(a, b);
336+
expect(addResult.amount[0]).toBe(2000000000n); // 2.0
337+
338+
const subResult = tmath.sub(a, b);
339+
expect(subResult.amount[0]).toBe(1000000000n); // 1.0
340+
341+
const mulResult = tmath.mul(a, b);
342+
expect(mulResult.amount[0]).toBe(750000000n); // 0.75
343+
344+
const divResult = tmath.div(a, b);
345+
expect(divResult.amount[0]).toBe(3000000000n); // 3.0
346+
});
347+
});
348+
});

0 commit comments

Comments
 (0)