diff --git a/CHANGELOG.md b/CHANGELOG.md index 257b78b..74ecf5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- `SerializedSignedOrder` type and `serializeOrder`/`deserializeOrder` now flatten `owner` and `signature` to the top level, matching Rust's `#[serde(flatten)]` on `Permit2Batch` + +### Changed + +- Added JSDoc comments to all exported functions, types, interfaces, and constants +- Added JSDoc requirement to CLAUDE.md code style guidelines + ### Added - `deadline_expired` check in `checkOrderFeasibility` — detects orders with expired permit deadlines diff --git a/CLAUDE.md b/CLAUDE.md index 3a5b7a4..bf7d988 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,8 @@ - Keep functions small and focused - Write tests that fail fast - use direct assertions, no Result returns - Avoid unnecessary nesting and closures +- ALWAYS write JSDoc comments for all exported functions, types, interfaces, and constants +- JSDoc should explain the purpose and any non-obvious design decisions (e.g. why a structure is shaped a certain way) ## Project Structure diff --git a/package.json b/package.json index 08cbcb6..e1a39c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@signet-sh/sdk", - "version": "0.4.4", + "version": "0.4.5", "description": "TypeScript SDK for Signet Orders - create, sign, and verify orders compatible with signet-types", "type": "module", "main": "./dist/index.js", diff --git a/src/abi/bundleHelper.ts b/src/abi/bundleHelper.ts index a8f3498..1ac22a3 100644 --- a/src/abi/bundleHelper.ts +++ b/src/abi/bundleHelper.ts @@ -1,4 +1,4 @@ -// Generated from crates/zenith/abi/BundleHelper.json +/** BundleHelper contract ABI. Generated from crates/zenith/abi/BundleHelper.json. */ export const bundleHelperAbi = [ { type: "constructor", diff --git a/src/abi/hostOrders.ts b/src/abi/hostOrders.ts index aa178ae..d67629b 100644 --- a/src/abi/hostOrders.ts +++ b/src/abi/hostOrders.ts @@ -1,4 +1,4 @@ -// Generated from crates/zenith/abi/HostOrders.json +/** HostOrders contract ABI. Generated from crates/zenith/abi/HostOrders.json. */ export const hostOrdersAbi = [ { type: "constructor", diff --git a/src/abi/passage.ts b/src/abi/passage.ts index 82b8e0d..12bf7d2 100644 --- a/src/abi/passage.ts +++ b/src/abi/passage.ts @@ -1,4 +1,4 @@ -// Generated from crates/zenith/abi/Passage.json +/** Passage contract ABI. Generated from crates/zenith/abi/Passage.json. */ export const passageAbi = [ { type: "constructor", diff --git a/src/abi/rollupPassage.ts b/src/abi/rollupPassage.ts index 3bf3258..0c799a1 100644 --- a/src/abi/rollupPassage.ts +++ b/src/abi/rollupPassage.ts @@ -1,4 +1,4 @@ -// Generated from crates/zenith/abi/RollupPassage.json +/** RollupPassage contract ABI. Generated from crates/zenith/abi/RollupPassage.json. */ export const rollupPassageAbi = [ { type: "constructor", diff --git a/src/abi/transactor.ts b/src/abi/transactor.ts index f6f7453..6537bb4 100644 --- a/src/abi/transactor.ts +++ b/src/abi/transactor.ts @@ -1,4 +1,4 @@ -// Generated from crates/zenith/abi/Transactor.json +/** Transactor contract ABI. Generated from crates/zenith/abi/Transactor.json. */ export const transactorAbi = [ { type: "constructor", diff --git a/src/abi/zenith.ts b/src/abi/zenith.ts index c7de4d0..6bdd5c0 100644 --- a/src/abi/zenith.ts +++ b/src/abi/zenith.ts @@ -1,4 +1,4 @@ -// Generated from crates/zenith/abi/Zenith.json +/** Zenith contract ABI. Generated from crates/zenith/abi/Zenith.json. */ export const zenithAbi = [ { type: "constructor", diff --git a/src/client/txCache.ts b/src/client/txCache.ts index e5723f6..8899b0b 100644 --- a/src/client/txCache.ts +++ b/src/client/txCache.ts @@ -1,13 +1,18 @@ +/** + * Client for the Signet transaction cache service. + */ import type { SignetEthBundle } from "../types/bundle.js"; import type { SignedOrder } from "../types/order.js"; import { serializeEthBundle } from "../types/bundleSerialization.js"; import { serializeOrder } from "../types/serialization.js"; +/** Client interface for submitting orders and bundles to the transaction cache. */ export interface TxCacheClient { submitOrder(order: SignedOrder): Promise<{ id: string }>; submitBundle(bundle: SignetEthBundle): Promise<{ id: string }>; } +/** Create a {@link TxCacheClient} that posts to the given base URL. */ export function createTxCacheClient(baseUrl: string): TxCacheClient { async function post(path: string, body: unknown): Promise<{ id: string }> { const response = await fetch(`${baseUrl}${path}`, { diff --git a/src/constants/chains.ts b/src/constants/chains.ts index ab0af09..78f87ae 100644 --- a/src/constants/chains.ts +++ b/src/constants/chains.ts @@ -1,6 +1,10 @@ +/** + * Chain configuration constants for Signet networks. + */ import type { Address } from "viem"; import type { TokenSymbol } from "../tokens/constants.js"; +/** System-wide contract addresses and chain IDs for a Signet deployment. */ export interface SignetSystemConstants { readonly hostChainId: bigint; readonly rollupChainId: bigint; @@ -15,6 +19,7 @@ export interface SignetSystemConstants { readonly tokenDecimals?: Partial>; } +/** Signet mainnet (host chain 1, rollup chain 519) constants. */ export const MAINNET: SignetSystemConstants = { hostChainId: 1n, rollupChainId: 519n, @@ -28,6 +33,7 @@ export const MAINNET: SignetSystemConstants = { slotTime: 12n, } as const; +/** Parmigiana testnet (host chain 3151908, rollup chain 88888) constants. */ export const PARMIGIANA: SignetSystemConstants = { hostChainId: 3151908n, rollupChainId: 88888n, @@ -44,6 +50,7 @@ export const PARMIGIANA: SignetSystemConstants = { }, } as const; +/** Look up the orders contract address for a given chain ID within a deployment. */ export function getOrdersContract( constants: SignetSystemConstants, chainId: bigint diff --git a/src/constants/viemChains.ts b/src/constants/viemChains.ts index 431246d..83d00ce 100644 --- a/src/constants/viemChains.ts +++ b/src/constants/viemChains.ts @@ -1,5 +1,9 @@ +/** + * Viem chain definitions for Signet networks. + */ import { defineChain } from "viem"; +/** Viem chain definition for the Signet mainnet rollup (chain 519). */ export const signetRollup = defineChain({ id: 519, name: "Signet", @@ -9,6 +13,7 @@ export const signetRollup = defineChain({ }, }); +/** Viem chain definition for the Parmigiana testnet rollup (chain 88888). */ export const parmigianaRollup = defineChain({ id: 88888, name: "Parmigiana", @@ -24,6 +29,7 @@ export const parmigianaRollup = defineChain({ }, }); +/** Viem chain definition for the Parmigiana testnet host chain (chain 3151908). */ export const parmigianaHost = defineChain({ id: 3151908, name: "Parmigiana Host", diff --git a/src/signing/encode.ts b/src/signing/encode.ts index be6cdd3..2e5abe6 100644 --- a/src/signing/encode.ts +++ b/src/signing/encode.ts @@ -1,9 +1,13 @@ +/** + * ABI encoding helpers for order and fill contract calls. + */ import { encodeFunctionData, type Address, type Hex } from "viem"; import type { SignedOrder } from "../types/order.js"; import type { SignedFill } from "../types/fill.js"; import { rollupOrdersAbi } from "../abi/rollupOrders.js"; import { hostOrdersAbi } from "../abi/hostOrders.js"; +/** Encode calldata for `RollupOrders.initiatePermit2`. */ export function encodeInitiatePermit2( order: SignedOrder, tokenRecipient: Address @@ -15,6 +19,7 @@ export function encodeInitiatePermit2( }); } +/** Encode calldata for `HostOrders.fillPermit2`. */ export function encodeFillPermit2(fill: SignedFill): Hex { return encodeFunctionData({ abi: hostOrdersAbi, diff --git a/src/signing/validate.ts b/src/signing/validate.ts index 3471c26..4638606 100644 --- a/src/signing/validate.ts +++ b/src/signing/validate.ts @@ -1,8 +1,12 @@ +/** + * Runtime validation for signed orders and fills. + */ import { isAddressEqual } from "viem"; import type { SignedFill } from "../types/fill.js"; import type { SignedOrder } from "../types/order.js"; import { nowSeconds } from "../utils/time.js"; +/** Validate that a signed order has not expired. Throws on failure. */ export function validateOrder(order: SignedOrder): void { const now = nowSeconds(); if (order.permit.permit.deadline < now) { @@ -12,6 +16,7 @@ export function validateOrder(order: SignedOrder): void { } } +/** Validate that a signed fill has not expired and its outputs match permitted tokens. Throws on failure. */ export function validateFill(fill: SignedFill): void { const now = nowSeconds(); if (fill.permit.permit.deadline < now) { diff --git a/src/tokens/addresses.ts b/src/tokens/addresses.ts index 1008a4c..1f44afd 100644 --- a/src/tokens/addresses.ts +++ b/src/tokens/addresses.ts @@ -1,3 +1,6 @@ +/** + * Token address lookups for Signet networks. + */ import { isAddressEqual, type Address } from "viem"; import type { SignetSystemConstants } from "../constants/chains.js"; import type { TokenSymbol } from "./constants.js"; @@ -59,6 +62,7 @@ function getSide( return undefined; } +/** Get the on-chain address for a token symbol on a specific chain. */ export function getTokenAddress( symbol: TokenSymbol, chainId: bigint, @@ -69,6 +73,7 @@ export function getTokenAddress( return getSide(network, chainId, constants)?.[symbol]; } +/** Resolve a token address back to its symbol on a specific chain. */ export function resolveTokenSymbol( address: Address, chainId: bigint, @@ -84,6 +89,7 @@ export function resolveTokenSymbol( return entry?.[0] as TokenSymbol | undefined; } +/** List all token symbols available on a specific chain. */ export function getAvailableTokens( chainId: bigint, constants: SignetSystemConstants diff --git a/src/tokens/constants.ts b/src/tokens/constants.ts index 83638eb..c2c64b7 100644 --- a/src/tokens/constants.ts +++ b/src/tokens/constants.ts @@ -1,11 +1,16 @@ +/** + * Token metadata and decimal lookups. + */ import type { SignetSystemConstants } from "../constants/chains.js"; +/** Metadata for a known token. */ export interface TokenMeta { readonly symbol: string; readonly name: string; readonly decimals: number; } +/** Registry of known tokens and their metadata. */ export const TOKENS = { ETH: { symbol: "ETH", name: "Ether", decimals: 18 }, WETH: { symbol: "WETH", name: "Wrapped Ether", decimals: 18 }, @@ -16,6 +21,7 @@ export const TOKENS = { USD: { symbol: "USD", name: "USD", decimals: 18 }, } as const satisfies Record; +/** Union of known token symbol strings. */ export type TokenSymbol = keyof typeof TOKENS; /** diff --git a/src/tokens/mapping.ts b/src/tokens/mapping.ts index afa80db..f0e4b92 100644 --- a/src/tokens/mapping.ts +++ b/src/tokens/mapping.ts @@ -19,6 +19,7 @@ const BRIDGEABLE_TOKENS = new Set([ "USDT", ]); +/** Map a token symbol across chains. Returns the symbol if bridgeable, undefined otherwise. */ export function mapTokenCrossChain( symbol: TokenSymbol ): TokenSymbol | undefined { diff --git a/src/types/order.ts b/src/types/order.ts index 648eddc..3968ad4 100644 --- a/src/types/order.ts +++ b/src/types/order.ts @@ -46,20 +46,22 @@ export interface UnsignedOrderParams { /** * Serialized form of a signed order for JSON transport. * Amounts are hex-encoded strings. + * + * The `owner` and `signature` fields are flattened to the top level to match + * Rust's `#[serde(flatten)]` on `Permit2Batch`. The `permit` field contains + * only the inner `PermitBatchTransferFrom` data. */ export interface SerializedSignedOrder { + readonly owner: Address; readonly permit: { - readonly permit: { - readonly permitted: readonly { - readonly token: Address; - readonly amount: Hex; - }[]; - readonly nonce: Hex; - readonly deadline: Hex; - }; - readonly owner: Address; - readonly signature: Hex; + readonly permitted: readonly { + readonly token: Address; + readonly amount: Hex; + }[]; + readonly nonce: Hex; + readonly deadline: Hex; }; + readonly signature: Hex; readonly outputs: readonly { readonly token: Address; readonly amount: Hex; diff --git a/src/types/serialization.ts b/src/types/serialization.ts index d0e02d7..e3e4eef 100644 --- a/src/types/serialization.ts +++ b/src/types/serialization.ts @@ -1,21 +1,23 @@ +/** + * Serialization and deserialization for signed orders. + */ import { fromHex, toHex } from "viem"; import type { SignedOrder, SerializedSignedOrder } from "./order.js"; +/** Serialize a {@link SignedOrder} to its JSON-transport representation. */ export function serializeOrder(order: SignedOrder): SerializedSignedOrder { const { permit, outputs } = order; return { + owner: permit.owner, permit: { - permit: { - permitted: permit.permit.permitted.map((p) => ({ - token: p.token, - amount: toHex(p.amount), - })), - nonce: toHex(permit.permit.nonce), - deadline: toHex(permit.permit.deadline), - }, - owner: permit.owner, - signature: permit.signature, + permitted: permit.permit.permitted.map((p) => ({ + token: p.token, + amount: toHex(p.amount), + })), + nonce: toHex(permit.permit.nonce), + deadline: toHex(permit.permit.deadline), }, + signature: permit.signature, outputs: outputs.map((o) => ({ token: o.token, amount: toHex(o.amount), @@ -25,19 +27,20 @@ export function serializeOrder(order: SignedOrder): SerializedSignedOrder { }; } +/** Deserialize a {@link SerializedSignedOrder} back into a {@link SignedOrder}. */ export function deserializeOrder(raw: SerializedSignedOrder): SignedOrder { return { permit: { permit: { - permitted: raw.permit.permit.permitted.map((p) => ({ + permitted: raw.permit.permitted.map((p) => ({ token: p.token, amount: fromHex(p.amount, "bigint"), })), - nonce: fromHex(raw.permit.permit.nonce, "bigint"), - deadline: fromHex(raw.permit.permit.deadline, "bigint"), + nonce: fromHex(raw.permit.nonce, "bigint"), + deadline: fromHex(raw.permit.deadline, "bigint"), }, - owner: raw.permit.owner, - signature: raw.permit.signature, + owner: raw.owner, + signature: raw.signature, }, outputs: raw.outputs.map((o) => ({ token: o.token, diff --git a/tests/serialization.test.ts b/tests/serialization.test.ts index cda18d1..8e8f558 100644 --- a/tests/serialization.test.ts +++ b/tests/serialization.test.ts @@ -59,5 +59,11 @@ describe("serialization round-trip", () => { const deserialized = deserializeOrder(serialized); expect(deserialized).toEqual(order); }); + + it(`serializes ${v.name} to match Rust vector shape`, () => { + const order = parseVector(v); + const serialized = serializeOrder(order); + expect(serialized).toEqual(v.signedOrder); + }); } });