Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/abi/bundleHelper.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/abi/hostOrders.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/abi/passage.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/abi/rollupPassage.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/abi/transactor.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/abi/zenith.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
5 changes: 5 additions & 0 deletions src/client/txCache.ts
Original file line number Diff line number Diff line change
@@ -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}`, {
Expand Down
7 changes: 7 additions & 0 deletions src/constants/chains.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,6 +19,7 @@ export interface SignetSystemConstants {
readonly tokenDecimals?: Partial<Record<TokenSymbol, number>>;
}

/** Signet mainnet (host chain 1, rollup chain 519) constants. */
export const MAINNET: SignetSystemConstants = {
hostChainId: 1n,
rollupChainId: 519n,
Expand All @@ -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,
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/constants/viemChains.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions src/signing/encode.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,6 +19,7 @@ export function encodeInitiatePermit2(
});
}

/** Encode calldata for `HostOrders.fillPermit2`. */
export function encodeFillPermit2(fill: SignedFill): Hex {
return encodeFunctionData({
abi: hostOrdersAbi,
Expand Down
5 changes: 5 additions & 0 deletions src/signing/validate.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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) {
Expand Down
6 changes: 6 additions & 0 deletions src/tokens/addresses.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/tokens/constants.ts
Original file line number Diff line number Diff line change
@@ -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 },
Expand All @@ -16,6 +21,7 @@ export const TOKENS = {
USD: { symbol: "USD", name: "USD", decimals: 18 },
} as const satisfies Record<string, TokenMeta>;

/** Union of known token symbol strings. */
export type TokenSymbol = keyof typeof TOKENS;

/**
Expand Down
1 change: 1 addition & 0 deletions src/tokens/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const BRIDGEABLE_TOKENS = new Set<TokenSymbol>([
"USDT",
]);

/** Map a token symbol across chains. Returns the symbol if bridgeable, undefined otherwise. */
export function mapTokenCrossChain(
symbol: TokenSymbol
): TokenSymbol | undefined {
Expand Down
22 changes: 12 additions & 10 deletions src/types/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
33 changes: 18 additions & 15 deletions src/types/serialization.ts
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions tests/serialization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
});
Loading