src/signing/types.ts
v0.2.0 · 4.6 KB
// EIP-712 domain and typed-data definitions of the Nado gateway.
//
// Domain: `{ name: 'Nado', version: '0.0.1', chainId, verifyingContract }`. For `place_order` the
// verifying contract is `address(productId)` (the product id written as a 20-byte address); for every
// other execute (`cancel_orders`, `link_signer`, ...) it is `endpoint_addr` from the `contracts` query.
// Domains are separated: a signature made under product 2 does not verify under product 18 (checked with
// viem `verifyTypedData`; orders signed this way executed on mainnet, 2026-07-24).
/** `0x`-prefixed hex string. */
export type Hex = `0x${string}`;
/** 20-byte address as lower-case hex. */
export type Address = `0x${string}`;
export const NADO_DOMAIN_NAME = 'Nado';
export const NADO_DOMAIN_VERSION = '0.0.1';
/** Typed-data domain of every Nado execute. */
export interface NadoDomain {
readonly name: typeof NADO_DOMAIN_NAME;
readonly version: typeof NADO_DOMAIN_VERSION;
readonly chainId: number;
readonly verifyingContract: Address;
}
/** Builds the domain for a given chain id and verifying contract. */
export function nadoDomain(chainId: number, verifyingContract: Address): NadoDomain {
if (!Number.isSafeInteger(chainId) || chainId <= 0)
throw new RangeError(`chainId must be a positive integer, got ${String(chainId)}`);
return { name: NADO_DOMAIN_NAME, version: NADO_DOMAIN_VERSION, chainId, verifyingContract };
}
/** `Order` struct of `place_order`. `amount` is signed: positive = buy, negative = sell. */
export const ORDER_TYPES = {
Order: [
{ name: 'sender', type: 'bytes32' },
{ name: 'priceX18', type: 'int128' },
{ name: 'amount', type: 'int128' },
{ name: 'expiration', type: 'uint64' },
{ name: 'nonce', type: 'uint64' },
{ name: 'appendix', type: 'uint128' },
],
} as const;
/** `Cancellation` struct of `cancel_orders` (by digests). */
export const CANCELLATION_TYPES = {
Cancellation: [
{ name: 'sender', type: 'bytes32' },
{ name: 'productIds', type: 'uint32[]' },
{ name: 'digests', type: 'bytes32[]' },
{ name: 'nonce', type: 'uint64' },
],
} as const;
/**
* `CancellationProducts` struct (cancel every order on the listed products with one signature).
*
* @experimental Present in the signing schema, not verified live; the execute body shape and the
* confirmation response were not observed.
*/
export const CANCELLATION_PRODUCTS_TYPES = {
CancellationProducts: [
{ name: 'sender', type: 'bytes32' },
{ name: 'productIds', type: 'uint32[]' },
{ name: 'nonce', type: 'uint64' },
],
} as const;
/** `LinkSigner` struct of `link_signer` (signed by the MASTER wallet with the `tx_nonce` from `nonces`). */
export const LINK_SIGNER_TYPES = {
LinkSigner: [
{ name: 'sender', type: 'bytes32' },
{ name: 'signer', type: 'bytes32' },
{ name: 'nonce', type: 'uint64' },
],
} as const;
/** Order message with bigint fields (what gets signed). */
export interface OrderMessage {
readonly sender: Hex;
readonly priceX18: bigint;
readonly amount: bigint;
readonly expiration: bigint;
readonly nonce: bigint;
readonly appendix: bigint;
}
/** Cancellation message with bigint nonce. */
export interface CancellationMessage {
readonly sender: Hex;
readonly productIds: readonly number[];
readonly digests: readonly Hex[];
readonly nonce: bigint;
}
/** CancellationProducts message. @experimental */
export interface CancellationProductsMessage {
readonly sender: Hex;
readonly productIds: readonly number[];
readonly nonce: bigint;
}
/** LinkSigner message: `signer` is bytes32 (address + 12 zero bytes), `nonce` is the `tx_nonce`. */
export interface LinkSignerMessage {
readonly sender: Hex;
readonly signer: Hex;
readonly nonce: bigint;
}
/** Order message as it goes into the JSON body: every bigint as a decimal string. */
export interface WireOrder {
readonly sender: Hex;
readonly priceX18: string;
readonly amount: string;
readonly expiration: string;
readonly nonce: string;
readonly appendix: string;
}
/**
* Minimal signer contract: a viem `LocalAccount` (`privateKeyToAccount(...)`) satisfies it directly. For a
* `WalletClient` pass `(p) => client.signTypedData({ ...p, account })`. The kit never holds keys.
*/
export interface TypedDataSigner {
signTypedData(parameters: {
domain: NadoDomain;
types: Record<string, readonly { name: string; type: string }[]>;
primaryType: string;
message: Record<string, unknown>;
}): Promise<Hex>;
}
/** Either a {@link TypedDataSigner} or a bare `signTypedData` function. */
export type SignerLike = TypedDataSigner | TypedDataSigner['signTypedData'];