Skip to content

Commit fec7335

Browse files
authored
Merge pull request #113 from macalinao/igm/bps
Add BPS schema
2 parents 7e29f95 + 7aa4e36 commit fec7335

File tree

9 files changed

+327
-4
lines changed

9 files changed

+327
-4
lines changed

.changeset/clear-taxis-go.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@macalinao/zod-solana": patch
3+
---
4+
5+
Add BPS schema

.changeset/fluffy-dingos-count.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@macalinao/token-utils": patch
3+
"@macalinao/zod-solana": patch
4+
"@macalinao/quarry": patch
5+
---
6+
7+
Eslint 9.38 force

bun.lock

Lines changed: 51 additions & 1 deletion
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"apps/*"
1111
],
1212
"catalog": {
13-
"eslint": "^9.38.0",
13+
"eslint": "=9.38.0",
1414
"typescript": "^5.9.3",
1515
"@macalinao/eslint-config": "^7.0.1",
1616
"@macalinao/eslint-config-react": "^6.0.2",

packages/quarry/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@
6969
"@macalinao/tsconfig": "catalog:",
7070
"@solana/kit": "catalog:",
7171
"@types/bun": "catalog:",
72-
"typescript": "catalog:"
72+
"typescript": "catalog:",
73+
"eslint": "catalog:"
7374
},
7475
"peerDependencies": {
7576
"@solana/kit": "catalog:"

packages/token-utils/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"@macalinao/tsconfig": "catalog:",
5252
"@solana/kit": "catalog:",
5353
"@types/bun": "catalog:",
54-
"typescript": "catalog:"
54+
"typescript": "catalog:",
55+
"eslint": "catalog:"
5556
},
5657
"peerDependencies": {
5758
"@solana/kit": "catalog:"
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import { describe, expect, it } from "bun:test";
2+
import { bpsSchema } from "./bps-schema.js";
3+
4+
describe("bpsSchema", () => {
5+
describe("valid values", () => {
6+
it("should accept 0 (0%)", () => {
7+
const result = bpsSchema.safeParse(0);
8+
expect(result.success).toBe(true);
9+
if (result.success) {
10+
expect(result.data).toBe(0);
11+
}
12+
});
13+
14+
it("should accept 1 (0.01%)", () => {
15+
const result = bpsSchema.safeParse(1);
16+
expect(result.success).toBe(true);
17+
if (result.success) {
18+
expect(result.data).toBe(1);
19+
}
20+
});
21+
22+
it("should accept 50 (0.5%)", () => {
23+
const result = bpsSchema.safeParse(50);
24+
expect(result.success).toBe(true);
25+
if (result.success) {
26+
expect(result.data).toBe(50);
27+
}
28+
});
29+
30+
it("should accept 100 (1%)", () => {
31+
const result = bpsSchema.safeParse(100);
32+
expect(result.success).toBe(true);
33+
if (result.success) {
34+
expect(result.data).toBe(100);
35+
}
36+
});
37+
38+
it("should accept 500 (5%)", () => {
39+
const result = bpsSchema.safeParse(500);
40+
expect(result.success).toBe(true);
41+
if (result.success) {
42+
expect(result.data).toBe(500);
43+
}
44+
});
45+
46+
it("should accept 1000 (10%)", () => {
47+
const result = bpsSchema.safeParse(1000);
48+
expect(result.success).toBe(true);
49+
if (result.success) {
50+
expect(result.data).toBe(1000);
51+
}
52+
});
53+
54+
it("should accept 5000 (50%)", () => {
55+
const result = bpsSchema.safeParse(5000);
56+
expect(result.success).toBe(true);
57+
if (result.success) {
58+
expect(result.data).toBe(5000);
59+
}
60+
});
61+
62+
it("should accept 10000 (100%)", () => {
63+
const result = bpsSchema.safeParse(10000);
64+
expect(result.success).toBe(true);
65+
if (result.success) {
66+
expect(result.data).toBe(10000);
67+
}
68+
});
69+
70+
it("should accept various valid values", () => {
71+
const validValues = [10, 25, 75, 150, 300, 750, 2500, 7500, 9999];
72+
for (const value of validValues) {
73+
const result = bpsSchema.safeParse(value);
74+
expect(result.success).toBe(true);
75+
if (result.success) {
76+
expect(result.data).toBe(value);
77+
}
78+
}
79+
});
80+
});
81+
82+
describe("invalid values", () => {
83+
it("should reject -1", () => {
84+
const result = bpsSchema.safeParse(-1);
85+
expect(result.success).toBe(false);
86+
if (!result.success) {
87+
expect(result.error.issues[0]?.message).toContain(">=0");
88+
}
89+
});
90+
91+
it("should reject -100", () => {
92+
const result = bpsSchema.safeParse(-100);
93+
expect(result.success).toBe(false);
94+
if (!result.success) {
95+
expect(result.error.issues[0]?.message).toContain(">=0");
96+
}
97+
});
98+
99+
it("should reject 10001 (max + 1)", () => {
100+
const result = bpsSchema.safeParse(10001);
101+
expect(result.success).toBe(false);
102+
if (!result.success) {
103+
expect(result.error.issues[0]?.message).toContain("<=10000");
104+
}
105+
});
106+
107+
it("should reject 50000", () => {
108+
const result = bpsSchema.safeParse(50000);
109+
expect(result.success).toBe(false);
110+
if (!result.success) {
111+
expect(result.error.issues[0]?.message).toContain("<=10000");
112+
}
113+
});
114+
115+
it("should reject 0.5 (non-integer)", () => {
116+
const result = bpsSchema.safeParse(0.5);
117+
expect(result.success).toBe(false);
118+
if (!result.success) {
119+
expect(result.error.issues[0]?.message).toContain("expected int");
120+
}
121+
});
122+
123+
it("should reject 100.5 (non-integer)", () => {
124+
const result = bpsSchema.safeParse(100.5);
125+
expect(result.success).toBe(false);
126+
if (!result.success) {
127+
expect(result.error.issues[0]?.message).toContain("expected int");
128+
}
129+
});
130+
131+
it("should reject 9999.99 (non-integer)", () => {
132+
const result = bpsSchema.safeParse(9999.99);
133+
expect(result.success).toBe(false);
134+
if (!result.success) {
135+
expect(result.error.issues[0]?.message).toContain("expected int");
136+
}
137+
});
138+
139+
it("should reject NaN", () => {
140+
const result = bpsSchema.safeParse(Number.NaN);
141+
expect(result.success).toBe(false);
142+
});
143+
144+
it("should reject Infinity", () => {
145+
const result = bpsSchema.safeParse(Number.POSITIVE_INFINITY);
146+
expect(result.success).toBe(false);
147+
});
148+
149+
it("should reject -Infinity", () => {
150+
const result = bpsSchema.safeParse(Number.NEGATIVE_INFINITY);
151+
expect(result.success).toBe(false);
152+
});
153+
154+
it("should reject string values", () => {
155+
const result = bpsSchema.safeParse("500");
156+
expect(result.success).toBe(false);
157+
});
158+
159+
it("should reject null", () => {
160+
const result = bpsSchema.safeParse(null);
161+
expect(result.success).toBe(false);
162+
});
163+
164+
it("should reject undefined", () => {
165+
const result = bpsSchema.safeParse(undefined);
166+
expect(result.success).toBe(false);
167+
});
168+
169+
it("should reject objects", () => {
170+
const result = bpsSchema.safeParse({ value: 500 });
171+
expect(result.success).toBe(false);
172+
});
173+
174+
it("should reject arrays", () => {
175+
const result = bpsSchema.safeParse([500]);
176+
expect(result.success).toBe(false);
177+
});
178+
179+
it("should reject boolean values", () => {
180+
const result = bpsSchema.safeParse(true);
181+
expect(result.success).toBe(false);
182+
});
183+
});
184+
185+
describe("edge cases", () => {
186+
it("should handle type coercion correctly", () => {
187+
const result = bpsSchema.safeParse(10000.0);
188+
expect(result.success).toBe(true);
189+
if (result.success) {
190+
expect(result.data).toBe(10000);
191+
}
192+
});
193+
194+
it("should reject very small negative decimals", () => {
195+
const result = bpsSchema.safeParse(-0.1);
196+
expect(result.success).toBe(false);
197+
});
198+
199+
it("should reject very large numbers", () => {
200+
const result = bpsSchema.safeParse(Number.MAX_SAFE_INTEGER);
201+
expect(result.success).toBe(false);
202+
});
203+
204+
it("should handle scientific notation within range", () => {
205+
const result = bpsSchema.safeParse(1e4); // 10000
206+
expect(result.success).toBe(true);
207+
if (result.success) {
208+
expect(result.data).toBe(10000);
209+
}
210+
});
211+
212+
it("should reject scientific notation out of range", () => {
213+
const result = bpsSchema.safeParse(1e5); // 100000
214+
expect(result.success).toBe(false);
215+
});
216+
});
217+
218+
describe("basis points specific tests", () => {
219+
it("should accept common percentage values in basis points", () => {
220+
const commonPercentages = [
221+
{ bps: 1, percentage: "0.01%" },
222+
{ bps: 25, percentage: "0.25%" },
223+
{ bps: 50, percentage: "0.5%" },
224+
{ bps: 100, percentage: "1%" },
225+
{ bps: 250, percentage: "2.5%" },
226+
{ bps: 500, percentage: "5%" },
227+
{ bps: 1000, percentage: "10%" },
228+
{ bps: 2500, percentage: "25%" },
229+
{ bps: 5000, percentage: "50%" },
230+
{ bps: 7500, percentage: "75%" },
231+
{ bps: 10000, percentage: "100%" },
232+
];
233+
234+
for (const { bps } of commonPercentages) {
235+
const result = bpsSchema.safeParse(bps);
236+
expect(result.success).toBe(true);
237+
if (result.success) {
238+
expect(result.data).toBe(bps);
239+
}
240+
}
241+
});
242+
243+
it("should have correct description", () => {
244+
expect(bpsSchema.description).toBe("Basis points (0-10000)");
245+
});
246+
});
247+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as z from "zod";
2+
3+
/**
4+
* Zod schema for basis points (0-10000)
5+
*/
6+
export const bpsSchema: z.ZodNumber = z
7+
.number()
8+
.int()
9+
.min(0)
10+
.max(10000)
11+
.describe("Basis points (0-10000)");

packages/zod-solana/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { addressSchema } from "./address-schema.js";
2+
export { bpsSchema } from "./bps-schema.js";
23
export { U8_MAX, U16_MAX, U32_MAX, U64_MAX } from "./constants.js";
34
export {
45
type TokenMetadata,

0 commit comments

Comments
 (0)