diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..73e09e0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{json,yml,yaml}] +indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3c071f..adb0403 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,9 @@ jobs: - name: Install dependencies run: npm install pnpm@latest -g && pnpm install + - name: Lint + run: pnpm lint + - name: Build run: pnpm run build diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..d9a32f2 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,15 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 120, + "tabWidth": 4, + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "experimentalSortImports": {}, + "ignorePatterns": [ + "dist", + "coverage", + "*.d.ts", + "packages/types/src/primitives" + ] +} diff --git a/AGENTS.md b/AGENTS.md index 173a9cb..b497af2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,11 +17,13 @@ ## Coding Style & Naming Conventions - TypeScript strict mode; ES2020+/ESNext modules. -- 2‑space indent, double quotes, named exports when practical. +- 4‑space indent, double quotes, named exports when practical. +- Format touched files before committing: `pnpm oxfmt --write ...`. - Files: kebab‑case; tests end with `.test.ts`; entry files `index.ts`. - Group imports; use explicit types for public APIs. - Use `node:crypto` (`randomInt`) instead of `Math.random()` when selecting from security‑relevant sets (e.g. tip accounts, signers). - Type all external API responses with explicit interfaces; never leave `fetch` JSON as `any`. +- No JSDoc, no code comments; the code should be self-explanatory. - Pre‑compute expensive objects (e.g. `PublicKey`) at module level when the inputs are static constants. ## Testing Guidelines @@ -40,7 +42,7 @@ ## Commit & Pull Request Guidelines - Prefer conventional style where helpful: `feat|fix|chore(scope): message`; keep messages imperative. - PRs: small, focused; include description, linked issues/PRs, and test notes or screenshots for API responses. -- Must pass `pnpm build` and `pnpm test`; do not commit `dist/`. +- Must pass `pnpm build`, `pnpm test`, and `pnpm lint`; do not commit `dist/`. ## Security & Configuration Tips - Configure via env vars used by API/providers: `PORT`, `SOLANA_URL`, `SUI_URL`, `TON_URL`. @@ -49,5 +51,6 @@ ## Agent‑Specific Instructions (all code agents) - Use `pnpm` workspace filters (`--filter`) and Justfile tasks; avoid changing file layout. - Keep edits minimal and focused; update adjacent docs/tests when touching APIs or providers. +- Fix any lint issues in files you touch: `pnpm oxlint ...`. - Prefer mocks for external calls; do not add unvetted network dependencies. - Reflect provider additions/removals in `apps/api/src/index.ts` and docs; exclude unimplemented protocols. diff --git a/apps/api/jest.config.js b/apps/api/jest.config.js index 26efa23..b4be920 100644 --- a/apps/api/jest.config.js +++ b/apps/api/jest.config.js @@ -1,19 +1,19 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - useESM: true, - tsconfig: 'tsconfig.json' - }, - ], - }, - extensionsToTreatAsEsm: ['.ts'], - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - testPathIgnorePatterns: ['/node_modules/', '/dist/'], + preset: "ts-jest", + testEnvironment: "node", + transform: { + "^.+\\.tsx?$": [ + "ts-jest", + { + useESM: true, + tsconfig: "tsconfig.json", + }, + ], + }, + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testPathIgnorePatterns: ["/node_modules/", "/dist/"], }; diff --git a/apps/api/package.json b/apps/api/package.json index 396f631..7bc247c 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,30 +1,30 @@ { - "name": "@gemwallet/api", - "version": "1.0.0", - "private": true, - "description": "Express API application for the monorepo", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "start": "node dist/index.js", - "dev": "ts-node-dev --respawn --transpile-only --clear src/index.ts", - "test": "jest --passWithNoTests" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@gemwallet/swapper": "workspace:*", - "@gemwallet/types": "workspace:*", - "dotenv": "17.3.1", - "express": "5.2.1" - }, - "devDependencies": { - "@types/express": "5.0.6", - "@types/node": "25.2.3", - "ts-node-dev": "2.0.0" - }, - "files": [ - "dist" - ] -} \ No newline at end of file + "name": "@gemwallet/api", + "version": "1.0.0", + "private": true, + "description": "Express API application for the monorepo", + "keywords": [], + "license": "ISC", + "author": "", + "files": [ + "dist" + ], + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "ts-node-dev --respawn --transpile-only --clear src/index.ts", + "test": "jest --passWithNoTests" + }, + "dependencies": { + "@gemwallet/swapper": "workspace:*", + "@gemwallet/types": "workspace:*", + "dotenv": "17.3.1", + "express": "5.2.1" + }, + "devDependencies": { + "@types/express": "5.0.6", + "@types/node": "25.2.3", + "ts-node-dev": "2.0.0" + } +} diff --git a/apps/api/src/error.ts b/apps/api/src/error.ts index 559572f..4b02a7a 100644 --- a/apps/api/src/error.ts +++ b/apps/api/src/error.ts @@ -5,49 +5,49 @@ type ErrorResponse = { type: string; message: string | object }; export type ProxyErrorResponse = { err: ErrorResponse } | { error: string }; export function errorResponse(err: SwapperError, rawError: unknown, structured: boolean): ProxyErrorResponse { - const rawMessage = extractMessage(rawError); - if (!structured) { - return { error: rawMessage ?? ("message" in err ? err.message : undefined) ?? "Unknown error occurred" }; - } - if (hasStringMessage(err)) { - return { err: { type: err.type, message: rawMessage ?? err.message ?? "" } }; - } - const { type, ...rest } = err; - return { err: { type, message: rest } }; + const rawMessage = extractMessage(rawError); + if (!structured) { + return { error: rawMessage ?? ("message" in err ? err.message : undefined) ?? "Unknown error occurred" }; + } + if (hasStringMessage(err)) { + return { err: { type: err.type, message: rawMessage ?? err.message ?? "" } }; + } + const { type, ...rest } = err; + return { err: { type, message: rest } }; } export function httpStatus(err: SwapperError): number { - switch (err.type) { - case "input_amount_error": - case "not_supported_chain": - case "not_supported_asset": - case "invalid_route": - return 400; - case "no_available_provider": - case "no_quote_available": - return 404; - case "compute_quote_error": - case "transaction_error": - default: - return 500; - } + switch (err.type) { + case "input_amount_error": + case "not_supported_chain": + case "not_supported_asset": + case "invalid_route": + return 400; + case "no_available_provider": + case "no_quote_available": + return 404; + case "compute_quote_error": + case "transaction_error": + default: + return 500; + } } function extractMessage(error: unknown): string | undefined { - if (error instanceof Error) return error.message; - if (typeof error === "string") return error; - return undefined; + if (error instanceof Error) return error.message; + if (typeof error === "string") return error; + return undefined; } function hasStringMessage(err: SwapperError): err is Extract { - return err.type === "compute_quote_error" || err.type === "transaction_error"; + return err.type === "compute_quote_error" || err.type === "transaction_error"; } export function sendErrorResponse( - res: Response, - swapperError: SwapperError, - rawError: unknown, - objectResponse: boolean + res: Response, + swapperError: SwapperError, + rawError: unknown, + objectResponse: boolean, ) { - res.status(httpStatus(swapperError)).json(errorResponse(swapperError, rawError, objectResponse)); + res.status(httpStatus(swapperError)).json(errorResponse(swapperError, rawError, objectResponse)); } diff --git a/apps/api/src/index.test.ts b/apps/api/src/index.test.ts index 8784108..a391dad 100644 --- a/apps/api/src/index.test.ts +++ b/apps/api/src/index.test.ts @@ -1,29 +1,30 @@ -import { errorResponse, httpStatus } from "./error"; import { SwapperError } from "@gemwallet/types"; +import { errorResponse, httpStatus } from "./error"; + describe("httpStatus", () => { - it.each([ - [400, { type: "input_amount_error", min_amount: "100" }], - [404, { type: "no_quote_available" }], - [500, { type: "compute_quote_error", message: "error" }], - ] as const)("returns %i for %s", (expected, err) => { - expect(httpStatus(err as SwapperError)).toBe(expected); - }); + it.each([ + [400, { type: "input_amount_error", min_amount: "100" }], + [404, { type: "no_quote_available" }], + [500, { type: "compute_quote_error", message: "error" }], + ] as const)("returns %i for %s", (expected, err) => { + expect(httpStatus(err as SwapperError)).toBe(expected); + }); }); describe("errorResponse", () => { - it("wraps input_amount_error fields in message object", () => { - const result = errorResponse({ type: "input_amount_error", min_amount: "19620000" }, null, true); - expect(result).toEqual({ err: { type: "input_amount_error", message: { min_amount: "19620000" } } }); - }); + it("wraps input_amount_error fields in message object", () => { + const result = errorResponse({ type: "input_amount_error", min_amount: "19620000" }, null, true); + expect(result).toEqual({ err: { type: "input_amount_error", message: { min_amount: "19620000" } } }); + }); - it("uses raw error message for compute_quote_error", () => { - const result = errorResponse({ type: "compute_quote_error", message: "" }, new Error("fail"), true); - expect(result).toEqual({ err: { type: "compute_quote_error", message: "fail" } }); - }); + it("uses raw error message for compute_quote_error", () => { + const result = errorResponse({ type: "compute_quote_error", message: "" }, new Error("fail"), true); + expect(result).toEqual({ err: { type: "compute_quote_error", message: "fail" } }); + }); - it("returns plain error when not structured", () => { - const result = errorResponse({ type: "compute_quote_error", message: "" }, new Error("fail"), false); - expect(result).toEqual({ error: "fail" }); - }); + it("returns plain error when not structured", () => { + const result = errorResponse({ type: "compute_quote_error", message: "" }, new Error("fail"), false); + expect(result).toEqual({ error: "fail" }); + }); }); diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index f41429c..94f9c1b 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,11 +1,22 @@ import path from "node:path"; + +import { + StonfiProvider, + Protocol, + MayanProvider, + CetusAggregatorProvider, + RelayProvider, + OrcaWhirlpoolProvider, + PanoraProvider, + OkxProvider, + SwapperException, +} from "@gemwallet/swapper"; +import { Quote, QuoteRequest, SwapQuoteData } from "@gemwallet/types"; import dotenv from "dotenv"; import express from "express"; -import { Quote, QuoteRequest, SwapQuoteData } from "@gemwallet/types"; -import { StonfiProvider, Protocol, MayanProvider, CetusAggregatorProvider, RelayProvider, OrcaWhirlpoolProvider, PanoraProvider, OkxProvider, SwapperException } from "@gemwallet/swapper"; -import versionInfo from "./version.json"; import { errorResponse, sendErrorResponse, ProxyErrorResponse } from "./error"; +import versionInfo from "./version.json"; if (process.env.NODE_ENV !== "production") { const rootEnvPath = path.resolve(__dirname, "../../..", ".env"); @@ -26,10 +37,7 @@ const API_VERSION = 1; const providers: Record = { stonfi_v2: new StonfiProvider(process.env.TON_URL || "https://toncenter.com"), - mayan: new MayanProvider( - solanaRpc, - process.env.SUI_URL || "https://fullnode.mainnet.sui.io" - ), + mayan: new MayanProvider(solanaRpc, process.env.SUI_URL || "https://fullnode.mainnet.sui.io"), cetus: new CetusAggregatorProvider(process.env.SUI_URL || "https://fullnode.mainnet.sui.io"), relay: new RelayProvider(), orca: new OrcaWhirlpoolProvider(solanaRpc), @@ -38,82 +46,84 @@ const providers: Record = { }; app.get("/", (_, res) => { - res.json({ - providers: Object.keys(providers), - version: versionInfo.version, - commit: versionInfo.commit, - }); + res.json({ + providers: Object.keys(providers), + version: versionInfo.version, + commit: versionInfo.commit, + }); }); app.post("/:providerId/quote", withProvider, async (req: ProviderRequest, res) => { - const provider = req.provider!; - const objectResponse = req.objectResponse!; - try { - const request: QuoteRequest = req.body; - - const quote = await provider.get_quote(request); - if (objectResponse) { - res.json({ ok: quote } satisfies ProxyResponse); - } else { - res.json(quote); + const provider = req.provider!; + const objectResponse = req.objectResponse!; + try { + const request: QuoteRequest = req.body; + + const quote = await provider.get_quote(request); + if (objectResponse) { + res.json({ ok: quote } satisfies ProxyResponse); + } else { + res.json(quote); + } + } catch (error) { + if (!isProduction) { + console.error("Error fetching quote via POST:", error); + console.debug("Request metadata:", { providerId: req.params.providerId, hasBody: Boolean(req.body) }); + } + const swapperError = SwapperException.isSwapperException(error) + ? error.swapperError + : { type: "compute_quote_error" as const, message: "" }; + sendErrorResponse(res, swapperError, error, objectResponse); } - } catch (error) { - if (!isProduction) { - console.error("Error fetching quote via POST:", error); - console.debug("Request metadata:", { providerId: req.params.providerId, hasBody: Boolean(req.body) }); - } - const swapperError = SwapperException.isSwapperException(error) - ? error.swapperError - : { type: "compute_quote_error" as const, message: "" }; - sendErrorResponse(res, swapperError, error, objectResponse); - } }); app.post("/:providerId/quote_data", withProvider, async (req: ProviderRequest, res) => { - const provider = req.provider!; - const objectResponse = req.objectResponse!; - const quote_request = req.body as Quote; - - try { - const quote = await provider.get_quote_data(quote_request); - if (objectResponse) { - res.json({ ok: quote } satisfies ProxyResponse); - } else { - res.json(quote); - } - } catch (error) { - if (!isProduction) { - console.error("Error fetching quote data:", error); - console.debug("Quote metadata:", { providerId: req.params.providerId, hasQuote: Boolean(quote_request) }); + const provider = req.provider!; + const objectResponse = req.objectResponse!; + const quote_request = req.body as Quote; + + try { + const quote = await provider.get_quote_data(quote_request); + if (objectResponse) { + res.json({ ok: quote } satisfies ProxyResponse); + } else { + res.json(quote); + } + } catch (error) { + if (!isProduction) { + console.error("Error fetching quote data:", error); + console.debug("Quote metadata:", { providerId: req.params.providerId, hasQuote: Boolean(quote_request) }); + } + const swapperError = SwapperException.isSwapperException(error) + ? error.swapperError + : { type: "transaction_error" as const, message: "" }; + sendErrorResponse(res, swapperError, error, objectResponse); } - const swapperError = SwapperException.isSwapperException(error) - ? error.swapperError - : { type: "transaction_error" as const, message: "" }; - sendErrorResponse(res, swapperError, error, objectResponse); - } }); app.listen(PORT, () => { - console.log(`swapper api is running on port ${PORT}.`); + console.log(`swapper api is running on port ${PORT}.`); }); function parseVersion(raw: unknown): number { - const num = typeof raw === "string" ? Number(raw) : Array.isArray(raw) ? Number(raw[0]) : NaN; - return Number.isFinite(num) ? num : 0; + const num = typeof raw === "string" ? Number(raw) : Array.isArray(raw) ? Number(raw[0]) : NaN; + return Number.isFinite(num) ? num : 0; } function withProvider(req: ProviderRequest, res: express.Response, next: express.NextFunction) { - const providerId = req.params.providerId as string; - const provider = providers[providerId]; - const version = parseVersion(req.query.v); - const objectResponse = version >= API_VERSION; - - if (!provider) { - res.status(404).json(errorResponse({ type: "no_available_provider" }, `Provider ${providerId} not found`, objectResponse)); - return; - } - - req.provider = provider; - req.objectResponse = objectResponse; - next(); + const providerId = req.params.providerId as string; + const provider = providers[providerId]; + const version = parseVersion(req.query.v); + const objectResponse = version >= API_VERSION; + + if (!provider) { + res.status(404).json( + errorResponse({ type: "no_available_provider" }, `Provider ${providerId} not found`, objectResponse), + ); + return; + } + + req.provider = provider; + req.objectResponse = objectResponse; + next(); } diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index ead495b..c588c95 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -1,30 +1,25 @@ { "compilerOptions": { - "target": "ES2016", - "module": "CommonJS", - "outDir": "./dist", - "baseUrl": ".", - "paths": { - "@gemwallet/swapper": ["../../packages/swapper/src/index.ts"], - "@gemwallet/types": ["../../packages/types/src/index.ts"] - }, - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "sourceMap": true, - "declaration": true, - "declarationMap": true, - "composite": true + "target": "ES2016", + "module": "CommonJS", + "outDir": "./dist", + "baseUrl": ".", + "paths": { + "@gemwallet/swapper": ["../../packages/swapper/src/index.ts"], + "@gemwallet/types": ["../../packages/types/src/index.ts"] + }, + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "composite": true }, "include": ["src/**/*", "src/version.json"], "exclude": ["node_modules", "dist"], - "references": [ - { "path": "../../packages/swapper" }, - { "path": "../../packages/types" } - ] - } - - \ No newline at end of file + "references": [{ "path": "../../packages/swapper" }, { "path": "../../packages/types" }] +} diff --git a/justfile b/justfile index 99fcfe0..b041bf2 100644 --- a/justfile +++ b/justfile @@ -16,6 +16,20 @@ dev: test: pnpm run test +lint: + pnpm run lint + +format: + pnpm run format + +format-check: + pnpm run format:check + +dead-code: + pnpm run dead-code + +check: lint format-check build test + bench PROVIDER="orca" ITERATIONS="2": pnpm exec ts-node -P packages/swapper/tsconfig.json scripts/provider-bench.ts --provider {{PROVIDER}} --iterations {{ITERATIONS}} diff --git a/knip.json b/knip.json new file mode 100644 index 0000000..68cd174 --- /dev/null +++ b/knip.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://unpkg.com/knip@5/schema.json", + "workspaces": { + "packages/types": { + "ignore": ["src/primitives/**"] + }, + "packages/swapper": { + "entry": ["src/index.ts!", "src/testkit/mock.ts!"], + "ignore": ["src/testkit/**"] + }, + "apps/api": {} + }, + "ignoreWorkspaces": ["scripts"] +} diff --git a/oxlintrc.json b/oxlintrc.json new file mode 100644 index 0000000..baa9887 --- /dev/null +++ b/oxlintrc.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc/json-schema/npm/oxlint/configuration_schema.json", + "rules": { + "no-unused-vars": "warn", + "no-console": "warn", + "no-debugger": "error", + "eqeqeq": "error", + "no-var": "error", + "prefer-const": "warn", + "no-constant-binary-expression": "error", + "no-constructor-return": "error", + "no-self-compare": "error", + "no-template-curly-in-string": "warn", + "no-array-constructor": "error", + "no-eval": "error", + "no-new-wrappers": "error", + "no-throw-literal": "error" + }, + "overrides": [ + { + "files": ["*.test.ts", "*.spec.ts", "integration.test.ts"], + "rules": { + "no-console": "off" + } + }, + { + "files": ["apps/api/src/**/*.ts"], + "rules": { + "no-console": "off" + } + } + ], + "ignorePatterns": [ + "dist", + "node_modules", + "*.js", + "*.d.ts", + "coverage", + "packages/types/src/primitives" + ] +} diff --git a/package.json b/package.json index 3089e91..1760922 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,11 @@ "start:api": "pnpm --filter @gemwallet/api start", "dev:api": "pnpm --filter @gemwallet/api dev", "clean": "find . -name node_modules -type d -prune -exec rm -rf '{}' + && find . -name dist -type d -prune -exec rm -rf '{}' + && rm -rf .pnpm-store", - "test": "jest --passWithNoTests" + "test": "jest --passWithNoTests", + "lint": "oxlint -c oxlintrc.json packages apps", + "format": "oxfmt --write packages apps", + "format:check": "oxfmt --check packages apps", + "dead-code": "knip" }, "keywords": [ "swap", @@ -21,6 +25,9 @@ "devDependencies": { "@types/jest": "30.0.0", "jest": "30.2.0", + "knip": "5.83.1", + "oxfmt": "0.33.0", + "oxlint": "1.48.0", "ts-jest": "29.4.5", "typescript": "5.9.3" }, diff --git a/packages/swapper/jest.config.js b/packages/swapper/jest.config.js index 26efa23..b4be920 100644 --- a/packages/swapper/jest.config.js +++ b/packages/swapper/jest.config.js @@ -1,19 +1,19 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - useESM: true, - tsconfig: 'tsconfig.json' - }, - ], - }, - extensionsToTreatAsEsm: ['.ts'], - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - testPathIgnorePatterns: ['/node_modules/', '/dist/'], + preset: "ts-jest", + testEnvironment: "node", + transform: { + "^.+\\.tsx?$": [ + "ts-jest", + { + useESM: true, + tsconfig: "tsconfig.json", + }, + ], + }, + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testPathIgnorePatterns: ["/node_modules/", "/dist/"], }; diff --git a/packages/swapper/package.json b/packages/swapper/package.json index 0cfe35f..f8b02f0 100644 --- a/packages/swapper/package.json +++ b/packages/swapper/package.json @@ -1,41 +1,41 @@ { - "name": "@gemwallet/swapper", - "version": "1.0.0", - "description": "", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "dev": "tsc -w", - "test": "jest --passWithNoTests" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@cetusprotocol/aggregator-sdk": "1.4.5", - "@coral-xyz/anchor": "0.32.1", - "@gemwallet/types": "workspace:*", - "@mayanfinance/swap-sdk": "12.2.5", - "@mysten/sui": "1.35.0", - "@okx-dex/okx-dex-sdk": "1.0.18", - "@orca-so/whirlpools": "6.0.0", - "@orca-so/whirlpools-client": "6.2.0", - "@orca-so/whirlpools-core": "3.0.0", - "@panoraexchange/swap-sdk": "^1.3.1", - "@solana-program/token-2022": "0.6.1", - "@solana/instructions": "5.0.0", - "@solana/kit": "5.0.0", - "@solana/spl-token": "0.4.14", - "@solana/web3.js": "1.98.4", - "@ston-fi/api": "0.30.0", - "@ston-fi/sdk": "2.7.0", - "@ton/ton": "16.2.2", - "@types/bn.js": "5.2.0", - "bn.js": "5.2.2", - "bs58": "6.0.0", - "js-sha3": "0.9.3" - }, - "files": [ - "dist" - ] -} \ No newline at end of file + "name": "@gemwallet/swapper", + "version": "1.0.0", + "description": "", + "keywords": [], + "license": "ISC", + "author": "", + "files": [ + "dist" + ], + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "dev": "tsc -w", + "test": "jest --passWithNoTests" + }, + "dependencies": { + "@cetusprotocol/aggregator-sdk": "1.4.5", + "@coral-xyz/anchor": "0.32.1", + "@gemwallet/types": "workspace:*", + "@mayanfinance/swap-sdk": "12.2.5", + "@mysten/sui": "1.35.0", + "@okx-dex/okx-dex-sdk": "1.0.18", + "@orca-so/whirlpools": "6.0.0", + "@orca-so/whirlpools-client": "6.2.0", + "@orca-so/whirlpools-core": "3.0.0", + "@panoraexchange/swap-sdk": "^1.3.1", + "@solana-program/token-2022": "0.6.1", + "@solana/instructions": "5.0.0", + "@solana/kit": "5.0.0", + "@solana/spl-token": "0.4.14", + "@solana/web3.js": "1.98.4", + "@ston-fi/api": "0.30.0", + "@ston-fi/sdk": "2.7.0", + "@ton/ton": "16.2.2", + "@types/bn.js": "5.2.0", + "bn.js": "5.2.2", + "bs58": "6.0.0", + "js-sha3": "0.9.3" + } +} diff --git a/packages/swapper/src/bigint_math.ts b/packages/swapper/src/bigint_math.ts index 2f2cc18..a298e89 100644 --- a/packages/swapper/src/bigint_math.ts +++ b/packages/swapper/src/bigint_math.ts @@ -1,56 +1,32 @@ export class BigIntMath { - /** - * Converts a decimal string or number to the specified decimal precision - * @param value The decimal to convert - * @param toDecimals The target number of decimals - * @returns A BigInt with the converted value - */ static parseDecimals(value: string | number, toDecimals: number): bigint { const stringValue = value.toString(); - const decimalParts = stringValue.split('.'); + const decimalParts = stringValue.split("."); - // Handle decimal numbers if (decimalParts[1]) { const decimals = decimalParts[1]; const actualDecimals = decimals.length; const wholeNumberPart = decimalParts[0]; - // Remove decimal point - const integerPart = wholeNumberPart === '0' ? '' : wholeNumberPart; + const integerPart = wholeNumberPart === "0" ? "" : wholeNumberPart; const fullInteger = `${integerPart}${decimals}`; - // Convert to target decimals const decimalDiff = actualDecimals - toDecimals; if (decimalDiff > 0) { - // Need to remove some decimals (round down) return BigInt(fullInteger) / BigInt(10 ** decimalDiff); } else if (decimalDiff < 0) { - // Need to add more decimals return BigInt(fullInteger) * BigInt(10 ** Math.abs(decimalDiff)); } return BigInt(fullInteger); } - // Handle whole numbers return BigInt(decimalParts[0]) * BigInt(10 ** toDecimals); } - /** - * Returns the maximum of two BigInt values - * @param a The first BigInt value - * @param b The second BigInt value - * @returns The maximum of the two values - */ static max(a: bigint, b: bigint): bigint { return a > b ? a : b; } - /** - * Increases a BigInt value by a specified percentage - * @param value The BigInt value to increase - * @param percentage The percentage to increase by (e.g., 20 for 20%) - * @returns The increased BigInt value - */ static increaseByPercent(value: bigint, percentage: number): bigint { if (percentage < 0) { throw new Error("Percentage must be non-negative"); @@ -59,12 +35,6 @@ export class BigIntMath { return (value * multiplier) / BigInt(100); } - /** - * Converts a raw integer value to a decimal string representation - * @param value The raw integer value (as string or bigint) - * @param decimals The number of decimal places - * @returns A decimal string (e.g., "10.5" for value=10500000, decimals=6) - */ static formatDecimals(value: string | bigint, decimals: number): string { const raw = typeof value === "string" ? BigInt(value) : value; if (decimals <= 0) { @@ -82,17 +52,12 @@ export class BigIntMath { return `${whole.toString()}.${fractionStr}`; } - /** - * Converts basis points to a percentage string - * @param bps Basis points (100 bps = 1%) - * @returns Percentage string (e.g., 150 bps -> "1.5") - */ static bpsToPercent(bps: number): string { return (bps / 100).toString(); } static applySlippage(value: string, bps: number): string { - return (BigInt(value) * BigInt(10000 - bps) / BigInt(10000)).toString(); + return ((BigInt(value) * BigInt(10000 - bps)) / BigInt(10000)).toString(); } static parseString(value: string): bigint { diff --git a/packages/swapper/src/cetus/bn_replacer.test.ts b/packages/swapper/src/cetus/bn_replacer.test.ts index 3990abb..23fc4ed 100644 --- a/packages/swapper/src/cetus/bn_replacer.test.ts +++ b/packages/swapper/src/cetus/bn_replacer.test.ts @@ -1,8 +1,9 @@ import BN from "bn.js"; + import { bnReplacer, bnReviver } from "./bn_replacer"; -describe('BN Replacer', () => { - it('Test BN type conversion for amountIn', () => { +describe("BN Replacer", () => { + it("Test BN type conversion for amountIn", () => { const bn = new BN(123456789); const object = { amountIn: bn, @@ -14,8 +15,8 @@ describe('BN Replacer', () => { expect(parsed.amountIn).toBeInstanceOf(BN); }); - it('Test BN type conversion for amountOut', () => { - const bn = new BN('3b9aca00', 16); + it("Test BN type conversion for amountOut", () => { + const bn = new BN("3b9aca00", 16); const object = { amountOut: bn, }; @@ -26,10 +27,8 @@ describe('BN Replacer', () => { expect(parsed.amountOut).toBeInstanceOf(BN); }); - it('Test Map serialization and deserialization', () => { - const map = new Map([ - ['aggregator_v3', '0x07c27e879ba9282506284b0fef26d393978906fc9496550d978c6f493dbfa3e5'] - ]); + it("Test Map serialization and deserialization", () => { + const map = new Map([["aggregator_v3", "0x07c27e879ba9282506284b0fef26d393978906fc9496550d978c6f493dbfa3e5"]]); const object = { packages: map, }; @@ -38,16 +37,18 @@ describe('BN Replacer', () => { expect(parsed.packages).toEqual(map); expect(parsed.packages).toBeInstanceOf(Map); - expect(parsed.packages.get('aggregator_v3')).toBe('0x07c27e879ba9282506284b0fef26d393978906fc9496550d978c6f493dbfa3e5'); + expect(parsed.packages.get("aggregator_v3")).toBe( + "0x07c27e879ba9282506284b0fef26d393978906fc9496550d978c6f493dbfa3e5", + ); }); - it('Test complex object with both BN and Map', () => { - const amountIn = new BN('3b9aca00', 16); - const amountOut = new BN('32987a', 16); + it("Test complex object with both BN and Map", () => { + const amountIn = new BN("3b9aca00", 16); + const amountOut = new BN("32987a", 16); const packages = new Map([ - ['aggregator_v3', '0x07c27e879ba9282506284b0fef26d393978906fc9496550d978c6f493dbfa3e5'] + ["aggregator_v3", "0x07c27e879ba9282506284b0fef26d393978906fc9496550d978c6f493dbfa3e5"], ]); - + const routeData = { amountIn, amountOut, @@ -65,7 +66,7 @@ describe('BN Replacer', () => { expect(parsed.byAmountIn).toBe(true); }); - it('Test empty Map serialization', () => { + it("Test empty Map serialization", () => { const emptyMap = new Map(); const object = { packages: emptyMap, diff --git a/packages/swapper/src/cetus/bn_replacer.ts b/packages/swapper/src/cetus/bn_replacer.ts index ed14bec..93c637c 100644 --- a/packages/swapper/src/cetus/bn_replacer.ts +++ b/packages/swapper/src/cetus/bn_replacer.ts @@ -13,11 +13,11 @@ export function bnReplacer(key: string, val: any) { } export function bnReviver(key: string, val: any) { - if (bnFields.has(key) && typeof val === 'string') { + if (bnFields.has(key) && typeof val === "string") { return new BN(val, 16); } - if (val && typeof val === 'object' && val.__map && Array.isArray(val.__map)) { + if (val && typeof val === "object" && val.__map && Array.isArray(val.__map)) { return new Map(val.__map); } return val; -} \ No newline at end of file +} diff --git a/packages/swapper/src/cetus/index.ts b/packages/swapper/src/cetus/index.ts index 03be03e..1a8f850 100644 --- a/packages/swapper/src/cetus/index.ts +++ b/packages/swapper/src/cetus/index.ts @@ -1 +1 @@ -export * from './provider'; +export * from "./provider"; diff --git a/packages/swapper/src/cetus/provider.ts b/packages/swapper/src/cetus/provider.ts index 4fb284d..2fe1826 100644 --- a/packages/swapper/src/cetus/provider.ts +++ b/packages/swapper/src/cetus/provider.ts @@ -1,13 +1,23 @@ +import { + AggregatorClient, + Env, + RouterDataV3, + BuildFastRouterSwapParamsV3, + CETUS, + DEEPBOOKV2, + DEEPBOOKV3, + BLUEFIN, +} from "@cetusprotocol/aggregator-sdk"; import { QuoteRequest, Quote, SwapQuoteData, AssetId, SwapQuoteDataType } from "@gemwallet/types"; -import { Protocol } from "../protocol"; -import { AggregatorClient, Env, RouterDataV3, BuildFastRouterSwapParamsV3, CETUS, DEEPBOOKV2, DEEPBOOKV3, BLUEFIN } from "@cetusprotocol/aggregator-sdk"; import { SuiClient } from "@mysten/sui/client"; -import { Transaction } from '@mysten/sui/transactions'; +import { Transaction } from "@mysten/sui/transactions"; import { BN } from "bn.js"; + import { SUI_COIN_TYPE } from "../chain/sui/constants"; -import { bnReplacer, bnReviver } from "./bn_replacer"; import { calculateGasBudget, prefillTransaction, getGasPriceAndCoinRefs } from "../chain/sui/tx_builder"; +import { Protocol } from "../protocol"; import { getReferrerAddresses, CETUS_PARTNER_ID } from "../referrer"; +import { bnReplacer, bnReviver } from "./bn_replacer"; export class CetusAggregatorProvider implements Protocol { private client: AggregatorClient; @@ -28,7 +38,7 @@ export class CetusAggregatorProvider implements Protocol { overlayFeeRate, overlayFeeReceiver, signer: address, - partner: CETUS_PARTNER_ID + partner: CETUS_PARTNER_ID, }); } @@ -63,7 +73,7 @@ export class CetusAggregatorProvider implements Protocol { } const rawOutputValue = BigInt(routeData.amountOut.toString(10)); - const referralValue = rawOutputValue * BigInt(request.referral_bps) / BigInt(10000); + const referralValue = (rawOutputValue * BigInt(request.referral_bps)) / BigInt(10000); const minOutputValue = rawOutputValue - referralValue; const quoteResult: Quote = { @@ -78,7 +88,6 @@ export class CetusAggregatorProvider implements Protocol { return quoteResult; } catch (err: unknown) { - console.error("CetusProvider: Error in get_quote", err); if (err instanceof Error) { throw new Error(`Get Quote failed: ${err.message}`); } @@ -94,7 +103,6 @@ export class CetusAggregatorProvider implements Protocol { try { route_data = JSON.parse(routeDataString, bnReviver) as RouterDataV3; } catch (parseError) { - console.error("Route data that failed to parse:", routeDataString); throw new Error(`Failed to parse route data: ${parseError}`); } @@ -111,14 +119,15 @@ export class CetusAggregatorProvider implements Protocol { }; // create a new client with user's address as signer, overlay fee rate and overlay fee receiver - const client = this.createClient(quote.quote.from_address, quote.quote.referral_bps / 10000, this.overlayFeeReceiver); + const client = this.createClient( + quote.quote.from_address, + quote.quote.referral_bps / 10000, + this.overlayFeeReceiver, + ); const gasPriceAndCoinRefsReq = getGasPriceAndCoinRefs(this.suiClient, quote.quote.from_address); const fastRouterSwapReq = client.fastRouterSwap(swapParams); - const [{ gasPrice, coinRefs }] = await Promise.all([ - gasPriceAndCoinRefsReq, - fastRouterSwapReq - ]); + const [{ gasPrice, coinRefs }] = await Promise.all([gasPriceAndCoinRefsReq, fastRouterSwapReq]); // inspect transaction const result = await client.devInspectTransactionBlock(txb); @@ -140,12 +149,11 @@ export class CetusAggregatorProvider implements Protocol { value: "0", data: Buffer.from(serializedTx).toString("base64"), dataType: SwapQuoteDataType.Contract, - gasLimit: gasBudget.toString(10) + gasLimit: gasBudget.toString(10), }; return quoteData; } catch (error: unknown) { - console.error("Error building transaction data with Cetus:", error); if (error instanceof Error) { throw new Error(`Get Quote Data failed: ${error.message}`); } diff --git a/packages/swapper/src/chain/solana/account.test.ts b/packages/swapper/src/chain/solana/account.test.ts index 4174545..670deef 100644 --- a/packages/swapper/src/chain/solana/account.test.ts +++ b/packages/swapper/src/chain/solana/account.test.ts @@ -1,4 +1,5 @@ import { PublicKey } from "@solana/web3.js"; + import { resolveTokenProgram } from "./account"; jest.mock("@solana-program/token-2022", () => ({ @@ -27,9 +28,7 @@ describe("resolveTokenProgram", () => { it("throws when mint account response is empty", async () => { fetchAllMint.mockResolvedValue([]); - await expect(resolveTokenProgram(rpc, mint)).rejects.toThrow( - "Failed to fetch mint account data", - ); + await expect(resolveTokenProgram(rpc, mint)).rejects.toThrow("Failed to fetch mint account data"); }); it("returns the mint program when account exists", async () => { diff --git a/packages/swapper/src/chain/solana/account.ts b/packages/swapper/src/chain/solana/account.ts index a618661..38128ed 100644 --- a/packages/swapper/src/chain/solana/account.ts +++ b/packages/swapper/src/chain/solana/account.ts @@ -1,8 +1,9 @@ import { AssetId } from "@gemwallet/types"; -import { WSOL_MINT } from "./constants"; -import { PublicKey } from "@solana/web3.js"; -import { address as toAddress, type Address, type createSolanaRpc } from "@solana/kit"; import { fetchAllMint } from "@solana-program/token-2022"; +import { address as toAddress, type Address, type createSolanaRpc } from "@solana/kit"; +import { PublicKey } from "@solana/web3.js"; + +import { WSOL_MINT } from "./constants"; type SolanaRpc = ReturnType; @@ -25,10 +26,7 @@ export function getMintAddress(asset: AssetId): Address { return toAddress(tokenId); } -export async function resolveTokenProgram( - rpc: SolanaRpc, - mint: PublicKey, -): Promise { +export async function resolveTokenProgram(rpc: SolanaRpc, mint: PublicKey): Promise { const mintAccount = await fetchAllMint(rpc, [toAddress(mint.toBase58())]); if (mintAccount.length === 0) { throw new Error("Failed to fetch mint account data"); diff --git a/packages/swapper/src/chain/solana/constants.ts b/packages/swapper/src/chain/solana/constants.ts index a414c2f..bfd703c 100644 --- a/packages/swapper/src/chain/solana/constants.ts +++ b/packages/swapper/src/chain/solana/constants.ts @@ -1,4 +1,4 @@ import { NATIVE_MINT } from "@solana/spl-token"; export const WSOL_MINT = NATIVE_MINT; -export const DEFAULT_COMMITMENT: "confirmed" = "confirmed"; +export const DEFAULT_COMMITMENT = "confirmed" as const; diff --git a/packages/swapper/src/chain/solana/tx_builder.ts b/packages/swapper/src/chain/solana/tx_builder.ts index f85c1ce..1548b2e 100644 --- a/packages/swapper/src/chain/solana/tx_builder.ts +++ b/packages/swapper/src/chain/solana/tx_builder.ts @@ -16,7 +16,10 @@ export async function getRecentBlockhash(connection: Connection, commitment?: "c return await connection.getLatestBlockhash(commitment || "confirmed"); } -export async function getRecentPriorityFee(connection: Connection, lockedWritableAccounts?: PublicKey[]): Promise { +export async function getRecentPriorityFee( + connection: Connection, + lockedWritableAccounts?: PublicKey[], +): Promise { try { const config = lockedWritableAccounts ? { lockedWritableAccounts } : undefined; const recentFees = await connection.getRecentPrioritizationFees(config); @@ -26,8 +29,8 @@ export async function getRecentPriorityFee(connection: Connection, lockedWritabl } const fees = recentFees - .map(fee => fee.prioritizationFee) - .filter(fee => fee > 0) + .map((fee) => fee.prioritizationFee) + .filter((fee) => fee > 0) .sort((a, b) => a - b); if (fees.length === 0) { @@ -37,14 +40,11 @@ export async function getRecentPriorityFee(connection: Connection, lockedWritabl const percentileIndex = Math.floor(fees.length * (PRIORITY_FEE_PERCENTILE / 100)); const percentileFee = fees[Math.min(percentileIndex, fees.length - 1)]; - const recommendedFee = Math.max( - Math.ceil(percentileFee * 1.2), - 1000 - ); + const recommendedFee = Math.max(Math.ceil(percentileFee * 1.2), 1000); return recommendedFee; } catch (error) { - console.warn("Failed to fetch recent prioritization fees, using default:", error); + void error; return DEFAULT_COMPUTE_UNIT_PRICE; } } diff --git a/packages/swapper/src/chain/sui/tx_builder.ts b/packages/swapper/src/chain/sui/tx_builder.ts index 52dbc59..de97663 100644 --- a/packages/swapper/src/chain/sui/tx_builder.ts +++ b/packages/swapper/src/chain/sui/tx_builder.ts @@ -1,5 +1,6 @@ -import { DevInspectResults, SuiClient, TransactionEffects } from "@mysten/sui/client"; +import { SuiClient, TransactionEffects } from "@mysten/sui/client"; import { Transaction } from "@mysten/sui/transactions"; + import { BigIntMath } from "../../bigint_math"; import { SUI_COIN_TYPE } from "./constants"; @@ -10,15 +11,14 @@ export interface SuiTransactionPrerequisites { export async function getGasPriceAndCoinRefs( suiClient: SuiClient, - ownerAddress: string + ownerAddress: string, ): Promise { - const [gasPrice, coins] = await Promise.all([ suiClient.getReferenceGasPrice(), - suiClient.getCoins({ owner: ownerAddress, coinType: SUI_COIN_TYPE, limit: 100 }) + suiClient.getCoins({ owner: ownerAddress, coinType: SUI_COIN_TYPE, limit: 100 }), ]); - const coinRefs = coins.data.map(coin => ({ + const coinRefs = coins.data.map((coin) => ({ objectId: coin.coinObjectId, version: coin.version, digest: coin.digest, @@ -27,10 +27,7 @@ export async function getGasPriceAndCoinRefs( return { gasPrice: BigInt(gasPrice), coinRefs }; } -export function calculateGasBudget( - transactionEffects: TransactionEffects, - increasePercentage: number = 20 -): bigint { +export function calculateGasBudget(transactionEffects: TransactionEffects, increasePercentage: number = 20): bigint { const gasUsed = transactionEffects.gasUsed; const computationBudget = BigInt(gasUsed.computationCost); const storageBudget = BigInt(gasUsed.storageCost) - BigInt(gasUsed.storageRebate); @@ -45,9 +42,8 @@ export function prefillTransaction( senderAddress: string, gasBudget: bigint, gasPrice: bigint, - coinRefs: { objectId: string; version: string; digest: string }[] + coinRefs: { objectId: string; version: string; digest: string }[], ) { - transaction.setSender(senderAddress); transaction.setGasPrice(gasPrice); transaction.setGasBudget(gasBudget); diff --git a/packages/swapper/src/error.ts b/packages/swapper/src/error.ts index 0a6f94e..84e6eb3 100644 --- a/packages/swapper/src/error.ts +++ b/packages/swapper/src/error.ts @@ -4,9 +4,7 @@ export class SwapperException extends Error { readonly swapperError: SwapperError; constructor(swapperError: SwapperError) { - const message = "message" in swapperError && swapperError.message - ? swapperError.message - : swapperError.type; + const message = "message" in swapperError && swapperError.message ? swapperError.message : swapperError.type; super(message); this.name = "SwapperException"; this.swapperError = swapperError; diff --git a/packages/swapper/src/index.ts b/packages/swapper/src/index.ts index 6e3007e..e617e78 100644 --- a/packages/swapper/src/index.ts +++ b/packages/swapper/src/index.ts @@ -1,10 +1,10 @@ -export * from './protocol'; -export * from './stonfi'; -export * from './mayan'; -export * from './cetus'; -export * from './relay'; -export * from './referrer'; -export * from './orca'; -export * from './panora'; -export * from './okx'; -export * from './error'; +export * from "./protocol"; +export * from "./stonfi"; +export * from "./mayan"; +export * from "./cetus"; +export * from "./relay"; +export * from "./referrer"; +export * from "./orca"; +export * from "./panora"; +export * from "./okx"; +export * from "./error"; diff --git a/packages/swapper/src/mayan/error.ts b/packages/swapper/src/mayan/error.ts index 0ae405a..f938945 100644 --- a/packages/swapper/src/mayan/error.ts +++ b/packages/swapper/src/mayan/error.ts @@ -1,5 +1,5 @@ -import { SwapperException } from "../error"; import { BigIntMath } from "../bigint_math"; +import { SwapperException } from "../error"; export function toMayanError(error: unknown, decimals: number): Error { if (SwapperException.isSwapperException(error)) { diff --git a/packages/swapper/src/mayan/evm.ts b/packages/swapper/src/mayan/evm.ts index 1dcb942..8dfa5ea 100644 --- a/packages/swapper/src/mayan/evm.ts +++ b/packages/swapper/src/mayan/evm.ts @@ -1,5 +1,6 @@ import { QuoteRequest, SwapQuoteData, SwapQuoteDataType } from "@gemwallet/types"; import { Quote as MayanQuote, ReferrerAddresses, getSwapFromEvmTxPayload } from "@mayanfinance/swap-sdk"; + import { getReferrerAddresses } from "../referrer"; export const EMPTY_ADDRESS = "0x0000000000000000000000000000000000000000"; diff --git a/packages/swapper/src/mayan/index.test.ts b/packages/swapper/src/mayan/index.test.ts index aa14f2f..f5660d2 100644 --- a/packages/swapper/src/mayan/index.test.ts +++ b/packages/swapper/src/mayan/index.test.ts @@ -1,7 +1,7 @@ -import { toMayanError } from "./error"; import { SwapperException } from "../error"; +import { toMayanError } from "./error"; -describe('toMayanError', () => { +describe("toMayanError", () => { it('converts "Amount too small (min ~0.01962 SOL)" to input_amount_error', () => { const result = toMayanError({ message: "Amount too small (min ~0.01962 SOL)" }, 9); @@ -20,7 +20,7 @@ describe('toMayanError', () => { }); }); - it('returns generic Error for non-amount errors', () => { + it("returns generic Error for non-amount errors", () => { const result = toMayanError({ message: "Network error" }, 9); expect(result).not.toBeInstanceOf(SwapperException); diff --git a/packages/swapper/src/mayan/provider.ts b/packages/swapper/src/mayan/provider.ts index acef6eb..04b0dcb 100644 --- a/packages/swapper/src/mayan/provider.ts +++ b/packages/swapper/src/mayan/provider.ts @@ -1,13 +1,21 @@ -import { fetchQuote, ChainName, QuoteParams, QuoteOptions, Quote as MayanQuote, ReferrerAddresses } from "@mayanfinance/swap-sdk"; import { QuoteRequest, Quote, SwapQuoteData, AssetId, Chain } from "@gemwallet/types"; +import { + fetchQuote, + ChainName, + QuoteParams, + QuoteOptions, + Quote as MayanQuote, + ReferrerAddresses, +} from "@mayanfinance/swap-sdk"; + +import { BigIntMath } from "../bigint_math"; +import { SUI_COIN_TYPE } from "../chain/sui/constants"; import { Protocol } from "../protocol"; +import { getReferrerAddresses } from "../referrer"; +import { toMayanError } from "./error"; import { buildEvmQuoteData, EMPTY_ADDRESS } from "./evm"; import { buildSolanaQuoteData } from "./solana"; import { buildSuiQuoteData } from "./sui"; -import { BigIntMath } from "../bigint_math"; -import { getReferrerAddresses } from "../referrer"; -import { SUI_COIN_TYPE } from "../chain/sui/constants"; -import { toMayanError } from "./error"; export class MayanProvider implements Protocol { private solanaRpc: string; @@ -56,18 +64,18 @@ export class MayanProvider implements Protocol { slippageBps: "auto", referrer: referrerAddresses.solana!, referrerBps, - } + }; // explicitly set which types of quotes we want to fetch const options: QuoteOptions = { - "wormhole": true, - "swift": true, - "gasless": false, - "mctp": true, - "shuttle": false, - "fastMctp": true, - "onlyDirect": false, - } + wormhole: true, + swift: true, + gasless: false, + mctp: true, + shuttle: false, + fastMctp: true, + onlyDirect: false, + }; let quotes: MayanQuote[]; try { @@ -90,7 +98,7 @@ export class MayanProvider implements Protocol { output_value: output_value.toString(), output_min_value: output_min_value.toString(), eta_in_seconds: quote.etaSeconds, - route_data: quote + route_data: quote, }; } diff --git a/packages/swapper/src/mayan/solana.ts b/packages/swapper/src/mayan/solana.ts index 49c5101..84cf9d8 100644 --- a/packages/swapper/src/mayan/solana.ts +++ b/packages/swapper/src/mayan/solana.ts @@ -1,15 +1,16 @@ import { QuoteRequest, SwapQuoteData, SwapQuoteDataType } from "@gemwallet/types"; import { Quote as MayanQuote, ReferrerAddresses, createSwapFromSolanaInstructions } from "@mayanfinance/swap-sdk"; import { Connection, MessageV0, PublicKey, VersionedTransaction } from "@solana/web3.js"; -import { getReferrerAddresses } from "../referrer"; -import { - getRecentBlockhash, - serializeTransaction, - findComputeUnitLimit, -} from "../chain/solana/tx_builder"; + import { DEFAULT_COMMITMENT } from "../chain/solana/constants"; +import { getRecentBlockhash, serializeTransaction, findComputeUnitLimit } from "../chain/solana/tx_builder"; +import { getReferrerAddresses } from "../referrer"; -export async function buildSolanaQuoteData(request: QuoteRequest, routeData: MayanQuote, rpcEndpoint: string): Promise { +export async function buildSolanaQuoteData( + request: QuoteRequest, + routeData: MayanQuote, + rpcEndpoint: string, +): Promise { const connection = new Connection(rpcEndpoint); const referrerAddresses = getReferrerAddresses() as ReferrerAddresses; const { serializedTrx, gasLimit } = await prepareSolanaSwapTransaction( @@ -17,7 +18,7 @@ export async function buildSolanaQuoteData(request: QuoteRequest, routeData: May request.from_address, request.to_address, referrerAddresses, - connection + connection, ); return { @@ -36,21 +37,26 @@ async function prepareSolanaSwapTransaction( referrerAddresses: ReferrerAddresses, connection: Connection, ): Promise<{ - serializedTrx: string, - gasLimit: string | undefined, + serializedTrx: string; + gasLimit: string | undefined; additionalInfo: { - blockhash: string, - lastValidBlockHeight: number, - isVersionedTransaction: boolean, - feePayer: string, - } + blockhash: string; + lastValidBlockHeight: number; + isVersionedTransaction: boolean; + feePayer: string; + }; }> { const [swapData, { blockhash, lastValidBlockHeight }] = await Promise.all([ createSwapFromSolanaInstructions( - quote, swapperWalletAddress, destinationAddress, - referrerAddresses, connection, { - separateSwapTx: false, - }), + quote, + swapperWalletAddress, + destinationAddress, + referrerAddresses, + connection, + { + separateSwapTx: false, + }, + ), getRecentBlockhash(connection, DEFAULT_COMMITMENT), ]); @@ -82,6 +88,6 @@ async function prepareSolanaSwapTransaction( lastValidBlockHeight, isVersionedTransaction: true, feePayer: swapper.toBase58(), - } + }, }; } diff --git a/packages/swapper/src/mayan/sui.ts b/packages/swapper/src/mayan/sui.ts index 2feac1d..ad79e02 100644 --- a/packages/swapper/src/mayan/sui.ts +++ b/packages/swapper/src/mayan/sui.ts @@ -1,16 +1,19 @@ import { SwapQuoteData, QuoteRequest, SwapQuoteDataType } from "@gemwallet/types"; import { Quote as MayanQuote, ReferrerAddresses, createSwapFromSuiMoveCalls } from "@mayanfinance/swap-sdk"; import { SuiClient } from "@mysten/sui/client"; -import { getReferrerAddresses } from "../referrer"; -import { SUI_COIN_TYPE } from "../chain/sui/constants"; + import { calculateGasBudget, prefillTransaction, getGasPriceAndCoinRefs } from "../chain/sui/tx_builder"; +import { getReferrerAddresses } from "../referrer"; -export async function buildSuiQuoteData(request: QuoteRequest, routeData: MayanQuote, suiRpc: string): Promise { +export async function buildSuiQuoteData( + request: QuoteRequest, + routeData: MayanQuote, + suiRpc: string, +): Promise { const referrerAddresses = getReferrerAddresses() as ReferrerAddresses; const suiClient = new SuiClient({ url: suiRpc }); try { - const [suiTx, { gasPrice, coinRefs }] = await Promise.all([ createSwapFromSuiMoveCalls( routeData, @@ -18,9 +21,9 @@ export async function buildSuiQuoteData(request: QuoteRequest, routeData: MayanQ request.to_address, referrerAddresses, null, - suiClient + suiClient, ), - getGasPriceAndCoinRefs(suiClient, request.from_address) + getGasPriceAndCoinRefs(suiClient, request.from_address), ]); const inspectResult = await suiClient.devInspectTransactionBlock({ @@ -31,7 +34,7 @@ export async function buildSuiQuoteData(request: QuoteRequest, routeData: MayanQ if (inspectResult.error) { throw new Error(`Failed to estimate gas budget: ${inspectResult.error}`); } - if (inspectResult.effects.status.status !== 'success') { + if (inspectResult.effects.status.status !== "success") { throw new Error(`Transaction simulation failed: ${inspectResult.effects.status.error}`); } @@ -44,7 +47,7 @@ export async function buildSuiQuoteData(request: QuoteRequest, routeData: MayanQ value: "0", data: Buffer.from(serializedTx).toString("base64"), dataType: SwapQuoteDataType.Contract, - gasLimit: gasBudget.toString(10) + gasLimit: gasBudget.toString(10), }; } catch (error) { throw new Error(`Failed to build Sui transaction: ${error}`); diff --git a/packages/swapper/src/okx/README.md b/packages/swapper/src/okx/README.md index 8bf039d..028599e 100644 --- a/packages/swapper/src/okx/README.md +++ b/packages/swapper/src/okx/README.md @@ -5,24 +5,24 @@ using the official `@okx-dex/okx-dex-sdk`. ## Scope -- `get_quote`: calls OKX DEX quote + swap data for auto-slippage -- `get_quote_data`: calls OKX DEX swap data for transaction building +- `get_quote`: calls OKX DEX quote with auto-slippage +- `get_quote_data`: calls OKX DEX swap data and estimate compute unit for transaction building - Supports: - - auto slippage (`autoSlippage: true`) - - max auto slippage cap (`slippage_bps * 2` -> `maxAutoSlippagePercent`) - - slippage from request (`slippage_bps` -> `slippagePercent`, capped at 1%; defaults to 1% when not set) - - referral fee (`referral_bps` -> `feePercent`) - - Solana referrer wallet (`fromTokenReferrerWalletAddress`) - - DEX filtering via `dexIds` (top Solana DEXes by TVL) + - auto slippage (`autoSlippage: true`) + - max auto slippage cap (`slippage_bps * 2` -> `maxAutoSlippagePercent`) + - slippage from request (`slippage_bps` -> `slippagePercent`, capped at 1%; defaults to 1% when not set) + - referral fee (`referral_bps` -> `feePercent`) + - Solana referrer wallet (`fromTokenReferrerWalletAddress`) + - DEX filtering via `dexIds` (top Solana DEXes by TVL) ## Auth and Security - Authentication is handled by the SDK's built-in HMAC-SHA256 signing. - Required env vars (server-side only): - - `OKX_API_KEY` - - `OKX_SECRET_KEY` - - `OKX_API_PASSPHRASE` - - `OKX_PROJECT_ID` + - `OKX_API_KEY` + - `OKX_SECRET_KEY` + - `OKX_API_PASSPHRASE` + - `OKX_PROJECT_ID` ## DEX Filtering @@ -37,9 +37,7 @@ The full liquidity list can be fetched via `OKXDexClient.dex.getLiquidity("501") 2. Validate Solana assets and map native SOL to `11111111111111111111111111111111` 3. Request quote via `OKXDexClient.dex.getQuote()` (filtered by `dexIds`) 4. Request swap data via `OKXDexClient.dex.getSwapData()` with `autoSlippage: true` -5. Store OKX route in `quote.route_data` with: - - `suggestedSlippagePercent` - - `suggestedSlippageBps` +5. Store OKX route in `quote.route_data` 6. `OkxProvider.get_quote_data(...)` 7. Build swap request with auto slippage and optional referral data 8. Decode OKX base58 tx payload and return base64 `SwapQuoteData` diff --git a/packages/swapper/src/okx/constants.ts b/packages/swapper/src/okx/constants.ts index d045d0f..5da9aca 100644 --- a/packages/swapper/src/okx/constants.ts +++ b/packages/swapper/src/okx/constants.ts @@ -1,18 +1,18 @@ const SOLANA_DEX_IDS = [ - "277", // Raydium - "278", // Raydium CL - "279", // Raydium Stable - "343", // Raydium CPMM - "72", // Orca - "103", // Orca Whirlpools - "284", // Meteora - "338", // Meteora DLMM - "372", // Sanctum - "403", // Sanctum Infinity - "444", // PumpSwap - "483", // PancakeSwap V3 - "357", // Phoenix - "345", // OpenBook V2 + "277", // Raydium + "278", // Raydium CL + "279", // Raydium Stable + "343", // Raydium CPMM + "72", // Orca + "103", // Orca Whirlpools + "284", // Meteora + "338", // Meteora DLMM + "372", // Sanctum + "403", // Sanctum Infinity + "444", // PumpSwap + "483", // PancakeSwap V3 + "357", // Phoenix + "345", // OpenBook V2 ]; export const SOLANA_CHAIN_INDEX = "501"; diff --git a/packages/swapper/src/okx/integration.test.ts b/packages/swapper/src/okx/integration.test.ts index 93350e1..ae4748e 100644 --- a/packages/swapper/src/okx/integration.test.ts +++ b/packages/swapper/src/okx/integration.test.ts @@ -1,12 +1,12 @@ import { QuoteRequest } from "@gemwallet/types"; -import { OkxProvider } from "./provider"; import { createSolanaUsdcQuoteRequest } from "../testkit/mock"; +import { OkxProvider } from "./provider"; const OKX_ENV_KEYS = ["OKX_API_KEY", "OKX_SECRET_KEY", "OKX_API_PASSPHRASE", "OKX_PROJECT_ID"]; function hasAuthEnv(): boolean { - return OKX_ENV_KEYS.every((key) => Boolean(process.env[key])); + return OKX_ENV_KEYS.every((key) => Boolean(process.env[key])); } const hasAuth = hasAuthEnv(); @@ -16,24 +16,24 @@ const itIntegration = runIntegration ? it : it.skip; const REQUEST_TEMPLATE: QuoteRequest = createSolanaUsdcQuoteRequest(); describe("OKX live integration", () => { - jest.setTimeout(60_000); + jest.setTimeout(60_000); - itIntegration("fetches a live quote and builds quote data", async () => { - const provider = new OkxProvider(); - const quote = await provider.get_quote(REQUEST_TEMPLATE); + itIntegration("fetches a live quote and builds quote data", async () => { + const provider = new OkxProvider(); + const quote = await provider.get_quote(REQUEST_TEMPLATE); - expect(BigInt(quote.output_value) > BigInt(0)).toBe(true); - expect(quote.route_data).toBeDefined(); + expect(BigInt(quote.output_value) > BigInt(0)).toBe(true); + expect(quote.route_data).toBeDefined(); - const quoteData = await provider.get_quote_data(quote); + const quoteData = await provider.get_quote_data(quote); - expect(quoteData.dataType).toBe("contract"); - expect(typeof quoteData.data).toBe("string"); - expect(quoteData.data.length).toBeGreaterThan(0); - expect(typeof quoteData.to).toBe("string"); - expect(quoteData.to.length).toBeGreaterThan(0); + expect(quoteData.dataType).toBe("contract"); + expect(typeof quoteData.data).toBe("string"); + expect(quoteData.data.length).toBeGreaterThan(0); + expect(typeof quoteData.to).toBe("string"); + expect(quoteData.to.length).toBeGreaterThan(0); - const serialized = Buffer.from(quoteData.data, "base64"); - expect(serialized.length).toBeGreaterThan(0); - }); + const serialized = Buffer.from(quoteData.data, "base64"); + expect(serialized.length).toBeGreaterThan(0); + }); }); diff --git a/packages/swapper/src/okx/provider.test.ts b/packages/swapper/src/okx/provider.test.ts index a27d38b..60ba32b 100644 --- a/packages/swapper/src/okx/provider.test.ts +++ b/packages/swapper/src/okx/provider.test.ts @@ -1,134 +1,134 @@ -import type { OKXDexClient } from "@okx-dex/okx-dex-sdk"; import { Quote } from "@gemwallet/types"; +import type { OKXDexClient } from "@okx-dex/okx-dex-sdk"; -import { OkxProvider } from "./provider"; import { createSolanaUsdcQuoteRequest } from "../testkit/mock"; +import { OkxProvider } from "./provider"; const SOL_MINT = "11111111111111111111111111111111"; const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; function createRequest(slippageBps = 100) { - return createSolanaUsdcQuoteRequest({ slippage_bps: slippageBps }); + return createSolanaUsdcQuoteRequest({ slippage_bps: slippageBps }); } function createProvider() { - const getQuote = jest.fn(); - const getSwapData = jest.fn(); - const getGasLimit = jest.fn().mockResolvedValue({ - code: "0", - msg: "", - data: [{ gasLimit: "500000" }], - }); - const client = { dex: { getQuote, getSwapData, getGasLimit } } as unknown as OKXDexClient; - const provider = new OkxProvider(client); - return { provider, getQuote, getSwapData, getGasLimit }; + const getQuote = jest.fn(); + const getSwapData = jest.fn(); + const getGasLimit = jest.fn().mockResolvedValue({ + code: "0", + msg: "", + data: [{ gasLimit: "500000" }], + }); + const client = { dex: { getQuote, getSwapData, getGasLimit } } as unknown as OKXDexClient; + const provider = new OkxProvider(client); + return { provider, getQuote, getSwapData, getGasLimit }; } const mockRoute = { - fromTokenAmount: "1000000", - toTokenAmount: "120000000", - fromToken: { tokenContractAddress: SOL_MINT }, - toToken: { tokenContractAddress: USDC_MINT }, + fromTokenAmount: "1000000", + toTokenAmount: "120000000", + fromToken: { tokenContractAddress: SOL_MINT }, + toToken: { tokenContractAddress: USDC_MINT }, }; function mockSwapResponse(overrides?: Record) { - return { - code: "0", - msg: "", - data: [ - { - routerResult: mockRoute, - tx: { - from: "SenderAddress", - to: "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB", - data: SOL_MINT, - slippagePercent: "0.42", - minReceiveAmount: "119500000", - ...overrides, - }, - }, - ], - }; + return { + code: "0", + msg: "", + data: [ + { + routerResult: mockRoute, + tx: { + from: "SenderAddress", + to: "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB", + data: SOL_MINT, + slippagePercent: "0.42", + minReceiveAmount: "119500000", + ...overrides, + }, + }, + ], + }; } function mockQuote(request = createRequest()): Quote { - return { - quote: request, - output_value: "120000000", - output_min_value: "120000000", - eta_in_seconds: 0, - route_data: mockRoute, - }; + return { + quote: request, + output_value: "120000000", + output_min_value: "120000000", + eta_in_seconds: 0, + route_data: mockRoute, + }; } describe("OkxProvider", () => { - describe("get_quote", () => { - it("returns quote from getQuote", async () => { - const { provider, getQuote, getSwapData } = createProvider(); - getQuote.mockResolvedValue({ code: "0", msg: "", data: [mockRoute] }); + describe("get_quote", () => { + it("returns quote from getQuote", async () => { + const { provider, getQuote, getSwapData } = createProvider(); + getQuote.mockResolvedValue({ code: "0", msg: "", data: [mockRoute] }); - const quote = await provider.get_quote(createRequest()); + const quote = await provider.get_quote(createRequest()); - expect(quote.output_value).toBe("120000000"); - expect(quote.output_min_value).toBe("118800000"); - expect(getSwapData).not.toHaveBeenCalled(); - }); + expect(quote.output_value).toBe("120000000"); + expect(quote.output_min_value).toBe("118800000"); + expect(getSwapData).not.toHaveBeenCalled(); + }); - it("throws when no quote is available", async () => { - const { provider, getQuote } = createProvider(); - getQuote.mockResolvedValue({ code: "0", msg: "", data: [] }); + it("throws when no quote is available", async () => { + const { provider, getQuote } = createProvider(); + getQuote.mockResolvedValue({ code: "0", msg: "", data: [] }); - await expect(provider.get_quote(createRequest())).rejects.toThrow(); + await expect(provider.get_quote(createRequest())).rejects.toThrow(); + }); }); - }); - describe("get_quote_data", () => { - it("calls getSwapData with auto slippage params", async () => { - const { provider, getSwapData } = createProvider(); - getSwapData.mockResolvedValue(mockSwapResponse()); + describe("get_quote_data", () => { + it("calls getSwapData with auto slippage params", async () => { + const { provider, getSwapData } = createProvider(); + getSwapData.mockResolvedValue(mockSwapResponse()); - await provider.get_quote_data(mockQuote()); + await provider.get_quote_data(mockQuote()); - const swapParams = getSwapData.mock.calls[0][0] as Record; - expect(swapParams.autoSlippage).toBe(true); - expect(swapParams.maxAutoSlippagePercent).toBe("2"); - expect(swapParams.slippagePercent).toBe("1"); - }); + const swapParams = getSwapData.mock.calls[0][0] as Record; + expect(swapParams.autoSlippage).toBe(true); + expect(swapParams.maxAutoSlippagePercent).toBe("2"); + expect(swapParams.slippagePercent).toBe("1"); + }); - it("estimates compute unit limit via getGasLimit", async () => { - const { provider, getSwapData, getGasLimit } = createProvider(); - getSwapData.mockResolvedValue(mockSwapResponse()); + it("estimates compute unit limit via getGasLimit", async () => { + const { provider, getSwapData, getGasLimit } = createProvider(); + getSwapData.mockResolvedValue(mockSwapResponse()); - const result = await provider.get_quote_data(mockQuote()); + const result = await provider.get_quote_data(mockQuote()); - expect(getGasLimit).toHaveBeenCalledWith({ - chainIndex: "501", - fromAddress: "SenderAddress", - toAddress: "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB", - extJson: { inputData: SOL_MINT }, - }); - expect(result.gasLimit).toBe("550000"); - }); + expect(getGasLimit).toHaveBeenCalledWith({ + chainIndex: "501", + fromAddress: "SenderAddress", + toAddress: "JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB", + extJson: { inputData: SOL_MINT }, + }); + expect(result.gasLimit).toBe("550000"); + }); - it("falls back to 1% slippage when slippage_bps is 0", async () => { - const { provider, getSwapData } = createProvider(); - getSwapData.mockResolvedValue(mockSwapResponse()); + it("falls back to 1% slippage when slippage_bps is 0", async () => { + const { provider, getSwapData } = createProvider(); + getSwapData.mockResolvedValue(mockSwapResponse()); - await provider.get_quote_data(mockQuote(createRequest(0))); + await provider.get_quote_data(mockQuote(createRequest(0))); - const swapParams = getSwapData.mock.calls[0][0] as Record; - expect(swapParams.slippagePercent).toBe("1"); - expect(swapParams.maxAutoSlippagePercent).toBeUndefined(); - }); + const swapParams = getSwapData.mock.calls[0][0] as Record; + expect(swapParams.slippagePercent).toBe("1"); + expect(swapParams.maxAutoSlippagePercent).toBeUndefined(); + }); - it("handles getGasLimit failure gracefully", async () => { - const { provider, getSwapData, getGasLimit } = createProvider(); - getGasLimit.mockRejectedValue(new Error("API error")); - getSwapData.mockResolvedValue(mockSwapResponse()); + it("handles getGasLimit failure gracefully", async () => { + const { provider, getSwapData, getGasLimit } = createProvider(); + getGasLimit.mockRejectedValue(new Error("API error")); + getSwapData.mockResolvedValue(mockSwapResponse()); - const result = await provider.get_quote_data(mockQuote()); + const result = await provider.get_quote_data(mockQuote()); - expect(result.gasLimit).toBeUndefined(); + expect(result.gasLimit).toBeUndefined(); + }); }); - }); }); diff --git a/packages/swapper/src/okx/provider.ts b/packages/swapper/src/okx/provider.ts index 02b545f..7f5068f 100644 --- a/packages/swapper/src/okx/provider.ts +++ b/packages/swapper/src/okx/provider.ts @@ -1,224 +1,215 @@ -import bs58 from "bs58"; +import { AssetId, Chain, Quote, QuoteRequest, SwapQuoteData, SwapQuoteDataType } from "@gemwallet/types"; import { OKXDexClient } from "@okx-dex/okx-dex-sdk"; import type { QuoteData, SwapParams, TransactionData } from "@okx-dex/okx-dex-sdk"; +import bs58 from "bs58"; -import { - AssetId, - Chain, - Quote, - QuoteRequest, - SwapQuoteData, - SwapQuoteDataType, -} from "@gemwallet/types"; -import { Protocol } from "../protocol"; +import { BigIntMath } from "../bigint_math"; +import { COMPUTE_UNIT_MULTIPLIER } from "../chain/solana/tx_builder"; import { SwapperException } from "../error"; +import { Protocol } from "../protocol"; import { getReferrerAddresses } from "../referrer"; import { - SOLANA_CHAIN_INDEX, - SOLANA_NATIVE_TOKEN_ADDRESS, - SOLANA_DEX_IDS_PARAM, - DEFAULT_SLIPPAGE_PERCENT, + SOLANA_CHAIN_INDEX, + SOLANA_NATIVE_TOKEN_ADDRESS, + SOLANA_DEX_IDS_PARAM, + DEFAULT_SLIPPAGE_PERCENT, } from "./constants"; -import { COMPUTE_UNIT_MULTIPLIER } from "../chain/solana/tx_builder"; -import { BigIntMath } from "../bigint_math"; function bpsToPercent(bps: number): string { - return (bps / 100).toString(); + return (bps / 100).toString(); } function assetToTokenAddress(assetId: AssetId): string { - if (assetId.chain !== Chain.Solana) { - throw new SwapperException({ type: "not_supported_chain" }); - } - if (!assetId.tokenId) { - return SOLANA_NATIVE_TOKEN_ADDRESS; - } - return assetId.tokenId; + if (assetId.chain !== Chain.Solana) { + throw new SwapperException({ type: "not_supported_chain" }); + } + if (!assetId.tokenId) { + return SOLANA_NATIVE_TOKEN_ADDRESS; + } + return assetId.tokenId; } function referralFeePercent(request: QuoteRequest): string | undefined { - if (request.referral_bps <= 0) { - return undefined; - } - return bpsToPercent(request.referral_bps); + if (request.referral_bps <= 0) { + return undefined; + } + return bpsToPercent(request.referral_bps); } function referralFeeAddress(request: QuoteRequest): string | undefined { - if (request.referral_bps <= 0) { - return undefined; - } - return getReferrerAddresses().solana || undefined; + if (request.referral_bps <= 0) { + return undefined; + } + return getReferrerAddresses().solana || undefined; } function slippagePercent(request: QuoteRequest): string { - if (request.slippage_bps <= 0) { - return DEFAULT_SLIPPAGE_PERCENT; - } - const percent = request.slippage_bps / 100; - return Math.min(percent, 1).toString(); + if (request.slippage_bps <= 0) { + return DEFAULT_SLIPPAGE_PERCENT; + } + const percent = request.slippage_bps / 100; + return Math.min(percent, 1).toString(); } function maxAutoSlippagePercent(request: QuoteRequest): string | undefined { - if (request.slippage_bps <= 0) { - return undefined; - } - return bpsToPercent(request.slippage_bps * 2); + if (request.slippage_bps <= 0) { + return undefined; + } + return bpsToPercent(request.slippage_bps * 2); } function buildSwapParams(request: QuoteRequest, route: QuoteData): SwapParams { - return { - chainIndex: SOLANA_CHAIN_INDEX, - amount: request.from_value, - fromTokenAddress: route.fromToken.tokenContractAddress, - toTokenAddress: route.toToken.tokenContractAddress, - userWalletAddress: request.from_address, - dexIds: SOLANA_DEX_IDS_PARAM, - slippagePercent: slippagePercent(request), - autoSlippage: true, - maxAutoSlippagePercent: maxAutoSlippagePercent(request), - feePercent: referralFeePercent(request), - fromTokenReferrerWalletAddress: referralFeeAddress(request), - }; + return { + chainIndex: SOLANA_CHAIN_INDEX, + amount: request.from_value, + fromTokenAddress: route.fromToken.tokenContractAddress, + toTokenAddress: route.toToken.tokenContractAddress, + userWalletAddress: request.from_address, + dexIds: SOLANA_DEX_IDS_PARAM, + slippagePercent: slippagePercent(request), + autoSlippage: true, + maxAutoSlippagePercent: maxAutoSlippagePercent(request), + feePercent: referralFeePercent(request), + fromTokenReferrerWalletAddress: referralFeeAddress(request), + }; } function minOutputValue(route: QuoteData, slippageBps: number): string { - if (route.tx?.minReceiveAmount) { - return route.tx.minReceiveAmount; - } - const bps = slippageBps > 0 ? slippageBps : 100; - return BigIntMath.applySlippage(route.toTokenAmount, bps); + if (route.tx?.minReceiveAmount) { + return route.tx.minReceiveAmount; + } + const bps = slippageBps > 0 ? slippageBps : 100; + return BigIntMath.applySlippage(route.toTokenAmount, bps); } export class OkxProvider implements Protocol { - private readonly client: OKXDexClient; - - constructor(client?: OKXDexClient) { - if (client) { - this.client = client; - return; - } + private readonly client: OKXDexClient; - const apiKey = process.env.OKX_API_KEY; - const secretKey = process.env.OKX_SECRET_KEY; - const apiPassphrase = process.env.OKX_API_PASSPHRASE; - const projectId = process.env.OKX_PROJECT_ID; + constructor(client?: OKXDexClient) { + if (client) { + this.client = client; + return; + } - const missing = [ - !apiKey && "OKX_API_KEY", - !secretKey && "OKX_SECRET_KEY", - !apiPassphrase && "OKX_API_PASSPHRASE", - !projectId && "OKX_PROJECT_ID", - ].filter(Boolean); + const apiKey = process.env.OKX_API_KEY; + const secretKey = process.env.OKX_SECRET_KEY; + const apiPassphrase = process.env.OKX_API_PASSPHRASE; + const projectId = process.env.OKX_PROJECT_ID; - if (missing.length > 0) { - throw new Error(`Missing OKX auth env variables: ${missing.join(", ")}`); - } + const missing = [ + !apiKey && "OKX_API_KEY", + !secretKey && "OKX_SECRET_KEY", + !apiPassphrase && "OKX_API_PASSPHRASE", + !projectId && "OKX_PROJECT_ID", + ].filter(Boolean); - this.client = new OKXDexClient({ - apiKey: apiKey!, - secretKey: secretKey!, - apiPassphrase: apiPassphrase!, - projectId: projectId!, - }); - } - - private async estimateComputeUnitLimit(tx: TransactionData): Promise { - try { - const result = await this.client.dex.getGasLimit({ - chainIndex: SOLANA_CHAIN_INDEX, - fromAddress: tx.from, - toAddress: tx.to, - extJson: { inputData: tx.data }, - }); - - if (result.code === "0") { - const raw = Number(result.data[0]?.gasLimit); - if (raw > 0) { - return Math.ceil(raw * COMPUTE_UNIT_MULTIPLIER).toString(); + if (missing.length > 0) { + throw new Error(`Missing OKX auth env variables: ${missing.join(", ")}`); } - } - } catch { - console.warn("Failed to estimate compute unit limit"); - } - return undefined; - } - - async get_quote(quoteRequest: QuoteRequest): Promise { - const fromAsset = AssetId.fromString(quoteRequest.from_asset.id); - const toAsset = AssetId.fromString(quoteRequest.to_asset.id); - - const fromTokenAddress = assetToTokenAddress(fromAsset); - const toTokenAddress = assetToTokenAddress(toAsset); - - const response = await this.client.dex.getQuote({ - chainIndex: SOLANA_CHAIN_INDEX, - amount: quoteRequest.from_value, - fromTokenAddress, - toTokenAddress, - dexIds: SOLANA_DEX_IDS_PARAM, - slippagePercent: slippagePercent(quoteRequest), - feePercent: referralFeePercent(quoteRequest), - }); - - if (response.code !== "0") { - throw new SwapperException({ - type: "compute_quote_error", - message: response.msg || "Failed to fetch OKX quote", - }); + + this.client = new OKXDexClient({ + apiKey: apiKey!, + secretKey: secretKey!, + apiPassphrase: apiPassphrase!, + projectId: projectId!, + }); } - const route = response.data[0]; - if (!route) { - throw new SwapperException({ type: "no_quote_available" }); + private async estimateComputeUnitLimit(tx: TransactionData): Promise { + try { + const result = await this.client.dex.getGasLimit({ + chainIndex: SOLANA_CHAIN_INDEX, + fromAddress: tx.from, + toAddress: tx.to, + extJson: { inputData: tx.data }, + }); + + if (result.code === "0") { + const raw = Number(result.data[0]?.gasLimit); + if (raw > 0) { + return Math.ceil(raw * COMPUTE_UNIT_MULTIPLIER).toString(); + } + } + } catch { } + return undefined; } - return { - quote: quoteRequest, - output_value: route.toTokenAmount, - output_min_value: minOutputValue(route, quoteRequest.slippage_bps), - eta_in_seconds: 0, - route_data: route, - }; - } + async get_quote(quoteRequest: QuoteRequest): Promise { + const fromAsset = AssetId.fromString(quoteRequest.from_asset.id); + const toAsset = AssetId.fromString(quoteRequest.to_asset.id); + + const fromTokenAddress = assetToTokenAddress(fromAsset); + const toTokenAddress = assetToTokenAddress(toAsset); + + const response = await this.client.dex.getQuote({ + chainIndex: SOLANA_CHAIN_INDEX, + amount: quoteRequest.from_value, + fromTokenAddress, + toTokenAddress, + dexIds: SOLANA_DEX_IDS_PARAM, + slippagePercent: slippagePercent(quoteRequest), + feePercent: referralFeePercent(quoteRequest), + }); + + if (response.code !== "0") { + throw new SwapperException({ + type: "compute_quote_error", + message: response.msg || "Failed to fetch OKX quote", + }); + } - async get_quote_data(quote: Quote): Promise { - const route = quote.route_data as QuoteData | undefined; - if (!route || !route.fromToken || !route.toToken) { - throw new SwapperException({ type: "invalid_route" }); + const route = response.data[0]; + if (!route) { + throw new SwapperException({ type: "no_quote_available" }); + } + + return { + quote: quoteRequest, + output_value: route.toTokenAmount, + output_min_value: minOutputValue(route, quoteRequest.slippage_bps), + eta_in_seconds: 0, + route_data: route, + }; } - const response = await this.client.dex.getSwapData(buildSwapParams(quote.quote, route)); + async get_quote_data(quote: Quote): Promise { + const route = quote.route_data as QuoteData | undefined; + if (!route || !route.fromToken || !route.toToken) { + throw new SwapperException({ type: "invalid_route" }); + } - if (response.code !== "0") { - throw new SwapperException({ - type: "compute_quote_error", - message: response.msg || "Failed to fetch OKX quote data", - }); - } + const response = await this.client.dex.getSwapData(buildSwapParams(quote.quote, route)); - const swapData = response.data[0]; - if (!swapData?.tx?.data) { - throw new SwapperException({ type: "invalid_route" }); - } + if (response.code !== "0") { + throw new SwapperException({ + type: "compute_quote_error", + message: response.msg || "Failed to fetch OKX quote data", + }); + } - const gasLimit = await this.estimateComputeUnitLimit(swapData.tx); + const swapData = response.data[0]; + if (!swapData?.tx?.data) { + throw new SwapperException({ type: "invalid_route" }); + } - let serializedBase64: string; - try { - serializedBase64 = Buffer.from(bs58.decode(swapData.tx.data)).toString("base64"); - } catch (error) { - throw new SwapperException({ - type: "transaction_error", - message: `invalid swap tx data: ${String(error)}`, - }); - } + const gasLimit = await this.estimateComputeUnitLimit(swapData.tx); - return { - to: swapData.tx.to, - value: "0", - data: serializedBase64, - dataType: SwapQuoteDataType.Contract, - gasLimit, - }; - } + let serializedBase64: string; + try { + serializedBase64 = Buffer.from(bs58.decode(swapData.tx.data)).toString("base64"); + } catch (error) { + throw new SwapperException({ + type: "transaction_error", + message: `invalid swap tx data: ${String(error)}`, + }); + } + + return { + to: swapData.tx.to, + value: "0", + data: serializedBase64, + dataType: SwapQuoteDataType.Contract, + gasLimit, + }; + } } diff --git a/packages/swapper/src/orca/fee.test.ts b/packages/swapper/src/orca/fee.test.ts index c7bd1f6..57a40e9 100644 --- a/packages/swapper/src/orca/fee.test.ts +++ b/packages/swapper/src/orca/fee.test.ts @@ -1,13 +1,8 @@ -import { Quote } from "@gemwallet/types"; import { BN } from "@coral-xyz/anchor"; +import { Quote } from "@gemwallet/types"; -import { - calculateReferralFeeAmount, - bnToNumberSafe, - BASIS_POINTS_DENOMINATOR, - MAX_SAFE_NUMBER_BN, -} from "./fee"; import { buildOrcaQuoteFixture } from "../testkit/mock"; +import { calculateReferralFeeAmount, bnToNumberSafe, BASIS_POINTS_DENOMINATOR, MAX_SAFE_NUMBER_BN } from "./fee"; const buildQuote = (fromValue: string, referralBps: number): Quote => buildOrcaQuoteFixture({ from_value: fromValue, referral_bps: referralBps }); diff --git a/packages/swapper/src/orca/fee.ts b/packages/swapper/src/orca/fee.ts index c979844..ca1d3c2 100644 --- a/packages/swapper/src/orca/fee.ts +++ b/packages/swapper/src/orca/fee.ts @@ -1,7 +1,8 @@ import { BN } from "@coral-xyz/anchor"; import { AssetId, Quote } from "@gemwallet/types"; -import { PublicKey } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; + import { parsePublicKey } from "../chain/solana/account"; export const BASIS_POINTS_DENOMINATOR = 10_000; @@ -45,7 +46,7 @@ export async function applyReferralFee( } if (asset.isNative()) { - const referralFee = amountIn * referralBps / BigInt(10_000); + const referralFee = (amountIn * referralBps) / BigInt(10_000); return amountIn - referralFee; } @@ -56,6 +57,6 @@ export async function applyReferralFee( return amountIn; } - const referralFee = amountIn * referralBps / BigInt(10_000); + const referralFee = (amountIn * referralBps) / BigInt(10_000); return amountIn - referralFee; } diff --git a/packages/swapper/src/orca/index.test.ts b/packages/swapper/src/orca/index.test.ts index 26dfa93..8179cac 100644 --- a/packages/swapper/src/orca/index.test.ts +++ b/packages/swapper/src/orca/index.test.ts @@ -1,7 +1,7 @@ import { Chain, Quote } from "@gemwallet/types"; -import { calculateReferralFeeAmount } from "./index"; import { buildOrcaQuoteFixture } from "../testkit/mock"; +import { calculateReferralFeeAmount } from "./index"; describe("calculateReferralFeeAmount", () => { const baseQuote = buildOrcaQuoteFixture({ @@ -44,8 +44,6 @@ describe("calculateReferralFeeAmount", () => { }, }; - expect(() => calculateReferralFeeAmount(quote)).toThrow( - /Invalid from_value provided/ - ); + expect(() => calculateReferralFeeAmount(quote)).toThrow(/Invalid from_value provided/); }); }); diff --git a/packages/swapper/src/orca/integration.test.ts b/packages/swapper/src/orca/integration.test.ts index 9b505df..f23b36e 100644 --- a/packages/swapper/src/orca/integration.test.ts +++ b/packages/swapper/src/orca/integration.test.ts @@ -1,13 +1,12 @@ import { Chain, QuoteRequest } from "@gemwallet/types"; -import { OrcaWhirlpoolProvider } from "./provider"; import { createOrcaQuoteRequest } from "../testkit/mock"; +import { OrcaWhirlpoolProvider } from "./provider"; const runIntegration = process.env.ORCA_INTEGRATION_TEST === "1"; const describeIntegration = runIntegration ? describe : describe.skip; -const SOLANA_MAINNET_RPC = - process.env.SOLANA_RPC || "https://solana-rpc.publicnode.com"; +const SOLANA_MAINNET_RPC = process.env.SOLANA_RPC || "https://solana-rpc.publicnode.com"; const WALLET_ADDRESS = "A21o4asMbFHYadqXdLusT9Bvx9xaC5YV9gcaidjqtdXC"; const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; @@ -64,9 +63,9 @@ describeIntegration("Orca live integration", () => { const quote = await provider.get_quote(token2022Request); expect(quote.route_data).toBeDefined(); - expect(quote.route_data && "outputMint" in quote.route_data - ? quote.route_data.outputMint - : null).toBe(PYUSD_MINT); + expect(quote.route_data && "outputMint" in quote.route_data ? quote.route_data.outputMint : null).toBe( + PYUSD_MINT, + ); const quoteData = await provider.get_quote_data(quote); expect(typeof quoteData.data).toBe("string"); diff --git a/packages/swapper/src/orca/model.ts b/packages/swapper/src/orca/model.ts index 21283bf..c61386d 100644 --- a/packages/swapper/src/orca/model.ts +++ b/packages/swapper/src/orca/model.ts @@ -15,7 +15,7 @@ export class OrcaRouteData { public readonly outputMint: string, public readonly amount: string, public readonly slippageBps: number, - ) { } + ) {} static create(fields: OrcaRouteDataType): OrcaRouteData { return new OrcaRouteData( diff --git a/packages/swapper/src/orca/provider.test.ts b/packages/swapper/src/orca/provider.test.ts index cdc238d..a3fe9a2 100644 --- a/packages/swapper/src/orca/provider.test.ts +++ b/packages/swapper/src/orca/provider.test.ts @@ -1,8 +1,9 @@ -import { OrcaWhirlpoolProvider } from "./provider"; import { Quote } from "@gemwallet/types"; -import { TransactionInstruction, PublicKey } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { TransactionInstruction, PublicKey } from "@solana/web3.js"; + import { SOL_ASSET, buildOrcaQuoteFixture, createOrcaQuoteRequest } from "../testkit/mock"; +import { OrcaWhirlpoolProvider } from "./provider"; jest.mock("@solana-program/token-2022", () => ({ fetchAllMint: jest.fn(), @@ -98,18 +99,16 @@ describe("OrcaWhirlpoolProvider.get_quote referral handling", () => { capturedAmount = null; mockFetchAllMint.fetchAllMint.mockReset(); - jest - .spyOn(provider as any, "buildExactInQuote") - .mockImplementation(async (...args: unknown[]) => { - const amount = args[2] as bigint; - capturedAmount = amount; - return { - quote: { - tokenEstOut: BigInt(5000), - tokenMinOut: BigInt(4000), - }, - }; - }); + jest.spyOn(provider as any, "buildExactInQuote").mockImplementation(async (...args: unknown[]) => { + const amount = args[2] as bigint; + capturedAmount = amount; + return { + quote: { + tokenEstOut: BigInt(5000), + tokenMinOut: BigInt(4000), + }, + }; + }); }); afterEach(() => { @@ -118,12 +117,8 @@ describe("OrcaWhirlpoolProvider.get_quote referral handling", () => { }); it("reduces swap amount for legacy SPL tokens when referral applies", async () => { - jest - .spyOn(provider as any, "findBestPool") - .mockResolvedValue({ account: { address: "PoolAddress" } }); - const mintProgramSpy = jest - .spyOn(provider as any, "getTokenProgram") - .mockResolvedValueOnce(TOKEN_PROGRAM_ID); + jest.spyOn(provider as any, "findBestPool").mockResolvedValue({ account: { address: "PoolAddress" } }); + const mintProgramSpy = jest.spyOn(provider as any, "getTokenProgram").mockResolvedValueOnce(TOKEN_PROGRAM_ID); const quote = await provider.get_quote( createOrcaQuoteRequest({ @@ -143,9 +138,7 @@ describe("OrcaWhirlpoolProvider.get_quote referral handling", () => { }); it("uses full input amount for Token-2022 tokens when referral cannot be collected", async () => { - jest - .spyOn(provider as any, "findBestPool") - .mockResolvedValue({ account: { address: "PoolAddress" } }); + jest.spyOn(provider as any, "findBestPool").mockResolvedValue({ account: { address: "PoolAddress" } }); const getProgramSpy = jest .spyOn(provider as any, "getTokenProgram") .mockResolvedValueOnce(new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")); diff --git a/packages/swapper/src/orca/provider.ts b/packages/swapper/src/orca/provider.ts index 6f6f924..93cd89d 100644 --- a/packages/swapper/src/orca/provider.ts +++ b/packages/swapper/src/orca/provider.ts @@ -1,35 +1,4 @@ import { AssetId, Quote, QuoteRequest, SwapQuoteData, SwapQuoteDataType } from "@gemwallet/types"; -import { - addComputeBudgetInstructions, - getRecentBlockhash, - getRecentPriorityFee, - serializeTransaction, - setTransactionBlockhash, -} from "../chain/solana/tx_builder"; -import { DEFAULT_COMMITMENT } from "../chain/solana/constants"; -import { Protocol } from "../protocol"; -import { getReferrerAddresses } from "../referrer"; -import { calculateReferralFeeAmount, bnToNumberSafe, applyReferralFee } from "./fee"; -import { OrcaRouteData } from "./model"; -import { BigIntMath } from "../bigint_math"; -import { getMintAddress, parsePublicKey, resolveTokenProgram } from "../chain/solana/account"; - -import { - Connection, - PublicKey, - SystemProgram, - Transaction, - TransactionInstruction, -} from "@solana/web3.js"; -import { createTransferInstruction, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from "@solana/spl-token"; - -import { - createSolanaRpc, - address as toAddress, - type Account, - type Address, - type TransactionSigner, -} from "@solana/kit"; import { orderMints, setNativeMintWrappingStrategy, @@ -54,12 +23,25 @@ import { swapQuoteByInputToken, type ExactInSwapQuote, } from "@orca-so/whirlpools-core"; +import { AccountRole, type AccountLookupMeta, type AccountMeta, type Instruction } from "@solana/instructions"; +import { createSolanaRpc, address as toAddress, type Account, type Address, type TransactionSigner } from "@solana/kit"; +import { createTransferInstruction, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from "@solana/spl-token"; +import { Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction } from "@solana/web3.js"; + +import { BigIntMath } from "../bigint_math"; +import { getMintAddress, parsePublicKey, resolveTokenProgram } from "../chain/solana/account"; +import { DEFAULT_COMMITMENT } from "../chain/solana/constants"; import { - AccountRole, - type AccountLookupMeta, - type AccountMeta, - type Instruction, -} from "@solana/instructions"; + addComputeBudgetInstructions, + getRecentBlockhash, + getRecentPriorityFee, + serializeTransaction, + setTransactionBlockhash, +} from "../chain/solana/tx_builder"; +import { Protocol } from "../protocol"; +import { getReferrerAddresses } from "../referrer"; +import { calculateReferralFeeAmount, bnToNumberSafe, applyReferralFee } from "./fee"; +import { OrcaRouteData } from "./model"; const POOL_CACHE_TTL_MS = 30_000; const SUPPORTED_TICK_SPACINGS = [1, 2, 4, 8, 16, 64, 96, 128, 256, 32896]; @@ -136,12 +118,7 @@ export class OrcaWhirlpoolProvider implements Protocol { const slippageBps = quoteRequest.slippage_bps ?? 100; const pool = await this.findBestPool(fromMintAddress, toMintAddress); - const quoteResult = await this.buildExactInQuote( - pool.account, - fromMintAddress, - swapAmount, - slippageBps, - ); + const quoteResult = await this.buildExactInQuote(pool.account, fromMintAddress, swapAmount, slippageBps); const routeData = OrcaRouteData.create({ poolAddress: String(pool.account.address), @@ -181,25 +158,13 @@ export class OrcaWhirlpoolProvider implements Protocol { signer, ); const priorityFeePromise = this.getPriorityFee(); - const blockhashPromise = getRecentBlockhash( - this.connection, - DEFAULT_COMMITMENT, - ); + const blockhashPromise = getRecentBlockhash(this.connection, DEFAULT_COMMITMENT); - const [{ instructions }, priorityFee] = await Promise.all([ - swapPromise, - priorityFeePromise, - ]); + const [{ instructions }, priorityFee] = await Promise.all([swapPromise, priorityFeePromise]); - const legacyInstructions = instructions.map((instruction) => - this.toLegacyInstruction(instruction), - ); + const legacyInstructions = instructions.map((instruction) => this.toLegacyInstruction(instruction)); - const computeBudgetInstructions = addComputeBudgetInstructions( - [], - undefined, - priorityFee, - ); + const computeBudgetInstructions = addComputeBudgetInstructions([], undefined, priorityFee); const referralInstruction = await this.buildReferralInstruction(quote, userPublicKey); @@ -237,9 +202,7 @@ export class OrcaWhirlpoolProvider implements Protocol { return this.priorityFeeCache.value; } - const value = await getRecentPriorityFee(this.connection, [ - new PublicKey(WHIRLPOOL_PROGRAM_ADDRESS), - ]); + const value = await getRecentPriorityFee(this.connection, [new PublicKey(WHIRLPOOL_PROGRAM_ADDRESS)]); this.priorityFeeCache = { value, expiresAt: now + 3_000, @@ -247,10 +210,7 @@ export class OrcaWhirlpoolProvider implements Protocol { return value; } - private async findBestPool( - mintA: Address, - mintB: Address, - ): Promise { + private async findBestPool(mintA: Address, mintB: Address): Promise { const [orderedA, orderedB] = orderMints(mintA, mintB); const cacheKey = `${String(orderedA)}-${String(orderedB)}`; @@ -261,12 +221,7 @@ export class OrcaWhirlpoolProvider implements Protocol { const candidateInfos = await Promise.all( this.supportedTickSpacings.map(async (tickSpacing) => { - const [address] = await getWhirlpoolAddress( - WHIRLPOOLS_CONFIG_ADDRESS, - orderedA, - orderedB, - tickSpacing, - ); + const [address] = await getWhirlpoolAddress(WHIRLPOOLS_CONFIG_ADDRESS, orderedA, orderedB, tickSpacing); return { tickSpacing, address }; }), ); @@ -370,9 +325,7 @@ export class OrcaWhirlpoolProvider implements Protocol { return { quote }; } - private async fetchTickArrays( - whirlpool: Account, - ): Promise { + private async fetchTickArrays(whirlpool: Account): Promise { const tickArrayStartIndex = getTickArrayStartTickIndex( whirlpool.data.tickCurrentIndex, whirlpool.data.tickSpacing, @@ -388,15 +341,15 @@ export class OrcaWhirlpoolProvider implements Protocol { const addresses = await Promise.all( indexes.map(async (startIndex) => { - const [address] = await getTickArrayAddress( - whirlpool.address, - startIndex, - ); + const [address] = await getTickArrayAddress(whirlpool.address, startIndex); return String(address); }), ); - const accounts = await fetchAllMaybeTickArray(this.rpc, addresses.map((addr) => toAddress(addr))); + const accounts = await fetchAllMaybeTickArray( + this.rpc, + addresses.map((addr) => toAddress(addr)), + ); return accounts.map((account, index): TickArrayData => { if ("exists" in account && account.exists) { @@ -448,9 +401,7 @@ export class OrcaWhirlpoolProvider implements Protocol { whirlpool: Account, ): Promise>["data"] | null> { try { - const feeTierIndex = - whirlpool.data.feeTierIndexSeed[0] + - whirlpool.data.feeTierIndexSeed[1] * 256; + const feeTierIndex = whirlpool.data.feeTierIndexSeed[0] + whirlpool.data.feeTierIndexSeed[1] * 256; if (whirlpool.data.tickSpacing === feeTierIndex) { return null; } @@ -474,10 +425,7 @@ export class OrcaWhirlpoolProvider implements Protocol { } private toLegacyInstruction(instruction: Instruction): TransactionInstruction { - const keys = - instruction.accounts?.map((account) => - this.toAccountMeta(account), - ) ?? []; + const keys = instruction.accounts?.map((account) => this.toAccountMeta(account)) ?? []; const data = instruction.data ? Buffer.from(instruction.data) : Buffer.alloc(0); return new TransactionInstruction({ @@ -487,15 +435,13 @@ export class OrcaWhirlpoolProvider implements Protocol { }); } - private toAccountMeta( - account: AccountMeta | AccountLookupMeta, - ): { pubkey: PublicKey; isSigner: boolean; isWritable: boolean } { - const isSigner = - account.role === AccountRole.READONLY_SIGNER || - account.role === AccountRole.WRITABLE_SIGNER; - const isWritable = - account.role === AccountRole.WRITABLE || - account.role === AccountRole.WRITABLE_SIGNER; + private toAccountMeta(account: AccountMeta | AccountLookupMeta): { + pubkey: PublicKey; + isSigner: boolean; + isWritable: boolean; + } { + const isSigner = account.role === AccountRole.READONLY_SIGNER || account.role === AccountRole.WRITABLE_SIGNER; + const isWritable = account.role === AccountRole.WRITABLE || account.role === AccountRole.WRITABLE_SIGNER; return { pubkey: new PublicKey(account.address), @@ -533,12 +479,7 @@ export class OrcaWhirlpoolProvider implements Protocol { if (!programId.equals(TOKEN_PROGRAM_ID)) { return null; } - const userTokenAccount = getAssociatedTokenAddressSync( - fromMintKey, - userPublicKey, - false, - programId, - ); + const userTokenAccount = getAssociatedTokenAddressSync(fromMintKey, userPublicKey, false, programId); const referrerTokenAccount = getAssociatedTokenAddressSync( fromMintKey, new PublicKey(referrer), @@ -555,5 +496,4 @@ export class OrcaWhirlpoolProvider implements Protocol { programId, ); } - } diff --git a/packages/swapper/src/panora/move.ts b/packages/swapper/src/panora/move.ts index ac3c36a..54f850c 100644 --- a/packages/swapper/src/panora/move.ts +++ b/packages/swapper/src/panora/move.ts @@ -149,9 +149,7 @@ function parseU8(value: unknown): number | null { } if (typeof value === "string") { const trimmed = value.trim(); - const parsed = trimmed.startsWith("0x") - ? Number.parseInt(trimmed, 16) - : Number.parseInt(trimmed, 10); + const parsed = trimmed.startsWith("0x") ? Number.parseInt(trimmed, 16) : Number.parseInt(trimmed, 10); if (!Number.isFinite(parsed)) { return null; } diff --git a/packages/swapper/src/panora/provider.test.ts b/packages/swapper/src/panora/provider.test.ts index c8bbf42..cecb9c8 100644 --- a/packages/swapper/src/panora/provider.test.ts +++ b/packages/swapper/src/panora/provider.test.ts @@ -1,4 +1,5 @@ import { Chain, QuoteRequest } from "@gemwallet/types"; + import { BigIntMath } from "../bigint_math"; import { APTOS_USDT_FA, createAptosUsdcQuoteRequest } from "../testkit/mock"; diff --git a/packages/swapper/src/panora/provider.ts b/packages/swapper/src/panora/provider.ts index 1ebaca7..21cc6bf 100644 --- a/packages/swapper/src/panora/provider.ts +++ b/packages/swapper/src/panora/provider.ts @@ -1,10 +1,11 @@ +import { QuoteRequest, Quote, SwapQuoteData, AssetId, SwapQuoteDataType } from "@gemwallet/types"; import Panora, { type PanoraConfig } from "@panoraexchange/swap-sdk"; -import { QuoteRequest, Quote, SwapQuoteData, AssetId, Chain, SwapQuoteDataType } from "@gemwallet/types"; + +import { BigIntMath } from "../bigint_math"; import { Protocol } from "../protocol"; import { getReferrerAddresses } from "../referrer"; import { type PanoraQuoteResponse, isPanoraQuoteResponse, getPanoraQuoteEntry } from "./model"; import { normalizePanoraArguments } from "./move"; -import { BigIntMath } from "../bigint_math"; const APTOS_NATIVE_COIN = "0x1::aptos_coin::AptosCoin"; @@ -74,9 +75,7 @@ export class PanoraProvider implements Protocol { const tokenDecimals = validatedRouteData.toToken?.decimals ?? request.to_asset.decimals; const outputValue = BigIntMath.parseDecimals(outputAmount, tokenDecimals); - const outputMinValue = outputMinAmount - ? BigIntMath.parseDecimals(outputMinAmount, tokenDecimals) - : outputValue; + const outputMinValue = outputMinAmount ? BigIntMath.parseDecimals(outputMinAmount, tokenDecimals) : outputValue; return { quote: request, @@ -94,10 +93,7 @@ export class PanoraProvider implements Protocol { throw new Error("Panora quote response missing transaction data"); } - const normalizedArguments = normalizePanoraArguments( - quoteEntry.txData.function, - quoteEntry.txData.arguments, - ); + const normalizedArguments = normalizePanoraArguments(quoteEntry.txData.function, quoteEntry.txData.arguments); const payload = { type: "entry_function_payload", diff --git a/packages/swapper/src/referrer.js b/packages/swapper/src/referrer.js deleted file mode 100644 index e403bed..0000000 --- a/packages/swapper/src/referrer.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getReferrerAddresses = void 0; -function getReferrerAddresses() { - return { - evm: "0x0D9DAB1A248f63B0a48965bA8435e4de7497a3dC", - solana: "5fmLrs2GuhfDP1B51ziV5Kd1xtAr9rw1jf3aQ4ihZ2gy", - sui: "0x9d6b98b18fd26b5efeec68d020dcf1be7a94c2c315353779bc6b3aed44188ddf", - ton: "UQDxJKarPSp0bCta9DFgp81Mpt5hpGbuVcSxwfeza0Bin201", - tron: "TYeyZXywpA921LEtw2PF3obK4B8Jjgpp32", - }; -} -exports.getReferrerAddresses = getReferrerAddresses; -//# sourceMappingURL=referrer.js.map \ No newline at end of file diff --git a/packages/swapper/src/relay/client.ts b/packages/swapper/src/relay/client.ts index cc1b67b..c7c0872 100644 --- a/packages/swapper/src/relay/client.ts +++ b/packages/swapper/src/relay/client.ts @@ -1,42 +1,34 @@ -import type { RelayQuotePostBodyParams, RelayQuoteResponse } from './model'; +import type { RelayQuotePostBodyParams, RelayQuoteResponse } from "./model"; -const RELAY_API_BASE_URL = 'https://api.relay.link/'; +const RELAY_API_BASE_URL = "https://api.relay.link/"; -export async function fetchQuote( - params: RelayQuotePostBodyParams -): Promise { - const apiUrl = `${RELAY_API_BASE_URL}quote`; +export async function fetchQuote(params: RelayQuotePostBodyParams): Promise { + const apiUrl = `${RELAY_API_BASE_URL}quote`; - try { - const response = await fetch(apiUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - }); + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(params), + }); - if (!response.ok) { - let errorData; - try { - errorData = await response.json(); - } catch (e) { - // If response is not JSON, use text - errorData = await response.text(); - } - console.error('Relay API error:', response.status, errorData); - throw new Error( - `Relay API request failed with status ${response.status}: ${JSON.stringify(errorData)}` - ); - } + if (!response.ok) { + let errorData; + try { + errorData = await response.json(); + } catch { + errorData = await response.text(); + } + throw new Error(`Relay API request failed with status ${response.status}: ${JSON.stringify(errorData)}`); + } - return await response.json() as RelayQuoteResponse; - } catch (error: any) { - // Handle network errors or other issues not caught by !response.ok - if (error instanceof Error && error.message.startsWith('Relay API request failed')) { - throw error; // Re-throw errors already processed from the API response + return (await response.json()) as RelayQuoteResponse; + } catch (error: any) { + if (error instanceof Error && error.message.startsWith("Relay API request failed")) { + throw error; + } + throw new Error(`An unexpected error occurred while fetching the quote from Relay: ${error.message}`); } - console.error('Unexpected error fetching Relay quote:', error); - throw new Error(`An unexpected error occurred while fetching the quote from Relay: ${error.message}`); - } } diff --git a/packages/swapper/src/relay/index.ts b/packages/swapper/src/relay/index.ts index f2d0a7c..eed8486 100644 --- a/packages/swapper/src/relay/index.ts +++ b/packages/swapper/src/relay/index.ts @@ -1,3 +1,3 @@ -export * from './model'; -export * from './client'; -export * from './provider'; +export * from "./model"; +export * from "./client"; +export * from "./provider"; diff --git a/packages/swapper/src/relay/model.ts b/packages/swapper/src/relay/model.ts index ad2e680..a86edf3 100644 --- a/packages/swapper/src/relay/model.ts +++ b/packages/swapper/src/relay/model.ts @@ -1,97 +1,97 @@ export interface RelayQuotePostBodyParams { - user: string; // Address that is depositing funds on the origin chain and submitting transactions or signatures - amount: string; // e.g., "1000000000000000000" - originCurrency: string; // e.g., "0x0000000000000000000000000000000000000000" (ETH) - destinationCurrency: string; // e.g., "bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8" (BTC) - originChainId: number; // e.g., 1 (Ethereum) - destinationChainId: number; // e.g., 8253038 (Bitcoin) - recipient: string; // Address that is receiving the funds on the destination chain, if not specified then this will default to the user address - tradeType: 'EXACT_INPUT' | 'EXACT_OUTPUT'; - appFee?: AppFee[]; // Fees added on top of the relay fees, this fee accrues offchain to minimize gas costs. - useDepositAddress?: boolean; // Enable this to use a deposit address when bridging, in scenarios where calldata cannot be sent alongside the transaction. only works on native currency bridges. - topupGas?: boolean; // If set, the destination fill will include a gas topup to the recipient (only supported for EVM chains if the requested currency is not the gas currency on the destination chain) - refundTo?: string; // Address to send the refund to in the case of failure, if not specified then the recipient address or user address is used - slippageTolerance?: string; // Optional: basis points (if not specified then the slippage tolerance is automatically calculated) + user: string; + amount: string; + originCurrency: string; + destinationCurrency: string; + originChainId: number; + destinationChainId: number; + recipient: string; + tradeType: "EXACT_INPUT" | "EXACT_OUTPUT"; + appFee?: AppFee[]; + useDepositAddress?: boolean; + topupGas?: boolean; + refundTo?: string; + slippageTolerance?: string; } export interface AppFee { - recipient: string; - fee: string; // basis points + recipient: string; + fee: string; } export interface CurrencyMetadata { - logoURI?: string; - verified: boolean; - isNative?: boolean; + logoURI?: string; + verified: boolean; + isNative?: boolean; } export interface CurrencyInfo { - chainId: number; - address: string; - symbol: string; - name: string; - decimals: number; - metadata?: CurrencyMetadata; + chainId: number; + address: string; + symbol: string; + name: string; + decimals: number; + metadata?: CurrencyMetadata; } export interface AmountDetails { - currency: CurrencyInfo; - amount: string; - amountFormatted: string; - amountUsd: string; - minimumAmount: string; + currency: CurrencyInfo; + amount: string; + amountFormatted: string; + amountUsd: string; + minimumAmount: string; } export interface StepDataItem { - from: string; - to: string; - data: string; - value: string; - maxFeePerGas?: string; - maxPriorityFeePerGas?: string; - chainId: number; - gas?: string; + from: string; + to: string; + data: string; + value: string; + maxFeePerGas?: string; + maxPriorityFeePerGas?: string; + chainId: number; + gas?: string; } export interface StepItem { - status: string; - data: StepDataItem; + status: string; + data: StepDataItem; } export interface Step { - id: 'deposit' | 'approve' | 'swap'; - kind: string; - requestId?: string; - items: StepItem[]; - depositAddress?: string; + id: "deposit" | "approve" | "swap"; + kind: string; + requestId?: string; + items: StepItem[]; + depositAddress?: string; } export interface SlippageDetail { - usd: string; - value: string; - percent: string; + usd: string; + value: string; + percent: string; } export interface SlippageTolerance { - origin: SlippageDetail; - destination: SlippageDetail; + origin: SlippageDetail; + destination: SlippageDetail; } export interface QuoteDetails { - operation: string; - sender: string; - recipient: string; - currencyIn: AmountDetails; - currencyOut: AmountDetails; - currencyGasTopup?: AmountDetails; - totalImpact?: { usd: string; percent: string }; - swapImpact?: { usd: string; percent: string }; - rate: string; - slippageTolerance?: SlippageTolerance; - timeEstimate?: number; + operation: string; + sender: string; + recipient: string; + currencyIn: AmountDetails; + currencyOut: AmountDetails; + currencyGasTopup?: AmountDetails; + totalImpact?: { usd: string; percent: string }; + swapImpact?: { usd: string; percent: string }; + rate: string; + slippageTolerance?: SlippageTolerance; + timeEstimate?: number; } export interface RelayQuoteResponse { - steps: Step[]; - details: QuoteDetails; + steps: Step[]; + details: QuoteDetails; } diff --git a/packages/swapper/src/relay/provider.test.ts b/packages/swapper/src/relay/provider.test.ts index ec434c4..890f61c 100644 --- a/packages/swapper/src/relay/provider.test.ts +++ b/packages/swapper/src/relay/provider.test.ts @@ -1,12 +1,13 @@ -import { RelayProvider } from './provider'; -import { AssetId } from '@gemwallet/types'; -import { Chain } from '@gemwallet/types'; +import { AssetId } from "@gemwallet/types"; +import { Chain } from "@gemwallet/types"; -describe('RelayProvider', () => { - it('Test mapAssetIdToCurrency', () => { +import { RelayProvider } from "./provider"; + +describe("RelayProvider", () => { + it("Test mapAssetIdToCurrency", () => { const provider = new RelayProvider(); - let assetId = new AssetId(Chain.Hyperliquid); + const assetId = new AssetId(Chain.Hyperliquid); - expect(provider.mapAssetIdToCurrency(assetId)).toBe('0x0000000000000000000000000000000000000000'); + expect(provider.mapAssetIdToCurrency(assetId)).toBe("0x0000000000000000000000000000000000000000"); }); }); diff --git a/packages/swapper/src/relay/provider.ts b/packages/swapper/src/relay/provider.ts index 37bacf2..3e45390 100644 --- a/packages/swapper/src/relay/provider.ts +++ b/packages/swapper/src/relay/provider.ts @@ -1,132 +1,131 @@ -import { Protocol } from '../protocol'; -import { QuoteRequest, Quote, SwapQuoteData, AssetId, Chain, SwapQuoteDataType } from '@gemwallet/types'; -import { fetchQuote } from './client'; -import { RelayQuotePostBodyParams, RelayQuoteResponse, Step } from './model'; -import { getReferrerAddresses } from '../referrer'; +import { QuoteRequest, Quote, SwapQuoteData, AssetId, Chain, SwapQuoteDataType } from "@gemwallet/types"; + +import { Protocol } from "../protocol"; +import { getReferrerAddresses } from "../referrer"; +import { fetchQuote } from "./client"; +import { RelayQuotePostBodyParams, RelayQuoteResponse, Step } from "./model"; const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; export class RelayProvider implements Protocol { - constructor() { } + constructor() {} - mapChainToRelayChainId(chain: Chain): number { - switch (chain) { - case Chain.Ethereum: - return 1; - case Chain.Base: - return 8453; - case Chain.Berachain: - return 80094; - case Chain.Hyperliquid: - return 999; - case Chain.Manta: - return 169; - case Chain.Mantle: - return 5000; - case Chain.SmartChain: - return 56; - case Chain.Unichain: - return 130; - default: - throw new Error(`Unsupported chain: ${chain}`); + mapChainToRelayChainId(chain: Chain): number { + switch (chain) { + case Chain.Ethereum: + return 1; + case Chain.Base: + return 8453; + case Chain.Berachain: + return 80094; + case Chain.Hyperliquid: + return 999; + case Chain.Manta: + return 169; + case Chain.Mantle: + return 5000; + case Chain.SmartChain: + return 56; + case Chain.Unichain: + return 130; + default: + throw new Error(`Unsupported chain: ${chain}`); + } } - } - mapAssetIdToCurrency(assetId: AssetId): string { - switch (assetId.chain) { - case Chain.Ethereum: - case Chain.Base: - case Chain.Berachain: - case Chain.Hyperliquid: - case Chain.Manta: - case Chain.Mantle: - case Chain.SmartChain: - case Chain.Unichain: - if (!assetId.tokenId) { - return ZERO_ADDRESS; + mapAssetIdToCurrency(assetId: AssetId): string { + switch (assetId.chain) { + case Chain.Ethereum: + case Chain.Base: + case Chain.Berachain: + case Chain.Hyperliquid: + case Chain.Manta: + case Chain.Mantle: + case Chain.SmartChain: + case Chain.Unichain: + if (!assetId.tokenId) { + return ZERO_ADDRESS; + } + return assetId.tokenId; + default: + throw new Error(`Unsupported asset: ${assetId.toString()}`); } - return assetId.tokenId; - default: - throw new Error(`Unsupported asset: ${assetId.toString()}`); } - } - getReferrerAddress(chain: Chain): string { - const referrers = getReferrerAddresses(); - switch (chain) { - case Chain.Ethereum: - case Chain.Base: - case Chain.Berachain: - case Chain.Hyperliquid: - case Chain.Manta: - case Chain.Mantle: - case Chain.SmartChain: - case Chain.Unichain: - return referrers.evm; - default: - throw new Error(`Unsupported chain: ${chain}`); + getReferrerAddress(chain: Chain): string { + const referrers = getReferrerAddresses(); + switch (chain) { + case Chain.Ethereum: + case Chain.Base: + case Chain.Berachain: + case Chain.Hyperliquid: + case Chain.Manta: + case Chain.Mantle: + case Chain.SmartChain: + case Chain.Unichain: + return referrers.evm; + default: + throw new Error(`Unsupported chain: ${chain}`); + } } - } + async get_quote(quoteRequest: QuoteRequest): Promise { + const fromAsset = AssetId.fromString(quoteRequest.from_asset.id); + const toAsset = AssetId.fromString(quoteRequest.to_asset.id); + const originChainId = this.mapChainToRelayChainId(fromAsset.chain); + const destinationChainId = this.mapChainToRelayChainId(toAsset.chain); - async get_quote(quoteRequest: QuoteRequest): Promise { - const fromAsset = AssetId.fromString(quoteRequest.from_asset.id); - const toAsset = AssetId.fromString(quoteRequest.to_asset.id); - const originChainId = this.mapChainToRelayChainId(fromAsset.chain); - const destinationChainId = this.mapChainToRelayChainId(toAsset.chain); - - const relayParams: RelayQuotePostBodyParams = { - user: quoteRequest.from_address, - amount: quoteRequest.from_value, - originCurrency: this.mapAssetIdToCurrency(fromAsset), - destinationCurrency: this.mapAssetIdToCurrency(toAsset), - originChainId: originChainId, - destinationChainId: destinationChainId, - recipient: quoteRequest.to_address, - tradeType: 'EXACT_INPUT', - appFee: [ - { - recipient: this.getReferrerAddress(fromAsset.chain), - fee: quoteRequest.referral_bps.toString(), - } - ], - refundTo: quoteRequest.from_address, - }; - - const relayResponse: RelayQuoteResponse = await fetchQuote(relayParams); + const relayParams: RelayQuotePostBodyParams = { + user: quoteRequest.from_address, + amount: quoteRequest.from_value, + originCurrency: this.mapAssetIdToCurrency(fromAsset), + destinationCurrency: this.mapAssetIdToCurrency(toAsset), + originChainId: originChainId, + destinationChainId: destinationChainId, + recipient: quoteRequest.to_address, + tradeType: "EXACT_INPUT", + appFee: [ + { + recipient: this.getReferrerAddress(fromAsset.chain), + fee: quoteRequest.referral_bps.toString(), + }, + ], + refundTo: quoteRequest.from_address, + }; - const outputValue = relayResponse.details.currencyOut.amount; - const outputMinValue = relayResponse.details.currencyOut.minimumAmount; + const relayResponse: RelayQuoteResponse = await fetchQuote(relayParams); - return { - quote: quoteRequest, - output_value: outputValue, - output_min_value: outputMinValue, - route_data: relayResponse.steps, - eta_in_seconds: relayResponse.details.timeEstimate || 0, - }; - } + const outputValue = relayResponse.details.currencyOut.amount; + const outputMinValue = relayResponse.details.currencyOut.minimumAmount; - async get_quote_data(quote: Quote): Promise { - if (!quote.route_data || !Array.isArray(quote.route_data)) { - throw new Error('RelayProvider: Invalid route_data structure'); + return { + quote: quoteRequest, + output_value: outputValue, + output_min_value: outputMinValue, + route_data: relayResponse.steps, + eta_in_seconds: relayResponse.details.timeEstimate || 0, + }; } - const steps = quote.route_data as Step[]; - // filter out approve step and get first transaction - const filtered = steps.filter(step => step.id !== 'approve' && step.kind === 'transaction'); - if (filtered.length === 0) { - throw new Error('RelayProvider: No steps found in quote data'); - } - const stepItems = filtered[0].items; - if (stepItems.length !== 1) { - throw new Error('RelayProvider: expect only one transaction in the step'); + + async get_quote_data(quote: Quote): Promise { + if (!quote.route_data || !Array.isArray(quote.route_data)) { + throw new Error("RelayProvider: Invalid route_data structure"); + } + const steps = quote.route_data as Step[]; + const filtered = steps.filter((step) => step.id !== "approve" && step.kind === "transaction"); + if (filtered.length === 0) { + throw new Error("RelayProvider: No steps found in quote data"); + } + const stepItems = filtered[0].items; + if (stepItems.length !== 1) { + throw new Error("RelayProvider: expect only one transaction in the step"); + } + const txData = stepItems[0].data; + return { + to: txData.to, + value: txData.value, + data: txData.data, + dataType: SwapQuoteDataType.Contract, + }; } - const txData = stepItems[0].data; - return { - to: txData.to, - value: txData.value, - data: txData.data, - dataType: SwapQuoteDataType.Contract, - }; - } } diff --git a/packages/swapper/src/stonfi/index.ts b/packages/swapper/src/stonfi/index.ts index 3cb9e54..b9835d5 100644 --- a/packages/swapper/src/stonfi/index.ts +++ b/packages/swapper/src/stonfi/index.ts @@ -1,7 +1,8 @@ -import { TonClient } from "@ton/ton"; -import { DEX, pTON } from "@ston-fi/sdk"; -import { StonApiClient } from '@ston-fi/api'; import { QuoteRequest, Quote, SwapQuoteData, AssetId, Chain, SwapQuoteDataType } from "@gemwallet/types"; +import { StonApiClient } from "@ston-fi/api"; +import { DEX, pTON } from "@ston-fi/sdk"; +import { TonClient } from "@ton/ton"; + import { Protocol } from "../protocol"; import { getReferrerAddresses } from "../referrer"; @@ -12,7 +13,7 @@ const PTON_VERSION_1 = "EQCM3B12QK1e4yZSf8GtBRT0aLMNyEsBc_DhVfRRtOEffLez"; const PTON_VERSION_2_1 = "EQBnGWMCf3-FZZq1W4IWcWiGAc3PHuZ0_H-7sad2oY00o83S"; function getTokenAddress(asset: AssetId): string { - return asset.isNative() ? TON_JETTON_ADDRESS : asset.tokenId ?? ''; + return asset.isNative() ? TON_JETTON_ADDRESS : (asset.tokenId ?? ""); } export class StonfiProvider implements Protocol { @@ -23,11 +24,11 @@ export class StonfiProvider implements Protocol { } async get_quote(quoteRequest: QuoteRequest): Promise { - const fromAsset = AssetId.fromString(quoteRequest.from_asset.id) - const toAsset = AssetId.fromString(quoteRequest.to_asset.id) + const fromAsset = AssetId.fromString(quoteRequest.from_asset.id); + const toAsset = AssetId.fromString(quoteRequest.to_asset.id); const referralAddress = getReferrerAddresses().ton; - if (fromAsset.chain != Chain.Ton || toAsset.chain != Chain.Ton) { + if (fromAsset.chain !== Chain.Ton || toAsset.chain !== Chain.Ton) { throw new Error("Only TON is supported"); } @@ -40,32 +41,30 @@ export class StonfiProvider implements Protocol { referralFeeBps: quoteRequest.referral_bps.toString(), }); - console.log("swapDirectSimulation", swapDirectSimulation); - return { quote: quoteRequest, output_value: swapDirectSimulation.askUnits, output_min_value: swapDirectSimulation.minAskUnits, route_data: {}, - eta_in_seconds: 3 // https://tonscan.org/blocks - } + eta_in_seconds: 3, // https://tonscan.org/blocks + }; } async get_quote_data(quote: Quote): Promise { - const fromAsset = AssetId.fromString(quote.quote.from_asset.id) - const toAsset = AssetId.fromString(quote.quote.to_asset.id) - const fromTokenAdddress = getTokenAddress(fromAsset) - const toTokenAddress = getTokenAddress(toAsset) - let routers = await client.getRouters(); - let pools = await client.getPoolsByAssetPair({ + const fromAsset = AssetId.fromString(quote.quote.from_asset.id); + const toAsset = AssetId.fromString(quote.quote.to_asset.id); + const fromTokenAdddress = getTokenAddress(fromAsset); + const toTokenAddress = getTokenAddress(toAsset); + const routers = await client.getRouters(); + const pools = await client.getPoolsByAssetPair({ asset0Address: fromTokenAdddress, - asset1Address: toTokenAddress + asset1Address: toTokenAddress, }); const pool = pools[0]; if (!pool) { throw new Error("No valid pools"); } - const router = routers.find(r => r.address === pool.routerAddress); + const router = routers.find((r) => r.address === pool.routerAddress); if (!router) { throw new Error("No matching router found"); } @@ -115,13 +114,13 @@ export class StonfiProvider implements Protocol { }); if (!params.body) { - throw new Error('Transaction body is required'); + throw new Error("Transaction body is required"); } return { to: params.to.toString(), value: params.value.toString(), - data: params.body.toBoc().toString('base64'), + data: params.body.toBoc().toString("base64"), dataType: SwapQuoteDataType.Contract, }; } else if (toAsset.isNative()) { @@ -136,13 +135,13 @@ export class StonfiProvider implements Protocol { }); if (!params.body) { - throw new Error('Transaction body is required'); + throw new Error("Transaction body is required"); } return { to: params.to.toString(), value: params.value.toString(), - data: params.body.toBoc().toString('base64'), + data: params.body.toBoc().toString("base64"), dataType: SwapQuoteDataType.Contract, }; } else { @@ -157,13 +156,13 @@ export class StonfiProvider implements Protocol { }); if (!params.body) { - throw new Error('Transaction body is required'); + throw new Error("Transaction body is required"); } return { to: params.to.toString(), value: params.value.toString(), - data: params.body.toBoc().toString('base64'), + data: params.body.toBoc().toString("base64"), dataType: SwapQuoteDataType.Contract, }; } diff --git a/packages/swapper/src/stonfi/integration.test.ts b/packages/swapper/src/stonfi/integration.test.ts index a0cbce7..0f08271 100644 --- a/packages/swapper/src/stonfi/integration.test.ts +++ b/packages/swapper/src/stonfi/integration.test.ts @@ -1,13 +1,12 @@ import { Chain, QuoteRequest } from "@gemwallet/types"; -import { StonfiProvider } from "./index"; import { TON_ASSET, USDT_TON_ASSET, createStonfiQuoteRequest } from "../testkit/mock"; +import { StonfiProvider } from "./index"; const runIntegration = process.env.STONFI_INTEGRATION_TEST === "1"; const describeIntegration = runIntegration ? describe : describe.skip; -const TON_RPC_ENDPOINT = - process.env.TON_URL || "https://toncenter.com"; +const TON_RPC_ENDPOINT = process.env.TON_URL || "https://toncenter.com"; // A valid TON wallet address for testing (TON Foundation address) const WALLET_ADDRESS = "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"; @@ -22,7 +21,7 @@ const REQUEST_TEMPLATE: QuoteRequest = createStonfiQuoteRequest({ slippage_bps: 100, }); -const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); describeIntegration("Stonfi live integration", () => { jest.setTimeout(60_000); diff --git a/packages/swapper/src/testkit/mock.ts b/packages/swapper/src/testkit/mock.ts index 6dac7d1..c433ee7 100644 --- a/packages/swapper/src/testkit/mock.ts +++ b/packages/swapper/src/testkit/mock.ts @@ -10,142 +10,142 @@ export const APTOS_USDC_FA = "0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591 export const APTOS_USDT_FA = "0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b"; export const SOL_ASSET = { - id: Chain.Solana, - symbol: "SOL", - decimals: 9, + id: Chain.Solana, + symbol: "SOL", + decimals: 9, }; export const USDC_SOL_ASSET = { - id: Chain.Solana, - symbol: "USDC", - decimals: 6, + id: Chain.Solana, + symbol: "USDC", + decimals: 6, }; export const TON_ASSET = { - id: Chain.Ton, - symbol: "TON", - decimals: 9, + id: Chain.Ton, + symbol: "TON", + decimals: 9, }; export const USDT_TON_ASSET = { - id: `${Chain.Ton}_EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs`, - symbol: "USDT", - decimals: 6, + id: `${Chain.Ton}_EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs`, + symbol: "USDT", + decimals: 6, }; export const ORCA_QUOTE_REQUEST_TEMPLATE: QuoteRequest = { - from_address: "A1testfromAddress1111111111111111111111111", - to_address: "A1testtoAddress11111111111111111111111111", - from_asset: SOL_ASSET, - to_asset: USDC_SOL_ASSET, - from_value: "1", - referral_bps: 0, - slippage_bps: 100, + from_address: "A1testfromAddress1111111111111111111111111", + to_address: "A1testtoAddress11111111111111111111111111", + from_asset: SOL_ASSET, + to_asset: USDC_SOL_ASSET, + from_value: "1", + referral_bps: 0, + slippage_bps: 100, }; export const STONFI_QUOTE_REQUEST_TEMPLATE: QuoteRequest = { - from_address: TON_TEST_WALLET_ADDRESS, - to_address: TON_TEST_DESTINATION_ADDRESS, - from_asset: TON_ASSET, - to_asset: USDT_TON_ASSET, - from_value: "1000000000", - referral_bps: 0, - slippage_bps: 100, + from_address: TON_TEST_WALLET_ADDRESS, + to_address: TON_TEST_DESTINATION_ADDRESS, + from_asset: TON_ASSET, + to_asset: USDT_TON_ASSET, + from_value: "1000000000", + referral_bps: 0, + slippage_bps: 100, }; export const OKX_SOLANA_USDC_REQUEST_TEMPLATE: QuoteRequest = { - from_address: SOLANA_TEST_WALLET_ADDRESS, - to_address: SOLANA_TEST_WALLET_ADDRESS, - from_asset: { - id: Chain.Solana, - symbol: "SOL", - decimals: 9, - }, - to_asset: { - id: `${Chain.Solana}_${SOLANA_USDC_MINT}`, - symbol: "USDC", - decimals: 6, - }, - from_value: "1000000", - referral_bps: 50, - slippage_bps: 100, + from_address: SOLANA_TEST_WALLET_ADDRESS, + to_address: SOLANA_TEST_WALLET_ADDRESS, + from_asset: { + id: Chain.Solana, + symbol: "SOL", + decimals: 9, + }, + to_asset: { + id: `${Chain.Solana}_${SOLANA_USDC_MINT}`, + symbol: "USDC", + decimals: 6, + }, + from_value: "1000000", + referral_bps: 50, + slippage_bps: 100, }; export const APTOS_USDC_REQUEST_TEMPLATE: QuoteRequest = { - from_address: APTOS_TEST_WALLET_ADDRESS, - to_address: APTOS_TEST_WALLET_ADDRESS, - from_asset: { - id: Chain.Aptos, - symbol: "APT", - decimals: 8, - }, - to_asset: { - id: `${Chain.Aptos}_${APTOS_USDC_FA}`, - symbol: "USDC", - decimals: 6, - }, - from_value: "1000000000", - referral_bps: 10, - slippage_bps: 100, -}; - -export function createQuoteRequest(base: QuoteRequest, overrides: Partial = {}): QuoteRequest { - return { - ...base, - ...overrides, + from_address: APTOS_TEST_WALLET_ADDRESS, + to_address: APTOS_TEST_WALLET_ADDRESS, from_asset: { - ...base.from_asset, - ...overrides.from_asset, + id: Chain.Aptos, + symbol: "APT", + decimals: 8, }, to_asset: { - ...base.to_asset, - ...overrides.to_asset, + id: `${Chain.Aptos}_${APTOS_USDC_FA}`, + symbol: "USDC", + decimals: 6, }, - }; + from_value: "1000000000", + referral_bps: 10, + slippage_bps: 100, +}; + +export function createQuoteRequest(base: QuoteRequest, overrides: Partial = {}): QuoteRequest { + return { + ...base, + ...overrides, + from_asset: { + ...base.from_asset, + ...overrides.from_asset, + }, + to_asset: { + ...base.to_asset, + ...overrides.to_asset, + }, + }; } export function buildQuoteFixture( - baseRequest: QuoteRequest, - requestOverrides: Partial = {}, - quoteOverrides: Partial> = {}, - etaInSeconds = 0, + baseRequest: QuoteRequest, + requestOverrides: Partial = {}, + quoteOverrides: Partial> = {}, + etaInSeconds = 0, ): Quote { - return { - quote: createQuoteRequest(baseRequest, requestOverrides), - output_value: "0", - output_min_value: "0", - eta_in_seconds: etaInSeconds, - route_data: {}, - ...quoteOverrides, - }; + return { + quote: createQuoteRequest(baseRequest, requestOverrides), + output_value: "0", + output_min_value: "0", + eta_in_seconds: etaInSeconds, + route_data: {}, + ...quoteOverrides, + }; } export function createOrcaQuoteRequest(overrides: Partial = {}): QuoteRequest { - return createQuoteRequest(ORCA_QUOTE_REQUEST_TEMPLATE, overrides); + return createQuoteRequest(ORCA_QUOTE_REQUEST_TEMPLATE, overrides); } export function buildOrcaQuoteFixture( - requestOverrides: Partial = {}, - quoteOverrides: Partial> = {}, + requestOverrides: Partial = {}, + quoteOverrides: Partial> = {}, ): Quote { - return buildQuoteFixture(ORCA_QUOTE_REQUEST_TEMPLATE, requestOverrides, quoteOverrides, 0); + return buildQuoteFixture(ORCA_QUOTE_REQUEST_TEMPLATE, requestOverrides, quoteOverrides, 0); } export function createStonfiQuoteRequest(overrides: Partial = {}): QuoteRequest { - return createQuoteRequest(STONFI_QUOTE_REQUEST_TEMPLATE, overrides); + return createQuoteRequest(STONFI_QUOTE_REQUEST_TEMPLATE, overrides); } export function buildStonfiQuoteFixture( - requestOverrides: Partial = {}, - quoteOverrides: Partial> = {}, + requestOverrides: Partial = {}, + quoteOverrides: Partial> = {}, ): Quote { - return buildQuoteFixture(STONFI_QUOTE_REQUEST_TEMPLATE, requestOverrides, quoteOverrides, 3); + return buildQuoteFixture(STONFI_QUOTE_REQUEST_TEMPLATE, requestOverrides, quoteOverrides, 3); } export function createSolanaUsdcQuoteRequest(overrides: Partial = {}): QuoteRequest { - return createQuoteRequest(OKX_SOLANA_USDC_REQUEST_TEMPLATE, overrides); + return createQuoteRequest(OKX_SOLANA_USDC_REQUEST_TEMPLATE, overrides); } export function createAptosUsdcQuoteRequest(overrides: Partial = {}): QuoteRequest { - return createQuoteRequest(APTOS_USDC_REQUEST_TEMPLATE, overrides); + return createQuoteRequest(APTOS_USDC_REQUEST_TEMPLATE, overrides); } diff --git a/packages/swapper/tsconfig.json b/packages/swapper/tsconfig.json index f427d21..3f4459f 100644 --- a/packages/swapper/tsconfig.json +++ b/packages/swapper/tsconfig.json @@ -1,25 +1,22 @@ { "compilerOptions": { - "target": "ES2016", - "module": "CommonJS", - "paths": { - "@gemwallet/types": ["../types/src/index.ts"] - }, - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "composite": true, + "target": "ES2016", + "module": "CommonJS", + "paths": { + "@gemwallet/types": ["../types/src/index.ts"] + }, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"], - "references": [ - { "path": "../types" } - ] - } - \ No newline at end of file + "references": [{ "path": "../types" }] +} diff --git a/packages/types/jest.config.js b/packages/types/jest.config.js index d573410..b4be920 100644 --- a/packages/types/jest.config.js +++ b/packages/types/jest.config.js @@ -1,19 +1,19 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - useESM: true, - tsconfig: 'tsconfig.json' - }, - ], - }, - extensionsToTreatAsEsm: ['.ts'], - moduleNameMapper: { - '^(\\.{1,2}/.*)\\.js$': '$1', - }, - testPathIgnorePatterns: ['/node_modules/', '/dist/'], -}; \ No newline at end of file + preset: "ts-jest", + testEnvironment: "node", + transform: { + "^.+\\.tsx?$": [ + "ts-jest", + { + useESM: true, + tsconfig: "tsconfig.json", + }, + ], + }, + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + testPathIgnorePatterns: ["/node_modules/", "/dist/"], +}; diff --git a/packages/types/package.json b/packages/types/package.json index df072a1..f77e871 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,20 +1,20 @@ { - "name": "@gemwallet/types", - "version": "1.0.0", - "description": "Shared TypeScript types for the monorepo", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "dev": "tsc -w --preserveWatchOutput", - "test": "jest --passWithNoTests" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": {}, - "devDependencies": {}, - "files": [ - "dist" - ] -} \ No newline at end of file + "name": "@gemwallet/types", + "version": "1.0.0", + "description": "Shared TypeScript types for the monorepo", + "keywords": [], + "license": "ISC", + "author": "", + "files": [ + "dist" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc -w --preserveWatchOutput", + "test": "jest --passWithNoTests" + }, + "dependencies": {}, + "devDependencies": {} +} diff --git a/packages/types/src/asset.test.ts b/packages/types/src/asset.test.ts index b98133f..fe060ad 100644 --- a/packages/types/src/asset.test.ts +++ b/packages/types/src/asset.test.ts @@ -1,74 +1,74 @@ -import { AssetId } from './asset'; -import { Chain } from './primitives'; +import { AssetId } from "./asset"; +import { Chain } from "./primitives"; -describe('Asset', () => { - describe('constructor', () => { - it('should create Asset with chain only', () => { +describe("Asset", () => { + describe("constructor", () => { + it("should create Asset with chain only", () => { const asset = new AssetId(Chain.Ton); expect(asset.chain).toBe(Chain.Ton); expect(asset.tokenId).toBeUndefined(); }); - it('should create Asset with chain and tokenId', () => { - const asset = new AssetId(Chain.Ton, 'TOKEN123'); + it("should create Asset with chain and tokenId", () => { + const asset = new AssetId(Chain.Ton, "TOKEN123"); expect(asset.chain).toBe(Chain.Ton); - expect(asset.tokenId).toBe('TOKEN123'); + expect(asset.tokenId).toBe("TOKEN123"); }); }); - describe('fromString', () => { - it('should parse native asset string', () => { - const asset = AssetId.fromString('ton'); + describe("fromString", () => { + it("should parse native asset string", () => { + const asset = AssetId.fromString("ton"); expect(asset.chain).toBe(Chain.Ton); expect(asset.tokenId).toBeUndefined(); }); - it('should parse token asset string', () => { - const asset = AssetId.fromString('ton_TOKEN123'); + it("should parse token asset string", () => { + const asset = AssetId.fromString("ton_TOKEN123"); expect(asset.chain).toBe(Chain.Ton); - expect(asset.tokenId).toBe('TOKEN123'); + expect(asset.tokenId).toBe("TOKEN123"); }); - it('should handle tokenId with underscores', () => { - const asset = AssetId.fromString('ton_TOKEN_123_ABC'); + it("should handle tokenId with underscores", () => { + const asset = AssetId.fromString("ton_TOKEN_123_ABC"); expect(asset.chain).toBe(Chain.Ton); - expect(asset.tokenId).toBe('TOKEN_123_ABC'); + expect(asset.tokenId).toBe("TOKEN_123_ABC"); }); }); - describe('toString', () => { - it('should convert native asset to string', () => { + describe("toString", () => { + it("should convert native asset to string", () => { const asset = new AssetId(Chain.Ton); - expect(asset.toString()).toBe('ton'); + expect(asset.toString()).toBe("ton"); }); - it('should convert token asset to string', () => { - const asset = new AssetId(Chain.Ton, 'TOKEN123'); - expect(asset.toString()).toBe('ton_TOKEN123'); + it("should convert token asset to string", () => { + const asset = new AssetId(Chain.Ton, "TOKEN123"); + expect(asset.toString()).toBe("ton_TOKEN123"); }); }); - describe('isNative', () => { - it('should return true for native asset', () => { + describe("isNative", () => { + it("should return true for native asset", () => { const asset = new AssetId(Chain.Ton); expect(asset.isNative()).toBe(true); }); - it('should return false for token asset', () => { - const asset = new AssetId(Chain.Ton, 'TOKEN123'); + it("should return false for token asset", () => { + const asset = new AssetId(Chain.Ton, "TOKEN123"); expect(asset.isNative()).toBe(false); }); }); - describe('toJSON', () => { - it('should serialize native asset', () => { + describe("toJSON", () => { + it("should serialize native asset", () => { const asset = new AssetId(Chain.Ton); - expect(asset.toJSON()).toBe('ton'); + expect(asset.toJSON()).toBe("ton"); }); - it('should serialize token asset', () => { - const asset = new AssetId(Chain.Ton, 'TOKEN123'); - expect(asset.toJSON()).toBe('ton_TOKEN123'); + it("should serialize token asset", () => { + const asset = new AssetId(Chain.Ton, "TOKEN123"); + expect(asset.toJSON()).toBe("ton_TOKEN123"); }); }); }); diff --git a/packages/types/src/asset.ts b/packages/types/src/asset.ts index 83b0a05..4d21585 100644 --- a/packages/types/src/asset.ts +++ b/packages/types/src/asset.ts @@ -1,14 +1,14 @@ -import { Chain } from './primitives'; +import { Chain } from "./primitives"; export class AssetId { constructor( public chain: Chain, - public tokenId?: string - ) { } + public tokenId?: string, + ) {} static fromString(asset: string): AssetId { - const [chain, ...rest] = asset.split('_'); - const tokenId = rest.length ? rest.join('_') : undefined; + const [chain, ...rest] = asset.split("_"); + const tokenId = rest.length ? rest.join("_") : undefined; return new AssetId(chain as Chain, tokenId); } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 99b7be0..fda6dea 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,11 +1,11 @@ -export * from './asset'; +export * from "./asset"; export { - Chain, - SwapProvider, - SwapperError, - QuoteAsset, - ProxyQuoteRequest as QuoteRequest, - ProxyQuote as Quote, - SwapQuoteData, - SwapQuoteDataType, -} from './primitives'; + Chain, + SwapProvider, + SwapperError, + QuoteAsset, + ProxyQuoteRequest as QuoteRequest, + ProxyQuote as Quote, + SwapQuoteData, + SwapQuoteDataType, +} from "./primitives"; diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index ed0b750..dcf4455 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -1,19 +1,18 @@ { "compilerOptions": { - "target": "ES2016", - "module": "CommonJS", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "composite": true, + "target": "ES2016", + "module": "CommonJS", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true }, "include": ["src/**/*"], // Include all files within the src directory. "exclude": ["node_modules", "dist"] // Exclude dependencies and the output directory. - } - \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bbc24f..8ddb095 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,15 @@ importers: jest: specifier: 30.2.0 version: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + knip: + specifier: 5.83.1 + version: 5.83.1(@types/node@25.2.3)(typescript@5.9.3) + oxfmt: + specifier: 0.33.0 + version: 0.33.0 + oxlint: + specifier: 1.48.0 + version: 1.48.0 ts-jest: specifier: 29.4.6 version: 29.4.6(@babel/core@7.28.4)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.4))(jest-util@30.2.0)(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3) @@ -349,11 +358,11 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@emnapi/core@1.6.0': - resolution: {integrity: sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} - '@emnapi/runtime@1.6.0': - resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} @@ -558,6 +567,9 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@noble/ciphers@1.2.1': resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} engines: {node: ^14.21.3 || >=16} @@ -601,6 +613,18 @@ packages: '@noble/secp256k1@1.7.1': resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@okx-dex/okx-dex-sdk@1.0.18': resolution: {integrity: sha512-KKsZIPqP2PZx5fg8J8SefBrJNl04cXaysJMyxtIC1XmY5CePYfisJUFxsFfnoXUjjFANSwqAic6WkLXNfq1QlA==} @@ -640,6 +664,358 @@ packages: peerDependencies: '@solana/kit': ^5.0.0 + '@oxc-resolver/binding-android-arm-eabi@11.17.1': + resolution: {integrity: sha512-+VuZyMYYaap5uDAU1xDU3Kul0FekLqpBS8kI5JozlWfYQKnc/HsZg2gHPkQrj0SC9lt74WMNCfOzZZJlYXSdEQ==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.17.1': + resolution: {integrity: sha512-YlDDTjvOEKhom/cRSVsXsMVeXVIAM9PJ/x2mfe08rfuS0iIEfJd8PngKbEIhG72WPxleUa+vkEZj9ncmC14z3Q==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.17.1': + resolution: {integrity: sha512-HOYYLSY4JDk14YkXaz/ApgJYhgDP4KsG8EZpgpOxdszGW9HmIMMY/vXqVKYW74dSH+GQkIXYxBrEh3nv+XODVg==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.17.1': + resolution: {integrity: sha512-JHPJbsa5HvPq2/RIdtGlqfaG9zV2WmgvHrKTYmlW0L5esqtKCBuetFudXTBzkNcyD69kSZLzH92AzTr6vFHMFg==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.17.1': + resolution: {integrity: sha512-UD1FRC8j8xZstFXYsXwQkNmmg7vUbee006IqxokwDUUA+xEgKZDpLhBEiVKM08Urb+bn7Q0gn6M1pyNR0ng5mg==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.17.1': + resolution: {integrity: sha512-wFWC1wyf2ROFWTxK5x0Enm++DSof3EBQ/ypyAesMDLiYxOOASDoMOZG1ylWUnlKaCt5W7eNOWOzABpdfFf/ssA==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.17.1': + resolution: {integrity: sha512-k/hUif0GEBk/csSqCfTPXb8AAVs1NNWCa/skBghvNbTtORcWfOVqJ3mM+2pE189+enRm4UnryLREu5ysI0kXEQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.17.1': + resolution: {integrity: sha512-Cwm6A071ww60QouJ9LoHAwBgEoZzHQ0Qaqk2E7WLfBdiQN9mLXIDhnrpn04hlRElRPhLiu/dtg+o5PPLvaINXQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.17.1': + resolution: {integrity: sha512-+hwlE2v3m0r3sk93SchJL1uyaKcPjf+NGO/TD2DZUDo+chXx7FfaEj0nUMewigSt7oZ2sQN9Z4NJOtUa75HE5Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.17.1': + resolution: {integrity: sha512-bO+rsaE5Ox8cFyeL5Ct5tzot1TnQpFa/Wmu5k+hqBYSH2dNVDGoi0NizBN5QV8kOIC6O5MZr81UG4yW/2FyDTA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.17.1': + resolution: {integrity: sha512-B/P+hxKQ1oX4YstI9Lyh4PGzqB87Ddqj/A4iyRBbPdXTcxa+WW3oRLx1CsJKLmHPdDk461Hmbghq1Bm3pl+8Aw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.17.1': + resolution: {integrity: sha512-ulp2H3bFXzd/th2maH+QNKj5qgOhJ3v9Yspdf1svTw3CDOuuTl6sRKsWQ7MUw0vnkSNvQndtflBwVXgzZvURsQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.17.1': + resolution: {integrity: sha512-LAXYVe3rKk09Zo9YKF2ZLBcH8sz8Oj+JIyiUxiHtq0hiYLMsN6dOpCf2hzQEjPAmsSEA/hdC1PVKeXo+oma8mQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.17.1': + resolution: {integrity: sha512-3RAhxipMKE8RCSPn7O//sj440i+cYTgYbapLeOoDvQEt6R1QcJjTsFgI4iz99FhVj3YbPxlZmcLB5VW+ipyRTA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.17.1': + resolution: {integrity: sha512-wpjMEubGU8r9VjZTLdZR3aPHaBqTl8Jl8F4DBbgNoZ+yhkhQD1/MGvY70v2TLnAI6kAHSvcqgfvaqKDa2iWsPQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.17.1': + resolution: {integrity: sha512-XIE4w17RYAVIgx+9Gs3deTREq5tsmalbatYOOBGNdH7n0DfTE600c7wYXsp7ANc3BPDXsInnOzXDEPCvO1F6cg==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.17.1': + resolution: {integrity: sha512-Lqi5BlHX3zS4bpSOkIbOKVf7DIk6Gvmdifr2OuOI58eUUyP944M8/OyaB09cNpPy9Vukj7nmmhOzj8pwLgAkIg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.17.1': + resolution: {integrity: sha512-l6lTcLBQVj1HNquFpXSsrkCIM8X5Hlng5YNQJrg00z/KyovvDV5l3OFhoRyZ+aLBQ74zUnMRaJZC7xcBnHyeNg==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-ia32-msvc@11.17.1': + resolution: {integrity: sha512-VTzVtfnCCsU/6GgvursWoyZrhe3Gj/RyXzDWmh4/U1Y3IW0u1FZbp+hCIlBL16pRPbDc5YvXVtCOnA41QOrOoQ==} + cpu: [ia32] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.17.1': + resolution: {integrity: sha512-jRPVU+6/12baj87q2+UGRh30FBVBzqKdJ7rP/mSqiL1kpNQB9yZ1j0+m3sru1m+C8hiFK7lBFwjUtYUBI7+UpQ==} + cpu: [x64] + os: [win32] + + '@oxfmt/binding-android-arm-eabi@0.33.0': + resolution: {integrity: sha512-ML6qRW8/HiBANteqfyFAR1Zu0VrJu+6o4gkPLsssq74hQ7wDMkufBYJXI16PGSERxEYNwKxO5fesCuMssgTv9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.33.0': + resolution: {integrity: sha512-WimmcyrGpTOntj7F7CO9RMssncOKYall93nBnzJbI2ZZDhVRuCkvFwTpwz80cZqwYm5udXRXfF40ZXcCxjp9jg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.33.0': + resolution: {integrity: sha512-PorspsX9O5ISstVaq34OK4esN0LVcuU4DVg+XuSqJsfJ//gn6z6WH2Tt7s0rTQaqEcp76g7+QdWQOmnJDZsEVg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.33.0': + resolution: {integrity: sha512-8278bqQtOcHRPhhzcqwN9KIideut+cftBjF8d2TOsSQrlsJSFx41wCCJ38mFmH9NOmU1M+x9jpeobHnbRP1okw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.33.0': + resolution: {integrity: sha512-BiqYVwWFHLf5dkfg0aCKsXa9rpi//vH1+xePCpd7Ulz9yp9pJKP4DWgS5g+OW8MaqOtt7iyAszhxtk/j1nDKHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.33.0': + resolution: {integrity: sha512-oAVmmurXx0OKbNOVv71oK92LsF1LwYWpnhDnX0VaAy/NLsCKf4B7Zo7lxkJh80nfhU20TibcdwYfoHVaqlStPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.33.0': + resolution: {integrity: sha512-YB6S8CiRol59oRxnuclJiWoV6l+l8ru/NsuQNYjXZnnPXfSTXKtMLWHCnL/figpCFYA1E7JyjrBbar1qxe2aZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.33.0': + resolution: {integrity: sha512-hrYy+FpWoB6N24E9oGRimhVkqlls9yeqcRmQakEPUHoAbij6rYxsHHYIp3+FHRiQZFAOUxWKn/CCQoy/Mv3Dgw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.33.0': + resolution: {integrity: sha512-O1YIzymGRdWj9cG5iVTjkP7zk9/hSaVN8ZEbqMnWZjLC1phXlv54cUvANGGXndgJp2JS4W9XENn7eo5I4jZueg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.33.0': + resolution: {integrity: sha512-2lrkNe+B0w1tCgQTaozfUNQCYMbqKKCGcnTDATmWCZzO77W2sh+3n04r1lk9Q1CK3bI+C3fPwhFPUR2X2BvlyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.33.0': + resolution: {integrity: sha512-8DSG1q0M6097vowHAkEyHnKed75/BWr1IBtgCJfytnWQg+Jn1X4DryhfjqonKZOZiv74oFQl5J8TCbdDuXXdtQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.33.0': + resolution: {integrity: sha512-eWaxnpPz7+p0QGUnw7GGviVBDOXabr6Cd0w7S/vnWTqQo9z1VroT7XXFnJEZ3dBwxMB9lphyuuYi/GLTCxqxlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.33.0': + resolution: {integrity: sha512-+mH8cQTqq+Tu2CdoB2/Wmk9CqotXResi+gPvXpb+AAUt/LiwpicTQqSolMheQKogkDTYHPuUiSN23QYmy7IXNQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.33.0': + resolution: {integrity: sha512-fjyslAYAPE2+B6Ckrs5LuDQ6lB1re5MumPnzefAXsen3JGwiRilra6XdjUmszTNoExJKbewoxxd6bcLSTpkAJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.33.0': + resolution: {integrity: sha512-ve/jGBlTt35Jl/I0A0SfCQX3wKnadzPDdyOFEwe2ZgHHIT9uhqhAv1PaVXTenSBpauICEWYH8mWy+ittzlVE/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.33.0': + resolution: {integrity: sha512-lsWRgY9e+uPvwXnuDiJkmJ2Zs3XwwaQkaALJ3/SXU9kjZP0Qh8/tGW8Tk/Z6WL32sDxx+aOK5HuU7qFY9dHJhg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.33.0': + resolution: {integrity: sha512-w8AQHyGDRZutxtQ7IURdBEddwFrtHQiG6+yIFpNJ4HiMyYEqeAWzwBQBfwSAxtSNh6Y9qqbbc1OM2mHN6AB3Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.33.0': + resolution: {integrity: sha512-j2X4iumKVwDzQtUx3JBDkaydx6eLuncgUZPl2ybZ8llxJMFbZIniws70FzUQePMfMtzLozIm7vo4bjkvQFsOzw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.33.0': + resolution: {integrity: sha512-lsBQxbepASwOBUh3chcKAjU+jVAQhLElbPYiagIq26cU8vA9Bttj6t20bMvCQCw31m440IRlNhrK7NpnUI8mzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.48.0': + resolution: {integrity: sha512-1Pz/stJvveO9ZO7ll4ZoEY3f6j2FiUgBLBcCRCiW6ylId9L9UKs+gn3X28m3eTnoiFCkhKwmJJ+VO6vwsu7Qtg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.48.0': + resolution: {integrity: sha512-Zc42RWGE8huo6Ht0lXKjd0NH2lWNmimQHUmD0JFcvShLOuwN+RSEE/kRakc2/0LIgOUuU/R7PaDMCOdQlPgNUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.48.0': + resolution: {integrity: sha512-jgZs563/4vaG5jH2RSt2TSh8A2jwsFdmhLXrElMdm3Mmto0HPf85FgInLSNi9HcwzQFvkYV8JofcoUg2GH1HTA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.48.0': + resolution: {integrity: sha512-kvo87BujEUjCJREuWDC4aPh1WoXCRFFWE4C7uF6wuoMw2f6N2hypA/cHHcYn9DdL8R2RrgUZPefC8JExyeIMKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.48.0': + resolution: {integrity: sha512-eyzzPaHQKn0RIM+ueDfgfJF2RU//Wp4oaKs2JVoVYcM5HjbCL36+O0S3wO5Xe1NWpcZIG3cEHc/SuOCDRqZDSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.48.0': + resolution: {integrity: sha512-p3kSloztK7GRO7FyO3u38UCjZxQTl92VaLDsMQAq0eGoiNmeeEF1KPeE4+Fr+LSkQhF8WvJKSuls6TwOlurdPA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.48.0': + resolution: {integrity: sha512-uWM+wiTqLW/V0ZmY/eyTWs8ykhIkzU+K2tz/8m35YepYEzohiUGRbnkpAFXj2ioXpQL+GUe5vmM3SLH6ozlfFw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.48.0': + resolution: {integrity: sha512-OhQNPjs/OICaYqxYJjKKMaIY7p3nJ9IirXcFoHKD+CQE1BZFCeUUAknMzUeLclDCfudH9Vb/UgjFm8+ZM5puAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.48.0': + resolution: {integrity: sha512-adu5txuwGvQ4C4fjYHJD+vnY+OCwCixBzn7J3KF3iWlVHBBImcosSv+Ye+fbMMJui4HGjifNXzonjKm9pXmOiw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.48.0': + resolution: {integrity: sha512-inlQQRUnHCny/7b7wA6NjEoJSSZPNea4qnDhWyeqBYWx8ukf2kzNDSiamfhOw6bfAYPm/PVlkVRYaNXQbkLeTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.48.0': + resolution: {integrity: sha512-YiJx6sW6bYebQDZRVWLKm/Drswx/hcjIgbLIhULSn0rRcBKc7d9V6mkqPjKDbhcxJgQD5Zi0yVccJiOdF40AWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.48.0': + resolution: {integrity: sha512-zwSqxMgmb2ITamNfDv9Q9EKBc/4ZhCBP9gkg2hhcgR6sEVGPUDl1AKPC89CBKMxkmPUi3685C38EvqtZn5OtHw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.48.0': + resolution: {integrity: sha512-c/+2oUWAOsQB5JTem0rW8ODlZllF6pAtGSGXoLSvPTonKI1vAwaKhD9Qw1X36jRbcI3Etkpu/9z/RRjMba8vFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.48.0': + resolution: {integrity: sha512-PhauDqeFW5DGed6QxCY5lXZYKSlcBdCXJnH03ZNU6QmDZ0BFM/zSy1oPT2MNb1Afx1G6yOOVk8ErjWsQ7c59ng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.48.0': + resolution: {integrity: sha512-6d7LIFFZGiavbHndhf1cK9kG9qmy2Dmr37sV9Ep7j3H+ciFdKSuOzdLh85mEUYMih+b+esMDlF5DU0WQRZPQjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.48.0': + resolution: {integrity: sha512-r+0KK9lK6vFp3tXAgDMOW32o12dxvKS3B9La1uYMGdWAMoSeu2RzG34KmzSpXu6MyLDl4aSVyZLFM8KGdEjwaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.48.0': + resolution: {integrity: sha512-Nkw/MocyT3HSp0OJsKPXrcbxZqSPMTYnLLfsqsoiFKoL1ppVNL65MFa7vuTxJehPlBkjy+95gUgacZtuNMECrg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.48.0': + resolution: {integrity: sha512-reO1SpefvRmeZSP+WeyWkQd1ArxxDD1MyKgMUKuB8lNuUoxk9QEohYtKnsfsxJuFwMT0JTr7p9wZjouA85GzGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.48.0': + resolution: {integrity: sha512-T6zwhfcsrorqAybkOglZdPkTLlEwipbtdO1qjE+flbawvwOMsISoyiuaa7vM7zEyfq1hmDvMq1ndvkYFioranA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@panoraexchange/swap-sdk@1.3.1': resolution: {integrity: sha512-3WQoFvWV/yVkcnRUwrkkW0yDoUTExN0dKABDcvKjoA+sqn1K07EFXQKRJNypKZMp0DsMSLLgjsikt2v224hdRg==} engines: {node: '>=20.0.0'} @@ -1408,6 +1784,9 @@ packages: argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + asmcrypto.js@2.3.2: resolution: {integrity: sha512-3FgFARf7RupsZETQ1nHnhLUUvpcttcCq1iZCaVAbJZbCZ5VNRrNyvpDyHTOb0KC3llFcsyOT/a99NZcCbeiEsA==} @@ -1942,6 +2321,10 @@ packages: resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} engines: {node: '> 0.1.90'} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -1951,9 +2334,15 @@ packages: fastestsmallesttextencoderdecoder@1.0.22: resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -1998,6 +2387,11 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -2414,6 +2808,10 @@ packages: node-notifier: optional: true + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} @@ -2430,6 +2828,10 @@ packages: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -2467,6 +2869,14 @@ packages: keyv@5.5.5: resolution: {integrity: sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==} + knip@5.83.1: + resolution: {integrity: sha512-av3ZG/Nui6S/BNL8Tmj12yGxYfTnwWnslouW97m40him7o8MwiMjZBY9TPvlEWUci45aVId0/HbgTwSKIDGpMw==} + engines: {node: '>=18.18.0'} + hasBin: true + peerDependencies: + '@types/node': '>=18' + typescript: 5.9.3 + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -2533,6 +2943,10 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + micro-ftch@0.3.1: resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} @@ -2677,6 +3091,24 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + oxc-resolver@11.17.1: + resolution: {integrity: sha512-pyRXK9kH81zKlirHufkFhOFBZRks8iAMLwPH8gU7lvKFiuzUH9L8MxDEllazwOb8fjXMcWjY1PMDfMJ2/yh5cw==} + + oxfmt@0.33.0: + resolution: {integrity: sha512-ogxBXA9R4BFeo8F1HeMIIxHr5kGnQwKTYZ5k131AEGOq1zLxInNhvYSpyRQ+xIXVMYfCN7yZHKff/lb5lp4auQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + oxlint@1.48.0: + resolution: {integrity: sha512-m5vyVBgPtPhVCJc3xI//8je9lRc8bYuYB4R/1PH3VPGOjA4vjVhkHtyJukdEjYEjwrw4Qf1eIf+pP9xvfhfMow==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.12.2' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + p-cancelable@4.0.1: resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==} engines: {node: '>=14.16'} @@ -2793,6 +3225,9 @@ packages: resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} engines: {node: '>=0.6'} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -2850,6 +3285,10 @@ packages: resolution: {integrity: sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==} engines: {node: '>=20'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -2870,6 +3309,9 @@ packages: rpc-websockets@9.1.1: resolution: {integrity: sha512-1IXGM/TfPT6nfYMIXkJdzn+L4JEsmb0FL1O2OBjaH03V3yuUDdKFulGLMFG6ErV+8pZ5HVC0limve01RyO+saA==} + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -2962,6 +3404,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + smol-toml@1.6.0: + resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} + engines: {node: '>= 18'} + source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -3033,6 +3479,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + superstruct@0.15.5: resolution: {integrity: sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==} @@ -3073,6 +3523,10 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -3267,6 +3721,10 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -3452,6 +3910,9 @@ packages: zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + snapshots: '@0no-co/graphql.web@1.1.2(graphql@16.11.0)': @@ -3729,13 +4190,13 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@emnapi/core@1.6.0': + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.6.0': + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 optional: true @@ -4097,8 +4558,15 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.6.0 - '@emnapi/runtime': 1.6.0 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -4134,6 +4602,18 @@ snapshots: '@noble/secp256k1@1.7.1': {} + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + '@okx-dex/okx-dex-sdk@1.0.18(bufferutil@4.0.9)(utf-8-validate@5.0.10)(zod@3.24.2)': dependencies: '@mysten/sui': 1.35.0(typescript@5.9.3) @@ -4240,6 +4720,182 @@ snapshots: - fastestsmallesttextencoderdecoder - typescript + '@oxc-resolver/binding-android-arm-eabi@11.17.1': + optional: true + + '@oxc-resolver/binding-android-arm64@11.17.1': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.17.1': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.17.1': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.17.1': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.17.1': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.17.1': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.17.1': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.17.1': + optional: true + + '@oxc-resolver/binding-win32-ia32-msvc@11.17.1': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.17.1': + optional: true + + '@oxfmt/binding-android-arm-eabi@0.33.0': + optional: true + + '@oxfmt/binding-android-arm64@0.33.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.33.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.33.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.33.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.33.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.33.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.33.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.33.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.33.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.33.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.33.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.33.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.33.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.33.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.33.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.33.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.33.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.33.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.48.0': + optional: true + + '@oxlint/binding-android-arm64@1.48.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.48.0': + optional: true + + '@oxlint/binding-darwin-x64@1.48.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.48.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.48.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.48.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.48.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.48.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.48.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.48.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.48.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.48.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.48.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.48.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.48.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.48.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.48.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.48.0': + optional: true + '@panoraexchange/swap-sdk@1.3.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@aptos-labs/ts-sdk': 5.2.1(got@14.6.6) @@ -5204,6 +5860,8 @@ snapshots: dependencies: sprintf-js: 1.0.3 + argparse@2.0.1: {} + asmcrypto.js@2.3.2: {} asynckit@0.4.0: {} @@ -5841,16 +6499,32 @@ snapshots: eyes@0.1.8: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-stable-stringify@1.0.0: {} fastestsmallesttextencoderdecoder@1.0.22: {} + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -5899,6 +6573,10 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -6533,6 +7211,8 @@ snapshots: - supports-color - ts-node + jiti@2.6.1: {} + js-base64@3.7.8: {} js-sha3@0.8.0: {} @@ -6546,6 +7226,10 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsesc@3.1.0: {} json-bigint@1.0.0: @@ -6574,6 +7258,23 @@ snapshots: dependencies: '@keyv/serialize': 1.1.1 + knip@5.83.1(@types/node@25.2.3)(typescript@5.9.3): + dependencies: + '@nodelib/fs.walk': 1.2.8 + '@types/node': 25.2.3 + fast-glob: 3.3.3 + formatly: 0.3.0 + jiti: 2.6.1 + js-yaml: 4.1.1 + minimist: 1.2.8 + oxc-resolver: 11.17.1 + picocolors: 1.1.1 + picomatch: 4.0.3 + smol-toml: 1.6.0 + strip-json-comments: 5.0.3 + typescript: 5.9.3 + zod: 4.3.6 + leven@3.1.0: {} lines-and-columns@1.2.4: {} @@ -6624,6 +7325,8 @@ snapshots: merge-stream@2.0.0: {} + merge2@1.4.1: {} + micro-ftch@0.3.1: {} micromatch@4.0.8: @@ -6731,6 +7434,75 @@ snapshots: dependencies: mimic-fn: 2.1.0 + oxc-resolver@11.17.1: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.17.1 + '@oxc-resolver/binding-android-arm64': 11.17.1 + '@oxc-resolver/binding-darwin-arm64': 11.17.1 + '@oxc-resolver/binding-darwin-x64': 11.17.1 + '@oxc-resolver/binding-freebsd-x64': 11.17.1 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.17.1 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.17.1 + '@oxc-resolver/binding-linux-arm64-gnu': 11.17.1 + '@oxc-resolver/binding-linux-arm64-musl': 11.17.1 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.17.1 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.17.1 + '@oxc-resolver/binding-linux-riscv64-musl': 11.17.1 + '@oxc-resolver/binding-linux-s390x-gnu': 11.17.1 + '@oxc-resolver/binding-linux-x64-gnu': 11.17.1 + '@oxc-resolver/binding-linux-x64-musl': 11.17.1 + '@oxc-resolver/binding-openharmony-arm64': 11.17.1 + '@oxc-resolver/binding-wasm32-wasi': 11.17.1 + '@oxc-resolver/binding-win32-arm64-msvc': 11.17.1 + '@oxc-resolver/binding-win32-ia32-msvc': 11.17.1 + '@oxc-resolver/binding-win32-x64-msvc': 11.17.1 + + oxfmt@0.33.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.33.0 + '@oxfmt/binding-android-arm64': 0.33.0 + '@oxfmt/binding-darwin-arm64': 0.33.0 + '@oxfmt/binding-darwin-x64': 0.33.0 + '@oxfmt/binding-freebsd-x64': 0.33.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.33.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.33.0 + '@oxfmt/binding-linux-arm64-gnu': 0.33.0 + '@oxfmt/binding-linux-arm64-musl': 0.33.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.33.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.33.0 + '@oxfmt/binding-linux-riscv64-musl': 0.33.0 + '@oxfmt/binding-linux-s390x-gnu': 0.33.0 + '@oxfmt/binding-linux-x64-gnu': 0.33.0 + '@oxfmt/binding-linux-x64-musl': 0.33.0 + '@oxfmt/binding-openharmony-arm64': 0.33.0 + '@oxfmt/binding-win32-arm64-msvc': 0.33.0 + '@oxfmt/binding-win32-ia32-msvc': 0.33.0 + '@oxfmt/binding-win32-x64-msvc': 0.33.0 + + oxlint@1.48.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.48.0 + '@oxlint/binding-android-arm64': 1.48.0 + '@oxlint/binding-darwin-arm64': 1.48.0 + '@oxlint/binding-darwin-x64': 1.48.0 + '@oxlint/binding-freebsd-x64': 1.48.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.48.0 + '@oxlint/binding-linux-arm-musleabihf': 1.48.0 + '@oxlint/binding-linux-arm64-gnu': 1.48.0 + '@oxlint/binding-linux-arm64-musl': 1.48.0 + '@oxlint/binding-linux-ppc64-gnu': 1.48.0 + '@oxlint/binding-linux-riscv64-gnu': 1.48.0 + '@oxlint/binding-linux-riscv64-musl': 1.48.0 + '@oxlint/binding-linux-s390x-gnu': 1.48.0 + '@oxlint/binding-linux-x64-gnu': 1.48.0 + '@oxlint/binding-linux-x64-musl': 1.48.0 + '@oxlint/binding-openharmony-arm64': 1.48.0 + '@oxlint/binding-win32-arm64-msvc': 1.48.0 + '@oxlint/binding-win32-ia32-msvc': 1.48.0 + '@oxlint/binding-win32-x64-msvc': 1.48.0 + p-cancelable@4.0.1: {} p-limit@2.3.0: @@ -6838,6 +7610,8 @@ snapshots: dependencies: side-channel: 1.1.0 + queue-microtask@1.2.3: {} + quick-lru@5.1.1: {} quick-lru@6.1.2: {} @@ -6897,6 +7671,8 @@ snapshots: dependencies: lowercase-keys: 3.0.0 + reusify@1.1.0: {} + rimraf@2.7.1: dependencies: glob: 7.2.3 @@ -6933,6 +7709,10 @@ snapshots: bufferutil: 4.0.9 utf-8-validate: 5.0.10 + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -7043,6 +7823,8 @@ snapshots: slash@3.0.0: {} + smol-toml@1.6.0: {} + source-map-support@0.5.13: dependencies: buffer-from: 1.1.2 @@ -7110,6 +7892,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + superstruct@0.15.5: {} superstruct@1.0.4: {} @@ -7142,6 +7926,8 @@ snapshots: through@2.3.8: {} + tinypool@2.1.0: {} + tmpl@1.0.5: {} to-buffer@1.2.2: @@ -7335,6 +8121,8 @@ snapshots: vary@1.1.2: {} + walk-up-path@4.0.0: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 @@ -7650,3 +8438,5 @@ snapshots: yocto-queue@0.1.0: {} zod@3.24.2: {} + + zod@4.3.6: {}