diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e5dfcb22..c10a19f7 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -205,7 +205,6 @@ Bridge hook internals are split by responsibility: - `bridge/hooks/approval.ts` resolves `onIntent` and `onAllowance` - `bridge/hooks/defaults.ts` provides default auto-accept behavior - `bridge/allowances/prepare.ts` handles paid ERC20 approval execution -- `bridge/allowances/prepare-swap-sbc.ts` handles swap-specific SBC approval fallback ## Transport (middleware + simulation) diff --git a/example/browser/package-lock.json b/example/browser/package-lock.json index 4ad36d62..c585249d 100644 --- a/example/browser/package-lock.json +++ b/example/browser/package-lock.json @@ -11,15 +11,12 @@ "@avail-project/nexus-core": "file:../..", "@coinbase/wallet-sdk": "^4.3.7", "@fontsource-variable/geist": "^5.2.8", - "@fontsource-variable/space-grotesk": "^5.2.10", "@fontsource/geist-mono": "^5.2.7", "@radix-ui/react-collapsible": "^1.1.13", "@radix-ui/react-dialog": "^1.1.16", "@radix-ui/react-hover-card": "^1.1.16", "@tanstack/react-query": "^5.101.0", "@walletconnect/ethereum-provider": "^2.23.9", - "canvas-confetti": "^1.9.4", - "clsx": "^2.1.1", "decimal.js": "10.6.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -30,7 +27,6 @@ "wagmi": "^3.6.16" }, "devDependencies": { - "@types/canvas-confetti": "^1.9.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^6.0.2", @@ -40,7 +36,7 @@ }, "../..": { "name": "@avail-project/nexus-core", - "version": "2.2.1", + "version": "2.2.2", "license": "MIT", "dependencies": { "@avail-project/nexus-types": "0.1.21", @@ -57,7 +53,7 @@ }, "devDependencies": { "@biomejs/biome": "2.3.8", - "@rollup/plugin-commonjs": "25.0.8", + "@rollup/plugin-commonjs": "29.0.3", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-node-resolve": "15.3.1", "@rollup/plugin-terser": "1.0.0", @@ -70,7 +66,6 @@ "rollup": "4.61.1", "rollup-plugin-dts": "6.3.0", "solc": "0.8.35", - "tsx": "4.22.4", "typescript": "5.9.3", "vitest": "4.1.8" }, @@ -324,15 +319,6 @@ "url": "https://github.com/sponsors/ayuhito" } }, - "node_modules/@fontsource-variable/space-grotesk": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@fontsource-variable/space-grotesk/-/space-grotesk-5.2.10.tgz", - "integrity": "sha512-yJQO/o35/hAP3CFnpdFTwQku2yzJOae2HIpBmqkOVoxhhXJaQP3g+b6Jrz7u+eI7A5ZdCIf88uMWpBJdFiGr5w==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, "node_modules/@fontsource/geist-mono": { "version": "5.2.8", "resolved": "https://registry.npmjs.org/@fontsource/geist-mono/-/geist-mono-5.2.8.tgz", @@ -3112,13 +3098,6 @@ "license": "0BSD", "optional": true }, - "node_modules/@types/canvas-confetti": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.9.0.tgz", - "integrity": "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -3964,16 +3943,6 @@ "node": ">=6" } }, - "node_modules/canvas-confetti": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.4.tgz", - "integrity": "sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==", - "license": "ISC", - "funding": { - "type": "donate", - "url": "https://www.paypal.me/kirilvatev" - } - }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", diff --git a/example/browser/package.json b/example/browser/package.json index 292722e1..3c75dc8b 100644 --- a/example/browser/package.json +++ b/example/browser/package.json @@ -12,15 +12,12 @@ "@avail-project/nexus-core": "file:../..", "@coinbase/wallet-sdk": "^4.3.7", "@fontsource-variable/geist": "^5.2.8", - "@fontsource-variable/space-grotesk": "^5.2.10", "@fontsource/geist-mono": "^5.2.7", "@radix-ui/react-collapsible": "^1.1.13", "@radix-ui/react-dialog": "^1.1.16", "@radix-ui/react-hover-card": "^1.1.16", "@tanstack/react-query": "^5.101.0", "@walletconnect/ethereum-provider": "^2.23.9", - "canvas-confetti": "^1.9.4", - "clsx": "^2.1.1", "decimal.js": "10.6.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -31,7 +28,6 @@ "wagmi": "^3.6.16" }, "devDependencies": { - "@types/canvas-confetti": "^1.9.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^6.0.2", diff --git a/example/browser/src/components/AaveMarketLink.tsx b/example/browser/src/components/AaveMarketLink.tsx deleted file mode 100644 index fa9bc457..00000000 --- a/example/browser/src/components/AaveMarketLink.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export function AaveMarketLink({ url }: { url: string }) { - return ( - - View on Aave - - - - - - - ); -} diff --git a/example/browser/src/components/BridgeResultCard.tsx b/example/browser/src/components/BridgeResultCard.tsx deleted file mode 100644 index ddee8acd..00000000 --- a/example/browser/src/components/BridgeResultCard.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import type { BridgeResultData } from "../lib/types"; - -type BridgeResultCardProps = { - result: BridgeResultData; - marketUrl?: string; -}; - -function LinkIcon({ type }: { type: BridgeResultData["links"][number]["icon"] }) { - if (type === "collection") { - return ( - - - - - - - ); - } - if (type === "fill") { - return ( - - - - ); - } - if (type === "intent") { - return ( - - - - - ); - } - if (type === "execute") { - return ( - - - - ); - } - return ( - - - - - ); -} - -function ExternalLinkIcon() { - return ( - - - - - - ); -} - -export function BridgeResultCard({ result, marketUrl }: BridgeResultCardProps) { - return ( -
-
{result.summary}
- -
- {result.links.map((link) => ( - - - {link.label} - - - ))} -
- - {marketUrl && ( - - View on Aave - - - )} -
- ); -} diff --git a/example/browser/src/components/ChevronIcon.tsx b/example/browser/src/components/ChevronIcon.tsx deleted file mode 100644 index 5a356199..00000000 --- a/example/browser/src/components/ChevronIcon.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export function ChevronIcon({ open }: { open: boolean }) { - return ( - - ); -} diff --git a/example/browser/src/components/StepBar.tsx b/example/browser/src/components/StepBar.tsx deleted file mode 100644 index 891a28a3..00000000 --- a/example/browser/src/components/StepBar.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import type { Phase, PhaseState } from "../lib/types"; - -function getPhaseState( - phases: Phase[], - phase: Phase, - started: boolean, - completedSteps: Set, -): PhaseState { - if (!started) return "idle"; - if (completedSteps.has(phase.doneWhen)) return "done"; - const idx = phases.findIndex((p) => p.key === phase.key); - const allPrevDone = phases.slice(0, idx).every((p) => completedSteps.has(p.doneWhen)); - return allPrevDone ? "active" : "idle"; -} - -export function StepBar({ - phases, - started, - completedSteps, -}: { - phases: Phase[]; - started: boolean; - completedSteps: Set; -}) { - if (!started) return null; - const allDone = phases.every((p) => completedSteps.has(p.doneWhen)); - return ( -
-
- {phases.map((p, i) => { - const state = getPhaseState(phases, p, started, completedSteps); - return ( -
-
-
- {state === "done" ? ( - - - - ) : state === "active" ? ( - - ) : ( - {i + 1} - )} -
- {p.label} -
- {i < phases.length - 1 && ( -
- )} -
- ); - })} -
-
- ); -} diff --git a/example/browser/src/components/SwapResultCard.tsx b/example/browser/src/components/SwapResultCard.tsx deleted file mode 100644 index 4f529308..00000000 --- a/example/browser/src/components/SwapResultCard.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import type { SwapResultData } from "../lib/types"; - -type SwapResultCardProps = { - result: SwapResultData; -}; - -function ExternalLinkIcon() { - return ( - - - - - - ); -} - -function StepIcon({ type }: { type: "source" | "bridge" | "destination" }) { - if (type === "source") { - return ( - - - - - ); - } - if (type === "bridge") { - return ( - - - - - ); - } - return ( - - - - ); -} - -function ArrowIcon() { - return ( - - - - - ); -} - -function truncateHash(hash: string): string { - if (!hash || hash.length <= 14) return hash; - return `${hash.slice(0, 8)}…${hash.slice(-6)}`; -} - -export function SwapResultCard({ result }: SwapResultCardProps) { - return ( -
-
{result.summary}
- -
- {result.route.map((step, i) => ( -
- {i > 0 && } -
- -
- {step.chainName} - {step.amount && ( - - {step.amount} {step.tokenSymbol} - - )} -
- {(step.txHash || step.explorerUrl) && ( - - {step.txHash ? truncateHash(step.txHash) : "View"} - - - )} -
-
- ))} -
- - {result.intentExplorerUrl && ( - - View intent on explorer - - - )} -
- ); -} diff --git a/example/browser/src/components/TxPanel.tsx b/example/browser/src/components/TxPanel.tsx deleted file mode 100644 index 41128ddb..00000000 --- a/example/browser/src/components/TxPanel.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { ReactNode } from "react"; - -type HashRecord = { - label: string; - value: string; - href?: string; -}; - -type TxPanelProps = { - title: string; - hashes: HashRecord[]; - action?: ReactNode; -}; - -export function TxPanel({ title, hashes, action }: TxPanelProps) { - if (hashes.length === 0 && !action) return null; - - return ( -
-
- {title} -
- {hashes.map((hash) => { - const display = - hash.value.startsWith("0x") && hash.value.length > 20 - ? `${hash.value.slice(0, 10)}…${hash.value.slice(-8)}` - : hash.value; - return ( -
- {hash.label} - {hash.href ? ( - - {display} - - ) : ( - {display} - )} -
- ); - })} - {action} -
- ); -} diff --git a/package-lock.json b/package-lock.json index b0c4c1e6..ef8d1d0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "rollup": "4.61.1", "rollup-plugin-dts": "6.3.0", "solc": "0.8.35", - "tsx": "4.22.4", "typescript": "5.9.3", "vitest": "4.1.8" }, diff --git a/package.json b/package.json index 9eec1ccf..98229d81 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,6 @@ "rollup": "4.61.1", "rollup-plugin-dts": "6.3.0", "solc": "0.8.35", - "tsx": "4.22.4", "typescript": "5.9.3", "vitest": "4.1.8" }, diff --git a/packages/tools/package-lock.json b/packages/tools/package-lock.json index bd85fc21..bfae9b72 100644 --- a/packages/tools/package-lock.json +++ b/packages/tools/package-lock.json @@ -14,13 +14,7 @@ "ansi-escapes": "^7.1.1", "cli-truncate": "^4.0.0", "ink": "^6.8.0", - "ink-divider": "^4.1.1", - "ink-scroll-view": "^0.3.5", - "ink-spinner": "^5.0.0", - "ink-table": "^3.1.0", - "ink-use-stdout-dimensions": "^1.0.5", - "react": "^19.2.0", - "string-width": "^7.2.0" + "react": "^19.2.0" }, "devDependencies": { "@types/node": "^22.18.10", @@ -679,18 +673,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -942,236 +924,6 @@ } } }, - "node_modules/ink-divider": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ink-divider/-/ink-divider-4.1.1.tgz", - "integrity": "sha512-zRRdotsjMddF7T98g7JlTd1aVvt6f2KN/t76tDXodXF0YMR0dbYqMmbrouiFMBGzCZxiSXRX6IBE4UwhZozFJw==", - "license": "MIT", - "dependencies": { - "ink": "^5.1.0" - } - }, - "node_modules/ink-divider/node_modules/@alcalzone/ansi-tokenize": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz", - "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^4.0.0" - }, - "engines": { - "node": ">=14.13.1" - } - }, - "node_modules/ink-divider/node_modules/ink": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ink/-/ink-5.2.1.tgz", - "integrity": "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg==", - "license": "MIT", - "dependencies": { - "@alcalzone/ansi-tokenize": "^0.1.3", - "ansi-escapes": "^7.0.0", - "ansi-styles": "^6.2.1", - "auto-bind": "^5.0.1", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "cli-cursor": "^4.0.0", - "cli-truncate": "^4.0.0", - "code-excerpt": "^4.0.0", - "es-toolkit": "^1.22.0", - "indent-string": "^5.0.0", - "is-in-ci": "^1.0.0", - "patch-console": "^2.0.0", - "react-reconciler": "^0.29.0", - "scheduler": "^0.23.0", - "signal-exit": "^3.0.7", - "slice-ansi": "^7.1.0", - "stack-utils": "^2.0.6", - "string-width": "^7.2.0", - "type-fest": "^4.27.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0", - "ws": "^8.18.0", - "yoga-layout": "~3.2.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "react": ">=18.0.0", - "react-devtools-core": "^4.19.1" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react-devtools-core": { - "optional": true - } - } - }, - "node_modules/ink-divider/node_modules/is-in-ci": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", - "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", - "license": "MIT", - "bin": { - "is-in-ci": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ink-divider/node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ink-divider/node_modules/react-reconciler": { - "version": "0.29.2", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", - "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/ink-divider/node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/ink-divider/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/ink-divider/node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ink-divider/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ink-divider/node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ink-scroll-view": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/ink-scroll-view/-/ink-scroll-view-0.3.6.tgz", - "integrity": "sha512-XqLkmFFCA5Ow2lVyvv7q0aVHzB78XojIM8gZMQ3//lvy8ZDpJsXWtTRwsCEsdQHnwvMCDCnKCzgF4xkkzoN8mQ==", - "license": "MIT", - "peerDependencies": { - "ink": "^5 || ^6", - "react": "^18 || ^19" - } - }, - "node_modules/ink-spinner": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ink-spinner/-/ink-spinner-5.0.0.tgz", - "integrity": "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA==", - "license": "MIT", - "dependencies": { - "cli-spinners": "^2.7.0" - }, - "engines": { - "node": ">=14.16" - }, - "peerDependencies": { - "ink": ">=4.0.0", - "react": ">=18.0.0" - } - }, - "node_modules/ink-table": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ink-table/-/ink-table-3.1.0.tgz", - "integrity": "sha512-qxVb4DIaEaJryvF9uZGydnmP9Hkmas3DCKVpEcBYC0E4eJd3qNgNe+PZKuzgCERFe9LfAS1TNWxCr9+AU4v3YA==", - "license": "MIT", - "dependencies": { - "object-hash": "^2.0.3" - }, - "peerDependencies": { - "ink": ">=3.0.0", - "react": ">=16.8.0" - } - }, - "node_modules/ink-use-stdout-dimensions": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ink-use-stdout-dimensions/-/ink-use-stdout-dimensions-1.0.5.tgz", - "integrity": "sha512-rVsqnw4tQEAJUoknU09+zHdDf30GJdkumkHr0iz/TOYMYEZJkYqziQSGJAM+Z+M603EDfO89+Nxyn/Ko2Zknfw==", - "license": "MIT", - "peerDependencies": { - "ink": ">=2.0.0", - "react": ">=16.0.0" - } - }, "node_modules/ink/node_modules/cli-truncate": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", @@ -1274,24 +1026,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -1301,15 +1035,6 @@ "node": ">=6" } }, - "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", diff --git a/packages/tools/package.json b/packages/tools/package.json index b2b67395..166cb748 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -15,13 +15,7 @@ "ansi-escapes": "^7.1.1", "cli-truncate": "^4.0.0", "ink": "^6.8.0", - "ink-divider": "^4.1.1", - "ink-scroll-view": "^0.3.5", - "ink-spinner": "^5.0.0", - "ink-table": "^3.1.0", - "ink-use-stdout-dimensions": "^1.0.5", - "react": "^19.2.0", - "string-width": "^7.2.0" + "react": "^19.2.0" }, "devDependencies": { "@types/node": "^22.18.10", diff --git a/packages/tools/src/e2e/balance-check.ts b/packages/tools/src/e2e/balance-check.ts index b2448680..58ec9b89 100644 --- a/packages/tools/src/e2e/balance-check.ts +++ b/packages/tools/src/e2e/balance-check.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises'; import process from 'node:process'; import Decimal from 'decimal.js'; -import * as sdkCore from '../../../../src/core/sdk'; +import * as sdkCore from '../../../../src/core/sdk/client'; import type { Chain, NexusNetwork, TokenBalance } from '../../../../src/domain/types'; import { normalizePrivateKey } from '../stress-test/private-key'; import { createPrivateKeyProvider } from '../stress-test/provider.node'; @@ -42,7 +42,7 @@ const getCreateNexusClient = () => { }; const fn = mod.createNexusClient ?? mod.default?.createNexusClient; if (typeof fn !== 'function') throw new Error('Failed to load createNexusClient from SDK.'); - return fn as typeof import('../../../../src/core/sdk').createNexusClient; + return fn as typeof import('../../../../src/core/sdk/client').createNexusClient; }; export const captureSnapshot = async (params: CaptureSnapshotParams): Promise => { diff --git a/packages/tools/src/e2e/check-settlements.ts b/packages/tools/src/e2e/check-settlements.ts index 448d0b1d..cfecd8c1 100644 --- a/packages/tools/src/e2e/check-settlements.ts +++ b/packages/tools/src/e2e/check-settlements.ts @@ -1,6 +1,6 @@ import process from 'node:process'; import { type Chain, createPublicClient, fallback, getAddress, http, parseAbiItem } from 'viem'; -import * as sdkCore from '../../../../src/core/sdk'; +import * as sdkCore from '../../../../src/core/sdk/client'; import type { NexusNetwork } from '../../../../src/domain/types'; import { runIfMain } from './cli-shim'; diff --git a/packages/tools/src/e2e/list-chains.ts b/packages/tools/src/e2e/list-chains.ts index 45a66581..2df5cd9f 100644 --- a/packages/tools/src/e2e/list-chains.ts +++ b/packages/tools/src/e2e/list-chains.ts @@ -1,5 +1,5 @@ import process from 'node:process'; -import * as sdkCore from '../../../../src/core/sdk'; +import * as sdkCore from '../../../../src/core/sdk/client'; import type { NexusNetwork } from '../../../../src/domain/types'; import type { ChainInfo } from './chain-select'; import { runIfMain } from './cli-shim'; @@ -11,7 +11,7 @@ const getCreateNexusClient = () => { }; const fn = mod.createNexusClient ?? mod.default?.createNexusClient; if (typeof fn !== 'function') throw new Error('Failed to load createNexusClient from SDK.'); - return fn as typeof import('../../../../src/core/sdk').createNexusClient; + return fn as typeof import('../../../../src/core/sdk/client').createNexusClient; }; export const listSupportedChains = async (network: NexusNetwork): Promise => { diff --git a/packages/tools/src/stress-test/cli.ts b/packages/tools/src/stress-test/cli.ts index bdd236a8..cc9e7ee2 100644 --- a/packages/tools/src/stress-test/cli.ts +++ b/packages/tools/src/stress-test/cli.ts @@ -4,7 +4,7 @@ import { dirname } from 'node:path'; import process from 'node:process'; import * as p from '@clack/prompts'; import { z } from 'zod'; -import * as sdkCore from '../../../../src/core/sdk'; +import * as sdkCore from '../../../../src/core/sdk/client'; import type { Chain, NexusNetwork } from '../../../../src/domain/types'; import { applyChainRpcOverrides, @@ -72,7 +72,7 @@ const getCreateNexusClient = () => { if (typeof fn !== 'function') { throw new Error('Failed to load createNexusClient from src/core/sdk at runtime.'); } - return fn as typeof import('../../../../src/core/sdk').createNexusClient; + return fn as typeof import('../../../../src/core/sdk/client').createNexusClient; }; const HELP_TEXT = `Usage @@ -1093,14 +1093,18 @@ const executeRun = async (cfg: ResolvedCliConfig) => { const eligibleDestinations = pickEligibleDestinations(supported, cfg.destinations, cfg.token); const total = cfg.runConfig.totalRequests; - const operations: Operation[] = Array.from({ length: total }, (_, idx) => ({ - id: idx + 1, - status: 'queued', - destinationChainId: - eligibleDestinations[Math.floor(Math.random() * eligibleDestinations.length)]!, - token: cfg.token, - amount: cfg.amount, - })); + const operations: Operation[] = Array.from({ length: total }, (_, idx) => { + const destinationChainId = + eligibleDestinations[Math.floor(Math.random() * eligibleDestinations.length)]; + if (destinationChainId === undefined) throw new Error('No eligible destination chains found.'); + return { + id: idx + 1, + status: 'queued', + destinationChainId, + token: cfg.token, + amount: cfg.amount, + }; + }); const startedAt = Date.now(); let stopRequested = false; diff --git a/packages/tools/src/stress-test/tui/components/ReportChains.tsx b/packages/tools/src/stress-test/tui/components/ReportChains.tsx deleted file mode 100644 index 0b05c527..00000000 --- a/packages/tools/src/stress-test/tui/components/ReportChains.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Box } from 'ink'; -import type { StressReport } from '../../types'; -import { activeTuiTheme as THEME } from '../theme'; -import { CardTitle, ChainTable } from './report-ui'; - -type Props = { - report: StressReport; -}; - -export default function ReportChains({ report }: Props) { - return ( - - - - - ); -} diff --git a/scripts/update-perf-baseline.ts b/scripts/update-perf-baseline.ts deleted file mode 100644 index b2688c59..00000000 --- a/scripts/update-perf-baseline.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { readFileSync, writeFileSync } from 'node:fs'; -import { resolve } from 'node:path'; -import { aggregateDurations } from '../src/analytics/aggregation'; - -type SpanSamples = Record; -type BaselineEntry = { p50: number; p95: number; p99: number }; -type BaselineMap = Record; - -const inputPath = process.argv[2]; -if (!inputPath) { - throw new Error('Usage: tsx scripts/update-perf-baseline.ts '); -} - -const sourcePath = resolve(process.cwd(), inputPath); -const raw = readFileSync(sourcePath, 'utf-8'); -const input = JSON.parse(raw) as SpanSamples; - -const output: BaselineMap = {}; -for (const [spanName, durations] of Object.entries(input)) { - const summary = aggregateDurations(durations); - output[spanName] = { - p50: summary.p50, - p95: summary.p95, - p99: summary.p99, - }; -} - -const baselinePath = resolve(process.cwd(), 'tests/performance/baselines/current.json'); -writeFileSync(baselinePath, `${JSON.stringify(output, null, 2)}\n`, 'utf-8'); diff --git a/src/abi/misc.ts b/src/abi/misc.ts deleted file mode 100644 index a6c136ed..00000000 --- a/src/abi/misc.ts +++ /dev/null @@ -1,16 +0,0 @@ -const ApproveABI = [ - { - constant: false, - inputs: [ - { name: '_spender', type: 'address' }, - { name: '_value', type: 'uint256' }, - ], - name: 'approve', - outputs: [{ name: '', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, -] as const; - -export { ApproveABI }; diff --git a/src/analytics/aggregation.ts b/src/analytics/aggregation.ts deleted file mode 100644 index 77899805..00000000 --- a/src/analytics/aggregation.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { groupBy, mapValues, sum } from 'es-toolkit'; -import type { SpanProperties } from './types'; - -export interface PerformanceAggregates { - count: number; - min: number; - max: number; - mean: number; - p50: number; - p90: number; - p95: number; - p99: number; -} - -export const computePercentile = (sorted: number[], percentile: number): number => { - if (sorted.length === 0) return 0; - if (percentile <= 0) return sorted[0] ?? 0; - if (percentile >= 100) return sorted[sorted.length - 1] ?? 0; - - const rank = (percentile / 100) * (sorted.length - 1); - const lower = Math.floor(rank); - const upper = Math.ceil(rank); - const weight = rank - lower; - const lowerValue = sorted[lower] ?? 0; - const upperValue = sorted[upper] ?? 0; - return lowerValue + (upperValue - lowerValue) * weight; -}; - -export const aggregateDurations = (durations: number[]): PerformanceAggregates => { - if (durations.length === 0) { - return { - count: 0, - min: 0, - max: 0, - mean: 0, - p50: 0, - p90: 0, - p95: 0, - p99: 0, - }; - } - - const sorted = [...durations].sort((a, b) => a - b); - const total = sum(sorted); - return { - count: sorted.length, - min: sorted[0] ?? 0, - max: sorted[sorted.length - 1] ?? 0, - mean: total / sorted.length, - p50: computePercentile(sorted, 50), - p90: computePercentile(sorted, 90), - p95: computePercentile(sorted, 95), - p99: computePercentile(sorted, 99), - }; -}; - -export const aggregateBySpanName = ( - spans: SpanProperties[] -): Map => { - const groupedSpans = groupBy(spans, (span) => span.operation); - const aggregates = mapValues(groupedSpans, (entries) => - aggregateDurations(entries.map((entry) => entry.duration)) - ); - return new Map(Object.entries(aggregates)); -}; diff --git a/src/analytics/index.ts b/src/analytics/index.ts deleted file mode 100644 index 6cb981b6..00000000 --- a/src/analytics/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Analytics module for Nexus SDK - * Provides comprehensive tracking of SDK operations and user behavior - */ - -// Main analytics manager -export { AnalyticsManager } from './AnalyticsManager'; -export { - aggregateBySpanName, - aggregateDurations, - computePercentile, - type PerformanceAggregates, -} from './aggregation'; -export type { NexusAnalyticsEvent } from './events'; -// Event constants -export { NexusAnalyticsEvents } from './events'; -export { installAxiosNetworkTiming } from './network-timing'; -export { PerformanceTracker } from './performance'; -export type { AnalyticsProvider as IAnalyticsProvider } from './providers/AnalyticsProvider'; -export { NoOpProvider } from './providers/NoOpProvider'; -// Providers (for advanced usage) -export { PostHogProvider } from './providers/PostHogProvider'; - -// Utilities (for advanced usage) -export { SessionManager } from './session'; -// Types -export type { - AnalyticsConfig, - AnalyticsProvider, - BalanceProperties, - BaseEventProperties, - DevTimingConfig, - ErrorProperties, - OperationContext, - PerformanceProperties, - SessionProperties, - SpanContext, - SpanId, - SpanProperties, - SpanTags, - TransactionProperties, - WalletProperties, -} from './types'; -export { - calculateUsdValue, - extractBreakdownStats, - extractBridgeProperties, - extractIntentProperties, - getWalletType, - sanitizeUrl, -} from './utils'; diff --git a/src/bridge/allowances/prepare-swap-sbc.ts b/src/bridge/allowances/prepare-swap-sbc.ts deleted file mode 100644 index 1523f816..00000000 --- a/src/bridge/allowances/prepare-swap-sbc.ts +++ /dev/null @@ -1,134 +0,0 @@ -import type { PrivateKeyAccount, WalletClient } from 'viem'; -import type { AllowanceHookSource, BridgeIntentDraft, ChainListType } from '../../domain'; -import { ERROR_CODES, Errors, ExecutionError } from '../../domain/errors'; -import { - type AllowanceExecutionInput, - executeAllowances, - resolveAllowanceInputs, -} from '../../services/allowances'; -import { waitForTxReceiptByChain } from '../../services/evm'; -import { equalFold } from '../../services/strings'; -import type { PublicClientList } from '../../swap/types'; -import type { SwapCache } from '../../swap/wallet/cache'; -import { chainSupports7702 } from '../../swap/wallet/capabilities'; -import type { - MiddlewareApprovalCreatorClient, - MiddlewareSbcSubmitterClient, -} from '../../transport'; - -export const prepareSwapBridgeExecution = async ( - intent: BridgeIntentDraft, - options: { - allowanceSelections: Array<'max' | 'min' | bigint | string>; - insufficientAllowanceSources: AllowanceHookSource[]; - middlewareClient: MiddlewareSbcSubmitterClient & MiddlewareApprovalCreatorClient; - ephemeralWallet: PrivateKeyAccount; - eoaAddress?: `0x${string}`; - eoaWallet?: WalletClient; - chainList: ChainListType; - publicClientList: PublicClientList; - cache: SwapCache | undefined; - } -): Promise => { - const { - allowanceSelections, - insufficientAllowanceSources, - middlewareClient, - ephemeralWallet, - eoaAddress, - eoaWallet, - chainList, - publicClientList, - } = options; - - if (insufficientAllowanceSources.length === 0) { - return; - } - - const resolved = resolveAllowanceInputs({ - sources: insufficientAllowanceSources, - allowances: allowanceSelections, - }); - - const ephemeralApprovals = resolved.filter( - (source) => !source.ownerAddress || equalFold(source.ownerAddress, ephemeralWallet.address) - ); - const eoaApprovals = resolved.filter( - (source) => source.ownerAddress && !equalFold(source.ownerAddress, ephemeralWallet.address) - ); - - const { createSBCTxFromCalls, requireSuccessfulSbcResult } = await import('../../services/sbc'); - const { encodeFunctionData, erc20Abi } = await import('viem'); - - const byChain = new Map(); - for (const r of ephemeralApprovals) { - // Non-7702 chains have no Calibur delegation, so SBC-based pre-approvals don't apply. The - // v1-style Safe deposit batch grants vault allowance via permit just-in-time, so the - // pre-pass is redundant there. Skip non-7702 chains entirely. - const chain = chainList.getChainByID(r.chainID); - if (!chainSupports7702(chain)) continue; - let bucket = byChain.get(r.chainID); - if (!bucket) { - bucket = []; - byChain.set(r.chainID, bucket); - } - bucket.push(r); - } - - for (const [chainId, chainSources] of byChain) { - const publicClient = publicClientList.get(chainId); - const calls = chainSources.map((s) => ({ - to: s.tokenContract, - data: encodeFunctionData({ - abi: erc20Abi, - functionName: 'approve', - args: [chainList.getVaultContractAddress(chainId), s.amount], - }), - value: 0n, - })); - - const sbcTx = await createSBCTxFromCalls({ - calls, - chainID: chainId, - ephemeralAddress: ephemeralWallet.address, - ephemeralWallet, - publicClient, - }); - - const results = await middlewareClient.submitSBCs([sbcTx]); - const txHash = requireSuccessfulSbcResult( - results, - chainId, - 'Swap bridge approval SBC submission' - ); - const [, error] = await waitForTxReceiptByChain(txHash, publicClient, chainId); - if (error) { - throw new ExecutionError( - ERROR_CODES.EXEC_TX_ONCHAIN_REVERTED, - `Swap bridge approval reverted on chain ${chainId}`, - { context: { service: 'rpc', chainId }, details: { txHash } } - ); - } - } - - if (eoaApprovals.length > 0) { - if (!eoaAddress || !eoaWallet) { - throw Errors.internal('EOA bridge approvals require eoaAddress and eoaWallet'); - } - - const dstChain = chainList.getChainByID(intent.destination.chain.id); - const allowanceOptions: AllowanceExecutionInput['options'] = { - evm: { - address: eoaAddress, - client: eoaWallet, - }, - chainList, - middlewareClient, - }; - await executeAllowances({ - sources: eoaApprovals, - options: allowanceOptions, - dstChain, - }); - } -}; diff --git a/src/bridge/bridge.md b/src/bridge/bridge.md index 55796a3d..020d0e62 100644 --- a/src/bridge/bridge.md +++ b/src/bridge/bridge.md @@ -33,7 +33,6 @@ Data shapes, wire formats, and rationale stay as prose / structs / tables. | Allowance detection (`findInsufficientAllowanceSources`) | `tests/flows/bridge-intent-builder.test.ts` | | Hooks (intent approve/deny/refresh, allowance selection) | `tests/flows/bridge-hooks.test.ts` | | Full pipeline (build→preview→hook→execute→events) | `tests/flows/characterization/bridge-pipeline.test.ts` | -| Sponsored allowance SBC (bridge‑in‑swap path) | `tests/bridge/allowances/prepare-swap-sbc-7702.test.ts` | | Transport (`submitRFF`, `getMayanQuotes`, `reportMayanNativeTx`) | `tests/transport/middleware.test.ts` | --- diff --git a/src/bridge/types.ts b/src/bridge/types.ts index 812e7e62..879a4f8c 100644 --- a/src/bridge/types.ts +++ b/src/bridge/types.ts @@ -4,7 +4,6 @@ import type { BridgeEvent, BridgeIntent, BridgeOptions, - BridgeResult, Chain, SourceTxs, TokenInfo, @@ -55,5 +54,3 @@ export type BridgeFlowOptions = { emit?: (event: BridgeEvent) => void; fillTimeoutMinutes?: number; }; - -export type BridgeFlowResult = BridgeResult; diff --git a/src/core/sdk.ts b/src/core/sdk.ts deleted file mode 100644 index ed2afb16..00000000 --- a/src/core/sdk.ts +++ /dev/null @@ -1 +0,0 @@ -export { createNexusClient } from './sdk/client'; diff --git a/src/domain/constants/index.ts b/src/domain/constants/index.ts deleted file mode 100644 index 9b1d5956..00000000 --- a/src/domain/constants/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './addresses'; -export * from './intent'; diff --git a/src/domain/errors.ts b/src/domain/errors.ts index d3301ce4..254af638 100644 --- a/src/domain/errors.ts +++ b/src/domain/errors.ts @@ -50,7 +50,6 @@ export type OperationName = // ── Service union per category ──────────────────────────────────────────────── -export type ValidationService = never; export type UserActionService = 'wallet' | 'hook'; export type SimulationService = 'rpc'; export type ExecutionService = 'wallet' | 'rpc'; @@ -63,8 +62,6 @@ export type ExternalServiceService = | 'mystic' | 'relay' | 'coinbase'; -export type InternalService = never; - /** * Maps a category literal to its allowed `service` values. Used by `ErrorContext` * and the wrap helpers so TypeScript rejects mismatches (e.g. `service: 'middleware'` diff --git a/src/domain/index.ts b/src/domain/index.ts index ca84abf3..8359e524 100644 --- a/src/domain/index.ts +++ b/src/domain/index.ts @@ -1,9 +1,12 @@ export * from './chain-abstraction'; -export * from './constants'; +export * from './constants/addresses'; +export * from './constants/intent'; export * from './errors'; export * from './permits'; export * from './types'; export * from './types/contract-types'; export * from './types/deployment-types'; export * from './types/middleware-error'; -export * from './utils'; +export * from './utils/format'; +export * from './utils/logger'; +export * from './utils/validation'; diff --git a/src/domain/types/contract-types.ts b/src/domain/types/contract-types.ts index e7ad3482..f6081461 100644 --- a/src/domain/types/contract-types.ts +++ b/src/domain/types/contract-types.ts @@ -6,13 +6,6 @@ export type GetAllowanceParams = { owner: Hex; }; -export type SetAllowanceParams = { - contractAddress: Hex; - spender: Hex; - owner: Hex; - amount: bigint; -}; - // ABI-compatible Vault.Request payload for EVM deposits. export type VaultSourcePair = { universe: number; diff --git a/src/domain/types/index.ts b/src/domain/types/index.ts index 783d5494..593b90b4 100644 --- a/src/domain/types/index.ts +++ b/src/domain/types/index.ts @@ -128,20 +128,6 @@ export interface TokenMetadata { isNative?: boolean; } -/** - * Generic event listener type for CA SDK events - */ -export type EventListener = (...args: unknown[]) => void; - -/** - * Response structure for token allowance. - */ -export interface AllowanceResponse { - chainID: number; - allowance: bigint; - token: string; -} - /** * Parameters for bridging tokens between chains. */ @@ -422,13 +408,6 @@ type BridgeIntentDraftSource = { mayanQuote?: MayanQuote; }; -export type IntentSourceForAllowance = { - chainID: number; - currentAllowance: bigint; - requiredAllowance: bigint; - token: TokenInfo; -}; - export type NetworkConfig = { MIDDLEWARE_HTTP_URL: string; INTENT_EXPLORER_URL: string; diff --git a/src/domain/utils/index.ts b/src/domain/utils/index.ts deleted file mode 100644 index eebe335d..00000000 --- a/src/domain/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './format'; -export * from './logger'; -export * from './validation'; diff --git a/src/index.ts b/src/index.ts index 56a2f124..20e57b23 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ export { AnalyticsManager } from './analytics/AnalyticsManager'; export { NexusAnalyticsEvents } from './analytics/events'; export type { AnalyticsConfig, DevTimingConfig, SpanProperties } from './analytics/types'; -export { createNexusClient } from './core/sdk'; +export { createNexusClient } from './core/sdk/client'; export type { NexusClient } from './core/types'; export { getFallbackTokenLogoDataUri } from './services/token-logo'; diff --git a/src/services/balances.ts b/src/services/balances.ts index 19f917c5..a9163c16 100644 --- a/src/services/balances.ts +++ b/src/services/balances.ts @@ -518,5 +518,4 @@ export const createUserAssets = (data: TokenBalance[]) => { }; }; -export type UserAssetInstance = ReturnType; export type UserAssetsInstance = ReturnType; diff --git a/src/services/evm.ts b/src/services/evm.ts index e0835b90..a1b24026 100644 --- a/src/services/evm.ts +++ b/src/services/evm.ts @@ -245,14 +245,6 @@ export const createPublicClientWithFallback = (chain: Chain): PublicClient => { }); }; -export const getPctGasBufferByChain = (chainId: number) => { - if (chainId === GAS_ORACLE_CHAIN_IDS.ARBITRUM) { - return 1; - } - - return 0.5; -}; - export const packERC20Approve = (spender: Hex, amount: bigint) => { return encodeFunctionData({ abi: ERC20ABI, diff --git a/src/services/fee-estimation.ts b/src/services/fee-estimation.ts index 6689722d..632df422 100644 --- a/src/services/fee-estimation.ts +++ b/src/services/fee-estimation.ts @@ -255,64 +255,3 @@ export async function estimateTotalFee( ); return result; } - -export async function validateBalance( - client: PublicClient, - chainId: number, - userAddress: `0x${string}`, - items: TxWithGas[], - priceTier: PriceTier = 'medium', - bufferOverrides?: Partial -): Promise< - | { ok: true; prepared: PreparedTx[]; fees: FeeEstimate[] } - | { ok: false; deficit: bigint; required: bigint; balance: bigint } -> { - const [fees, balance] = await Promise.all([ - estimateTotalFees(client, chainId, items, priceTier, bufferOverrides), - client.getBalance({ address: userAddress }), - ]); - - const totalMaxCost = fees.reduce((sum, fee) => sum + fee.recommended.totalMaxCost, 0n); - const totalValue = items.reduce((sum, item) => sum + (item.tx.value ?? 0n), 0n); - const required = totalMaxCost + totalValue; - - if (balance < required) { - return { - ok: false, - deficit: required - balance, - required, - balance, - }; - } - - const prepared = items.map((item, i) => { - const recommendation = fees[i].recommended; - const base = { - to: item.tx.to, - data: item.tx.data, - value: item.tx.value, - gas: recommendation.gasLimit, - }; - - if (recommendation.useLegacyPricing) { - return { ...base, gasPrice: recommendation.maxFeePerGas } as PreparedTx; - } - - return { - ...base, - maxFeePerGas: recommendation.maxFeePerGas, - maxPriorityFeePerGas: recommendation.maxPriorityFeePerGas, - } as PreparedTx; - }); - - return { ok: true, prepared, fees }; -} - -type PreparedTx = TxRequest & { gas: bigint } & ( - | { gasPrice: bigint } - | { maxFeePerGas: bigint; maxPriorityFeePerGas: bigint } - ); - -export function hasFeeModel(chainId: number): boolean { - return chainId in CHAIN_FEE_MODEL; -} diff --git a/src/services/math.ts b/src/services/math.ts index abe3e5ad..3656447c 100644 --- a/src/services/math.ts +++ b/src/services/math.ts @@ -24,10 +24,3 @@ export const divDecimals = (input: bigint | number | string, decimals: number) = export const mulDecimals = (input: Decimal | number | string, decimals: number) => { return BigInt(new Decimal(input).mul(Decimal.pow(10, decimals)).toFixed(0, Decimal.ROUND_CEIL)); }; - -export function pctAdditionWithSuggestion(base: bigint, percentage: number) { - const pctAmount = BigInt(new Decimal(base).mul(percentage).toFixed(0, Decimal.ROUND_CEIL)); - const value = base + pctAmount; - const reducedValue = base + (pctAmount * 70n) / 100n; - return [reducedValue, value]; -} diff --git a/src/services/platform.ts b/src/services/platform.ts index 26c9b02e..01b83f59 100644 --- a/src/services/platform.ts +++ b/src/services/platform.ts @@ -39,14 +39,6 @@ export const cryptoGetRandomValues = async ( return window.crypto.getRandomValues(bytes); }; -export const locationProtocol = (): string => { - if (typeof window === 'undefined') { - return 'https'; - } - - return window.location.protocol; -}; - export const locationHost = (): string => { if (typeof window === 'undefined') { return 'localhost'; diff --git a/src/services/safe.ts b/src/services/safe.ts index f1a739a0..bcbaddd0 100644 --- a/src/services/safe.ts +++ b/src/services/safe.ts @@ -5,7 +5,6 @@ import { type PrivateKeyAccount, type PublicClient, toHex, - zeroAddress, } from 'viem'; import { Errors } from '../domain/errors'; import { safeExecTransactionAbi, safeNonceAbi } from '../swap/safe/abis'; @@ -255,10 +254,3 @@ export async function ensureSafeForEphemeral(input: { signature, }); } - -export const __testing = { - HEX32_ZERO, - buildFieldsForCalls, - readSafeNonce, - zeroAddress, -}; diff --git a/src/services/telemetry.ts b/src/services/telemetry.ts index 978a3121..75bbb213 100644 --- a/src/services/telemetry.ts +++ b/src/services/telemetry.ts @@ -75,7 +75,6 @@ const setLoggerProvider = async (networkConfig: NetworkConfig): Promise => // and clear the lock so a future call can retry. The promise itself still // resolves successfully — `await setLoggerProvider(...)` from `initialize()` // or `setEVMProvider()` won't reject just because OTel setup blew up. - // biome-ignore lint/suspicious/noConsole: production fallback for telemetry failure console.error('Telemetry init failed; SDK will run without OTel emission', err); initInFlight = null; } diff --git a/src/services/time.ts b/src/services/time.ts index 746b0847..3f4bce3a 100644 --- a/src/services/time.ts +++ b/src/services/time.ts @@ -1,6 +1 @@ export const minutesToMs = (min: number) => min * 60 * 1000; - -export const createDeadlineFromNow = (minutes = 3n): bigint => { - const nowInSeconds = BigInt(Math.floor(Date.now() / 1000)); - return nowInSeconds + minutes * 60n; -}; diff --git a/src/swap/aggregators/bebop.ts b/src/swap/aggregators/bebop.ts index 760bf186..0a38a34f 100644 --- a/src/swap/aggregators/bebop.ts +++ b/src/swap/aggregators/bebop.ts @@ -1,10 +1,10 @@ import Decimal from 'decimal.js'; import { getAddress, type Hex } from 'viem'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { divDecimals } from '../../services/math'; +import { normalizeExpectedOutput } from './expected-output'; import type { Aggregator, Quote, QuoteRequest } from './types'; import { QuoteType } from './types'; -import { normalizeExpectedOutput } from './expected-output'; type BebopApi = 'aggregation' | 'rfq'; @@ -33,10 +33,7 @@ const CHAIN_NAME_MAP: Record = { }; export class BebopAggregator implements Aggregator { - private readonly getQuote: ( - params: Record, - api?: BebopApi - ) => Promise; + private readonly getQuote: (params: Record, api?: BebopApi) => Promise; private readonly useRfq: boolean; constructor( @@ -104,12 +101,7 @@ export class BebopAggregator implements Aggregator { logQuoteFailure(requests[index].api, req.chainId, result.reason); }); - return this.parseResponse( - quotes, - sellToken, - buyToken, - req.type === QuoteType.EXACT_OUT - ); + return this.parseResponse(quotes, sellToken, buyToken, req.type === QuoteType.EXACT_OUT); } catch (error) { logQuoteFailure('request', req.chainId, error); return null; diff --git a/src/swap/aggregators/index.ts b/src/swap/aggregators/index.ts index 96f3ff4a..475ccf21 100644 --- a/src/swap/aggregators/index.ts +++ b/src/swap/aggregators/index.ts @@ -1,12 +1,12 @@ import Decimal from 'decimal.js'; import type { ExternalServiceService } from '../../domain/errors'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { isNativeAddress } from '../../services/addresses'; import { divDecimals } from '../../services/math'; import type { MiddlewareAggregatorQuoteClient } from '../../transport'; import { EADDRESS } from '../constants'; -import { refreshExpectedOutput } from './expected-output'; import { BebopAggregator } from './bebop'; +import { refreshExpectedOutput } from './expected-output'; import { FibrousAggregator } from './fibrous'; import { LiFiAggregator } from './lifi'; import { MysticAggregator } from './mystic'; diff --git a/src/swap/algorithms/auto-select.ts b/src/swap/algorithms/auto-select.ts index 41a69f42..5bbc5a53 100644 --- a/src/swap/algorithms/auto-select.ts +++ b/src/swap/algorithms/auto-select.ts @@ -2,6 +2,7 @@ import Decimal from 'decimal.js'; import type { Hex } from 'viem'; import { Errors } from '../../domain/errors'; import type { ChainListType } from '../../domain/types'; +import { logger } from '../../domain/utils/logger'; import { divDecimals, mulDecimals } from '../../services/math'; import { equalFold } from '../../services/strings'; import { @@ -15,7 +16,6 @@ import { QuoteType, } from '../aggregators'; import type { CurrencyID } from '../cot'; -import { logger } from '../../domain/utils'; import { convergeExactIn, firstSuccess, timedCandidate, tryExactOutDirect } from './convergence'; import { filterMayanSourcesByChain } from './mayan-floor'; diff --git a/src/swap/algorithms/convergence.ts b/src/swap/algorithms/convergence.ts index 2e57608b..c08f7e17 100644 --- a/src/swap/algorithms/convergence.ts +++ b/src/swap/algorithms/convergence.ts @@ -1,5 +1,5 @@ import Decimal from 'decimal.js'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { AggregateMode, type Aggregator, diff --git a/src/swap/algorithms/destination.ts b/src/swap/algorithms/destination.ts index d3ee2bef..7d80c5e0 100644 --- a/src/swap/algorithms/destination.ts +++ b/src/swap/algorithms/destination.ts @@ -1,7 +1,7 @@ import Decimal from 'decimal.js'; import type { Hex } from 'viem'; import type { ChainListType } from '../../domain/types'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { equalFold } from '../../services/strings'; import { AggregateMode, diff --git a/src/swap/balance/ankr.ts b/src/swap/balance/ankr.ts deleted file mode 100644 index 121852ff..00000000 --- a/src/swap/balance/ankr.ts +++ /dev/null @@ -1,108 +0,0 @@ -import axios from 'axios'; -import type { Hex } from 'viem'; -import { getFallbackTokenLogoDataUri } from '../../services/token-logo'; -import { EADDRESS } from '../constants'; -import type { FlatBalance } from '../types'; - -// --------------------------------------------------------------------------- -// Chain name → Chain ID mapping -// --------------------------------------------------------------------------- - -export const ANKR_CHAIN_MAP: Record = { - eth: 1, - arbitrum: 42161, - optimism: 10, - base: 8453, - polygon: 137, - bsc: 56, - avalanche: 43114, - scroll: 534352, - linea: 59144, - polygon_zkevm: 1101, - mantle: 5000, - blast: 81457, - gnosis: 100, - fantom: 250, - celo: 42220, - moonbeam: 1284, - flare: 14, - syscoin: 57, - rollux: 570, - telos: 40, - xdai: 100, - zksync_era: 324, -}; - -const ANKR_API_URL = 'https://rpc.ankr.com/multichain'; -const TIMEOUT_MS = 15_000; - -// --------------------------------------------------------------------------- -// AnkrClient -// --------------------------------------------------------------------------- - -export class AnkrClient { - async getBalances(walletAddress: Hex): Promise { - try { - const response = await axios.post( - ANKR_API_URL, - { - jsonrpc: '2.0', - method: 'ankr_getAccountBalance', - params: { - walletAddress, - onlyWhitelisted: false, - }, - id: 1, - }, - { timeout: TIMEOUT_MS } - ); - - const assets: AnkrAsset[] = response.data?.result?.assets ?? []; - return this.parseAssets(assets); - } catch { - return []; - } - } - - private parseAssets(assets: AnkrAsset[]): FlatBalance[] { - const balances: FlatBalance[] = []; - - for (const asset of assets) { - const chainID = ANKR_CHAIN_MAP[asset.blockchain]; - if (!chainID) continue; - - const balance = Number.parseFloat(asset.balance ?? '0'); - if (balance <= 0) continue; - - const isNative = !asset.contractAddress || asset.tokenType === 'NATIVE'; - const tokenAddress = isNative ? EADDRESS : (asset.contractAddress as Hex); - - balances.push({ - amount: asset.balance, - chainID, - decimals: asset.tokenDecimals, - logo: getFallbackTokenLogoDataUri(asset.tokenSymbol), - name: '', - symbol: asset.tokenSymbol, - tokenAddress, - value: Number.parseFloat(asset.balanceUsd ?? '0'), - }); - } - - return balances; - } -} - -// --------------------------------------------------------------------------- -// Ankr response types (internal) -// --------------------------------------------------------------------------- - -type AnkrAsset = { - blockchain: string; - contractAddress?: string; - tokenType: string; - tokenSymbol: string; - tokenDecimals: number; - balance: string; - balanceUsd: string; -}; diff --git a/src/swap/constants.ts b/src/swap/constants.ts index 8b91447d..162a2b50 100644 --- a/src/swap/constants.ts +++ b/src/swap/constants.ts @@ -7,7 +7,6 @@ import { CurrencyID } from './cot'; export { EADDRESS } from '../domain/constants/addresses'; export const SWEEPER_ADDRESS = '0x0000000000296e7a886f40f7bc60D2E3653216a6' as const; export const CALIBUR_ADDRESS = '0x00000000557A0daF2659cbb6A45f2beB6081e6AE' as const; -export const CANONICAL_MULTICALL_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11' as const; export const ZERO_BYTES_32 = new Uint8Array(32); // --------------------------------------------------------------------------- @@ -26,8 +25,6 @@ export const CALIBUR_EIP712_BASE = { export const SBC_DEADLINE_MINUTES = 15n; export const SLIPPAGE_DEFAULT = 0.005; -export const RATE_GUARD_THRESHOLD = 0.995; -export const REQUOTE_THRESHOLD_SECONDS = 24; export const MAX_RETRIES = 2; export const DIRECT_DST_QUOTE_TTL_MS = 45_000; @@ -41,7 +38,6 @@ export const SRC_BUFFER_PCT = 0.02; export const SRC_BUFFER_MAX_USD = 1; export const MAX_SWAP_HAIRCUT_PCT = 0.03; export const MAX_SWAP_HAIRCUT_MIN_USDC = 3; -export const GAS_TO_COT_BUFFER = 1.02; // EXACT_OUT's start-of-route provider check surveys bridged source value with a rough // greedy walk over priority-ordered holdings; this overshoot fraction makes it count a @@ -61,13 +57,3 @@ export const B2_STABLE_CURRENCY_IDS: ReadonlySet = new Set([ CurrencyID.USDC, CurrencyID.USDT, ]); - -// --------------------------------------------------------------------------- -// Token symbol normalization -// --------------------------------------------------------------------------- - -export const normalizeSymbol = (symbol: string): string => { - const upper = symbol.toUpperCase(); - if (['USD$', 'USD$0', 'USDT'].includes(upper) || symbol === 'USDt') return 'USDT'; - return symbol; -}; diff --git a/src/swap/index.ts b/src/swap/index.ts deleted file mode 100644 index 17cd9e8b..00000000 --- a/src/swap/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -// --------------------------------------------------------------------------- -// Public re-exports -// --------------------------------------------------------------------------- - -export type { - CreateSafeExecuteTxRequest, - CreateSafeExecuteTxResponse, - EnsureAuthParams, - EnsureSafeAccountRequest, - EnsureSafeAccountResponse, - GetSafeAccountAddressRequest, - GetSafeAccountAddressResponse, - MultiSendCall, - SafeAccountAddress, - SafeClient, - SafeClientOptions, - SafeExecuteCall, - SafeHttpClient, - SafeMiddlewareClient, - SafeOperation, - SafeTxFields, -} from './safe'; - -export { - buildDefaultSafeTxFields, - buildEnsureAuthDigest, - buildMultiSendPayload, - buildSafeInitializer, - createSafeClient, - createSafeMiddlewareClient, - hashSafeTx, - packMultiSendCall, - predictSafeAccountAddress, - SAFE_FALLBACK_HANDLER_ADDRESS, - SAFE_L2_SINGLETON_ADDRESS, - SAFE_MULTI_SEND_CALL_ONLY_ADDRESS, - SAFE_OPERATION_CALL, - SAFE_OPERATION_DELEGATECALL, - SAFE_PROXY_FACTORY_ADDRESS, - SAFE_PROXY_INIT_CODE_HASH, - SAFE_SALT_NONCE, - safeDomain, - safeTxTypes, - signEnsureAuth, - signSafeTx, -} from './safe'; -export { createSwapPlan } from './swap-steps-builder'; -export type { - ChainSwap, - OnIntentHookData, - OnSwapIntentHookData, - Source, - Swap, - SwapAndExecuteParams, - SwapAndExecuteResult, - SwapExactInParams, - SwapExactOutParams, - SwapExecuteParams, - SwapIntent, - SwapMaxParams, - SwapMaxResult, - SwapResult, -} from './types'; diff --git a/src/swap/route.ts b/src/swap/route.ts index 60c5f1f6..a8b3ab40 100644 --- a/src/swap/route.ts +++ b/src/swap/route.ts @@ -1,11 +1,16 @@ +import type Decimal from 'decimal.js'; +import type { Hex } from 'viem'; import { assertMayanSupportedDestination } from '../bridge/intent/quote-request'; import type { ChainListType, TimingSpanHooks, TokenInfo } from '../domain'; import { Errors } from '../domain/errors'; -import { logger } from '../domain/utils'; +import { logger } from '../domain/utils/logger'; import type { MiddlewareSwapPreflightClient } from '../transport'; -import type { Hex } from 'viem'; -import type Decimal from 'decimal.js'; import type { Aggregator } from './aggregators/types'; +import { + type ExactInAmountBasis, + resolveExactInAmountBasis, + selectExactInQuoteOutput, +} from './amount-basis'; import { type CurrencyID, resolveCOT } from './cot'; import { _exactInRoute } from './routing/exact-in'; import { _exactOutRoute } from './routing/exact-out'; @@ -19,7 +24,6 @@ import type { WalletPath, } from './types'; import { SwapMode } from './types'; -import { type ExactInAmountBasis, resolveExactInAmountBasis, selectExactInQuoteOutput } from './amount-basis'; export type RouteOptions = { aggregators: Aggregator[]; diff --git a/src/swap/routing/bridge.ts b/src/swap/routing/bridge.ts index b29710fd..f2a81ac7 100644 --- a/src/swap/routing/bridge.ts +++ b/src/swap/routing/bridge.ts @@ -8,7 +8,7 @@ import { } from '../../bridge/intent/quote-request'; import type { ChainListType, TokenInfo } from '../../domain'; import { Errors } from '../../domain/errors'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { isNativeAddress } from '../../services/addresses'; import { divDecimals, mulDecimals } from '../../services/math'; import { diff --git a/src/swap/routing/exact-in.ts b/src/swap/routing/exact-in.ts index 96cb2aed..ed3c6e6a 100644 --- a/src/swap/routing/exact-in.ts +++ b/src/swap/routing/exact-in.ts @@ -1,7 +1,7 @@ import Decimal from 'decimal.js'; import { formatUnits, type Hex } from 'viem'; import { Errors } from '../../domain/errors'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { divDecimals, mulDecimals } from '../../services/math'; import { MAYAN_MIN_USD_PER_LEG, selectMayanQuoteOutput } from '../../services/mayan'; import { equalFold } from '../../services/strings'; diff --git a/src/swap/routing/exact-out.ts b/src/swap/routing/exact-out.ts index 75d3cfaf..7b1e8101 100644 --- a/src/swap/routing/exact-out.ts +++ b/src/swap/routing/exact-out.ts @@ -2,7 +2,7 @@ import Decimal from 'decimal.js'; import { formatUnits, type Hex, parseUnits } from 'viem'; import type { ChainListType } from '../../domain'; import { Errors } from '../../domain/errors'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { convertGasToToken } from '../../services/intent'; import { divDecimals, mulDecimals } from '../../services/math'; import { equalFold } from '../../services/strings'; diff --git a/src/swap/routing/fast-paths.ts b/src/swap/routing/fast-paths.ts index dc33ad56..25c35f71 100644 --- a/src/swap/routing/fast-paths.ts +++ b/src/swap/routing/fast-paths.ts @@ -3,7 +3,7 @@ import { formatUnits, type Hex } from 'viem'; import type { ChainListType } from '../../domain'; import { ZERO_ADDRESS } from '../../domain/constants/addresses'; import { Errors } from '../../domain/errors'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { isNativeAddress } from '../../services/addresses'; import { divDecimals, mulDecimals } from '../../services/math'; import { selectMayanQuoteOutput } from '../../services/mayan'; diff --git a/src/swap/routing/holdings.ts b/src/swap/routing/holdings.ts index 89dd6ed0..0f58056f 100644 --- a/src/swap/routing/holdings.ts +++ b/src/swap/routing/holdings.ts @@ -1,13 +1,13 @@ import Decimal from 'decimal.js'; -import { parseUnits, type Hex } from 'viem'; +import { type Hex, parseUnits } from 'viem'; import type { ChainListType } from '../../domain'; import { Errors } from '../../domain/errors'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { divDecimals } from '../../services/math'; import { equalFold } from '../../services/strings'; +import { filterMayanSourcesByChain } from '../algorithms/mayan-floor'; import { EADDRESS, EXACT_OUT_PROVIDER_BUFFER } from '../constants'; import type { FlatBalance, OraclePriceResponse, Source } from '../types'; -import { filterMayanSourcesByChain } from '../algorithms/mayan-floor'; // Drop selected source chains whose aggregate bridged USD can't clear Mayan's per-leg quote floor. // Mayan-only — Nexus has no per-leg minimum, so this runs only inside the `bridgeProvider==='mayan'` diff --git a/src/swap/routing/prices.ts b/src/swap/routing/prices.ts index 1588db7f..b90b6dd3 100644 --- a/src/swap/routing/prices.ts +++ b/src/swap/routing/prices.ts @@ -1,7 +1,7 @@ import Decimal from 'decimal.js'; import type { Hex } from 'viem'; import { ZERO_ADDRESS } from '../../domain/constants/addresses'; -import { logger } from '../../domain/utils'; +import { logger } from '../../domain/utils/logger'; import { isNativeAddress } from '../../services/addresses'; import { equalFold } from '../../services/strings'; import type { MiddlewareTokenPriceClient } from '../../transport'; diff --git a/src/swap/safe/abis.ts b/src/swap/safe/abis.ts index 0ad09167..94b268b7 100644 --- a/src/swap/safe/abis.ts +++ b/src/swap/safe/abis.ts @@ -17,20 +17,6 @@ export const safeSetupAbi = [ }, ] as const; -export const safeProxyFactoryAbi = [ - { - name: 'createProxyWithNonce', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { name: '_singleton', type: 'address' }, - { name: 'initializer', type: 'bytes' }, - { name: 'saltNonce', type: 'uint256' }, - ], - outputs: [{ name: 'proxy', type: 'address' }], - }, -] as const; - export const safeExecTransactionAbi = [ { name: 'execTransaction', diff --git a/src/swap/swap.md b/src/swap/swap.md index 41149b7a..a7f88e81 100644 --- a/src/swap/swap.md +++ b/src/swap/swap.md @@ -40,7 +40,7 @@ untested (this map exists because that scoping mistake has bitten twice): | Aggregator quote slippage | `tests/swap/aggregators/constants.test.ts` | | Intent / bridge-intent / plan / prepare | `tests/swap/{intent,bridge-intent,swap-steps-builder,prepare}.test.ts` | | Execution (source / bridge / destination / cleanup / safe-dispatch) | `tests/swap/execution/*.test.ts` | -| Wallet primitives (SBC, stark, derived-key, cache, capabilities, eoa-executor, sweep, cot) | `tests/swap/wallet/*.test.ts`, `tests/swap/{sweep,cot}.test.ts` | +| Wallet primitives (SBC, stark, derived-key, cache, capabilities, sweep, cot) | `tests/swap/wallet/*.test.ts`, `tests/swap/{sweep,cot}.test.ts` | | Safe machinery | `tests/swap/safe/*.test.ts` | | Balances | `tests/swap/balance/*.test.ts` | | `calculateMaxForSwap` | `tests/swap/max.test.ts`, `tests/swap/characterization/max-pipeline.test.ts` | @@ -861,10 +861,6 @@ amount)` (native → bare value send). The destination swap returns the *known* (`balanceOf − consumed`) with this instead of the blind `createSweeperTxs` drain — no approve, no external Sweeper CALL; emitted only when the leftover is > 0. -**EOA dispatch primitives (`eoa-executor`)** — `executeViaEoa` (`wallet_sendCalls` + -`experimental_fallback`, then `waitForCallsStatus`; throws `failed`/`no receipt`), `dispatchViaEoa` -(switch chain, send without waiting), `waitForDispatchedEoaCalls`. - **Safe** — `predictSafeAccountAddress(owner)` → `{address, factoryAddress, initializer}`, deterministic (owner `0x1111…` → `0x9eAc…5cf9`). **EnsureAuth** EIP‑712 (`NexusSafeEnsure`, domain `{name:'NexusSafeEnsureAuth', version:'1', chainId, verifyingContract: SAFE_PROXY_FACTORY_ADDRESS}`, diff --git a/src/swap/wallet/capabilities.ts b/src/swap/wallet/capabilities.ts index 1b5e9e77..75279c7c 100644 --- a/src/swap/wallet/capabilities.ts +++ b/src/swap/wallet/capabilities.ts @@ -21,11 +21,3 @@ export const chainSupports7702 = (chain: Chain): boolean => { * the absence of a destination-swap step, not by this function. */ export const resolveWalletPath = (is7702: boolean): WalletPath => (is7702 ? 'ephemeral' : 'safe'); - -/** - * Filters chains to only those that support EIP-7702 (Calibur). - * Chains without `supports7702` default to true. - */ -export const filterValidSourceChains = (chains: Chain[]): Chain[] => { - return chains.filter(chainSupports7702); -}; diff --git a/src/swap/wallet/eoa-executor.ts b/src/swap/wallet/eoa-executor.ts deleted file mode 100644 index 0d57347d..00000000 --- a/src/swap/wallet/eoa-executor.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { Hex, WalletClient } from 'viem'; -import type { Chain } from '../../domain'; -import { switchChain } from '../../services/evm'; - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -export type EoaCall = { - to: Hex; - data: Hex; - value: bigint; -}; - -type ExecuteViaEoaInput = { - walletClient: WalletClient; - calls: EoaCall[]; - chain: Chain; - address: Hex; - maxWaitMs?: number; -}; - -type ExecuteViaEoaResult = { - txHash: Hex; -}; - -export type DispatchedEoaCalls = { - id: string; - chainId: number; - address: Hex; -}; - -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -const DEFAULT_MAX_WAIT_MS = 120_000; // 2 minutes - -// --------------------------------------------------------------------------- -// executeViaEoa -// --------------------------------------------------------------------------- - -export const dispatchViaEoa = async (input: ExecuteViaEoaInput): Promise => { - const { walletClient, calls, address, chain } = input; - - await switchChain(walletClient, chain); - const { id } = await walletClient.sendCalls({ - account: address, - calls, - chain, - experimental_fallback: true, - }); - - return { - id, - chainId: chain.id, - address, - }; -}; - -export const waitForDispatchedEoaCalls = async (input: { - walletClient: WalletClient; - dispatch: DispatchedEoaCalls; - maxWaitMs?: number; -}): Promise => { - const { walletClient, dispatch, maxWaitMs = DEFAULT_MAX_WAIT_MS } = input; - - const result = await walletClient.waitForCallsStatus({ - id: dispatch.id, - timeout: maxWaitMs, - }); - - if (result.status === 'failure') { - throw new Error('executeViaEoa: wallet calls failed'); - } - - const txHash = result.receipts?.[0]?.transactionHash as Hex | undefined; - if (!txHash) { - throw new Error('executeViaEoa: success but no receipt txHash'); - } - - return txHash; -}; - -/** - * Executes batched calls via the EOA wallet using EIP-5792. - * - * 1. Send calls via walletClient.sendCalls - * 2. Wait for completion via walletClient.waitForCallsStatus - * 3. Return txHash from first receipt - * - * Throws on timeout or failure status. - */ -export const executeViaEoa = async (input: ExecuteViaEoaInput): Promise => { - const dispatch = await dispatchViaEoa(input); - const txHash = await waitForDispatchedEoaCalls({ - walletClient: input.walletClient, - dispatch, - maxWaitMs: input.maxWaitMs, - }); - - return { txHash }; -}; diff --git a/src/transport/middleware.ts b/src/transport/middleware.ts index a8054c10..207cc08b 100644 --- a/src/transport/middleware.ts +++ b/src/transport/middleware.ts @@ -36,7 +36,7 @@ import { } from '../domain'; import { ZERO_ADDRESS } from '../domain/constants/addresses'; import { BackendError, ERROR_CODES, Errors, formatUnknownError } from '../domain/errors'; -import { logger } from '../domain/utils'; +import { logger } from '../domain/utils/logger'; import { addressString, hexString } from '../domain/utils/validation'; import { convertAddressByUniverse } from '../services/addresses'; import { equalFold } from '../services/strings'; @@ -603,7 +603,9 @@ const middlewareErrorDetails = (error: unknown): Record => { const groupSbcTxsByChain = (sbcTxs: SBCTx[]): Record => sbcTxs.reduce>((acc, tx) => { - (acc[tx.chainId] ??= []).push(tx); + const chainTxs = acc[tx.chainId] ?? []; + chainTxs.push(tx); + acc[tx.chainId] = chainTxs; return acc; }, {}); diff --git a/tests/analytics/aggregation.test.ts b/tests/analytics/aggregation.test.ts deleted file mode 100644 index 0aaa319d..00000000 --- a/tests/analytics/aggregation.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { - aggregateBySpanName, - aggregateDurations, - computePercentile, -} from '../../src/analytics/aggregation'; -import type { SpanProperties } from '../../src/analytics/types'; - -describe('aggregation utilities', () => { - it('computes percentiles with interpolation', () => { - const sorted = [10, 20, 30, 40]; - expect(computePercentile(sorted, 50)).toBe(25); - expect(computePercentile(sorted, 90)).toBe(37); - }); - - it('aggregates duration summary stats', () => { - const result = aggregateDurations([10, 20, 30, 40, 50]); - expect(result.count).toBe(5); - expect(result.min).toBe(10); - expect(result.max).toBe(50); - expect(result.mean).toBe(30); - expect(result.p50).toBe(30); - }); - - it('aggregates by span name', () => { - const spans: SpanProperties[] = [ - { - operation: 'flow.bridge.prepare_intent', - duration: 10, - success: true, - spanId: 'a', - rootSpanId: 'a', - startedAtIso: '2026-01-01T00:00:00.000Z', - endedAtIso: '2026-01-01T00:00:00.010Z', - }, - { - operation: 'flow.bridge.prepare_intent', - duration: 30, - success: true, - spanId: 'b', - rootSpanId: 'a', - parentSpanId: 'a', - startedAtIso: '2026-01-01T00:00:01.000Z', - endedAtIso: '2026-01-01T00:00:01.030Z', - }, - { - operation: 'flow.bridge.submit_rff', - duration: 50, - success: true, - spanId: 'c', - rootSpanId: 'a', - startedAtIso: '2026-01-01T00:00:02.000Z', - endedAtIso: '2026-01-01T00:00:02.050Z', - }, - ]; - - const map = aggregateBySpanName(spans); - expect(map.get('flow.bridge.prepare_intent')?.count).toBe(2); - expect(map.get('flow.bridge.prepare_intent')?.p50).toBe(20); - expect(map.get('flow.bridge.submit_rff')?.mean).toBe(50); - }); -}); diff --git a/tests/bridge/allowances/prepare-swap-sbc-7702.test.ts b/tests/bridge/allowances/prepare-swap-sbc-7702.test.ts deleted file mode 100644 index 668a0839..00000000 --- a/tests/bridge/allowances/prepare-swap-sbc-7702.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { Hex } from 'viem'; -import type { PrivateKeyAccount } from 'viem/accounts'; -import { prepareSwapBridgeExecution } from '../../../src/bridge/allowances/prepare-swap-sbc'; -import type { AllowanceHookSource, BridgeIntentDraft, ChainListType } from '../../../src/domain'; -import type { PublicClientList } from '../../../src/swap/types'; - -vi.mock('../../../src/services/sbc', () => ({ - createSBCTxFromCalls: vi.fn().mockResolvedValue({ - chainId: 42161, - address: '0x0000000000000000000000000000000000000abc' as Hex, - calls: [], - deadline: '0x1' as Hex, - keyHash: '0x0' as Hex, - nonce: '0x1' as Hex, - revertOnFailure: true, - signature: '0x1234' as Hex, - }), - requireSuccessfulSbcResult: vi.fn(() => '0xsbc_tx' as Hex), -})); - -import { createSBCTxFromCalls } from '../../../src/services/sbc'; - -const ARB_CHAIN = 42161; -const HYPEREVM_CHAIN = 999; -const USDC_ARB = '0xaf88d065e77c8cc2239327c5edb3a432268e5831' as Hex; -const USDC_HYPEREVM = '0xb88339cb7199b77e23db6e890353e22632ba630f' as Hex; -const EPHEMERAL = '0xbbbb000000000000000000000000000000000002' as Hex; - -const makeChainList = (): ChainListType => - ({ - chains: [], - // ARB = 7702-supported, HyperEVM = non-7702 - getChainByID: vi.fn().mockImplementation((chainId: number) => ({ - id: chainId, - supports7702: chainId === ARB_CHAIN, - name: `Chain ${chainId}`, - })), - getVaultContractAddress: vi - .fn() - .mockReturnValue('0x9999999999999999999999999999999999999999' as Hex), - getTokenByCurrencyId: vi.fn(), - getTokenByAddress: vi.fn(), - getChainAndTokenByAddress: vi.fn(), - getNativeToken: vi.fn(), - getTokenInfoBySymbol: vi.fn(), - getChainAndTokenFromSymbol: vi.fn(), - }) as unknown as ChainListType; - -const makePublicClientList = (): PublicClientList => - ({ - get: vi.fn().mockReturnValue({ - waitForTransactionReceipt: vi.fn().mockResolvedValue({ status: 'success' }), - }), - }) as unknown as PublicClientList; - -const makeEphemeralWallet = (): PrivateKeyAccount => - ({ - address: EPHEMERAL, - signTypedData: vi.fn(), - }) as unknown as PrivateKeyAccount; - -const makeIntent = (): BridgeIntentDraft => - ({ - destination: { chain: { id: ARB_CHAIN } }, - }) as unknown as BridgeIntentDraft; - -describe('prepareSwapBridgeExecution', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('skips ephemeral SBC pre-approvals on non-7702 source chains', async () => { - const submitSBCs = vi.fn().mockResolvedValue([ - { - chainId: ARB_CHAIN, - address: '0x0000000000000000000000000000000000000abc' as Hex, - errored: false, - txHash: '0xsbc_tx' as Hex, - }, - ]); - const createApprovals = vi.fn(); - - const makeSource = (chainId: number, tokenAddress: Hex): AllowanceHookSource => ({ - allowance: { - current: '0', - currentRaw: 0n, - minimum: '1', - minimumRaw: 1000000n, - }, - chain: { id: chainId, logo: '', name: `Chain ${chainId}` }, - token: { - contractAddress: tokenAddress, - decimals: 6, - logo: '', - name: 'USDC', - symbol: 'USDC', - }, - }); - const insufficientAllowanceSources: AllowanceHookSource[] = [ - makeSource(ARB_CHAIN, USDC_ARB), - makeSource(HYPEREVM_CHAIN, USDC_HYPEREVM), - ]; - - await prepareSwapBridgeExecution(makeIntent(), { - allowanceSelections: ['min', 'min'], - insufficientAllowanceSources, - middlewareClient: { - submitSBCs, - createApprovals, - } as never, - ephemeralWallet: makeEphemeralWallet(), - chainList: makeChainList(), - publicClientList: makePublicClientList(), - cache: undefined, - }); - - expect(submitSBCs).toHaveBeenCalledTimes(1); - const calledChainIds = vi - .mocked(createSBCTxFromCalls) - .mock.calls.map(([input]) => input.chainID); - expect(calledChainIds).toEqual([ARB_CHAIN]); - expect(calledChainIds).not.toContain(HYPEREVM_CHAIN); - }); -}); diff --git a/tests/core/sdk-cot-sweep-init.test.ts b/tests/core/sdk-cot-sweep-init.test.ts index 8289af4d..da15487b 100644 --- a/tests/core/sdk-cot-sweep-init.test.ts +++ b/tests/core/sdk-cot-sweep-init.test.ts @@ -43,7 +43,7 @@ vi.mock('../../src/services/chain-list', () => ({ createChainList: hoisted.createChainList, })); -import { createNexusClient } from '../../src/core/sdk'; +import { createNexusClient } from '../../src/core/sdk/client'; const ADDRESS = '0x0000000000000000000000000000000000000aaa' as Hex; const DERIVED_ADDRESS = '0x0000000000000000000000000000000000000bbb' as Hex; diff --git a/tests/core/sdk-derived-key-storage.test.ts b/tests/core/sdk-derived-key-storage.test.ts index 0fb50820..d49130cc 100644 --- a/tests/core/sdk-derived-key-storage.test.ts +++ b/tests/core/sdk-derived-key-storage.test.ts @@ -49,7 +49,7 @@ vi.mock('../../src/services/chain-list', () => ({ createChainList: hoisted.createChainList, })); -import { createNexusClient } from '../../src/core/sdk'; +import { createNexusClient } from '../../src/core/sdk/client'; const ADDRESS = '0x0000000000000000000000000000000000000aaa' as Hex; const DERIVED_ADDRESS = '0x0000000000000000000000000000000000000bbb' as Hex; diff --git a/tests/core/sdk-swap-balances.test.ts b/tests/core/sdk-swap-balances.test.ts index cbfe3273..061dcc22 100644 --- a/tests/core/sdk-swap-balances.test.ts +++ b/tests/core/sdk-swap-balances.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { Hex } from 'viem'; import type { EthereumProvider } from '../../src/domain'; import type { MiddlewareClient } from '../../src/transport'; -import { createNexusClient } from '../../src/core/sdk'; +import { createNexusClient } from '../../src/core/sdk/client'; import { makeMiddlewareClient as makeBaseMiddlewareClient } from '../helpers/middleware-client'; const hoisted = vi.hoisted(() => ({ diff --git a/tests/flows/bridge-hooks.test.ts b/tests/flows/bridge-hooks.test.ts index f8a54bc4..bffa623a 100644 --- a/tests/flows/bridge-hooks.test.ts +++ b/tests/flows/bridge-hooks.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'; import Decimal from 'decimal.js'; import type { Hex } from 'viem'; -import type { BridgeIntentDraft, ChainListType, AllowanceHookSource } from '../../src/domain'; +import type { BridgeIntentDraft, ChainListType } from '../../src/domain'; import { Universe } from '../../src/domain/chain-abstraction'; vi.mock('../../src/services/allowance-utils', () => ({ @@ -16,31 +16,7 @@ vi.mock('../../src/bridge/intent/readable', () => ({ convertIntent: vi.fn().mockReturnValue({ id: 'mock-readable-intent' }), })); -vi.mock('../../src/services/sbc', () => ({ - createSBCTxFromCalls: vi.fn().mockResolvedValue({ - chainId: 1, - address: '0x0000000000000000000000000000000000000001', - calls: [], - deadline: '0x0000000000000000000000000000000000000000000000000000000000000001', - keyHash: '0x0000000000000000000000000000000000000000000000000000000000000000', - nonce: '0x0000000000000000000000000000000000000000000000000000000000000001', - revertOnFailure: true, - signature: '0x1234', - }), - requireSuccessfulSbcResult: vi.fn((results, chainId) => { - const result = results.find((entry: { chainId: number }) => entry.chainId === chainId) as - | { errored: false; txHash: Hex } - | { errored: true; message: string } - | undefined; - if (!result || result.errored) { - throw new Error(result?.message ?? 'SBC submission failed'); - } - return result.txHash; - }), -})); - import { prepareBridgeExecution } from '../../src/bridge/allowances/prepare'; -import { prepareSwapBridgeExecution } from '../../src/bridge/allowances/prepare-swap-sbc'; import { runBridgeHooks } from '../../src/bridge/hooks/approval'; import { buildHookStateFromIntent } from '../../src/bridge/hooks/state'; import { getAllowances } from '../../src/services/allowance-utils'; @@ -197,108 +173,3 @@ describe('prepareBridgeExecution', () => { ).resolves.toBeUndefined(); }); }); - -describe('prepareSwapBridgeExecution', () => { - it('no-ops when no insufficient sources', async () => { - const submitSBCs = vi.fn().mockResolvedValue([]); - await prepareSwapBridgeExecution(makeIntent(), { - allowanceSelections: [], - insufficientAllowanceSources: [], - middlewareClient: { submitSBCs } as any, - ephemeralWallet: { address: '0xeph' as Hex } as any, - chainList: makeChainList(), - publicClientList: { get: vi.fn() }, - cache: undefined, - }); - - expect(submitSBCs).not.toHaveBeenCalled(); - }); - - it('submits SBC approve transactions when allowances are insufficient', async () => { - const submitSBCs = vi.fn().mockResolvedValue([ - { - chainId: 42161, - address: '0x0000000000000000000000000000000000000abc' as Hex, - errored: false, - txHash: '0xapprove' as Hex, - }, - ]); - const insufficientSources: AllowanceHookSource[] = [ - { - allowance: { current: '0', currentRaw: 0n, minimum: '100', minimumRaw: 100000000n }, - chain: { id: 42161, logo: '', name: 'Arbitrum' }, - token: { contractAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831' as Hex, decimals: 6, logo: '', name: 'USDC', symbol: 'USDC' }, - }, - ]; - - await prepareSwapBridgeExecution(makeIntent(), { - allowanceSelections: ['min'], - insufficientAllowanceSources: insufficientSources, - middlewareClient: { submitSBCs } as any, - ephemeralWallet: { - address: '0xbbbb000000000000000000000000000000000002' as Hex, - signTypedData: vi.fn().mockResolvedValue('0x' + 'aa'.repeat(65)), - signAuthorization: vi.fn().mockResolvedValue({ r: '0x01', s: '0x02', yParity: 0, nonce: 0 }), - } as any, - chainList: makeChainList(), - publicClientList: { - get: vi.fn().mockReturnValue({ - getCode: vi.fn().mockResolvedValue(undefined), - getTransactionCount: vi.fn().mockResolvedValue(0), - multicall: vi.fn().mockResolvedValue([]), - waitForTransactionReceipt: vi.fn().mockResolvedValue({ - status: 'success', - transactionHash: '0xapprove' as Hex, - }), - }), - }, - cache: undefined, - }); - - expect(submitSBCs).toHaveBeenCalledTimes(1); - }); - - it('throws when SBC approval submission reports failure', async () => { - const submitSBCs = vi.fn().mockResolvedValue([ - { - chainId: 42161, - address: '0x0000000000000000000000000000000000000abc' as Hex, - errored: true, - message: 'approval failed', - }, - ]); - const insufficientSources: AllowanceHookSource[] = [ - { - allowance: { current: '0', currentRaw: 0n, minimum: '100', minimumRaw: 100000000n }, - chain: { id: 42161, logo: '', name: 'Arbitrum' }, - token: { contractAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831' as Hex, decimals: 6, logo: '', name: 'USDC', symbol: 'USDC' }, - }, - ]; - - await expect( - prepareSwapBridgeExecution(makeIntent(), { - allowanceSelections: ['min'], - insufficientAllowanceSources: insufficientSources, - middlewareClient: { submitSBCs } as any, - ephemeralWallet: { - address: '0xbbbb000000000000000000000000000000000002' as Hex, - signTypedData: vi.fn().mockResolvedValue('0x' + 'aa'.repeat(65)), - signAuthorization: vi.fn().mockResolvedValue({ r: '0x01', s: '0x02', yParity: 0, nonce: 0 }), - } as any, - chainList: makeChainList(), - publicClientList: { - get: vi.fn().mockReturnValue({ - getCode: vi.fn().mockResolvedValue(undefined), - getTransactionCount: vi.fn().mockResolvedValue(0), - multicall: vi.fn().mockResolvedValue([]), - waitForTransactionReceipt: vi.fn().mockResolvedValue({ - status: 'success', - transactionHash: '0xapprove' as Hex, - }), - }), - }, - cache: undefined, - }) - ).rejects.toThrow(/approval failed|submission/i); - }); -}); diff --git a/tests/performance/baselines/current.json b/tests/performance/baselines/current.json deleted file mode 100644 index b84262a3..00000000 --- a/tests/performance/baselines/current.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "flow.bridge.prepare_intent": { - "p50": 10, - "p95": 20, - "p99": 30 - } -} diff --git a/tests/performance/span-regression.test.ts b/tests/performance/span-regression.test.ts deleted file mode 100644 index 5d97b454..00000000 --- a/tests/performance/span-regression.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import baseline from './baselines/current.json' with { type: 'json' }; -import { describe, expect, it } from 'vitest'; -import { computeRegressionPercent, median, toSummary } from './utils'; - -describe('performance span regression checks', () => { - it('compares percentiles against baseline thresholds', () => { - // Deterministic fixture-style sample set for CI gating logic validation. - const currentDurations = [9, 10, 10, 10, 10, 11, 12, 13, 14, 15]; - const summary = toSummary(currentDurations); - expect(median(currentDurations)).toBe(summary.p50); - - const baselineEntry = baseline['flow.bridge.prepare_intent']; - expect(baselineEntry).toBeDefined(); - - const p50Regression = computeRegressionPercent(summary.p50, baselineEntry.p50); - const p95Regression = computeRegressionPercent(summary.p95, baselineEntry.p95); - const p99Regression = computeRegressionPercent(summary.p99, baselineEntry.p99); - - expect(p50Regression).toBeLessThanOrEqual(15); - expect(p95Regression).toBeLessThanOrEqual(20); - expect(p99Regression).toBeLessThanOrEqual(30); - }); -}); diff --git a/tests/performance/utils.ts b/tests/performance/utils.ts deleted file mode 100644 index a39e15e4..00000000 --- a/tests/performance/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { PerformanceAggregates } from '../../src/analytics/aggregation'; -import { aggregateDurations } from '../../src/analytics/aggregation'; - -export const median = (values: number[]): number => { - const sorted = [...values].sort((a, b) => a - b); - const mid = Math.floor(sorted.length / 2); - if (sorted.length === 0) return 0; - if (sorted.length % 2 === 0) { - return ((sorted[mid - 1] ?? 0) + (sorted[mid] ?? 0)) / 2; - } - return sorted[mid] ?? 0; -}; - -export const collectSamples = async ( - run: () => Promise, - config?: { warmupRuns?: number; samples?: number } -): Promise => { - const warmupRuns = config?.warmupRuns ?? 3; - const samples = config?.samples ?? 10; - - for (let i = 0; i < warmupRuns; i += 1) { - await run(); - } - - const durations: number[] = []; - for (let i = 0; i < samples; i += 1) { - durations.push(await run()); - } - return durations; -}; - -export const computeRegressionPercent = (current: number, baseline: number): number => { - if (baseline === 0) return 0; - return ((current - baseline) / baseline) * 100; -}; - -export const toSummary = (durations: number[]): PerformanceAggregates => { - return aggregateDurations(durations); -}; diff --git a/tests/services/rff.test.ts b/tests/services/rff.test.ts index 9b6e1a17..1611f05d 100644 --- a/tests/services/rff.test.ts +++ b/tests/services/rff.test.ts @@ -2,7 +2,7 @@ import Decimal from 'decimal.js'; import { describe, expect, it } from 'vitest'; import type { BridgeIntentDraft, TokenInfo } from '../../src/domain'; import { Universe } from '../../src/domain/chain-abstraction'; -import { ZERO_ADDRESS } from '../../src/domain/constants'; +import { ZERO_ADDRESS } from '../../src/domain/constants/addresses'; import { convertTo32BytesHex } from '../../src/services/encoding'; import { getSourcesAndDestinationsForRFF } from '../../src/services/rff'; diff --git a/tests/swap/aggregators/aggregate.test.ts b/tests/swap/aggregators/aggregate.test.ts index 33beaa33..93659294 100644 --- a/tests/swap/aggregators/aggregate.test.ts +++ b/tests/swap/aggregators/aggregate.test.ts @@ -14,7 +14,7 @@ import { RelayAggregator } from '../../../src/swap/aggregators/relay'; import { ZeroExAggregator } from '../../../src/swap/aggregators/zerox'; import { EADDRESS } from '../../../src/swap/constants'; import { ZERO_ADDRESS } from '../../../src/domain/constants/addresses'; -import { logger } from '../../../src/domain/utils'; +import { logger } from '../../../src/domain/utils/logger'; const makeQuote = ( outputAmountRaw: bigint, diff --git a/tests/swap/balance/ankr.test.ts b/tests/swap/balance/ankr.test.ts deleted file mode 100644 index 45dd364d..00000000 --- a/tests/swap/balance/ankr.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import axios from 'axios'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { AnkrClient, ANKR_CHAIN_MAP } from '../../../src/swap/balance/ankr'; - -vi.mock('axios'); - -const makeAnkrAsset = ( - blockchain: string, - tokenAddress: string, - symbol: string, - balance: string, - balanceUsd: string, - decimals: number, -) => ({ - blockchain, - contractAddress: tokenAddress === '' ? undefined : tokenAddress, - tokenType: tokenAddress === '' ? 'NATIVE' : 'ERC20', - tokenSymbol: symbol, - tokenDecimals: decimals, - balance, - balanceUsd, -}); - -const makeAnkrResponse = (assets: ReturnType[]) => ({ - data: { - result: { - assets, - }, - }, -}); - -describe('AnkrClient', () => { - let client: AnkrClient; - - beforeEach(() => { - vi.clearAllMocks(); - client = new AnkrClient(); - }); - - it('maps Ankr response to FlatBalance array', async () => { - vi.mocked(axios.post).mockResolvedValueOnce( - makeAnkrResponse([ - makeAnkrAsset('arbitrum', '0xaf88d065e77c8cc2239327c5edb3a432268e5831', 'USDC', '10.5', '10.50', 6), - ]), - ); - - const balances = await client.getBalances('0xUser' as `0x${string}`); - - expect(balances).toHaveLength(1); - expect(balances[0].chainID).toBe(42161); - expect(balances[0].symbol).toBe('USDC'); - expect(balances[0].amount).toBe('10.5'); - expect(balances[0].name).toBe(''); - expect(balances[0].logo).toMatch(/^data:image\/svg\+xml/); - expect(balances[0].value).toBe(10.5); - expect(balances[0].tokenAddress).toBe('0xaf88d065e77c8cc2239327c5edb3a432268e5831'); - }); - - it('maps native token to EADDRESS', async () => { - vi.mocked(axios.post).mockResolvedValueOnce( - makeAnkrResponse([makeAnkrAsset('eth', '', 'ETH', '1.5', '3000', 18)]), - ); - - const balances = await client.getBalances('0xUser' as `0x${string}`); - - expect(balances).toHaveLength(1); - expect(balances[0].tokenAddress.toLowerCase()).toBe( - '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', - ); - expect(balances[0].chainID).toBe(1); - }); - - it('chain name mapping covers known chains', () => { - // Verify key chain mappings - expect(ANKR_CHAIN_MAP.eth).toBe(1); - expect(ANKR_CHAIN_MAP.arbitrum).toBe(42161); - expect(ANKR_CHAIN_MAP.optimism).toBe(10); - expect(ANKR_CHAIN_MAP.base).toBe(8453); - expect(ANKR_CHAIN_MAP.polygon).toBe(137); - expect(ANKR_CHAIN_MAP.bsc).toBe(56); - expect(ANKR_CHAIN_MAP.avalanche).toBe(43114); - expect(ANKR_CHAIN_MAP.scroll).toBe(534352); - }); - - it('returns empty array on API failure', async () => { - vi.mocked(axios.post).mockRejectedValueOnce(new Error('network failure')); - - const balances = await client.getBalances('0xUser' as `0x${string}`); - - expect(balances).toHaveLength(0); - }); - - it('filters out zero-balance tokens', async () => { - vi.mocked(axios.post).mockResolvedValueOnce( - makeAnkrResponse([ - makeAnkrAsset('arbitrum', '0xtoken1', 'TK1', '0', '0', 18), - makeAnkrAsset('arbitrum', '0xtoken2', 'TK2', '5.0', '10.0', 18), - ]), - ); - - const balances = await client.getBalances('0xUser' as `0x${string}`); - - expect(balances).toHaveLength(1); - expect(balances[0].symbol).toBe('TK2'); - }); - - it('skips assets with unknown blockchain name', async () => { - vi.mocked(axios.post).mockResolvedValueOnce( - makeAnkrResponse([ - makeAnkrAsset('unknown_chain', '0xtoken', 'TK', '10', '20', 18), - makeAnkrAsset('arbitrum', '0xtoken2', 'TK2', '5.0', '10.0', 18), - ]), - ); - - const balances = await client.getBalances('0xUser' as `0x${string}`); - - expect(balances).toHaveLength(1); - expect(balances[0].chainID).toBe(42161); - }); -}); diff --git a/tests/swap/constants.test.ts b/tests/swap/constants.test.ts index 520ae55a..c13456e1 100644 --- a/tests/swap/constants.test.ts +++ b/tests/swap/constants.test.ts @@ -4,7 +4,6 @@ import { B2_STABLE_CURRENCY_IDS, DST_BUFFER_MAX_USD, DST_BUFFER_PCT, - GAS_TO_COT_BUFFER, MAX_RETRIES, MAX_SWAP_HAIRCUT_MIN_USDC, MAX_SWAP_HAIRCUT_PCT, @@ -48,9 +47,8 @@ describe('swap economic constants', () => { expect(MAX_CONVERGENCE_ITERATIONS).toBe(10); }); - it('execution thresholds: 0.5% default slippage, gas×1.02, 2 retries, 15-minute SBC deadline', () => { + it('execution thresholds: 0.5% default slippage, 2 retries, 15-minute SBC deadline', () => { expect(SLIPPAGE_DEFAULT).toBe(0.005); - expect(GAS_TO_COT_BUFFER).toBe(1.02); expect(MAX_RETRIES).toBe(2); expect(SBC_DEADLINE_MINUTES).toBe(15n); }); diff --git a/tests/swap/execution/bridge-contracts.test.ts b/tests/swap/execution/bridge-contracts.test.ts index 6b4155f3..9843b45f 100644 --- a/tests/swap/execution/bridge-contracts.test.ts +++ b/tests/swap/execution/bridge-contracts.test.ts @@ -34,10 +34,6 @@ vi.mock('../../../src/bridge/hooks/approval', () => ({ })), })); -vi.mock('../../../src/bridge/allowances/prepare-swap-sbc', () => ({ - prepareSwapBridgeExecution: vi.fn().mockResolvedValue(undefined), -})); - vi.mock('../../../src/services/rff', () => ({ createRequestFromIntent: vi.fn(), })); diff --git a/tests/swap/route-structure.test.ts b/tests/swap/route-structure.test.ts index ad0a7f4c..ba7f0057 100644 --- a/tests/swap/route-structure.test.ts +++ b/tests/swap/route-structure.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest'; const repositoryRoot = resolve(import.meta.dirname, '../..'); describe('swap routing module structure', () => { - it('keeps route.ts as a small facade over mode-owned routing modules', () => { + it('keeps mode-owned routing modules in place', () => { const expectedModules = [ 'exact-in.ts', 'exact-out.ts', @@ -20,8 +20,5 @@ describe('swap routing module structure', () => { readFileSync(resolve(repositoryRoot, 'src/swap/routing', module), 'utf8') ).not.toThrow(); } - - const facade = readFileSync(resolve(repositoryRoot, 'src/swap/route.ts'), 'utf8'); - expect(facade.split('\n').length).toBeLessThan(150); }); }); diff --git a/tests/swap/wallet/capabilities.test.ts b/tests/swap/wallet/capabilities.test.ts index 6d269afe..7b584f0b 100644 --- a/tests/swap/wallet/capabilities.test.ts +++ b/tests/swap/wallet/capabilities.test.ts @@ -1,11 +1,10 @@ import { describe, expect, it, vi } from 'vitest'; +import type { Chain } from '../../../src/domain'; +import { Universe } from '../../../src/domain/chain-abstraction'; import { chainSupports7702, - filterValidSourceChains, resolveWalletPath, } from '../../../src/swap/wallet/capabilities'; -import type { Chain } from '../../../src/domain'; -import { Universe } from '../../../src/domain/chain-abstraction'; const makeChain = (id: number, supports7702?: boolean): Chain => ({ id, @@ -45,24 +44,3 @@ describe('resolveWalletPath', () => { expect(resolveWalletPath(false)).toBe('safe'); }); }); - -describe('filterValidSourceChains', () => { - it('keeps 7702 chains', () => { - const chains = [makeChain(42161, true), makeChain(10, true)]; - const filtered = filterValidSourceChains(chains); - expect(filtered).toHaveLength(2); - }); - - it('filters out non-7702 chains', () => { - const chains = [makeChain(42161, true), makeChain(10, false)]; - const filtered = filterValidSourceChains(chains); - expect(filtered).toHaveLength(1); - expect(filtered[0].id).toBe(42161); - }); - - it('defaults undefined supports7702 to true (keeps chain)', () => { - const chains = [makeChain(42161, undefined)]; - const filtered = filterValidSourceChains(chains); - expect(filtered).toHaveLength(1); - }); -}); diff --git a/tests/swap/wallet/eoa-executor.test.ts b/tests/swap/wallet/eoa-executor.test.ts deleted file mode 100644 index 23cbcd92..00000000 --- a/tests/swap/wallet/eoa-executor.test.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { describe, expect, it, vi, beforeEach } from 'vitest'; -import { - dispatchViaEoa, - executeViaEoa, - waitForDispatchedEoaCalls, -} from '../../../src/swap/wallet/eoa-executor'; -import type { Hex, WalletClient } from 'viem'; -import { makeChain } from '../../helpers/chains'; - -const MOCK_CALL_ID = '0xcallid123'; -const CHAIN = makeChain(42161, 'Arbitrum'); - -const makeWalletClient = () => { - let currentChainId = 1; - return { - getChainId: vi.fn(async () => currentChainId), - switchChain: vi.fn(async ({ id }: { id: number }) => { - currentChainId = id; - return CHAIN; - }), - addChain: vi.fn(), - sendCalls: vi.fn().mockResolvedValue({ id: MOCK_CALL_ID }), - waitForCallsStatus: vi.fn().mockResolvedValue({ - status: 'success', - receipts: [{ transactionHash: '0xabc123' as Hex }], - }), - } as unknown as WalletClient & { - getChainId: ReturnType; - switchChain: ReturnType; - sendCalls: ReturnType; - waitForCallsStatus: ReturnType; - }; -}; - -const MOCK_CALLS = [ - { to: '0x1111111111111111111111111111111111111111' as Hex, data: '0xabcdef' as Hex, value: 0n }, -]; - -describe('executeViaEoa', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('sends calls via walletClient.sendCalls and returns txHash', async () => { - const walletClient = makeWalletClient(); - - const result = await executeViaEoa({ - walletClient, - calls: MOCK_CALLS, - chain: CHAIN, - address: '0xaaaa' as Hex, - }); - - expect(result.txHash).toBe('0xabc123'); - expect(walletClient.sendCalls).toHaveBeenCalledWith( - expect.objectContaining({ - calls: MOCK_CALLS, - chain: CHAIN, - account: '0xaaaa', - experimental_fallback: true, - }), - ); - }); - - it('passes timeout to waitForCallsStatus', async () => { - const walletClient = makeWalletClient(); - - await executeViaEoa({ - walletClient, - calls: MOCK_CALLS, - chain: CHAIN, - address: '0xaaaa' as Hex, - maxWaitMs: 5000, - }); - - expect(walletClient.waitForCallsStatus).toHaveBeenCalledWith( - expect.objectContaining({ - id: MOCK_CALL_ID, - timeout: 5000, - }), - ); - }); - - it('throws when waitForCallsStatus returns failure', async () => { - const walletClient = makeWalletClient(); - walletClient.waitForCallsStatus.mockResolvedValue({ - status: 'failure', - receipts: [], - }); - - await expect( - executeViaEoa({ - walletClient, - calls: MOCK_CALLS, - chain: CHAIN, - address: '0xaaaa' as Hex, - }), - ).rejects.toThrow(/failed/i); - }); - - it('throws when no receipt txHash on success', async () => { - const walletClient = makeWalletClient(); - walletClient.waitForCallsStatus.mockResolvedValue({ - status: 'success', - receipts: [], - }); - - await expect( - executeViaEoa({ - walletClient, - calls: MOCK_CALLS, - chain: CHAIN, - address: '0xaaaa' as Hex, - }), - ).rejects.toThrow(/no receipt/i); - }); - - it('handles single call correctly', async () => { - const walletClient = makeWalletClient(); - const singleCall = { to: '0x2222222222222222222222222222222222222222' as Hex, data: '0x00' as Hex, value: 100n }; - - const result = await executeViaEoa({ - walletClient, - calls: [singleCall], - chain: CHAIN, - address: '0xaaaa' as Hex, - }); - - expect(result.txHash).toBe('0xabc123'); - expect(walletClient.sendCalls).toHaveBeenCalledWith( - expect.objectContaining({ - calls: [singleCall], - }), - ); - }); - - it('dispatchViaEoa sends calls without waiting for receipts', async () => { - const walletClient = makeWalletClient(); - - const dispatched = await dispatchViaEoa({ - walletClient, - calls: MOCK_CALLS, - chain: CHAIN, - address: '0xaaaa' as Hex, - }); - - expect(dispatched).toEqual({ - id: MOCK_CALL_ID, - chainId: 42161, - address: '0xaaaa', - }); - expect(walletClient.sendCalls).toHaveBeenCalledTimes(1); - expect(walletClient.waitForCallsStatus).not.toHaveBeenCalled(); - }); - - it('dispatchViaEoa switches to the target chain before wallet_sendCalls', async () => { - const walletClient = makeWalletClient(); - - await dispatchViaEoa({ - walletClient, - calls: MOCK_CALLS, - chain: CHAIN, - address: '0xaaaa' as Hex, - }); - - expect(walletClient.switchChain).toHaveBeenCalledWith({ id: CHAIN.id }); - expect(walletClient.sendCalls).toHaveBeenCalledWith( - expect.objectContaining({ - chain: CHAIN, - }) - ); - expect(walletClient.switchChain.mock.invocationCallOrder[0]).toBeLessThan( - walletClient.sendCalls.mock.invocationCallOrder[0] - ); - }); - - it('waitForDispatchedEoaCalls waits only when explicitly asked', async () => { - const walletClient = makeWalletClient(); - - const txHash = await waitForDispatchedEoaCalls({ - walletClient, - dispatch: { - id: MOCK_CALL_ID, - chainId: 42161, - address: '0xaaaa' as Hex, - }, - maxWaitMs: 5000, - }); - - expect(txHash).toBe('0xabc123'); - expect(walletClient.waitForCallsStatus).toHaveBeenCalledWith({ - id: MOCK_CALL_ID, - timeout: 5000, - }); - }); -});