Skip to content

Commit d588eb5

Browse files
authored
fix(sdk): fix scale schema compat with zod-to-json-schema and type inference (#8602)
1 parent 2e151e5 commit d588eb5

4 files changed

Lines changed: 54 additions & 30 deletions

File tree

.changeset/calm-foxes-sing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hyperlane-xyz/sdk': patch
3+
---
4+
5+
Replaced z.coerce.bigint().positive() with ZBigNumberish.refine() in TokenMetadataSchema scale field for zod-to-json-schema compatibility. Fixed validateZodResult generic to correctly return output type for schemas with transforms.

typescript/sdk/src/token/types.ts

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
IsmType,
1212
OffchainLookupIsmConfigSchema,
1313
} from '../ism/types.js';
14-
import { ZHash } from '../metadata/customZodTypes.js';
14+
import { ZBigNumberish, ZHash } from '../metadata/customZodTypes.js';
1515
import {
1616
DerivedRouterConfig,
1717
GasRouterConfigSchema,
@@ -34,31 +34,13 @@ export const contractVersionMatchesDependency = (version: string) => {
3434
export const VERSION_ERROR_MESSAGE = `Contract version must match the @hyperlane-xyz/core dependency version (${CONTRACTS_PACKAGE_VERSION})`;
3535

3636
/**
37-
* Coerces bigint or string to bigint with positivity check.
38-
* Needed because bigints are serialised as strings in JSON/YAML
39-
* and must be converted back on parse. Uses .refine() instead of
40-
* .positive() to avoid bigint values in zod-to-json-schema output.
37+
* Coerces string to bigint at runtime with positivity check.
38+
* Needed because bigints are serialized as strings in JSON/YAML
39+
* and must be converted back on parse.
4140
*/
42-
const PositiveBigIntFromString = z
43-
.union([
44-
z.bigint(),
45-
z
46-
.string()
47-
.refine(
48-
(s) => {
49-
try {
50-
BigInt(s);
51-
return true;
52-
} catch {
53-
return false;
54-
}
55-
},
56-
{ message: 'Must be a bigint-compatible string' },
57-
)
58-
.transform((s) => BigInt(s)),
59-
])
60-
.pipe(z.bigint())
61-
.refine((n) => n > 0n, { message: 'Must be positive' });
41+
const PositiveZBigNumberish = ZBigNumberish.refine((n) => n > 0n, {
42+
message: 'Must be positive',
43+
});
6244

6345
export const TokenMetadataSchema = z.object({
6446
name: z.string(),
@@ -72,8 +54,8 @@ export const TokenMetadataSchema = z.object({
7254
denominator: z.number().int().gt(0),
7355
}),
7456
z.object({
75-
numerator: PositiveBigIntFromString,
76-
denominator: PositiveBigIntFromString,
57+
numerator: PositiveZBigNumberish,
58+
denominator: PositiveZBigNumberish,
7759
}),
7860
])
7961
.optional(),
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { expect } from 'chai';
2+
import { z } from 'zod';
3+
4+
import { validateZodResult } from './schemas.js';
5+
6+
describe('validateZodResult', () => {
7+
it('returns parsed data on success', () => {
8+
const schema = z.object({ name: z.string() });
9+
const result = schema.safeParse({ name: 'hello' });
10+
expect(validateZodResult(result)).to.deep.equal({ name: 'hello' });
11+
});
12+
13+
it('throws on validation failure', () => {
14+
const schema = z.object({ name: z.string() });
15+
const result = schema.safeParse({ name: 123 });
16+
expect(() => validateZodResult(result)).to.throw();
17+
});
18+
19+
it('returns output type for schemas with transforms', () => {
20+
const schema = z.object({
21+
value: z.string().transform((s) => parseInt(s, 10)),
22+
});
23+
const result = schema.safeParse({ value: '42' });
24+
const parsed = validateZodResult(result);
25+
expect(parsed.value).to.equal(42);
26+
expect(typeof parsed.value).to.equal('number');
27+
});
28+
29+
it('handles schemas with bigint coercion', () => {
30+
const schema = z.object({
31+
amount: z.bigint().or(z.string().regex(/^\d+$/).transform(BigInt)),
32+
});
33+
const result = schema.safeParse({ amount: '1000000000000' });
34+
const parsed = validateZodResult(result);
35+
expect(parsed.amount).to.equal(1000000000000n);
36+
});
37+
});

typescript/sdk/src/utils/schemas.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ export function isCompliant<S extends z.ZodTypeAny>(schema: S) {
77
schema.safeParse(config).success;
88
}
99

10-
export function validateZodResult<T>(
11-
result: SafeParseReturnType<T, T>,
10+
export function validateZodResult<I, O>(
11+
result: SafeParseReturnType<I, O>,
1212
desc: string = 'config',
13-
): T {
13+
): O {
1414
if (!result.success) {
1515
rootLogger.warn(`Invalid ${desc}`, result.error);
1616
throw new Error(`Invalid desc: ${result.error.toString()}`);

0 commit comments

Comments
 (0)