From e294ea60b51171df9eb2ee12c1d0cb17f75020f0 Mon Sep 17 00:00:00 2001 From: init4samwise Date: Sat, 14 Mar 2026 15:23:29 +0000 Subject: [PATCH 1/2] fix: flatten owner and signature in SerializedSignedOrder to match Rust serde Rust's SignedOrder uses #[serde(flatten)] on the Permit2Batch field, which hoists `owner` and `signature` to be siblings of `permit` in JSON. The TypeScript type and serialization functions were nesting them inside `permit`, causing a shape mismatch with the Rust implementation. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 4 ++++ src/types/order.ts | 18 ++++++++---------- src/types/serialization.ts | 28 +++++++++++++--------------- tests/serialization.test.ts | 6 ++++++ 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 257b78b..bdeffd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ 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` + ### Added - `deadline_expired` check in `checkOrderFeasibility` — detects orders with expired permit deadlines diff --git a/src/types/order.ts b/src/types/order.ts index 648eddc..403f212 100644 --- a/src/types/order.ts +++ b/src/types/order.ts @@ -48,18 +48,16 @@ export interface UnsignedOrderParams { * Amounts are hex-encoded strings. */ 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..54193a8 100644 --- a/src/types/serialization.ts +++ b/src/types/serialization.ts @@ -4,18 +4,16 @@ import type { SignedOrder, SerializedSignedOrder } from "./order.js"; 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), @@ -29,15 +27,15 @@ 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); + }); } }); From 3cd0974a50a8135185866a998b5de13f97d72726 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 16 Mar 2026 08:43:52 -0400 Subject: [PATCH 2/2] docs: add JSDoc to all exports, require JSDoc in CLAUDE.md Adds JSDoc comments to all exported functions, types, interfaces, and constants across the codebase. Updates CLAUDE.md code style to require JSDoc for all exports. Bumps version to 0.4.5. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 5 +++++ CLAUDE.md | 2 ++ package.json | 2 +- src/abi/bundleHelper.ts | 2 +- src/abi/hostOrders.ts | 2 +- src/abi/passage.ts | 2 +- src/abi/rollupPassage.ts | 2 +- src/abi/transactor.ts | 2 +- src/abi/zenith.ts | 2 +- src/client/txCache.ts | 5 +++++ src/constants/chains.ts | 7 +++++++ src/constants/viemChains.ts | 6 ++++++ src/signing/encode.ts | 5 +++++ src/signing/validate.ts | 5 +++++ src/tokens/addresses.ts | 6 ++++++ src/tokens/constants.ts | 6 ++++++ src/tokens/mapping.ts | 1 + src/types/order.ts | 4 ++++ src/types/serialization.ts | 5 +++++ 19 files changed, 64 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdeffd5..74ecf5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `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 403f212..3968ad4 100644 --- a/src/types/order.ts +++ b/src/types/order.ts @@ -46,6 +46,10 @@ 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; diff --git a/src/types/serialization.ts b/src/types/serialization.ts index 54193a8..e3e4eef 100644 --- a/src/types/serialization.ts +++ b/src/types/serialization.ts @@ -1,6 +1,10 @@ +/** + * 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 { @@ -23,6 +27,7 @@ export function serializeOrder(order: SignedOrder): SerializedSignedOrder { }; } +/** Deserialize a {@link SerializedSignedOrder} back into a {@link SignedOrder}. */ export function deserializeOrder(raw: SerializedSignedOrder): SignedOrder { return { permit: {