From 66289f0ee77a5da41d0b3e69a3c2a2f3ac5553e3 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 15 Feb 2026 21:13:00 -0500 Subject: [PATCH 1/2] add ci --- .github/workflows/ci.yml | 39 ++++ biome.json | 39 ++++ bun.lock | 19 ++ examples/baseToSolanaCall.ts | 2 +- examples/call.ts | 7 +- examples/evmToSolanaTokenTransfer.ts | 7 +- examples/transfer.ts | 4 +- package.json | 9 +- src/adapters/chains/evm/adapter.ts | 10 +- src/adapters/chains/evm/types.ts | 4 +- src/adapters/chains/solana/adapter.ts | 8 +- src/adapters/chains/solana/types.ts | 6 +- src/core/capabilities.ts | 2 +- src/core/client.ts | 28 +-- src/core/errors.ts | 26 +-- src/core/monitor/backoff.ts | 4 +- src/core/monitor/polling.ts | 6 +- src/core/protocol/deployments.ts | 14 +- src/core/protocol/encoding.ts | 12 +- src/core/protocol/engines/base-engine.ts | 80 ++++---- src/core/protocol/engines/constants.ts | 4 +- src/core/protocol/engines/solana-engine.ts | 222 +++++++++++---------- src/core/protocol/engines/types.ts | 4 +- src/core/protocol/identity.ts | 12 +- src/core/protocol/router.ts | 14 +- src/core/protocol/routes/base-to-svm.ts | 62 +++--- src/core/protocol/routes/svm-to-base.ts | 36 ++-- src/core/types.ts | 2 +- src/core/utils.ts | 10 +- src/index.ts | 5 +- src/utils/bridge-idl.constants.ts | 39 ++-- src/utils/relayer-idl.constants.ts | 63 +++--- tests/hubspoke.test.ts | 10 +- tests/identity.test.ts | 12 +- tests/polling-monitor.test.ts | 24 ++- tests/quote.test.ts | 24 +-- tests/solana-call.test.ts | 12 +- tests/transitions.test.ts | 2 +- 38 files changed, 507 insertions(+), 376 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 biome.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1efd0e3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + ci: + name: Build & Test + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Check formatting & lint + run: bun run check:ci + + - name: Typecheck + run: bun run typecheck + + - name: Build + run: bun run build + + - name: Test + run: bun test diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..0fdfb4b --- /dev/null +++ b/biome.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.0/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "formatter": { + "indentStyle": "space", + "indentWidth": 2 + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always", + "trailingCommas": "all" + } + }, + "linter": { + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "warn" + }, + "style": { + "noNonNullAssertion": "warn" + } + } + }, + "files": { + "includes": [ + "**", + "!dist", + "!src/clients/ts", + "!src/interfaces/abis", + "!src/interfaces/idls" + ] + } +} diff --git a/bun.lock b/bun.lock index 97a2d96..c8d85ce 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "viem": "2.39.2", }, "devDependencies": { + "@biomejs/biome": "2.4.0", "@types/bun": "1.3.2", "typescript": "5.9.3", }, @@ -21,6 +22,24 @@ "packages": { "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], + "@biomejs/biome": ["@biomejs/biome@2.4.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.0", "@biomejs/cli-darwin-x64": "2.4.0", "@biomejs/cli-linux-arm64": "2.4.0", "@biomejs/cli-linux-arm64-musl": "2.4.0", "@biomejs/cli-linux-x64": "2.4.0", "@biomejs/cli-linux-x64-musl": "2.4.0", "@biomejs/cli-win32-arm64": "2.4.0", "@biomejs/cli-win32-x64": "2.4.0" }, "bin": { "biome": "bin/biome" } }, "sha512-iluT61cORUDIC5i/y42ljyQraCemmmcgbMLLCnYO+yh+2hjTmcMFcwY8G0zTzWCsPb3t3AyKc+0t/VuhPZULUg=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-L+YpOtPSuU0etomfvFTPWRsa7+8ejaJL3yaROEoT/96HDJbR6OsvZQk0C8JUYou+XFdP+JcGxqZknkp4n934RA=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Aq+S7ffpb5ynTyLgtnEjG+W6xuTd2F7FdC7J6ShpvRhZwJhjzwITGF9vrqoOnw0sv1XWkt2Q1Rpg+hleg/Xg7Q=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-u2p54IhvNAWB+h7+rxCZe3reNfQYFK+ppDw+q0yegrGclFYnDPZAntv/PqgUacpC3uxTeuWFgWW7RFe3lHuxOA=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1rhDUq8sf7xX3tg7vbnU3WVfanKCKi40OXc4VleBMzRStmQHdeBY46aFP6VdwEomcVjyNiu+Zcr3LZtAdrZrjQ=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WVFOhsnzhrbMGOSIcB9yFdRV2oG2KkRRhIZiunI9gJqSU3ax9ErdnTxRfJUxZUI9NbzVxC60OCXNcu+mXfF/Tw=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Omo0xhl63z47X+CrE5viEWKJhejJyndl577VoXg763U/aoATrK3r5+8DPh02GokWPeODX1Hek00OtjjooGan9w=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-aqRwW0LJLV1v1NzyLvLWQhdLmDSAV1vUh+OBdYJaa8f28XBn5BZavo+WTfqgEzALZxlNfBmu6NGO6Al3MbCULw=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.0", "", { "os": "win32", "cpu": "x64" }, "sha512-g47s+V+OqsGxbSZN3lpav6WYOk0PIc3aCBAq+p6dwSynL3K5MA6Cg6nkzDOlu28GEHwbakW+BllzHCJCxnfK5Q=="], + "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], "@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], diff --git a/examples/baseToSolanaCall.ts b/examples/baseToSolanaCall.ts index 2b53d8e..8acc1dd 100644 --- a/examples/baseToSolanaCall.ts +++ b/examples/baseToSolanaCall.ts @@ -1,6 +1,6 @@ import { createBridgeClient } from "../src"; -import { makeSolanaAdapter } from "../src/adapters/chains/solana/adapter"; import { makeEvmAdapter } from "../src/adapters/chains/evm/adapter"; +import { makeSolanaAdapter } from "../src/adapters/chains/solana/adapter"; import { BASE_MAINNET_CHAIN_ID } from "../src/core/protocol/router"; import { loadSolanaKeypair } from "../src/node"; diff --git a/examples/call.ts b/examples/call.ts index ed8ace5..b5df2b6 100644 --- a/examples/call.ts +++ b/examples/call.ts @@ -1,6 +1,6 @@ import { createBridgeClient } from "../src"; -import { makeSolanaAdapter } from "../src/adapters/chains/solana/adapter"; import { makeEvmAdapter } from "../src/adapters/chains/evm/adapter"; +import { makeSolanaAdapter } from "../src/adapters/chains/solana/adapter"; import { BASE_MAINNET_CHAIN_ID } from "../src/core/protocol/router"; import { loadSolanaKeypair } from "../src/node"; @@ -24,7 +24,10 @@ async function main() { }); const op = await client.call({ - route: { sourceChain: "solana:mainnet", destinationChain: BASE_MAINNET_CHAIN_ID }, + route: { + sourceChain: "solana:mainnet", + destinationChain: BASE_MAINNET_CHAIN_ID, + }, call: { kind: "evm", call: { diff --git a/examples/evmToSolanaTokenTransfer.ts b/examples/evmToSolanaTokenTransfer.ts index b78a3f0..60dc67b 100644 --- a/examples/evmToSolanaTokenTransfer.ts +++ b/examples/evmToSolanaTokenTransfer.ts @@ -1,6 +1,6 @@ import { createBridgeClient } from "../src"; -import { makeSolanaAdapter } from "../src/adapters/chains/solana/adapter"; import { makeEvmAdapter } from "../src/adapters/chains/evm/adapter"; +import { makeSolanaAdapter } from "../src/adapters/chains/solana/adapter"; import { BASE_MAINNET_CHAIN_ID } from "../src/core/protocol/router"; import { loadSolanaKeypair } from "../src/node"; @@ -33,7 +33,10 @@ async function main() { }); const op = await client.transfer({ - route: { sourceChain: BASE_MAINNET_CHAIN_ID, destinationChain: "solana:mainnet" }, + route: { + sourceChain: BASE_MAINNET_CHAIN_ID, + destinationChain: "solana:mainnet", + }, asset: { kind: "token", address: "0x0000000000000000000000000000000000000000", diff --git a/examples/transfer.ts b/examples/transfer.ts index a2bc077..e142834 100644 --- a/examples/transfer.ts +++ b/examples/transfer.ts @@ -1,7 +1,7 @@ import { createBridgeClient } from "../src"; -import { base, solanaMainnet } from "../src/chains"; -import { makeSolanaAdapter } from "../src/adapters/chains/solana/adapter"; import { makeEvmAdapter } from "../src/adapters/chains/evm/adapter"; +import { makeSolanaAdapter } from "../src/adapters/chains/solana/adapter"; +import { base, solanaMainnet } from "../src/chains"; import { loadSolanaKeypair } from "../src/node"; // Example: Solana -> Base (EVM) transfer (native SOL) diff --git a/package.json b/package.json index 135a286..76cce30 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,13 @@ "types": "tsc -p tsconfig.build.json", "test": "bun test", "typecheck": "tsc --noEmit", - "dev": "bun --hot ./index.ts" + "dev": "bun --hot ./index.ts", + "lint": "biome lint --write .", + "lint:check": "biome lint .", + "format": "biome format --write .", + "format:check": "biome format .", + "check": "biome check --write .", + "check:ci": "biome check ." }, "keywords": [ "bridge", @@ -41,6 +47,7 @@ "author": "Base", "license": "MIT", "devDependencies": { + "@biomejs/biome": "2.4.0", "@types/bun": "1.3.2", "typescript": "5.9.3" }, diff --git a/src/adapters/chains/evm/adapter.ts b/src/adapters/chains/evm/adapter.ts index 826dc8d..dc14e63 100644 --- a/src/adapters/chains/evm/adapter.ts +++ b/src/adapters/chains/evm/adapter.ts @@ -1,15 +1,15 @@ -import type { ChainRef } from "../../../core/types"; import { + type Chain, createPublicClient, createWalletClient, - http, - type Chain, type Hash, type Hex, + http, type PublicClient, type WalletClient, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; +import type { ChainRef } from "../../../core/types"; import type { EvmAdapterConfig, EvmChainAdapter } from "./types"; function makeViemChain(chainId: number): Chain { @@ -23,7 +23,7 @@ function makeViemChain(chainId: number): Chain { } function hasViemChain( - config: EvmAdapterConfig + config: EvmAdapterConfig, ): config is Extract { return (config as any).chain != null; } @@ -36,7 +36,7 @@ export function makeEvmAdapter(config: EvmAdapterConfig): EvmChainAdapter { : config.chainId; const chain: ChainRef = { id: `eip155:${chainId}` }; const viemChain = hasViemChain(config) - ? (config.chain as any).viem ?? config.chain + ? ((config.chain as any).viem ?? config.chain) : makeViemChain(chainId); const publicClient = createPublicClient({ diff --git a/src/adapters/chains/evm/types.ts b/src/adapters/chains/evm/types.ts index 82cd9bd..df54f5e 100644 --- a/src/adapters/chains/evm/types.ts +++ b/src/adapters/chains/evm/types.ts @@ -1,5 +1,5 @@ -import type { ChainAdapter, ChainRef } from "../../../core/types"; import type { Chain, Hash, Hex, PublicClient, WalletClient } from "viem"; +import type { ChainAdapter, ChainRef } from "../../../core/types"; export type EvmWalletConfig = | { type: "privateKey"; key: Hex } @@ -48,6 +48,6 @@ export interface EvmChainAdapter extends ChainAdapter { /** Convenience reads */ getTransactionReceipt( - hash: Hash + hash: Hash, ): Promise>>; } diff --git a/src/adapters/chains/solana/adapter.ts b/src/adapters/chains/solana/adapter.ts index 8763c85..157f5a5 100644 --- a/src/adapters/chains/solana/adapter.ts +++ b/src/adapters/chains/solana/adapter.ts @@ -1,13 +1,13 @@ import { - createSolanaRpc, type Account, + createSolanaRpc, type Address as SolAddress, } from "@solana/kit"; -import type { ChainRef } from "../../../core/types"; import { fetchOutgoingMessage, type OutgoingMessage, } from "../../../clients/ts/src/bridge"; +import type { ChainRef } from "../../../core/types"; import type { SolanaAdapterConfig, SolanaChainAdapter } from "./types"; /** @@ -26,7 +26,7 @@ import type { SolanaAdapterConfig, SolanaChainAdapter } from "./types"; * }); */ export function makeSolanaAdapter( - config: SolanaAdapterConfig + config: SolanaAdapterConfig, ): SolanaChainAdapter { const payer = config.payer.signer; const chain: ChainRef = config.chain ?? { id: "solana:mainnet" }; @@ -41,7 +41,7 @@ export function makeSolanaAdapter( await rpc.getLatestBlockhash().send(); }, async fetchOutgoingMessage( - address: SolAddress + address: SolAddress, ): Promise> { return await fetchOutgoingMessage(rpc, address); }, diff --git a/src/adapters/chains/solana/types.ts b/src/adapters/chains/solana/types.ts index 02c0d25..4dedd2a 100644 --- a/src/adapters/chains/solana/types.ts +++ b/src/adapters/chains/solana/types.ts @@ -1,10 +1,10 @@ import type { Account, - Address as SolAddress, KeyPairSigner, + Address as SolAddress, } from "@solana/kit"; -import type { ChainAdapter, ChainRef } from "../../../core/types"; import type { OutgoingMessage } from "../../../clients/ts/src/bridge"; +import type { ChainAdapter, ChainRef } from "../../../core/types"; export type SolanaPayerConfig = { type: "signer"; signer: KeyPairSigner }; @@ -22,6 +22,6 @@ export interface SolanaChainAdapter extends ChainAdapter { readonly payer: KeyPairSigner; fetchOutgoingMessage( - address: SolAddress + address: SolAddress, ): Promise>; } diff --git a/src/core/capabilities.ts b/src/core/capabilities.ts index 35d9ff9..c9bba10 100644 --- a/src/core/capabilities.ts +++ b/src/core/capabilities.ts @@ -22,7 +22,7 @@ export function isTerminalStatus(s: ExecutionStatus): boolean { export function isAllowedTransition( from: ExecutionStatus["type"], - to: ExecutionStatus["type"] + to: ExecutionStatus["type"], ): boolean { if (from === to) return true; if (to === "Failed" || to === "Expired") return true; diff --git a/src/core/client.ts b/src/core/client.ts index 68857e3..f580a85 100644 --- a/src/core/client.ts +++ b/src/core/client.ts @@ -1,11 +1,11 @@ -import { NOOP_LOGGER, type Logger } from "../utils/logger"; +import { type Logger, NOOP_LOGGER } from "../utils/logger"; import { BridgeUnsupportedRouteError } from "./errors"; +import { mergeBridgeDeployments } from "./protocol/deployments"; import { + type BridgeConfig, resolveBridgeRoute, supportsBridgeRoute, - type BridgeConfig, } from "./protocol/router"; -import { mergeBridgeDeployments } from "./protocol/deployments"; import type { BridgeOperation, BridgeRequest, @@ -79,7 +79,7 @@ export interface BridgeClient { status(ref: MessageRef, opts?: StatusOptions): Promise; monitor( ref: MessageRef, - opts?: MonitorOptions + opts?: MonitorOptions, ): AsyncIterable; /** Discovery */ @@ -150,7 +150,7 @@ class DefaultBridgeClient implements BridgeClient { async request(req: BridgeRequest): Promise { const adapter = await this.getRouteAdapter(req.route); this.logger.debug( - `bridge.request: initiating ${req.route.sourceChain} -> ${req.route.destinationChain}` + `bridge.request: initiating ${req.route.sourceChain} -> ${req.route.destinationChain}`, ); return await adapter.initiate(req); } @@ -158,7 +158,7 @@ class DefaultBridgeClient implements BridgeClient { async quote(req: QuoteRequest): Promise { const adapter = await this.getRouteAdapter(req.route); this.logger.debug( - `bridge.quote: estimating ${req.route.sourceChain} -> ${req.route.destinationChain}` + `bridge.quote: estimating ${req.route.sourceChain} -> ${req.route.destinationChain}`, ); return await adapter.quote(req); } @@ -166,25 +166,25 @@ class DefaultBridgeClient implements BridgeClient { async prove(ref: MessageRef, opts?: ProveOptions): Promise { const adapter = await this.getRouteAdapter(ref.route); this.logger.debug( - `bridge.prove: ${ref.route.sourceChain} -> ${ref.route.destinationChain}` + `bridge.prove: ${ref.route.sourceChain} -> ${ref.route.destinationChain}`, ); return await adapter.prove(ref, opts); } async execute( ref: MessageRef, - opts?: ExecuteOptions + opts?: ExecuteOptions, ): Promise { const adapter = await this.getRouteAdapter(ref.route); this.logger.debug( - `bridge.execute: ${ref.route.sourceChain} -> ${ref.route.destinationChain}` + `bridge.execute: ${ref.route.sourceChain} -> ${ref.route.destinationChain}`, ); return await adapter.execute(ref, opts); } async status( ref: MessageRef, - opts?: StatusOptions + opts?: StatusOptions, ): Promise { const adapter = await this.getRouteAdapter(ref.route); return await adapter.status(ref, opts); @@ -192,7 +192,7 @@ class DefaultBridgeClient implements BridgeClient { async *monitor( ref: MessageRef, - opts?: MonitorOptions + opts?: MonitorOptions, ): AsyncIterable { const adapter = await this.getRouteAdapter(ref.route); const merged: MonitorOptions = { @@ -221,13 +221,13 @@ class DefaultBridgeClient implements BridgeClient { const existing = this.adapterCache.get(key); if (existing) { this.logger.debug( - `bridge.resolveRoute: cache hit for ${route.sourceChain} -> ${route.destinationChain}` + `bridge.resolveRoute: cache hit for ${route.sourceChain} -> ${route.destinationChain}`, ); return existing; } this.logger.debug( - `bridge.resolveRoute: constructing adapter for ${route.sourceChain} -> ${route.destinationChain}` + `bridge.resolveRoute: constructing adapter for ${route.sourceChain} -> ${route.destinationChain}`, ); const created = resolveBridgeRoute(route, this.chains, this.bridge); this.adapterCache.set(key, created); @@ -241,7 +241,7 @@ export function createBridgeClient(config: BridgeClientConfig): BridgeClient { const id = adapter.chain.id; if (chains[id]) { throw new Error( - `Duplicate chain adapter registered for ${id}. Ensure each adapter has a unique chain id.` + `Duplicate chain adapter registered for ${id}. Ensure each adapter has a unique chain id.`, ); } chains[id] = adapter; diff --git a/src/core/errors.ts b/src/core/errors.ts index 52ea514..132212f 100644 --- a/src/core/errors.ts +++ b/src/core/errors.ts @@ -101,8 +101,8 @@ export class BridgeUnsupportedStepError extends BridgeError { args.step === "prove" ? "prove" : args.step === "execute" - ? "execute" - : "monitor", + ? "execute" + : "monitor", route: args.route, cause: args.cause, }); @@ -137,7 +137,7 @@ export class BridgeConfigError extends BridgeError { route?: BridgeRoute; chain?: ChainId; cause?: unknown; - } + }, ) { super({ message, @@ -160,7 +160,7 @@ export class BridgeRpcError extends BridgeError { route?: BridgeRoute; chain?: ChainId; cause?: unknown; - } + }, ) { super({ message, @@ -183,7 +183,7 @@ export class BridgeTimeoutError extends BridgeError { route?: BridgeRoute; chain?: ChainId; cause?: unknown; - } + }, ) { super({ message, @@ -206,7 +206,7 @@ export class BridgeNotFinalError extends BridgeError { route?: BridgeRoute; chain?: ChainId; cause?: unknown; - } + }, ) { super({ message, @@ -224,7 +224,7 @@ export class BridgeNotFinalError extends BridgeError { export class BridgeProofNotAvailableError extends BridgeError { constructor( message: string, - args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown } + args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown }, ) { super({ message, @@ -242,7 +242,7 @@ export class BridgeProofNotAvailableError extends BridgeError { export class BridgeAlreadyProvenError extends BridgeError { constructor( message: string, - args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown } + args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown }, ) { super({ message, @@ -260,7 +260,7 @@ export class BridgeAlreadyProvenError extends BridgeError { export class BridgeNotProvenError extends BridgeError { constructor( message: string, - args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown } + args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown }, ) { super({ message, @@ -278,7 +278,7 @@ export class BridgeNotProvenError extends BridgeError { export class BridgeAlreadyExecutedError extends BridgeError { constructor( message: string, - args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown } + args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown }, ) { super({ message, @@ -296,7 +296,7 @@ export class BridgeAlreadyExecutedError extends BridgeError { export class BridgeExecutionRevertedError extends BridgeError { constructor( message: string, - args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown } + args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown }, ) { super({ message, @@ -314,7 +314,7 @@ export class BridgeExecutionRevertedError extends BridgeError { export class BridgeMessageFailedError extends BridgeError { constructor( message: string, - args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown } + args: { route?: BridgeRoute; chain?: ChainId; cause?: unknown }, ) { super({ message, @@ -337,7 +337,7 @@ export class BridgeInvariantViolationError extends BridgeError { route?: BridgeRoute; chain?: ChainId; cause?: unknown; - } + }, ) { super({ message, diff --git a/src/core/monitor/backoff.ts b/src/core/monitor/backoff.ts index 4103f5b..d6a3f5b 100644 --- a/src/core/monitor/backoff.ts +++ b/src/core/monitor/backoff.ts @@ -14,10 +14,10 @@ export const DEFAULT_BACKOFF: BackoffOptions = { export function backoffDelayMs( attempt: number, - opts: Partial = {} + opts: Partial = {}, ): number { const o: BackoffOptions = { ...DEFAULT_BACKOFF, ...opts }; - const base = Math.min(o.initialMs * Math.pow(o.factor, attempt), o.maxMs); + const base = Math.min(o.initialMs * o.factor ** attempt, o.maxMs); const jitter = base * o.jitterRatio; const randomized = base + (Math.random() * 2 - 1) * jitter; return Math.max(0, Math.floor(randomized)); diff --git a/src/core/monitor/polling.ts b/src/core/monitor/polling.ts index 2085d9e..627cdb2 100644 --- a/src/core/monitor/polling.ts +++ b/src/core/monitor/polling.ts @@ -41,7 +41,7 @@ function raceAbort(promise: Promise, signal?: AbortSignal): Promise { (err) => { signal.removeEventListener("abort", onAbort); reject(err); - } + }, ); }); } @@ -79,7 +79,7 @@ function stableStatusKey(s: ExecutionStatus): string { */ export async function* pollingMonitor( getStatus: (signal?: AbortSignal) => Promise, - opts: MonitorOptions = {} + opts: MonitorOptions = {}, ): AsyncIterable { const timeoutMs = opts.timeoutMs ?? 60_000; const pollIntervalMs = opts.pollIntervalMs ?? 5_000; @@ -106,7 +106,7 @@ export async function* pollingMonitor( if (prev && !isAllowedTransition(prev.type, next.type)) { throw new BridgeInvariantViolationError( `Illegal status transition: ${prev.type} -> ${next.type}`, - { stage: "monitor" } + { stage: "monitor" }, ); } diff --git a/src/core/protocol/deployments.ts b/src/core/protocol/deployments.ts index d20803b..1648c7f 100644 --- a/src/core/protocol/deployments.ts +++ b/src/core/protocol/deployments.ts @@ -1,7 +1,7 @@ -import { address as solAddress, type Address as SolAddress } from "@solana/kit"; +import { type Address as SolAddress, address as solAddress } from "@solana/kit"; import type { Hex } from "viem"; -import { BASE_MAINNET_CHAIN_ID, type BridgeConfig } from "./router"; import type { ChainId } from "../types"; +import { BASE_MAINNET_CHAIN_ID, type BridgeConfig } from "./router"; /** * Built-in bridge deployments bundled with the SDK. @@ -38,7 +38,7 @@ function mergeSolanaDeployments( ChainId, Partial<{ bridgeProgram: SolAddress; relayerProgram: SolAddress }> > - | undefined + | undefined, ): Record { if (!override) return base; const out: Record< @@ -64,7 +64,7 @@ function mergeSolanaDeployments( function mergeEvmDeployments( base: Record, - override?: Record> | undefined + override?: Record> | undefined, ): Record { if (!override) return base; const out: Record = { ...base }; @@ -82,16 +82,16 @@ function mergeEvmDeployments( } export function mergeBridgeDeployments( - overrides?: Partial + overrides?: Partial, ): Deployments { return { solana: mergeSolanaDeployments( DEFAULT_BRIDGE_DEPLOYMENTS.solana, - overrides?.solana as any + overrides?.solana as any, ), base: mergeEvmDeployments( DEFAULT_BRIDGE_DEPLOYMENTS.base, - overrides?.base as any + overrides?.base as any, ), }; } diff --git a/src/core/protocol/encoding.ts b/src/core/protocol/encoding.ts index 1ef149b..73da07a 100644 --- a/src/core/protocol/encoding.ts +++ b/src/core/protocol/encoding.ts @@ -3,7 +3,7 @@ import { getBase58Encoder, type Address as SolAddress, } from "@solana/kit"; -import { encodeAbiParameters, padHex, toHex, type Hex } from "viem"; +import { encodeAbiParameters, type Hex, padHex, toHex } from "viem"; import type { BridgeSolanaToBaseStateOutgoingMessageMessage, Call, @@ -24,7 +24,7 @@ export function bytes32FromSolanaPubkey(pubkey: SolAddress): Hex { } export function encodeOutgoingMessagePayload( - msg: BridgeSolanaToBaseStateOutgoingMessageMessage + msg: BridgeSolanaToBaseStateOutgoingMessageMessage, ): { ty: number; data: Hex } { // Call if (msg.__kind === "Call") { @@ -60,7 +60,7 @@ export function encodeOutgoingMessagePayload( ], }, ], - [transferTuple] + [transferTuple], ); if (transfer.call.__option === "None") { @@ -90,7 +90,7 @@ export function encodeOutgoingMessagePayload( ], }, ], - [transferTuple, callTuple] + [transferTuple, callTuple], ); return { ty: MESSAGE_TYPE.TransferAndCall, data }; @@ -123,7 +123,7 @@ export function encodeCallData(call: Call): Hex { value: BigInt(call.value), data: toHex(new Uint8Array(call.data)), }, - ] + ], ); } @@ -138,7 +138,7 @@ export function callTupleObject(call: Call) { } export function outgoingMessagePubkeyBytes32( - outgoing: Awaited> + outgoing: Awaited>, ): Hex { const pubkeyBase58 = getBase58Codec().encode(outgoing.address); return `0x${pubkeyBase58.toHex()}` as Hex; diff --git a/src/core/protocol/engines/base-engine.ts b/src/core/protocol/engines/base-engine.ts index fb9088e..ba58924 100644 --- a/src/core/protocol/engines/base-engine.ts +++ b/src/core/protocol/engines/base-engine.ts @@ -1,41 +1,41 @@ import { - getIxAccountEncoder, - type Call, - type Ix, - type fetchOutgoingMessage, - type OutgoingMessage, -} from "../../../clients/ts/src/bridge"; -import { BRIDGE_ABI } from "../../../interfaces/abis/bridge.abi"; -import { MessageType, type EngineConfig, type CallParams } from "./types"; -import { - getBase58Codec, - getBase58Encoder, type Account, type Address, + getBase58Codec, + getBase58Encoder, } from "@solana/kit"; -import { sleep } from "../../../utils/time"; import { createPublicClient, createWalletClient, decodeEventLog, encodeAbiParameters, + type Hash, + type Hex, http, keccak256, + type PublicClient, padHex, toHex, - type Hash, - type Hex, - type PublicClient, type WalletClient, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; +import { + type Call, + type fetchOutgoingMessage, + getIxAccountEncoder, + type Ix, + type OutgoingMessage, +} from "../../../clients/ts/src/bridge"; +import { BRIDGE_ABI } from "../../../interfaces/abis/bridge.abi"; +import { BRIDGE_VALIDATOR_ABI } from "../../../interfaces/abis/bridge-validator.abi"; +import { type Logger, NOOP_LOGGER } from "../../../utils/logger"; +import { sleep } from "../../../utils/time"; import { DEFAULT_EVM_GAS_LIMIT, DEFAULT_MONITOR_POLL_INTERVAL_MS, DEFAULT_MONITOR_TIMEOUT_MS, } from "./constants"; -import { type Logger, NOOP_LOGGER } from "../../../utils/logger"; -import { BRIDGE_VALIDATOR_ABI } from "../../../interfaces/abis/bridge-validator.abi"; +import { type CallParams, type EngineConfig, MessageType } from "./types"; export interface BaseEngineOpts { config: EngineConfig; @@ -102,7 +102,7 @@ export class BaseEngine { async bridgeCall(opts: BaseBridgeCallOpts): Promise { if (!this.walletClient || !this.config.base.privateKey) { throw new Error( - "Base wallet client not initialized (missing privateKey)" + "Base wallet client not initialized (missing privateKey)", ); } @@ -124,7 +124,7 @@ export class BaseEngine { async bridgeToken(opts: BaseBridgeTokenOpts): Promise { if (!this.walletClient || !this.config.base.privateKey) { throw new Error( - "Base wallet client not initialized (missing privateKey)" + "Base wallet client not initialized (missing privateKey)", ); } @@ -164,7 +164,7 @@ export class BaseEngine { .map((log) => { if (blockNumber < log.blockNumber) { throw new Error( - `Solana bridge state is stale (behind transaction block). Bridge state block: ${blockNumber}, Transaction block: ${log.blockNumber}` + `Solana bridge state is stale (behind transaction block). Bridge state block: ${blockNumber}, Transaction block: ${log.blockNumber}`, ); } @@ -210,7 +210,7 @@ export class BaseEngine { async monitorMessageExecution( outgoingMessageAccount: Account, - options: { timeoutMs?: number; pollIntervalMs?: number } = {} + options: { timeoutMs?: number; pollIntervalMs?: number } = {}, ) { const timeoutMs = options.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS; const pollIntervalMs = @@ -243,11 +243,11 @@ export class BaseEngine { gasLimit?: bigint; timeoutMs?: number; pollIntervalMs?: number; - } = {} + } = {}, ): Promise { if (!this.walletClient || !this.config.base.privateKey) { throw new Error( - "Base wallet client not initialized (missing privateKey)" + "Base wallet client not initialized (missing privateKey)", ); } @@ -256,7 +256,7 @@ export class BaseEngine { // Compute inner message hash as Base contracts do const { outerHash, evmMessage } = this.buildEvmMessage( outgoingMessageAccount, - options.gasLimit + options.gasLimit, ); // Batch all on-chain reads into a single multicall for performance @@ -293,14 +293,14 @@ export class BaseEngine { // Check if message previously failed if (failuresResult) { throw new Error( - `Message previously failed execution on Base. Hash: ${outerHash}` + `Message previously failed execution on Base. Hash: ${outerHash}`, ); } // Assert Bridge.getMessageHash(message) equals expected hash if (this.sanitizeHex(messageHashResult) !== this.sanitizeHex(outerHash)) { throw new Error( - `Hash mismatch: getMessageHash != expected. got=${messageHashResult}, expected=${outerHash}` + `Hash mismatch: getMessageHash != expected. got=${messageHashResult}, expected=${outerHash}`, ); } @@ -308,7 +308,7 @@ export class BaseEngine { await this.waitForApproval( outerHash, options.timeoutMs, - options.pollIntervalMs + options.pollIntervalMs, ); // Execute the message on Base @@ -327,7 +327,7 @@ export class BaseEngine { private async waitForApproval( messageHash: Hex, timeoutMs = DEFAULT_MONITOR_TIMEOUT_MS, - intervalMs = DEFAULT_MONITOR_POLL_INTERVAL_MS + intervalMs = DEFAULT_MONITOR_POLL_INTERVAL_MS, ) { const validatorAddress = await this.getValidatorAddress(); @@ -350,12 +350,12 @@ export class BaseEngine { await sleep(currentInterval); currentInterval = Math.min( Math.floor(currentInterval * 1.5), - maxInterval + maxInterval, ); } throw new Error( - `Timed out waiting for BridgeValidator approval after ${timeoutMs}ms` + `Timed out waiting for BridgeValidator approval after ${timeoutMs}ms`, ); } @@ -363,7 +363,7 @@ export class BaseEngine { return ixs.map((ix) => ({ programId: this.bytes32FromPubkey(ix.programId), serializedAccounts: ix.accounts.map((acc) => - toHex(new Uint8Array(getIxAccountEncoder().encode(acc))) + toHex(new Uint8Array(getIxAccountEncoder().encode(acc))), ), data: toHex(new Uint8Array(ix.data)), })); @@ -371,7 +371,7 @@ export class BaseEngine { buildEvmMessage( outgoing: Awaited>, - gasLimit: bigint = DEFAULT_EVM_GAS_LIMIT + gasLimit: bigint = DEFAULT_EVM_GAS_LIMIT, ) { const nonce = BigInt(outgoing.data.nonce); const senderBytes32 = this.bytes32FromPubkey(outgoing.data.sender); @@ -380,8 +380,8 @@ export class BaseEngine { const innerHash = keccak256( encodeAbiParameters( [{ type: "bytes32" }, { type: "uint8" }, { type: "bytes" }], - [senderBytes32, ty, data] - ) + [senderBytes32, ty, data], + ), ); const pubkey = getBase58Codec().encode(outgoing.address); @@ -389,8 +389,8 @@ export class BaseEngine { const outerHash = keccak256( encodeAbiParameters( [{ type: "uint64" }, { type: "bytes32" }, { type: "bytes32" }], - [nonce, `0x${pubkey.toHex()}`, innerHash] - ) + [nonce, `0x${pubkey.toHex()}`, innerHash], + ), ); const evmMessage = { @@ -413,7 +413,7 @@ export class BaseEngine { } private buildIncomingPayload( - outgoing: Awaited> + outgoing: Awaited>, ) { const msg = outgoing.data.message; @@ -451,7 +451,7 @@ export class BaseEngine { ], }, ], - [transferTuple] + [transferTuple], ); if (transfer.call.__option === "None") { @@ -483,7 +483,7 @@ export class BaseEngine { ], }, ], - [transferTuple, callTuple] + [transferTuple, callTuple], ); return { ty, data, transferTuple, callTuple }; @@ -514,7 +514,7 @@ export class BaseEngine { value: BigInt(call.value), data: toHex(new Uint8Array(call.data)), }, - ] + ], ); } diff --git a/src/core/protocol/engines/constants.ts b/src/core/protocol/engines/constants.ts index 9671e5d..5f4c455 100644 --- a/src/core/protocol/engines/constants.ts +++ b/src/core/protocol/engines/constants.ts @@ -1,10 +1,10 @@ import { address } from "@solana/kit"; export const TOKEN_2022_PROGRAM_ADDRESS = address( - "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" + "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", ); export const SYSTEM_PROGRAM_ADDRESS = address( - "11111111111111111111111111111111" + "11111111111111111111111111111111", ); export const DEFAULT_RELAY_GAS_LIMIT = 200_000n; diff --git a/src/core/protocol/engines/solana-engine.ts b/src/core/protocol/engines/solana-engine.ts index f623fb3..dd855b8 100644 --- a/src/core/protocol/engines/solana-engine.ts +++ b/src/core/protocol/engines/solana-engine.ts @@ -1,31 +1,8 @@ import { - CallType, - fetchBridge, - fetchMaybeIncomingMessage, - fetchMaybeOutgoingMessage, - fetchOutgoingMessage, - getBridgeCallInstruction, - getBridgeSolInstruction, - getBridgeSplInstruction, - getBridgeWrappedTokenInstruction, - getProveMessageInstruction, - getRelayMessageInstruction, - getWrapTokenInstruction, - type Ix, - type OutgoingMessage, - type WrapTokenInstructionDataArgs, -} from "../../../clients/ts/src/bridge"; -import type { - EngineConfig, - MessageCall, - MessageTransfer, - MessageTransferSol, - MessageTransferSpl, - MessageTransferWrappedToken, - Rpc, -} from "./types"; -import { getIdlConstant } from "../../../utils/bridge-idl.constants"; -import { + type Account, + type AccountMeta, + AccountRole, + address, addSignersToTransactionMessage, appendTransactionMessageInstructions, assertIsSendableTransaction, @@ -34,35 +11,25 @@ import { createSolanaRpc, createSolanaRpcSubscriptions, createTransactionMessage, + Endian, + getBase58Codec, + getBase58Encoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, getSignatureFromTransaction, + getU8Codec, + getU64Encoder, + type Instruction, + type KeyPairSigner, pipe, + type Signature, + type Address as SolAddress, sendAndConfirmTransactionFactory, setTransactionMessageFeePayer, setTransactionMessageLifetimeUsingBlockhash, signTransactionMessageWithSigners, - type Instruction, - type KeyPairSigner, type TransactionSigner, - type Address as SolAddress, - type Account, - address, - getBase58Encoder, - getU8Codec, - getU64Encoder, - Endian, - AccountRole, - type AccountMeta, - getBase58Codec, - type Signature, } from "@solana/kit"; -import { keccak256, toBytes, type Address, type Hash, type Hex } from "viem"; -import { - fetchCfg, - getPayForRelayInstruction, -} from "../../../clients/ts/src/base-relayer"; -import { getRelayerIdlConstant } from "../../../utils/relayer-idl.constants"; import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, fetchMaybeMint, @@ -70,15 +37,48 @@ import { findAssociatedTokenPda, type Mint, } from "@solana-program/token"; +import { type Address, type Hash, type Hex, keccak256, toBytes } from "viem"; +import { + fetchCfg, + getPayForRelayInstruction, +} from "../../../clients/ts/src/base-relayer"; +import { + CallType, + fetchBridge, + fetchMaybeIncomingMessage, + fetchMaybeOutgoingMessage, + fetchOutgoingMessage, + getBridgeCallInstruction, + getBridgeSolInstruction, + getBridgeSplInstruction, + getBridgeWrappedTokenInstruction, + getProveMessageInstruction, + getRelayMessageInstruction, + getWrapTokenInstruction, + type Ix, + type OutgoingMessage, + type WrapTokenInstructionDataArgs, +} from "../../../clients/ts/src/bridge"; +import { getIdlConstant } from "../../../utils/bridge-idl.constants"; +import { getRelayerIdlConstant } from "../../../utils/relayer-idl.constants"; +import { sleep } from "../../../utils/time"; import { - SYSTEM_PROGRAM_ADDRESS, - TOKEN_2022_PROGRAM_ADDRESS, - DEFAULT_RELAY_GAS_LIMIT, DEFAULT_MONITOR_POLL_INTERVAL_MS, DEFAULT_MONITOR_TIMEOUT_MS, + DEFAULT_RELAY_GAS_LIMIT, + SYSTEM_PROGRAM_ADDRESS, + TOKEN_2022_PROGRAM_ADDRESS, } from "./constants"; -import { sleep } from "../../../utils/time"; -import type { CallParams } from "./types"; +import type { + CallParams, + EngineConfig, + MessageCall, + MessageTransfer, + MessageTransferSol, + MessageTransferSpl, + MessageTransferWrappedToken, + Rpc, +} from "./types"; export interface SolanaEngineOpts { config: EngineConfig; @@ -139,7 +139,7 @@ export class SolanaEngine { async getOutgoingMessage( pubkey: SolAddress, - options: { timeoutMs?: number; pollIntervalMs?: number } = {} + options: { timeoutMs?: number; pollIntervalMs?: number } = {}, ): Promise> { const rpc = createSolanaRpc(this.config.solana.rpcUrl); const timeoutMs = options.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS; @@ -219,7 +219,7 @@ export class SolanaEngine { * @returns The compute units consumed, or undefined if simulation fails */ async simulateInstructions( - instructions: Instruction[] + instructions: Instruction[], ): Promise { if (instructions.length === 0) { return 0n; @@ -240,8 +240,9 @@ export class SolanaEngine { const txMessage = pipe( createTransactionMessage({ version: 0 }), (msg) => setTransactionMessageFeePayer(feePayer, msg), - (msg) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, msg), - (msg) => appendTransactionMessageInstructions(instructions, msg) + (msg) => + setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, msg), + (msg) => appendTransactionMessageInstructions(instructions, msg), ); // Compile to transaction (unsigned) @@ -296,11 +297,11 @@ export class SolanaEngine { amount: opts.amount, call: this.formatCall(opts.call), }, - { programAddress: this.config.solana.bridgeProgram } + { programAddress: this.config.solana.bridgeProgram }, ), ]; }, - opts.idempotencyKey + opts.idempotencyKey, ); } @@ -344,11 +345,11 @@ export class SolanaEngine { amount, call: this.formatCall(opts.call), }, - { programAddress: this.config.solana.bridgeProgram } + { programAddress: this.config.solana.bridgeProgram }, ), ]; }, - opts.idempotencyKey + opts.idempotencyKey, ); } @@ -378,11 +379,11 @@ export class SolanaEngine { amount, call: this.formatCall(opts.call), }, - { programAddress: this.config.solana.bridgeProgram } + { programAddress: this.config.solana.bridgeProgram }, ), ]; }, - opts.idempotencyKey + opts.idempotencyKey, ); } @@ -413,11 +414,11 @@ export class SolanaEngine { data: Buffer.from(callData, "hex"), }, }, - { programAddress: this.config.solana.bridgeProgram } + { programAddress: this.config.solana.bridgeProgram }, ), ]; }, - opts.idempotencyKey + opts.idempotencyKey, ); } @@ -454,11 +455,11 @@ export class SolanaEngine { encodedSymbol, Buffer.from(instructionArgs.remoteToken), Buffer.from(getU8Codec().encode(instructionArgs.scalerExponent)), - ]) + ]), ); const decimalsSeed = Buffer.from( - getU8Codec().encode(instructionArgs.decimals) + getU8Codec().encode(instructionArgs.decimals), ); const [mintAddress] = await getProgramDerivedAddress({ @@ -483,11 +484,11 @@ export class SolanaEngine { ...instructionArgs, }, - { programAddress: this.config.solana.bridgeProgram } + { programAddress: this.config.solana.bridgeProgram }, ), ]; }, - opts.idempotencyKey + opts.idempotencyKey, ); } @@ -514,7 +515,7 @@ export class SolanaEngine { }; }, rawProof: readonly `0x${string}`[], - blockNumber: bigint + blockNumber: bigint, ): Promise<{ signature?: Signature; messageHash: Hash }> { const rpc = createSolanaRpc(this.config.solana.rpcUrl); @@ -560,7 +561,7 @@ export class SolanaEngine { proof: rawProof.map((e: string) => toBytes(e)), messageHash: toBytes(event.messageHash), }, - { programAddress: this.config.solana.bridgeProgram } + { programAddress: this.config.solana.bridgeProgram }, ); const signature = await this.buildAndSendTransaction([ix], payer); @@ -582,11 +583,11 @@ export class SolanaEngine { const maybeIncomingMessage = await fetchMaybeIncomingMessage( rpc, - messagePda + messagePda, ); if (!maybeIncomingMessage.exists) { throw new Error( - `Message not found at ${messagePda}. Ensure it has been proven on Solana first.` + `Message not found at ${messagePda}. Ensure it has been proven on Solana first.`, ); } const incomingMessage = maybeIncomingMessage; @@ -611,7 +612,7 @@ export class SolanaEngine { : await this.messageTransferAccounts( rpc, message, - this.config.solana.bridgeProgram + this.config.solana.bridgeProgram, ); remainingAccounts = remainingAccounts.map((acct) => { @@ -634,7 +635,7 @@ export class SolanaEngine { const relayMessageIx = getRelayMessageInstruction( { message: messagePda, bridge: bridgeAccountAddress }, - { programAddress: this.config.solana.bridgeProgram } + { programAddress: this.config.solana.bridgeProgram }, ); const relayMessageIxWithRemainingAccounts: Instruction = { @@ -645,7 +646,7 @@ export class SolanaEngine { const signature = await this.buildAndSendTransaction( [relayMessageIxWithRemainingAccounts], - payer + payer, ); return signature; } @@ -668,18 +669,18 @@ export class SolanaEngine { private async messageTransferAccounts( rpc: Rpc, message: MessageTransfer, - solanaBridge: SolAddress + solanaBridge: SolAddress, ) { const remainingAccounts: Array = message.transfer.__kind === "Sol" ? await this.messageTransferSolAccounts(message.transfer, solanaBridge) : message.transfer.__kind === "Spl" - ? await this.messageTransferSplAccounts( - rpc, - message.transfer, - solanaBridge - ) - : await this.messageTransferWrappedTokenAccounts(message.transfer); + ? await this.messageTransferSplAccounts( + rpc, + message.transfer, + solanaBridge, + ) + : await this.messageTransferWrappedTokenAccounts(message.transfer); const ixs = message.ixs; @@ -688,7 +689,7 @@ export class SolanaEngine { ...ixs.map((i: Ix) => ({ address: i.programId, role: AccountRole.READONLY, - })) + })), ); return remainingAccounts; @@ -696,7 +697,7 @@ export class SolanaEngine { private async messageTransferSolAccounts( message: MessageTransferSol, - solanaBridge: SolAddress + solanaBridge: SolAddress, ) { const { to } = message.fields[0]; @@ -715,7 +716,7 @@ export class SolanaEngine { private async messageTransferSplAccounts( rpc: Rpc, message: MessageTransferSpl, - solanaBridge: SolAddress + solanaBridge: SolAddress, ) { const { remoteToken, localToken, to } = message.fields[0]; @@ -742,7 +743,7 @@ export class SolanaEngine { } private async messageTransferWrappedTokenAccounts( - message: MessageTransferWrappedToken + message: MessageTransferWrappedToken, ) { const { localToken, to } = message.fields[0]; @@ -766,10 +767,10 @@ export class SolanaEngine { ? AccountRole.WRITABLE_SIGNER : AccountRole.WRITABLE : acc.isSigner - ? AccountRole.READONLY_SIGNER - : AccountRole.READONLY, + ? AccountRole.READONLY_SIGNER + : AccountRole.READONLY, }; - }) + }), ); allIxsAccounts.push(...ixAccounts); @@ -802,18 +803,17 @@ export class SolanaEngine { outgoingMessage: SolAddress; salt: Uint8Array; }) => Promise, - idempotencyKey?: string + idempotencyKey?: string, ): Promise { - const { payer, bridge, outgoingMessage, salt } = await this.setupMessage( - idempotencyKey - ); + const { payer, bridge, outgoingMessage, salt } = + await this.setupMessage(idempotencyKey); const ixs = await builder({ payer, bridge, outgoingMessage, salt }); return await this.submitMessage( ixs, outgoingMessage, payer, !!payForRelay, - gasLimit + gasLimit, ); } @@ -828,15 +828,14 @@ export class SolanaEngine { const bridge = await fetchBridge(rpc, bridgeAccountAddress); - const { salt, pubkey: outgoingMessage } = await this.outgoingMessagePubkey( - idempotencyKey - ); + const { salt, pubkey: outgoingMessage } = + await this.outgoingMessagePubkey(idempotencyKey); return { payer, bridge, outgoingMessage, salt }; } private async setupSpl( opts: { mint: string; amount: bigint }, - payer: KeyPairSigner + payer: KeyPairSigner, ) { const rpc = createSolanaRpc(this.config.solana.rpcUrl); @@ -851,7 +850,7 @@ export class SolanaEngine { const fromTokenAccount = await this.resolveFromTokenAccount( "payer", payer.address, - maybeMint + maybeMint, ); const tokenProgram = maybeMint.programAddress; @@ -863,11 +862,15 @@ export class SolanaEngine { outgoingMessage: SolAddress, payer: KeyPairSigner, payForRelay: boolean, - gasLimit?: bigint + gasLimit?: bigint, ): Promise { if (payForRelay) { ixs.push( - await this.buildPayForRelayInstruction(outgoingMessage, payer, gasLimit) + await this.buildPayForRelayInstruction( + outgoingMessage, + payer, + gasLimit, + ), ); } @@ -903,7 +906,7 @@ export class SolanaEngine { private async buildAndSendTransaction( instructions: Instruction[], - payer: TransactionSigner + payer: TransactionSigner, ) { const rpc = createSolanaRpc(this.config.solana.rpcUrl); @@ -923,12 +926,11 @@ export class SolanaEngine { (tx) => setTransactionMessageFeePayer(payer.address, tx), (tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash.value, tx), (tx) => appendTransactionMessageInstructions(instructions, tx), - (tx) => addSignersToTransactionMessage([payer], tx) + (tx) => addSignersToTransactionMessage([payer], tx), ); - const signedTransaction = await signTransactionMessageWithSigners( - transactionMessage - ); + const signedTransaction = + await signTransactionMessageWithSigners(transactionMessage); const signature = getSignatureFromTransaction(signedTransaction); assertIsSendableTransaction(signedTransaction); @@ -944,7 +946,7 @@ export class SolanaEngine { private async buildPayForRelayInstruction( outgoingMessage: SolAddress, payer: KeyPairSigner, - gasLimit?: bigint + gasLimit?: bigint, ) { const rpc = createSolanaRpc(this.config.solana.rpcUrl); @@ -956,7 +958,7 @@ export class SolanaEngine { const cfg = await fetchCfg(rpc, cfgAddress); const { salt, pubkey: messageToRelay } = await this.mtrPubkey( - this.config.solana.relayerProgram + this.config.solana.relayerProgram, ); return getPayForRelayInstruction( @@ -971,7 +973,7 @@ export class SolanaEngine { outgoingMessage: outgoingMessage, gasLimit: gasLimit ?? DEFAULT_RELAY_GAS_LIMIT, }, - { programAddress: this.config.solana.relayerProgram } + { programAddress: this.config.solana.relayerProgram }, ); } @@ -989,7 +991,7 @@ export class SolanaEngine { private async resolveFromTokenAccount( from: string, payerAddress: SolAddress, - mint: Account + mint: Account, ) { const rpc = createSolanaRpc(this.config.solana.rpcUrl); @@ -1009,7 +1011,7 @@ export class SolanaEngine { tokenProgram: mint.programAddress, mint: mint.address, }, - { programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS } + { programAddress: ASSOCIATED_TOKEN_PROGRAM_ADDRESS }, ); const maybeAta = await fetchMaybeToken(rpc, ataAddress); diff --git a/src/core/protocol/engines/types.ts b/src/core/protocol/engines/types.ts index 589e1a7..041e7e5 100644 --- a/src/core/protocol/engines/types.ts +++ b/src/core/protocol/engines/types.ts @@ -1,10 +1,10 @@ +import type { Address, createSolanaRpc, KeyPairSigner } from "@solana/kit"; +import type { Chain, Address as EvmAddress, Hex } from "viem"; import type { BridgeBaseToSolanaStateIncomingMessageMessage, BridgeBaseToSolanaStateIncomingMessageTransfer, CallType, } from "../../../clients/ts/src/bridge"; -import type { Address, createSolanaRpc, KeyPairSigner } from "@solana/kit"; -import type { Chain, Address as EvmAddress, Hex } from "viem"; export interface BaseConfig { rpcUrl: string; diff --git a/src/core/protocol/identity.ts b/src/core/protocol/identity.ts index 420b256..df5e3f3 100644 --- a/src/core/protocol/identity.ts +++ b/src/core/protocol/identity.ts @@ -1,4 +1,4 @@ -import { encodeAbiParameters, keccak256, type Hex } from "viem"; +import { encodeAbiParameters, type Hex, keccak256 } from "viem"; import type { fetchOutgoingMessage } from "../../clients/ts/src/bridge"; import { bytes32FromSolanaPubkey, @@ -20,7 +20,7 @@ export interface EvmIncomingMessage { */ export function buildEvmIncomingMessage( outgoing: Awaited>, - args: { gasLimit: bigint } + args: { gasLimit: bigint }, ): { innerHash: Hex; outerHash: Hex; @@ -34,15 +34,15 @@ export function buildEvmIncomingMessage( const innerHash = keccak256( encodeAbiParameters( [{ type: "bytes32" }, { type: "uint8" }, { type: "bytes" }], - [sender, ty, data] - ) + [sender, ty, data], + ), ); const outerHash = keccak256( encodeAbiParameters( [{ type: "uint64" }, { type: "bytes32" }, { type: "bytes32" }], - [nonce, outgoingMessagePubkey, innerHash] - ) + [nonce, outgoingMessagePubkey, innerHash], + ), ); return { diff --git a/src/core/protocol/router.ts b/src/core/protocol/router.ts index a63ce6e..c29df35 100644 --- a/src/core/protocol/router.ts +++ b/src/core/protocol/router.ts @@ -1,3 +1,8 @@ +import type { Address as SolAddress } from "@solana/kit"; +import type { Hex } from "viem"; +import type { EvmChainAdapter } from "../../adapters/chains/evm/types"; +import type { SolanaChainAdapter } from "../../adapters/chains/solana/types"; +import { BridgeUnsupportedRouteError } from "../errors"; import type { BridgeRoute, ChainAdapter, @@ -5,13 +10,8 @@ import type { RouteAdapter, } from "../types"; import { isSolanaChainId } from "../utils"; -import { BridgeUnsupportedRouteError } from "../errors"; -import type { EvmChainAdapter } from "../../adapters/chains/evm/types"; -import type { SolanaChainAdapter } from "../../adapters/chains/solana/types"; import { BaseToSvmRouteAdapter } from "./routes/base-to-svm"; import { SvmToBaseRouteAdapter } from "./routes/svm-to-base"; -import type { Address as SolAddress } from "@solana/kit"; -import type { Hex } from "viem"; /** * Hub chain identifiers for the bridge. @@ -67,7 +67,7 @@ function isBaseEvmChainId(id: string): boolean { } function asSolanaAdapter( - adapter: ChainAdapter + adapter: ChainAdapter, ): SolanaChainAdapter | undefined { return (adapter as any)?.kind === "solana" ? (adapter as SolanaChainAdapter) @@ -98,7 +98,7 @@ export function supportsBridgeRoute(route: BridgeRoute): boolean { export async function resolveBridgeRoute( route: BridgeRoute, chains: Record, - config: BridgeConfig + config: BridgeConfig, ): Promise { const source = chains[route.sourceChain]; const dest = chains[route.destinationChain]; diff --git a/src/core/protocol/routes/base-to-svm.ts b/src/core/protocol/routes/base-to-svm.ts index 8e64b03..b42e339 100644 --- a/src/core/protocol/routes/base-to-svm.ts +++ b/src/core/protocol/routes/base-to-svm.ts @@ -1,7 +1,11 @@ -import type { Address as SolAddress, Instruction } from "@solana/kit"; +import type { Instruction, Address as SolAddress } from "@solana/kit"; import { AccountRole, address as solAddress } from "@solana/kit"; import type { Hash, Hex } from "viem"; import { decodeEventLog, toBytes } from "viem"; +import type { EvmChainAdapter } from "../../../adapters/chains/evm/types"; +import type { SolanaChainAdapter } from "../../../adapters/chains/solana/types"; +import type { Ix } from "../../../clients/ts/src/bridge"; +import { BRIDGE_ABI } from "../../../interfaces/abis/bridge.abi"; import { BridgeAlreadyExecutedError, BridgeNotProvenError, @@ -29,13 +33,9 @@ import type { StatusOptions, } from "../../types"; import { isSolanaDestinationCall } from "../../utils"; -import type { Ix } from "../../../clients/ts/src/bridge"; -import type { EvmChainAdapter } from "../../../adapters/chains/evm/types"; -import type { SolanaChainAdapter } from "../../../adapters/chains/solana/types"; -import type { EngineConfig } from "../engines/types"; -import { SolanaEngine } from "../engines/solana-engine"; import { BaseEngine } from "../engines/base-engine"; -import { BRIDGE_ABI } from "../../../interfaces/abis/bridge.abi"; +import { SolanaEngine } from "../engines/solana-engine"; +import type { EngineConfig } from "../engines/types"; // ───────────────────────────────────────────────────────────────────────────── // Gas estimation constants for Base -> SVM quotes @@ -146,7 +146,7 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { sourceGas = req.action.kind === "call" ? DEFAULT_CALL_GAS : DEFAULT_TRANSFER_GAS; warnings.push( - `Source gas estimation failed: ${err instanceof Error ? err.message : String(err)}. Using conservative estimate.` + `Source gas estimation failed: ${err instanceof Error ? err.message : String(err)}. Using conservative estimate.`, ); } @@ -213,7 +213,9 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { } // Estimate gas for bridgeCall const instructionCount = req.action.call.call.instructions.length; - return BRIDGE_CALL_BASE_GAS + BigInt(instructionCount) * GAS_PER_INSTRUCTION; + return ( + BRIDGE_CALL_BASE_GAS + BigInt(instructionCount) * GAS_PER_INSTRUCTION + ); } if (req.action.kind === "transfer") { @@ -227,7 +229,9 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { }); } const instructionCount = call.call.instructions.length; - return BRIDGE_TOKEN_BASE_GAS + BigInt(instructionCount) * GAS_PER_INSTRUCTION; + return ( + BRIDGE_TOKEN_BASE_GAS + BigInt(instructionCount) * GAS_PER_INSTRUCTION + ); } return BRIDGE_TOKEN_BASE_GAS; } @@ -241,7 +245,7 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { */ private async estimateExecuteFee( req: QuoteRequest, - warnings: string[] + warnings: string[], ): Promise { // Extract instructions from the request let instructions: SolanaInstruction[] = []; @@ -273,21 +277,29 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { // Fee = base tx fee + compute budget fee // Note: This is a simplified model; actual fees depend on priority fee market const computeFee = (totalCU * LAMPORTS_PER_CU) / 1_000_000n; // microlamports to lamports - return SOLANA_BASE_TX_FEE + (computeFee > 0n ? computeFee : MIN_COMPUTE_FEE_LAMPORTS); + return ( + SOLANA_BASE_TX_FEE + + (computeFee > 0n ? computeFee : MIN_COMPUTE_FEE_LAMPORTS) + ); } // Simulation failed - fall back to heuristic warnings.push( - `Could not simulate instructions; using heuristic estimate for ${instructions.length} instruction(s)` + `Could not simulate instructions; using heuristic estimate for ${instructions.length} instruction(s)`, ); - return SOLANA_BASE_TX_FEE + BigInt(instructions.length) * FALLBACK_LAMPORTS_PER_INSTRUCTION; + return ( + SOLANA_BASE_TX_FEE + + BigInt(instructions.length) * FALLBACK_LAMPORTS_PER_INSTRUCTION + ); } /** * Convert SDK SolanaInstruction[] to @solana/kit Instruction[] for simulation. */ - private convertToInstruction(instructions: SolanaInstruction[]): Instruction[] { + private convertToInstruction( + instructions: SolanaInstruction[], + ): Instruction[] { return instructions.map((ix) => ({ programAddress: solAddress(ix.programId), accounts: ix.accounts.map((acc) => ({ @@ -474,7 +486,7 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { if (!txHash) { throw new BridgeProofNotAvailableError( "Missing derived.txHash; cannot prove without the initiating EVM transaction hash.", - { route: ref.route, chain: ref.route.sourceChain } + { route: ref.route, chain: ref.route.sourceChain }, ); } @@ -484,12 +496,12 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { const { event, rawProof } = await this.baseEngine.generateProof( txHash, - blockNumber + blockNumber, ); const res = await this.solanaEngine.handleProveMessage( event, rawProof, - blockNumber + blockNumber, ); if (!res.signature) { @@ -501,7 +513,7 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { async execute( ref: MessageRef, - _opts?: ExecuteOptions + _opts?: ExecuteOptions, ): Promise { const messageHash = ref.source.id.scheme === "evm:messageHash" @@ -525,7 +537,7 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { { route: ref.route, chain: ref.route.destinationChain, - } + }, ); } if (msg.includes("Ensure it has been proven")) { @@ -540,7 +552,7 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { async status( ref: MessageRef, - opts?: StatusOptions + opts?: StatusOptions, ): Promise { const at = Date.now(); const messageHash = @@ -552,7 +564,7 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { const pda = await this.deriveIncomingMessagePda(messageHash); const rpc = (await import("@solana/kit")).createSolanaRpc( - this.solana.rpcUrl + this.solana.rpcUrl, ); const { fetchMaybeIncomingMessage } = await import( "../../../clients/ts/src/bridge" @@ -574,13 +586,13 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { monitor( ref: MessageRef, - opts?: MonitorOptions + opts?: MonitorOptions, ): AsyncIterable { return pollingMonitor((signal) => this.status(ref, { signal }), opts); } private async deriveIncomingMessagePda( - messageHash: Hex + messageHash: Hex, ): Promise { const { getProgramDerivedAddress } = await import("@solana/kit"); const { getIdlConstant } = await import( @@ -626,7 +638,7 @@ export class BaseToSvmRouteAdapter implements RouteAdapter { if (events.length !== 1) { throw new BridgeProofNotAvailableError( `Expected exactly 1 MessageInitiated event in tx receipt; found ${events.length}`, - { route: this.route, chain: this.route.sourceChain } + { route: this.route, chain: this.route.sourceChain }, ); } diff --git a/src/core/protocol/routes/svm-to-base.ts b/src/core/protocol/routes/svm-to-base.ts index 0c82ef5..d35adec 100644 --- a/src/core/protocol/routes/svm-to-base.ts +++ b/src/core/protocol/routes/svm-to-base.ts @@ -1,6 +1,9 @@ import type { Address as SolAddress } from "@solana/kit"; import { address as solAddress } from "@solana/kit"; -import type { Hex, Hash } from "viem"; +import type { Hash, Hex } from "viem"; +import type { EvmChainAdapter } from "../../../adapters/chains/evm/types"; +import type { SolanaChainAdapter } from "../../../adapters/chains/solana/types"; +import { BRIDGE_ABI } from "../../../interfaces/abis/bridge.abi"; import { BridgeUnsupportedActionError, BridgeUnsupportedStepError, @@ -26,12 +29,9 @@ import type { StatusOptions, } from "../../types"; import { isEvmDestinationCall } from "../../utils"; -import type { EvmChainAdapter } from "../../../adapters/chains/evm/types"; -import type { SolanaChainAdapter } from "../../../adapters/chains/solana/types"; -import { SolanaEngine } from "../engines/solana-engine"; import { BaseEngine } from "../engines/base-engine"; +import { SolanaEngine } from "../engines/solana-engine"; import type { EngineConfig } from "../engines/types"; -import { BRIDGE_ABI } from "../../../interfaces/abis/bridge.abi"; import { buildEvmIncomingMessage } from "../identity"; // ───────────────────────────────────────────────────────────────────────────── @@ -144,12 +144,12 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { // Validate gas limit is within allowed bounds if (gasLimit < relayerGasConfig.minGasLimitPerMessage) { warnings.push( - `Gas limit ${gasLimit} is below minimum ${relayerGasConfig.minGasLimitPerMessage}` + `Gas limit ${gasLimit} is below minimum ${relayerGasConfig.minGasLimitPerMessage}`, ); } if (gasLimit > relayerGasConfig.maxGasLimitPerMessage) { warnings.push( - `Gas limit ${gasLimit} exceeds maximum ${relayerGasConfig.maxGasLimitPerMessage}` + `Gas limit ${gasLimit} exceeds maximum ${relayerGasConfig.maxGasLimitPerMessage}`, ); } } @@ -170,7 +170,7 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { // Gas estimation may fail if call would revert, use default destinationGas = gasLimit; warnings.push( - `Destination gas estimation failed: ${err instanceof Error ? err.message : String(err)}. Using provided limit.` + `Destination gas estimation failed: ${err instanceof Error ? err.message : String(err)}. Using provided limit.`, ); } } else if (req.action.kind === "transfer") { @@ -277,7 +277,7 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { const destinationHash = await this.deriveOuterHash( outgoingPda, - gasLimit + gasLimit, ); const messageRef: MessageRef = { @@ -326,7 +326,7 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { const destinationHash = await this.deriveOuterHash( outgoingPda, - gasLimit + gasLimit, ); const messageRef: MessageRef = { @@ -365,7 +365,7 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { const destinationHash = await this.deriveOuterHash( outgoingPda, - gasLimit + gasLimit, ); const messageRef: MessageRef = { @@ -409,7 +409,7 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { * Extract optional EvmCall from an optional DestinationCall. */ private extractOptionalEvmCall( - destCall?: DestinationCall + destCall?: DestinationCall, ): EvmCall | undefined { if (!destCall) return undefined; return this.extractEvmCall(destCall); @@ -421,7 +421,7 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { async execute( ref: MessageRef, - _opts?: ExecuteOptions + _opts?: ExecuteOptions, ): Promise { if ( !ref.destination || @@ -434,7 +434,7 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { } const outgoing = await this.solanaEngine.getOutgoingMessage( - solAddress(ref.source.id.value) + solAddress(ref.source.id.value), ); const tx = await this.baseEngine.executeMessage(outgoing); @@ -443,7 +443,7 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { async status( ref: MessageRef, - opts?: StatusOptions + opts?: StatusOptions, ): Promise { const at = Date.now(); @@ -490,17 +490,17 @@ export class SvmToBaseRouteAdapter implements RouteAdapter { monitor( ref: MessageRef, - opts?: MonitorOptions + opts?: MonitorOptions, ): AsyncIterable { return pollingMonitor((signal) => this.status(ref, { signal }), opts); } private async deriveOuterHash( outgoingPda: SolAddress, - gasLimit: bigint + gasLimit: bigint, ): Promise { const outgoing = await this.solanaEngine.getOutgoingMessage( - solAddress(outgoingPda) + solAddress(outgoingPda), ); const { outerHash } = buildEvmIncomingMessage(outgoing, { gasLimit }); return outerHash as Hash; diff --git a/src/core/types.ts b/src/core/types.ts index 918ed12..ebe82ce 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -358,7 +358,7 @@ export interface RouteAdapter { status(ref: MessageRef, opts?: StatusOptions): Promise; monitor( ref: MessageRef, - opts?: MonitorOptions + opts?: MonitorOptions, ): AsyncIterable; } diff --git a/src/core/utils.ts b/src/core/utils.ts index b657804..de4d12d 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -10,7 +10,7 @@ import type { * Type guard for SolanaCall destination. */ export function isSolanaDestinationCall( - call: DestinationCall + call: DestinationCall, ): call is { kind: "solana"; call: SolanaCall } { return call.kind === "solana"; } @@ -19,7 +19,7 @@ export function isSolanaDestinationCall( * Type guard for EVM destination call. */ export function isEvmDestinationCall( - call: DestinationCall + call: DestinationCall, ): call is { kind: "evm"; call: EvmCall } { return call.kind === "evm"; } @@ -38,20 +38,20 @@ export function isSolanaChainId(chainId: ChainId): boolean { */ export function validateDestinationCall( call: DestinationCall, - route: BridgeRoute + route: BridgeRoute, ): void { const isSvmDestination = isSolanaChainId(route.destinationChain); if (isSvmDestination && call.kind !== "solana") { throw new Error( `Call type mismatch: route destination is Solana but call kind is "${call.kind}". ` + - `Use { kind: "solana", call: SolanaCall } for Base -> SVM routes.` + `Use { kind: "solana", call: SolanaCall } for Base -> SVM routes.`, ); } if (!isSvmDestination && call.kind !== "evm") { throw new Error( `Call type mismatch: route destination is EVM but call kind is "${call.kind}". ` + - `Use { kind: "evm", call: EvmCall } for SVM -> Base routes.` + `Use { kind: "evm", call: EvmCall } for SVM -> Base routes.`, ); } } diff --git a/src/index.ts b/src/index.ts index 171aa6c..adea396 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ +// Re-export KeyPairSigner for consumers using loadSolanaKeypair +export type { KeyPairSigner } from "@solana/kit"; export type { BridgeClient, BridgeClientConfig } from "./core/client"; export { createBridgeClient } from "./core/client"; export type * from "./core/types"; - -// Re-export KeyPairSigner for consumers using loadSolanaKeypair -export type { KeyPairSigner } from "@solana/kit"; diff --git a/src/utils/bridge-idl.constants.ts b/src/utils/bridge-idl.constants.ts index 926989f..7322ef7 100644 --- a/src/utils/bridge-idl.constants.ts +++ b/src/utils/bridge-idl.constants.ts @@ -1,4 +1,4 @@ -import { address, type Address } from "@solana/kit"; +import { type Address, address } from "@solana/kit"; import { IDL } from "../interfaces/idls/bridge.idl"; type BridgeConstants = typeof IDL.constants; @@ -6,34 +6,38 @@ type BridgeConstantNames = BridgeConstants[number]["name"]; type BridgeConstant< T extends BridgeConstants, - Name extends BridgeConstantNames + Name extends BridgeConstantNames, > = Extract; type BridgeConstantField< T extends BridgeConstants, Name extends BridgeConstantNames, - Field extends keyof BridgeConstant = "value" + Field extends keyof BridgeConstant = "value", > = BridgeConstant[Field]; type ParsedConstantValue = BridgeConstantField extends "pubkey" ? Address : BridgeConstantField extends "u128" | "u64" - ? bigint - : BridgeConstantField extends "u16" | "u8" - ? number - : BridgeConstantField extends "bytes" - ? number[] - : BridgeConstantField extends { - array: any; - } - ? number[] - : BridgeConstantField extends "string" - ? string - : never; + ? bigint + : BridgeConstantField extends "u16" | "u8" + ? number + : BridgeConstantField extends "bytes" + ? number[] + : BridgeConstantField extends { + array: any; + } + ? number[] + : BridgeConstantField< + BridgeConstants, + Name, + "type" + > extends "string" + ? string + : never; export const getIdlConstant = ( - name: T + name: T, ): ParsedConstantValue => { const constant = IDL.constants.find((c) => c.name === name); if (!constant) { @@ -61,8 +65,9 @@ export const getIdlConstant = ( case "bytes": return JSON.parse(value) as unknown as ParsedConstantValue; - default: + default: { const t: never = type; return t as unknown as ParsedConstantValue; + } } }; diff --git a/src/utils/relayer-idl.constants.ts b/src/utils/relayer-idl.constants.ts index a48c743..635056d 100644 --- a/src/utils/relayer-idl.constants.ts +++ b/src/utils/relayer-idl.constants.ts @@ -1,4 +1,4 @@ -import { type Address } from "@solana/kit"; +import type { Address } from "@solana/kit"; import { IDL } from "../interfaces/idls/base-relayer.idl"; type BaseRelayerConstants = typeof IDL.constants; @@ -6,46 +6,50 @@ type BaseRelayerConstantNames = BaseRelayerConstants[number]["name"]; type BaseRelayerConstant< T extends BaseRelayerConstants, - Name extends BaseRelayerConstantNames + Name extends BaseRelayerConstantNames, > = Extract; type BaseRelayerConstantField< T extends BaseRelayerConstants, Name extends BaseRelayerConstantNames, - Field extends keyof BaseRelayerConstant = "value" + Field extends keyof BaseRelayerConstant = "value", > = BaseRelayerConstant[Field]; type ParsedConstantValue = BaseRelayerConstantField extends "pubkey" ? Address : BaseRelayerConstantField extends - | "u128" - | "u64" - ? bigint - : BaseRelayerConstantField extends - | "u16" - | "u8" - ? number - : BaseRelayerConstantField< - BaseRelayerConstants, - Name, - "type" - > extends "bytes" - ? number[] - : BaseRelayerConstantField extends { - array: any; - } - ? number[] - : BaseRelayerConstantField< - BaseRelayerConstants, - Name, - "type" - > extends "string" - ? string - : never; + | "u128" + | "u64" + ? bigint + : BaseRelayerConstantField extends + | "u16" + | "u8" + ? number + : BaseRelayerConstantField< + BaseRelayerConstants, + Name, + "type" + > extends "bytes" + ? number[] + : BaseRelayerConstantField< + BaseRelayerConstants, + Name, + "type" + > extends { + array: any; + } + ? number[] + : BaseRelayerConstantField< + BaseRelayerConstants, + Name, + "type" + > extends "string" + ? string + : never; export const getRelayerIdlConstant = ( - name: T + name: T, ): ParsedConstantValue => { const constant = IDL.constants.find((c) => c.name === name); if (!constant) { @@ -67,8 +71,9 @@ export const getRelayerIdlConstant = ( case "u128": return Number(value) as unknown as ParsedConstantValue; - default: + default: { const t: never = type; return t as unknown as ParsedConstantValue; + } } }; diff --git a/tests/hubspoke.test.ts b/tests/hubspoke.test.ts index 5f8baad..0f93a82 100644 --- a/tests/hubspoke.test.ts +++ b/tests/hubspoke.test.ts @@ -1,8 +1,8 @@ -import { test, expect } from "bun:test"; +import { expect, test } from "bun:test"; import { - supportsBridgeRoute, BASE_MAINNET_CHAIN_ID, BASE_SEPOLIA_CHAIN_ID, + supportsBridgeRoute, } from "../src/core/protocol/router"; test("bridge: supports only routes that include Base mainnet or Base Sepolia", () => { @@ -11,7 +11,7 @@ test("bridge: supports only routes that include Base mainnet or Base Sepolia", ( supportsBridgeRoute({ sourceChain: "solana:mainnet", destinationChain: BASE_MAINNET_CHAIN_ID, - }) + }), ).toBe(true); // Allowed (includes Base Sepolia) @@ -19,7 +19,7 @@ test("bridge: supports only routes that include Base mainnet or Base Sepolia", ( supportsBridgeRoute({ sourceChain: BASE_SEPOLIA_CHAIN_ID, destinationChain: "solana:mainnet", - }) + }), ).toBe(true); // Disallowed: no Base in route @@ -27,6 +27,6 @@ test("bridge: supports only routes that include Base mainnet or Base Sepolia", ( supportsBridgeRoute({ sourceChain: "solana:mainnet", destinationChain: "eip155:10", - }) + }), ).toBe(false); }); diff --git a/tests/identity.test.ts b/tests/identity.test.ts index 8c0e2d9..085d3b8 100644 --- a/tests/identity.test.ts +++ b/tests/identity.test.ts @@ -1,10 +1,14 @@ -import { test, expect } from "bun:test"; -import { buildEvmIncomingMessage } from "../src/core/protocol/identity"; -import { BaseEngine } from "../src/core/protocol/engines/base-engine"; -import { address as solAddress, type Account, type KeyPairSigner } from "@solana/kit"; +import { expect, test } from "bun:test"; +import { + type Account, + type KeyPairSigner, + address as solAddress, +} from "@solana/kit"; import { base } from "viem/chains"; import type { OutgoingMessage } from "../src/clients/ts/src/bridge"; import { CallType } from "../src/clients/ts/src/bridge"; +import { BaseEngine } from "../src/core/protocol/engines/base-engine"; +import { buildEvmIncomingMessage } from "../src/core/protocol/identity"; // Mock KeyPairSigner for testing - the payer is not used in this test const mockPayer = { diff --git a/tests/polling-monitor.test.ts b/tests/polling-monitor.test.ts index eac54c7..af6dc7f 100644 --- a/tests/polling-monitor.test.ts +++ b/tests/polling-monitor.test.ts @@ -1,9 +1,9 @@ -import { test, expect } from "bun:test"; +import { expect, test } from "bun:test"; import { pollingMonitor } from "../src/core/monitor/polling"; import type { ExecutionStatus } from "../src/core/types"; async function collect( - iter: AsyncIterable + iter: AsyncIterable, ): Promise { const results: ExecutionStatus[] = []; for await (const s of iter) { @@ -20,7 +20,7 @@ test("pollingMonitor: abort before iteration throws immediately", async () => { Promise.resolve({ type: "Unknown", at: Date.now() } as ExecutionStatus); await expect( - collect(pollingMonitor(getStatus, { signal: ac.signal })) + collect(pollingMonitor(getStatus, { signal: ac.signal })), ).rejects.toThrow(); }); @@ -65,7 +65,7 @@ test("pollingMonitor: abort reason is propagated", async () => { Promise.resolve({ type: "Unknown", at: Date.now() } as ExecutionStatus); await expect( - collect(pollingMonitor(getStatus, { signal: ac.signal })) + collect(pollingMonitor(getStatus, { signal: ac.signal })), ).rejects.toThrow("custom cancellation"); }); @@ -86,7 +86,7 @@ test("pollingMonitor: works normally without signal", async () => { }; const results = await collect( - pollingMonitor(getStatus, { pollIntervalMs: 1 }) + pollingMonitor(getStatus, { pollIntervalMs: 1 }), ); expect(results.map((r) => r.type)).toEqual([ @@ -112,7 +112,9 @@ test("pollingMonitor: abort during slow getStatus rejects promptly", async () => setTimeout(() => ac.abort(), 50); await expect( - collect(pollingMonitor(getStatus, { signal: ac.signal, pollIntervalMs: 1 })) + collect( + pollingMonitor(getStatus, { signal: ac.signal, pollIntervalMs: 1 }), + ), ).rejects.toThrow(); const elapsed = Date.now() - start; @@ -131,8 +133,8 @@ test("pollingMonitor: timeout fires with signal present", async () => { signal: ac.signal, timeoutMs: 100, pollIntervalMs: 10, - }) - ) + }), + ), ).rejects.toThrow("monitor timed out"); }); @@ -152,7 +154,7 @@ test("pollingMonitor: signal present but never aborted runs to completion", asyn }; const results = await collect( - pollingMonitor(getStatus, { signal: ac.signal, pollIntervalMs: 1 }) + pollingMonitor(getStatus, { signal: ac.signal, pollIntervalMs: 1 }), ); expect(results.map((r) => r.type)).toEqual([ @@ -219,7 +221,7 @@ test("pollingMonitor: sleep cleanup — abort after completion causes no unhandl }; const results = await collect( - pollingMonitor(getStatus, { signal: ac.signal, pollIntervalMs: 1 }) + pollingMonitor(getStatus, { signal: ac.signal, pollIntervalMs: 1 }), ); expect(results.map((r) => r.type)).toEqual([ @@ -246,7 +248,7 @@ test("pollingMonitor: getStatus receives the signal argument", async () => { }; await collect( - pollingMonitor(getStatus, { signal: ac.signal, pollIntervalMs: 1 }) + pollingMonitor(getStatus, { signal: ac.signal, pollIntervalMs: 1 }), ); expect(receivedSignal).toBe(ac.signal); diff --git a/tests/quote.test.ts b/tests/quote.test.ts index aaf68ce..70a7d81 100644 --- a/tests/quote.test.ts +++ b/tests/quote.test.ts @@ -1,11 +1,11 @@ -import { test, expect, describe } from "bun:test"; +import { describe, expect, test } from "bun:test"; +import { BASE_MAINNET_CHAIN_ID } from "../src/core/protocol/router"; import type { + BridgeRoute, + FeeEstimate, Quote, QuoteRequest, - FeeEstimate, - BridgeRoute, } from "../src/core/types"; -import { BASE_MAINNET_CHAIN_ID } from "../src/core/protocol/router"; describe("Quote types", () => { test("FeeEstimate supports optional note field", () => { @@ -184,14 +184,10 @@ describe("Quote validation logic", () => { const warnings: string[] = []; if (gasLimit < minGasLimit) { - warnings.push( - `Gas limit ${gasLimit} is below minimum ${minGasLimit}` - ); + warnings.push(`Gas limit ${gasLimit} is below minimum ${minGasLimit}`); } if (gasLimit > maxGasLimit) { - warnings.push( - `Gas limit ${gasLimit} exceeds maximum ${maxGasLimit}` - ); + warnings.push(`Gas limit ${gasLimit} exceeds maximum ${maxGasLimit}`); } expect(warnings).toHaveLength(1); @@ -205,14 +201,10 @@ describe("Quote validation logic", () => { const warnings: string[] = []; if (gasLimit < minGasLimit) { - warnings.push( - `Gas limit ${gasLimit} is below minimum ${minGasLimit}` - ); + warnings.push(`Gas limit ${gasLimit} is below minimum ${minGasLimit}`); } if (gasLimit > maxGasLimit) { - warnings.push( - `Gas limit ${gasLimit} exceeds maximum ${maxGasLimit}` - ); + warnings.push(`Gas limit ${gasLimit} exceeds maximum ${maxGasLimit}`); } expect(warnings).toHaveLength(1); diff --git a/tests/solana-call.test.ts b/tests/solana-call.test.ts index b7a854e..1ea997b 100644 --- a/tests/solana-call.test.ts +++ b/tests/solana-call.test.ts @@ -1,14 +1,14 @@ -import { test, expect, describe } from "bun:test"; +import { describe, expect, test } from "bun:test"; import type { + BridgeRoute, + DestinationCall, EvmCall, SolanaCall, - DestinationCall, - BridgeRoute, } from "../src/core/types"; import { - isSolanaDestinationCall, isEvmDestinationCall, isSolanaChainId, + isSolanaDestinationCall, validateDestinationCall, } from "../src/core/utils"; @@ -118,14 +118,14 @@ describe("validateDestinationCall", () => { test("throws for SolanaCall to EVM destination", () => { const destCall: DestinationCall = { kind: "solana", call: solanaCall }; expect(() => validateDestinationCall(destCall, evmRoute)).toThrow( - /route destination is EVM but call kind is "solana"/ + /route destination is EVM but call kind is "solana"/, ); }); test("throws for EvmCall to SVM destination", () => { const destCall: DestinationCall = { kind: "evm", call: evmCall }; expect(() => validateDestinationCall(destCall, svmRoute)).toThrow( - /route destination is Solana but call kind is "evm"/ + /route destination is Solana but call kind is "evm"/, ); }); }); diff --git a/tests/transitions.test.ts b/tests/transitions.test.ts index 52c6ee0..c1a5a97 100644 --- a/tests/transitions.test.ts +++ b/tests/transitions.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test"; +import { expect, test } from "bun:test"; import { isAllowedTransition } from "../src/core/capabilities"; test("execution status transitions: happy path is allowed", () => { From 9b481f51bb43ce67ed52df24ca633977a87dfe65 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 15 Feb 2026 21:18:15 -0500 Subject: [PATCH 2/2] downgrade biome --- bun.lock | 20 ++++++++++---------- package.json | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bun.lock b/bun.lock index c8d85ce..e754d84 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,7 @@ "viem": "2.39.2", }, "devDependencies": { - "@biomejs/biome": "2.4.0", + "@biomejs/biome": "2.3.15", "@types/bun": "1.3.2", "typescript": "5.9.3", }, @@ -22,23 +22,23 @@ "packages": { "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], - "@biomejs/biome": ["@biomejs/biome@2.4.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.0", "@biomejs/cli-darwin-x64": "2.4.0", "@biomejs/cli-linux-arm64": "2.4.0", "@biomejs/cli-linux-arm64-musl": "2.4.0", "@biomejs/cli-linux-x64": "2.4.0", "@biomejs/cli-linux-x64-musl": "2.4.0", "@biomejs/cli-win32-arm64": "2.4.0", "@biomejs/cli-win32-x64": "2.4.0" }, "bin": { "biome": "bin/biome" } }, "sha512-iluT61cORUDIC5i/y42ljyQraCemmmcgbMLLCnYO+yh+2hjTmcMFcwY8G0zTzWCsPb3t3AyKc+0t/VuhPZULUg=="], + "@biomejs/biome": ["@biomejs/biome@2.3.15", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.15", "@biomejs/cli-darwin-x64": "2.3.15", "@biomejs/cli-linux-arm64": "2.3.15", "@biomejs/cli-linux-arm64-musl": "2.3.15", "@biomejs/cli-linux-x64": "2.3.15", "@biomejs/cli-linux-x64-musl": "2.3.15", "@biomejs/cli-win32-arm64": "2.3.15", "@biomejs/cli-win32-x64": "2.3.15" }, "bin": { "biome": "bin/biome" } }, "sha512-u+jlPBAU2B45LDkjjNNYpc1PvqrM/co4loNommS9/sl9oSxsAQKsNZejYuUztvToB5oXi1tN/e62iNd6ESiY3g=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-L+YpOtPSuU0etomfvFTPWRsa7+8ejaJL3yaROEoT/96HDJbR6OsvZQk0C8JUYou+XFdP+JcGxqZknkp4n934RA=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SDCdrJ4COim1r8SNHg19oqT50JfkI/xGZHSyC6mGzMfKrpNe/217Eq6y98XhNTc0vGWDjznSDNXdUc6Kg24jbw=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Aq+S7ffpb5ynTyLgtnEjG+W6xuTd2F7FdC7J6ShpvRhZwJhjzwITGF9vrqoOnw0sv1XWkt2Q1Rpg+hleg/Xg7Q=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-RkyeSosBtn3C3Un8zQnl9upX0Qbq4E3QmBa0qjpOh1MebRbHhNlRC16jk8HdTe/9ym5zlfnpbb8cKXzW+vlTxw=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-u2p54IhvNAWB+h7+rxCZe3reNfQYFK+ppDw+q0yegrGclFYnDPZAntv/PqgUacpC3uxTeuWFgWW7RFe3lHuxOA=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-FN83KxrdVWANOn5tDmW6UBC0grojchbGmcEz6JkRs2YY6DY63sTZhwkQ56x6YtKhDVV1Unz7FJexy8o7KwuIhg=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1rhDUq8sf7xX3tg7vbnU3WVfanKCKi40OXc4VleBMzRStmQHdeBY46aFP6VdwEomcVjyNiu+Zcr3LZtAdrZrjQ=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-SSSIj2yMkFdSkXqASzIBdjySBXOe65RJlhKEDlri7MN19RC4cpez+C0kEwPrhXOTgJbwQR9QH1F4+VnHkC35pg=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WVFOhsnzhrbMGOSIcB9yFdRV2oG2KkRRhIZiunI9gJqSU3ax9ErdnTxRfJUxZUI9NbzVxC60OCXNcu+mXfF/Tw=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.15", "", { "os": "linux", "cpu": "x64" }, "sha512-T8n9p8aiIKOrAD7SwC7opiBM1LYGrE5G3OQRXWgbeo/merBk8m+uxJ1nOXMPzfYyFLfPlKF92QS06KN1UW+Zbg=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Omo0xhl63z47X+CrE5viEWKJhejJyndl577VoXg763U/aoATrK3r5+8DPh02GokWPeODX1Hek00OtjjooGan9w=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.15", "", { "os": "linux", "cpu": "x64" }, "sha512-dbjPzTh+ijmmNwojFYbQNMFp332019ZDioBYAMMJj5Ux9d8MkM+u+J68SBJGVwVeSHMYj+T9504CoxEzQxrdNw=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-aqRwW0LJLV1v1NzyLvLWQhdLmDSAV1vUh+OBdYJaa8f28XBn5BZavo+WTfqgEzALZxlNfBmu6NGO6Al3MbCULw=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-puMuenu/2brQdgqtQ7geNwQlNVxiABKEZJhMRX6AGWcmrMO8EObMXniFQywy2b81qmC+q+SDvlOpspNwz0WiOA=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.0", "", { "os": "win32", "cpu": "x64" }, "sha512-g47s+V+OqsGxbSZN3lpav6WYOk0PIc3aCBAq+p6dwSynL3K5MA6Cg6nkzDOlu28GEHwbakW+BllzHCJCxnfK5Q=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.15", "", { "os": "win32", "cpu": "x64" }, "sha512-kDZr/hgg+igo5Emi0LcjlgfkoGZtgIpJKhnvKTRmMBv6FF/3SDyEV4khBwqNebZIyMZTzvpca9sQNSXJ39pI2A=="], "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], diff --git a/package.json b/package.json index 76cce30..b9831cc 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "author": "Base", "license": "MIT", "devDependencies": { - "@biomejs/biome": "2.4.0", + "@biomejs/biome": "2.3.15", "@types/bun": "1.3.2", "typescript": "5.9.3" },