As zod validation is really important for using bignumber values along standard ts types, it would be great to include zod or Standard Schema validation to come out of the box. Here is what I use for zod, feel free to use that as a base
import { BigNumber } from "bignumber.js"
import { z } from "zod"
/**
* Acceptable input types for a BigNumber.
*/
export type BigNumberLike = BigNumber.Value | BigNumber | null | undefined
/**
* Type guard to check if a value is a BigNumber instance.
*/
export function isBigNumber(v: unknown): v is BigNumber {
return v instanceof BigNumber
}
/**
* Normalize any BigNumberLike into a BigNumber instance.
*/
export function toBigNumber(v: BigNumberLike): BigNumber {
return v instanceof BigNumber ? v : new BigNumber(v as BigNumber.Value)
}
const ZodBigNumberSchemaRaw = z.instanceof(BigNumber, {
message: "Expected a BigNumber instance",
})
/**
* Base Zod schema for raw BigNumber input (string | number | BigNumber).
*
* z.union() ensures val is either BigNumber | string | number | bigint
* If it's already a BigNumber instance (same constructor), return as-is.
*/
export const BigNumberInputSchema = z.union([
z.string(),
z.number(),
z.bigint(),
ZodBigNumberSchemaRaw,
])
/**
* Coercing: accept strings/numbers/BigNumber and always transform to a BigNumber instance.
* This is convenient for I/O (HTTP, JSON, forms).
*/
export const CoercibleBigNumberSchema = BigNumberInputSchema.transform((val) =>
toBigNumber(val),
)
export const DecimalStringToBigNumber = z.string().transform((val) => toBigNumber(val))
export const DecimalStringOrNullToBigNumber = z
.union([z.string(), z.null()])
.transform((val) => toBigNumber(val))
export const DecimalNumberToBigNumber = z.number().transform((val) => toBigNumber(val))
/**
* Core BigNumber schema: parses (string | number | BigNumber) -> BigNumber.
* Ensures finite value (no Infinity / NaN).
*/
export const BigNumberSchema = CoercibleBigNumberSchema.refine((bn) => bn.isFinite(), {
message: "BigNumber must be finite",
})
export function ZodBigNumberSchema({ coerce = false } = {}) {
const base = coerce ? CoercibleBigNumberSchema : ZodBigNumberSchemaRaw
return Object.assign(base, {
biggerThan(val: BigNumberLike, message?: string) {
return base.refine((num) => isDefined(val) && num.isGreaterThan(val), {
message: message ?? `Value must be greater than ${val?.toString()}`,
})
},
lessThan(val: BigNumberLike, message?: string) {
return base.refine((num) => isDefined(val) && num.isLessThan(val), {
message: message ?? `Value must be less than ${val?.toString()}`,
})
},
positive(message?: string) {
return base.refine((val) => val.isPositive(), {
message: message ?? `Value must be positive`,
})
},
negative(message?: string) {
return base.refine((val) => val.isNegative(), {
message: message ?? `Value must be negative`,
})
},
nonPositive(message?: string) {
return base.refine((val) => !val.isPositive(), {
message: message ?? `Value must be non-positive`,
})
},
nonNegative(message?: string) {
return base.refine((val) => !val.isNegative(), {
message: message ?? `Value must be non-negative`,
})
},
finite(message?: string) {
return base.refine((val) => val.isFinite(), {
message: message ?? `Value must be valid number`,
})
},
int(message?: string) {
return base.refine((val) => val.isInteger(), {
message: message ?? `Value must be an integer`,
})
},
})
}
export type ZodBigNumber = z.infer<typeof ZodBigNumberSchema>
/**
* Robust duck-typed schema: verifies "BigNumber-ness" without instanceof.
* Useful when data may come from another BigNumber constructor (clone/another()) or be a plain object that lost prototype.
*
* Detection strategy:
* - If obj has a truthy `isBigNumber` or `_isBigNumber` prototype flag (bignumber.js uses a prototype flag),
* accept it as BigNumber-like.
* - Or, if it has a `toString` and typical BigNumber instance methods like `plus`/`toFixed`, accept it.
*/
export const BigNumberLikeSchema = z.custom<BigNumber>(
(value) => {
if (!value || typeof value !== "object") {
return false
}
// 1) fast path: same constructor
if (isBigNumber(value)) {
return true
}
// 2) prototype flag added in some bignumber.js versions
if ((value as any).isBigNumber === true || (value as any)._isBigNumber === true) {
return true
}
// 3) duck-typing: minimal set of methods/props expected on a BigNumber instance
const hasDuck =
typeof (value as any).toString === "function" &&
typeof (value as any).plus === "function" &&
typeof (value as any).toFixed === "function"
return hasDuck
},
{ message: "Expected a BigNumber-like value" },
)
// todo see if NumericLike and BigNumberInputSchema can be unified
// Accept a number or a numeric string (e.g. "10", "0.5")
export const NumericLike = z.union([
z.number(),
ZodBigNumberSchema(),
z.string().regex(/^-?\d+(\.\d+)?$/, "Expected a numeric string"),
])
As zod validation is really important for using bignumber values along standard ts types, it would be great to include zod or Standard Schema validation to come out of the box. Here is what I use for
zod, feel free to use that as a base