diff --git a/.github/workflows/deploy-typedoc.yml b/.github/workflows/deploy-typedoc.yml new file mode 100644 index 00000000..fccd991b --- /dev/null +++ b/.github/workflows/deploy-typedoc.yml @@ -0,0 +1,91 @@ +name: Build & Publish TypeDoc (master -> root, develop -> /develop) + +on: + push: + branches: + - develop + - master + +permissions: + contents: write + +jobs: + build-and-publish: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install dependencies + run: npm ci + + - name: Build TypeDoc (runs your 'docs' script) + run: npm run docs + # typedoc_html.json should output to ./html_docs + + - name: Publish to gh-pages (master -> root, develop -> /develop) + env: + REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + echo "Publishing TypeDoc for branch: ${BRANCH_NAME}" + + REMOTE="https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" + TMP=tmp-gh-pages + rm -rf "$TMP" + mkdir -p "$TMP" + cd "$TMP" + + # If gh-pages exists on remote, clone it; otherwise init an empty repo and add remote + if git ls-remote --heads "${REMOTE}" gh-pages | grep -q 'refs/heads/gh-pages'; then + git clone --depth 1 --branch gh-pages "${REMOTE}" . + else + git init + git remote add origin "${REMOTE}" + fi + + # Ensure .nojekyll is present + touch .nojekyll + + # Publish logic: + # - If run on master => publish html_docs to root of gh-pages (overwrites root content but preserves /develop) + # - If run on develop => publish html_docs to gh-pages/develop/ + if [ "${BRANCH_NAME}" = "master" ]; then + echo "Publishing master build to gh-pages root..." + # keep .git and /develop and .nojekyll - remove everything else from root + # enable extglob to use !(pattern) + bash -lc 'shopt -s extglob || true; rm -rf -- !(develop|.nojekyll|.git)' + + # copy the built docs into the root + cp -r ../html_docs/* . || true + else + echo "Publishing develop build to gh-pages/develop/..." + rm -rf "${BRANCH_NAME}" + mkdir -p "${BRANCH_NAME}" + cp -r ../html_docs/* "${BRANCH_NAME}/" || true + fi + + # Configure git user + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Stage changes and commit + git add -A + + if git rev-parse --verify HEAD >/dev/null 2>&1; then + git commit -m "chore(docs): update TypeDoc for ${BRANCH_NAME} (run: ${GITHUB_RUN_ID})" || echo "No changes to commit" + else + git commit -m "chore(docs): initialize gh-pages (run: ${GITHUB_RUN_ID})" + fi + + # Push to gh-pages (creates branch if needed) + git push --force "${REMOTE}" HEAD:gh-pages diff --git a/.gitignore b/.gitignore index d20eded1..127f87dc 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ yarn-error.log* .DS_Store Thumbs.db +*.tgz + # IDE .idea/ .vscode/ @@ -33,3 +35,4 @@ Thumbs.db *.swo /dist-tarballs +html_docs/ \ No newline at end of file diff --git a/README.md b/README.md index 6954e509..74404474 100644 --- a/README.md +++ b/README.md @@ -1,212 +1,438 @@ -# Nexus SDK +# @avail-project/nexus/core -A powerful TypeScript SDK for cross-chain operations, token bridging, and unified balance management across multiple EVM chains. +A **headless TypeScript SDK** for **cross-chain operations**, **token bridging**, **swapping**, and **unified balance management** β€” built for backends, CLIs, and custom UI integrations. -## Packages +> ⚑ Powering next-generation cross-chain apps with a single interface. -This monorepo contains two main packages: +--- -### [@avail-project/nexus-core](./packages/core/) - -**Headless SDK for cross-chain operations** - -- No React dependencies -- Direct chain abstraction integration +## πŸ“¦ Installation ```bash npm install @avail-project/nexus-core ``` -[πŸ“– Core Documentation](./packages/core/README.md) +--- -### [@avail-project/nexus-widgets](./packages/widgets/) +## πŸš€ Quick Start -**React components for cross-chain transactions** +```typescript +import { NexusSDK, NEXUS_EVENTS } from '@avail-project/nexus-core'; -- Ready-to-use React widgets -- Drop-in bridge, transfer, bridge-and-execute and execute components +// Initialize SDK +const sdk = new NexusSDK({ network: 'mainnet' }); +await sdk.initialize(provider); // Your EVM-compatible wallet provider + +// (Optional) Add TRON support +const tronLinkAdapter = new TronLinkAdapter(); +sdk.addTron(tronLinkAdapter); + +// --------------------------- +// 1️⃣ Get unified balances +// --------------------------- +const balances = await sdk.getUnifiedBalances(false); // false = CA balances only +console.log('Balances:', balances); + +// --------------------------- +// 2️⃣ Bridge tokens +// --------------------------- +const bridgeResult = await sdk.bridge( + { + token: 'USDC', + amount: 1_500_000n, + recipient: '0x...' // Optional + toChainId: 137, // Polygon + }, + { + onEvent: (event) => { + if (event.name === NEXUS_EVENTS.STEPS_LIST) console.log('Bridge steps:', event.args); + if (event.name === NEXUS_EVENTS.STEP_COMPLETE) console.log('Step completed:', event.args); + }, + }, +); + +// --------------------------- +// 3️⃣ Transfer tokens +// --------------------------- +const transferResult = await sdk.bridgeAndTransfer( + { + token: 'ETH', + amount: 1_500_000n, + toChainId: 1, // Ethereum + recipient: '0x742d35Cc6634C0532925a3b8D4C9db96c4b4Db45', + }, + { + onEvent: (event) => { + if (event.name === NEXUS_EVENTS.STEPS_LIST) console.log('Transfer steps:', event.args); + if (event.name === NEXUS_EVENTS.STEP_COMPLETE) console.log('Step completed:', event.args); + }, + }, +); + +// --------------------------- +// 4️⃣ Execute a contract +// --------------------------- +const executeResult = await sdk.execute( + { + to: '0x...', + value: 0n, + data: '0x...', + toChainId: 1, + tokenApproval: { token: 'USDC', amount: 10000n, spender: "0x..." }, + }, + { + onEvent: (event) => { + if (event.name === NEXUS_EVENTS.STEPS_LIST) console.log('Execute steps:', event.args); + if (event.name === NEXUS_EVENTS.STEP_COMPLETE) console.log('Step completed:', event.args); + }, + }, +); + +// --------------------------- +// 5️⃣ Bridge and Execute +// --------------------------- +const bridgeAndExecuteResult = await sdk.bridgeAndExecute( + { + token: 'USDC', + amount: 100_000_000n, + toChainId: 1, + sourceChains: [8453], + execute: { + to: '0x...', + data: '0x...', + tokenApproval: { token: 'USDC', amount: 100_000_000n, spender: "0x..." }, + }, + }, + { + onEvent: (event) => { + if (event.name === NEXUS_EVENTS.STEPS_LIST) console.log('Bridge+Execute steps:', event.args); + if (event.name === NEXUS_EVENTS.STEP_COMPLETE) console.log('Step completed:', event.args); + }, + }, +); + +// --------------------------- +// 6️⃣ Swap tokens +// --------------------------- +const swapResult = await sdk.swapWithExactIn( + { + from: [ + { chainId: 10, amount: 1_000_000n, tokenAddress: '0x...' }, + ], + toChainId: 8453, + toTokenAddress: '0x...', + }, + { + onEvent: (event) => console.log('Swap event:', event), + }, +); -```bash -npm install @avail-project/nexus-widgets ``` -[Widgets Documentation](./packages/widgets/README.md) +--- -## Supported Networks +## ✨ Core Features -### Mainnet Chains +- **Cross-chain bridging** β€” Move tokens seamlessly across 16+ chains. +- **Cross-chain swaps** β€” Execute EXACT_IN and EXACT_OUT swaps between any supported networks. +- **Unified balances** β€” Aggregate user assets and balances across all connected chains. +- **Optimized transfers** β€” Automatically choose the most efficient transfer route. +- **Contract execution** β€” Call smart contracts with automatic bridging and funding logic. +- **Transaction simulation** β€” Estimate gas, fees, and required approvals before sending. +- **Complete testnet coverage** β€” Full multi-chain test environment. +- **Comprehensive utilities** β€” Address, token, and chain helpers built in. -| Network | Chain ID | Native Currency | Status | -| --------- | -------- | --------------- | ------ | -| Ethereum | 1 | ETH | βœ… | -| Optimism | 10 | ETH | βœ… | -| Polygon | 137 | MATIC | βœ… | -| Arbitrum | 42161 | ETH | βœ… | -| Avalanche | 43114 | AVAX | βœ… | -| Base | 8453 | ETH | βœ… | -| Scroll | 534352 | ETH | βœ… | -| Sophon | 50104 | SOPH | βœ… | -| Kaia | 8217 | KAIA | βœ… | -| BNB | 56 | BNB | βœ… | -| HyperEVM | 999 | HYPE | βœ… | +--- -**Testnet Chains:** +## 🧠 Smart Optimizations -| Network | Chain ID | Native Currency | Status | -| ---------------- | -------- | --------------- | ------ | -| Optimism Sepolia | 11155420 | ETH | βœ… | -| Polygon Amoy | 80002 | MATIC | βœ… | -| Arbitrum Sepolia | 421614 | ETH | βœ… | -| Base Sepolia | 84532 | ETH | βœ… | -| Sepolia | 11155111 | ETH | βœ… | -| Monad Testnet | 10143 | MON | βœ… | +### πŸ” Bridge Skip Optimization -## Supported Tokens +During **bridge-and-execute** operations, the SDK checks whether sufficient funds already exist on the destination chain: -| Token | Networks | -| ----- | -------------- | -| ETH | All EVM chains | -| USDC | All supported | -| USDT | All supported | +- **Balance detection** β€” Verifies token and gas availability. +- **Integrated gas supply** β€” Provides gas alongside bridged tokens. +- **Adaptive bridging** β€” Skips unnecessary bridging or transfers only the shortfall. +- **Seamless fallback** β€” Uses chain abstraction if local funds are insufficient. -## πŸš€ Quick Examples +### ⚑ Direct Transfer Optimization -### Headless SDK +For transfers, the SDK automatically chooses the most efficient execution path: + +- **Local balance checking** β€” Confirms token and gas availability on the target chain. +- **Direct EVM transfers** β€” Uses native transfers where possible (faster, cheaper). +- **Chain abstraction fallback** β€” Uses CA routing only when required. +- **Universal compatibility** β€” Works with both native tokens (ETH, MATIC) and ERC-20s (USDC, USDT). + +--- + +## πŸ—οΈ Initialization ```typescript -import { NexusSDK } from '@avail-project/nexus-core'; +import { NexusSDK, type NexusNetwork } from '@avail-project/nexus-core'; +// Mainnet const sdk = new NexusSDK({ network: 'mainnet' }); -await sdk.initialize(provider); -// Bridge tokens -const result = await sdk.bridge({ - token: 'USDC', - amount: 100, - chainId: 137, +// Testnet +const sdkTest = new NexusSDK({ network: 'testnet' }); + +// Initialize with wallet provider +await sdk.initialize(window.ethereum); +``` + +--- + +## πŸ“‘ Event Handling + +**All main SDK functions support the `onEvent` hook**: + +- `bridge` +- `bridgeAndTransfer` +- `execute` +- `bridgeAndExecute` +- `swapWithExactIn` / `swapWithExactOut` + +Example usage for **progress steps**: + +```typescript +sdk.bridge({...}, { + onEvent: (event) => { + if(event.name === NEXUS_EVENTS.STEPS_LIST) { + // Store list of steps + } else if(event.name === NEXUS_EVENTS.STEP_COMPLETE) { + // Mark step as done + } + } }); ``` -### React Widgets +Additional hooks for user interactions: ```typescript -import { NexusProvider, BridgeButton } from '@avail-project/nexus-widgets'; - -function App() { - return ( - - - {({ onClick, isLoading }) => ( - - )} - - - ); -} +sdk.setOnIntentHook(({ intent, allow, deny, refresh }) => { + if (userApproves) allow(); + else deny(); +}); + +sdk.setOnSwapIntentHook(({ intent, allow, deny, refresh }) => { + if (userApproves) allow(); + else deny(); +}); + +sdk.setOnAllowanceHook(({ sources, allow, deny }) => { + allow(['min']); // 'max' or custom bigint[] supported +}); ``` -## Documentation +### Consistent Event Pattern -- [Core SDK Documentation](./packages/core/README.md) - Headless SDK API reference -- [Widgets Documentation](./packages/widgets/README.md) - React components guide -- [API Documentation](https://docs.availproject.org/api-reference/avail-nexus-sdk) +| Operation Type | Event Name | Description | +| ---------------- | -------------------- | --------------------------------------- | +| Bridge / Execute | `STEPS_LIST` | Full ordered list of steps emitted once | +| | `STEP_COMPLETE` | Fired per completed step with data | +| Swap | `SWAP_STEP_COMPLETE` | Fired per completed step with data | -## πŸ› οΈ Development +All events include `typeID`, `transactionHash`, `explorerURL`, and `error` (if any). -```bash -# Install dependencies -pnpm install +--- -# Build all packages -pnpm build +## πŸ’° Balance Operations -# Run tests -pnpm test +```typescript +const unifiedBridgeBalances = await sdk.getBalancesForBridge(); // Returns balances that can be used in bridge operations +--- +const swapBalances = await sdk.getBalancesForSwap(); // Returns balances that can be used in swap operations ``` -## Monorepo & Workspace +--- -- Packages live under `packages/` and are linked via pnpm workspaces. -- Internal shared code stays in `@nexus/commons` (private). It is imported in source during development and bundled into `dist/commons` at build so consumers never install it directly. -- Published package names used everywhere (dev and build): - - `@avail-project/nexus-core` - - `@avail-project/nexus-widgets` +## πŸŒ‰ Bridge Operations -### TS path mapping for local DX +```typescript +const result = await sdk.bridge({ + token: 'USDC', + amount: 83_500_000n, + toChainId: 137, + recipient: '0x....', +}); -- Dev imports use published names while resolving locally: - - Root `tsconfig.json` maps `@avail-project/nexus-core` β†’ `packages/core/*` - - Widgets `tsconfig.json` also maps `@avail-project/nexus-core` β†’ `../core/*` -- Keep importing `@nexus/commons` in source; build rewrites it to `./commons` inside the dist. +const simulation = await sdk.simulateBridge({ + token: 'USDC', + amount: 83_500_000n, + toChainId: 137, + recipient: '0x....', +}); +``` -### Workspace versions and overrides +--- -- Root `package.json` defines pnpm overrides to pin shared versions: - - `typescript`, `rollup`, `decimal.js`, `viem` -- Update once for all packages: +## πŸ” Transfer Operations -```bash -pnpm -r up typescript rollup decimal.js viem +```typescript +const result = await sdk.bridgeAndTransfer({ + token: 'USDC', + amount: 1_530_000n, + toChainId: 42161, + recipient: '0x...', +}); +const simulation = await sdk.simulateBridgeAndTransfer({ + token: 'USDC', + amount: 1_530_000n, // = 1.53 USDC + toChainId: 42161, + recipient: '0x...', +}); ``` -## Releases & Scripts +--- -- Scripts live in `scripts/`. Both core and widgets have an interactive wizard and CI-friendly flags. -- Commons is always bundled; it is removed from published dependencies. +## βš™οΈ Execute & Bridge + Execute -### Dev (pre-release) policy +```typescript +// Direct contract execution +const result = await sdk.execute({ + toChainId: 1, + to: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', + data: '0x...', + tokenApproval: { token: 'USDC', amount: 1000000n, spender: '0x...' }, +}); -- Pre-release numbers progress `0..9` and roll over to the next patch: - - `0.0.2-beta.0 β†’ 0.0.2-beta.1 … β†’ 0.0.2-beta.9 β†’ 0.0.3-beta.0` -- Widgets depends on the most recently published core prerelease by publish time (not by semver magnitude). +// Bridge and execute +const result2 = await sdk.bridgeAndExecute({ + token: 'USDC', + amount: 100_000_000n, + toChainId: 1, + sourceChains: [8453], + execute: { + to: '0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE', + data: '0x...', + tokenApproval: { token: 'USDC', amount: 100_000_000n, spender: '0x...' }, + }, +}); +``` -### Flags +--- -- `--yes` or `--ci`: skip interactive prompts (useful in CI) -- `--dry-run` or `-n`: simulate publish (runs `npm pack`, skips git push/tag) +## πŸ”„ Swap Operations -### Core examples +```typescript +const swapResult = await sdk.swapWithExactIn( + { + from: [{ chainId: 10, amount: 1_000_000n, tokenAddress: '0x...' }], + toChainId: 8453, + toTokenAddress: '0x...', + }, + { onEvent: (event) => console.log(event) }, +); +``` -```bash -# Interactive dev prerelease (choose tag like beta/alpha/dev) -./scripts/release-core.sh +### Swap Types -# Non-interactive dev prerelease (beta), dry-run -./scripts/release-core.sh dev patch beta --yes --dry-run +| Type | Description | Example | +| ------------- | ------------------------------------------------- | --------------------------- | +| **EXACT_IN** | Specify the amount you’re spending; output varies | β€œSwap 100 USDC for max ETH” | +| **EXACT_OUT** | Specify the amount you’ll receive; input varies | β€œGet exactly 1 ETH” | -# Non-interactive dev prerelease (beta), publish for real -./scripts/release-core.sh dev patch beta --yes +--- -# Production release (patch) -./scripts/release-core.sh prod patch --yes +## 🧩 Intent Management + +```typescript +const intents = await sdk.getMyIntents(1); +console.log('Active intents:', intents); ``` -### Widgets examples +--- -```bash -# Interactive dev prerelease (requires a matching core prerelease on npm) -./scripts/release-widgets.sh +## πŸ› οΈ Utilities + +```typescript +import { CHAIN_METADATA } from '@avail-project/nexus-core'; + +const isValid = sdk.utils.isValidAddress('0x...'); +const chainMeta = CHAIN_METADATA[137]; +const formatted = sdk.utils.formatTokenBalance('0.000294700412452583', { + symbol: 'ETH', + decimals: 18, +}); // "~0.0β‚„2552 ETH" +``` + +--- -# Non-interactive dev prerelease (beta), resolves latest core beta by timestamp, dry-run -./scripts/release-widgets.sh dev patch beta --yes --dry-run +## 🧾 Error Handling -# Production release (patch) – ensure core is published first -./scripts/release-widgets.sh prod patch --yes +```typescript +try { + await sdk.bridge({ token: 'USDC', amount: 1_530_000n, toChainId: 137 }); +} catch (err) { + if (err instanceof NexusError) { + console.error(`[${err.code}] ${err.message}`); + } else { + console.error('Unexpected error:', err); + } +} ``` -### Local tarballs (no publish) +--- -```bash -# Build and create .tgz files for local install -./scripts/local-pack.sh +## 🧠 TypeScript Support -# In another project -pnpm add /absolute/path/to/dist-tarballs/avail-project-nexus-core-*.tgz \ - /absolute/path/to/dist-tarballs/avail-project-nexus-widgets-*.tgz +```typescript +import type { + BridgeParams, + ExecuteParams, + TransferParams, + SwapResult, + NexusNetwork, + TokenMetadata, +} from '@avail-project/nexus-core'; ``` -## License +--- + +## 🌐 Supported Networks + +### Mainnets + +| Network | Chain ID | Native | Status | +| --------- | --------- | ------ | ------ | +| Ethereum | 1 | ETH | βœ… | +| Optimism | 10 | ETH | βœ… | +| Polygon | 137 | MATIC | βœ… | +| Arbitrum | 42161 | ETH | βœ… | +| Avalanche | 43114 | AVAX | βœ… | +| Base | 8453 | ETH | βœ… | +| Scroll | 534352 | ETH | βœ… | +| Sophon | 50104 | SOPH | βœ… | +| Kaia | 8217 | KAIA | βœ… | +| BNB | 56 | BNB | βœ… | +| HyperEVM | 999 | HYPE | βœ… | +| TRON | 728126428 | TRX | βœ… | + +### Testnets + +| Network | Chain ID | Native | Status | +| ---------------- | -------- | ------ | ------ | +| Optimism Sepolia | 11155420 | ETH | βœ… | +| Polygon Amoy | 80002 | MATIC | βœ… | +| Arbitrum Sepolia | 421614 | ETH | βœ… | +| Base Sepolia | 84532 | ETH | βœ… | +| Sepolia | 11155111 | ETH | βœ… | +| Monad Testnet | 10143 | MON | βœ… | + +--- + +## πŸ’Ž Supported Tokens + +| Token | Name | Decimals | Availability | +| ----- | ---------- | -------- | -------------- | +| ETH | Ethereum | 18 | All EVM chains | +| USDC | USD Coin | 6 | All supported | +| USDT | Tether USD | 6 | All supported | + +--- + +## πŸ”— Resources -MIT +- **GitHub:** [availproject/nexus-sdk](https://github.com/availproject/nexus-sdk) +- **Docs:** [docs.availproject.org](https://docs.availproject.org/nexus/avail-nexus-sdk) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..5c10ad02 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4273 @@ +{ + "name": "@avail-project/nexus-core", + "version": "1.0.0-beta.50", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@avail-project/nexus-core", + "version": "1.0.0-beta.50", + "license": "MIT", + "dependencies": { + "@avail-project/ca-common": "1.0.0-rc.1", + "@cosmjs/proto-signing": "0.34.0", + "@cosmjs/stargate": "0.34.0", + "@metamask/safe-event-emitter": "3.1.2", + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/exporter-logs-otlp-http": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@starkware-industries/starkware-crypto-utils": "^0.2.1", + "@tronweb3/tronwallet-abstract-adapter": "^1.1.9", + "axios": "^1.12.2", + "buffer": "6.0.3", + "decimal.js": "^10.6.0", + "es-toolkit": "^1.40.0", + "it-ws": "^6.1.5", + "long": "^5.3.2", + "msgpackr": "^1.11.5", + "tronweb": "^6.0.4", + "tslib": "2.8.1", + "viem": "^2.31.7" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^25.0.8", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-node-resolve": "^15.3.1", + "@rollup/plugin-typescript": "^11.1.6", + "rollup": "^4.52.4", + "rollup-plugin-dts": "^6.2.3", + "rollup-plugin-typescript2": "0.36.0", + "typedoc": "0.28.14", + "typedoc-plugin-extras": "4.0.1", + "typedoc-plugin-missing-exports": "4.1.2", + "typedoc-plugin-rename-defaults": "0.7.3", + "typedoc-theme-fresh": "0.2.1", + "typescript": "^5.9.3" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "license": "MIT" + }, + "node_modules/@avail-project/ca-common": { + "version": "1.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@avail-project/ca-common/-/ca-common-1.0.0-rc.1.tgz", + "integrity": "sha512-6qOnrqFDa3HG1DYxdjUHoA9FKHKfUdhOdLdYISY3mI4PDI88L3dl1HRoQrmdL4RKHUrPXbnaVxawYtX+RU2jOw==", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.6.0", + "@improbable-eng/grpc-web": "^0.15.0", + "browser-headers": "^0.4.1", + "es-toolkit": "^1.39.7", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "@cosmjs/proto-signing": "^0.34.0", + "@cosmjs/stargate": "^0.34.0", + "axios": "^1.10.0", + "decimal.js": "^10.6.0", + "long": "^5.3.2", + "msgpackr": "^1.11.4", + "viem": "^2.31.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.1.tgz", + "integrity": "sha512-ckS3+vyJb5qGpEYv/s1OebUHDi/xSNtfgw1wqKZo7MR9F2z+qXr0q5XagafAG/9O0QPVIUfST0smluYSTpYFkg==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@cosmjs/amino": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/amino/-/amino-0.34.1.tgz", + "integrity": "sha512-9kfGtJf/skhS5O/xKkCtSJgLHcFK1VoEEzCFJsMV4YLvlMIZznVmzwqlOwMtI/dYaVQpyzfHedLwxph2bfIIQA==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/crypto": "^0.34.1", + "@cosmjs/encoding": "^0.34.1", + "@cosmjs/math": "^0.34.1", + "@cosmjs/utils": "^0.34.1" + } + }, + "node_modules/@cosmjs/crypto": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.34.1.tgz", + "integrity": "sha512-fouXCXB4vNKLi9hyhdLL1elJ12reZH1UxJK4JPEkMOSaLAPClIHEu8NZ8cXYNlu2yTi86NX+I0TE4cEPsvSpWA==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/encoding": "^0.34.1", + "@cosmjs/math": "^0.34.1", + "@cosmjs/utils": "^0.34.1", + "@noble/curves": "^1.9.2", + "@noble/hashes": "^1", + "bn.js": "^5.2.0", + "libsodium-wrappers-sumo": "^0.7.11" + } + }, + "node_modules/@cosmjs/encoding": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/encoding/-/encoding-0.34.1.tgz", + "integrity": "sha512-5+Ta7v05UxAqicKgk16c4rg0vsHPbzJOp5rfOxbiiJOfu3fVJkmmQgz5VuTrsvl5AZf9pulen7b8SK/rQYzKDg==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "bech32": "^1.1.4", + "readonly-date": "^1.0.0" + } + }, + "node_modules/@cosmjs/json-rpc": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/json-rpc/-/json-rpc-0.34.1.tgz", + "integrity": "sha512-hSq4eEQ2cc7YVvvJER0Fr+Y02ZzKNz02+wHTetVh0JcniEP71ZDQC00E+/je817V+vt7mN1dxoeeC5IY9kKtRw==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/stream": "^0.34.1", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/math": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.34.1.tgz", + "integrity": "sha512-QRFMcxCZ5JMQr6ANU7+5mrPdx3XTT0/6jGHY1wP4q4T3IfgHJItu5HqOi22VbSNb/IWpDWwTS46eqvbXyRMeqg==", + "license": "Apache-2.0", + "dependencies": { + "bn.js": "^5.2.0" + } + }, + "node_modules/@cosmjs/proto-signing": { + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@cosmjs/proto-signing/-/proto-signing-0.34.0.tgz", + "integrity": "sha512-1/f4JNSAhsP5lr7fdCJxT+qkWqeDq8vViwCilqMIkqvxLAcf6FxEkvmTOpYBAdOT5fVe3+5nZ5GX5FYMq1tdfA==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/amino": "^0.34.0", + "@cosmjs/crypto": "^0.34.0", + "@cosmjs/encoding": "^0.34.0", + "@cosmjs/math": "^0.34.0", + "@cosmjs/utils": "^0.34.0", + "cosmjs-types": "^0.9.0" + } + }, + "node_modules/@cosmjs/socket": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/socket/-/socket-0.34.1.tgz", + "integrity": "sha512-+MiuC+WDzaA8cU635JEK8tY/ImrRRcPzE+0ExoL4ZzqgzIcXf1hbgmqgKF85yyhGnk/nMTLsEteyoJIaWxsTtA==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/stream": "^0.34.1", + "isomorphic-ws": "^4.0.1", + "ws": "^7", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/stargate": { + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/@cosmjs/stargate/-/stargate-0.34.0.tgz", + "integrity": "sha512-FU/A0OdkNKfqQ4d7CC8KceTVGoCy3BemFgVjbXL/K5FnAafEjqqWInQ531oNvBejpMdm1NSkfbp97CfILeTW7A==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/amino": "^0.34.0", + "@cosmjs/encoding": "^0.34.0", + "@cosmjs/math": "^0.34.0", + "@cosmjs/proto-signing": "^0.34.0", + "@cosmjs/stream": "^0.34.0", + "@cosmjs/tendermint-rpc": "^0.34.0", + "@cosmjs/utils": "^0.34.0", + "cosmjs-types": "^0.9.0" + } + }, + "node_modules/@cosmjs/stream": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/stream/-/stream-0.34.1.tgz", + "integrity": "sha512-EVbSFUnBMHkRX7Oy+JrGe3i8TjxnRs5BKJa8CZnn4ZHSWf6Krw+nWlaJ7FixFttd+PX8c+Agzw+Qhv3pm9HPVg==", + "license": "Apache-2.0", + "dependencies": { + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/tendermint-rpc": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.34.1.tgz", + "integrity": "sha512-cYiyln2wmYvY4/n4ehfAjoFf3SoO8P8mgioqdDb6QItTeQmwX4lDU+YOv3b2KzoA6ufH6SX1nJyPJ3X0YJhfwA==", + "license": "Apache-2.0", + "dependencies": { + "@cosmjs/crypto": "^0.34.1", + "@cosmjs/encoding": "^0.34.1", + "@cosmjs/json-rpc": "^0.34.1", + "@cosmjs/math": "^0.34.1", + "@cosmjs/socket": "^0.34.1", + "@cosmjs/stream": "^0.34.1", + "@cosmjs/utils": "^0.34.1", + "cross-fetch": "^4.1.0", + "readonly-date": "^1.0.0", + "xstream": "^11.14.0" + } + }, + "node_modules/@cosmjs/utils": { + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.34.1.tgz", + "integrity": "sha512-OjevgFwbVN7t8afmFF8A3rj80jQnOXqwdGEzfv7jbYxTvhUGPa8SvpeaulhWQYQ49K3zlIuB9a2PJWdf1H9Udw==", + "license": "Apache-2.0" + }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.15.0.tgz", + "integrity": "sha512-L5IHdZIDa4bG4yJaOzfasOH/o22MCesY0mx+n6VATbaiCtMeR59pdRqYk4bEiQkIHfxsHPNgdi7VJlZb2FhdMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.15.0", + "@shikijs/langs": "^3.15.0", + "@shikijs/themes": "^3.15.0", + "@shikijs/types": "^3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@improbable-eng/grpc-web": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.15.0.tgz", + "integrity": "sha512-ERft9/0/8CmYalqOVnJnpdDry28q+j+nAlFFARdjyxXDJ+Mhgv9+F600QC8BR9ygOfrXRlAk6CvST2j+JCpQPg==", + "license": "Apache-2.0", + "dependencies": { + "browser-headers": "^0.4.1" + }, + "peerDependencies": { + "google-protobuf": "^3.14.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@metamask/safe-event-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz", + "integrity": "sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==", + "license": "ISC", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", + "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.208.0.tgz", + "integrity": "sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/sdk-logs": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.208.0.tgz", + "integrity": "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-transformer": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.208.0.tgz", + "integrity": "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@opentelemetry/sdk-metrics": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0", + "protobufjs": "^7.3.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz", + "integrity": "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", + "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "25.0.8", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz", + "integrity": "sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz", + "integrity": "sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", + "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", + "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", + "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", + "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", + "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", + "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", + "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", + "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", + "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", + "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", + "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", + "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", + "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", + "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", + "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", + "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", + "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", + "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", + "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", + "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", + "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", + "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", + "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.15.0.tgz", + "integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.15.0.tgz", + "integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@starkware-industries/starkware-crypto-utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@starkware-industries/starkware-crypto-utils/-/starkware-crypto-utils-0.2.1.tgz", + "integrity": "sha512-rA5O9b53zaoBOQwQxBd0cbumFbQoBm9NH/vfu+o0Cq3oouEbNPALneLlLjOmFEId2/WOJ5ecC64rFLI/PwuIPQ==", + "license": "Apache-2.0", + "dependencies": { + "assert": "^2.0.0", + "bip39": "^3.0.4", + "bn.js": "^4.12.0", + "brorand": "^1.1.0", + "buffer": "^6.0.3", + "crypto-browserify": "^3.12.0", + "elliptic": "^6.5.4", + "enc-utils": "^3.0.0", + "ethereumjs-wallet": "^1.0.2", + "hash.js": "^1.1.7", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "js-sha3": "^0.8.0", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1", + "stream-browserify": "^3.0.0" + } + }, + "node_modules/@starkware-industries/starkware-crypto-utils/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/@tronweb3/tronwallet-abstract-adapter": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@tronweb3/tronwallet-abstract-adapter/-/tronwallet-abstract-adapter-1.1.10.tgz", + "integrity": "sha512-gZExaEZwPfI9oI7qSi56p/5Zl/DEzo1JlcD3lQzz1cuz0rZmEFpIqDS2mhY4r/IwEPwilIU7lg7T8/RQDVk9gA==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "tronweb": "6" + }, + "engines": { + "node": ">=16", + "pnpm": ">=7" + } + }, + "node_modules/@types/bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/secp256k1": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.7.tgz", + "integrity": "sha512-Rcvjl6vARGAKRO6jHeKMatGrvOMGrR/AR11N1x2LqintPCyDZ7NBhrh238Z2VZc7aM7KIwnFpFQ7fnfK4H/9Qw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abitype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", + "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "license": "ISC", + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/browser-headers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz", + "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==", + "license": "Apache-2.0" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "license": "MIT", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmjs-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/cosmjs-types/-/cosmjs-types-0.9.0.tgz", + "integrity": "sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==", + "license": "Apache-2.0" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/enc-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/enc-utils/-/enc-utils-3.0.0.tgz", + "integrity": "sha512-e57t/Z2HzWOLwOp7DZcV0VMEY8t7ptWwsxyp6kM2b2zrk6JqIpXxzkruHAMiBsy5wg9jp/183GdiRXCvBtzsYg==", + "license": "MIT", + "dependencies": { + "is-typedarray": "1.0.0", + "typedarray-to-buffer": "3.1.5" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-toolkit": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.41.0.tgz", + "integrity": "sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "license": "MIT", + "dependencies": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "node_modules/ethereumjs-util": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", + "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", + "license": "MPL-2.0", + "dependencies": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ethereumjs-wallet": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-1.0.2.tgz", + "integrity": "sha512-CCWV4RESJgRdHIvFciVQFnCHfqyhXWchTPlkfp28Qc53ufs+doi5I/cV2+xeK9+qEo25XCWfP9MiL+WEPAZfdA==", + "deprecated": "New package name format for new versions: @ethereumjs/wallet. Please update.", + "license": "MIT", + "dependencies": { + "aes-js": "^3.1.2", + "bs58check": "^2.1.2", + "ethereum-cryptography": "^0.1.3", + "ethereumjs-util": "^7.1.2", + "randombytes": "^2.1.0", + "scrypt-js": "^3.0.1", + "utf8": "^3.0.0", + "uuid": "^8.3.2" + } + }, + "node_modules/ethers": { + "version": "6.13.5", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz", + "integrity": "sha512-+knKNieu5EKRThQJWwqaJ10a6HE9sSehGeqWN65//wE7j47ZpFhKAnHB/JJFibwwg61I/koxaPsXbXpD/skNOQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/ethers/node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT" + }, + "node_modules/ethers/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, + "node_modules/ethers/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/ethers/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/event-iterator": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz", + "integrity": "sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==", + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/it-stream-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/it-stream-types/-/it-stream-types-2.0.2.tgz", + "integrity": "sha512-Rz/DEZ6Byn/r9+/SBCuJhpPATDF9D+dz5pbgSUyBsCDtza6wtNATrz/jz1gDyNanC3XdLboriHnOC925bZRBww==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/it-ws": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/it-ws/-/it-ws-6.1.5.tgz", + "integrity": "sha512-uWjMtpy5HqhSd/LlrlP3fhYrr7rUfJFFMABv0F5d6n13Q+0glhZthwUKpEAVhDrXY95Tb1RB5lLqqef+QbVNaw==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@types/ws": "^8.2.2", + "event-iterator": "^2.0.0", + "it-stream-types": "^2.0.1", + "uint8arrays": "^5.0.0", + "ws": "^8.4.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/it-ws/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keccak": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keccak/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/libsodium-sumo": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/libsodium-sumo/-/libsodium-sumo-0.7.15.tgz", + "integrity": "sha512-5tPmqPmq8T8Nikpm1Nqj0hBHvsLFCXvdhBFV7SGOitQPZAA6jso8XoL0r4L7vmfKXr486fiQInvErHtEvizFMw==", + "license": "ISC" + }, + "node_modules/libsodium-wrappers-sumo": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/libsodium-wrappers-sumo/-/libsodium-wrappers-sumo-0.7.15.tgz", + "integrity": "sha512-aSWY8wKDZh5TC7rMvEdTHoyppVq/1dTSAeAR7H6pzd6QRT3vQWcT5pGwCotLcpPEOLXX6VvqihSPkpEhYAjANA==", + "license": "ISC", + "dependencies": { + "libsodium-sumo": "^0.7.15" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/msgpackr": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, + "node_modules/multiformats": { + "version": "13.4.1", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.1.tgz", + "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==", + "license": "Apache-2.0 OR MIT" + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ox": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.6.tgz", + "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readonly-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", + "integrity": "sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==", + "license": "Apache-2.0" + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "license": "MPL-2.0", + "dependencies": { + "bn.js": "^5.2.0" + }, + "bin": { + "rlp": "bin/rlp" + } + }, + "node_modules/rollup": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", + "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.2", + "@rollup/rollup-android-arm64": "4.53.2", + "@rollup/rollup-darwin-arm64": "4.53.2", + "@rollup/rollup-darwin-x64": "4.53.2", + "@rollup/rollup-freebsd-arm64": "4.53.2", + "@rollup/rollup-freebsd-x64": "4.53.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", + "@rollup/rollup-linux-arm-musleabihf": "4.53.2", + "@rollup/rollup-linux-arm64-gnu": "4.53.2", + "@rollup/rollup-linux-arm64-musl": "4.53.2", + "@rollup/rollup-linux-loong64-gnu": "4.53.2", + "@rollup/rollup-linux-ppc64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-musl": "4.53.2", + "@rollup/rollup-linux-s390x-gnu": "4.53.2", + "@rollup/rollup-linux-x64-gnu": "4.53.2", + "@rollup/rollup-linux-x64-musl": "4.53.2", + "@rollup/rollup-openharmony-arm64": "4.53.2", + "@rollup/rollup-win32-arm64-msvc": "4.53.2", + "@rollup/rollup-win32-ia32-msvc": "4.53.2", + "@rollup/rollup-win32-x64-gnu": "4.53.2", + "@rollup/rollup-win32-x64-msvc": "4.53.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-dts": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.2.3.tgz", + "integrity": "sha512-UgnEsfciXSPpASuOelix7m4DrmyQgiaWBnvI0TM4GxuDh5FkqW8E5hu57bCxXB90VvR1WNfLV80yEDN18UogSA==", + "dev": true, + "license": "LGPL-3.0-only", + "dependencies": { + "magic-string": "^0.30.17" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.27.1" + }, + "peerDependencies": { + "rollup": "^3.29.4 || ^4", + "typescript": "^4.5 || ^5.0" + } + }, + "node_modules/rollup-plugin-typescript2": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.36.0.tgz", + "integrity": "sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^4.1.2", + "find-cache-dir": "^3.3.2", + "fs-extra": "^10.0.0", + "semver": "^7.5.4", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "rollup": ">=1.26.3", + "typescript": ">=2.4.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "license": "MIT" + }, + "node_modules/secp256k1": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.4.tgz", + "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/secp256k1/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-observable": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", + "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tronweb": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tronweb/-/tronweb-6.1.0.tgz", + "integrity": "sha512-ANEr2YneA2frXTpsxDR21yk2cJLIvOdPe7dg7gu96TyqfVbS9eCrguNuN+qCUZOC/zW3n6R880bBDbEWKZiWzA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.26.10", + "axios": "1.12.2", + "bignumber.js": "9.1.2", + "ethereum-cryptography": "2.2.1", + "ethers": "6.13.5", + "eventemitter3": "5.0.1", + "google-protobuf": "3.21.4", + "semver": "7.7.1", + "validator": "13.15.20" + } + }, + "node_modules/tronweb/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tronweb/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tronweb/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tronweb/node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tronweb/node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/tronweb/node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/tronweb/node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/tronweb/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/tronweb/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typedoc": { + "version": "0.28.14", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.14.tgz", + "integrity": "sha512-ftJYPvpVfQvFzpkoSfHLkJybdA/geDJ8BGQt/ZnkkhnBYoYW6lBgPQXu6vqLxO4X75dA55hX8Af847H5KXlEFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.12.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.8.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" + } + }, + "node_modules/typedoc-plugin-extras": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/typedoc-plugin-extras/-/typedoc-plugin-extras-4.0.1.tgz", + "integrity": "sha512-ab3T37ukHCwBjwBJpAcWdxy/XAaLdUyy5pwbgF9WDqCRN0DTJmJPLew9Kv6qrx5qnxiyfe6F4Og+PUp15kJWLw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc": "0.27.x || 0.28.x" + } + }, + "node_modules/typedoc-plugin-missing-exports": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-4.1.2.tgz", + "integrity": "sha512-WNoeWX9+8X3E3riuYPduilUTFefl1K+Z+5bmYqNeH5qcWjtnTRMbRzGdEQ4XXn1WEO4WCIlU0vf46Ca2y/mspg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc": "^0.28.1" + } + }, + "node_modules/typedoc-plugin-rename-defaults": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/typedoc-plugin-rename-defaults/-/typedoc-plugin-rename-defaults-0.7.3.tgz", + "integrity": "sha512-fDtrWZ9NcDfdGdlL865GW7uIGQXlthPscURPOhDkKUe4DBQSRRFUf33fhWw41FLlsz8ZTeSxzvvuNmh54MynFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^8.0.0" + }, + "peerDependencies": { + "typedoc": ">=0.22.x <0.29.x" + } + }, + "node_modules/typedoc-theme-fresh": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/typedoc-theme-fresh/-/typedoc-theme-fresh-0.2.1.tgz", + "integrity": "sha512-LQMQka0wrgG+L/Ar/VgZbbq1d0JwzZbDm+N6jaKtMwSUXdWQ4RNBQSmr0y800uJzW4jtqtVGAYjgoDmNhmMPeg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typedoc": "^0.28.14" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/uint8arrays": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.0.tgz", + "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "multiformats": "^13.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "license": "MIT" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validator": { + "version": "13.15.20", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz", + "integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/viem": { + "version": "2.39.0", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.39.0.tgz", + "integrity": "sha512-rCN+IfnMESlrg/iPyyVL+M9NS/BHzyyNy72470tFmbTuscY3iPaZGMtJDcHKKV8TC6HV9DjWk0zWX6cpu0juyA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.1.0", + "isows": "1.0.7", + "ox": "0.9.6", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xstream": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/xstream/-/xstream-11.14.0.tgz", + "integrity": "sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==", + "license": "MIT", + "dependencies": { + "globalthis": "^1.0.1", + "symbol-observable": "^2.0.3" + } + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + } + } +} diff --git a/package.json b/package.json index 05085250..6bfc0eab 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,30 @@ { - "name": "nexus-sdk-monorepo", - "version": "0.0.2", - "private": true, - "description": "Nexus SDK monorepo - cross-chain transactions with minimal friction", + "name": "@avail-project/nexus-core", + "version": "1.0.0-beta.53", + "description": "Nexus headless SDK for cross-chain transactions", + "main": "./dist/index.js", + "module": "./dist/index.esm.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "README.md" + ], "scripts": { - "build:commons": "pnpm -F @nexus/commons build", - "build:core": "pnpm -F @nexus/commons build && pnpm -F @avail-project/nexus-core build", - "build:widgets": "pnpm -F @nexus/commons build && pnpm -F @avail-project/nexus-widgets build", - "build": "pnpm run build:core && pnpm run build:widgets", - "dev:core": "pnpm -F @avail-project/nexus-core dev", - "dev:widgets": "pnpm -F @avail-project/nexus-widgets dev", - "dev": "pnpm run dev:core & pnpm run dev:widgets", - "format": "prettier --write \"packages/**/*.{ts,tsx}\"", - "prepare": "husky install", - "typecheck": "pnpm -r typecheck", - "clean": "rimraf packages/widgets/dist packages/core/dist packages/commons/dist", - "clean:modules": "rimraf node_modules packages/widgets/node_modules packages/core/node_modules packages/commons/node_modules", - "release:core:dev": "./scripts/release-core.sh dev", - "release:core:prod": "./scripts/release-core.sh prod", - "release:widgets:dev": "./scripts/release-widgets.sh dev", - "release:widgets:prod": "./scripts/release-widgets.sh prod" + "build": "rollup -c", + "buildAndPack": "rollup -c && npm pack", + "dev": "rollup -c -w", + "docs": "typedoc --plugin typedoc-theme-fresh --theme fresh --options typedoc_html.json", + "clean": "rimraf dist dist-tarballs html_docs", + "typecheck": "tsc --noEmit", + "prepublishOnly": "npm run build" + }, + "sideEffects": false, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.js", + "require": "./dist/index.js" + } }, "keywords": [ "nexus", @@ -30,27 +35,48 @@ "web3", "chain", "abstraction", - "intent", - "unified", - "balance", - "sdk" + "headless" ], - "author": "decocereus", + "author": "decocereus, makyl", "license": "MIT", - "pnpm": { - "overrides": { - "typescript": "^5.0.0", - "rollup": "^4.0.0", - "decimal.js": "10.6.0", - "viem": "^2.0.0" - } + "dependencies": { + "@avail-project/ca-common": "1.0.0-rc.1", + "@cosmjs/proto-signing": "0.34.0", + "@cosmjs/stargate": "0.34.0", + "@metamask/safe-event-emitter": "3.1.2", + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/exporter-logs-otlp-http": "0.208.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@starkware-industries/starkware-crypto-utils": "^0.2.1", + "@tronweb3/tronwallet-abstract-adapter": "^1.1.9", + "axios": "^1.12.2", + "buffer": "6.0.3", + "decimal.js": "^10.6.0", + "es-toolkit": "^1.40.0", + "it-ws": "^6.1.5", + "long": "^5.3.2", + "msgpackr": "^1.11.5", + "tronweb": "^6.0.4", + "tslib": "2.8.1", + "viem": "^2.31.7" }, "devDependencies": { - "@rollup/plugin-alias": "^5.1.1", - "@types/node": "^20.0.0", - "husky": "^8.0.0", - "prettier": "^3.0.0", - "rimraf": "^5.0.0", - "typescript": "^5.0.0" + "@rollup/plugin-commonjs": "^25.0.8", + "@rollup/plugin-json": "6.1.0", + "@rollup/plugin-node-resolve": "^15.3.1", + "@rollup/plugin-typescript": "^11.1.6", + "rollup": "^4.52.4", + "rollup-plugin-dts": "^6.2.3", + "rollup-plugin-typescript2": "0.36.0", + "typedoc": "0.28.14", + "typedoc-plugin-extras": "4.0.1", + "typedoc-plugin-missing-exports": "4.1.2", + "typedoc-plugin-rename-defaults": "0.7.3", + "typedoc-theme-fresh": "0.2.1", + "typescript": "^5.9.3" + }, + "publishConfig": { + "access": "public" } } diff --git a/packages/commons/package.json b/packages/commons/package.json deleted file mode 100644 index ca155370..00000000 --- a/packages/commons/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@nexus/commons", - "version": "0.0.1", - "description": "Shared types, constants, and utilities for Nexus SDK", - "main": "./dist/index.js", - "module": "./dist/index.esm.js", - "types": "./dist/index.d.ts", - "author": "decocereus", - "private": true, - "sideEffects": false, - "scripts": { - "build": "rollup -c", - "dev": "rollup -c -w", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@cosmjs/proto-signing": "^0.34.0", - "fuels": "0.101.1", - "@arcana/ca-common": "1.0.1-alpha.6", - "decimal.js": "^10.6.0", - "viem": "^2.31.7" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-node-resolve": "^15.0.0", - "@rollup/plugin-typescript": "^11.0.0", - "rollup": "^4.0.0", - "rollup-plugin-dts": "^6.0.0", - "typescript": "^5.0.0" - } -} diff --git a/packages/commons/rollup.config.mjs b/packages/commons/rollup.config.mjs deleted file mode 100644 index 229c5a40..00000000 --- a/packages/commons/rollup.config.mjs +++ /dev/null @@ -1,62 +0,0 @@ -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import typescript from '@rollup/plugin-typescript'; -import json from '@rollup/plugin-json'; -import dts from 'rollup-plugin-dts'; -import { defineConfig } from 'rollup'; -import { createRequire } from 'module'; - -const require = createRequire(import.meta.url); -const packageJson = require('./package.json'); - -const isProduction = process.env.NODE_ENV === 'production'; -const shouldGenerateSourceMaps = false; - -// Base configuration -const baseConfig = { - input: 'index.ts', - plugins: [ - json(), - resolve({ - browser: true, - preferBuiltins: false, - }), - commonjs({ - include: /node_modules/, - }), - typescript({ - tsconfig: './tsconfig.json', - sourceMap: shouldGenerateSourceMaps, - }), - ], - external: [...Object.keys(packageJson.dependencies || {}), /^viem/], -}; - -export default defineConfig([ - // Build configurations - { - ...baseConfig, - output: [ - { - file: 'dist/index.js', - format: 'cjs', - sourcemap: shouldGenerateSourceMaps, - exports: 'named', - }, - { - file: 'dist/index.esm.js', - format: 'esm', - sourcemap: shouldGenerateSourceMaps, - exports: 'named', - }, - ], - }, - - // TypeScript declarations - { - input: 'index.ts', - output: [{ file: 'dist/index.d.ts', format: 'esm' }], - plugins: [dts()], - external: [...Object.keys(packageJson.dependencies || {}), /^viem/], - }, -]); diff --git a/packages/commons/tsconfig.json b/packages/commons/tsconfig.json deleted file mode 100644 index 1545e341..00000000 --- a/packages/commons/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": ".", - "baseUrl": "." - }, - "include": [ - "**/*.ts", - "**/*.d.ts" - ], - "exclude": [ - "dist", - "node_modules" - ] -} \ No newline at end of file diff --git a/packages/commons/types/service-types.ts b/packages/commons/types/service-types.ts deleted file mode 100644 index e86318af..00000000 --- a/packages/commons/types/service-types.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { TransactionReceipt } from 'viem'; -import type { SUPPORTED_TOKENS, EthereumProvider } from './index'; - -/** - * Service-specific types for the adapter architecture - */ - -/** - * Transaction handling options - */ -export interface TransactionOptions { - enableTransactionPolling?: boolean; - transactionTimeout?: number; - waitForReceipt?: boolean; - receiptTimeout?: number; - requiredConfirmations?: number; -} - -/** - * Transaction result with receipt information - */ -export interface TransactionResult { - receipt?: TransactionReceipt; - confirmations?: number; - gasUsed?: string; - effectiveGasPrice?: string; -} - -/** - * Execute preparation result - */ -export interface ExecutePreparation { - provider: EthereumProvider; - fromAddress: string; - encodedData: `0x${string}`; - value?: string; -} - -/** - * Approval transaction result - */ -export interface ApprovalResult { - transactionHash?: string; - wasNeeded: boolean; - error?: string; - confirmed?: boolean; -} - -/** - * Chain switching result - */ -export interface ChainSwitchResult { - success: boolean; - error?: string; -} - -/** - * Token approval info for service operations - */ -export interface TokenApprovalInfo { - token: SUPPORTED_TOKENS; - amount: string; - spenderAddress: string; - chainId: number; -} diff --git a/packages/commons/utils/index.ts b/packages/commons/utils/index.ts deleted file mode 100644 index facd33f4..00000000 --- a/packages/commons/utils/index.ts +++ /dev/null @@ -1,548 +0,0 @@ -import { - TOKEN_METADATA, - CHAIN_METADATA, - MAINNET_CHAINS, - TESTNET_CHAINS, - TESTNET_TOKEN_METADATA, - TOKEN_CONTRACT_ADDRESSES, -} from '../constants'; -import Decimal from 'decimal.js'; -import { - ChainMetadata, - SUPPORTED_CHAINS_IDS, - SUPPORTED_TOKENS, - TokenMetadata, - EthereumProvider, - Block, - TransactionReceipt, -} from '../types/index'; -import { - encodeFunctionData, - type Abi, - type Address, - type Chain, - isAddress, - isHash, - createPublicClient, - custom, -} from 'viem'; -import { mainnet, polygon, arbitrum, optimism, base } from 'viem/chains'; -import { logger } from '../utils/logger'; - -/** - * Shared utility for standardized error message extraction - */ -export function extractErrorMessage(error: unknown, fallbackContext: string): string { - return error instanceof Error ? error.message : `Unknown ${fallbackContext} error`; -} - -export function wait(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -/** - * Get Viem chain configuration for supported chains - */ -export function getViemChain(chainId: number): Chain { - switch (chainId) { - case 1: - return mainnet; - case 137: - return polygon; - case 42161: - return arbitrum; - case 10: - return optimism; - case 8453: - return base; - default: - // Return a basic chain config for unsupported chains - return { - id: chainId, - name: `Chain ${chainId}`, - nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 }, - rpcUrls: { - default: { http: [] }, - public: { http: [] }, - }, - }; - } -} - -/** - * Format a balance string to a human-readable format using Decimal.js - */ -export function formatBalance(balance: string, decimals: number, precision: number = 4): string { - const balanceDecimal = new Decimal(balance); - const divisor = new Decimal(10).pow(decimals); - const formatted = balanceDecimal.div(divisor); - - if (formatted.isZero()) return '0'; - if (formatted.lt(0.0001)) return '< 0.0001'; - - return formatted.toFixed(precision).replace(/\.?0+$/, ''); -} - -/** - * Parse units from a human-readable string to wei/smallest unit using Decimal.js - */ -export function parseUnits(value: string, decimals: number): bigint { - const valueDecimal = new Decimal(value); - const multiplier = new Decimal(10).pow(decimals); - const result = valueDecimal.mul(multiplier); - - return BigInt(result.toFixed(0)); -} - -/** - * Format units from wei/smallest unit to human-readable string using Decimal.js - */ -export function formatUnits(value: bigint, decimals: number): string { - const valueDecimal = new Decimal(value.toString()); - const divisor = new Decimal(10).pow(decimals); - const result = valueDecimal.div(divisor); - - return result.toFixed(); -} - -/** - * Validate if a string is a valid Ethereum address using viem - */ -export function isValidAddress(address: string): address is Address { - return isAddress(address); -} - -/** - * Get mainnet token metadata by symbol - */ -export const getMainnetTokenMetadata = (symbol: SUPPORTED_TOKENS): TokenMetadata | undefined => { - return TOKEN_METADATA[symbol]; -}; - -/** - * Get testnet token metadata by symbol - */ -export const getTestnetTokenMetadata = (symbol: SUPPORTED_TOKENS): TokenMetadata | undefined => { - return TESTNET_TOKEN_METADATA[symbol]; -}; - -/** - * Get token metadata by symbol (defaults to mainnet, kept for backward compatibility) - */ -export const getTokenMetadata = (symbol: SUPPORTED_TOKENS): TokenMetadata | undefined => { - return TOKEN_METADATA[symbol]; -}; - -/** - * Get chain metadata by chain ID - */ -export function getChainMetadata(chainId: SUPPORTED_CHAINS_IDS): ChainMetadata { - return CHAIN_METADATA[chainId]; -} - -/** - * Format a mainnet token amount with proper decimals and symbol - */ -export function formatTokenAmount( - amount: string | bigint, - tokenSymbol: SUPPORTED_TOKENS, - precision: number = 4, -): string { - const metadata = getMainnetTokenMetadata(tokenSymbol); - if (!metadata) return `${amount} ${tokenSymbol}`; - - const amountStr = typeof amount === 'bigint' ? amount.toString() : amount; - const formatted = formatBalance(amountStr, metadata.decimals, precision); - - return `${formatted} ${metadata.symbol}`; -} - -/** - * Format a testnet token amount with proper decimals and symbol - */ -export function formatTestnetTokenAmount( - amount: string | bigint, - tokenSymbol: SUPPORTED_TOKENS, - precision: number = 4, -): string { - const metadata = getTestnetTokenMetadata(tokenSymbol); - if (!metadata) return `${amount} ${tokenSymbol}`; - - const amountStr = typeof amount === 'bigint' ? amount.toString() : amount; - const formatted = formatBalance(amountStr, metadata.decimals, precision); - - return `${formatted} ${metadata.symbol}`; -} - -/** - * Truncate an address for display purposes - */ -export function truncateAddress( - address: string, - startLength: number = 6, - endLength: number = 4, -): string { - if (!isValidAddress(address)) return address; - - if (address.length <= startLength + endLength + 2) return address; - - return `${address.slice(0, startLength)}...${address.slice(-endLength)}`; -} - -/** - * Convert chain ID to hex format - */ -export function chainIdToHex(chainId: number): string { - return `0x${chainId.toString(16)}`; -} - -/** - * Convert hex chain ID to number - */ -export function hexToChainId(hex: string): number { - return parseInt(hex, 16); -} - -export const isMainnetChain = (chainId: SUPPORTED_CHAINS_IDS): boolean => { - return (MAINNET_CHAINS as readonly number[]).includes(chainId); -}; - -export const isTestnetChain = (chainId: SUPPORTED_CHAINS_IDS): boolean => { - return (TESTNET_CHAINS as readonly number[]).includes(chainId); -}; - -/** - * Enhanced contract parameter validation with detailed error messages - */ -export function validateContractParams(params: { - contractAddress: string; - contractAbi: Abi; - functionName: string; - functionParams: readonly unknown[]; - chainId: number; -}): { isValid: boolean; error?: string } { - const { contractAddress, contractAbi, functionName, functionParams, chainId } = params; - - // Validate contract address - if (!contractAddress || typeof contractAddress !== 'string') { - return { isValid: false, error: 'Contract address is required and must be a string' }; - } - - if (!isAddress(contractAddress)) { - return { isValid: false, error: 'Contract address must be a checksummed Ethereum address' }; - } - - // Validate ABI - if (!Array.isArray(contractAbi) || contractAbi.length === 0) { - return { isValid: false, error: 'Contract ABI is required and must be a non-empty array' }; - } - - // Validate function name - if (!functionName || typeof functionName !== 'string') { - return { isValid: false, error: 'Function name is required and must be a string' }; - } - - // Find function in ABI - const functionAbi = contractAbi.find( - (item) => item.type === 'function' && item.name === functionName, - ); - - if (!functionAbi) { - return { isValid: false, error: `Function '${functionName}' not found in contract ABI` }; - } - - // Validate parameters count - const expectedParamsCount = functionAbi.inputs?.length ?? 0; - const providedParamsCount = functionParams?.length || 0; - - if (expectedParamsCount !== providedParamsCount) { - return { - isValid: false, - error: `Function '${functionName}' expects ${expectedParamsCount} parameters, but ${providedParamsCount} were provided`, - }; - } - - // Validate chain ID - if (!chainId || !CHAIN_METADATA[chainId]) { - return { isValid: false, error: `Unsupported chain ID: ${chainId}` }; - } - - return { isValid: true }; -} - -/** - * Enhanced contract call encoding with comprehensive error handling - */ -export function encodeContractCall(params: { - contractAbi: Abi; - functionName: string; - functionParams: readonly unknown[]; -}): { success: boolean; data?: `0x${string}`; error?: string } { - try { - const { contractAbi, functionName, functionParams } = params; - - const data = encodeFunctionData({ - abi: contractAbi, - functionName, - args: functionParams, - }); - - return { success: true, data }; - } catch (error) { - return { - success: false, - error: `Failed to encode contract call: ${extractErrorMessage(error, 'encoding')}`, - }; - } -} - -/** - * Validate and ensure a value is a valid transaction hash - */ -export function validateTransactionHash(value: unknown): value is `0x${string}` { - if (typeof value !== 'string') return false; - return isHash(value); -} - -/** - * Validate hex response from RPC calls - */ -export function validateHexResponse( - value: unknown, - fieldName: string, -): { isValid: boolean; error?: string } { - if (typeof value !== 'string') { - return { isValid: false, error: `${fieldName} must be a string, got ${typeof value}` }; - } - - if (!value.startsWith('0x')) { - return { isValid: false, error: `${fieldName} must be a hex string starting with 0x` }; - } - - return { isValid: true }; -} - -/** - * Enhanced block explorer URL generation with fallback support - */ -export function getBlockExplorerUrl(chainId: number, txHash: string): string { - const chainMetadata = CHAIN_METADATA[chainId]; - - if (!chainMetadata?.blockExplorerUrls?.[0]) { - logger.warn(`No block explorer URL found for chain ${chainId}`); - return ''; - } - - const baseUrl = chainMetadata.blockExplorerUrls[0]; - return `${baseUrl}/tx/${txHash}`; -} - -/** - * Search for transaction hash in block transactions - */ -async function searchTransactionInBlock( - provider: EthereumProvider, - fromAddress: string, -): Promise<`0x${string}` | null> { - const latestBlock = (await provider.request({ - method: 'eth_getBlockByNumber', - params: ['latest', true], - })) as Block; - - if (!latestBlock?.transactions) return null; - - for (const tx of latestBlock.transactions) { - if (tx.from?.toLowerCase() === fromAddress.toLowerCase()) { - if (validateTransactionHash(tx.hash)) { - return tx.hash; - } - } - } - - return null; -} - -/** - * Poll for transaction hash with timeout - */ -async function pollForTransactionHash( - provider: EthereumProvider, - fromAddress: string, - timeout: number, -): Promise<{ success: boolean; hash?: `0x${string}`; error?: string }> { - const startTime = Date.now(); - - while (Date.now() - startTime < timeout) { - const hash = await searchTransactionInBlock(provider, fromAddress); - if (hash) { - return { success: true, hash }; - } - await wait(2000); - } - - return { success: false, error: 'Transaction hash not found within timeout period' }; -} - -/** - * Get transaction hash with multiple fallback strategies - */ -export async function getTransactionHashWithFallback( - provider: EthereumProvider, - response: unknown, - options: { - enablePolling?: boolean; - timeout?: number; - fromAddress?: string; - } = {}, -): Promise<{ success: boolean; hash?: `0x${string}`; error?: string }> { - const { enablePolling = false, timeout = 30000, fromAddress } = options; - - // Strategy 1: Direct response validation - if (validateTransactionHash(response)) { - return { success: true, hash: response }; - } - - // Strategy 2: Transaction polling (if enabled) - if (enablePolling && fromAddress) { - try { - return await pollForTransactionHash(provider, fromAddress, timeout); - } catch (error) { - return { - success: false, - error: `Transaction polling failed: ${extractErrorMessage(error, 'polling')}`, - }; - } - } - - return { - success: false, - error: `Invalid transaction hash response: ${typeof response}${enablePolling ? ' (polling disabled)' : ''}`, - }; -} - -/** - * Enhanced transaction receipt waiting using Viem - */ -export async function waitForTransactionReceipt( - provider: EthereumProvider, - txHash: `0x${string}`, - options: { - timeout?: number; - requiredConfirmations?: number; - pollingInterval?: number; - } = {}, - chainId: number = 1, -): Promise<{ - success: boolean; - receipt?: TransactionReceipt; - confirmations?: number; - error?: string; -}> { - const { - timeout = 300000, // 5 minutes default - requiredConfirmations = 1, - pollingInterval = 2000, - } = options; - - try { - const client = createPublicClient({ - chain: getViemChain(chainId), - transport: custom(provider), - }); - - // Use Viem's waitForTransactionReceipt with timeout - const receipt = await client.waitForTransactionReceipt({ - hash: txHash, - timeout, - pollingInterval, - }); - - // Check transaction status - if (receipt.status === 'reverted') { - return { - success: false, - error: 'Transaction failed (reverted)', - receipt, - }; - } - - // Get current block number for confirmation count - const currentBlock = await client.getBlockNumber(); - const confirmations = Number(currentBlock - receipt.blockNumber) + 1; - - // Check if we have enough confirmations - if (confirmations >= requiredConfirmations) { - return { - success: true, - receipt, - confirmations, - }; - } - - const confirmationStartTime = Date.now(); - const confirmationTimeout = timeout || 300000; - - // Wait for additional confirmations if needed - while (true) { - await wait(pollingInterval); - - if (Date.now() - confirmationStartTime > confirmationTimeout) { - return { - success: false, - error: `Confirmation timeout: only ${confirmations} of ${requiredConfirmations} confirmations received`, - receipt, - confirmations, - }; - } - - const latestBlock = await client.getBlockNumber(); - const currentConfirmations = Number(latestBlock - receipt.blockNumber) + 1; - - if (currentConfirmations >= requiredConfirmations) { - return { - success: true, - receipt, - confirmations: currentConfirmations, - }; - } - } - } catch (error) { - const errorMessage = - error instanceof Error - ? error.message - : (error as { shortMessage?: string; message?: string })?.shortMessage || - (error as { shortMessage?: string; message?: string })?.message || - 'Transaction receipt timeout'; - return { - success: false, - error: errorMessage, - }; - } -} - -/** - * Utility function to get token contract address for a specific token and chain - * @param token Token symbol (e.g., 'USDC', 'USDT') - * @param chainId Chain ID - * @param isTestnet Whether to use testnet addresses - * @returns Contract address or undefined if not found - */ -export function getTokenContractAddress( - token: SUPPORTED_TOKENS, - chainId: SUPPORTED_CHAINS_IDS, -): string | undefined { - const registry = TOKEN_CONTRACT_ADDRESSES; - const address = registry[token]?.[chainId]; - return address || undefined; -} - -// Export logger utilities from commons -export { - LOG_LEVEL, - setExceptionReporter, - setLogLevel, - getLogger, - logger, - type LogLevel, - type ExceptionReporter, -} from '../utils/logger'; diff --git a/packages/core/README.md b/packages/core/README.md deleted file mode 100644 index ba8cb528..00000000 --- a/packages/core/README.md +++ /dev/null @@ -1,900 +0,0 @@ -# @avail-project/nexus/core - -A powerful headless TypeScript SDK for cross-chain operations, token bridging, and unified balance management. Perfect for backends, CLIs, and custom UI implementations. - -## Installation - -```bash -npm install @avail-project/nexus-core -``` - -## πŸš€ Quick Start - -```typescript -import { NexusSDK } from '@avail-project/nexus-core'; - -// Initialize SDK -const sdk = new NexusSDK({ network: 'mainnet' }); -await sdk.initialize(provider); // Your wallet provider - -// Get unified balances -// false by default to get CA applicable token balances -// true to get all the balances including the swappable tokens -const balances = await sdk.getUnifiedBalances(false); -console.log('All balances:', balances); - -// Swap tokens (EXACT_IN - specify input amount) - -const swapWithExactInInput: ExactInSwapInput = { - from: [ - { - chainId: inputData.fromChainID, - amount: parseUnits( - fromAmountStr.toString(), - TOKEN_METADATA[inputData?.fromTokenAddress]?.decimals, - ), - tokenAddress: actualFromTokenAddress as `0x${string}`, - }, - ], - toChainId: inputData.toChainID, - toTokenAddress: actualToTokenAddress as `0x${string}`, -}; - -const swapWithExactInResult = await sdk.swapWithExactIn(swapWithExactInInput, { - swapIntentHook: async (data: Parameters[0]) => {}, -}); - -// Swap tokens (EXACT_OUT - only specify destination chain, token and amount) - -const swapWithExactOutInput: ExactOutSwapInput = { - toChainId: inputData.toChainID, - toTokenAddress: actualToTokenAddress as `0x${string}`, - toAmount: 0n, // bigint -}; - -const swapWithExactOutResult = await sdk.swapWithExactOut(swapWithExactOutInput, { - swapIntentHook: async (data: Parameters[0]) => {}, -}); - -// Bridge tokens -const bridgeResult = await sdk.bridge({ - token: 'USDC', - amount: 100, - chainId: 137, // to Polygon - sourceChains: [10, 42161], // Only use USDC from `Optimism` and `Arbitrum` as source for bridge -}); - -// Transfer tokens (automatically optimized) -const transferResult = await sdk.transfer({ - token: 'ETH', - amount: 0.1, - chainId: 1, // Uses direct transfer if ETH + gas available on Ethereum - recipient: '0x742d35Cc6634C0532925a3b8D4C9db96c4b4Db45', - sourceChains: [8453], // Only use ETH from `Base` as source for bridge -}); - -const executeResult = await sdk.execute({ - contractAddress, - contractAbi: contractAbi, - functionName: functionName, - buildFunctionParams: ( - token: SUPPORTED_TOKENS, - amount: string, - chainId: SUPPORTED_CHAINS_IDS, - user: `0x${string}`, - ) => { - const decimals = TOKEN_METADATA[token].decimals; - const amountWei = parseUnits(amount, decimals); - const tokenAddr = TOKEN_CONTRACT_ADDRESSES[token][chainId]; - return { functionParams: [tokenAddr, amountWei, user, 0] }; - }, - value: ethValue, - tokenApproval: { - token: 'USDC', - amount: '100000000', - }, -}); -``` - -## Core Features - -- **Cross-chain bridging** - Seamless token bridging between 16 chains -- **Cross-chain swaps** - Token swapping between chains with EXACT_IN and EXACT_OUT modes -- **Unified balances** - Aggregated portfolio view across all chains -- **Allowance management** - Efficient token approval handling -- **Smart direct transfers** - Send tokens to any address with automatic optimization -- **Smart execution** - Direct smart contract interactions with balance checking -- **Full testnet support** - Complete development environment -- **Transaction simulation** - Preview costs before execution -- **Rich utilities** - Address validation, formatting, and metadata -- **Smart optimizations** - Automatic chain abstraction skipping when funds are available locally - -## Smart Optimizations - -### Bridge Skip Optimization - -When executing bridge-and-execute operations, the SDK checks if sufficient funds already exist on the target chain: - -- **Smart balance detection** - Validates token balance + gas requirements on destination -- **Automatic bypass** - Skips bridging when funds are available locally or only bridges the required amount -- **Cost reduction** - Eliminates unnecessary bridge fees and delays -- **Seamless fallback** - Uses chain abstraction when local funds are insufficient - -### Direct Transfer Optimization - -For transfer operations, the SDK intelligently chooses the most efficient path: - -- **Local balance checking** - Validates token + gas availability on target chain -- **Direct EVM transfers** - Uses native blockchain calls when possible (faster, cheaper) -- **Chain abstraction fallback** - Automatically uses CA when direct transfer isn't possible -- **Universal compatibility** - Works with both native tokens (ETH, MATIC) and ERC20 (USDC, USDT) - -## Initialization - -```typescript -import type { NexusNetwork } from '@avail-project/nexus-core'; - -// Mainnet (default) -const sdk = new NexusSDK(); - -// Testnet -const sdk = new NexusSDK({ network: 'testnet' as NexusNetwork }); - -// Initialize with provider (required) -await sdk.initialize(window.ethereum); // Returns: Promise -``` - -## πŸ“‘ Event Handling - -```typescript -import type { OnIntentHook, OnAllowanceHook, EventListener } from '@avail-project/nexus-core'; - -// Intent approval flows -sdk.setOnIntentHook(({ intent, allow, deny, refresh }: Parameters[0]) => { - // This is a hook for the dev to show user the intent, the sources and associated fees - - // intent: Intent data containing sources and fees for display purpose - - // allow(): accept the current intent and continue the flow - - // deny(): deny the intent and stop the flow - - // refresh(): should be on a timer of 5s to refresh the intent - // (old intents might fail due to fee changes if not refreshed) - if (userConfirms) allow(); - else deny(); -}); - -// Allowance approvals -sdk.setOnAllowanceHook(({ allow, deny, sources }: Parameters[0]) => { - // This is a hook for the dev to show user the allowances that need to be setup - // for the current tx to happen. - - // sources: an array of objects with minAllowance, chainID, token symbol, etc. - - // allow(allowances): continues the transaction flow with `allowances` array - // allowances.length === sources.length; - // valid values are "max" | "min" | string | bigint - - // deny(): stops the flow - allow(['min']); // or ['max'] or custom amounts -}); - -// Account/chain changes -sdk.onAccountChanged((account) => console.log('Account:', account)); -sdk.onChainChanged((chainId) => console.log('Chain:', chainId)); -``` - -### Progress Events for All Operations - -```typescript -import { NEXUS_EVENTS, ProgressStep } from '@avail-project/nexus-core'; - -// Bridge & Execute Progress -const unsubscribeBridgeExecuteExpected = sdk.nexusEvents.on( - NEXUS_EVENTS.BRIDGE_EXECUTE_EXPECTED_STEPS, - (steps: ProgressStep[]) => { - console.log( - 'Bridge & Execute steps β†’', - steps.map((s) => s.typeID), - ); - }, -); - -const unsubscribeBridgeExecuteCompleted = sdk.nexusEvents.on( - NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS, - (step: ProgressStep) => { - console.log('Bridge & Execute completed β†’', step.typeID, step.data); - - if (step.typeID === 'IS' && step.data.explorerURL) { - console.log('View transaction:', step.data.explorerURL); - } - }, -); - -// Transfer & Bridge Progress (optimized operations) -const unsubscribeTransferExpected = sdk.nexusEvents.on( - NEXUS_EVENTS.EXPECTED_STEPS, - (steps: ProgressStep[]) => { - console.log( - 'Transfer/Bridge steps β†’', - steps.map((s) => s.typeID), - ); - // For direct transfers: ['CS', 'TS', 'IS'] (3 steps, ~5-15s) - }, -); - -const unsubscribeTransferCompleted = sdk.nexusEvents.on( - NEXUS_EVENTS.STEP_COMPLETE, - (step: ProgressStep) => { - console.log('Transfer/Bridge completed β†’', step.typeID, step.data); - - if (step.typeID === 'IS' && step.data.explorerURL) { - // Transaction submitted with hash - works for both direct and CA - console.log('Transaction hash:', step.data.transactionHash); - console.log('Explorer URL:', step.data.explorerURL); - } - }, -); - -// Cleanup -return () => { - unsubscribeBridgeExecuteExpected(); - unsubscribeBridgeExecuteCompleted(); - unsubscribeTransferExpected(); - unsubscribeTransferCompleted(); -}; -``` - -The SDK emits **consistent event patterns** for all operations: - -**Bridge & Execute Operations:** - -1. `bridge_execute_expected_steps` – _once_ with full ordered array of `ProgressStep`s -2. `bridge_execute_completed_steps` – _many_; one per finished step with runtime data - -**Transfer & Bridge Operations:** - -1. `expected_steps` – _once_ with full ordered array of `ProgressStep`s -2. `step_complete` – _many_; one per finished step with runtime data - -All events include the same `typeID` structure and runtime `data` such as `transactionHash`, `explorerURL`, `confirmations`, `error`, etc. This provides consistent progress tracking whether using optimized direct operations or chain abstraction. - -## Balance Operations - -```typescript -import type { UserAsset, TokenBalance } from '@avail-project/nexus-core'; - -// Get all balances across chains -// false by default to get CA applicable token balances -// true to get all the balances including the swappable tokens -const balances: UserAsset[] = await sdk.getUnifiedBalances(); - -// Get balance for specific token -// false by default to get CA applicable token balances -// true to get all the balances including the swappable tokens -const usdcBalance: UserAsset | undefined = await sdk.getUnifiedBalance('USDC', false); -``` - -## Bridge Operations - -```typescript -import type { BridgeParams, BridgeResult, SimulationResult } from '@avail-project/nexus-core'; - -// Bridge tokens between chains -const result: BridgeResult = await sdk.bridge({ - token: 'USDC', - amount: 100, - chainId: 137, -} as BridgeParams); - -// Simulate bridge to preview costs -const simulation: SimulationResult = await sdk.simulateBridge({ - token: 'USDC', - amount: 100, - chainId: 137, -}); -``` - -## Transfer Operations - -```typescript -import type { TransferParams, TransferResult } from '@avail-project/nexus-core'; - -// Smart transfer with automatic optimization -const result: TransferResult = await sdk.transfer({ - token: 'USDC', - amount: 100, - chainId: 42161, // Arbitrum - recipient: '0x...', -} as TransferParams); - -// The SDK automatically: -// 1. Checks if you have USDC + ETH for gas on Arbitrum -// 2. Uses direct EVM transfer if available (faster, cheaper) -// 3. Falls back to chain abstraction if local funds insufficient - -// Simulate transfer to preview costs and optimization path -const simulation: SimulationResult = await sdk.simulateTransfer({ - token: 'USDC', - amount: 100, - chainId: 42161, - recipient: '0x...', -}); - -// Check if direct transfer will be used -console.log('Fees:', simulation.intent.fees); -// For direct transfers: gasSupplied shows actual native token cost -// For CA transfers: includes additional CA routing fees -``` - -## Execute Operations - -```typescript -import type { - ExecuteParams, - ExecuteResult, - ExecuteSimulation, - BridgeAndExecuteParams, - BridgeAndExecuteResult, - BridgeAndExecuteSimulationResult, -} from '@avail-project/nexus-core'; - -// Execute contract functions with dynamic parameter builder - Compound V3 Supply -const result: ExecuteResult = await sdk.execute({ - toChainId: 1, - contractAddress: '0xc3d688B66703497DAA19211EEdff47f25384cdc3', // Compound V3 USDC Market - contractAbi: [ - { - inputs: [ - { internalType: 'address', name: 'asset', type: 'address' }, - { internalType: 'uint256', name: 'amount', type: 'uint256' }, - ], - name: 'supply', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - ], - functionName: 'supply', - buildFunctionParams: ( - token: SUPPORTED_TOKENS, - amount: string, - chainId: SUPPORTED_CHAINS_IDS, - userAddress: `0x${string}`, - ) => { - const decimals = TOKEN_METADATA[token].decimals; - const amountWei = parseUnits(amount, decimals); - const tokenAddress = TOKEN_CONTRACT_ADDRESSES[token][chainId]; - return { - functionParams: [tokenAddress, amountWei], - }; - }, - waitForReceipt: true, - requiredConfirmations: 3, - tokenApproval: { - token: 'USDC', - amount: '1000000', // Amount in token units - }, -} as ExecuteParams); - -// Simulate execute to preview costs and check for approval requirements -const simulation: ExecuteSimulation = await sdk.simulateExecute(executeParams); -if (!simulation.success) { - console.log('Simulation failed:', simulation.error); - // Error might indicate missing token approval -} - -// Bridge tokens and execute contract function - Yearn Vault Deposit -const bridgeAndExecuteResult: BridgeAndExecuteResult = await sdk.bridgeAndExecute({ - token: 'USDC', - amount: '100000000', // 100 USDC (6 decimals) - toChainId: 1, // Ethereum - sourceChains: [8453], // Only use USDC from `Base` as source for bridge - execute: { - contractAddress: '0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE', // Yearn USDC Vault - contractAbi: [ - { - inputs: [ - { internalType: 'uint256', name: 'assets', type: 'uint256' }, - { internalType: 'address', name: 'receiver', type: 'address' }, - ], - name: 'deposit', - outputs: [{ internalType: 'uint256', name: 'shares', type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'function', - }, - ], - functionName: 'deposit', - buildFunctionParams: ( - token: SUPPORTED_TOKENS, - amount: string, - chainId: SUPPORTED_CHAINS_IDS, - userAddress: `0x${string}`, - ) => { - const decimals = TOKEN_METADATA[token].decimals; - const amountWei = parseUnits(amount, decimals); - return { - functionParams: [amountWei, userAddress], - }; - }, - tokenApproval: { - token: 'USDC', - amount: '100000000', - }, - }, - waitForReceipt: true, -} as BridgeAndExecuteParams); - -// Comprehensive simulation with detailed step analysis and approval handling -const simulation: BridgeAndExecuteSimulationResult = await sdk.simulateBridgeAndExecute(params); - -// The simulation provides detailed step analysis: -console.log('Steps:', simulation.steps); - -console.log('Total estimated cost:', simulation.totalEstimatedCost); - -console.log('Approval required:', simulation.metadata?.approvalRequired); -console.log('Bridge receive amount:', simulation.metadata?.bridgeReceiveAmount); -``` - -## Swap Operations - -```typescript -import type { ExactInSwapInput, SwapIntentHook, ExactOutSwapInput SwapResult } from '@avail-project/nexus-core'; - - -// Swap tokens (EXACT_IN - specify input amount) - -const swapWithExactInInput: ExactInSwapInput = { - from: [ - { - chainId: inputData.fromChainID, - amount: parseUnits( - fromAmountStr.toString(), - TOKEN_METADATA[inputData?.fromTokenAddress]?.decimals, - ), - tokenAddress: actualFromTokenAddress as `0x${string}`, - }, - ], - toChainId: inputData.toChainID, - toTokenAddress: actualToTokenAddress as `0x${string}`, -}; - -const swapWithExactInResult = await sdk.swapWithExactIn(swapWithExactInInput, {swapIntentHook : async (data: Parameters[0]) => { - // use this to capture the intent allow, reject and refresh functions - const {intent, allow, reject, refresh} = data; - // Use it to handle user interaction or display transaction details - - // setSwapIntent(intent); - // setAllowCallback(allow); - // setRejectCallback(reject); - // setRefreshCallback(refresh); - - // or directly approve or reject the intent - // calling allow processes the txn - allow() -}}); - -// Swap tokens (EXACT_OUT - only specify destination chain, token and amount) - -const swapWithExactOutInput: ExactOutSwapInput = { - toChainId: inputData.toChainID, - toTokenAddress: actualToTokenAddress as `0x${string}`, - toAmount: 0n // bigint -}; - -const swapWithExactOutResult = await sdk.swapWithExactOut(swapWithExactOutInput, {swapIntentHook : async (data: Parameters[0]) => { - // use this to capture the intent allow, reject and refresh functions - const {intent, allow, reject, refresh} = data; - // Use it to handle user interaction or display transaction details - - // setSwapIntent(intent); - // setAllowCallback(allow); - // setRejectCallback(reject); - // setRefreshCallback(refresh); - - // or directly approve or reject the intent - // calling allow processes the txn - allow() -}}); - - -// Handle swap results -if (swapWithExactInResult.success) { - console.log('βœ… Swap successful!'); - console.log('Source transaction:', exactInSwap.result.sourceSwaps); - console.log('Destination transaction:', exactInSwap.result.destinationSwap); - console.log('Explorer URL:', exactInSwap.result.explorerURL); -} else { - console.error('❌ Swap failed:', exactInSwap.error); -} -``` - -### Discovering Available Swap Options - -```typescript -import type { SupportedChainsResult } from '@avail-project/nexus-core'; -import { DESTINATION_SWAP_TOKENS } from '@avail-project/nexus-core'; - -// Get supported source chains and tokens for swaps -const supportedOptions: SupportedChainsResult = sdk.utils.getSwapSupportedChainsAndTokens(); -console.log('Supported source chains and tokens:', supportedOptions); - -// Example: Build a source token selector -supportedOptions.forEach((chain) => { - console.log(`Chain: ${chain.name} (${chain.id})`); - chain.tokens.forEach((token) => { - console.log(` - ${token.symbol}: ${token.tokenAddress}`); - }); -}); - -// Get suggested destination tokens (optional - destination can be any token or chain) -const optimismDestinations = DESTINATION_SWAP_TOKENS.get(10); // Optimism -const arbitrumDestinations = DESTINATION_SWAP_TOKENS.get(42161); // Arbitrum -const baseDestinations = DESTINATION_SWAP_TOKENS.get(8453); // Base - -console.log('Popular Optimism destinations:', optimismDestinations); -console.log('Popular Arbitrum destinations:', arbitrumDestinations); -console.log('Popular Base destinations:', baseDestinations); - -// Example: Build destination token options for UI -const buildDestinationOptions = (chainId: number) => { - const popularTokens = DESTINATION_SWAP_TOKENS.get(chainId) || []; - return popularTokens.map((token) => ({ - label: `${token.symbol} - ${token.name}`, - value: token.tokenAddress, - icon: token.logo, - decimals: token.decimals, - })); -}; -``` - -**Note:** - -- **Source chains/tokens** are restricted to what `getSwapSupportedChainsAndTokens()` returns -- **Destination chains/tokens** can be any supported chain and token address -- `DESTINATION_SWAP_TOKENS` provides popular destination options but is not exhaustive - -### Swap Types - -**EXACT_IN Swaps:** - -- You specify exactly how much you want to spend (`fromAmount`) -- Output amount varies based on market conditions and fees -- Use case: "I want to swap all my 100 USDC" - -**EXACT_OUT Swaps:** - -- You specify exactly how much you want to receive (`toAmount`) -- Input amount varies based on market conditions and fees -- Use case: "I need exactly 1 ETH for a specific purpose" - -### Swap Progress Events - -```typescript -import { NEXUS_EVENTS } from '@avail-project/nexus-core'; - -// Listen for swap progress updates -const unsubscribeSwapSteps = sdk.nexusEvents.on(NEXUS_EVENTS.SWAP_STEPS, (step) => { - console.log('Swap step:', step.type); - - if (step.type === 'SOURCE_SWAP_HASH' && step.explorerURL) { - console.log('Source transaction:', step.explorerURL); - } - - if (step.type === 'DESTINATION_SWAP_HASH' && step.explorerURL) { - console.log('Destination transaction:', step.explorerURL); - } - - if (step.type === 'SWAP_COMPLETE' && step.completed) { - console.log('βœ… Swap completed successfully!'); - } -}); - -// Cleanup -unsubscribeSwapSteps(); -``` - -## Allowance Management - -```typescript -import type { AllowanceResponse } from '@avail-project/nexus-core'; - -// Check allowances -const allowances: AllowanceResponse[] = await sdk.getAllowance(137, ['USDC', 'USDT']); - -// Set allowances -await sdk.setAllowance(137, ['USDC'], 1000000n); - -// Revoke allowances -await sdk.revokeAllowance(137, ['USDC']); -``` - -## Intent Management - -```typescript -import type { RequestForFunds } from '@avail-project/nexus-core'; - -// Get user's transaction intents -// Bound by web domain and user's wallet address -const intents: RequestForFunds[] = await sdk.getMyIntents(1); -``` - -## Utilities - -All utility functions are available under `sdk.utils`: - -```typescript -import type { ChainMetadata, TokenMetadata, SUPPORTED_TOKENS } from '@avail-project/nexus-core'; - -// Address utilities -const isValid: boolean = sdk.utils.isValidAddress('0x...'); -const shortened: string = sdk.utils.truncateAddress('0x...'); - -// Balance formatting -const formatted: string = sdk.utils.formatBalance('1000000', 6); -const units: bigint = sdk.utils.parseUnits('100.5', 6); -const readable: string = sdk.utils.formatUnits(100500000n, 6); - -// Token amount formatting -const formattedAmount: string = sdk.utils.formatTokenAmount('1000000', 'USDC'); // "1.0 USDC" -const testnetFormatted: string = sdk.utils.formatTestnetTokenAmount('1000000', 'USDC'); // "1.0 USDC" - -// Chain & token info -const chainMeta: ChainMetadata | undefined = sdk.utils.getChainMetadata(137); -const tokenMeta: TokenMetadata | undefined = sdk.utils.getTokenMetadata('USDC'); -const mainnetTokenMeta: TokenMetadata | undefined = sdk.utils.getMainnetTokenMetadata('USDC'); -const testnetTokenMeta: TokenMetadata | undefined = sdk.utils.getTestnetTokenMetadata('USDC'); - -// Chain/token validation -const isSupported: boolean = sdk.utils.isSupportedChain(137); -const isSupportedToken: boolean = sdk.utils.isSupportedToken('USDC'); - -// Get supported chains -const chains: Array<{ id: number; name: string; logo: string }> = sdk.utils.getSupportedChains(); - -// Swap discovery utilities -const swapOptions: SupportedChainsResult = sdk.utils.getSwapSupportedChainsAndTokens(); - -// Chain ID conversion -const hexChainId: string = sdk.utils.chainIdToHex(137); -const decimalChainId: number = sdk.utils.hexToChainId('0x89'); -``` - -## Provider Methods - -```typescript -import type { EthereumProvider, RequestArguments } from '@avail-project/nexus-core'; - -// Get chain abstracted provider -const provider: EthereumProvider = sdk.getEVMProviderWithCA(); - -// Make EIP-1193 requests -const result = await sdk.request({ - method: 'eth_accounts', - params: [], -} as RequestArguments); - -// Cleanup -await sdk.deinit(); -``` - -## Usage Examples - -### Basic Bridge with Result Handling - -```typescript -import { NexusSDK, type BridgeResult } from '@avail-project/nexus-core'; - -const sdk = new NexusSDK(); -await sdk.initialize(window.ethereum); - -try { - const result: BridgeResult = await sdk.bridge({ - token: 'USDC', - amount: 100, - chainId: 137, - }); - - if (result.success) { - console.log('βœ… Bridge successful!'); - if (result.explorerUrl) { - console.log('View transaction:', result.explorerUrl); - } - } else { - console.error('❌ Bridge failed:', result.error); - } -} catch (error) { - console.error('Bridge error:', error); -} -``` - -### Execute with Receipt Confirmation - -```typescript -import type { ExecuteResult } from '@avail-project/nexus-core'; - -// MakerDAO DSR (Dai Savings Rate) Deposit -const result: ExecuteResult = await sdk.execute({ - toChainId: 1, - contractAddress: '0x373238337Bfe1146fb49989fc222523f83081dDb', // DSR Manager - contractAbi: [ - { - inputs: [ - { internalType: 'address', name: 'usr', type: 'address' }, - { internalType: 'uint256', name: 'wad', type: 'uint256' }, - ], - name: 'join', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - ], - functionName: 'join', - buildFunctionParams: ( - token: SUPPORTED_TOKENS, - amount: string, - chainId: SUPPORTED_CHAINS_IDS, - userAddress: `0x${string}`, - ) => { - const decimals = TOKEN_METADATA[token].decimals; - const amountWei = parseUnits(amount, decimals); - return { - functionParams: [userAddress, amountWei], - }; - }, - waitForReceipt: true, - requiredConfirmations: 3, - tokenApproval: { - token: 'USDC', // Will be converted to DAI in the bridge - amount: '1000000', - }, -}); - -console.log('Transaction hash:', result.transactionHash); -console.log('Explorer URL:', result.explorerUrl); -console.log('Gas used:', result.gasUsed); -console.log('Confirmations:', result.confirmations); -``` - -### Complete Portfolio Management - -```typescript -import type { UserAsset, ChainMetadata } from '@avail-project/nexus-core'; - -// Get complete balance overview -// false by default to get CA applicable token balances -// true to get all the balances including the swappable tokens -const balances: UserAsset[] = await sdk.getUnifiedBalances(); - -for (const asset of balances) { - console.log(`\n${asset.symbol}: ${asset.balance}`); - console.log(`Fiat value: $${asset.balanceInFiat || 0}`); - - if (asset.breakdown) { - console.log('Chain breakdown:'); - for (const chainBalance of asset.breakdown) { - const chain: ChainMetadata | undefined = sdk.utils.getChainMetadata(chainBalance.chain.id); - console.log(` ${chain?.name}: ${chainBalance.balance}`); - } - } -} -``` - -## Error Handling - -```typescript -import type { BridgeResult } from '@avail-project/nexus-core'; - -try { - const result: BridgeResult = await sdk.bridge({ token: 'USDC', amount: 100, chainId: 137 }); - - if (!result.success) { - // Handle bridge failure - console.error('Bridge failed:', result.error); - } -} catch (error) { - if (error.message.includes('User denied')) { - // User cancelled transaction - } else if (error.message.includes('Insufficient')) { - // Insufficient balance - } else if (error.message.includes('Unsupported')) { - // Unsupported chain or token - } else { - // Other errors - console.error('Unexpected error:', error); - } -} -``` - -```typescript -import type { ExecuteSimulation, ExecuteResult } from '@avail-project/nexus-core'; - -// Simulate before executing -const simulation: ExecuteSimulation = await sdk.simulateExecute(params); -if (simulation.success) { - const result: ExecuteResult = await sdk.execute(params); -} - -// Cleanup when done -sdk.removeAllListeners(); -await sdk.deinit(); -``` - -## TypeScript Support - -The SDK is fully typed with comprehensive TypeScript definitions. Import the types you need: - -```typescript -import type { - BridgeParams, - BridgeResult, - TransferParams, - TransferResult, - ExecuteParams, - ExecuteResult, - ExecuteSimulation, - BridgeAndExecuteParams, - BridgeAndExecuteResult, - SimulationResult, - SwapInput, - SwapResult, - SwapBalances, - SupportedChainsResult, - DESTINATION_SWAP_TOKENS, - UserAsset, - TokenBalance, - AllowanceResponse, - ChainMetadata, - TokenMetadata, - OnIntentHook, - OnAllowanceHook, - EthereumProvider, - RequestArguments, - EventListener, - NexusNetwork, -} from '@avail-project/nexus-core'; -``` - -## Supported Networks & Tokens - -### Mainnet Chains - -| Network | Chain ID | Native Currency | Status | -| --------- | -------- | --------------- | ------ | -| Ethereum | 1 | ETH | βœ… | -| Optimism | 10 | ETH | βœ… | -| Polygon | 137 | MATIC | βœ… | -| Arbitrum | 42161 | ETH | βœ… | -| Avalanche | 43114 | AVAX | βœ… | -| Base | 8453 | ETH | βœ… | -| Scroll | 534352 | ETH | βœ… | -| Sophon | 50104 | SOPH | βœ… | -| Kaia | 8217 | KAIA | βœ… | -| BNB | 56 | BNB | βœ… | -| HyperEVM | 999 | HYPE | βœ… | - -### Testnet Chains - -| Network | Chain ID | Native Currency | Status | -| ---------------- | -------- | --------------- | ------ | -| Optimism Sepolia | 11155420 | ETH | βœ… | -| Polygon Amoy | 80002 | MATIC | βœ… | -| Arbitrum Sepolia | 421614 | ETH | βœ… | -| Base Sepolia | 84532 | ETH | βœ… | -| Sepolia | 11155111 | ETH | βœ… | -| Monad Testnet | 10143 | MON | βœ… | - -### Supported Tokens - -| Token | Name | Decimals | Networks | -| ----- | ---------- | -------- | -------------- | -| ETH | Ethereum | 18 | All EVM chains | -| USDC | USD Coin | 6 | All supported | -| USDT | Tether USD | 6 | All supported | - -## πŸ”— Links - -- [GitHub Repository](https://github.com/availproject/nexus-sdk) -- [API Documentation](https://docs.availproject.org/api-reference/avail-nexus-sdk) diff --git a/packages/core/adapters/chain-abstraction-adapter.ts b/packages/core/adapters/chain-abstraction-adapter.ts deleted file mode 100644 index d69da568..00000000 --- a/packages/core/adapters/chain-abstraction-adapter.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Network, SupportedChainsResult } from '@nexus/commons'; -import { isSupportedChain, isSupportedToken } from './core/validation'; -// Services -import { ExecuteService } from './services/execute-service'; -import { BridgeExecuteService } from './services/bridge-execute-service'; -import { - type BridgeAndExecuteParams, - type BridgeAndExecuteResult, - type ExecuteParams, - type ExecuteResult, - type ExecuteSimulation, - type BridgeAndExecuteSimulationResult, - type SUPPORTED_CHAINS_IDS, - logger, -} from '@nexus/commons'; -import { getSupportedChains } from 'sdk/ca-base/utils'; -import { NexusSDK } from 'sdk'; - -/** - * Provides a unified interface for chain abstraction operations. - */ -export class ChainAbstractionAdapter { - private executeService: ExecuteService; - private bridgeExecuteService: BridgeExecuteService; - - constructor(public nexusSDK: NexusSDK) { - logger.debug('ChainAbstractionAdapter', { nexusSDK }); - - // Initialize services - this.executeService = new ExecuteService(this); - this.bridgeExecuteService = new BridgeExecuteService(this); - this.setGasEstimationEnabled(true); - } - - public async getEVMClient() { - return this.nexusSDK.getEVMClient(); - } - - /** - * Execute a contract call using the execute service. - */ - public async execute(params: ExecuteParams): Promise { - return this.executeService.execute(params); - } - - /** - * Simulate contract execution using the execute service. - */ - public async simulateExecute(params: ExecuteParams): Promise { - return this.executeService.simulateExecute(params); - } - - /** - * Get the list of supported chains from the CA SDK. - */ - public getSupportedChains(env?: Network): SupportedChainsResult { - return getSupportedChains(env); - } - - /** - * Check if a chain is supported by the adapter. - */ - public isSupportedChain(chainId: SUPPORTED_CHAINS_IDS): boolean { - return isSupportedChain(chainId); - } - - /** - * Check if a token is supported by the adapter. - */ - public isSupportedToken(token: string): boolean { - return isSupportedToken(token); - } - - /** - * Bridge and execute operation - uses the BridgeExecuteService - */ - public async bridgeAndExecute(params: BridgeAndExecuteParams): Promise { - return this.bridgeExecuteService.bridgeAndExecute(params); - } - - /** - * Simulate bridge and execute operation - */ - public async simulateBridgeAndExecute( - params: BridgeAndExecuteParams, - ): Promise { - return this.bridgeExecuteService.simulateBridgeAndExecute(params); - } - - /** - * Enable or disable gas estimation for transactions - * When enabled, gas estimation will run before each transaction execution - * This helps identify potential failures early and provides cost estimates - */ - private setGasEstimationEnabled(enabled: boolean): void { - this.bridgeExecuteService.setGasEstimationEnabled(enabled); - this.executeService.setGasEstimationEnabled(enabled); - } -} diff --git a/packages/core/adapters/core/validation.ts b/packages/core/adapters/core/validation.ts deleted file mode 100644 index 36b9033d..00000000 --- a/packages/core/adapters/core/validation.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { SUPPORTED_CHAINS, type SUPPORTED_CHAINS_IDS } from '@nexus/commons'; - -/** - * Common validation helpers for adapter services - */ - -/** - * Validate bridge/transfer parameters - */ -export function validateBridgeTransferParams(params: { - chainId: SUPPORTED_CHAINS_IDS; - token: string; -}): void { - if (!isSupportedChain(params.chainId)) { - throw new Error('Unsupported chain'); - } - if (!isSupportedToken(params.token)) { - throw new Error('Unsupported token'); - } -} - -/** - * Validation that returns result objects instead of throwing - */ -export function validateForResultReturn(params: { - chainId: SUPPORTED_CHAINS_IDS; - token: string; - initialized: boolean; -}): { success: boolean; error?: string } { - if (!isSupportedChain(params.chainId)) { - return { success: false, error: 'Unsupported chain' }; - } - if (!isSupportedToken(params.token)) { - return { success: false, error: 'Unsupported token' }; - } - if (!params.initialized) { - return { success: false, error: 'CA SDK not initialized. Call initialize() first.' }; - } - return { success: true }; -} - -/** - * Check if a chain is supported - */ -export function isSupportedChain(chainId: SUPPORTED_CHAINS_IDS): boolean { - return Object.values(SUPPORTED_CHAINS).includes(chainId); -} - -/** - * Check if a token is supported - */ -export function isSupportedToken(token: string): boolean { - const supportedTokens = ['ETH', 'USDC', 'USDT']; - return supportedTokens.includes(token.toUpperCase()); -} - -/** - * Validate ExecuteParams with callback pattern - */ -export function validateExecuteParams(params: { - toChainId: SUPPORTED_CHAINS_IDS; - contractAddress: string; - contractAbi: any; - functionName: string; - buildFunctionParams: Function; - tokenApproval?: { token: string }; -}): { success: boolean; error?: string } { - // Validate chain - if (!isSupportedChain(params.toChainId)) { - return { success: false, error: `Unsupported chain: ${params.toChainId}` }; - } - - // Validate contract address - if (!params.contractAddress || !params.contractAddress.startsWith('0x')) { - return { success: false, error: 'Invalid contract address' }; - } - - // Validate contract ABI - if (!params.contractAbi || !Array.isArray(params.contractAbi)) { - return { success: false, error: 'Invalid contract ABI' }; - } - - // Validate function name - if (!params.functionName || typeof params.functionName !== 'string') { - return { success: false, error: 'Invalid function name' }; - } - - // Validate callback function - if (!params.buildFunctionParams || typeof params.buildFunctionParams !== 'function') { - return { success: false, error: 'buildFunctionParams must be a valid function' }; - } - - // Validate token approval if present - if (params.tokenApproval) { - if (!params.tokenApproval.token || !isSupportedToken(params.tokenApproval.token)) { - return { - success: false, - error: `Unsupported token for approval: ${params.tokenApproval.token}`, - }; - } - } - - return { success: true }; -} diff --git a/packages/core/adapters/services/approval-service.ts b/packages/core/adapters/services/approval-service.ts deleted file mode 100644 index 144e340d..00000000 --- a/packages/core/adapters/services/approval-service.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { - getTokenContractAddress, - extractErrorMessage, - logger, - TOKEN_METADATA, - type ApprovalResult, - type ApprovalInfo, - type SUPPORTED_TOKENS, - type SUPPORTED_CHAINS_IDS, -} from '@nexus/commons'; -import { ChainAbstractionAdapter } from 'adapters/chain-abstraction-adapter'; -import { parseUnits, formatUnits, erc20Abi, Hex } from 'viem'; - -/** - * Internal constants for adapter behavior - */ -const ADAPTER_CONSTANTS = { - // Default 2% buffer (200 bps) to handle precision issues. Can be overridden per-call via ExecuteParams.approvalBufferBps - APPROVAL_BUFFER_BPS_DEFAULT: 200n, - DEFAULT_DECIMALS: 18, - MAX_APPROVAL_AMOUNT: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', -} as const; - -/** - * Service responsible for handling contract approvals - */ -export class ApprovalService { - constructor(private adapter: ChainAbstractionAdapter) {} - - /** - * Check if approval is needed for a token spending operation - */ - async checkApprovalNeeded( - tokenApproval: { token: SUPPORTED_TOKENS; amount: string }, - spenderAddress: string, - chainId: number, - approvalBufferBps?: number, - ): Promise { - const accounts = await this.adapter.nexusSDK.getEVMClient().getAddresses(); - - if (!accounts || accounts.length === 0) { - throw new Error('No accounts available'); - } - - const ownerAddress = accounts[0]; - const tokenContractAddress = getTokenContractAddress( - tokenApproval.token, - chainId as SUPPORTED_CHAINS_IDS, - ); - - if (!tokenContractAddress) { - throw new Error( - `Token contract address not found for ${tokenApproval.token} on chain ${chainId}`, - ); - } - - try { - // Convert amount to proper token units - handle both decimal and integer formats - let amountInWei: bigint; - - // Get token metadata for decimal handling - const tokenMetadata = TOKEN_METADATA[tokenApproval.token.toUpperCase()]; - const decimals = tokenMetadata?.decimals || ADAPTER_CONSTANTS.DEFAULT_DECIMALS; - - try { - // Handle both decimal strings (user-friendly) and integer strings (already converted) - // This matches the logic from the legacy adapter - if (tokenApproval.amount.includes('.')) { - // Decimal amount - user-friendly format like "0.01" - amountInWei = parseUnits(tokenApproval.amount, decimals); - } else { - // Integer amount - likely already in wei/micro format like "10000" - // For USDC and other 6-decimal tokens, check if this is already in micro-units - const amountNum = BigInt(tokenApproval.amount); - const USDC_THRESHOLD = 1_000_000n; // 1 USDC in micro-units - - if (decimals === 6 && amountNum > USDC_THRESHOLD) { - // For USDC, large numbers are likely already in micro-units - amountInWei = amountNum; - } else if (decimals === 18 && amountNum > 1_000_000_000_000_000_000n) { - // For ETH, large numbers are likely already in wei - amountInWei = amountNum; - } else { - // Small numbers are likely user amounts that need conversion - amountInWei = parseUnits(tokenApproval.amount, decimals); - } - } - } catch (error) { - throw new Error( - `Failed to parse amount ${tokenApproval.amount} for ${tokenApproval.token}: ${extractErrorMessage(error, 'amount parsing')}`, - ); - } - - const currentAllowance = await this.adapter.nexusSDK.getEVMClient().readContract({ - address: tokenContractAddress as Hex, - abi: erc20Abi, - functionName: 'allowance', - args: [ownerAddress as Hex, spenderAddress as Hex], - }); - - // Add a small buffer to avoid repeated approvals due to minor amount differences - const bufferBps = - approvalBufferBps !== undefined && approvalBufferBps >= 0 - ? BigInt(approvalBufferBps) - : ADAPTER_CONSTANTS.APPROVAL_BUFFER_BPS_DEFAULT; - const requiredAmountWithBuffer = amountInWei + (amountInWei * bufferBps) / 10000n; - - const needsApproval = currentAllowance < requiredAmountWithBuffer; - - return { - needsApproval, - currentAllowance, - requiredAmount: amountInWei, - tokenAddress: tokenContractAddress, - spenderAddress, - token: tokenApproval.token, - chainId, - hasPendingApproval: !needsApproval, - }; - } catch (error) { - throw new Error( - `Failed to check approval for ${tokenApproval.token}: ${extractErrorMessage(error, 'approval check')}`, - ); - } - } - - /** - * Ensure contract approval is in place for token spending - */ - async ensureContractApproval( - tokenApproval: { token: SUPPORTED_TOKENS; amount: string }, - spenderAddress: string, - chainId: number, - waitForConfirmation: boolean = false, - approvalBufferBps?: number, - ): Promise { - try { - // Check if approval is needed - const approvalInfo = await this.checkApprovalNeeded( - tokenApproval, - spenderAddress, - chainId, - approvalBufferBps, - ); - - // Skip approval if sufficient allowance exists - if (!approvalInfo.needsApproval) { - return { - wasNeeded: false, - confirmed: true, - }; - } - - const accounts = await this.adapter.nexusSDK.getEVMClient().getAddresses(); - - if (!accounts || accounts.length === 0) { - return { - wasNeeded: true, - error: 'No accounts available', - }; - } - - // Calculate buffer amount with proper decimal handling for MetaMask display - const bufferBps = - approvalBufferBps !== undefined && approvalBufferBps >= 0 - ? BigInt(approvalBufferBps) - : ADAPTER_CONSTANTS.APPROVAL_BUFFER_BPS_DEFAULT; - const requiredAmountWithBuffer = - approvalInfo.requiredAmount + (approvalInfo.requiredAmount * bufferBps) / 10000n; - - // Get token decimals for proper formatting - const tokenMetadata = TOKEN_METADATA[tokenApproval.token.toUpperCase()]; - const tokenDecimals = tokenMetadata?.decimals || ADAPTER_CONSTANTS.DEFAULT_DECIMALS; - // Convert to human-readable format first, then back to wei for better MetaMask display - // This ensures MetaMask shows "0.01001" instead of "10100" - const humanReadableAmount = formatUnits(requiredAmountWithBuffer, tokenDecimals); - logger.info('DEBUG approval - Human readable amount for MetaMask:', { - humanReadableAmount, - token: tokenApproval.token, - }); - - // Convert back to wei for the transaction - const finalApprovalAmount = parseUnits(humanReadableAmount, tokenDecimals); - - const chain = this.adapter.nexusSDK.chainList.getChainByID(chainId); - if (!chain) { - throw new Error('chain not supported'); - } - const transactionHash = await this.adapter.nexusSDK.getEVMClient().writeContract({ - functionName: 'approve', - abi: erc20Abi, - address: approvalInfo.tokenAddress as Hex, - args: [spenderAddress as Hex, finalApprovalAmount], - chain, - account: accounts[0], - }); - - if (waitForConfirmation) { - try { - await this.adapter.nexusSDK.getEVMClient().waitForTransactionReceipt({ - hash: transactionHash, - retryCount: 10, - }); - } catch (confirmationError) { - logger.warn('DEBUG approval - Confirmation failed:', confirmationError); - return { - transactionHash, - wasNeeded: true, - confirmed: false, - error: `Approval confirmation failed: ${extractErrorMessage(confirmationError, 'approval confirmation')}`, - }; - } - - return { - transactionHash, - wasNeeded: true, - confirmed: true, - }; - } - return { - transactionHash, - wasNeeded: false, - confirmed: false, - }; - } catch (error) { - logger.error('DEBUG approval - Error:', error as Error); - return { - wasNeeded: true, - error: extractErrorMessage(error, 'contract approval'), - }; - } - } -} diff --git a/packages/core/adapters/services/balance-detection-service.ts b/packages/core/adapters/services/balance-detection-service.ts deleted file mode 100644 index 597f2b68..00000000 --- a/packages/core/adapters/services/balance-detection-service.ts +++ /dev/null @@ -1,403 +0,0 @@ -import { - getTokenContractAddress, - TOKEN_METADATA, - logger, - type SUPPORTED_CHAINS_IDS, - type SUPPORTED_TOKENS, - CHAIN_METADATA, -} from '@nexus/commons'; -import { ChainAbstractionAdapter } from 'adapters/chain-abstraction-adapter'; -import { Hex, erc20Abi } from 'viem'; - -/** - * Detailed balance information for a user - */ -export interface DetailedBalanceInfo { - token: SUPPORTED_TOKENS; - chainId: number; - userAddress: string; - tokenAddress: string; - balance: string; - balanceFormatted: string; - sufficient: boolean; - shortfall: string; - shortfallFormatted: string; - decimals: number; - isNative: boolean; - lastChecked: string; -} - -/** - * Multi-token balance check result - */ -export interface MultiTokenBalanceResult { - userAddress: string; - chainId: number; - balances: DetailedBalanceInfo[]; - totalSufficient: boolean; - insufficientTokens: SUPPORTED_TOKENS[]; - lastChecked: string; -} - -/** - * Balance requirement specification - */ -export interface BalanceRequirement { - token: SUPPORTED_TOKENS; - amount: string; - allowPartial?: boolean; // If true, partial balance is acceptable -} - -/** - * Smart balance detection and analysis service - */ -export class BalanceDetectionService { - private adapter: ChainAbstractionAdapter; - - constructor(adapter: ChainAbstractionAdapter) { - this.adapter = adapter; - } - - private ensureInitialized() { - if (!this.adapter.nexusSDK.isInitialized()) { - throw new Error('Adapter not initialized'); - } - } - - /** - * Check detailed balance for a single token - */ - async getDetailedBalance( - userAddress: string, - token: SUPPORTED_TOKENS, - chainId: number, - requiredAmount?: string, - ): Promise { - this.ensureInitialized(); - - try { - logger.debug('DEBUG BalanceDetectionService - Checking balance:', { - userAddress, - token, - chainId, - requiredAmount, - }); - - const tokenAddress = getTokenContractAddress(token, chainId as SUPPORTED_CHAINS_IDS); - if (!tokenAddress) { - throw new Error(`Token ${token} not supported on chain ${CHAIN_METADATA[chainId]?.name}`); - } - - const isNative = token === 'ETH'; - const tokenMetadata = TOKEN_METADATA[token.toUpperCase()]; - const decimals = tokenMetadata?.decimals || 18; - - let balance: bigint; - - if (isNative) { - // Get ETH balance - balance = await this.adapter.nexusSDK.getEVMClient().getBalance({ - address: userAddress as Hex, - blockTag: 'latest', - }); - } else { - // Get ERC20 token balance using balanceOf - balance = await this.getERC20Balance(userAddress, tokenAddress); - } - - const balanceBigInt = BigInt(balance); - const balanceFormatted = this.formatTokenAmount(balance.toString(), decimals); - - // Calculate sufficiency and shortfall - let sufficient = true; - let shortfall = '0'; - let shortfallFormatted = '0'; - - if (requiredAmount) { - const requiredBigInt = BigInt(requiredAmount); - sufficient = balanceBigInt >= requiredBigInt; - - if (!sufficient) { - shortfall = (requiredBigInt - balanceBigInt).toString(); - shortfallFormatted = this.formatTokenAmount(shortfall, decimals); - } - } - - const result: DetailedBalanceInfo = { - token, - chainId, - userAddress, - tokenAddress, - balance: balance.toString(), - balanceFormatted, - sufficient, - shortfall, - shortfallFormatted, - decimals, - isNative, - lastChecked: new Date().toISOString(), - }; - - logger.info('DEBUG BalanceDetectionService - Balance check result:', { - token, - balance: balanceFormatted, - sufficient, - shortfall: shortfallFormatted, - }); - - return result; - } catch (error) { - logger.error( - `Failed to get detailed balance for ${token}:`, - error instanceof Error ? error : String(error), - ); - - // Return error state - return { - token, - chainId, - userAddress, - tokenAddress: getTokenContractAddress(token, chainId as SUPPORTED_CHAINS_IDS) || '', - balance: '0', - balanceFormatted: '0', - sufficient: false, - shortfall: requiredAmount || '0', - shortfallFormatted: '0', - decimals: TOKEN_METADATA[token.toUpperCase()]?.decimals || 18, - isNative: token === 'ETH', - lastChecked: new Date().toISOString(), - }; - } - } - - /** - * Check balances for multiple tokens - */ - async getMultiTokenBalances( - userAddress: string, - chainId: number, - requirements: BalanceRequirement[], - ): Promise { - this.ensureInitialized(); - - logger.info('DEBUG BalanceDetectionService - Multi-token balance check:', { - userAddress, - chainId, - requirements: requirements.length, - }); - - const balances: DetailedBalanceInfo[] = []; - const insufficientTokens: SUPPORTED_TOKENS[] = []; - - // Check each token balance - for (const requirement of requirements) { - try { - const balanceInfo = await this.getDetailedBalance( - userAddress, - requirement.token, - chainId, - requirement.amount, - ); - - balances.push(balanceInfo); - - // Track insufficient tokens (unless partial is allowed) - if (!balanceInfo.sufficient && !requirement.allowPartial) { - insufficientTokens.push(requirement.token); - } - } catch (error) { - logger.error( - `Failed to check balance for ${requirement.token}:`, - error instanceof Error ? error : String(error), - ); - insufficientTokens.push(requirement.token); - } - } - - const totalSufficient = insufficientTokens.length === 0; - - const result: MultiTokenBalanceResult = { - userAddress, - chainId, - balances, - totalSufficient, - insufficientTokens, - lastChecked: new Date().toISOString(), - }; - - logger.info('DEBUG BalanceDetectionService - Multi-token result:', { - totalSufficient, - insufficientCount: insufficientTokens.length, - insufficientTokens, - }); - - return result; - } - - /** - * Get ERC20 token balance using balanceOf call - */ - private async getERC20Balance(userAddress: string, tokenAddress: string): Promise { - try { - const balance = await this.adapter.nexusSDK.getEVMClient().readContract({ - abi: erc20Abi, - functionName: 'balanceOf', - address: tokenAddress as Hex, - args: [userAddress as Hex], - }); - - return balance; - } catch (error) { - logger.error( - `Failed to get ERC20 balance for ${tokenAddress}:`, - error instanceof Error ? error : String(error), - ); - return 0n; - } - } - - /** - * Format token amount from wei to human readable - */ - private formatTokenAmount(amount: string, decimals: number): string { - try { - const amountBigInt = BigInt(amount); - const divisor = BigInt(10) ** BigInt(decimals); - - // Handle whole number part - const wholePart = amountBigInt / divisor; - const fractionalPart = amountBigInt % divisor; - - if (fractionalPart === 0n) { - return wholePart.toString(); - } - - // Convert fractional part to decimal string - const fractionalStr = fractionalPart.toString().padStart(decimals, '0'); - const trimmedFractional = fractionalStr.replace(/0+$/, ''); - - if (trimmedFractional === '') { - return wholePart.toString(); - } - - return `${wholePart}.${trimmedFractional}`; - } catch (error) { - logger.error( - 'Failed to format token amount:', - error instanceof Error ? error : String(error), - ); - return '0'; - } - } - - /** - * Analyze balance gaps and suggest funding strategies - */ - async analyzeBalanceGaps( - userAddress: string, - chainId: number, - requirements: BalanceRequirement[], - ): Promise<{ - analysis: MultiTokenBalanceResult; - fundingStrategy: { - totalFundingNeeded: boolean; - tokenFunding: Array<{ - token: SUPPORTED_TOKENS; - shortfall: string; - shortfallFormatted: string; - priority: 'high' | 'medium' | 'low'; - suggestionType: 'bridge' | 'swap' | 'acquire'; - }>; - }; - }> { - const analysis = await this.getMultiTokenBalances(userAddress, chainId, requirements); - - const tokenFunding = analysis.balances - .filter((balance) => !balance.sufficient) - .map((balance) => ({ - token: balance.token, - shortfall: balance.shortfall, - shortfallFormatted: balance.shortfallFormatted, - priority: this.calculateFundingPriority(balance), - suggestionType: this.suggestFundingMethod(balance.token), - })); - - return { - analysis, - fundingStrategy: { - totalFundingNeeded: !analysis.totalSufficient, - tokenFunding, - }, - }; - } - - /** - * Calculate funding priority based on shortfall amount and token importance - */ - private calculateFundingPriority(balance: DetailedBalanceInfo): 'high' | 'medium' | 'low' { - const shortfallBigInt = BigInt(balance.shortfall); - const divisor = BigInt(10) ** BigInt(balance.decimals); - const shortfallNormalized = Number(shortfallBigInt) / Number(divisor); - - // High priority for native tokens or large amounts - if (balance.isNative || shortfallNormalized > 1000) { - return 'high'; - } - - // Medium priority for moderate amounts - if (shortfallNormalized > 10) { - return 'medium'; - } - - return 'low'; - } - - /** - * Suggest the best funding method for a token on a specific chain - */ - private suggestFundingMethod(token: SUPPORTED_TOKENS): 'bridge' | 'swap' | 'acquire' { - // For now, simple logic - can be enhanced with actual bridge/swap availability - if (token === 'ETH') { - return 'acquire'; // Need to buy ETH - } - - // For stablecoins, bridging is usually preferred - if (token === 'USDC' || token === 'USDT') { - return 'bridge'; - } - - // Default to swap for other tokens - return 'swap'; - } - - /** - * Check if user can afford a transaction including gas - */ - async canAffordTransaction( - userAddress: string, - chainId: number, - tokenRequirements: BalanceRequirement[], - estimatedGasCost: string, - ): Promise<{ - canAfford: boolean; - tokenDeficits: DetailedBalanceInfo[]; - gasDeficit: string; - totalDeficitValue?: string; - }> { - // Check token requirements - const tokenAnalysis = await this.getMultiTokenBalances(userAddress, chainId, tokenRequirements); - - // Check ETH balance for gas - const ethBalance = await this.getDetailedBalance(userAddress, 'ETH', chainId, estimatedGasCost); - - const canAfford = tokenAnalysis.totalSufficient && ethBalance.sufficient; - const tokenDeficits = tokenAnalysis.balances.filter((b) => !b.sufficient); - const gasDeficit = ethBalance.sufficient ? '0' : ethBalance.shortfall; - - return { - canAfford, - tokenDeficits, - gasDeficit, - }; - } -} diff --git a/packages/core/adapters/services/bridge-execute-service.ts b/packages/core/adapters/services/bridge-execute-service.ts deleted file mode 100644 index 71b394c1..00000000 --- a/packages/core/adapters/services/bridge-execute-service.ts +++ /dev/null @@ -1,1317 +0,0 @@ -import { ExecuteService } from './execute-service'; -import { parseUnits, Hex, toHex, TransactionReceipt } from 'viem'; -import type { ChainAbstractionAdapter } from '../chain-abstraction-adapter'; -import { - type BridgeAndExecuteParams, - type BridgeAndExecuteResult, - type BridgeAndExecuteSimulationResult, - type ExecuteParams, - type ExecuteSimulation, - type SimulationResult, - type SimulationStep, - type SUPPORTED_CHAINS_IDS, - type SUPPORTED_TOKENS, - NEXUS_EVENTS, - TOKEN_METADATA, - CHAIN_METADATA, - extractErrorMessage, - logger, - ProgressStep, - UserAssetDatum as UserAsset, -} from '@nexus/commons'; - -// Local constants for the service -const ADAPTER_CONSTANTS = { - DEFAULT_DECIMALS: 18, -}; - -interface ProviderError extends Error { - data?: { - message?: string; - }; -} - -export class BridgeExecuteService { - private executeService: ExecuteService; - private skipBridge: boolean = false; - private optimalBridgeAmount: string = '0'; - - constructor(private adapter: ChainAbstractionAdapter) { - this.executeService = new ExecuteService(adapter); - } - - /** - * Enable or disable gas estimation for execute transactions - * This provides easy control over whether gas estimation runs before execution - */ - public setGasEstimationEnabled(enabled: boolean): void { - // Access the transaction service through the execute service's public method - this.executeService.setGasEstimationEnabled(enabled); - } - - /** - * Bridge and execute operation - combines bridge and execute with proper sequencing - * Now includes smart balance checking to skip bridging when sufficient funds exist - */ - public async bridgeAndExecute(params: BridgeAndExecuteParams): Promise { - const { - toChainId, - token, - amount, - execute, - enableTransactionPolling = false, - transactionTimeout = 30000, - waitForReceipt = true, - receiptTimeout = 300000, - requiredConfirmations = 1, - } = params; - - // Declare here so accessible in catch/finally - let stepForwarder: (step: ProgressStep) => void = () => {}; - - try { - // Normalize the input amount to ensure consistent processing - const normalizedAmount = this.normalizeAmountToWei(amount, token); - - // Check if simulation was run - if not, calculate optimal bridge amount - if (this.optimalBridgeAmount === '0' && !this.skipBridge) { - logger.info('Simulation was not run, calculating optimal bridge amount...'); - const bridgeOptimization = await this.calculateOptimalBridgeAmount( - toChainId, - token, - normalizedAmount, - ); - this.skipBridge = bridgeOptimization.skipBridge; - this.optimalBridgeAmount = bridgeOptimization.optimalAmount; - } - - // Use the skipBridge flag set during simulation to determine execution path - if (this.skipBridge && execute) { - logger.info( - `Enhanced smart routing: Sufficient ${token} + gas balance on chain ${toChainId}, skipping bridge and executing directly`, - ); - - // Skip bridging - execute directly with existing funds - return await this.executeDirectly( - execute, - toChainId, - token, - normalizedAmount, - enableTransactionPolling, - transactionTimeout, - waitForReceipt, - receiptTimeout, - requiredConfirmations, - ); - } - - // Original bridge-and-execute flow when enhanced balance check fails - logger.info( - `Enhanced smart routing: Insufficient ${token} or gas balance on chain ${toChainId}, proceeding with bridge + execute`, - ); - - // Set up listeners to capture Arcana bridge steps and forward step completions - const bridgeStepsPromise: Promise = new Promise((resolve) => { - const expectedHandler = (steps: ProgressStep[]) => { - this.adapter.nexusSDK.nexusEvents.off(NEXUS_EVENTS.EXPECTED_STEPS, expectedHandler); - resolve(steps); - }; - this.adapter.nexusSDK.nexusEvents.on(NEXUS_EVENTS.EXPECTED_STEPS, expectedHandler); - }); - - stepForwarder = (step: ProgressStep) => { - this.adapter.nexusSDK.nexusEvents.emit(NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS, step); - }; - this.adapter.nexusSDK.nexusEvents.on(NEXUS_EVENTS.STEP_COMPLETE, stepForwarder); - - // Perform the actual bridge transaction using optimal amount - // Convert optimal bridge amount from wei to user-friendly format for bridge service - const tokenMetadata = TOKEN_METADATA[token.toUpperCase()]; - const decimals = tokenMetadata?.decimals || 18; - const { formatUnits } = await import('viem'); - const userFriendlyBridgeAmount = formatUnits(BigInt(this.optimalBridgeAmount), decimals); - - logger.info('Bridge amount conversion for execution:', { - optimalBridgeAmountWei: this.optimalBridgeAmount, - userFriendlyBridgeAmount, - decimals, - token, - }); - - const bridgeResult = await this.adapter.nexusSDK.bridge({ - token, - amount: userFriendlyBridgeAmount, - chainId: toChainId, - sourceChains: params.sourceChains, - }); - - if (!bridgeResult.success) { - throw new Error(`Bridge failed: ${bridgeResult.error}`); - } - - // Wait for captured bridge steps - const bridgeSteps = await bridgeStepsPromise; - - // Add a small delay to ensure bridge settlement is complete - logger.info('DEBUG bridgeAndExecute - Waiting for bridge settlement...'); - await new Promise((resolve) => setTimeout(resolve, 2000)); // 2 second delay - logger.info('DEBUG bridgeAndExecute - Bridge settlement delay complete'); - - // Prepare extra steps for approval/execute/receipt/confirmation - const extraSteps: ProgressStep[] = []; - - const makeStep = ( - typeID: string, - type: string, - data: Record = {}, - ): ProgressStep => ({ - typeID, - type, - data: { - chainID: toChainId, - chainName: CHAIN_METADATA[toChainId]?.name || toChainId.toString(), - ...data, - }, - }); - - if (execute?.tokenApproval) { - extraSteps.push(makeStep('AP', 'APPROVAL')); - } - - if (execute) { - extraSteps.push(makeStep('TS', 'TRANSACTION_SENT')); - if (waitForReceipt) { - extraSteps.push(makeStep('RR', 'RECEIPT_RECEIVED')); - } - if ((requiredConfirmations ?? 0) > 0) { - extraSteps.push(makeStep('CN', 'TRANSACTION_CONFIRMED')); - } - } - - // Emit consolidated expected steps for the whole operation - this.adapter.nexusSDK.nexusEvents.emit(NEXUS_EVENTS.BRIDGE_EXECUTE_EXPECTED_STEPS, [ - ...bridgeSteps, - ...extraSteps, - ]); - - const { executeTransactionHash, executeExplorerUrl, approvalTransactionHash } = - await this.handleExecutePhase( - execute, - toChainId, - token, - normalizedAmount, - enableTransactionPolling, - transactionTimeout, - waitForReceipt, - receiptTimeout, - requiredConfirmations, - // pass helper to emit steps - (step) => - this.adapter.nexusSDK.nexusEvents.emit( - NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS, - step, - ), - makeStep, - ); - - const result: BridgeAndExecuteResult = { - executeTransactionHash, - executeExplorerUrl, - approvalTransactionHash, - bridgeTransactionHash: bridgeResult.transactionHash, - bridgeExplorerUrl: bridgeResult.explorerUrl, - toChainId, - success: true, - bridgeSkipped: false, // bridge was performed normally - }; - - // Clean up listener - this.adapter.nexusSDK.nexusEvents.off(NEXUS_EVENTS.STEP_COMPLETE, stepForwarder); - - return result; - } catch (error) { - const errorMessage = extractErrorMessage(error, 'bridge and execute'); - - // Forward error step (generic) for UI consumers - this.adapter.nexusSDK.nexusEvents.emit(NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS, { - typeID: 'ER', - type: 'operation.failed', - data: { - error: errorMessage, - stage: errorMessage.includes('Execute phase failed') ? 'execute' : 'bridge', - }, - }); - - // Clean listener - this.adapter.nexusSDK.nexusEvents.off(NEXUS_EVENTS.STEP_COMPLETE, stepForwarder); - - return { - toChainId, - success: false, - error: `Bridge and execute operation failed: ${errorMessage}`, - bridgeSkipped: false, // error occurred during normal bridge flow - }; - } - } - - /** - * Simulate bridge and execute operation - * Now includes smart routing simulation - */ - public async simulateBridgeAndExecute( - params: BridgeAndExecuteParams, - ): Promise { - try { - const { execute } = params; - const steps: SimulationStep[] = []; - - // Normalize the input amount to ensure consistent processing - const normalizedAmount = this.normalizeAmountToWei(params.amount, params.token); - - // First, calculate optimal bridge amount based on destination balance - const bridgeOptimization = await this.calculateOptimalBridgeAmount( - params.toChainId, - params.token, - normalizedAmount, - ); - - this.skipBridge = bridgeOptimization.skipBridge; - this.optimalBridgeAmount = bridgeOptimization.optimalAmount; - - // Run simulations with optimal amounts - let bridgeSimulation: SimulationResult | ExecuteSimulation | null = null; - let bridgeReceiveAmount = '0'; - let totalBridgeFee = '0'; - - // Only add bridge step if we're not skipping it - if (!this.skipBridge) { - // Convert optimal bridge amount from wei to user-friendly format for bridge service - const tokenMetadata = TOKEN_METADATA[params.token.toUpperCase()]; - const decimals = tokenMetadata?.decimals || 18; - const { formatUnits } = await import('viem'); - const userFriendlyBridgeAmount = formatUnits(BigInt(this.optimalBridgeAmount), decimals); - - logger.info('Bridge amount conversion for simulation:', { - optimalBridgeAmountWei: this.optimalBridgeAmount, - userFriendlyBridgeAmount, - decimals, - token: params.token, - }); - - bridgeSimulation = await this.adapter.nexusSDK.simulateBridge({ - token: params.token, - amount: userFriendlyBridgeAmount, - chainId: params.toChainId, - sourceChains: params.sourceChains, - }); - steps.push({ - type: 'bridge', - required: true, - simulation: bridgeSimulation!, - description: `Bridge ${userFriendlyBridgeAmount} ${params.token} to chain ${params.toChainId}`, - }); - - // Enhanced bridge analysis - if (bridgeSimulation?.intent) { - const intent = bridgeSimulation.intent; - - // Extract destination amount (received amount after bridging) - if (intent.destination?.amount && intent.destination.amount !== '0') { - bridgeReceiveAmount = intent.destination.amount; - } - - // Format bridge fees properly - if (intent.fees?.total) { - totalBridgeFee = `${intent.fees.total}`; - } - } - } - - let executeSimulation: ExecuteSimulation | undefined; - const approvalRequired = false; - - if (execute) { - try { - // Use the received amount from bridge simulation for execute simulation - let receivedAmountForContract = normalizedAmount; // fallback to normalized original amount - - if (bridgeReceiveAmount !== '0') { - // Get token decimals from bridge simulation - const tokenDecimals = - bridgeSimulation?.intent?.token?.decimals || bridgeSimulation?.token?.decimals; - - if (tokenDecimals) { - const receivedAmountBigInt = parseUnits(bridgeReceiveAmount, tokenDecimals); - receivedAmountForContract = receivedAmountBigInt.toString(); - } - } - - // Create execute parameters for simulation - use wei format for SimulationEngine - // SimulationEngine expects amounts in wei format, not user-friendly format - const modifiedExecuteParams: ExecuteParams = { - ...execute, - toChainId: params.toChainId, - ...(params.token !== 'ETH' && execute.tokenApproval - ? { - tokenApproval: { - token: params.token, - amount: receivedAmountForContract, // Keep in wei format for SimulationEngine - }, - } - : {}), - }; - - executeSimulation = - await this.executeService.simulateExecuteEnhanced(modifiedExecuteParams); - if (executeSimulation) { - steps.push({ - type: 'execute', - required: true, - simulation: executeSimulation, - description: `Execute ${execute.functionName} on contract ${execute.contractAddress}`, - }); - } - - // Execute analysis details are available in the simulation result - } catch (simulationError) { - logger.warn(`Execute simulation error: ${simulationError}`); - executeSimulation = { - contractAddress: execute.contractAddress, - functionName: execute.functionName, - gasUsed: '0', - success: false, - error: `Simulation failed: ${simulationError}`, - }; - - steps.push({ - type: 'execute', - required: true, - simulation: executeSimulation, - description: `Execute ${execute.functionName} on contract ${execute.contractAddress} (failed)`, - }); - } - } - - // Calculate enhanced total cost with approval step - let totalEstimatedCost: - | { total: string; breakdown: { bridge: string; execute: string } } - | undefined; - - if (totalBridgeFee !== '0' || executeSimulation?.gasUsed) { - logger.debug('DEBUG bridge-execute-service - totalBridgeFee (ETH):', totalBridgeFee); - logger.debug( - 'DEBUG bridge-execute-service - executeSimulation?.gasUsed:', - executeSimulation?.gasUsed, - ); - - try { - const executeFee = executeSimulation?.gasCostEth || executeSimulation?.gasUsed || '0'; - logger.debug('DEBUG bridge-execute-service - executeFee source value:', executeFee); - - let executeFeeEth = executeFee; - - // If gasCostEth wasn't available, executeFee will be gas units – convert. - if (executeSimulation?.gasCostEth === undefined) { - logger.debug('DEBUG bridge-execute-service - executeFee (gas units):', executeFee); - - try { - // Get the current gas price from the connected provider (wei, hex string) - const gasPriceHex = (await this.adapter.nexusSDK.request({ - method: 'eth_gasPrice', - })) as string; - const gasPriceWei = parseInt(gasPriceHex, 16); - - // gasUsed (string) * gasPriceWei (number) => wei, then convert to ETH - const gasUsedNum = parseFloat(executeFee); - const costEthNum = (gasUsedNum * gasPriceWei) / 1e18; // 1e18 wei per ETH - executeFeeEth = costEthNum.toFixed(8); // keep reasonable precision - } catch (gpErr) { - logger.warn('Failed to fetch gas price for execute fee conversion:', gpErr); - } - } - logger.debug('DEBUG bridge-execute-service - executeFee (ETH):', executeFeeEth); - - // Add bridge fee (already an ETH figure) with converted execute fee - const totalFeeEth = (parseFloat(totalBridgeFee) + parseFloat(executeFeeEth)).toString(); - logger.debug('DEBUG bridge-execute-service - totalFeeEth:', totalFeeEth); - - totalEstimatedCost = { - total: totalFeeEth, - breakdown: { - bridge: totalBridgeFee, - execute: executeFeeEth, - }, - }; - } catch (error) { - logger.warn('Could not calculate total cost - cost breakdown may be incomplete:', error); - } - } - - // Enhanced balance check after simulations are complete - // Re-validate the skip bridge decision with actual gas estimates - if (!this.skipBridge && executeSimulation?.gasUsed) { - const finalOptimization = await this.calculateOptimalBridgeAmount( - params.toChainId, - params.token, - normalizedAmount, - executeSimulation?.gasUsed, - executeSimulation?.gasCostEth, - ); - - // Update skip bridge decision if gas check reveals we can skip - if (finalOptimization.skipBridge && !this.skipBridge) { - this.skipBridge = true; - this.optimalBridgeAmount = '0'; - logger.info('Updated bridge decision after gas validation: bridge can be skipped'); - } - } - - logger.info( - `Enhanced balance check result: skipBridge = ${this.skipBridge} for chain ${params.toChainId}`, - ); - - // Adjust simulation result based on skip decision - let finalBridgeSimulation: SimulationResult | null = bridgeSimulation; - let finalSteps = steps; - - if (this.skipBridge) { - // When bridge is skipped, set bridgeSimulation to null and filter out bridge steps - finalBridgeSimulation = null; - finalSteps = steps.filter((step) => step.type !== 'bridge'); - - logger.info('Bridge will be skipped - using execute-only simulation result'); - return { - steps: finalSteps, - bridgeSimulation: finalBridgeSimulation, - executeSimulation, - totalEstimatedCost, - success: true, - metadata: { - contractAddress: executeSimulation?.contractAddress ?? '', - functionName: executeSimulation?.functionName ?? '', - bridgeReceiveAmount: this.skipBridge - ? params.amount.toString() - : bridgeReceiveAmount !== '0' - ? bridgeReceiveAmount - : this.optimalBridgeAmount, - bridgeFee: this.skipBridge ? '0' : totalBridgeFee.replace(' ETH', '') || '0', - inputAmount: params.amount.toString(), - optimalBridgeAmount: this.optimalBridgeAmount, - targetChain: params.toChainId, - approvalRequired, - bridgeSkipped: this.skipBridge, - token: params?.token, - }, - }; - } - - return { - steps: finalSteps, - bridgeSimulation: finalBridgeSimulation, - executeSimulation, - totalEstimatedCost, - success: true, - }; - } catch (error) { - return { - steps: [], - bridgeSimulation: null, - executeSimulation: undefined, - success: false, - error: `Simulation failed: ${extractErrorMessage(error, 'simulation')}`, - }; - } - } - - /** - * Handle the execute phase of bridge and execute - * Uses callback-based parameter pattern for dynamic parameter building - */ - private async handleExecutePhase( - execute: Omit | undefined, - toChainId: SUPPORTED_CHAINS_IDS, - bridgeToken: SUPPORTED_TOKENS, - bridgeAmount: string, - enableTransactionPolling: boolean, - transactionTimeout: number, - waitForReceipt?: boolean, - receiptTimeout?: number, - requiredConfirmations?: number, - emitStep?: (step: ProgressStep) => void, - makeStep?: (typeID: string, type: string, data?: Record) => ProgressStep, - approvalBufferBpsOverride?: number, - ): Promise<{ - executeTransactionHash?: string; - executeExplorerUrl?: string; - approvalTransactionHash?: string; - }> { - if (!execute || !emitStep || !makeStep) return {}; - - try { - // Debug logging to understand amount handling - logger.info('DEBUG handleExecutePhase - Bridge amount (micro-units):', bridgeAmount); - logger.info('DEBUG handleExecutePhase - Bridge token:', bridgeToken); - - const { formatUnits } = await import('viem'); - - const decimals = TOKEN_METADATA[bridgeToken]?.decimals || 18; - const userFriendlyAmount = formatUnits(BigInt(bridgeAmount), decimals); - - logger.info('DEBUG handleExecutePhase - Amount conversion:', { - microUnits: bridgeAmount, - decimals, - userFriendly: userFriendlyAmount, - bridgeToken, - }); - - // Create execute parameters with user-friendly amount for the callback - // Only include token approval for ERC-20 tokens. Native ETH should never attempt approval. - const finalExecuteParams: ExecuteParams = { - ...execute, - toChainId, - ...(bridgeToken !== 'ETH' && execute.tokenApproval - ? { - tokenApproval: { - token: bridgeToken, - amount: userFriendlyAmount, - }, - } - : {}), - ...(approvalBufferBpsOverride !== undefined - ? { approvalBufferBps: approvalBufferBpsOverride } - : undefined), - }; - - logger.info('DEBUG handleExecutePhase - Execute params created with user-friendly amount:', { - userFriendlyAmount, - originalBridgeAmount: bridgeAmount, - token: bridgeToken, - decimals, - }); - - // Check user balance on destination chain before executing - try { - const destinationBalance = await this.getDestinationChainBalance(toChainId, bridgeToken); - logger.info('DEBUG handleExecutePhase - User balance on destination chain:', { - chainId: toChainId, - token: bridgeToken, - balance: destinationBalance, - requiredAmount: bridgeAmount, - }); - } catch (balanceError) { - logger.warn( - 'DEBUG handleExecutePhase - Could not check destination balance:', - balanceError, - ); - } - - // Execute the target contract call - let execute service handle approval - logger.info('DEBUG handleExecutePhase - Executing contract call with params:', { - ...finalExecuteParams, - toChainId, - }); - - const executeResult = await this.executeService.execute({ - ...finalExecuteParams, - enableTransactionPolling, - transactionTimeout, - waitForReceipt, - receiptTimeout, - requiredConfirmations, - }); - - // Check if we should verify transaction success - if (executeResult.transactionHash) { - // Transaction sent step - emitStep( - makeStep('TS', 'transaction.sent', { - txHash: executeResult.transactionHash, - }), - ); - } - - if (waitForReceipt && executeResult.transactionHash) { - logger.info( - 'DEBUG handleExecutePhase - Checking transaction success for:', - executeResult.transactionHash, - ); - - const transactionCheck = await this.checkTransactionSuccess( - executeResult.transactionHash, - toChainId, - ); - - if (!transactionCheck.success) { - logger.error('DEBUG handleExecutePhase - Transaction failed:', transactionCheck.error); - emitStep( - makeStep('EX', 'execute', { - error: transactionCheck.error, - }), - ); - throw new Error(`Execute transaction failed: ${transactionCheck.error}`); - } - - logger.info( - 'DEBUG handleExecutePhase - Transaction succeeded with gas used:', - transactionCheck.gasUsed, - ); - - // Emit receipt received step - emitStep( - makeStep('RR', 'receipt.received', { - txHash: executeResult.transactionHash, - }), - ); - - // Emit confirmation step if requiredConfirmations met - if ((requiredConfirmations ?? 0) > 0) { - emitStep( - makeStep('CN', 'transaction.confirmed', { - confirmations: requiredConfirmations, - }), - ); - } - } - - return { - executeTransactionHash: executeResult.transactionHash, - executeExplorerUrl: executeResult.explorerUrl, - approvalTransactionHash: executeResult.approvalTransactionHash, - }; - } catch (executeError) { - logger.error('DEBUG handleExecutePhase - Execute error:', executeError as Error); - emitStep(makeStep('EX', 'execute', { error: (executeError as Error).message })); - throw new Error( - `Execute phase failed: ${extractErrorMessage(executeError, 'execute phase')}`, - ); - } - } - - /** - * Normalize amount input to wei format for consistent processing - * Supports various input formats and automatically handles token decimals - */ - private normalizeAmountToWei(amount: string | number, token: string): string { - try { - // Convert to string if it's a number - const amountStr = amount.toString(); - - logger.info('DEBUG normalizeAmountToWei - Input:', { amount: amountStr, token }); - - // Handle edge cases - if (!amountStr || amountStr === '0') { - return '0'; - } - - // Get token metadata for accurate decimal handling - const tokenUpper = token.toUpperCase(); - const tokenMetadata = TOKEN_METADATA[tokenUpper]; - const decimals = tokenMetadata?.decimals || ADAPTER_CONSTANTS?.DEFAULT_DECIMALS || 18; - - logger.info('DEBUG normalizeAmountToWei - Token info:', { - tokenUpper, - decimals, - tokenMetadata, - }); - - // If it's already in wei format (no decimals, large number), return as-is - // Check length to avoid converting small integers to wei incorrectly - if (!amountStr.includes('.') && amountStr.length > 10) { - logger.info('DEBUG normalizeAmountToWei - Already in wei format'); - return amountStr; - } - - // Handle hex values - if (amountStr.startsWith('0x')) { - const result = BigInt(amountStr).toString(); - logger.info(`DEBUG normalizeAmountToWei - Hex conversion: ${result}`); - return result; - } - - // Handle decimal amounts (need conversion to wei) - if (amountStr.includes('.')) { - const result = parseUnits(amountStr, decimals).toString(); - logger.info(`DEBUG normalizeAmountToWei - Decimal conversion: ${amountStr} -> ${result}`); - return result; - } - - // Handle whole number inputs - const numValue = parseFloat(amountStr); - - // For USDC specifically, be more careful with the conversion - // USDC typically has 6 decimals, so 1 USDC = 1,000,000 micro-USDC - const USDC_MICRO_UNITS_THRESHOLD = 1_000_000; // 1 USDC - - if (tokenUpper === 'USDC') { - // For USDC, small numbers (< 1,000,000) are likely user amounts that need conversion - if (numValue < USDC_MICRO_UNITS_THRESHOLD) { - const result = parseUnits(amountStr, 6).toString(); - logger.info( - `DEBUG normalizeAmountToWei - USDC user amount conversion: ${amountStr} -> ${result}`, - ); - return result; - } else { - // Larger numbers are likely already in micro-USDC - logger.info('DEBUG normalizeAmountToWei - USDC already in micro format'); - return amountStr; - } - } - - // For small whole numbers, likely represent user-friendly amounts (e.g., "1" ETH) - // For larger numbers, likely already in wei format - if (numValue < 1000 || (tokenMetadata?.decimals === 6 && numValue < 1000000)) { - // Convert small numbers as user-friendly amounts - const result = parseUnits(amountStr, decimals).toString(); - logger.info( - `DEBUG normalizeAmountToWei - User amount conversion: ${amountStr} -> ${result}`, - ); - return result; - } else { - // Assume larger numbers are already in the correct format - logger.info('DEBUG normalizeAmountToWei - Already in correct format'); - return amountStr; - } - } catch (error) { - // If conversion fails, return original - logger.warn(`Failed to normalize amount ${amount} for token ${token}:`, error); - return amount.toString(); - } - } - - /** - * Get transaction receipt with retry logic - * Note: Assumes we're already on the correct chain (handled by checkTransactionSuccess) - */ - private async getTransactionReceipt( - txHash: string, - maxRetries: number = 3, - ): Promise { - return this.adapter.nexusSDK.getEVMClient().waitForTransactionReceipt({ - hash: txHash as Hex, - retryCount: maxRetries, - }); - } - - /** - * Simulate a failed transaction to get the revert reason - * Note: Assumes we're already on the correct chain (handled by checkTransactionSuccess) - */ - private async simulateFailedTransaction(txHash: string): Promise { - try { - // Get the original transaction details - const tx = await this.adapter.nexusSDK.getEVMClient().getTransaction({ - hash: txHash as Hex, - }); - - if (!tx || tx === null) { - return null; - } - - // Type guard to ensure transaction has required properties - if (typeof tx !== 'object' || tx === null) { - return 'Invalid transaction data'; - } - - if (!tx.to || !tx.input) { - return 'Invalid transaction data'; - } - - // Get the transaction receipt to find the block number where it failed - const receipt = await this.adapter.nexusSDK.getEVMClient().getTransactionReceipt({ - hash: txHash as Hex, - }); - - // Use the block number where the transaction was mined, or the previous block - // This ensures we simulate the exact state when the transaction failed - let simulationBlock = 0n; - - if (receipt) { - if (receipt.blockNumber) { - simulationBlock = receipt.blockNumber; - } - } else if (tx.blockNumber) { - simulationBlock = tx.blockNumber; - } - - logger.info(`DEBUG simulateFailedTransaction - Simulating at block: ${simulationBlock}`); - - // Simulate the transaction call to get revert reason - await this.adapter.nexusSDK.getEVMClient().call({ - to: tx.to, - data: tx.input, - value: tx.value, - gas: tx.gas, - blockNumber: simulationBlock, - }); - - // If eth_call succeeds when we expected it to fail, this is suspicious - // The original transaction failed but the simulation passes - logger.warn( - 'DEBUG simulateFailedTransaction - eth_call succeeded but original transaction failed. This might indicate a state-dependent failure.', - ); - return 'Transaction failed due to state changes or gas issues'; - } catch (error: unknown) { - logger.info( - 'DEBUG simulateFailedTransaction - eth_call failed as expected, extracting revert reason', - ); - - // This is the expected path - eth_call should fail and give us the revert reason - // Extract revert reason from error - if (error && typeof error === 'object' && 'data' in error) { - const providerError = error as ProviderError; - if (providerError.data?.message) { - return providerError.data.message; - } - } - - if (error && typeof error === 'object' && 'message' in error) { - const errorWithMessage = error as { message: string }; - - // Parse common revert reason patterns - const revertMatch = errorWithMessage.message.match(/revert (.+?)(?:\s|$)/i); - if (revertMatch) { - return revertMatch[1]; - } - - // Check for execution reverted patterns - if (errorWithMessage.message.includes('execution reverted')) { - const cleanMessage = errorWithMessage.message.replace('execution reverted: ', '').trim(); - return cleanMessage || 'Transaction reverted without reason'; - } - - // Handle other common error patterns - if (errorWithMessage.message.includes('insufficient funds')) { - return 'Insufficient funds for gas * price + value'; - } - - if (errorWithMessage.message.includes('gas required exceeds allowance')) { - return 'Out of gas'; - } - - return errorWithMessage.message; - } - - return 'Transaction simulation failed'; - } - } - - /** - * Check transaction success and get detailed error information - */ - private async checkTransactionSuccess( - txHash: string, - chainId: number, - maxRetries: number = 5, - retryDelay: number = 3000, - ): Promise<{ - success: boolean; - error?: string; - gasUsed?: string; - }> { - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - logger.info( - `DEBUG checkTransactionSuccess - Attempt ${attempt}/${maxRetries}: Checking transaction: ${txHash} on chain: ${chainId}`, - ); - - // Ensure we're on the correct chain before checking transaction - const currentChainId = await this.adapter.nexusSDK.getEVMClient().getChainId(); - - if (currentChainId !== chainId) { - logger.info( - `DEBUG checkTransactionSuccess - Switching from chain ${currentChainId} to ${chainId}`, - ); - try { - await this.adapter.nexusSDK.getEVMClient().switchChain({ id: chainId }); - // Wait a bit after chain switch - await new Promise((resolve) => setTimeout(resolve, 1000)); - } catch (switchError) { - logger.error( - `DEBUG checkTransactionSuccess - Failed to switch to chain ${chainId}:`, - switchError as Error, - ); - return { - success: false, - error: `Failed to switch to chain ${chainId} for transaction verification`, - }; - } - } - - // 1. Get transaction receipt - basic success/failure - const receipt = await this.getTransactionReceipt(txHash); - - if (!receipt) { - if (attempt < maxRetries) { - logger.info( - `DEBUG checkTransactionSuccess - Receipt not found, retrying in ${retryDelay}ms...`, - ); - await new Promise((resolve) => setTimeout(resolve, retryDelay)); - continue; // Retry - } - - return { - success: false, - error: 'Transaction receipt not found after multiple attempts', - }; - } - - logger.info(`DEBUG checkTransactionSuccess - Receipt status: ${receipt.status}`); - - // Check if transaction succeeded - if (receipt.status === 'success') { - return { - success: true, - gasUsed: toHex(receipt.gasUsed), - }; - } else { - let errorMessage = 'Transaction failed'; - - // 3. Simulate the transaction to get detailed error - try { - const simulationError = await this.simulateFailedTransaction(txHash); - if (simulationError) { - errorMessage = simulationError; - } - } catch (simError) { - logger.warn('DEBUG checkTransactionSuccess - Simulation failed:', simError); - // Keep generic error message if simulation fails - } - - logger.info(`DEBUG checkTransactionSuccess - Final error: ${errorMessage}`); - - return { - success: false, - error: errorMessage, - gasUsed: toHex(receipt.gasUsed), - }; - } - - // Transaction failed - now get the error reason - } catch (error) { - logger.error(`DEBUG checkTransactionSuccess - Attempt ${attempt} failed:`, error as Error); - - if (attempt < maxRetries) { - logger.info(`DEBUG checkTransactionSuccess - Retrying in ${retryDelay}ms...`); - await new Promise((resolve) => setTimeout(resolve, retryDelay)); - continue; // Retry - } - - // Final attempt failed - return { - success: false, - error: `Failed to check transaction status after ${maxRetries} attempts: ${extractErrorMessage(error, 'transaction check')}`, - }; - } - } - - // This should never be reached, but just in case - return { - success: false, - error: `Transaction check failed after ${maxRetries} attempts`, - }; - } - - /** - * Calculate optimal bridge amount based on destination chain balance - * Returns the exact amount needed to bridge, or indicates if bridge can be skipped entirely - */ - private async calculateOptimalBridgeAmount( - chainId: SUPPORTED_CHAINS_IDS, - token: SUPPORTED_TOKENS, - requiredAmount: string, - gasEstimate?: string, - gasCostEth?: string, - ): Promise<{ skipBridge: boolean; optimalAmount: string }> { - try { - // Get destination chain balance - const destinationBalance = await this.getDestinationChainBalance(chainId, token); - - if (destinationBalance === null) { - // If we can't get balance info, bridge the full amount - return { skipBridge: false, optimalAmount: requiredAmount }; - } - - const requiredAmountBigInt = BigInt(requiredAmount); - const destinationBalanceBigInt = BigInt(destinationBalance); - - // Check if we have sufficient balance on destination to skip bridge entirely - if (destinationBalanceBigInt >= requiredAmountBigInt) { - // Check gas balance if we have gas estimate - if (gasEstimate || gasCostEth) { - const hasGasBalance = await this.checkGasBalance(chainId, gasEstimate, gasCostEth); - if (!hasGasBalance) { - logger.info(`Insufficient gas balance on chain ${chainId}, cannot skip bridge`); - return { skipBridge: false, optimalAmount: requiredAmount }; - } - } - - logger.info( - `Sufficient ${token} and gas balance on chain ${chainId}, bridge can be skipped`, - ); - return { skipBridge: true, optimalAmount: '0' }; - } - - // Calculate how much we need to bridge (required - what's already on destination) - const optimalBridgeAmountBigInt = requiredAmountBigInt - destinationBalanceBigInt; - const optimalAmount = ( - optimalBridgeAmountBigInt > 0n ? optimalBridgeAmountBigInt : 0n - ).toString(); - - logger.info(`Optimal bridge calculation:`, { - token, - chainId, - requiredAmount, - destinationBalance, - optimalBridgeAmount: optimalAmount, - }); - - return { skipBridge: false, optimalAmount }; - } catch (error) { - logger.warn(`Failed to calculate optimal bridge amount: ${error}`); - // Default to bridging full amount on error - return { skipBridge: false, optimalAmount: requiredAmount }; - } - } - - /** - * Get destination chain balance for a specific token - * Returns balance in wei as string, or null if not found - */ - private async getDestinationChainBalance( - chainId: SUPPORTED_CHAINS_IDS, - token: SUPPORTED_TOKENS, - ): Promise { - try { - logger.info(`Getting ${token} balance on chain ${chainId}`); - - // Get user's unified balances - const balances = (await this.adapter.nexusSDK.getUnifiedBalances()) as UserAsset[]; - - // Find the balance for the specific token - const tokenBalance = balances.find((asset) => asset.symbol === token); - - if (!tokenBalance || !tokenBalance.breakdown) { - logger.info(`No ${token} balance found`); - return null; - } - - // Find balance on the specific chain - const chainBalance = tokenBalance.breakdown.find((balance) => balance.chain.id === chainId); - - if (!chainBalance) { - logger.info(`No ${token} balance found on chain ${chainId}`); - return '0'; // Return 0 if no balance on this chain - } - - // Get token metadata for decimal conversion - const tokenMetadata = TOKEN_METADATA[token.toUpperCase()]; - const decimals = tokenMetadata?.decimals || 18; - - // Convert the balance to wei for calculation - const balanceInWei = parseUnits(chainBalance.balance, decimals); - - logger.info(`Balance found:`, { - token, - chainId, - balance: chainBalance.balance, - balanceInWei: balanceInWei.toString(), - }); - - return balanceInWei.toString(); - } catch (error) { - logger.warn(`Failed to get destination chain balance: ${error}`); - return null; - } - } - - /** - * Check native token balance for gas requirements - */ - private async checkGasBalance( - chainId: SUPPORTED_CHAINS_IDS, - gasEstimate?: string, - gasCostEth?: string, - ): Promise { - try { - // Get native token symbol for this chain - const chainMetadata = CHAIN_METADATA[chainId]; - if (!chainMetadata) { - logger.warn(`No chain metadata found for chain ${chainId}`); - return false; - } - - const nativeTokenSymbol = chainMetadata.nativeCurrency.symbol; - logger.info(`Checking ${nativeTokenSymbol} balance on chain ${chainId} for gas`); - - // Get user's unified balances - const balances = (await this.adapter.nexusSDK.getUnifiedBalances()) as UserAsset[]; - - // Find the native token balance - const nativeTokenBalance = balances.find((asset) => asset.symbol === nativeTokenSymbol); - - if (!nativeTokenBalance || !nativeTokenBalance.breakdown) { - logger.info(`No ${nativeTokenSymbol} balance found`); - return false; - } - - // Find balance on the specific chain - const chainBalance = nativeTokenBalance.breakdown.find( - (balance) => balance.chain.id === chainId, - ); - - if (!chainBalance) { - logger.info(`No ${nativeTokenSymbol} balance found on chain ${chainId}`); - return false; - } - - // Calculate required gas cost - let requiredGasCost = '0'; - - if (gasCostEth) { - // If we have gas cost in ETH, use it directly - requiredGasCost = gasCostEth; - } else if (gasEstimate) { - // Convert gas estimate to ETH using current gas price - try { - const gasPriceHex = (await this.adapter.nexusSDK.request({ - method: 'eth_gasPrice', - })) as string; - const gasPriceWei = parseInt(gasPriceHex, 16); - - const gasUsedNum = parseFloat(gasEstimate); - const costEthNum = (gasUsedNum * gasPriceWei) / 1e18; // Convert wei to ETH - requiredGasCost = costEthNum.toString(); - } catch (error) { - logger.warn(`Failed to fetch gas price for gas balance check: ${error}`); - return false; - } - } - - // Add 10% buffer to required gas cost - const requiredGasCostWithBuffer = (parseFloat(requiredGasCost) * 1.1).toString(); - - // Compare balances (both in user-friendly format like ETH) - const userBalance = parseFloat(chainBalance.balance); - const requiredGasFloat = parseFloat(requiredGasCostWithBuffer); - - const hasSufficientGasBalance = userBalance >= requiredGasFloat; - - logger.info(`Gas balance check result:`, { - nativeTokenSymbol, - chainId, - userBalance: chainBalance.balance, - requiredGasCost, - requiredGasCostWithBuffer, - hasSufficientGasBalance, - }); - - return hasSufficientGasBalance; - } catch (error) { - logger.warn(`Failed to check gas balance: ${error}`); - return false; - } - } - - /** - * Execute directly without bridging when user has sufficient funds - * Uses callback-based parameters for dynamic execution - */ - private async executeDirectly( - execute: Omit, - toChainId: SUPPORTED_CHAINS_IDS, - token: SUPPORTED_TOKENS, - amount: string, - enableTransactionPolling: boolean, - transactionTimeout: number, - waitForReceipt?: boolean, - receiptTimeout?: number, - requiredConfirmations?: number, - ): Promise { - try { - // Emit expected steps for execute-only flow - const executeSteps: ProgressStep[] = []; - - const makeStep = ( - typeID: string, - type: string, - data: Record = {}, - ): ProgressStep => ({ - typeID, - type, - data: { - chainID: toChainId, - chainName: CHAIN_METADATA[toChainId]?.name || toChainId.toString(), - ...data, - }, - }); - - // Add steps for execute-only flow - if (execute.tokenApproval) { - executeSteps.push(makeStep('AP', 'APPROVAL')); - } - executeSteps.push(makeStep('TS', 'TRANSACTION_SENT')); - if (waitForReceipt) { - executeSteps.push(makeStep('RR', 'RECEIPT_RECEIVED')); - } - if ((requiredConfirmations ?? 0) > 0) { - executeSteps.push(makeStep('CN', 'TRANSACTION_CONFIRMED')); - } - - // Emit expected steps for execute-only flow - this.adapter.nexusSDK.nexusEvents.emit( - NEXUS_EVENTS.BRIDGE_EXECUTE_EXPECTED_STEPS, - executeSteps, - ); - - // Execute directly using existing funds - const { executeTransactionHash, executeExplorerUrl, approvalTransactionHash } = - await this.handleExecutePhase( - execute, - toChainId, - token, - amount, - enableTransactionPolling, - transactionTimeout, - waitForReceipt, - receiptTimeout, - requiredConfirmations, - (step) => - this.adapter.nexusSDK.nexusEvents.emit( - NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS, - step, - ), - makeStep, - ); - - return { - executeTransactionHash, - executeExplorerUrl, - approvalTransactionHash, - bridgeTransactionHash: undefined, // bridge was skipped - bridgeExplorerUrl: undefined, // bridge was skipped - toChainId, - success: true, - bridgeSkipped: true, // bridge was skipped due to sufficient funds - }; - } catch (error) { - const errorMessage = extractErrorMessage(error, 'execute directly'); - - // Emit error step - this.adapter.nexusSDK.nexusEvents.emit(NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS, { - typeID: 'ER', - type: 'operation.failed', - data: { - error: errorMessage, - stage: 'execute', - }, - }); - - return { - toChainId, - success: false, - error: `Execute-only operation failed: ${errorMessage}`, - bridgeSkipped: true, // error occurred during execute-only flow - }; - } - } -} diff --git a/packages/core/adapters/services/execute-service.ts b/packages/core/adapters/services/execute-service.ts deleted file mode 100644 index 31e79bdd..00000000 --- a/packages/core/adapters/services/execute-service.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { TransactionService } from './transaction-service'; -import { ApprovalService } from './approval-service'; -import { getSimulationClient } from '../../integrations/tenderly'; -import { - extractErrorMessage, - logger, - type ExecuteParams, - type ExecuteResult, - type ExecuteSimulation, -} from '@nexus/commons'; -import { ChainAbstractionAdapter } from '../chain-abstraction-adapter'; -import { Hex, hexToNumber } from 'viem'; -import { SimulationEngine } from './simulation-engine'; - -/** - * Service responsible for handling execution operations - */ -export class ExecuteService { - private transactionService: TransactionService; - private approvalService: ApprovalService; - - constructor(private adapter: ChainAbstractionAdapter) { - this.transactionService = new TransactionService(adapter); - this.approvalService = new ApprovalService(adapter); - } - - /** - * Enable or disable gas estimation for transactions - */ - public setGasEstimationEnabled(enabled: boolean): void { - this.transactionService.setGasEstimationEnabled(enabled); - } - - /** - * Execute a contract call with approval handling - */ - async execute(params: ExecuteParams): Promise { - try { - // Prepare execution (includes chain switching) - const preparation = await this.transactionService.prepareExecution(params); - - // Handle approval if needed (after chain switching) - let approvalTxHash: string | undefined; - if (params.tokenApproval) { - const approvalResult = await this.approvalService.ensureContractApproval( - params.tokenApproval, - params.contractAddress, - params.toChainId, - false, - params.approvalBufferBps, - ); - - if (approvalResult.error) { - throw new Error(`Approval failed: ${approvalResult.error}`); - } - - approvalTxHash = approvalResult.transactionHash; - } - - // Send transaction - const transactionHash = await this.transactionService.sendTransaction( - preparation.provider, - preparation.fromAddress, - params.contractAddress, - preparation.encodedData, - preparation.value || params.value || '0x0', - { - enableTransactionPolling: params.enableTransactionPolling, - transactionTimeout: params.transactionTimeout, - waitForReceipt: params.waitForReceipt, - receiptTimeout: params.receiptTimeout, - requiredConfirmations: params.requiredConfirmations, - }, - ); - - // Handle transaction confirmation - const receiptInfo = await this.transactionService.handleTransactionConfirmation( - preparation.provider, - transactionHash, - { - waitForReceipt: params.waitForReceipt, - receiptTimeout: params.receiptTimeout, - requiredConfirmations: params.requiredConfirmations, - }, - params.toChainId, - ); - - // Build result - const result = this.transactionService.buildExecuteResult( - transactionHash, - params.toChainId, - receiptInfo, - ); - - // If approval happened, attach approval tx hash if available - if (params.tokenApproval && approvalTxHash) { - // Augment the typed result by casting to the extended type locally before returning - const extendedResult: ExecuteResult = { - ...result, - approvalTransactionHash: approvalTxHash, - }; - return extendedResult; - } - - return result; - } catch (error) { - throw error; - } - } - - /** - * Simulate contract execution - */ - async simulateExecute(params: ExecuteParams): Promise { - try { - // Get simulation client - const simulationClient = getSimulationClient(); - - if (!simulationClient) { - return { - contractAddress: params.contractAddress, - functionName: params.functionName, - gasUsed: '0', - success: false, - error: 'Simulation client not configured', - }; - } - - // Get user address for callback - const fromAddress = await this.adapter.nexusSDK.getEVMClient().getAddresses(); - - if (!fromAddress || fromAddress.length === 0) { - throw new Error('No accounts available'); - } - - // Prepare execution to get encoded data and value (calls buildFunctionParams internally) - const preparation = await this.transactionService.prepareExecution(params); - - // Create simulation parameters - const simulationParams = { - from: preparation.fromAddress, - to: params.contractAddress, - data: preparation.encodedData, - value: preparation.value || params.value || '0x0', - chainId: params.toChainId.toString(), - }; - - // Run simulation - const simulationResult = await simulationClient.simulate(simulationParams); - - if (!simulationResult.success) { - return { - contractAddress: params.contractAddress, - functionName: params.functionName, - gasUsed: '0', - success: false, - error: simulationResult.errorMessage || 'Simulation failed', - } as ExecuteSimulation; - } - - const gasUsedDecimal = hexToNumber(simulationResult.gasUsed as Hex); - let gasCostEth: string | undefined; - try { - const gasPriceHex = (await this.adapter.nexusSDK.request({ - method: 'eth_gasPrice', - })) as string; - const gasPriceWei = parseInt(gasPriceHex, 16); - const costEthNum = (gasUsedDecimal * gasPriceWei) / 1e18; - gasCostEth = costEthNum.toFixed(8); - } catch (gpErr) { - logger.warn('Failed to fetch gas price during simulation cost calc:', gpErr); - } - - return { - gasUsed: gasUsedDecimal.toString(), - success: true, - ...(gasCostEth ? { gasCostEth } : {}), - } as ExecuteSimulation; - } catch (error) { - return { - contractAddress: params.contractAddress, - functionName: params.functionName, - gasUsed: '0', - success: false, - error: extractErrorMessage(error, 'execution simulation'), - }; - } - } - - /** - * Enhanced simulation with automatic state setup - */ - async simulateExecuteEnhanced(params: ExecuteParams): Promise { - try { - // Check if we should use enhanced simulation - logger.debug('DEBUG ExecuteService - Full params received:', { - functionName: params.functionName, - contractAddress: params.contractAddress, - tokenApproval: params.tokenApproval, - buildFunctionParams: typeof params.buildFunctionParams, - toChainId: params.toChainId, - }); - - const shouldUseEnhancedSimulation = this.shouldUseEnhancedSimulation(params); - logger.debug( - 'DEBUG ExecuteService - Final enhanced simulation decision:', - shouldUseEnhancedSimulation, - ); - - if (shouldUseEnhancedSimulation) { - return await this.runEnhancedSimulation(params); - } - - return await this.simulateExecute(params); - } catch (error) { - return { - contractAddress: params.contractAddress, - functionName: params.functionName, - gasUsed: '0', - success: false, - error: extractErrorMessage(error, 'enhanced simulation'), - }; - } - } - - /** - * Determine if enhanced simulation should be used - */ - private shouldUseEnhancedSimulation(params: ExecuteParams): boolean { - // Use enhanced simulation if: - // 1. Token approval is required (indicates ERC20 interaction) - // 2. Function is likely to fail without proper balance setup - const shouldUse = - params.tokenApproval !== undefined && - params.tokenApproval.token !== 'ETH' && - this.isComplexContractCall(params); - logger.debug('DEBUG shouldUseEnhancedSimulation - Decision:', { - hasTokenApproval: !!params.tokenApproval, - isComplex: this.isComplexContractCall(params), - functionName: params.functionName, - finalDecision: shouldUse, - }); - return ( - params.tokenApproval !== undefined && - params.tokenApproval.token !== 'ETH' && - this.isComplexContractCall(params) - ); - } - - /** - * Check if this is a complex contract call that benefits from enhanced simulation - */ - private isComplexContractCall(params: ExecuteParams): boolean { - const complexFunctions = [ - 'deposit', - 'withdraw', - 'swap', - 'trade', - 'stake', - 'unstake', - 'mint', - 'burn', - 'transfer', - 'transferFrom', - 'approve', - 'supply', - 'borrow', - 'repay', - 'redeem', - 'lend', - ]; - - return complexFunctions.some((func) => - params.functionName.toLowerCase().includes(func.toLowerCase()), - ); - } - - /** - * Run enhanced simulation with automatic state setup - */ - private async runEnhancedSimulation(params: ExecuteParams): Promise { - try { - // Check if evmProvider is available - if (!this.adapter.nexusSDK.getEVMProviderWithCA()) { - throw new Error('EVM provider not available for enhanced simulation'); - } - - const simulationEngine = new SimulationEngine(this.adapter); - - // Get user address - const preparation = await this.transactionService.prepareExecution(params); - - // Convert tokenApproval amount to proper format if needed - const tokenAmount = params.tokenApproval?.amount || '0'; - - logger.info('DEBUG ExecuteService - Running enhanced simulation:', { - user: preparation.fromAddress, - token: params.tokenApproval?.token, - amount: tokenAmount, - function: params.functionName, - }); - - // Run enhanced simulation (tokenApproval is guaranteed to exist here due to shouldUseEnhancedSimulation check) - if (!params.tokenApproval) { - throw new Error('Enhanced simulation requires token approval information'); - } - - const enhancedResult = await simulationEngine.simulateWithStateSetup({ - user: preparation.fromAddress, - tokenRequired: params.tokenApproval.token, - amountRequired: tokenAmount, - contractCall: params, - }); - - // Convert enhanced result to ExecuteSimulation format - if (!enhancedResult.success) { - return { - contractAddress: params.contractAddress, - functionName: params.functionName, - gasUsed: '0', - success: false, - error: enhancedResult.error || 'Enhanced simulation failed', - }; - } - - // enhancedResult.totalGasUsed is already an ETH-denominated string (SimulationEngine converts) - return { - contractAddress: params.contractAddress, - functionName: params.functionName, - gasUsed: enhancedResult.totalGasUsed, - success: true, - gasCostEth: enhancedResult.totalGasUsed, - } as ExecuteSimulation; - } catch (error) { - logger.error('Enhanced simulation failed, falling back to standard:', error as Error); - - // Fallback to standard simulation - return await this.simulateExecute(params); - } - } -} diff --git a/packages/core/adapters/services/simulation-engine.ts b/packages/core/adapters/services/simulation-engine.ts deleted file mode 100644 index 65fc2fbf..00000000 --- a/packages/core/adapters/services/simulation-engine.ts +++ /dev/null @@ -1,684 +0,0 @@ -import type { - EnhancedSimulationResult, - EnhancedSimulationStep, - StateOverride, -} from '../../integrations/types'; -import { encodePacked, keccak256, formatUnits, Hex, erc20Abi } from 'viem'; -import { - type SUPPORTED_TOKENS, - type ExecuteParams, - type SUPPORTED_CHAINS_IDS, - extractErrorMessage, - getTokenContractAddress, - logger, - encodeContractCall, - TOKEN_METADATA, - CHAIN_METADATA, -} from '@nexus/commons'; - -import { getSimulationClient } from '../../integrations/tenderly'; -import { ChainAbstractionAdapter } from 'adapters/chain-abstraction-adapter'; - -/** - * Balance check result interface - */ -export interface BalanceCheckResult { - balance: string; - sufficient: boolean; - shortfall: string; - tokenAddress: string; -} - -/** - * Multi-step simulation engine with state override capabilities - */ -export class SimulationEngine { - private adapter: ChainAbstractionAdapter; - - constructor(adapter: ChainAbstractionAdapter) { - this.adapter = adapter; - } - - private ensureInitialized() { - if (!this.adapter.nexusSDK.isInitialized()) { - throw new Error('Adapter not initialized'); - } - } - - /** - * Main entry point for enhanced simulation with automatic state setup - */ - async simulateWithStateSetup(params: { - user: string; - tokenRequired: SUPPORTED_TOKENS; - amountRequired: string; - contractCall: ExecuteParams; - }): Promise { - this.ensureInitialized(); - - try { - const { user, tokenRequired, amountRequired, contractCall } = params; - const chainId = contractCall.toChainId; - - logger.info('DEBUG SimulationEngine - Starting enhanced simulation with full context:', { - user, - tokenRequired, - amountRequired, - chainId, - contract: contractCall.contractAddress, - function: contractCall.functionName, - contractCallParams: { - tokenApproval: contractCall.tokenApproval, - buildFunctionParams: typeof contractCall.buildFunctionParams, - }, - }); - - // Step 1: Check user's current token balance - const balanceCheck = await this.checkUserBalance( - user, - tokenRequired, - chainId, - amountRequired, - ); - logger.info('DEBUG SimulationEngine - Balance check result:', balanceCheck); - - // Step 2: Generate simulation steps - const steps = await this.generateSimulationSteps({ - user, - tokenRequired, - amountRequired, - contractCall, - balanceCheck, - }); - - logger.info('DEBUG SimulationEngine - Generated steps:', steps.length); - - // Step 3: Execute multi-step simulation - const result = await this.executeBatchSimulation(steps, chainId); - - logger.info('DEBUG SimulationEngine - Simulation complete:', { - success: result.success, - totalGas: result.totalGasUsed, - stepsExecuted: result.steps.length, - }); - - return result; - } catch (error) { - logger.error('DEBUG SimulationEngine - Simulation failed:', error as Error); - return this.createFailedResult( - `Enhanced simulation failed: ${extractErrorMessage(error, 'simulation')}`, - ); - } - } - - /** - * Check user's token balance on specific chain - */ - async checkUserBalance( - user: string, - token: SUPPORTED_TOKENS, - chainId: number, - requiredAmount?: string, - ): Promise { - try { - const tokenAddress = getTokenContractAddress(token, chainId as SUPPORTED_CHAINS_IDS); - if (!tokenAddress) { - throw new Error(`Token ${token} not supported on chain ${CHAIN_METADATA[chainId]?.name}`); - } - - // For native ETH, use eth_getBalance - if (token === 'ETH') { - const balance = await this.adapter.nexusSDK.getEVMClient().getBalance({ - address: user as Hex, - blockTag: 'latest', - }); - - const balanceBigInt = BigInt(balance); - const requiredBigInt = requiredAmount ? BigInt(requiredAmount) : BigInt(0); - const sufficient = balanceBigInt >= requiredBigInt; - const shortfall = sufficient ? '0' : (requiredBigInt - balanceBigInt).toString(); - - return { - balance: balance.toString(), - sufficient, - shortfall, - tokenAddress, - }; - } - - const balance = await this.adapter.nexusSDK.getEVMClient().readContract({ - abi: erc20Abi, - functionName: 'balanceOf', - args: [user as Hex], - address: tokenAddress as Hex, - }); - - if (requiredAmount) { - const requiredBigInt = BigInt(requiredAmount); - const sufficient = balance >= requiredBigInt; - const shortfall = sufficient ? '0' : (requiredBigInt - balance).toString(); - - return { - balance: balance.toString(), - sufficient, - shortfall, - tokenAddress, - }; - } - - return { - balance: balance.toString(), - sufficient: false, // Cannot determine without required amount - shortfall: '0', - tokenAddress, - }; - } catch (error) { - logger.warn(`Failed to check balance for ${token} on chain ${chainId}:`, error); - return { - balance: '0', - sufficient: false, - shortfall: requiredAmount || '0', - tokenAddress: getTokenContractAddress(token, chainId as SUPPORTED_CHAINS_IDS) || '', - }; - } - } - - /** - * Get the storage slot for token balances mapping - Production Ready Static Mapping - * Based on actual contract analysis for all supported tokens and chains - */ - private getBalanceStorageSlot(token: SUPPORTED_TOKENS, chainId: number): number { - const storageSlotMapping: Record> = { - // Ethereum Mainnet (1) - 1: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Base Mainnet (8453) - 8453: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Arbitrum One (42161) - 42161: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Optimism (10) - 10: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Polygon (137) - 137: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Avalanche C-Chain (43114) - 43114: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Scroll (534352) - 534352: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Base Sepolia Testnet (84532) - 84532: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Arbitrum Sepolia Testnet (421614) - 421614: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Optimism Sepolia Testnet (11155420) - 11155420: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - - // Polygon Amoy Testnet (80002) - 80002: { - ETH: 0, - USDC: 9, - USDT: 2, - }, - }; - - const chainMapping = storageSlotMapping[chainId]; - if (!chainMapping) { - logger.warn(`Unsupported chain ${chainId}, falling back to defaults`); - // Fallback defaults based on most common patterns - return token === 'USDC' ? 9 : token === 'USDT' ? 2 : 0; - } - - const slot = chainMapping[token]; - if (slot === undefined) { - logger.warn( - `Token ${token} not supported on chain ${CHAIN_METADATA[chainId]?.name}, falling back to defaults`, - ); - return token === 'USDC' ? 9 : token === 'USDT' ? 2 : 0; - } - - logger.info(`Using storage slot ${slot} for ${token} on chain ${chainId}`); - return slot; - } - - /** - * Generate state overrides to fund user with required tokens - */ - async generateStateOverrides( - user: string, - token: SUPPORTED_TOKENS, - requiredAmount: string, - chainId: number, - ): Promise { - try { - const tokenAddress = getTokenContractAddress(token, chainId as SUPPORTED_CHAINS_IDS); - if (!tokenAddress) { - throw new Error(`Token ${token} not supported on chain ${CHAIN_METADATA[chainId]?.name}`); - } - - // For native ETH - if (token === 'ETH') { - return { - [user]: { - balance: `0x${BigInt(requiredAmount).toString(16)}`, - }, - }; - } - - // For ERC20 tokens - override the balance mapping using verified storage slots - const balanceSlot = this.getBalanceStorageSlot(token, chainId); - - // Calculate storage slot for user's balance: keccak256(user_address . balances_slot) - const userBalanceSlot = keccak256( - encodePacked(['address', 'uint256'], [user as `0x${string}`, BigInt(balanceSlot)]), - ); - - // Convert amount to hex with proper padding - const amountHex = `0x${BigInt(requiredAmount).toString(16).padStart(64, '0')}`; - - logger.info( - `Generating state override for ${token} on chain ${chainId}: slot=${balanceSlot}, storageKey=${userBalanceSlot}`, - ); - - return { - [tokenAddress]: { - storage: { - [userBalanceSlot]: amountHex, - }, - }, - }; - } catch (error) { - logger.error('Error generating state overrides:', error as Error); - throw error; - } - } - - /** - * Generate the sequence of simulation steps needed - */ - private async generateSimulationSteps(params: { - user: string; - tokenRequired: SUPPORTED_TOKENS; - amountRequired: string; - contractCall: ExecuteParams; - balanceCheck: BalanceCheckResult; - }): Promise { - const { user, tokenRequired, amountRequired, contractCall, balanceCheck } = params; - const steps: EnhancedSimulationStep[] = []; - - // Check if user has sufficient balance - const requiredAmountBigInt = BigInt(amountRequired); - const currentBalanceBigInt = BigInt(balanceCheck.balance); - const needsFunding = currentBalanceBigInt < requiredAmountBigInt; - - logger.info('DEBUG generateSimulationSteps - Balance analysis:', { - required: requiredAmountBigInt.toString(), - current: currentBalanceBigInt.toString(), - needsFunding, - tokenRequired, - user, - contractCall: { - functionName: contractCall.functionName, - contractAddress: contractCall.contractAddress, - tokenApproval: contractCall.tokenApproval, - buildFunctionParamsType: typeof contractCall.buildFunctionParams, - }, - }); - - // Step 1: Funding step (if needed) - if (needsFunding) { - const stateOverrides = await this.generateStateOverrides( - user, - tokenRequired, - amountRequired, - contractCall.toChainId, - ); - - steps.push({ - type: 'funding', - required: true, - description: `Fund user with ${amountRequired} ${tokenRequired}`, - stepId: 'funding-step', - stateOverride: stateOverrides, - params: { - chainId: contractCall.toChainId.toString(), - from: user, - to: user, - value: '0x0', - }, - }); - } - - // First, convert amountRequired from micro-units to user-friendly format for the callback - // The callback expects amount in user-friendly format (e.g., "0.01" for 0.01 USDC) - // but amountRequired comes in micro-units (e.g., "10000" for 0.01 USDC) - logger.info('Token metadata:', { meta: TOKEN_METADATA, tokenRequired }); - const decimals = TOKEN_METADATA[tokenRequired]?.decimals ?? 6; - const userFriendlyAmount = formatUnits(BigInt(amountRequired), decimals); - - logger.info('DEBUG SimulationEngine - Amount conversion:', { - microUnits: amountRequired, - decimals, - userFriendly: userFriendlyAmount, - }); - - // Call the buildFunctionParams with user-friendly amount - logger.info('DEBUG SimulationEngine - Calling buildFunctionParams with:', { - tokenRequired, - userFriendlyAmount, - chainId: contractCall.toChainId, - user, - }); - - const { functionParams, value } = contractCall.buildFunctionParams( - tokenRequired, - userFriendlyAmount, - contractCall.toChainId, - user as `0x${string}`, - ); - - logger.info('DEBUG SimulationEngine - buildFunctionParams result:', { - functionParams, - value, - functionParamsLength: functionParams?.length, - functionParamsTypes: functionParams?.map((p) => typeof p), - }); - - // Step 2: Approval step (if needed for ERC20) - if (tokenRequired !== 'ETH' && contractCall.tokenApproval) { - const actualAmountToApprove = amountRequired; - - logger.info('DEBUG SimulationEngine - Approval step preparation:', { - tokenRequired, - amountRequired, - actualAmountToApprove, - contractToApprove: contractCall.contractAddress, - tokenAddress: balanceCheck.tokenAddress, - }); - - const approvalCallData = await this.buildApprovalCallData( - contractCall.contractAddress, - actualAmountToApprove, - ); - - steps.push({ - type: 'approval', - required: true, - description: `Approve ${contractCall.contractAddress} to spend ${tokenRequired}`, - stepId: 'approval-step', - dependsOn: needsFunding ? ['funding-step'] : undefined, - params: { - chainId: contractCall.toChainId.toString(), - from: user, - to: balanceCheck.tokenAddress, - data: approvalCallData, - value: '0x0', - }, - }); - } - - // Step 3: Execute step - - // Encode the function call with the built parameters - const encodingResult = encodeContractCall({ - contractAbi: contractCall.contractAbi, - functionName: contractCall.functionName, - functionParams, - }); - - if (!encodingResult.success) { - throw new Error(`Failed to encode contract call: ${encodingResult.error}`); - } - - logger.info('DEBUG SimulationEngine - Execute step preparation:', { - encodedData: encodingResult.data, - contractCallValue: contractCall.value, - callbackValue: value, - finalValue: value || contractCall.value || '0x0', - dependsOn: - tokenRequired !== 'ETH' ? ['approval-step'] : needsFunding ? ['funding-step'] : undefined, - }); - - // Normalize ETH value to 0x-hex if provided - const normalizedValue = (() => { - const v = value || contractCall.value; - if (!v) return '0x0'; - if (typeof v === 'string' && v.startsWith('0x')) return v; - try { - return `0x${BigInt(v).toString(16)}`; - } catch { - return '0x0'; - } - })(); - - steps.push({ - type: 'execute', - required: true, - description: `Execute ${contractCall.functionName} on ${contractCall.contractAddress}`, - stepId: 'execute-step', - dependsOn: - tokenRequired !== 'ETH' ? ['approval-step'] : needsFunding ? ['funding-step'] : undefined, - params: { - chainId: contractCall.toChainId.toString(), - from: user, - to: contractCall.contractAddress, - data: encodingResult.data!, - value: normalizedValue, - }, - }); - - logger.info('DEBUG SimulationEngine - Final steps generated:', { - totalSteps: steps.length, - stepTypes: steps.map((s) => s.type), - stepIds: steps.map((s) => s.stepId), - }); - - return steps; - } - - /** - * Build approval call data for ERC20 token - */ - private async buildApprovalCallData(spender: string, amount: string): Promise { - // ERC20 approve function selector: approve(address,uint256) - const approveSelector = '0x095ea7b3'; - const paddedSpender = spender.slice(2).padStart(64, '0'); - const paddedAmount = BigInt(amount).toString(16).padStart(64, '0'); - - return `${approveSelector}${paddedSpender}${paddedAmount}`; - } - - /** - * Execute batch simulation using bundle endpoint - */ - private async executeBatchSimulation( - steps: EnhancedSimulationStep[], - chainId: number, - ): Promise { - const simulationClient = getSimulationClient(); - if (!simulationClient) { - return this.createFailedResult('Simulation client not configured'); - } - - logger.info( - `DEBUG executeBatchSimulation - Starting bundle simulation with ${steps.length} steps`, - ); - - // Build cumulative state overrides - let cumulativeStateOverrides: StateOverride = {}; - const bundleSimulations: Array<{ - stepId: string; - type: string; - from: string; - to: string; - data: string; - value: string; - stateOverride: StateOverride; - }> = []; - - for (const step of steps) { - // Merge cumulative state overrides with step-specific overrides - cumulativeStateOverrides = this.mergeStateOverrides( - cumulativeStateOverrides, - step.stateOverride || {}, - ); - - // Add to bundle - bundleSimulations.push({ - stepId: step.stepId || '', - type: step.type, - from: step.params.from || '', - to: step.params.to || '', - data: step.params.data || '0x', - value: step.params.value || '0x0', - stateOverride: { ...cumulativeStateOverrides }, // Each step gets cumulative state - }); - - logger.info(`DEBUG executeBatchSimulation - Prepared step: ${step.stepId} (${step.type})`); - } - - try { - // Execute bundle simulation - const bundleRequest = { - chainId: chainId.toString(), - simulations: bundleSimulations, - }; - - logger.info('DEBUG executeBatchSimulation - Sending bundle request'); - const bundleResult = await simulationClient.simulateBundle(bundleRequest); - - if (!bundleResult.success) { - return { - totalGasUsed: '0', - success: false, - error: 'Bundle simulation failed', - steps: bundleResult.results.map((result) => ({ - stepId: result.stepId, - type: bundleSimulations.find((sim) => sim.stepId === result.stepId)?.type || '', - gasUsed: result.gasUsed, - success: result.success, - error: result.error, - })), - stateOverrides: cumulativeStateOverrides, - }; - } - - // Process successful bundle result - const executedSteps = bundleResult.results.map((result) => { - const stepType = bundleSimulations.find((sim) => sim.stepId === result.stepId)?.type || ''; - - logger.info(`DEBUG executeBatchSimulation - Step ${result.stepId} completed:`, { - gasUsed: result.gasUsed, - }); - - return { - stepId: result.stepId, - type: stepType, - gasUsed: result.gasUsed, - success: result.success, - error: result.error, - stateChanges: bundleSimulations.find((sim) => sim.stepId === result.stepId) - ?.stateOverride, - }; - }); - - return { - totalGasUsed: bundleResult.totalGasUsed, - success: true, - steps: executedSteps, - stateOverrides: cumulativeStateOverrides, - simulationMetadata: { - blockNumber: 'latest', - timestamp: new Date().toISOString(), - chainId: chainId.toString(), - }, - }; - } catch (error) { - logger.error('Bundle simulation error:', error as Error); - return this.createFailedResult( - `Bundle simulation failed: ${extractErrorMessage(error, 'bundle simulation')}`, - ); - } - } - - /** - * Merge two state override objects - */ - private mergeStateOverrides(base: StateOverride, additional: StateOverride): StateOverride { - const merged: StateOverride = { ...base }; - - for (const [address, overrides] of Object.entries(additional)) { - if (merged[address]) { - merged[address] = { - ...merged[address], - ...overrides, - storage: { - ...merged[address].storage, - ...overrides.storage, - }, - }; - } else { - merged[address] = overrides; - } - } - - return merged; - } - - /** - * Create a failed simulation result - */ - private createFailedResult(error: string): EnhancedSimulationResult { - return { - totalGasUsed: '0', - success: false, - error, - steps: [], - }; - } -} diff --git a/packages/core/adapters/services/transaction-service.ts b/packages/core/adapters/services/transaction-service.ts deleted file mode 100644 index c15b65f6..00000000 --- a/packages/core/adapters/services/transaction-service.ts +++ /dev/null @@ -1,534 +0,0 @@ -import { ChainAbstractionAdapter } from 'adapters/chain-abstraction-adapter'; - -import { - type EthereumProvider, - type ExecuteParams, - type TransactionOptions, - type TransactionResult, - type ExecutePreparation, - type ChainSwitchResult, - validateContractParams, - encodeContractCall, - getBlockExplorerUrl, - getTransactionHashWithFallback, - waitForTransactionReceipt, - extractErrorMessage, - logger, -} from '@nexus/commons'; - -// Interface for gas estimation result -interface GasEstimationResult { - success: boolean; - gasEstimate?: string; - gasEstimateDecimal?: number; - gasPriceGwei?: string; - estimatedCostEth?: string; - error?: string; - revertReason?: string; -} - -/** - * Service responsible for transaction handling and preparation - */ -export class TransactionService { - constructor(private adapter: ChainAbstractionAdapter) {} - // Flag to enable/disable gas estimation (can be set via constructor or method) - private enableGasEstimation: boolean = true; - - /** - * Enable or disable gas estimation before transaction execution - */ - setGasEstimationEnabled(enabled: boolean): void { - this.enableGasEstimation = enabled; - } - - /** - * Estimate gas for a transaction before execution - */ - async estimateTransactionGas( - provider: EthereumProvider, - transactionParams: { - from: string; - to: string; - data: string; - value: string; - }, - ): Promise { - logger.info('TransactionService - Starting gas estimation...'); - logger.info('TransactionService - Transaction params:', { - from: transactionParams.from, - to: transactionParams.to, - data: transactionParams.data.slice(0, 50) + '...', // Truncate for logging - value: transactionParams.value, - }); - - try { - // Step 1: Estimate gas - const gasEstimate = (await provider.request({ - method: 'eth_estimateGas', - params: [transactionParams], - })) as string; - - const gasEstimateDecimal = parseInt(gasEstimate, 16); - - logger.info('TransactionService - Gas estimation successful:', { - gasEstimateHex: gasEstimate, - gasEstimateDecimal: gasEstimateDecimal, - gasEstimateFormatted: gasEstimateDecimal.toLocaleString(), - }); - - // Step 2: Get current gas price for cost calculation - let gasPriceGwei: string | undefined; - let estimatedCostEth: string | undefined; - - try { - const gasPrice = (await provider.request({ - method: 'eth_gasPrice', - })) as string; - - const gasPriceDecimal = parseInt(gasPrice, 16); - const estimatedCostWei = gasEstimateDecimal * gasPriceDecimal; - const estimatedCostEthNum = estimatedCostWei / 1e18; - - gasPriceGwei = (gasPriceDecimal / 1e9).toFixed(4) + ' gwei'; - estimatedCostEth = estimatedCostEthNum.toFixed(8) + ' ETH'; - - logger.info('TransactionService - Gas cost estimation:', { - gasPriceHex: gasPrice, - gasPriceGwei: gasPriceGwei, - estimatedCostWei: estimatedCostWei.toString(), - estimatedCostEth: estimatedCostEth, - }); - } catch (gasPriceError) { - logger.warn('TransactionService - Failed to get gas price:', gasPriceError); - } - - return { - success: true, - gasEstimate, - gasEstimateDecimal, - gasPriceGwei, - estimatedCostEth, - }; - } catch (gasEstimateError) { - logger.error('TransactionService - Gas estimation failed:', gasEstimateError as Error); - - // Extract revert reason if available - let revertReason: string | undefined; - let errorMessage = 'Gas estimation failed'; - - if (gasEstimateError && typeof gasEstimateError === 'object') { - if ('data' in gasEstimateError && gasEstimateError.data) { - logger.error( - 'TransactionService - Gas estimation revert data:', - gasEstimateError.data as string, - ); - revertReason = JSON.stringify(gasEstimateError.data); - } - if ('message' in gasEstimateError && gasEstimateError.message) { - errorMessage = gasEstimateError.message as string; - logger.error('TransactionService - Gas estimation error message:', errorMessage); - - // Extract common revert patterns - if (errorMessage.includes('execution reverted')) { - const revertMatch = errorMessage.match(/execution reverted:?\s*(.+)/i); - if (revertMatch && revertMatch[1]) { - revertReason = revertMatch[1].trim(); - } else { - revertReason = 'Transaction would revert (no reason provided)'; - } - } else if (errorMessage.includes('insufficient funds')) { - revertReason = 'Insufficient funds for gas * price + value'; - } else if (errorMessage.includes('out of gas')) { - revertReason = 'Transaction would run out of gas'; - } - } - } - - return { - success: false, - error: errorMessage, - revertReason, - }; - } - } - - /** - * Ensure we're on the correct chain, switch if needed - */ - async ensureCorrectChain(targetChainId: number): Promise { - try { - const currentChainId = await this.adapter.nexusSDK.getEVMClient().getChainId(); - - if (currentChainId !== targetChainId) { - try { - await this.adapter.nexusSDK.getEVMClient().switchChain({ id: targetChainId }); - return { success: true }; - } catch (switchError) { - if ( - switchError && - typeof switchError === 'object' && - 'code' in switchError && - switchError.code === 4902 - ) { - throw new Error( - `Chain ${targetChainId} is not configured in wallet. Please add it manually.`, - ); - } - throw switchError; - } - } - return { success: true }; - } catch (error) { - return { - success: false, - error: extractErrorMessage(error, 'chain switching'), - }; - } - } - - /** - * Prepare execution by validating parameters and encoding function call - */ - async prepareExecution(params: ExecuteParams): Promise { - // Get the from address first (needed for callback) - const fromAddress = await this.adapter.nexusSDK.getEVMClient().getAddresses(); - - if (!fromAddress || fromAddress.length === 0) { - throw new Error('No accounts available'); - } - - // Ensure we're on the correct chain - const chainResult = await this.ensureCorrectChain(params.toChainId); - if (!chainResult.success) { - throw new Error(`Failed to switch to chain ${params.toChainId}: ${chainResult.error}`); - } - - // Call buildFunctionParams callback to get the actual function parameters - // For ETH transactions, provide ETH as token and 0 as amount if tokenApproval is undefined - const token = params.tokenApproval?.token || 'ETH'; - const amount = params.tokenApproval?.amount || '0'; - - const { functionParams, value: callbackValue } = params.buildFunctionParams( - token, - amount, - params.toChainId, - fromAddress[0] as `0x${string}`, - ); - - // Validate contract parameters with built function params - const validation = validateContractParams({ - contractAddress: params.contractAddress, - contractAbi: params.contractAbi, - functionName: params.functionName, - functionParams, - chainId: params.toChainId, - }); - - if (!validation.isValid) { - throw new Error(`Invalid contract parameters: ${validation.error}`); - } - - // Encode the function call - const encodingResult = encodeContractCall({ - contractAbi: params.contractAbi, - functionName: params.functionName, - functionParams, - }); - - if (!encodingResult.success) { - throw new Error(`Failed to encode contract call: ${encodingResult.error}`); - } - - return { - provider: this.adapter.nexusSDK.getEVMProviderWithCA(), - fromAddress: fromAddress[0], - encodedData: encodingResult.data!, - value: callbackValue, - }; - } - - /** - * Send transaction with enhanced error handling and polling support - */ - async sendTransaction( - provider: EthereumProvider, - fromAddress: string, - contractAddress: string, - encodedData: `0x${string}`, - value: string, - options: TransactionOptions, - ): Promise<`0x${string}`> { - // Normalize value to 0x-hex string in wei - const normalizedValue = (() => { - if (!value) return '0x0'; - if (typeof value === 'string' && value.startsWith('0x')) return value; - try { - return `0x${BigInt(value).toString(16)}`; - } catch { - // If parsing fails, default to 0x0 to avoid provider rejection; gas estimation will catch issues - return '0x0'; - } - })(); - - const transactionParams = { - from: fromAddress, - to: contractAddress, - data: encodedData, - value: normalizedValue || '0x0', - }; - - try { - // Perform gas estimation if enabled - if (this.enableGasEstimation) { - logger.info('TransactionService - Performing pre-execution gas estimation...'); - const gasEstimation = await this.estimateTransactionGas(provider, transactionParams); - - if (!gasEstimation.success) { - logger.error( - 'TransactionService - Pre-execution gas estimation failed:', - gasEstimation.error, - ); - - if (gasEstimation.revertReason) { - logger.warn( - `TransactionService - Transaction will likely fail: ${gasEstimation.revertReason}`, - ); - throw new Error(`Transaction simulation failed: ${gasEstimation.revertReason}`); - } - } else { - logger.info('TransactionService - Gas estimation completed successfully:', { - gasEstimate: gasEstimation.gasEstimate, - estimatedCost: gasEstimation.estimatedCostEth, - gasPrice: gasEstimation.gasPriceGwei, - }); - } - } else { - logger.info('TransactionService - Gas estimation disabled, proceeding with transaction'); - } - - logger.info('TransactionService - Sending transaction...'); - const response = await provider.request({ - method: 'eth_sendTransaction', - params: [transactionParams], - }); - - // Get transaction hash with fallback polling - const hashResult = await getTransactionHashWithFallback(provider, response, { - enablePolling: options.enableTransactionPolling, - timeout: options.transactionTimeout, - fromAddress, - }); - - if (!hashResult.success || !hashResult.hash) { - throw new Error( - hashResult.error || 'Failed to retrieve transaction hash from provider response', - ); - } - - logger.info('TransactionService - Transaction sent successfully:', { - transactionHash: hashResult.hash, - }); - - return hashResult.hash; - } catch (error) { - // Enhanced error handling for common transaction failures - if (error && typeof error === 'object' && 'code' in error) { - if (error.code === 4001) { - throw new Error('Transaction rejected by user'); - } else if (error.code === -32000) { - throw new Error('Insufficient funds for transaction'); - } else if (error.code === -32603) { - throw new Error('Internal JSON-RPC error during transaction'); - } - } - - throw new Error(`Transaction failed: ${extractErrorMessage(error, 'transaction')}`); - } - } - - /** - * Handle transaction confirmation with receipt and confirmations - */ - async handleTransactionConfirmation( - provider: EthereumProvider, - transactionHash: `0x${string}`, - options: TransactionOptions, - chainId: number, - ): Promise { - if (!options.waitForReceipt) { - return {}; - } - - try { - const receiptResult = await waitForTransactionReceipt( - provider, - transactionHash, - { - timeout: options.receiptTimeout, - requiredConfirmations: options.requiredConfirmations, - }, - chainId, - ); - - if (!receiptResult.success) { - logger.warn(`Failed to get transaction receipt: ${receiptResult.error}`); - return {}; - } - - return { - receipt: receiptResult.receipt, - confirmations: receiptResult.confirmations, - gasUsed: receiptResult.receipt?.gasUsed?.toString(), - effectiveGasPrice: receiptResult.receipt?.effectiveGasPrice?.toString(), - }; - } catch (error) { - logger.warn(`Receipt retrieval failed: ${extractErrorMessage(error, 'receipt retrieval')}`); - return {}; - } - } - - /** - * Build execute result with transaction information - */ - buildExecuteResult(transactionHash: string, chainId: number, receiptInfo: TransactionResult) { - return { - transactionHash, - explorerUrl: getBlockExplorerUrl(chainId, transactionHash), - chainId, - ...receiptInfo, - }; - } - - /** - * Direct native token transfer (ETH, MATIC, AVAX, etc.) - */ - async transferNativeToken( - provider: EthereumProvider, - fromAddress: string, - toAddress: string, - amount: string, // Amount in human-readable format (e.g., "0.1") - decimals: number = 18, - ): Promise<{ - success: boolean; - hash?: `0x${string}`; - error?: string; - }> { - const { parseUnits } = await import('viem'); - - const valueInWei = parseUnits(amount, decimals); - const transactionParams = { - from: fromAddress, - to: toAddress, - data: '0x', - value: `0x${valueInWei.toString(16)}`, - }; - - try { - // Perform gas estimation if enabled - if (this.enableGasEstimation) { - logger.info('TransactionService - Performing gas estimation for native token transfer...'); - const gasEstimation = await this.estimateTransactionGas(provider, transactionParams); - - if (!gasEstimation.success) { - logger.error( - 'TransactionService - Gas estimation failed for native token transfer:', - gasEstimation.error, - ); - throw new Error(`Native token transfer gas estimation failed: ${gasEstimation.error}`); - } - - logger.info('TransactionService - Native token transfer gas estimation successful:', { - gasEstimate: gasEstimation.gasEstimate, - estimatedCost: gasEstimation.estimatedCostEth, - }); - } - - logger.info('TransactionService - Sending native token transfer...'); - const response = await provider.request({ - method: 'eth_sendTransaction', - params: [transactionParams], - }); - - const transactionHash = getTransactionHashWithFallback(provider, response); - logger.info('TransactionService - Native token transfer sent successfully:', transactionHash); - return transactionHash; - } catch (error) { - logger.error('TransactionService - Native token transfer failed:', error as Error); - throw new Error( - `Native token transfer failed: ${extractErrorMessage(error, 'native transfer')}`, - ); - } - } - - /** - * Direct ERC20 token transfer - */ - async transferERC20Token( - provider: EthereumProvider, - fromAddress: string, - tokenAddress: string, - toAddress: string, - amount: string, // Amount in human-readable format (e.g., "100") - decimals: number = 18, - ): Promise<{ - success: boolean; - hash?: `0x${string}`; - error?: string; - }> { - const { parseUnits } = await import('viem'); - - try { - const amountInWei = parseUnits(amount, decimals); - - // ERC20 transfer function selector: transfer(address,uint256) - const transferSelector = '0xa9059cbb'; - const paddedRecipient = toAddress.slice(2).padStart(64, '0'); - const paddedAmount = amountInWei.toString(16).padStart(64, '0'); - const transferData = `${transferSelector}${paddedRecipient}${paddedAmount}`; - - const transactionParams = { - from: fromAddress, - to: tokenAddress, - data: transferData, - value: '0x0', - }; - - // Perform gas estimation if enabled - if (this.enableGasEstimation) { - logger.info('TransactionService - Performing gas estimation for ERC20 transfer...'); - const gasEstimation = await this.estimateTransactionGas(provider, transactionParams); - - if (!gasEstimation.success) { - logger.error( - 'TransactionService - Gas estimation failed for ERC20 transfer:', - gasEstimation.error, - ); - - if (gasEstimation.revertReason) { - throw new Error(`ERC20 transfer will fail: ${gasEstimation.revertReason}`); - } - throw new Error(`ERC20 transfer gas estimation failed: ${gasEstimation.error}`); - } - - logger.info('TransactionService - ERC20 transfer gas estimation successful:', { - gasEstimate: gasEstimation.gasEstimate, - estimatedCost: gasEstimation.estimatedCostEth, - }); - } - - logger.info('TransactionService - Sending ERC20 transfer...'); - const response = await provider.request({ - method: 'eth_sendTransaction', - params: [transactionParams], - }); - - const transactionHash = getTransactionHashWithFallback(provider, response); - logger.info('TransactionService - ERC20 transfer sent successfully:', transactionHash); - return transactionHash; - } catch (error) { - logger.error('TransactionService - ERC20 transfer failed:', error as Error); - throw new Error(`ERC20 transfer failed: ${extractErrorMessage(error, 'ERC20 transfer')}`); - } - } -} diff --git a/packages/core/integrations/tenderly.ts b/packages/core/integrations/tenderly.ts deleted file mode 100644 index 0c0b90c7..00000000 --- a/packages/core/integrations/tenderly.ts +++ /dev/null @@ -1,405 +0,0 @@ -import { formatEther, hexToBigInt } from 'viem'; -import { - type ApiResponse, - type BackendConfig, - type ChainSupportResponse, - type GasEstimationRequest, - type GasEstimationResponse, - type HealthCheckResponse, - type ServiceStatusResponse, - type BundleSimulationRequest, - type BundleSimulationResponse, - type BackendBundleResponse, -} from './types'; -import { CHAIN_METADATA, logger } from '@nexus/commons'; - -/** - * Backend simulation result interface - */ -export interface BackendSimulationResult { - gasUsed: string; - gasPrice: string; - maxFeePerGas?: string; - maxPriorityFeePerGas?: string; - success: boolean; - errorMessage?: string; - estimatedCost: { - totalFee: string; - }; -} - -const BACKEND_URL = 'https://nexus-backend.avail.so'; - -/** - * Backend client for gas estimation using new API - */ - -export class BackendSimulationClient { - private readonly baseUrl: string; - - constructor(config: BackendConfig) { - this.baseUrl = config.baseUrl; - } - - /** - * Check if a specific chain is supported - */ - async isChainSupported(chainId: number): Promise { - try { - const response = await fetch(`${this.baseUrl}/api/gas-estimation/check-chain/${chainId}`); - if (!response.ok) return false; - - const result: ApiResponse = await response.json(); - return result.success && result.data?.supported === true; - } catch (error) { - logger.warn(`Error checking chain support for ${chainId}:`, error); - return false; - } - } - - /** - * Get all supported chains - */ - async getSupportedChains(): Promise | null> { - try { - const response = await fetch(`${this.baseUrl}/api/gas-estimation/supported-chains`); - if (!response.ok) return null; - - const result: ApiResponse> = await response.json(); - return result.success ? result.data || null : null; - } catch (error) { - logger.warn('Error fetching supported chains:', error); - return null; - } - } - - /** - * Get service status - */ - async getServiceStatus(): Promise { - try { - const response = await fetch(`${this.baseUrl}/api/gas-estimation/status`); - if (!response.ok) return null; - - const result: ApiResponse = await response.json(); - return result.success ? result.data || null : null; - } catch (error) { - logger.warn('Error fetching service status:', error); - return null; - } - } - - /** - * Health check - */ - async healthCheck(): Promise { - try { - const response = await fetch(`${this.baseUrl}/api/health`); - if (!response.ok) return null; - - const result: ApiResponse = await response.json(); - return result.success ? result.data || null : null; - } catch (error) { - logger.warn('Error performing health check:', error); - return null; - } - } - - /** - * Test connectivity and service health - */ - async testConnection(): Promise { - try { - const health = await this.healthCheck(); - return health?.status === 'ok'; - } catch (error) { - logger.warn('Connection test failed:', error); - return false; - } - } - - /** - * Get detailed service information - */ - async getServiceInfo(): Promise<{ - healthy: boolean; - configured: boolean; - supportedChains: number; - version?: string; - uptime?: number; - }> { - try { - const [health, status] = await Promise.all([this.healthCheck(), this.getServiceStatus()]); - - return { - healthy: health?.status === 'ok', - configured: status?.configured || false, - supportedChains: status?.supportedChainsCount || 0, - version: health?.version, - uptime: health?.uptime, - }; - } catch (error) { - logger.warn('Error getting service info:', error); - return { - healthy: false, - configured: false, - supportedChains: 0, - }; - } - } - - /** - * Simulate transaction using Tenderly's Gateway RPC with state overrides - * This provides more accurate simulation results than basic gas estimation - */ - async simulate(request: GasEstimationRequest): Promise { - try { - const response = await fetch(`${this.baseUrl}/api/gas-estimation/simulate`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(request), - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Simulation API error: ${response.status} - ${errorText}`); - } - - const result: ApiResponse = await response.json(); - - if (!result.success || !result.data) { - throw new Error(result.error || result.message || 'Simulation failed'); - } - - const gasData = result.data; - - return { - gasUsed: gasData.gasUsed, - gasPrice: gasData.gasPrice || '0x0', - maxFeePerGas: gasData.maxFeePerGas, - maxPriorityFeePerGas: gasData.maxPriorityFeePerGas, - success: true, - estimatedCost: { - totalFee: gasData?.gasUsed || '0', - }, - }; - } catch (error) { - logger.error('Simulation API error:', error as Error); - return { - gasUsed: '0x0', - gasPrice: '0x0', - success: false, - errorMessage: error instanceof Error ? error.message : 'Unknown error', - estimatedCost: { - totalFee: '0', - }, - }; - } - } - - /** - * Fetch current gas price via RPC - */ - private async getCurrentGasPrice(chainId: string): Promise { - try { - const rpcUrl = this.getRpcUrl(chainId); - - const response = await fetch(rpcUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - jsonrpc: '2.0', - method: 'eth_gasPrice', - params: [], - id: 1, - }), - }); - - if (!response.ok) { - throw new Error(`RPC request failed: ${response.status}`); - } - - const result = await response.json(); - - if (result.error) { - throw new Error(`RPC error: ${result.error.message}`); - } - - // Convert hex gas price to bigint - return hexToBigInt(result.result); - } catch (error) { - logger.warn('Failed to fetch current gas price, using fallback:', error); - // Fallback to 20 gwei if RPC call fails - return BigInt('20000000000'); // 20 gwei in wei - } - } - - /** - * Get RPC URL for a given chain ID using CHAIN_METADATA - */ - private getRpcUrl(chainId: string): string { - const chainIdNum = parseInt(chainId, 10); - const chainMetadata = CHAIN_METADATA[chainIdNum]; - - if (!chainMetadata || !chainMetadata.rpcUrls || chainMetadata.rpcUrls.length === 0) { - throw new Error(`No RPC URL available for chain ${chainId}`); - } - - // Use the first RPC URL from the metadata - return chainMetadata.rpcUrls[0]; - } - - async simulateBundle(request: BundleSimulationRequest): Promise { - try { - logger.info('DEBUG simulateBundle - request:', JSON.stringify(request, null, 2)); - - const response = await fetch(`${this.baseUrl}/api/gas-estimation/bundle`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(request), - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Bundle simulation API error: ${response.status} - ${errorText}`); - } - - const result: BackendBundleResponse = await response.json(); - - if (!result.success || !result.data) { - throw new Error(result.message || 'Bundle simulation failed'); - } - - logger.info('DEBUG simulateBundle - backend response:', result); - - // Fetch current gas price via RPC - const currentGasPrice = await this.getCurrentGasPrice(request.chainId); - logger.info('DEBUG - Raw gas price from RPC (wei):', currentGasPrice.toString()); - logger.info('DEBUG - Gas price in gwei:', (Number(currentGasPrice) / 1e9).toFixed(2)); - logger.info('DEBUG - Chain ID:', request.chainId); - - // Transform backend response to human-readable format - const transformedResults = result.data.map((item, index) => { - const gasUsed = hexToBigInt(item.gasUsed); - logger.info('DEBUG - Gas used (units):', gasUsed.toString()); - const gasCostWei = gasUsed * currentGasPrice; - logger.info('DEBUG - Gas cost (wei):', gasCostWei.toString()); - const gasCostEther = formatEther(gasCostWei); - - return { - stepId: request.simulations[index]?.stepId || `step-${index}`, - gasUsed: gasCostEther, // Human-readable cost like "0.004205" - success: true, - error: undefined, - }; - }); - - // Calculate total cost - const totalGasCostWei = result.data.reduce((sum, item) => { - const gasUsed = hexToBigInt(item.gasUsed); - return sum + gasUsed * currentGasPrice; - }, BigInt(0)); - - const totalGasCostEther = formatEther(totalGasCostWei); - - logger.info('DEBUG simulateBundle - transformed response:', { - results: transformedResults, - totalGasUsed: totalGasCostEther, - gasPriceUsed: formatEther(currentGasPrice * BigInt(1000000000)) + ' gwei', - }); - - return { - success: true, - results: transformedResults, - totalGasUsed: totalGasCostEther, - }; - } catch (error) { - logger.error('Bundle simulation API error:', error as Error); - return { - success: false, - results: request.simulations.map((sim) => ({ - stepId: sim.stepId, - gasUsed: '0.0', - success: false, - error: error instanceof Error ? error.message : 'Unknown error', - })), - totalGasUsed: '0.0', - }; - } - } -} - -/** - * Factory function to create a backend simulation client - */ -export function createBackendSimulationClient(config: BackendConfig): BackendSimulationClient { - return new BackendSimulationClient(config); -} - -/** - * Default backend simulation client instance - */ -let defaultSimulationClient: BackendSimulationClient | null = null; - -/** - * Configure the default simulation client - */ -export function configureSimulationBackend(config: BackendConfig): void { - defaultSimulationClient = new BackendSimulationClient(config); -} - -/** - * Get the default simulation client - */ -export function getSimulationClient(): BackendSimulationClient | null { - return defaultSimulationClient; -} - -/** - * Check if simulation backend is configured - */ -export function isSimulationConfigured(): boolean { - return defaultSimulationClient !== null; -} - -/** - * Initialize simulation client with health check - */ -export async function initializeSimulationClient(baseUrl: string = BACKEND_URL): Promise<{ - success: boolean; - error?: string; -}> { - try { - const client = new BackendSimulationClient({ baseUrl }); - - // Test the connection - const isHealthy = await client.testConnection(); - if (!isHealthy) { - return { - success: false, - error: `Backend service at ${baseUrl} is not responding or unhealthy`, - }; - } - - // Configure as default client - defaultSimulationClient = client; - - return { - success: true, - }; - } catch (error) { - return { - success: false, - error: error instanceof Error ? error.message : 'Unknown initialization error', - }; - } -} - -// Initialize with BACKEND_URL by default -configureSimulationBackend({ baseUrl: BACKEND_URL }); diff --git a/packages/core/package.json b/packages/core/package.json deleted file mode 100644 index 54ab0af8..00000000 --- a/packages/core/package.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "@avail-project/nexus-core", - "version": "0.0.2", - "description": "Nexus headless SDK for cross-chain transactions", - "main": "./dist/index.js", - "module": "./dist/index.esm.js", - "types": "./dist/index.d.ts", - "files": [ - "dist", - "README.md" - ], - "scripts": { - "build": "rollup -c && pnpm run copy:commons", - "copy:commons": "mkdir -p dist/commons && cp -R ../commons/dist/* dist/commons/ || true", - "dev": "rollup -c -w", - "typecheck": "tsc --noEmit" - }, - "sideEffects": false, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.esm.js", - "require": "./dist/index.js" - } - }, - "keywords": [ - "nexus", - "sdk", - "blockchain", - "bridge", - "swap", - "web3", - "chain", - "abstraction", - "headless" - ], - "author": "decocereus", - "license": "MIT", - "dependencies": { - "@arcana/ca-common": "1.0.1-alpha.6", - "@cosmjs/proto-signing": "^0.34.0", - "@cosmjs/stargate": "^0.34.0", - "@metamask/safe-event-emitter": "3.1.2", - "@starkware-industries/starkware-crypto-utils": "^0.2.1", - "axios": "^1.7.7", - "decimal.js": "^10.6.0", - "es-toolkit": "^1.39.8", - "fuels": "0.101.1", - "it-ws": "^6.1.5", - "long": "^5.3.2", - "msgpackr": "^1.11.4", - "tslib": "2.8.1" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-node-resolve": "^15.0.0", - "@rollup/plugin-typescript": "^11.0.0", - "rollup": "^4.0.0", - "rollup-plugin-dts": "^6.0.0", - "rollup-plugin-typescript2": "0.36.0", - "typescript": "^5.0.0" - }, - "peerDependencies": { - "viem": "^2.0.0" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/core/sdk/ca-base/abi/vault.ts b/packages/core/sdk/ca-base/abi/vault.ts deleted file mode 100644 index 5d18a0fc..00000000 --- a/packages/core/sdk/ca-base/abi/vault.ts +++ /dev/null @@ -1,27 +0,0 @@ -const FillEvent = { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "requestHash", - type: "bytes32", - }, - { - indexed: false, - internalType: "address", - name: "from", - type: "address", - }, - { - indexed: false, - internalType: "address", - name: "solver", - type: "address", - }, - ], - name: "Fill", - type: "event", -} as const; - -export { FillEvent }; diff --git a/packages/core/sdk/ca-base/ca.ts b/packages/core/sdk/ca-base/ca.ts deleted file mode 100644 index dc83cee8..00000000 --- a/packages/core/sdk/ca-base/ca.ts +++ /dev/null @@ -1,627 +0,0 @@ -import { createCosmosWallet } from '@arcana/ca-common'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; -import SafeEventEmitter from '@metamask/safe-event-emitter'; -import { keyDerivation } from '@starkware-industries/starkware-crypto-utils'; -import { - Account, - CHAIN_IDS, - FuelConnector, - FuelConnectorSendTxParams, - Provider, - TransactionRequestLike, - TransactionResponse, -} from 'fuels'; -import { - createWalletClient, - custom, - WalletActions, - publicActions, - type PublicActions, - Client, - CustomTransport, -} from 'viem'; -import { privateKeyToAccount, PrivateKeyAccount } from 'viem/accounts'; -import { createSiweMessage } from 'viem/siwe'; -import { ChainList } from './chains'; -import { getNetworkConfig } from './config'; -import { FUEL_NETWORK_URL } from './constants'; -import { getLogger, LOG_LEVEL, setLogLevel } from './logger'; -import { AllowanceQuery, BridgeQuery, TransferQuery } from './query'; -import { fixTx } from './requestHandlers/fuel/common'; -import { getFuelProvider } from './requestHandlers/fuel/provider'; -import { createHandler } from './requestHandlers/router'; -import { - BridgeQueryInput, - ChainListType, - EthereumProvider, - EVMTransaction, - ExactInSwapInput, - ExactOutSwapInput, - NetworkConfig, - NexusNetwork, - OnAllowanceHook, - OnIntentHook, - RequestArguments, - SDKConfig, - SwapInputOptionalParams, - SwapMode, - SwapParams, - SupportedChainsResult, - TransferQueryInput, - TxOptions, -} from '@nexus/commons'; -import { - cosmosFeeGrant, - equalFold, - fetchMyIntents, - getSDKConfig, - getSupportedChains, - getTxOptions, - isArcanaWallet, - isEVMTx, - minutesToMs, - refundExpiredIntents, - switchChain, -} from './utils'; -import { swap } from './swap/swap'; -import { getBalances } from './swap/route'; -import { getSwapSupportedChains } from './swap/utils'; - -setLogLevel(LOG_LEVEL.NOLOGS); -const logger = getLogger(); - -enum INIT_STATUS { - CREATED, - RUNNING, - DONE, -} - -const SIWE_STATEMENT = 'Sign in to enable Nexus'; - -export class CA { - static getSupportedChains = getSupportedChains; - protected _caEvents = new SafeEventEmitter(); - #cosmosWallet?: DirectSecp256k1Wallet; - #ephemeralWallet?: PrivateKeyAccount; - public chainList: ChainListType; - protected _config: Required; - protected _evm?: { - client: Client; - modProvider: EthereumProvider; - provider: EthereumProvider; - }; - protected _fuel?: { - account: Account; - address: string; - connector: FuelConnector; - modConnector: FuelConnector; - modProvider: Provider; - provider: Provider; - }; - protected _hooks: { - onAllowance: OnAllowanceHook; - onIntent: OnIntentHook; - } = { - onAllowance: (data) => data.allow(data.sources.map(() => 'max')), - onIntent: (data) => data.allow(), - }; - protected _initPromises: (() => void)[] = []; - protected _initStatus = INIT_STATUS.CREATED; - protected _isArcanaProvider = false; - protected _networkConfig: NetworkConfig; - protected _refundInterval: number | undefined; - protected constructor( - config: { network?: NexusNetwork; debug?: boolean } = { debug: false, network: 'testnet' }, - ) { - this._config = getSDKConfig(config); - this._networkConfig = getNetworkConfig(this._config.network); - this.chainList = new ChainList(this._networkConfig.NETWORK_HINT); - if (this._config.debug) { - setLogLevel(LOG_LEVEL.DEBUG); - } - } - - protected _allowance() { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - - return new AllowanceQuery(this._evm.client, this._networkConfig, this.chainList); - } - - protected async _bridge(input: BridgeQueryInput) { - const bq = new BridgeQuery( - input, - this._init, - this._changeChain.bind(this), - this._createEVMHandler.bind(this), - this._createFuelHandler.bind(this), - await this._getEVMAddress(), - this.chainList, - this._fuel?.account, - ); - - await bq.initHandler(); - return { exec: bq.exec, simulate: bq.simulate }; - } - - protected _deinit = () => { - this.#cosmosWallet = undefined; - if (this._evm) { - this._evm.provider.removeListener('accountsChanged', this.onAccountsChanged); - } - if (this._refundInterval) { - clearInterval(this._refundInterval); - this._refundInterval = undefined; - } - this._initStatus = INIT_STATUS.CREATED; - }; - - protected _getEVMProviderWithCA = () => { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - - return this._evm.modProvider; - }; - - protected async _getFuelWithCA() { - if (!this._fuel) { - throw new Error('Fuel connector is not set.'); - } - - return { - connector: this._fuel.modConnector, - provider: this._fuel.modProvider, - }; - } - - protected async _getMyIntents(page = 1) { - const wallet = await this._getCosmosWallet(); - const address = (await wallet.getAccounts())[0].address; - return fetchMyIntents(address, this._networkConfig.GRPC_URL, page); - } - - protected async _getUnifiedBalance(symbol: string, includeSwappableBalances = false) { - const balances = await this._getUnifiedBalances(includeSwappableBalances); - - return balances.find((s) => equalFold(s.symbol, symbol)); - } - - protected async _getUnifiedBalances(includeSwappableBalances = false) { - if (!this._evm) { - throw new Error('CA not initialized'); - } - const { assets } = await getBalances({ - networkHint: this._networkConfig.NETWORK_HINT, - evmAddress: (await this._evm.client.requestAddresses())[0], - chainList: this.chainList, - filter: false, - isCA: includeSwappableBalances === false, - vscDomain: this._networkConfig.VSC_DOMAIN, - fuelAddress: this._fuel?.address, - }); - return assets; - } - - protected _isInitialized() { - return this._initStatus === INIT_STATUS.DONE; - } - - protected async _swapWithExactIn(input: ExactInSwapInput, options?: SwapInputOptionalParams) { - return swap( - { - mode: SwapMode.EXACT_IN, - data: input, - }, - await this.getCommonSwapParams(options), - ); - } - protected async _swapWithExactOut(input: ExactOutSwapInput, options?: SwapInputOptionalParams) { - return swap( - { - mode: SwapMode.EXACT_OUT, - data: input, - }, - await this.getCommonSwapParams(options), - ); - } - - private async getCommonSwapParams(options?: SwapInputOptionalParams): Promise { - return { - emit: this._caEvents.emit.bind(this._caEvents), - chainList: this.chainList, - address: { - cosmos: (await this.#cosmosWallet!.getAccounts())[0].address, - eoa: (await this._evm!.client.getAddresses())[0], - ephemeral: this.#ephemeralWallet!.address, - }, - wallet: { - cosmos: this.#cosmosWallet!, - ephemeral: this.#ephemeralWallet!, - eoa: this._evm!.client, - }, - networkConfig: this._networkConfig, - ...options, - }; - } - - protected async _handleEVMTx(args: RequestArguments, options: Partial = {}) { - const response = await this._createEVMHandler( - (args.params as EVMTransaction[])[0], - getTxOptions(options), - ); - - if (response) { - await response.handler?.process(); - return response.processTx(); - } - return; - } - - protected _init = async () => { - if (!this._evm) { - throw new Error('use setEVMProvider before calling init()'); - } - if (this._initStatus === INIT_STATUS.CREATED) { - this._initStatus = INIT_STATUS.RUNNING; - try { - const address = await this._getEVMAddress(); - this._setProviderHooks(); - - if (!this._isArcanaProvider) { - this.#cosmosWallet = await this._createCosmosWallet(); - this._checkPendingRefunds(); - } - - this._initStatus = INIT_STATUS.DONE; - this._resolveInitPromises(); - this._caEvents.emit('accountsChanged', [address]); - } catch (e) { - this._initStatus = INIT_STATUS.CREATED; - logger.error('Error initializing CA', e); - throw new Error('Error initializing CA'); - } - } else if (this._initStatus === INIT_STATUS.RUNNING) { - return await this._waitForInit(); - } - }; - - protected onAccountsChanged = (accounts: Array<`0x${string}`>) => { - this._deinit(); - if (accounts.length !== 0) { - this._init(); - } - }; - - async _setEVMProvider(provider: EthereumProvider) { - if (this._evm?.provider === provider) { - return; - } - - this._evm = { - client: createWalletClient({ - transport: custom(provider), - }).extend(publicActions), - modProvider: Object.assign({}, provider, { - request: async (args: RequestArguments): Promise => { - if (args.method === 'eth_sendTransaction') { - if (!this._isArcanaProvider) { - return this._handleEVMTx(args); - } - } - return provider.request(args); - }, - }), - provider, - }; - - this._isArcanaProvider = isArcanaWallet(provider); - } - - protected async _setFuelConnector(connector: FuelConnector) { - if (this._fuel?.connector === connector) { - return; - } - - logger.debug('setFuelConnector', { - connected: connector.connected, - connector: connector, - }); - - if (!(await connector.isConnected())) { - await connector.connect(); - } - - const address = await connector.currentAccount(); - if (!address) { - throw new Error('could not get current account from connector'); - } - - const modProvider = getFuelProvider( - this._getUnifiedBalances.bind(this), - address, - this.chainList.getChainByID(CHAIN_IDS.fuel.mainnet)!, - ); - - const provider = new Provider(FUEL_NETWORK_URL, { - resourceCacheTTL: -1, - }); - - const clone: FuelConnector = Object.create(connector); - clone.sendTransaction = async ( - _address: string, - _transaction: TransactionRequestLike, - _params?: FuelConnectorSendTxParams, - ): Promise => { - logger.debug('fuelClone:sendTransaction:1', { - _address, - _params, - _transaction, - }); - const handlerResponse = await this._createFuelHandler(_transaction, { - bridge: false, - gas: 0n, - }); - - if (handlerResponse) { - await handlerResponse.handler?.process(); - } - - logger.debug('fuelClone:sendTransaction:2', { - request: Object.assign( - { - inputs: [], - }, - _transaction, - ), - }); - - const tx = await fixTx(_address, _transaction, provider); - - return connector.sendTransaction(_address, tx, _params); - }; - - this._fuel = { - account: new Account(address, modProvider, connector), - address, - connector: connector, - modConnector: clone, - modProvider, - provider, - }; - } - - protected _setOnAllowanceHook(hook: OnAllowanceHook) { - this._hooks.onAllowance = hook; - } - - protected _setOnIntentHook(hook: OnIntentHook) { - this._hooks.onIntent = hook; - } - - protected async _transfer(input: TransferQueryInput) { - const tq = new TransferQuery( - input, - this._init, - this._changeChain.bind(this), - this._createEVMHandler.bind(this), - this._createFuelHandler.bind(this), - await this._getEVMAddress(), - this.chainList, - this._fuel?.account, - ); - await tq.initHandler(); - return { exec: tq.exec, simulate: tq.simulate }; - } - - protected _changeChain(chainID: number) { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - const chain = this.chainList.getChainByID(chainID); - if (!chain) { - throw new Error('chain not supported'); - } - - return switchChain(this._evm.client, chain); - } - - protected async _checkPendingRefunds() { - await this._init(); - const account = await this._getEVMAddress(); - try { - await refundExpiredIntents(account, this._networkConfig.COSMOS_URL, this.#cosmosWallet!); - - this._refundInterval = window.setInterval(async () => { - await refundExpiredIntents(account, this._networkConfig.COSMOS_URL, this.#cosmosWallet!); - }, minutesToMs(10)); - } catch (e) { - logger.error('Error checking pending refunds', e); - } - } - - protected async _createCosmosWallet() { - const sig = await this._signatureForLogin(); - const pvtKey = keyDerivation.getPrivateKeyFromEthSignature(sig); - - const cosmosWallet = await createCosmosWallet(`0x${pvtKey.padStart(64, '0')}`); - this.#ephemeralWallet = privateKeyToAccount(`0x${pvtKey.padStart(64, '0')}`); - const address = (await cosmosWallet.getAccounts())[0].address; - await cosmosFeeGrant(this._networkConfig.COSMOS_URL, this._networkConfig.VSC_DOMAIN, address); - return cosmosWallet; - } - - protected async _createEVMHandler(tx: EVMTransaction, options: Partial = {}) { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - - if (!isEVMTx(tx)) { - logger.debug('invalid evm tx, returning', { tx }); - return null; - } - - const opt = getTxOptions(options); - - const chainId = await this._getChainID(); - const chain = this.chainList.getChainByID(chainId); - if (!chain) { - logger.info('chain not supported, returning', { - chainId, - }); - return null; - } - - return createHandler({ - chain, - chainList: this.chainList, - cosmosWallet: await this._getCosmosWallet(), - evm: { - address: await this._getEVMAddress(), - client: this._evm.client, - tx, - }, - fuel: this._fuel, - hooks: this._hooks, - options: { - emit: this._caEvents.emit.bind(this._caEvents), - networkConfig: this._networkConfig, - ...opt, - }, - }); - } - - public getEVMClient() { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - return this._evm.client; - } - - protected async _createFuelHandler(tx: TransactionRequestLike, options: Partial = {}) { - const chain = this.chainList.getChainByID(CHAIN_IDS.fuel.mainnet); - if (!chain) { - throw new Error(`chain not found: ${CHAIN_IDS.fuel.mainnet}`); - } - - if (!this._fuel) { - throw new Error('Fuel provider is not connected'); - } - - const address = await this._fuel.connector.currentAccount(); - if (!address) { - throw new Error('could not get current account from connector'); - } - - const opt = getTxOptions(options); - - return createHandler({ - chain, - chainList: this.chainList, - cosmosWallet: await this._getCosmosWallet(), - evm: { - address: await this._getEVMAddress(), - client: this._evm!.client, - }, - fuel: { - address, - connector: this._fuel.connector, - provider: this._fuel.provider, - tx, - }, - hooks: { - onAllowance: this._hooks.onAllowance, - onIntent: this._hooks.onIntent, - }, - options: { - emit: this._caEvents.emit.bind(this._caEvents), - networkConfig: this._networkConfig, - ...opt, - }, - }); - } - - protected _getChainID() { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - - return this._evm.client.getChainId(); - } - - protected async _getCosmosWallet() { - if (!this.#cosmosWallet) { - this.#cosmosWallet = await this._createCosmosWallet(); - } - return this.#cosmosWallet; - } - - protected async _getEVMAddress() { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - return (await this._evm.client.requestAddresses())[0]; - } - - protected _resolveInitPromises() { - const list = this._initPromises; - this._initPromises = []; - - for (const r of list) { - r(); - } - } - - protected async _setProviderHooks() { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - if (this._evm.provider) { - this._evm.provider.on('accountsChanged', this.onAccountsChanged); - } - } - - protected async _signatureForLogin() { - if (!this._evm) { - throw new Error('EVM provider is not set'); - } - const scheme = window.location.protocol.slice(0, -1); - const domain = window.location.host; - const origin = window.location.origin; - const address = await this._getEVMAddress(); - const message = createSiweMessage({ - address, - chainId: 1, - domain, - issuedAt: new Date('2024-12-16T12:17:43.182Z'), // this remains same to arrive at same pvt key - nonce: 'iLjYWC6s8frYt4l8w', // maybe this can be shortened hash of address - scheme, - statement: SIWE_STATEMENT, - uri: origin, - version: '1', - }); - const currentChain = await this._getChainID(); - try { - await this._evm.client.switchChain({ id: 1 }); - const res = await this._evm.client.signMessage({ - account: address, - message, - }); - return res; - } finally { - await this._evm.client.switchChain({ id: currentChain }); - } - } - - protected async _waitForInit(): Promise { - const promise = new Promise((resolve) => { - this._initPromises.push(resolve); - }); - return await promise; - } - - protected _getSwapSupportedChainsAndTokens(): SupportedChainsResult { - return getSwapSupportedChains(this.chainList); - } -} diff --git a/packages/core/sdk/ca-base/config.ts b/packages/core/sdk/ca-base/config.ts deleted file mode 100644 index ec66079f..00000000 --- a/packages/core/sdk/ca-base/config.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Environment } from '@arcana/ca-common'; - -import { NetworkConfig } from '@nexus/commons'; - -// Testnet with mainnet tokens -const CORAL_CONFIG: NetworkConfig = { - COSMOS_URL: 'https://cosmos01-testnet.arcana.network', - EXPLORER_URL: 'https://explorer.nexus.availproject.org', - FAUCET_URL: 'https://gateway001-testnet.arcana.network/api/v1/faucet', - GRPC_URL: 'https://grpcproxy-testnet.arcana.network', - NETWORK_HINT: Environment.CORAL, - SIMULATION_URL: 'https://ca-sim-testnet.arcana.network', - VSC_DOMAIN: 'vsc1-testnet.arcana.network', -}; - -// Dev with mainnet tokens -const CERISE_CONFIG: NetworkConfig = { - COSMOS_URL: 'https://cosmos01-dev.arcana.network', - EXPLORER_URL: 'https://explorer.nexus-cerise.availproject.org', - FAUCET_URL: 'https://gateway-dev.arcana.network/api/v1/faucet', - GRPC_URL: 'https://mimosa-dash-grpc.arcana.network', - NETWORK_HINT: Environment.CERISE, - SIMULATION_URL: 'https://ca-sim-dev.arcana.network', - VSC_DOMAIN: 'mimosa-dash-vsc.arcana.network', -}; - -// Dev with testnet tokens -const FOLLY_CONFIG: NetworkConfig = { - COSMOS_URL: 'https://cosmos04-dev.arcana.network', - EXPLORER_URL: 'https://explorer.nexus-folly.availproject.org', - FAUCET_URL: 'https://gateway-dev.arcana.network/api/v1/faucet', - GRPC_URL: 'https://grpc-folly.arcana.network', - NETWORK_HINT: Environment.FOLLY, - SIMULATION_URL: 'https://ca-sim-dev.arcana.network', - VSC_DOMAIN: 'vsc1-folly.arcana.network', -}; - -const isNetworkConfig = (config?: Environment | NetworkConfig): config is NetworkConfig => { - if (typeof config !== 'object') { - return false; - } - if ( - !( - config.VSC_DOMAIN && - config.COSMOS_URL && - config.SIMULATION_URL && - config.FAUCET_URL && - config.EXPLORER_URL && - config.GRPC_URL - ) - ) { - return false; - } - if (config.NETWORK_HINT === undefined) { - return false; - } - return true; -}; - -const getNetworkConfig = (network?: Environment | NetworkConfig): NetworkConfig => { - if (isNetworkConfig(network)) { - return network; - } - switch (network) { - case Environment.CERISE: - return CERISE_CONFIG; - case Environment.FOLLY: - return FOLLY_CONFIG; - default: - return CORAL_CONFIG; - } -}; - -export { CERISE_CONFIG, CORAL_CONFIG, getNetworkConfig }; diff --git a/packages/core/sdk/ca-base/constants.ts b/packages/core/sdk/ca-base/constants.ts deleted file mode 100644 index 8cc8fb18..00000000 --- a/packages/core/sdk/ca-base/constants.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Universe } from '@arcana/ca-common'; - -import { convertTo32BytesHex } from './utils'; - -const KAIA_CHAIN_ID = 8217; -const SOPHON_CHAIN_ID = 50104; -const HYPEREVM_CHAIN_ID = 0x3e7; -const MONAD_TESTNET_CHAIN_ID = 10143; - -const FUEL_NETWORK_URL = 'https://mainnet.fuel.network/v1/graphql'; - -const SymbolToLogo: { [k: string]: string } = { - BNB: 'https://assets.coingecko.com/coins/images/825/large/bnb-icon2_2x.png', - AVAX: 'https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png', - ETH: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png', - KAIA: 'https://assets.coingecko.com/coins/images/39901/large/KAIA.png', - MATIC: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png', - MON: 'https://assets.coingecko.com/coins/images/38927/large/monad.jpg', - POL: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png', - SOPH: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png', - USDC: 'https://coin-images.coingecko.com/coins/images/6319/large/usdc.png', - USDT: 'https://coin-images.coingecko.com/coins/images/35023/large/USDT.png', - WETH: 'https://coin-images.coingecko.com/coins/images/2518/standard/weth.png', - HYPE: 'https://assets.coingecko.com/coins/images/50882/large/hyperliquid.jpg', -}; - -const FUEL_BASE_ASSET_ID = '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07'; - -const getLogoFromSymbol = (symbol: string) => { - const logo = SymbolToLogo[symbol]; - if (!logo) { - return ''; - } - - return logo; -}; - -const isNativeAddress = (universe: Universe, address: `0x${string}`) => { - if (universe === Universe.ETHEREUM) { - return address === ZERO_ADDRESS || address === ZERO_ADDRESS_FUEL; - } - - if (universe === Universe.FUEL) { - return address === FUEL_BASE_ASSET_ID; - } - - // Handle other universes or return false by default - return false; -}; - -const INTENT_EXPIRY = 15 * 60 * 1000; - -const AaveTokenContracts: { - [key: number]: { - [symbol: string]: `0x${string}`; - }; -} = { - 1: { - USDC: '0x98C23E9d8f34FEFb1B7BD6a91B7FF122F4e16F5c', - USDT: '0x23878914EFE38d27C4D67Ab83ed1b93A74D4086a', - WETH: '0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8', - }, - 10: { - USDC: '0x38d693cE1dF5AaDF7bC62595A37D667aD57922e5', - USDT: '0x6ab707Aca953eDAeFBc4fD23bA73294241490620', - WETH: '0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8', - }, - 11155420: { - USDC: '0xa818F1B57c201E092C4A2017A91815034326Efd1', - }, - 137: { - USDC: '0xA4D94019934D8333Ef880ABFFbF2FDd611C762BD', - USDT: '0x6ab707Aca953eDAeFBc4fD23bA73294241490620', - WETH: '0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8', - }, - 42161: { - USDC: '0x724dc807b04555b71ed48a6896b6F41593b8C637', - USDT: '0x6ab707Aca953eDAeFBc4fD23bA73294241490620', - WETH: '0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8', - }, - // Testnet chains - 421614: { - USDC: '0x460b97BD498E1157530AEb3086301d5225b91216', - }, - 43114: { - USDC: '0x625E7708f30cA75bfd92586e17077590C60eb4cD', - USDT: '0x6ab707Aca953eDAeFBc4fD23bA73294241490620', - WETH: '0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8', - }, - 534352: { - USDC: '0x1D738a3436A8C49CefFbaB7fbF04B660fb528CbD', - WETH: '0xf301805bE1Df81102C957f6d4Ce29d2B8c056B2a', - }, - 56: { - USDC: '0x00901a076785e0906d1028c7d6372d247bec7d61', - USDT: '0xa9251ca9DE909CB71783723713B21E4233fbf1B1', - }, - 59144: { - USDC: '0x374D7860c4f2f604De0191298dD393703Cce84f3', - USDT: '0x88231dfEC71D4FF5c1e466D08C321944A7adC673', - WETH: '0x787897dF92703BB3Fc4d9Ee98e15C0b8130Bf163', - }, - 8453: { - USDC: '0x4e65fE4DbA92790696d040ac24Aa414708F5c0AB', - WETH: '0x7C307e128efA31F540F2E2d976C995E0B65F51F6', - }, - 84532: { - USDC: '0x10F1A9D11CDf50041f3f8cB7191CBE2f31750ACC', - USDT: '0xcE3CAae5Ed17A7AafCEEbc897DE843fA6CC0c018', - }, -}; - -const TOKEN_MINTER_CONTRACTS: { - [key: number]: { - [symbol: string]: `0x${string}`; - }; -} = { - 534352: { - USDT: '0xe2b4795039517653c5ae8c2a9bfdd783b48f447a', - }, - 59144: { - USDC: '0xA2Ee6Fce4ACB62D95448729cDb781e3BEb62504A', - USDT: '0x353012dc4a9A6cF55c941bADC267f82004A8ceB9', - }, - 8453: { - USDT: '0x4200000000000000000000000000000000000010', - }, -}; - -const TOP_OWNER: { - [key: number]: { - [symbol: string]: `0x${string}`; - }; -} = { - [SOPHON_CHAIN_ID]: { - ETH: '0x353B35a3362Dff8174cd9679BC4a46365CcD4dA7', - USDC: '0x61a87fa6Dd89a23c78F0754EF3372d35ccde5935', - USDT: '0x61a87fa6Dd89a23c78F0754EF3372d35ccde5935', - }, -}; - -const ZERO_ADDRESS: `0x${string}` = '0x0000000000000000000000000000000000000000'; - -const ZERO_ADDRESS_FUEL = convertTo32BytesHex(ZERO_ADDRESS); - -export { - AaveTokenContracts, - FUEL_BASE_ASSET_ID, - FUEL_NETWORK_URL, - getLogoFromSymbol, - HYPEREVM_CHAIN_ID, - INTENT_EXPIRY, - isNativeAddress, - KAIA_CHAIN_ID, - MONAD_TESTNET_CHAIN_ID, - SOPHON_CHAIN_ID, - TOKEN_MINTER_CONTRACTS, - TOP_OWNER, - ZERO_ADDRESS, -}; diff --git a/packages/core/sdk/ca-base/errors.ts b/packages/core/sdk/ca-base/errors.ts deleted file mode 100644 index 079c7e87..00000000 --- a/packages/core/sdk/ca-base/errors.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { InternalRpcError, UserRejectedRequestError } from "viem"; - -const ErrorUserDeniedIntent = new UserRejectedRequestError( - new Error("User denied intent."), -); - -const ErrorUserDeniedAllowance = new UserRejectedRequestError( - new Error("User denied allowance."), -); - -const ErrorInsufficientBalance = new InternalRpcError( - new Error("Insufficient balance."), -); - -const ErrorBuildingIntent = new InternalRpcError( - new Error("Error while building intent."), -); - -const ErrorLiquidityTimeout = new InternalRpcError( - new Error("Timed out waiting for liquidity."), -); - -export { - ErrorBuildingIntent, - ErrorInsufficientBalance, - ErrorLiquidityTimeout, - ErrorUserDeniedAllowance, - ErrorUserDeniedIntent, -}; diff --git a/packages/core/sdk/ca-base/index.ts b/packages/core/sdk/ca-base/index.ts deleted file mode 100644 index eeaf895b..00000000 --- a/packages/core/sdk/ca-base/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -export { CA } from './ca'; -export { simulateTransaction, type SimulationRequest, type SimulationResponse } from './simulate'; - -export { SwapStep } from './swap/steps'; -export type { - AllowanceHookSources, - BridgeQueryInput, - EthereumProvider, - ReadableIntent as Intent, - NetworkConfig, - OnAllowanceHook, - onAllowanceHookSource, - OnIntentHook, - Step as ProgressStep, - Steps as ProgressSteps, - RequestArguments, - RFF, - SDKConfig, - StepInfo, - TransferQueryInput, - UserAssetDatum as UserAsset, -} from '@nexus/commons'; - -export { Environment as Network, RequestForFunds } from '@arcana/ca-common'; diff --git a/packages/core/sdk/ca-base/logger.ts b/packages/core/sdk/ca-base/logger.ts deleted file mode 100644 index f0146063..00000000 --- a/packages/core/sdk/ca-base/logger.ts +++ /dev/null @@ -1,93 +0,0 @@ -export const LOG_LEVEL = { - DEBUG: 1, - ERROR: 4, - INFO: 2, - NOLOGS: 5, - WARNING: 3, -}; - -type ExceptionReporter = ((msg: string) => void) | null; -export const setExceptionReporter = (reporter: (msg: string) => void): void => { - state.exceptionReporter = reporter; -}; - -const sendException = (msg: string) => { - if (state.exceptionReporter) { - state.exceptionReporter(msg); - } -}; - -export const setLogLevel = (level: number): void => { - state.logLevel = level; -}; - -export const getLogger = (): Logger => { - return state.logger; -}; - -class Logger { - private prefix = "XAR_CA_SDK"; - - consoleLog(level: number, message: string, params: unknown): void { - if (level < state.logLevel) { - return; - } - - switch (level) { - case LOG_LEVEL.DEBUG: - console.debug(`[DEBUG]`, message, params); - break; - case LOG_LEVEL.ERROR: - console.error(`[ERROR]`, message, params); - break; - case LOG_LEVEL.INFO: - console.info(`[INFO]`, message, params); - break; - case LOG_LEVEL.WARNING: - console.warn(`[WARN]`, message, params); - break; - default: - console.log(`[LOG]`, message, params); - } - } - - debug(message: string, params: unknown = {}): void { - this.internalLog(LOG_LEVEL.DEBUG, message, params); - } - - error(message: string, err: unknown): void { - if (err instanceof Error) { - this.internalLog(LOG_LEVEL.ERROR, message, err.message); - sendException(JSON.stringify({ error: err.message, message })); - return; - } - if (typeof err == "string") { - this.internalLog(LOG_LEVEL.ERROR, message, err); - sendException(JSON.stringify({ error: err, message })); - } - } - - info(message: string, params: unknown = {}): void { - this.internalLog(LOG_LEVEL.INFO, message, params); - } - - internalLog(level: number, message: string, params: unknown): void { - const logMessage = `[${this.prefix}] Msg: ${message}\n`; - - this.consoleLog(level, logMessage, params); - } - - warn(message: string, params: unknown = {}): void { - this.internalLog(LOG_LEVEL.WARNING, message, params); - } -} - -const state: { - exceptionReporter: ExceptionReporter | null; - logger: Logger; - logLevel: number; -} = { - exceptionReporter: null, - logger: new Logger(), - logLevel: LOG_LEVEL.NOLOGS, -}; diff --git a/packages/core/sdk/ca-base/query/allowance.ts b/packages/core/sdk/ca-base/query/allowance.ts deleted file mode 100644 index c17d752d..00000000 --- a/packages/core/sdk/ca-base/query/allowance.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { WalletClient } from 'viem'; - -import { equalFold, getAllowance, setAllowances, switchChain } from '../utils'; -import { NetworkConfig, ChainListType } from '@nexus/commons'; - -class AllowanceQuery { - constructor( - private walletClient: WalletClient, - private networkConfig: NetworkConfig, - private chainList: ChainListType, - ) {} - - async get(input: { chainID?: number; tokens?: string[] }) { - const addresses = await this.walletClient.getAddresses(); - if (!addresses.length) { - throw new Error('No account connected with wallet client'); - } - const address = addresses[0]; - const tokens = input.tokens ?? ['USDT', 'USDC']; - const chainID = input.chainID ? [input.chainID] : this.chainList.chains.map((c) => c.id); - - const inp = []; - const out: Array<{ - allowance: bigint; - chainID: number; - token: string; - }> = []; - for (const c of chainID) { - for (const t of tokens) { - const token = this.chainList.getTokenInfoBySymbol(c, t); - if (token) { - const chain = this.chainList.getChainByID(c); - if (!chain) { - throw new Error('chain not supported'); - } - inp.push( - getAllowance(chain, address, token.contractAddress, this.chainList).then((val) => { - out.push({ - allowance: val, - chainID: c, - token: t, - }); - }), - ); - } - } - } - return Promise.all(inp).then(() => out); - } - - async revoke(input: { chainID: number; tokens: string[] }) { - await this.set({ ...input, amount: 0n }); - } - - async set(input: { amount: bigint; chainID: number; tokens: string[] }) { - if (input.tokens == null) { - throw new Error('missing token param'); - } - - if (input.amount == null) { - throw new Error('missing amount param'); - } - - if (input.chainID == null) { - throw new Error('missing chainID param'); - } - - const chain = this.chainList.getChainByID(input.chainID); - if (!chain) { - throw new Error('chain not supported'); - } - - let chainID = await this.walletClient.getChainId(); - if (input.chainID && input.chainID !== chainID) { - await switchChain(this.walletClient, chain); - chainID = input.chainID; - } - - const tokenAddresses: Array<`0x${string}`> = []; - for (const t of input.tokens) { - const token = chain.custom.knownTokens.find((kt) => equalFold(kt.symbol, t)); - if (token) { - tokenAddresses.push(token.contractAddress); - } - } - - if (!tokenAddresses.length) { - throw new Error('None of the supplied token symbols are recognised on this chain'); - } - - await setAllowances( - tokenAddresses, - this.walletClient, - this.networkConfig, - this.chainList, - chain, - input.amount, - ); - } -} - -export { AllowanceQuery }; diff --git a/packages/core/sdk/ca-base/query/bridge.ts b/packages/core/sdk/ca-base/query/bridge.ts deleted file mode 100644 index 505e876f..00000000 --- a/packages/core/sdk/ca-base/query/bridge.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { Account, bn, CHAIN_IDS } from 'fuels'; -import { encodeFunctionData, toHex } from 'viem'; - -import ERC20ABI from '../abi/erc20'; -import { ZERO_ADDRESS } from '../constants'; -import { convertIntent, equalFold, mulDecimals } from '../utils'; -import { - BridgeQueryInput, - CA, - EVMTransaction, - IRequestHandler, - ChainListType, -} from '@nexus/commons'; - -class BridgeQuery { - private handler?: IRequestHandler | null = null; - constructor( - private input: BridgeQueryInput, - private init: CA['init'], - private switchChain: CA['switchChain'], - private createEVMHandler: CA['createEVMHandler'], - private createFuelHandler: CA['createFuelHandler'], - private address: `0x${string}`, - private chainList: ChainListType, - private fuelAccount?: Account, - ) {} - - exec = () => { - if (!this.handler) { - throw new Error('ca not applicable'); - } - - return this.handler.process(); - }; - - public async initHandler() { - if (!this.handler) { - const input = this.input; - await this.init(); - - if (input.token && input.amount && input.chainId) { - const token = this.chainList.getTokenInfoBySymbol(input.chainId, input.token); - if (!token) { - throw new Error('Token not supported on this chain.'); - } - - const bridgeAmount = mulDecimals(input.amount, token.decimals); - - if (input.chainId === CHAIN_IDS.fuel.mainnet) { - if (this.fuelAccount) { - const tx = await this.fuelAccount.createTransfer( - // Random address, since bridge won't call the final tx - '0xE78655DfAd552fc3658c01bfb427b9EAb0c628F54e60b54fDA16c95aaAdE797A', - bn(bridgeAmount.toString()), - token.contractAddress, - ); - - const handlerResponse = await this.createFuelHandler(tx, { - bridge: true, - gas: input.gas ?? 0n, - skipTx: true, - }); - - this.handler = handlerResponse?.handler; - } else { - throw new Error('Fuel connector is not set'); - } - } else { - await this.switchChain(input.chainId); - const p: EVMTransaction = { - from: this.address, - to: this.address, - }; - - const isNative = equalFold(token.contractAddress, ZERO_ADDRESS); - - if (isNative) { - p.value = toHex(bridgeAmount); - input.gas = 0n; - } else { - p.to = token.contractAddress; - p.data = encodeFunctionData({ - abi: ERC20ABI, - args: [this.address, BigInt(bridgeAmount.toString())], - functionName: 'transfer', - }); - } - - const handlerResponse = await this.createEVMHandler(p, { - bridge: true, - gas: input.gas ?? 0n, - skipTx: true, - sourceChains: input.sourceChains, - }); - - this.handler = handlerResponse?.handler; - } - - return; - } - throw new Error('bridge: missing params'); - } - } - - simulate = async () => { - if (!this.handler) { - throw new Error('ca not applicable'); - } - - const response = await this.handler.buildIntent(this.input.sourceChains ?? []); - if (!response) { - throw new Error('ca not applicable'); - } - - return { - intent: convertIntent(response.intent, response.token, this.chainList), - token: response.token, - }; - }; -} - -export { BridgeQuery }; diff --git a/packages/core/sdk/ca-base/query/index.ts b/packages/core/sdk/ca-base/query/index.ts deleted file mode 100644 index 49611871..00000000 --- a/packages/core/sdk/ca-base/query/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./allowance"; -export * from "./bridge"; -export * from "./transfer"; diff --git a/packages/core/sdk/ca-base/query/transfer.ts b/packages/core/sdk/ca-base/query/transfer.ts deleted file mode 100644 index d8264489..00000000 --- a/packages/core/sdk/ca-base/query/transfer.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Account, bn, CHAIN_IDS } from 'fuels'; -import { encodeFunctionData, Hex } from 'viem'; - -import ERC20ABI from '../abi/erc20'; -import { ZERO_ADDRESS } from '../constants'; -import { getLogger } from '../logger'; -import { convertIntent, equalFold, mulDecimals } from '../utils'; -import { - CA, - CreateHandlerResponse, - EVMTransaction, - TransferQueryInput, - ChainListType, -} from '@nexus/commons'; - -const logger = getLogger(); - -class TransferQuery { - private handlerResponse: CreateHandlerResponse | null = null; - constructor( - private input: TransferQueryInput, - private init: CA['init'], - private switchChain: CA['switchChain'], - private createEVMHandler: CA['createEVMHandler'], - private createFuelHandler: CA['createFuelHandler'], - private evmAddress: Hex, - private chainList: ChainListType, - private fuelAccount?: Account, - ) {} - - exec = async () => { - if (!this.handlerResponse?.handler) { - throw new Error('ca not applicable'); - } - - let explorerURL = ''; - const result = await this.handlerResponse.handler.process(); - if (result) { - explorerURL = result.explorerURL; - } - logger.debug('TransferQuery:Exec', { - state: 'processing completed, going to processTx()', - }); - const hash = (await this.handlerResponse.processTx()) as Hex; - return { - hash, - explorerURL, - }; - }; - - public async initHandler() { - if (!this.handlerResponse) { - const input = this.input; - await this.init(); - - logger.debug('SendQueryBuilder.exec', { - c: input.chainId, - p: input, - }); - if (input.to && input.amount !== undefined && input.token && input.chainId) { - const tokenInfo = this.chainList.getTokenInfoBySymbol(input.chainId, input.token); - if (!tokenInfo) { - throw new Error('Token not supported on this chain.'); - } - - const amount = mulDecimals(input.amount, tokenInfo.decimals); - - logger.debug('transfer:2', { amount, tokenInfo }); - - if (input.chainId === CHAIN_IDS.fuel.mainnet) { - if (this.fuelAccount) { - const tx = await this.fuelAccount.createTransfer( - input.to, - bn(amount.toString()), - tokenInfo.contractAddress, - ); - this.handlerResponse = await this.createFuelHandler(tx, { - bridge: false, - gas: 0n, - skipTx: false, - }); - } else { - throw new Error('Fuel connector is not set'); - } - } else { - await this.switchChain(input.chainId); - const isNative = equalFold(tokenInfo.contractAddress, ZERO_ADDRESS); - - const p: EVMTransaction = { - from: this.evmAddress, - to: input.to, - }; - if (isNative) { - p.value = `0x${amount.toString(16)}`; - } else { - p.to = tokenInfo.contractAddress; - p.data = encodeFunctionData({ - abi: ERC20ABI, - args: [input.to, amount], - functionName: 'transfer', - }); - } - - this.handlerResponse = await this.createEVMHandler(p, { - bridge: false, - gas: 0n, - skipTx: false, - sourceChains: input.sourceChains, - }); - } - - return; - } - throw new Error('transfer: missing params'); - } - } - - simulate = async () => { - if (!this.handlerResponse?.handler) { - throw new Error('ca not applicable'); - } - - const response = await this.handlerResponse.handler.buildIntent(this.input.sourceChains ?? []); - if (!response) { - throw new Error('ca not applicable'); - } - - return { - intent: convertIntent(response.intent, response.token, this.chainList), - token: response.token, - }; - }; -} - -export { TransferQuery }; diff --git a/packages/core/sdk/ca-base/requestHandlers/common/base.ts b/packages/core/sdk/ca-base/requestHandlers/common/base.ts deleted file mode 100644 index 3f4700f0..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/common/base.ts +++ /dev/null @@ -1,1004 +0,0 @@ -import { - ArcanaVault, - ChaindataMap, - ERC20ABI, - EVMVaultABI, - MsgCreateRequestForFunds, - OmniversalChainID, - OmniversalRFF, - PermitVariant, - Universe, -} from '@arcana/ca-common'; -import Decimal from 'decimal.js'; -import { Account, BN, CHAIN_IDS, hexlify } from 'fuels'; -import Long from 'long'; -import { Hex, hexToBytes, JsonRpcAccount, maxUint256, parseSignature, toBytes, toHex } from 'viem'; -import { INTENT_EXPIRY, isNativeAddress } from '../../constants'; -import { - ErrorInsufficientBalance, - ErrorUserDeniedAllowance, - ErrorUserDeniedIntent, -} from '../../errors'; -import { getLogger } from '../../logger'; -import { - ALLOWANCE_APPROVAL_MINED, - ALLOWANCE_APPROVAL_REQ, - ALLOWANCE_COMPLETE, - createSteps, - INTENT_ACCEPTED, - INTENT_DEPOSIT_REQ, - INTENT_DEPOSITS_CONFIRMED, - INTENT_FULFILLED, - INTENT_HASH_SIGNED, - INTENT_SUBMITTED, -} from '../../steps'; -import { - ChainListType, - Intent, - IRequestHandler, - onAllowanceHookSource, - RequestHandlerInput, - SetAllowanceInput, - SimulateReturnType, - SponsoredApprovalDataArray, - Step, - StepInfo, - TokenInfo, -} from '@nexus/commons'; -import { - convertGasToToken, - convertIntent, - convertTo32Bytes, - convertTo32BytesHex, - cosmosCreateRFF, - createDepositDoubleCheckTx, - createPublicClientWithFallback, - createRequestEVMSignature, - createRequestFuelSignature, - equalFold, - FeeStore, - fetchPriceOracle, - getAllowances, - getExplorerURL, - getFeeStore, - getSourcesAndDestinationsForRFF, - mulDecimals, - removeIntentHashFromStore, - signPermitForAddressAndValue, - storeIntentHashToStore, - switchChain, - vscCreateRFF, - vscCreateSponsoredApprovals, - vscPublishRFF, - waitForTxReceipt, - UserAssets, -} from '../../utils'; -import { getBalances } from 'sdk/ca-base/swap/route'; - -const logger = getLogger(); - -abstract class BaseRequest implements IRequestHandler { - abstract destinationUniverse: Universe; - protected chainList: ChainListType; - protected steps: Step[] = []; - - constructor(readonly input: RequestHandlerInput) { - this.chainList = this.input.chainList; - } - - buildIntent = async (sourceChains: number[] = []) => { - console.time('process:preIntentSteps'); - - console.time('preIntentSteps:API'); - const [simulation, [balances, oraclePrices, feeStore]] = await Promise.all([ - this.simulateTx(), - Promise.all([ - getBalances({ - networkHint: this.input.options.networkConfig.NETWORK_HINT, - vscDomain: this.input.options.networkConfig.VSC_DOMAIN, - evmAddress: this.input.evm.address, - chainList: this.chainList, - fuelAddress: this.input.fuel?.address, - isCA: true, - }), - fetchPriceOracle(this.input.options.networkConfig.GRPC_URL), - getFeeStore(this.input.options.networkConfig.GRPC_URL), - ]), - ]); - - // if simulation is null, then the transaction is not a supported token transfer, so skip - if (!simulation) { - return; - } - - console.timeEnd('preIntentSteps:API'); - logger.debug('Step 1:', { - balances, - feeStore, - oraclePrices, - simulation, - }); - - console.time('preIntentSteps: Parse'); - - const { assets } = balances; - // Step 2: parse simulation results - - const userAssets = new UserAssets(assets); - const { amount, gas, isIntentRequired } = this.parseSimulation({ - assets: userAssets, - simulation, - }); - - console.timeEnd('preIntentSteps: Parse'); - if (!isIntentRequired) { - return; - } - console.time('preIntentSteps: CalculateGas'); - - const gasInToken = convertGasToToken( - simulation.token, - oraclePrices, - this.input.chain.id, - this.input.chain.universe, - gas, - ); - console.timeEnd('preIntentSteps: CalculateGas'); - - logger.debug('preIntent:1', { - gasInNative: gas.toFixed(), - gasInToken: gasInToken.toFixed(), - }); - - // Step 4: create intent - console.time('preIntentSteps: CreateIntent'); - const intent = this.createIntent({ - amount, - assets: userAssets, - feeStore, - gas, - gasInToken, - sourceChains, - token: simulation.token, - }); - console.timeEnd('preIntentSteps: CreateIntent'); - console.timeEnd('process:preIntentSteps'); - - if (intent.isAvailableBalanceInsufficient) { - throw ErrorInsufficientBalance; - } - - return { intent, token: simulation.token }; - }; - - getUnallowedSources(intent: Intent, allowances: Awaited>) { - const sources: onAllowanceHookSource[] = []; - for (const s of intent.sources) { - if ( - s.chainID === intent.destination.chainID || - isNativeAddress(s.universe, s.tokenContract) - ) { - continue; - } - - const chain = this.chainList.getChainByID(s.chainID); - if (!chain) { - throw new Error('chain is not supported'); - } - - const token = this.chainList.getTokenByAddress(s.chainID, s.tokenContract); - if (!token) { - throw new Error('token is not supported'); - } - - const requiredAllowance = mulDecimals(s.amount, token.decimals); - const currentAllowance = allowances[s.chainID] ?? 0n; - - logger.debug('getUnallowedSources:1', { - currentAllowance: currentAllowance.toString(), - requiredAllowance: requiredAllowance.toString(), - token, - }); - - if (requiredAllowance > currentAllowance) { - const d = { - allowance: { - current: currentAllowance.toString(), - minimum: requiredAllowance.toString(), - }, - chain: { - id: chain.id, - logo: chain.custom.icon, - name: chain.name, - }, - token: { - contractAddress: token.contractAddress, - decimals: token.decimals, - logo: token.logo || '', - name: token.name, - symbol: token.symbol, - }, - }; - sources.push(d); - } - } - return sources; - } - - abstract parseSimulation(input: { assets: UserAssets; simulation: SimulateReturnType }): { - amount: Decimal; - gas: Decimal; - isIntentRequired: boolean; - }; - - process = async () => { - const i = await this.buildIntent(this.input.options.sourceChains); - if (!i) { - return; - } - let intent = i.intent; - const token = i.token; - - if (intent.isAvailableBalanceInsufficient) { - throw ErrorInsufficientBalance; - } - - // Create steps like a crazy person to create another one again - const allowances = await getAllowances( - intent.allSources, - this.input.evm.address, - this.input.chainList, - ); - - let unallowedSources = this.getUnallowedSources(intent, allowances); - this.createExpectedSteps(intent, unallowedSources); - - let accepted = false; - const refresh = async (sourceChains?: number[]) => { - if (accepted) { - logger.warn('Intent refresh called after acceptance'); - return convertIntent(intent, token, this.chainList); - } - const i = await this.buildIntent(sourceChains); - intent = i!.intent; - logger.debug('in refresh', { - i, - intent, - }); - if (intent.isAvailableBalanceInsufficient) { - throw ErrorInsufficientBalance; - } - unallowedSources = this.getUnallowedSources(intent, allowances); - this.createExpectedSteps(intent, unallowedSources); - - return convertIntent(intent, token, this.chainList); - }; - - // wait for intent acceptance hook - await new Promise((resolve, reject) => { - const allow = () => { - accepted = true; - return resolve('User allowed intent'); - }; - - const deny = () => { - return reject(ErrorUserDeniedIntent); - }; - - this.input.hooks.onIntent({ - allow, - deny, - intent: convertIntent(intent, token, this.chainList), - refresh, - }); - }); - - this.markStepDone(INTENT_ACCEPTED); - - console.time('process:AllowanceHook'); - - // Step 5: set allowance if not set - await this.waitForOnAllowanceHook(unallowedSources); - console.timeEnd('process:AllowanceHook'); - - // FIXME: Add showing intent again if prices change? - // Step 6: process intent - return await this.processIntent(intent); - }; - - async processIntent(intent: Intent) { - logger.debug('intent', { intent }); - - const { explorerURL, id, requestHash, waitForDoubleCheckTx } = await this.processRFF(intent); - - storeIntentHashToStore(this.input.evm.address, id.toNumber()); - await this.waitForFill(requestHash, id, waitForDoubleCheckTx); - removeIntentHashFromStore(this.input.evm.address, id); - - this.markStepDone(INTENT_FULFILLED); - - if (this.input.chain.universe === Universe.ETHEREUM) { - await this.input.evm.client.switchChain({ id: this.input.chain.id }); - } - - return { explorerURL }; - } - - async processRFF(intent: Intent) { - const { destinations, sources, universes } = getSourcesAndDestinationsForRFF( - intent, - this.input.chainList, - this.destinationUniverse, - ); - - const parties: Array<{ address: string; universe: Universe }> = []; - for (const universe of universes) { - if (universe === Universe.ETHEREUM) { - parties.push({ - address: convertTo32BytesHex(this.input.evm.address), - universe: universe, - }); - } - - if (universe === Universe.FUEL) { - parties.push({ - address: convertTo32BytesHex(this.input.fuel!.address as Hex), - universe, - }); - } - } - - logger.debug('processRFF:1', { - destinations, - parties, - sources, - universes, - }); - - const omniversalRff = new OmniversalRFF({ - destinationChainID: convertTo32Bytes(intent.destination.chainID), - destinations: destinations.map((dest) => ({ - tokenAddress: toBytes(dest.tokenAddress), - value: toBytes(dest.value), - })), - destinationUniverse: intent.destination.universe, - expiry: Long.fromString((BigInt(Date.now() + INTENT_EXPIRY) / 1000n).toString()), - nonce: window.crypto.getRandomValues(new Uint8Array(32)), - // @ts-ignore - signatureData: parties.map((p) => ({ - address: toBytes(p.address), - universe: p.universe, - })), - // @ts-ignore - sources: sources.map((source) => ({ - chainID: convertTo32Bytes(source.chainID), - tokenAddress: convertTo32Bytes(source.tokenAddress), - universe: source.universe, - value: toBytes(source.value), - })), - }); - - const signatureData: { - address: Uint8Array; - requestHash: `0x${string}`; - signature: Uint8Array; - universe: Universe; - }[] = []; - - for (const universe of universes) { - if (universe === Universe.ETHEREUM) { - const { requestHash, signature } = await createRequestEVMSignature( - omniversalRff.asEVMRFF(), - this.input.evm.address, - this.input.evm.client, - ); - - signatureData.push({ - address: convertTo32Bytes(this.input.evm.address), - requestHash, - signature, - universe: Universe.ETHEREUM, - }); - } - - if (universe === Universe.FUEL) { - if ( - !this.input.fuel?.address || - !this.input.fuel?.provider || - !this.input.fuel?.connector - ) { - logger.error('universe has fuel but not expected input', { - fuelInput: this.input.fuel, - }); - throw new Error('universe has fuel but not expected input'); - } - - const { requestHash, signature } = await createRequestFuelSignature( - this.input.chainList.getVaultContractAddress(CHAIN_IDS.fuel.mainnet), - this.input.fuel.provider, - this.input.fuel.connector, - omniversalRff.asFuelRFF(), - ); - signatureData.push({ - address: toBytes(this.input.fuel.address), - requestHash, - signature, - universe: Universe.FUEL, - }); - } - } - - logger.debug('processRFF:2', { omniversalRff, signatureData }); - - this.markStepDone(INTENT_HASH_SIGNED); - - const cosmosWalletAddress = (await this.input.cosmosWallet.getAccounts())[0].address; - - const msgBasicCosmos = MsgCreateRequestForFunds.create({ - destinationChainID: omniversalRff.protobufRFF.destinationChainID, - destinations: omniversalRff.protobufRFF.destinations, - destinationUniverse: omniversalRff.protobufRFF.destinationUniverse, - expiry: omniversalRff.protobufRFF.expiry, - nonce: omniversalRff.protobufRFF.nonce, - signatureData: signatureData.map((s) => ({ - address: s.address, - signature: s.signature, - universe: s.universe, - })), - sources: omniversalRff.protobufRFF.sources, - user: cosmosWalletAddress, - }); - - logger.debug('processRFF:3', { msgBasicCosmos }); - - const intentID = await cosmosCreateRFF({ - address: cosmosWalletAddress, - cosmosURL: this.input.options.networkConfig.COSMOS_URL, - msg: msgBasicCosmos, - wallet: this.input.cosmosWallet, - }); - - const explorerURL = getExplorerURL(this.input.options.networkConfig.EXPLORER_URL, intentID); - this.markStepDone(INTENT_SUBMITTED, { - explorerURL, - intentID: intentID.toNumber(), - }); - - const tokenCollections = []; - for (const [i, s] of sources.entries()) { - if (!isNativeAddress(s.universe, s.tokenAddress)) { - tokenCollections.push(i); - } - } - - const evmDeposits: Promise[] = []; - const fuelDeposits: Promise[] = []; - - const evmSignatureData = signatureData.find((d) => d.universe === Universe.ETHEREUM); - - if (!evmSignatureData && universes.has(Universe.ETHEREUM)) { - throw new Error('ethereum in universe list but no signature data present'); - } - - const fuelSignatureData = signatureData.find((d) => d.universe === Universe.FUEL); - - if (!fuelSignatureData && universes.has(Universe.FUEL)) { - throw new Error('fuel in universe list but no signature data present'); - } - - const doubleCheckTxs = []; - - for (const [i, s] of sources.entries()) { - const chain = this.input.chainList.getChainByID(Number(s.chainID)); - if (!chain) { - throw new Error('chain not found'); - } - - if (s.universe === Universe.FUEL) { - if (!this.input.fuel) { - throw new Error('fuel is involved but no associated data'); - } - - const account = new Account( - this.input.fuel.address, - this.input.fuel.provider, - this.input.fuel.connector, - ); - - const vault = new ArcanaVault( - this.chainList.getVaultContractAddress(CHAIN_IDS.fuel.mainnet), - account, - ); - - const tx = await vault.functions - .deposit(omniversalRff.asFuelRFF(), hexlify(fuelSignatureData!.signature), i) - .callParams({ - forward: { - amount: new BN(s.value.toString()), - assetId: s.tokenAddress, - }, - }) - .call(); - - this.markStepDone(INTENT_DEPOSIT_REQ(i + 1)); - - fuelDeposits.push( - (async function () { - const txResult = await tx.waitForResult(); - logger.debug('PostIntentSubmission: Fuel deposit result', { - txResult, - }); - - if (txResult.transactionResult.isStatusFailure) { - throw new Error('fuel deposit failed'); - } - })(), - ); - } else if (s.universe === Universe.ETHEREUM && isNativeAddress(s.universe, s.tokenAddress)) { - const chain = this.input.chainList.getChainByID(Number(s.chainID)); - if (!chain) { - throw new Error('chain not found'); - } - - await switchChain(this.input.evm.client, chain); - - const publicClient = createPublicClientWithFallback(chain); - - const { request } = await publicClient.simulateContract({ - abi: EVMVaultABI, - account: this.input.evm.address, - address: this.input.chainList.getVaultContractAddress(chain.id), - args: [omniversalRff.asEVMRFF(), toHex(evmSignatureData!.signature), BigInt(i)], - chain: chain, - functionName: 'deposit', - value: s.value, - }); - const hash = await this.input.evm.client.writeContract(request); - this.markStepDone(INTENT_DEPOSIT_REQ(i + 1)); - - evmDeposits.push(waitForTxReceipt(hash, publicClient)); - } - doubleCheckTxs.push( - createDepositDoubleCheckTx( - convertTo32Bytes(chain.id), - { - address: cosmosWalletAddress, - wallet: this.input.cosmosWallet, - }, - intentID, - this.input.options.networkConfig, - ), - ); - } - - if (evmDeposits.length || fuelDeposits.length) { - await Promise.all([Promise.all(evmDeposits), Promise.all(fuelDeposits)]); - this.markStepDone(INTENT_DEPOSITS_CONFIRMED); - } - - logger.debug('PostIntentSubmission: Intent ID', { - id: intentID.toNumber(), - }); - - if (tokenCollections.length > 0) { - logger.debug('processRFF', { - intentID: intentID.toString(), - message: 'going to create RFF', - tokenCollections, - }); - await vscCreateRFF( - this.input.options.networkConfig.VSC_DOMAIN, - intentID, - this.markStepDone, - tokenCollections, - ); - } else { - logger.debug('processRFF', { - message: 'going to publish RFF', - }); - await vscPublishRFF(this.input.options.networkConfig.VSC_DOMAIN, intentID); - } - - const destinationSigData = signatureData.find( - (s) => s.universe === intent.destination.universe, - ); - - if (!destinationSigData) { - throw new Error('requestHash not found for destination'); - } - - return { - explorerURL, - id: intentID, - requestHash: destinationSigData.requestHash, - waitForDoubleCheckTx: waitForDoubleCheckTx(doubleCheckTxs), - }; - } - - async setAllowances(input: Array) { - const originalChain = this.input.chain.id; - logger.debug('setAllowances', { originalChain }); - - const sponsoredApprovalParams: SponsoredApprovalDataArray = []; - try { - for (const source of input) { - logger.debug('setAllowances', { originalChain }); - const chain = this.chainList.getChainByID(source.chainID); - if (!chain) { - throw new Error('chain not supported'); - } - - const publicClient = createPublicClientWithFallback(chain); - - const vc = this.input.chainList.getVaultContractAddress(chain.id); - - const chainId = new OmniversalChainID(Universe.ETHEREUM, source.chainID); - const chainDatum = ChaindataMap.get(chainId); - if (!chainDatum) { - throw new Error('Chain data not found'); - } - const currency = chainDatum.CurrencyMap.get(convertTo32Bytes(source.tokenContract)); - if (!currency) { - throw new Error('Currency not found'); - } - logger.debug('setAllowances chain switching to ', { chain }); - await switchChain(this.input.evm.client, chain); - logger.debug('setAllowances chain switched to ', { - originalChain, - switchedTo: await this.input.evm.client?.getChainId(), - chain, - }); - - // FIXME: should be fixed on refactor - if (currency.permitVariant === PermitVariant.Unsupported || chain.id === 1) { - const h = await this.input.evm.client.writeContract({ - abi: ERC20ABI, - account: this.input.evm.address, - address: source.tokenContract, - args: [vc, BigInt(source.amount)], - chain, - functionName: 'approve', - }); - - this.markStepDone(ALLOWANCE_APPROVAL_REQ(source.chainID)); - - await publicClient.waitForTransactionReceipt({ - hash: h, - }); - - this.markStepDone(ALLOWANCE_APPROVAL_MINED(source.chainID)); - } else { - const account: JsonRpcAccount = { - address: this.input.evm.address, - type: 'json-rpc', - }; - - const signed = parseSignature( - await signPermitForAddressAndValue( - currency, - this.input.evm.client, - publicClient, - account, - vc, - source.amount, - ), - ); - - this.markStepDone(ALLOWANCE_APPROVAL_REQ(source.chainID)); - - sponsoredApprovalParams.push({ - address: convertTo32Bytes(account.address), - chain_id: chainDatum.ChainID32, - operations: [ - { - sig_r: hexToBytes(signed.r), - sig_s: hexToBytes(signed.s), - sig_v: signed.yParity < 27 ? signed.yParity + 27 : signed.yParity, - token_address: currency.tokenAddress, - value: convertTo32Bytes(source.amount), - variant: currency.permitVariant === PermitVariant.PolygonEMT ? 2 : 1, - }, - ], - universe: chainDatum.Universe, - }); - } - } - - if (sponsoredApprovalParams.length) { - logger.debug('setAllowances:sponsoredApprovals', { - sponsoredApprovalParams, - }); - await vscCreateSponsoredApprovals( - this.input.options.networkConfig.VSC_DOMAIN, - sponsoredApprovalParams, - this.markStepDone, - ); - } - } catch (e) { - console.error('Error setting allowances', e); - throw ErrorUserDeniedAllowance; - } finally { - if (this.input.chain.universe === Universe.ETHEREUM) { - await switchChain(this.input.evm.client, this.input.chain); - } - this.markStepDone(ALLOWANCE_COMPLETE); - } - } - - abstract simulateTx(): Promise; - - abstract waitForFill( - requestHash: `0x${string}`, - intentID: Long, - waitForDoubleCheckTx: () => Promise, - ): Promise; - - async waitForOnAllowanceHook(sources: onAllowanceHookSource[]): Promise { - if (sources.length === 0) { - return false; - } - - await new Promise((resolve, reject) => { - const allow = (allowances: Array<'max' | 'min' | bigint | string>) => { - if (sources.length !== allowances.length) { - return reject( - new Error( - `invalid input length for allow(). expected: ${sources.length} got: ${allowances.length}`, - ), - ); - } - - logger.debug('CA:BaseRequest:Allowances', { - allowances, - sources, - }); - const val: Array = []; - for (let i = 0; i < sources.length; i++) { - const source = sources[i]; - const allowance = allowances[i]; - let amount = 0n; - if (typeof allowance === 'string' && equalFold(allowance, 'max')) { - amount = maxUint256; - } else if (typeof allowance === 'string' && equalFold(allowance, 'min')) { - amount = BigInt(source.allowance.minimum); - } else if (typeof allowance === 'string') { - amount = mulDecimals(allowance, source.token.decimals); - } else { - amount = allowance; - } - val.push({ - amount, - chainID: source.chain.id, - tokenContract: source.token.contractAddress, - }); - } - this.setAllowances(val).then(resolve).catch(reject); - }; - - const deny = () => { - return reject(ErrorUserDeniedAllowance); - }; - - this.input.hooks.onAllowance({ - allow, - deny, - sources, - }); - }); - return true; - } - - protected createExpectedSteps(intent: Intent, unallowedSources?: onAllowanceHookSource[]) { - this.steps = createSteps(intent, this.chainList, unallowedSources); - - this.input.options.emit('expected_steps', this.steps); - logger.debug('ExpectedSteps', this.steps); - } - - protected createIntent(input: { - amount: Decimal; - assets: UserAssets; - feeStore: FeeStore; - gas: Decimal; - gasInToken: Decimal; - sourceChains: number[]; - token: TokenInfo; - }) { - const { amount, assets, feeStore, gas, gasInToken, token } = input; - const intent: Intent = { - allSources: [], - destination: { - amount: new Decimal('0'), - chainID: this.input.chain.id, - decimals: token.decimals, - gas: 0n, - tokenContract: token.contractAddress, - universe: this.destinationUniverse, - }, - fees: { - caGas: '0', - collection: '0', - fulfilment: '0', - gasSupplied: input.gasInToken.toFixed(), - protocol: '0', - solver: '0', - }, - isAvailableBalanceInsufficient: false, - sources: [], - }; - - const asset = assets.find(token.symbol); - if (!asset) { - throw new Error(`Asset ${token.symbol} not found in UserAssets`); - } - - const allSources = asset.iterate(feeStore).map((v) => ({ ...v, amount: v.balance })); - - intent.allSources = allSources; - - const destinationBalance = asset.getBalanceOnChain(this.input.chain.id, token.contractAddress); - - let borrow = new Decimal(0); - if (this.input.options.bridge) { - borrow = amount; - } else { - if (amount.greaterThan(destinationBalance)) { - borrow = amount.minus(destinationBalance); - } - if (destinationBalance !== '0') { - intent.sources.push({ - amount: amount.greaterThan(destinationBalance) ? new Decimal(destinationBalance) : amount, - chainID: this.input.chain.id, - tokenContract: token.contractAddress, - universe: this.destinationUniverse, - }); - } - } - - const protocolFee = feeStore.calculateProtocolFee(borrow); - intent.fees.protocol = protocolFee.toFixed(); - - let borrowWithFee = borrow.add(gasInToken).add(protocolFee); - - logger.debug('createIntent:0', { - borrow: borrow.toFixed(), - borrowWithFee: borrowWithFee.toFixed(), - destinationBalance, - gasInToken: gasInToken.toFixed(), - protocolFee: protocolFee.toFixed(), - }); - - const fulfilmentFee = feeStore.calculateFulfilmentFee({ - decimals: token.decimals, - destinationChainID: this.input.chain.id, - destinationTokenAddress: token.contractAddress, - }); - logger.debug('createIntent:1', { fulfilmentFee }); - - intent.fees.fulfilment = fulfilmentFee.toFixed(); - - borrowWithFee = borrowWithFee.add(fulfilmentFee); - - let accountedAmount = new Decimal(0); - - const allowedSources = allSources.filter((b) => { - if (input.sourceChains.length === 0) { - return true; - } - return input.sourceChains.includes(b.chainID); - }); - - logger.debug('createIntent:1.1', { allowedSources }); - - for (const assetC of allowedSources) { - if (accountedAmount.greaterThanOrEqualTo(borrowWithFee)) { - break; - } - - if (assetC.chainID === this.input.chain.id) { - continue; - } - - if (assetC.chainID === CHAIN_IDS.fuel.mainnet) { - const fuelChain = this.chainList.getChainByID(CHAIN_IDS.fuel.mainnet); - const baseAssetBalanceOnFuel = assets.getNativeBalance(fuelChain!); - if (new Decimal(baseAssetBalanceOnFuel).lessThan('0.000_003')) { - logger.debug('fuel base asset balance is lesser than min expected deposit fee, so skip', { - current: baseAssetBalanceOnFuel, - minimum: '0.000_003', - }); - continue; - } - } - - if (!isNativeAddress(assetC.universe, assetC.tokenContract)) { - const collectionFee = feeStore.calculateCollectionFee({ - decimals: assetC.decimals, - sourceChainID: assetC.chainID, - sourceTokenAddress: assetC.tokenContract, - }); - - intent.fees.collection = collectionFee.add(intent.fees.collection).toFixed(); - - borrowWithFee = borrowWithFee.add(collectionFee); - - logger.debug('createIntent:2', { collectionFee }); - } - - const unaccountedAmount = borrowWithFee.minus(accountedAmount); - - let borrowFromThisChain = new Decimal(assetC.balance).lessThanOrEqualTo(unaccountedAmount) - ? new Decimal(assetC.balance) - : unaccountedAmount; - - logger.debug('createIntent:2.1', { - accountedAmount: accountedAmount.toFixed(), - asset: assetC, - balance: assetC.balance.toFixed(), - borrowFromThisChain: borrowFromThisChain.toFixed(), - unaccountedAmount: unaccountedAmount.toFixed(), - }); - - const solverFee = feeStore.calculateSolverFee({ - borrowAmount: borrowFromThisChain, - decimals: assetC.decimals, - destinationChainID: this.input.chain.id, - destinationTokenAddress: token.contractAddress, - sourceChainID: assetC.chainID, - sourceTokenAddress: assetC.tokenContract, - }); - intent.fees.solver = solverFee.add(intent.fees.solver).toFixed(); - - logger.debug('createIntent:3', { solverFee }); - - borrowWithFee = borrowWithFee.add(solverFee); - - const unaccountedBalance = borrowWithFee.minus(accountedAmount); - - borrowFromThisChain = new Decimal(assetC.balance).lessThanOrEqualTo(unaccountedBalance) - ? new Decimal(assetC.balance) - : unaccountedBalance; - - intent.sources.push({ - amount: borrowFromThisChain, - chainID: assetC.chainID, - tokenContract: assetC.tokenContract, - universe: assetC.universe, - }); - - accountedAmount = accountedAmount.add(borrowFromThisChain); - } - - intent.destination.amount = borrow; - - if (accountedAmount.lt(borrowWithFee)) { - intent.isAvailableBalanceInsufficient = true; - } - - if (!gas.equals(0)) { - intent.destination.gas = mulDecimals(gas, this.input.chain.nativeCurrency.decimals); - } - - logger.debug('createIntent:4', { intent }); - - return intent; - } - - protected markStepDone = (step: StepInfo, data?: { [k: string]: unknown }) => { - const s = this.steps.find((s) => s.typeID === step.typeID); - if (s) { - this.input.options.emit('step_complete', { - ...s, - ...(data ? { data } : {}), - }); - } - }; -} - -const waitForDoubleCheckTx = (input: Array<() => Promise>) => { - return async () => { - await Promise.allSettled(input.map((i) => i())); - }; -}; - -export default BaseRequest; diff --git a/packages/core/sdk/ca-base/requestHandlers/common/utils.ts b/packages/core/sdk/ca-base/requestHandlers/common/utils.ts deleted file mode 100644 index 672ae225..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/common/utils.ts +++ /dev/null @@ -1,121 +0,0 @@ -import Decimal from 'decimal.js'; - -import { SOPHON_CHAIN_ID } from '../../constants'; -import { getLogger } from '../../logger'; -import { Chain, SimulateReturnType } from '@nexus/commons'; -import { divDecimals, UserAssets } from '../../utils'; - -const logger = getLogger(); - -const tokenRequestParseSimulation = ({ - assets, - bridge, - chain, - iGas, - simulation, -}: { - assets: UserAssets; - bridge: boolean; - chain: Chain; - iGas: bigint; - simulation: SimulateReturnType; -}) => { - const tokenContract = simulation.token.contractAddress; - const amount = simulation.amount ?? new Decimal(0); - const nativeToken = chain.nativeCurrency; - - logger.debug('ERC20RequestBase:ParseSimulation:1', { - assets, - tokenContract, - }); - const { chainsWithBalance, destinationAssetBalance, destinationGasBalance } = - assets.getAssetDetails(chain, tokenContract); - - const gasMultiple = simulation.gasFee - .mul(chain.id === SOPHON_CHAIN_ID ? 3 : 2) - .add(divDecimals(iGas, nativeToken.decimals)); - - logger.debug('ERC20RequestBase:ParseSimulation:0', { - destinationGasBalance, - expectedGas: gasMultiple.toFixed(), - simGas: simulation.gasFee.toFixed(), - }); - - const isGasRequiredToBeBorrowed = bridge - ? gasMultiple.greaterThan(0) - : gasMultiple.greaterThan(destinationGasBalance); - - let isIntentRequired = false; - if (bridge) { - isIntentRequired = true; - } - - let gas = new Decimal(0); - - logger.debug('ERC20RequestBase:parseSimulation:1', { - chainsWithBalance, - destinationAssetBalance, - isGasRequiredToBeBorrowed, - }); - if (chainsWithBalance) { - if (amount.greaterThan(destinationAssetBalance)) { - isIntentRequired = true; - } - - if (isGasRequiredToBeBorrowed) { - isIntentRequired = true; - gas = bridge ? gasMultiple : gasMultiple.minus(destinationGasBalance); - } - } - - return { - amount, - gas, - isIntentRequired, - }; -}; - -const nativeRequestParseSimulation = ({ - assets, - bridge, - chain, - simulation, -}: { - assets: UserAssets; - bridge: boolean; - chain: Chain; - simulation: SimulateReturnType; -}) => { - const { chainsWithBalance, destinationGasBalance } = assets.getAssetDetails( - chain, - simulation.token.contractAddress, - ); - - const gasMultiple = simulation.gasFee.mul(2); - - let isIntentRequired = false; - - if (bridge) { - isIntentRequired = true; - } - - if (chainsWithBalance) { - if (simulation.amount.add(gasMultiple).greaterThan(destinationGasBalance)) { - isIntentRequired = true; - } - } - - logger.debug('parseSimulation', { - amount: simulation.amount.toFixed(), - destinationGasBalance: destinationGasBalance, - gas: gasMultiple.toFixed(), - }); - - return { - amount: simulation.amount, - gas: gasMultiple, - isIntentRequired, - }; -}; - -export { nativeRequestParseSimulation, tokenRequestParseSimulation }; diff --git a/packages/core/sdk/ca-base/requestHandlers/evm/common.ts b/packages/core/sdk/ca-base/requestHandlers/evm/common.ts deleted file mode 100644 index ab0007a0..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/evm/common.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { RequestHandlerInput } from '@nexus/commons'; -import { getTokenTxFunction } from '../../utils'; - -const isERC20TokenTransfer = (input: RequestHandlerInput) => { - if (input.evm.tx) { - const { data, to } = input.evm.tx; - if (!data) { - return false; - } - const token = input.chainList.getTokenByAddress(input.chain.id, to); - const isTokenSupported = !!token; - if (isTokenSupported && data) { - const { functionName } = getTokenTxFunction(data as `0x${string}`); - if (functionName === 'transfer') { - return true; - } - } - } - return false; -}; - -const isNativeTokenTransfer = (input: RequestHandlerInput) => { - if (input.evm.tx) { - const { value } = input.evm.tx; - if (!value) return false; - try { - return BigInt(value) > 0n; - } catch { - return false; - } - } - return false; -}; - -export { isERC20TokenTransfer, isNativeTokenTransfer }; diff --git a/packages/core/sdk/ca-base/requestHandlers/evm/erc20.ts b/packages/core/sdk/ca-base/requestHandlers/evm/erc20.ts deleted file mode 100644 index 44eac2e2..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/evm/erc20.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { Universe } from '@arcana/ca-common'; -import Decimal from 'decimal.js'; -import Long from 'long'; -import { - createPublicClient, - decodeFunctionData, - PublicClient, - serializeTransaction, - webSocket, - WebSocketTransport, -} from 'viem'; -import type { RequestHandlerInput, SimulateReturnType } from '@nexus/commons'; -import { ERC20TransferABI } from '../../abi/erc20'; -import { KAIA_CHAIN_ID, SOPHON_CHAIN_ID } from '../../chains'; -import { - AaveTokenContracts, - HYPEREVM_CHAIN_ID, - MONAD_TESTNET_CHAIN_ID, - TOKEN_MINTER_CONTRACTS, - TOP_OWNER, -} from '../../constants'; -import { getLogger } from '../../logger'; -import { simulateTransaction, SimulationRequest } from '../../simulate'; -import { divDecimals, evmWaitForFill, getL1Fee, UserAssets } from '../../utils'; -import RequestBase from '../common/base'; -import { tokenRequestParseSimulation } from '../common/utils'; - -const logger = getLogger(); - -class ERC20Transfer extends RequestBase { - destinationUniverse = Universe.ETHEREUM; - publicClient: PublicClient; - simulateTxRes?: SimulateReturnType; - - constructor(readonly input: RequestHandlerInput) { - super(input); - this.publicClient = createPublicClient({ - transport: webSocket(this.input.chain.rpcUrls.default.webSocket[0]), - }); - } - - parseSimulation({ assets, simulation }: { assets: UserAssets; simulation: SimulateReturnType }) { - return tokenRequestParseSimulation({ - assets, - bridge: this.input.options.bridge, - chain: this.input.chain, - iGas: this.input.options.gas, - simulation, - }); - } - - async simulateTx(): Promise { - const { data, to } = this.input.evm.tx!; - const from = this.input.evm.address; - const token = this.chainList.getTokenByAddress(this.input.chain.id, to); - const nativeToken = this.chainList.getNativeToken(this.input.chain.id); - if (!token) { - return; - } - - const { args } = decodeFunctionData({ - abi: [ERC20TransferABI], - data: data ?? '0x00', - }); - - const amount = args[1]; - const amountInDecimal = divDecimals(amount, token.decimals); - - if (this.input.options.bridge) { - this.simulateTxRes = { - amount: amountInDecimal, - gas: this.input.options.gas, - gasFee: new Decimal(0), - token, - }; - } else if ( - [HYPEREVM_CHAIN_ID, KAIA_CHAIN_ID, MONAD_TESTNET_CHAIN_ID].includes(this.input.chain.id) - ) { - this.simulateTxRes = { - amount: amountInDecimal, - gas: 100_000n, - gasFee: new Decimal(0), - token, - }; - } - - if (this.simulateTxRes) { - let gasFee = 0n; - - if (this.simulateTxRes.gas > 0n) { - const [{ gasPrice, maxFeePerGas }, l1Fee] = await Promise.all([ - this.publicClient.estimateFeesPerGas(), - this.input.options.bridge - ? Promise.resolve(0n) - : getL1Fee( - this.input.chain, - serializeTransaction({ - chainId: this.input.chain.id, - data: data ?? '0x00', - to: to, - type: 'eip1559', - }), - ), - ]); - - const gasUnitPrice = maxFeePerGas ?? gasPrice ?? 0n; - if (gasUnitPrice === 0n) { - throw new Error('could not get maxFeePerGas or gasPrice from RPC'); - } - - gasFee = this.simulateTxRes.gas * gasUnitPrice + l1Fee; - } - - return { - ...this.simulateTxRes, - gasFee: divDecimals(gasFee, nativeToken.decimals), - }; - } - - const amountToAdd = new Decimal(args[1].toString()) - .toHexadecimal() - .split('0x')[1] - .padStart(40, '0'); - - let txsToSimulate: SimulationRequest[] = []; - if (AaveTokenContracts[this.input.chain.id]?.[token.symbol]) { - txsToSimulate.push({ - from: AaveTokenContracts[this.input.chain.id][token.symbol], - input: `0xa9059cbb000000000000000000000000${from - .replace('0x', '') - .toLowerCase()}000000000000000000000000${amountToAdd}`, - to: token.contractAddress, - }); - } else if (TOKEN_MINTER_CONTRACTS[this.input.chain.id]?.[token.symbol]) { - txsToSimulate.push({ - from: TOKEN_MINTER_CONTRACTS[this.input.chain.id]?.[token.symbol], - input: `0x40c10f19000000000000000000000000${from - .replace('0x', '') - .toLowerCase()}000000000000000000000000000000000000000000000000000000003b9aca00`, - to: token.contractAddress, - }); - } - txsToSimulate.push({ - from, - input: data, - to, - }); - - if (TOP_OWNER[this.input.chain.id]?.[token.symbol]) { - const ownerAddress = TOP_OWNER[this.input.chain.id][token.symbol]; - txsToSimulate = [ - { - from: ownerAddress, - input: data as `0x${string}`, - to, - }, - ]; - } - - const [simulation, feeData, l1Fee] = await Promise.all([ - simulateTransaction( - this.input.chain.id, - txsToSimulate, - this.input.options.networkConfig.SIMULATION_URL, - ), - this.publicClient.estimateFeesPerGas(), - getL1Fee( - this.input.chain, - serializeTransaction({ - chainId: this.input.chain.id, - data: data ?? '0x00', - to: to, - type: 'eip1559', - }), - ), - ]); - - logger.debug('simulateTx', { feeData }); - - const gasUnitPrice = feeData.maxFeePerGas ?? feeData.gasPrice ?? 0n; - if (gasUnitPrice === 0n) { - throw new Error('could not get maxFeePerGas or gasPrice from RPC'); - } - - let gasFee = - (this.input.chain.id === SOPHON_CHAIN_ID - ? BigInt(simulation.data.gas) - : BigInt(simulation.data.gas_used)) * - gasUnitPrice + - l1Fee; - - logger.debug('erc20:simulateTx', { - args, - feeData, - l1Fee, - maxFeePerGas: gasUnitPrice, - simulation, - totalGas: gasFee, - totalGasInDecimal: divDecimals(gasFee, nativeToken.decimals).toFixed(), - }); - - if (this.input.options.bridge) { - gasFee = 0n; - } - - this.simulateTxRes = { - amount: divDecimals(amount, token.decimals), - gas: - this.input.chain.id === SOPHON_CHAIN_ID - ? BigInt(simulation.data.gas) - : BigInt(simulation.data.gas_used), - gasFee: divDecimals(gasFee, nativeToken.decimals), - token, - }; - return this.simulateTxRes; - } - - async waitForFill( - requestHash: `0x${string}`, - intentID: Long, - waitForDoubleCheckTx: () => Promise, - ) { - logger.debug('waitForFill', { - intentID, - requestHash, - waitForDoubleCheckTx, - }); - - waitForDoubleCheckTx(); - - try { - await evmWaitForFill( - this.input.chainList.getVaultContractAddress(this.input.chain.id), - this.publicClient, - requestHash, - intentID, - this.input.options.networkConfig.GRPC_URL, - this.input.options.networkConfig.COSMOS_URL, - ); - } finally { - (await this.publicClient.transport.getRpcClient()).close(); - } - } -} - -export default ERC20Transfer; diff --git a/packages/core/sdk/ca-base/requestHandlers/evm/native.ts b/packages/core/sdk/ca-base/requestHandlers/evm/native.ts deleted file mode 100644 index b2197eb2..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/evm/native.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { Universe } from '@arcana/ca-common'; -import Long from 'long'; -import { - createPublicClient, - hexToBigInt, - PublicClient, - serializeTransaction, - webSocket, - WebSocketTransport, -} from 'viem'; - -import { ZERO_ADDRESS } from '../../constants'; -import { getLogger } from '../../logger'; -import { simulateTransaction, SimulationRequest } from '../../simulate'; -import { RequestHandlerInput, SimulateReturnType } from '@nexus/commons'; -import { divDecimals, evmWaitForFill, getL1Fee, UserAssets } from '../../utils'; -import RequestBase from '../common/base'; -import { nativeRequestParseSimulation } from '../common/utils'; -import Decimal from 'decimal.js'; - -const logger = getLogger(); - -class NativeTransfer extends RequestBase { - destinationUniverse = Universe.ETHEREUM; - private publicClient: PublicClient; - private simulateTxRes?: SimulateReturnType; - - constructor(readonly input: RequestHandlerInput) { - super(input); - const wsUrls = this.input.chain.rpcUrls?.default?.webSocket; - if (!wsUrls?.length) { - throw new Error(`Web-Socket RPC URL missing for chain ${this.input.chain.id}`); - } - - this.publicClient = createPublicClient({ - transport: webSocket(wsUrls[0]), - }); - } - - parseSimulation({ assets, simulation }: { assets: UserAssets; simulation: SimulateReturnType }) { - return nativeRequestParseSimulation({ - assets, - bridge: this.input.options.bridge, - chain: this.input.chain, - simulation, - }); - } - - async simulateTx() { - const { data, to, value } = this.input.evm.tx!; - const nativeToken = this.input.chainList.getNativeToken(this.input.chain.id); - - const amount = hexToBigInt((value as `0x${string}`) ?? `0x00`); - const amountInDecimal = divDecimals(amount, nativeToken.decimals); - - if (this.input.options.bridge) { - this.simulateTxRes = { - amount: amountInDecimal, - gas: this.input.options.gas, - gasFee: new Decimal(0), - token: nativeToken, - }; - } - - if (this.simulateTxRes) { - let gasFee = 0n; - - if (this.simulateTxRes.gas > 0n) { - const [{ gasPrice, maxFeePerGas }, l1Fee] = await Promise.all([ - this.publicClient.estimateFeesPerGas(), - this.input.options.bridge - ? Promise.resolve(0n) - : getL1Fee( - this.input.chain, - serializeTransaction({ - chainId: this.input.chain.id, - data: data ?? '0x00', - to: to, - type: 'eip1559', - value: amount, - }), - ), - ]); - - const gasUnitPrice = maxFeePerGas ?? gasPrice ?? 0n; - if (gasUnitPrice === 0n) { - throw new Error('could not get maxFeePerGas or gasPrice from RPC'); - } - - gasFee = this.simulateTxRes.gas * gasUnitPrice + l1Fee; - } - - return { - ...this.simulateTxRes, - gasFee: divDecimals(gasFee, nativeToken.decimals), - }; - } - - const txsToSimulate: SimulationRequest[] = [ - { - from: ZERO_ADDRESS, - input: data ?? '0x00', - to, - value: (value as `0x${string}`) ?? '0x00', - }, - ]; - - const [simulation, feeData, l1Fee] = await Promise.all([ - simulateTransaction( - this.input.chain.id, - txsToSimulate, - this.input.options.networkConfig.SIMULATION_URL, - ), - this.publicClient.estimateFeesPerGas(), - getL1Fee( - this.input.chain, - serializeTransaction({ - chainId: this.input.chain.id, - data: data ?? '0x00', - to: to, - type: 'eip1559', - value: hexToBigInt((value as `0x${string}`) ?? '0x00'), - }), - ), - ]); - - const gasUnitPrice = feeData.maxFeePerGas ?? feeData.gasPrice ?? 0n; - if (gasUnitPrice === 0n) { - throw new Error('could not get maxFeePerGas or gasPrice from RPC'); - } - - let gasFee = BigInt(simulation.data.gas_used) * gasUnitPrice + l1Fee; - - logger.debug('native:simulateTx', { - feeData, - l1Fee, - maxFeePerGas: gasUnitPrice, - simulation, - totalGas: gasFee, - totalGasInDecimal: divDecimals(gasFee, nativeToken.decimals), - }); - - this.simulateTxRes = { - amount: amountInDecimal, - gas: BigInt(simulation.data.gas_used), - gasFee: divDecimals(gasFee, nativeToken.decimals), - token: { - contractAddress: ZERO_ADDRESS, - decimals: nativeToken.decimals, - name: nativeToken.name, - symbol: nativeToken.symbol, - }, - }; - return this.simulateTxRes; - } - - async waitForFill( - requestHash: `0x${string}`, - intentID: Long, - waitForDoubleCheckTx: () => Promise, - ) { - waitForDoubleCheckTx(); - try { - await evmWaitForFill( - this.input.chainList.getVaultContractAddress(this.input.chain.id), - this.publicClient, - requestHash, - intentID, - this.input.options.networkConfig.GRPC_URL, - this.input.options.networkConfig.COSMOS_URL, - ); - } finally { - (await this.publicClient.transport.getRpcClient()).close(); - } - } -} - -export default NativeTransfer; diff --git a/packages/core/sdk/ca-base/requestHandlers/fuel/common.ts b/packages/core/sdk/ca-base/requestHandlers/fuel/common.ts deleted file mode 100644 index 62b5cc8c..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/fuel/common.ts +++ /dev/null @@ -1,143 +0,0 @@ -import Decimal from 'decimal.js'; -import { - Account, - bn, - CHAIN_IDS, - hexlify, - OutputType, - Provider, - TransactionRequest, - TransactionRequestLike, -} from 'fuels'; -import { Hex } from 'viem'; - -import { FUEL_BASE_ASSET_ID } from '../../constants'; -import { getLogger } from '../../logger'; -import { divDecimals } from '../../utils'; -import { ChainListType } from '@nexus/commons'; - -const logger = getLogger(); - -const simulate = async ( - tx: TransactionRequestLike, - address: string, - provider: Provider, - chainList: ChainListType, -) => { - const outputs = tx.outputs?.filter((o) => o.type === OutputType.Coin) ?? []; - const tokens = outputs - .map((o) => { - const token = chainList.getTokenByAddress(CHAIN_IDS.fuel.mainnet, o.assetId as Hex); - if (!token) return null; - return { - from: hexlify(address).toLowerCase(), - to: hexlify(o.to).toLowerCase(), - token: { - address: hexlify(o.assetId) as Hex, - amount: divDecimals(o.amount.toString(), token.decimals), - decimals: token.decimals, - logo: token.logo, - name: token.name, - symbol: token.symbol, - }, - }; - }) - .filter((o) => !!o) - .reduce((acc, o) => { - const existingCoin = acc.find( - (a) => o.from === a.from && o.token.address === a.token.contractAddress, - ); - if (existingCoin) { - existingCoin.token.amount = new Decimal(existingCoin.token.amount).plus(o.token.amount); - return acc; - } - acc.push({ - from: o.from, - to: o.to, - token: { - amount: new Decimal(o.token.amount), - contractAddress: o.token.address, - decimals: o.token.decimals, - logo: o.token.logo, - name: o.token.name, - symbol: o.token.symbol, - }, - }); - return acc; - }, [] as CoinTransfer[]) - .sort((a, b) => (new Decimal(a.token.amount).lessThan(b.token.amount) ? 1 : -1)); - - const { assembledRequest } = await provider.assembleTx({ - feePayerAccount: new Account(address), - request: tx as TransactionRequest, - }); - - logger.debug('Fuel Simulate: mappedOutputsToInputs', { - assembledRequest, - }); - - const coin = tokens?.length ? tokens[0] : null; - if (!coin) { - return; - } - - logger.debug('FuelSimulate', { - amount: coin.token.amount.toFixed(), - coin: coin, - }); - - const { amount, ...token } = coin.token; - return { - amount: amount, - gas: BigInt(0), - gasFee: divDecimals(BigInt(assembledRequest.maxFee.toString()) * 2n, 9), - token: { ...token, type: 'src20' }, - }; -}; - -const fixTx = async (address: string, tx: TransactionRequestLike, provider: Provider) => { - delete tx.inputs; - - const outputQuantities = tx.outputs - ?.filter((o) => o.type === OutputType.Coin) - .map(({ amount, assetId }) => ({ - amount: bn(amount), - assetId: String(assetId), - })); - - const aResponse = await provider.assembleTx({ - accountCoinQuantities: outputQuantities, - estimatePredicates: true, - feePayerAccount: new Account(address), - // @ts-ignore - request: tx, - }); - - logger.debug('fixTx:sendTransaction:3', { - assembleTxResponse: aResponse, - request: tx, - }); - - return aResponse.assembledRequest as TransactionRequestLike; -}; - -type CoinTransfer = { - from: string; - to: string; - token: { - amount: Decimal; - contractAddress: Hex; - decimals: number; - logo?: string; - name: string; - symbol: string; - }; -}; - -const isFuelNativeTransfer = (tx: TransactionRequestLike) => { - return tx.outputs?.every((o) => { - return 'assetId' in o && o.assetId === FUEL_BASE_ASSET_ID; - }); -}; - -export { fixTx, isFuelNativeTransfer, simulate }; diff --git a/packages/core/sdk/ca-base/requestHandlers/fuel/native.ts b/packages/core/sdk/ca-base/requestHandlers/fuel/native.ts deleted file mode 100644 index f95dd23f..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/fuel/native.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Universe } from '@arcana/ca-common'; -import Decimal from 'decimal.js'; -import { Account, TransactionRequest, TransactionRequestLike } from 'fuels'; -import Long from 'long'; - -import { getLogger } from '../../logger'; -import { RequestHandlerInput, SimulateReturnType } from '@nexus/commons'; -import { cosmosFillCheck, divDecimals, UserAssets } from '../../utils'; -import { requestTimeout } from '../../utils'; -import RequestBase from '../common/base'; -import { nativeRequestParseSimulation } from '../common/utils'; -import { simulate } from './common'; - -const logger = getLogger(); - -class FuelNativeTransfer extends RequestBase { - allowances: { [k: number]: bigint | null } | null = null; - destinationUniverse = Universe.FUEL; - fuelAddress: string; - simulateTxRes?: SimulateReturnType; - tx: TransactionRequestLike; - constructor(readonly input: RequestHandlerInput) { - super(input); - if (!this.input.fuel?.tx) { - throw new Error('Invalid request'); - } - - if (!this.input.fuel.address) { - throw new Error('fuel address missing'); - } - - this.tx = this.input.fuel.tx; - this.fuelAddress = this.input.fuel.address; - } - parseSimulation(input: { assets: UserAssets; simulation: SimulateReturnType }) { - return nativeRequestParseSimulation({ - ...input, - bridge: this.input.options.bridge, - chain: this.input.chain, - }); - } - - async simulateTx() { - logger.debug('fuel: reached simulate tx'); - const nativeCurrency = this.input.chain.nativeCurrency; - if (this.simulateTxRes) { - let gasFee = new Decimal(0); - if (!this.input.options.bridge) { - const { assembledRequest } = await this.input.fuel!.provider.assembleTx({ - feePayerAccount: new Account(this.input.fuel!.address), - request: this.input.fuel!.tx as TransactionRequest, - }); - gasFee = divDecimals( - BigInt(assembledRequest.maxFee.toString()) * 2n, - nativeCurrency.decimals, - ); - } - return { - ...this.simulateTxRes, - gasFee, - }; - } - - this.simulateTxRes = await simulate( - this.tx, - this.fuelAddress, - this.input.fuel!.provider, - this.input.chainList, - ); - - if (this.input.options.bridge && this.simulateTxRes) { - this.simulateTxRes.gasFee = new Decimal(0); - } - return this.simulateTxRes; - } - - async waitForFill(_: `0x${string}`, intentID: Long) { - const ac = new AbortController(); - await Promise.race([ - requestTimeout(3, ac), - cosmosFillCheck( - intentID, - this.input.options.networkConfig.GRPC_URL, - this.input.options.networkConfig.COSMOS_URL, - ac, - ), - ]); - } -} - -export default FuelNativeTransfer; diff --git a/packages/core/sdk/ca-base/requestHandlers/fuel/provider.ts b/packages/core/sdk/ca-base/requestHandlers/fuel/provider.ts deleted file mode 100644 index 662743c5..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/fuel/provider.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Universe } from '@arcana/ca-common'; -import { - Account, - Address, - AssembleTxParams, - AssembleTxResponse, - bn, - BN, - CoinTransactionRequestOutput, - FakeResources, - Provider as FuelProvider, - hexlify, - OutputType, - Provider, - ProviderOptions, - randomBytes, - Resource, - TransactionRequest, - UTXO_ID_LEN, -} from 'fuels'; - -import { FUEL_BASE_ASSET_ID, FUEL_NETWORK_URL } from '../../constants'; -import { getLogger } from '../../logger'; -import { Chain, UserAssetDatum } from '@nexus/commons'; -import { equalFold, mulDecimals } from '../../utils'; - -const logger = getLogger(); - -const getFuelProvider = ( - getBalances: () => Promise, - address: string, - chain: Chain, -): Provider => { - return new (class Provider extends FuelProvider { - constructor(url: string, options?: ProviderOptions) { - super(url, { ...options, resourceCacheTTL: -1 }); - } - - async assembleTx( - params: AssembleTxParams, - ): Promise> { - const { request } = params; - logger.debug('ffProvider', { - request, - }); - const addr = new Address(address); - - const balances = await getBalances(); - const assetIdsOnFuel = chain.custom.knownTokens.map((c) => c.contractAddress); - - const outputAssetList: CoinTransactionRequestOutput[] = request.outputs.filter( - (o) => o.type === OutputType.Coin, - ); - - const allAssetSupported = outputAssetList.every((a) => - assetIdsOnFuel.includes(hexlify(a.assetId) as `0x${string}`), - ); - - logger.debug('FuelProvide:1', { - allAssetSupported, - assetIdsOnFuel, - outputAssetList, - }); - - if (!allAssetSupported) { - return super.assembleTx({ - ...params, - feePayerAccount: new Account(addr), - request, - }); - } - - const al = []; - for (const a of assetIdsOnFuel) { - if (!outputAssetList.map((al) => al.assetId).includes(a) && a !== FUEL_BASE_ASSET_ID) { - continue; - } - const asset = balances.find((asset) => - asset.breakdown.find( - (b) => equalFold(b.contractAddress, hexlify(a)) && b.universe === Universe.FUEL, - ), - ); - - const chainAsset = asset?.breakdown.find( - (b) => equalFold(b.contractAddress, hexlify(a)) && b.universe === Universe.FUEL, - ); - - logger.debug('FuelProvider:2', { - asset, - chainAsset, - }); - - if (asset && chainAsset) { - const decimals = equalFold(FUEL_BASE_ASSET_ID, chainAsset.contractAddress) - ? 9 - : asset.decimals; - - const amount = new BN(mulDecimals(asset.balance, decimals).toString()); - - logger.debug('FuelProvider:3', { - amount, - assetId: hexlify(a), - }); - - al.push({ - amount, - assetId: hexlify(a), - }); - } - } - - request.addResources(generateFakeResources(al, new Address(address))); - - const { accountCoinQuantities, ...rest } = params; - - logger.debug('FuelProvider:4', { - accountCoinQuantities, - params: { ...params }, - request, - rest, - }); - - const response = await super.assembleTx({ - ...rest, - request, - }); - - logger.debug('FuelProvider:4', { - accountCoinQuantities, - params: { ...params }, - request, - response, - }); - return response as AssembleTxResponse; - } - })(FUEL_NETWORK_URL); -}; - -const generateFakeResources = (coins: FakeResources[], address: Address): Array => { - return coins.map((coin) => ({ - blockCreated: bn(1), - id: hexlify(randomBytes(UTXO_ID_LEN)), - owner: address, - txCreatedIdx: bn(1), - ...coin, - })); -}; - -export { getFuelProvider }; diff --git a/packages/core/sdk/ca-base/requestHandlers/fuel/token.ts b/packages/core/sdk/ca-base/requestHandlers/fuel/token.ts deleted file mode 100644 index 0e803ea6..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/fuel/token.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Universe } from '@arcana/ca-common'; -import Decimal from 'decimal.js'; -import { Account, TransactionRequest, TransactionRequestLike } from 'fuels'; -import Long from 'long'; - -import { getLogger } from '../../logger'; -import { RequestHandlerInput, SimulateReturnType } from '@nexus/commons'; -import { cosmosFillCheck, divDecimals, requestTimeout, UserAssets } from '../../utils'; -import RequestBase from '../common/base'; -import { tokenRequestParseSimulation } from '../common/utils'; -import { simulate } from './common'; - -const logger = getLogger(); - -class FuelTokenTransfer extends RequestBase { - allowances: { [k: number]: bigint | null } | null = null; - destinationUniverse = Universe.FUEL; - fuelAddress: string; - simulateTxRes?: SimulateReturnType; - tx: TransactionRequestLike; - constructor(readonly input: RequestHandlerInput) { - super(input); - if (!this.input.fuel?.tx) { - throw new Error('Invalid request'); - } - if (!this.input.fuel.address) { - throw new Error('fuel address missing'); - } - this.tx = this.input.fuel.tx; - this.fuelAddress = this.input.fuel.address; - } - - parseSimulation({ assets, simulation }: { assets: UserAssets; simulation: SimulateReturnType }) { - return tokenRequestParseSimulation({ - assets, - bridge: this.input.options.bridge, - chain: this.input.chain, - iGas: this.input.options.gas, - simulation, - }); - } - - async simulateTx() { - logger.debug('fuel: reached simulate tx'); - const nativeCurrency = this.input.chain.nativeCurrency; - - if (this.simulateTxRes) { - let gasFee = new Decimal(0); - if (!this.input.options.bridge) { - const { assembledRequest } = await this.input.fuel!.provider.assembleTx({ - feePayerAccount: new Account(this.input.fuel!.address), - request: this.input.fuel!.tx as TransactionRequest, - }); - gasFee = divDecimals( - BigInt(assembledRequest.maxFee.toString()) * 2n, - nativeCurrency.decimals, - ); - } - return { - ...this.simulateTxRes, - gasFee, - }; - } - - this.simulateTxRes = await simulate( - this.tx, - this.fuelAddress, - this.input.fuel!.provider, - this.input.chainList, - ); - if (this.input.options.bridge && this.simulateTxRes) { - this.simulateTxRes.gasFee = new Decimal(0); - } - - return this.simulateTxRes; - } - - async waitForFill(_: `0x${string}`, intentID: Long) { - const ac = new AbortController(); - await Promise.race([ - requestTimeout(3, ac), - cosmosFillCheck( - intentID, - this.input.options.networkConfig.GRPC_URL, - this.input.options.networkConfig.COSMOS_URL, - ac, - ), - ]); - } -} - -export default FuelTokenTransfer; diff --git a/packages/core/sdk/ca-base/requestHandlers/router.ts b/packages/core/sdk/ca-base/requestHandlers/router.ts deleted file mode 100644 index 27237023..00000000 --- a/packages/core/sdk/ca-base/requestHandlers/router.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { FUEL_NETWORK_URL } from '../constants'; -import { getLogger } from '../logger'; -import { CreateHandlerResponse, RequestHandler, RequestHandlerInput } from '@nexus/commons'; -import { switchChain } from '../utils'; -import { isERC20TokenTransfer, isNativeTokenTransfer } from './evm/common'; -import ERC20Transfer from './evm/erc20'; -import NativeTransfer from './evm/native'; -import { fixTx, isFuelNativeTransfer } from './fuel/common'; -import FuelNativeTransfer from './fuel/native'; -import FuelTokenTransfer from './fuel/token'; - -const logger = getLogger(); - -enum TxType { - EVMERC20Transfer, - EVMNativeTransfer, - FuelTokenTransfer, - FuelNativeTransfer, -} - -const handlers: Record = { - [TxType.EVMERC20Transfer]: ERC20Transfer, - [TxType.EVMNativeTransfer]: NativeTransfer, - [TxType.FuelNativeTransfer]: FuelNativeTransfer, - [TxType.FuelTokenTransfer]: FuelTokenTransfer, -}; - -const createHandler = (input: RequestHandlerInput): CreateHandlerResponse => { - logger.debug('router', { input }); - let handler: null | RequestHandler = null; - let processTx: () => Promise = async () => {}; - if (input.evm.tx) { - const tx = input.evm.tx; - if (isERC20TokenTransfer(input)) { - handler = handlers[TxType.EVMERC20Transfer]; - } else if (isNativeTokenTransfer(input)) { - handler = handlers[TxType.EVMNativeTransfer]; - } - processTx = async () => { - if (!input.options.bridge && !input.options.skipTx) { - logger.debug('in processTx', { - tx: input.evm.tx, - }); - await switchChain(input.evm.client, input.chain); - return input.evm.client.request({ - method: 'eth_sendTransaction', - params: [tx], - }); - } - return; - }; - } else if (input.fuel?.tx) { - if (isFuelNativeTransfer(input.fuel.tx)) { - handler = handlers[TxType.FuelNativeTransfer]; - } else { - handler = handlers[TxType.FuelTokenTransfer]; - } - - processTx = async () => { - if (!input.options.bridge && !input.options.skipTx) { - logger.debug('in processTx', { - address: input.fuel!.address, - provider: input.fuel!.provider, - tx: input.fuel?.tx, - }); - const tx = await fixTx(input.fuel!.address, input.fuel!.tx!, input.fuel!.provider); - - return input.fuel!.connector.sendTransaction(input.fuel!.address, tx, { - provider: { - url: FUEL_NETWORK_URL, - }, - }); - } - return; - }; - } else { - throw Error('Unknown handler'); - } - - return { - handler: handler ? new handler(input) : null, - processTx, - }; -}; - -export { createHandler }; diff --git a/packages/core/sdk/ca-base/simulate.ts b/packages/core/sdk/ca-base/simulate.ts deleted file mode 100644 index 10abe111..00000000 --- a/packages/core/sdk/ca-base/simulate.ts +++ /dev/null @@ -1,43 +0,0 @@ -import axios from "axios"; - -const simulateTransaction = async ( - chainID: number, - simulations: SimulationRequest[], - baseURL: string, -) => { - const url = new URL("/simulate", baseURL).toString(); - return await axios.post( - url, - { - chainID, - simulations, - }, - { - headers: { - "Content-Type": "application/json", - }, - }, - ); -}; - -type SimulationRequest = { - from: `0x${string}`; - input?: `0x${string}`; - to: `0x${string}`; - value?: `0x${string}`; -}; - -type SimulationResponse = { - amount: string; - gas: string; - gas_used: string; - token?: { - contract_address: `0x${string}`; - decimals: number; - name: string; - symbol: string; - type: string; - }; -}; - -export { simulateTransaction, type SimulationRequest, type SimulationResponse }; diff --git a/packages/core/sdk/ca-base/steps.ts b/packages/core/sdk/ca-base/steps.ts deleted file mode 100644 index 2ed1d305..00000000 --- a/packages/core/sdk/ca-base/steps.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { isNativeAddress } from './constants'; -import { ChainListType, Intent, onAllowanceHookSource, Step } from '@nexus/commons'; - -const INTENT_ACCEPTED = { - type: 'INTENT_ACCEPTED', - typeID: 'IA', -} as const; - -const INTENT_HASH_SIGNED = { - type: 'INTENT_HASH_SIGNED', - typeID: 'IHS', -} as const; - -const INTENT_SUBMITTED = { - type: 'INTENT_SUBMITTED', - typeID: 'IS', -} as const; - -const INTENT_INIT_STEPS = [ - INTENT_HASH_SIGNED, - { - ...INTENT_SUBMITTED, - data: { - explorerURL: '', - intentID: 0, - }, - }, -]; - -const INTENT_FULFILLED = { - type: 'INTENT_FULFILLED', - typeID: 'IF', -}; -const ALLOWANCE_APPROVAL_REQ = (chainID: number) => - ({ - type: 'ALLOWANCE_USER_APPROVAL', - typeID: `AUA_${chainID}`, - }) as const; - -const ALLOWANCE_APPROVAL_MINED = (chainID: number) => ({ - type: 'ALLOWANCE_APPROVAL_MINED', - typeID: `AAM_${chainID}`, -}); -const ALLOWANCE_COMPLETE = { - type: 'ALLOWANCE_ALL_DONE', - typeID: 'AAD', -}; - -const INTENT_DEPOSIT_REQ = (id: number) => ({ - type: 'INTENT_DEPOSIT', - typeID: `ID_${id}`, -}); - -const INTENT_DEPOSITS_CONFIRMED = { - type: 'INTENT_DEPOSITS_CONFIRMED', - typeID: 'UIDC', -}; - -const INTENT_COLLECTION_COMPLETE = { - type: 'INTENT_COLLECTION_COMPLETE', - typeID: 'ICC', -}; -const INTENT_COLLECTION = (id: number) => ({ - type: 'INTENT_COLLECTION', - typeID: `IC_${id}`, -}); - -const INTENT_FINISH_STEPS = [INTENT_FULFILLED]; - -const createSteps = ( - intent: Intent, - chainList: ChainListType, - unallowedSources?: onAllowanceHookSource[], -) => { - const steps: Step[] = []; - - steps.push(INTENT_ACCEPTED); - if (unallowedSources && unallowedSources?.length > 0) { - for (const source of unallowedSources) { - steps.push( - { - ...ALLOWANCE_APPROVAL_REQ(source.chain.id), - data: { - chainID: source.chain.id, - chainName: source.chain.name, - }, - }, - { - ...ALLOWANCE_APPROVAL_MINED(source.chain.id), - data: { - chainID: source.chain.id, - chainName: source.chain.name, - }, - }, - ); - } - steps.push(ALLOWANCE_COMPLETE); - } - - steps.push(...INTENT_INIT_STEPS); - - const sources = intent.sources.filter((s) => s.chainID !== intent.destination.chainID); - - let collections = 0, - deposits = 0; - for (const [i, s] of sources.entries()) { - const isNative = isNativeAddress(s.universe, s.tokenContract); - if (isNative) { - deposits++; - const chain = chainList.getChainByID(s.chainID); - if (!chain) { - throw new Error(`Unknown chain ID ${s.chainID} while building steps`); - } - - steps.push({ - ...INTENT_DEPOSIT_REQ(i + 1), - data: { - amount: s.amount.toString(), - chainID: chain.id, - chainName: chain.name, - symbol: chain.nativeCurrency.symbol, - }, - }); - } else { - collections++; - steps.push({ - ...INTENT_COLLECTION(i + 1), - data: { - confirmed: i + 1, - total: sources.length, - }, - }); - } - } - - if (collections > 0) { - steps.push(INTENT_COLLECTION_COMPLETE); - } - - if (deposits > 0) { - steps.push(INTENT_DEPOSITS_CONFIRMED); - } - - steps.push(...INTENT_FINISH_STEPS); - return steps; -}; - -export { - ALLOWANCE_APPROVAL_MINED, - ALLOWANCE_APPROVAL_REQ, - ALLOWANCE_COMPLETE, - createSteps, - INTENT_ACCEPTED, - INTENT_COLLECTION, - INTENT_COLLECTION_COMPLETE, - INTENT_DEPOSIT_REQ, - INTENT_DEPOSITS_CONFIRMED, - INTENT_FULFILLED, - INTENT_HASH_SIGNED, - INTENT_SUBMITTED, -}; diff --git a/packages/core/sdk/ca-base/swap/errors.ts b/packages/core/sdk/ca-base/swap/errors.ts deleted file mode 100644 index fdd83255..00000000 --- a/packages/core/sdk/ca-base/swap/errors.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Hex } from 'viem'; - -const ErrorChainDataNotFound = new Error('Chain data not found.'); -const ErrorCOTNotFound = (chainID: number) => new Error(`COT not found on chain: ${chainID}`); -const ErrorTokenNotFound = (address: Hex, chainID: number) => - new Error(`Token(${address}) not found on chain: ${chainID}`); -const ErrorSingleSourceHasNoSource = new Error('Single source swap has input source missing.'); -const ErrorInsufficientBalance = (available: string, required: string) => - new Error(`Insufficient balance: available:${available}, required:${required}.`); - -export { - ErrorChainDataNotFound, - ErrorCOTNotFound, - ErrorInsufficientBalance, - ErrorSingleSourceHasNoSource, - ErrorTokenNotFound, -}; diff --git a/packages/core/sdk/ca-base/utils/index.ts b/packages/core/sdk/ca-base/utils/index.ts deleted file mode 100644 index fa1d6d2b..00000000 --- a/packages/core/sdk/ca-base/utils/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./api.utils"; -export * from "./common.utils"; -export * from "./contract.utils"; -export * from "./cosmos.utils"; -export * from "./rff.utils"; diff --git a/packages/core/sdk/index.ts b/packages/core/sdk/index.ts deleted file mode 100644 index b08308ac..00000000 --- a/packages/core/sdk/index.ts +++ /dev/null @@ -1,279 +0,0 @@ -// src/core/sdk/index.ts -import { NexusUtils } from './utils'; -import { initializeSimulationClient } from '../integrations/tenderly'; -import type { - BridgeParams, - BridgeResult, - TransferParams, - TransferResult, - AllowanceResponse, - OnIntentHook, - OnAllowanceHook, - EthereumProvider, - RequestArguments, - UserAsset, - SimulationResult, - RequestForFunds, - NexusNetwork, - BridgeAndExecuteParams, - BridgeAndExecuteResult, - ExecuteParams, - ExecuteResult, - ExecuteSimulation, - BridgeAndExecuteSimulationResult, - SwapResult, - SupportedChainsResult, - ExactInSwapInput, - SwapInputOptionalParams, - ExactOutSwapInput, -} from '@nexus/commons'; -import { logger } from '@nexus/commons'; -import SafeEventEmitter from '@metamask/safe-event-emitter'; -import { CA } from './ca-base'; -import { ChainAbstractionAdapter } from '../adapters/chain-abstraction-adapter'; - -export class NexusSDK extends CA { - private readonly nexusAdapter: ChainAbstractionAdapter; - public readonly nexusEvents: SafeEventEmitter; - public readonly utils: NexusUtils; - - constructor(config?: { network?: NexusNetwork; debug?: boolean }) { - super(config); - logger.debug('Nexus SDK initialized with config:', config); - this.nexusAdapter = new ChainAbstractionAdapter(this); - this.nexusEvents = this._caEvents; - this.utils = new NexusUtils(this.nexusAdapter, () => this.isInitialized()); - } - - /** - * Initialize the SDK with a provider - */ - public async initialize(provider: EthereumProvider): Promise { - // Initialize the core adapter first - this._setEVMProvider(provider); - await this._init(); - const BACKEND_URL = 'https://nexus-backend.avail.so'; - if (BACKEND_URL) { - try { - const initResult = await initializeSimulationClient(BACKEND_URL); - if (!initResult.success) { - throw new Error('Backend initialization failed'); - } - } catch (error) { - throw new Error('Backend initialization failed'); - } - } - } - - /** - * Get unified balances across all chains - */ - public async getUnifiedBalances(includeSwappableBalances = false): Promise { - return this._getUnifiedBalances(includeSwappableBalances); - } - - /** - * Get unified balance for a specific token - */ - public async getUnifiedBalance( - symbol: string, - includeSwappableBalances = false, - ): Promise { - return this._getUnifiedBalance(symbol, includeSwappableBalances); - } - - /** - * Cross chain token transfer - */ - public async bridge(params: BridgeParams): Promise { - try { - const result = await (await this._bridge(params)).exec(); - return { - success: true, - explorerUrl: result?.explorerURL ?? '', - }; - } catch (e) { - return { - success: false, - error: e instanceof Error ? e.message : String(e), - }; - } - } - - /** - * Cross chain token transfer to EOA - */ - public async transfer(params: TransferParams): Promise { - try { - const result = await (await this._transfer({ ...params, to: params.recipient })).exec(); - return { - success: true, - transactionHash: result.hash, - explorerUrl: result.explorerURL, - }; - } catch (e) { - return { - success: false, - error: e instanceof Error ? e.message : String(e), - }; - } - } - - public async swapWithExactIn( - input: ExactInSwapInput, - options?: SwapInputOptionalParams, - ): Promise { - try { - const result = await this._swapWithExactIn(input, options); - return { - success: true, - result, - }; - } catch (error) { - console.error('Error in swap with exact out', error); - return { - success: false, - error: error instanceof Error ? error.message : String(error), - }; - } - } - - public async swapWithExactOut( - input: ExactOutSwapInput, - options?: SwapInputOptionalParams, - ): Promise { - try { - const result = await this._swapWithExactOut(input, options); - return { - success: true, - result, - }; - } catch (error) { - console.error('Error in swap with exact out', error); - return { - success: false, - error: error instanceof Error ? error.message : String(error), - }; - } - } - - /** - * Get chain abstracted provider allowing use of chain asbtraction - * @returns EthereumProvider - */ - - public getEVMProviderWithCA(): EthereumProvider { - return this._getEVMProviderWithCA(); - } - - /** - * Simulate bridge transaction to get costs and fees - */ - public async simulateBridge(params: BridgeParams): Promise { - return (await this._bridge(params)).simulate(); - } - - /** - * Simulate transfer transaction to get costs and fees - */ - public async simulateTransfer(params: TransferParams): Promise { - return (await this._transfer({ ...params, to: params.recipient })).simulate(); - } - - /** - * Get user's intents with pagination - */ - public async getMyIntents(page: number = 1): Promise { - return this._getMyIntents(page); - } - - /** - * Check allowance for tokens on a specific chain - */ - public async getAllowance(chainId?: number, tokens?: string[]): Promise { - return this._allowance().get({ chainID: chainId, tokens }); - } - - /** - * Set allowance for a token on a specific chain - */ - public async setAllowance(chainId: number, tokens: string[], amount: bigint): Promise { - return this._allowance().set({ chainID: chainId, tokens, amount }); - } - - /** - * Revoke allowance for a token on a specific chain - */ - public async revokeAllowance(chainId: number, tokens: string[]): Promise { - return this._allowance().revoke({ chainID: chainId, tokens }); - } - - /** - * Set callback for intent status updates - */ - public setOnIntentHook(callback: OnIntentHook): void { - this._setOnIntentHook(callback); - } - - /** - * Set callback for allowance approval events - */ - public setOnAllowanceHook(callback: OnAllowanceHook): void { - this._setOnAllowanceHook(callback); - } - - public async deinit(): Promise { - return this._deinit(); - } - - public async request(args: RequestArguments): Promise { - return this._handleEVMTx(args); - } - - /** - * Standalone function to execute funds into a smart contract - * @param params execute parameters including contract details and transaction settings - * @returns Promise resolving to execute result with transaction hash and explorer URL - */ - public async execute(params: ExecuteParams): Promise { - return this.nexusAdapter.execute(params); - } - - /** - * Simulate a standalone execute to estimate gas costs and validate parameters - * @param params execute parameters for simulation - * @returns Promise resolving to simulation result with gas estimates - */ - public async simulateExecute(params: ExecuteParams): Promise { - return this.nexusAdapter.simulateExecute(params); - } - - /** - * Enhanced bridge and execute function with optional execute step and improved error handling - * @param params Enhanced bridge and execute parameters - * @returns Promise resolving to comprehensive operation result - */ - public async bridgeAndExecute(params: BridgeAndExecuteParams): Promise { - return this.nexusAdapter.bridgeAndExecute(params); - } - - /** - * Simulate bridge and execute operation using bridge output amounts for realistic execute cost estimation - * This method provides more accurate gas estimates by using the actual amount that will be - * received on the destination chain after bridging (accounting for fees, slippage, etc.) - * Includes detailed step-by-step breakdown with approval handling. - */ - public async simulateBridgeAndExecute( - params: BridgeAndExecuteParams, - ): Promise { - return this.nexusAdapter.simulateBridgeAndExecute(params); - } - - public getSwapSupportedChainsAndTokens(): SupportedChainsResult { - return this._getSwapSupportedChainsAndTokens(); - } - - public isInitialized() { - return this._isInitialized(); - } -} diff --git a/packages/core/sdk/utils.ts b/packages/core/sdk/utils.ts deleted file mode 100644 index 5ff18dc3..00000000 --- a/packages/core/sdk/utils.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - type SUPPORTED_CHAINS, - formatBalance as utilFormatBalance, - parseUnits as utilParseUnits, - formatUnits as utilFormatUnits, - isValidAddress as utilIsValidAddress, - truncateAddress as utilTruncateAddress, - chainIdToHex as utilChainIdToHex, - hexToChainId as utilHexToChainId, - getMainnetTokenMetadata as utilGetMainnetTokenMetadata, - getTestnetTokenMetadata as utilGetTestnetTokenMetadata, - getTokenMetadata as utilGetTokenMetadata, - getChainMetadata as utilGetChainMetadata, - formatTokenAmount as utilFormatTokenAmount, - formatTestnetTokenAmount as utilFormatTestnetTokenAmount, - SupportedChainsResult, - Network, -} from '@nexus/commons'; -import { ChainAbstractionAdapter } from '../adapters/chain-abstraction-adapter'; - -export class NexusUtils { - constructor( - private readonly adapter: ChainAbstractionAdapter, - private readonly isReady: () => boolean, - ) {} - - private ensureInitialized(): void { - if (!this.isReady()) { - throw new Error( - 'NexusSDK must be initialized before using utils methods that require adapter access. Call sdk.initialize() first.', - ); - } - } - - // Pure utility functions (no adapter dependency) - formatBalance = utilFormatBalance; - parseUnits = utilParseUnits; - formatUnits = utilFormatUnits; - isValidAddress = utilIsValidAddress; - truncateAddress = utilTruncateAddress; - chainIdToHex = utilChainIdToHex; - hexToChainId = utilHexToChainId; - getMainnetTokenMetadata = utilGetMainnetTokenMetadata; - getTestnetTokenMetadata = utilGetTestnetTokenMetadata; - getTokenMetadata = utilGetTokenMetadata; - getChainMetadata = utilGetChainMetadata; - formatTokenAmount = utilFormatTokenAmount; - formatTestnetTokenAmount = utilFormatTestnetTokenAmount; - - getSupportedChains(env?: Network): SupportedChainsResult { - this.ensureInitialized(); - return this.adapter.getSupportedChains(env); - } - - getSwapSupportedChainsAndTokens(): SupportedChainsResult { - this.ensureInitialized(); - return this.adapter.nexusSDK.getSwapSupportedChainsAndTokens(); - } - - /* Same for isSupportedChain / isSupportedToken */ - - isSupportedChain(chainId: (typeof SUPPORTED_CHAINS)[keyof typeof SUPPORTED_CHAINS]): boolean { - this.ensureInitialized(); - return this.adapter.isSupportedChain(chainId); - } - - isSupportedToken(token: string): boolean { - this.ensureInitialized(); - return this.adapter.isSupportedToken(token); - } -} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json deleted file mode 100644 index a4e45bda..00000000 --- a/packages/core/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "baseUrl": ".", - "paths": { - "@nexus/commons": ["../commons/index.ts"], - "@nexus/commons/*": ["../commons/*"] - } - }, - "include": ["**/*.ts", "**/*.d.ts"], - "exclude": ["dist", "node_modules"] -} diff --git a/packages/widgets/README.md b/packages/widgets/README.md deleted file mode 100644 index af29f1c1..00000000 --- a/packages/widgets/README.md +++ /dev/null @@ -1,758 +0,0 @@ -# @avail-project/nexus-widgets - -Ready-to-use React components for cross-chain transactions. Drop-in widgets that provide complete bridge, transfer, and execute flows with customizable styling. - -## Installation - -```bash -npm install @avail-project/nexus-widgets -``` - -**Required peer dependencies:** - -```bash -npm install react react-dom viem -``` - -## Quick Start - -### Wrap your app with `NexusProvider` - -```tsx -import { NexusProvider } from '@avail-project/nexus-widgets'; - -export default function App() { - return ( - - - - ); -} -``` - -### Forward the user's wallet provider - -```tsx -import { useEffect } from 'react'; -import { useAccount } from '@wagmi/react'; // any wallet lib works -import { useNexus } from '@avail-project/nexus-widgets'; - -export function WalletBridge() { - const { connector, isConnected } = useAccount(); - const { setProvider } = useNexus(); - - useEffect(() => { - if (isConnected && connector?.getProvider) { - connector.getProvider().then(setProvider); - } - }, [isConnected, connector, setProvider]); - - return null; -} -``` - -### Alternative: Manual SDK Initialization - -For developers who need to use SDK methods directly (like `getUnifiedBalances`) before using UI components: - -```tsx -import { useNexus } from '@avail-project/nexus-widgets'; - -function MyComponent() { - const { initializeSdk, sdk, isSdkInitialized } = useNexus(); - - const handleInitialize = async () => { - const provider = await window.ethereum; // or get from your wallet library - await initializeSdk(provider); // Initializes both SDK and UI state - - // Now you can use SDK methods directly - // false by default to get CA applicable token balances - // true to get all the balances including the swappable tokens - const balances = await sdk.getUnifiedBalances(); - console.log('Balances:', balances); - - // UI components will already be initialized when used - }; - - return ( - - ); -} -``` - -**Benefits of manual initialization:** - -- Use SDK methods immediately after initialization -- No duplicate initialization when UI components are used -- Full control over initialization timing -- Access to unified balances and other SDK features before transactions - -### Use Widgets - -```tsx -import { - BridgeButton, - TransferButton, - BridgeAndExecuteButton, - SwapButton, - TOKEN_CONTRACT_ADDRESSES, - TOKEN_METADATA, - SUPPORTED_CHAINS, - DESTINATION_SWAP_TOKENS, - type SUPPORTED_TOKENS, - type SUPPORTED_CHAIN_IDS -} from '@avail-project/nexus-widgets'; -import { parseUnits } from 'viem'; - -/* Bridge ----------------------------------------------------------- */ - - {({ onClick, isLoading }) => ( - - )} - - -/* Transfer --------------------------------------------------------- */ - - {({ onClick }) => Send Funds} - - -/* Bridge + Execute ------------------------------------------------- */ - { - const decimals = TOKEN_METADATA[token].decimals - const amountWei = parseUnits(amount, decimals) - const tokenAddr = TOKEN_CONTRACT_ADDRESSES[token][_chainId] - return { functionParams: [tokenAddr, amountWei, user, 0] } - }} - prefill={{ - toChainId: 42161, - token: 'USDT', - }} - > - {({ onClick, isLoading }) => ( - - )} - - -/* Swap | EXACT_IN only ------------------------------------------------------------- */ - - {({ onClick, isLoading }) => ( - - )} - -``` - -## Component APIs - -### `BridgeButton` - -Bridge tokens between chains with a customizable button interface. - -```tsx -interface BridgeButtonProps { - title?: string; // Will appear once intialization is completed - prefill?: Partial; // chainId, token, amount - className?: string; - children(props: { onClick(): void; isLoading: boolean }): React.ReactNode; -} -``` - -**Example:** - -```tsx - - {({ onClick, isLoading }) => ( - - )} - -``` - -### `TransferButton` - -Send tokens to any address with automatic optimization (direct transfer when possible). - -```tsx -interface TransferButtonProps { - title?: string; // Will appear once intialization is completed - prefill?: Partial; // chainId, token, amount, recipient - className?: string; - children(props: { onClick(): void; isLoading: boolean }): React.ReactNode; -} -``` - -**Example:** - -```tsx - - {({ onClick, isLoading }) => ( - - )} - -``` - -### `BridgeAndExecuteButton` - -Bridge tokens and execute a smart contract function in a single flow. - -```tsx -type DynamicParamBuilder = ( - token: SUPPORTED_TOKENS, - amount: string, - chainId: SUPPORTED_CHAINS_IDS, - userAddress: `0x${string}`, -) => { - functionParams: readonly unknown[]; - value?: string; // wei; defaults to "0" -}; - -interface BridgeAndExecuteButtonProps { - title?: string; // Will appear once intialization is completed - contractAddress: `0x${string}`; // REQUIRED - contractAbi: Abi; // REQUIRED - functionName: string; // REQUIRED - buildFunctionParams: DynamicParamBuilder; // REQUIRED - prefill?: { toChainId?: number; token?: SUPPORTED_TOKENS; amount?: string }; - className?: string; - children(props: { onClick(): void; isLoading: boolean; disabled: boolean }): React.ReactNode; -} -``` - -**Example - Aave Supply:** - -```tsx - { - const decimals = TOKEN_METADATA[token].decimals; - const amountWei = parseUnits(amount, decimals); - const tokenAddress = TOKEN_CONTRACT_ADDRESSES[token][chainId]; - return { - functionParams: [tokenAddress, amountWei, userAddress, 0], - }; - }} - prefill={{ toChainId: 1, token: 'USDC' }} -> - {({ onClick, isLoading, disabled }) => ( - - )} - -``` - -`buildFunctionParams` receives the validated UX input (token, amount, destination chainId) plus the **connected wallet address** and must return the encoded `functionParams` (and optional ETH `value`) used in the destination call. - -Nexus then: - -1. Bridges the asset to `toChainId` -2. Sets ERC-20 allowance if required -3. Executes `contractAddress.functionName(functionParams, { value })` - -### `SwapButton` - -Cross-chain token swapping with support for both EXACT_IN and EXACT_OUT modes. - -```tsx -interface SwapButtonProps { - title?: string; // Will appear once initialization is completed - prefill?: Omit; // fromChainID, toChainID, fromTokenAddress, toTokenAddress, fromAmount - className?: string; - children(props: { onClick(): void; isLoading: boolean }): React.ReactNode; -} - -interface SwapInputData { - fromChainID?: number; - toChainID?: number; - fromTokenAddress?: string; - toTokenAddress?: string; - fromAmount?: string | number; - toAmount?: string | number; -} -``` - -**EXACT_IN Swap Example:** - -```tsx -// Swap exactly 100 USDC from Polygon to LDO on Arbitrum - - {({ onClick, isLoading }) => ( - - )} - -``` - -## Swap Utilities - -### Discovering Available Swap Options - -```tsx -import { useNexus, DESTINATION_SWAP_TOKENS } from '@avail-project/nexus-widgets'; - -function SwapOptionsExample() { - const { sdk, isSdkInitialized } = useNexus(); - const [swapOptions, setSwapOptions] = useState(null); - - useEffect(() => { - if (isSdkInitialized) { - // Get supported source chains and tokens for swaps - const supportedOptions = sdk.utils.getSwapSupportedChainsAndTokens(); - setSwapOptions(supportedOptions); - } - }, [sdk, isSdkInitialized]); - - return ( -
- {/* Source chains and tokens */} - {swapOptions?.map(chain => ( -
-

{chain.name} (Chain ID: {chain.id})

- {chain.tokens.map(token => ( -
- {token.symbol}: {token.tokenAddress} -
- ))} -
- ))} - - {/* Popular destination options */} -

Popular Destinations:

- {Array.from(DESTINATION_SWAP_TOKENS.entries()).map(([chainId, tokens]) => ( -
-

Chain {chainId}

- {tokens.map(token => ( -
- {token.symbol} - {token.name} -
- ))} -
- ))} -
- ); -} -``` - -**Key Points:** -- **Source restrictions**: Source chains/tokens are limited to what `getSwapSupportedChainsAndTokens()` returns -- **Destination flexibility**: Destination can be any supported chain and token address -- **DESTINATION_SWAP_TOKENS**: Provides popular destination options for UI building, but is not exhaustive - -## Prefill Behavior - -| Widget | Supported keys | Locked in UI | -| ------------------------ | ------------------------------------------------------------------ | ------------ | -| `BridgeButton` | `chainId`, `token`, `amount` | βœ… | -| `TransferButton` | `chainId`, `token`, `amount`, `recipient` | βœ… | -| `BridgeAndExecuteButton` | `toChainId`, `token`, `amount` | βœ… | -| `SwapButton` | `fromChainID`, `toChainID`, `fromTokenAddress`, `toTokenAddress`, `fromAmount`, `toAmount` | βœ… | - -Values passed in `prefill` appear as **read-only** fields, enforcing your desired flow. - -## πŸ“± Widget Examples - -### Cross-Chain Swapping - -```tsx -// Multi-step cross-chain swap with destination selection - - {({ onClick, isLoading }) => ( -
-

Cross-Chain Swap

-

Swap tokens across any supported chains

- -
- )} -
- -// Fixed-route swap for arbitrage or specific pairs - - {({ onClick, isLoading }) => ( -
-

Arbitrage Opportunity

-

Better WETH rates on Optimism

- -
- )} -
-``` - -### DeFi Protocol Integration - -```tsx -// Compound V3 Supply Widget - { - const decimals = TOKEN_METADATA[token].decimals; - const amountWei = parseUnits(amount, decimals); - const tokenAddress = TOKEN_CONTRACT_ADDRESSES[token][chainId]; - return { - functionParams: [tokenAddress, amountWei], - }; - }} - prefill={{ toChainId: 1, token: 'USDC' }} -> - {({ onClick, isLoading }) => ( -
-

Earn with Compound

- -
- )} -
-``` - -### Simple Payment Flow - -```tsx -// Payment button for e-commerce - - {({ onClick, isLoading }) => ( - - )} - -``` - -### Multi-Chain Liquidity - -```tsx -// Bridge to specific chain for better rates - - {({ onClick, isLoading }) => ( -
-

Better rates on Arbitrum

-

Save 60% on gas fees

- -
- )} -
-``` - -## Advanced Usage - -### Custom Loading States - -```tsx - - {({ onClick, isLoading }) => ( - - )} - -``` - -### Error Handling - -```tsx -function MyBridgeComponent() { - const [error, setError] = useState(null); - - return ( -
- {error &&
{error}
} - - - {({ onClick, isLoading }) => ( - - )} - -
- ); -} -``` - -### Access to SDK Methods - -```tsx -function BalanceAwareWidget() { - const { sdk, isSdkInitialized } = useNexus(); - const [balances, setBalances] = useState([]); - - useEffect(() => { - if (isSdkInitialized) { - sdk.getUnifiedBalances().then(setBalances); - } - }, [sdk, isSdkInitialized]); - - return ( -
-
- {balances.map((balance) => ( -
- {balance.symbol}: {balance.balance} -
- ))} -
- - - {({ onClick, isLoading }) => ( - - )} - -
- ); -} -``` - -## Best Practices - -### 1. Always simulate first - -```tsx -// Good: Let the widget handle simulation internally - - {({ onClick, isLoading }) => ( - - )} - -``` - -### 2. Handle loading states gracefully - -```tsx -// Good: Provide clear feedback - - {({ onClick, isLoading }) => ( - - )} - -``` - -### 3. Use appropriate confirmation levels - -```tsx -// Good: For high-value transactions, the widget will automatically -// request higher confirmation levels - - {/* Widget handles confirmation requirements */} - -``` - -### 4. Clean up resources - -```tsx -function MyComponent() { - const { deinitializeSdk } = useNexus(); - - useEffect(() => { - return () => { - deinitializeSdk(); - }; - }, []); - - return {/* ... */}; -} -``` - -## Supported Networks & Tokens - -### Mainnet Chains - -| Network | Chain ID | Native Currency | Status | -| --------- | -------- | --------------- | ------ | -| Ethereum | 1 | ETH | βœ… | -| Optimism | 10 | ETH | βœ… | -| Polygon | 137 | MATIC | βœ… | -| Arbitrum | 42161 | ETH | βœ… | -| Avalanche | 43114 | AVAX | βœ… | -| Base | 8453 | ETH | βœ… | -| Scroll | 534352 | ETH | βœ… | -| Sophon | 50104 | SOPH | βœ… | -| Kaia | 8217 | KAIA | βœ… | -| BNB | 56 | BNB | βœ… | -| HyperEVM | 999 | HYPE | βœ… | - -### Testnet Chains - -| Network | Chain ID | Native Currency | Status | -| ---------------- | -------- | --------------- | ------ | -| Optimism Sepolia | 11155420 | ETH | βœ… | -| Polygon Amoy | 80002 | MATIC | βœ… | -| Arbitrum Sepolia | 421614 | ETH | βœ… | -| Base Sepolia | 84532 | ETH | βœ… | -| Sepolia | 11155111 | ETH | βœ… | -| Monad Testnet | 10143 | MON | βœ… | - -### Supported Tokens - -| Token | Name | Decimals | Networks | -| ----- | ---------- | -------- | -------------- | -| ETH | Ethereum | 18 | All EVM chains | -| USDC | USD Coin | 6 | All supported | -| USDT | Tether USD | 6 | All supported | - -## πŸ”— Links - -- [GitHub Repository](https://github.com/availproject/nexus-sdk) -- [API Documentation](https://docs.availproject.org/api-reference/avail-nexus-sdk) diff --git a/packages/widgets/package.json b/packages/widgets/package.json deleted file mode 100644 index 9b75999d..00000000 --- a/packages/widgets/package.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "name": "@avail-project/nexus-widgets", - "version": "0.0.6", - "description": "Nexus React components for cross-chain transactions", - "main": "./dist/index.js", - "module": "./dist/index.esm.js", - "types": "./dist/index.d.ts", - "files": [ - "dist", - "README.md" - ], - "scripts": { - "build": "rollup -c && pnpm run copy:commons", - "copy:commons": "mkdir -p dist/commons && cp -R ../commons/dist/* dist/commons/ || true", - "dev": "rollup -c -w", - "typecheck": "tsc --noEmit" - }, - "sideEffects": [ - "**/*.css" - ], - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.esm.js", - "require": "./dist/index.js" - } - }, - "keywords": [ - "nexus", - "react", - "components", - "widgets", - "blockchain", - "bridge", - "web3", - "ui" - ], - "author": "decocereus", - "license": "MIT", - "dependencies": { - "@lottiefiles/dotlottie-react": "0.14.2", - "@nexus/commons": "workspace:*", - "@avail-project/nexus-core": "workspace:*", - "class-variance-authority": "0.7.1", - "clsx": "2.1.1", - "decimal.js": "10.4.3", - "motion": "12.23.0", - "tailwind-merge": "3.3.1" - }, - "devDependencies": { - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "6.1.0", - "@rollup/plugin-node-resolve": "^15.0.0", - "@rollup/plugin-typescript": "^11.0.0", - "@tailwindcss/postcss": "4.1.10", - "@types/react": "19.1.8", - "@types/react-dom": "19.1.6", - "autoprefixer": "10.4.21", - "postcss": "8.5.6", - "postcss-import": "16.1.1", - "postcss-nesting": "13.0.2", - "rollup": "^4.0.0", - "rollup-plugin-dts": "^6.0.0", - "rollup-plugin-postcss": "4.0.2", - "rollup-plugin-typescript2": "0.36.0", - "tailwindcss": "4.1.10", - "typescript": "^5.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0", - "viem": "^2.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": false - }, - "react-dom": { - "optional": false - } - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/widgets/postcss.config.js b/packages/widgets/postcss.config.js deleted file mode 100644 index 52e338af..00000000 --- a/packages/widgets/postcss.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - plugins: { - 'postcss-import': {}, - '@tailwindcss/postcss': {}, - 'autoprefixer': {}, - }, -} \ No newline at end of file diff --git a/packages/widgets/rollup.config.mjs b/packages/widgets/rollup.config.mjs deleted file mode 100644 index 6023d136..00000000 --- a/packages/widgets/rollup.config.mjs +++ /dev/null @@ -1,127 +0,0 @@ -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import typescript from 'rollup-plugin-typescript2'; -import json from '@rollup/plugin-json'; -import alias from '@rollup/plugin-alias'; -import dts from 'rollup-plugin-dts'; -import postcss from 'rollup-plugin-postcss'; -import { defineConfig } from 'rollup'; -import { createRequire } from 'module'; - -const require = createRequire(import.meta.url); -const packageJson = require('./package.json'); - -const isProduction = process.env.NODE_ENV === 'production'; -const shouldGenerateSourceMaps = false; - -// Base configuration for widgets (includes React, CSS) -const baseConfig = { - input: 'src/index.ts', - plugins: [ - alias({ - entries: [{ find: '@nexus/commons', replacement: '../commons/dist/index.esm.js' }], - }), - json(), - resolve({ - browser: true, - preferBuiltins: false, - exportConditions: ['browser', 'module', 'import'], - dedupe: ['react', 'react-dom'], - }), - commonjs({ - include: /node_modules/, - transformMixedEsModules: true, - ignoreTryCatch: false, - }), - // PostCSS plugin for CSS processing - postcss({ - inject: true, - extract: false, - minimize: isProduction, - sourceMap: shouldGenerateSourceMaps, - modules: false, - config: { - path: './postcss.config.js', - }, - }), - typescript({ - tsconfig: './tsconfig.json', - useTsconfigDeclarationDir: true, - }), - ], - external: [ - // Peer dependencies that consumers should install - ...Object.keys(packageJson.peerDependencies || {}), - /^react/, - /^react-dom/, - /^viem/, - // External dependencies that consumers should install - '@lottiefiles/dotlottie-react', - /^motion/, - 'decimal.js', - '@avail-project/nexus-core', - ], - treeshake: { - moduleSideEffects: false, - propertyReadSideEffects: false, - unknownGlobalSideEffects: false, - }, -}; - -export default defineConfig([ - // Build configurations - { - ...baseConfig, - output: [ - { - file: 'dist/index.js', - format: 'cjs', - sourcemap: shouldGenerateSourceMaps, - exports: 'named', - interop: 'auto', - inlineDynamicImports: true, - // no path rewrite needed when source imports already use @avail-project/nexus-core - }, - { - file: 'dist/index.esm.js', - format: 'esm', - sourcemap: shouldGenerateSourceMaps, - exports: 'named', - inlineDynamicImports: true, - // no path rewrite needed when source imports already use @avail-project/nexus-core - }, - ], - }, - - // TypeScript declarations - { - input: 'src/index.ts', - output: [{ file: 'dist/index.d.ts', format: 'esm' }], - plugins: [ - resolve({ - browser: true, - preferBuiltins: false, - }), - dts({ exclude: ['**/*.css'] }), - // Rewrite import specifiers in generated d.ts - { - name: 'rewrite-commons-and-core-imports-dts', - renderChunk(code) { - return code - .replace(/@nexus\/commons\/constants/g, './commons/constants') - .replace(/@nexus\/commons/g, './commons'); - }, - }, - ], - external: [ - ...Object.keys(packageJson.peerDependencies || {}), - /^react/, - /^viem/, - '@lottiefiles/dotlottie-react', - /^motion/, - 'decimal.js', - '@avail-project/nexus-core', - /\.css$/, - ], - }, -]); diff --git a/packages/widgets/src/components/bridge-execute/bridge-execute-button.tsx b/packages/widgets/src/components/bridge-execute/bridge-execute-button.tsx deleted file mode 100644 index 9ef811bc..00000000 --- a/packages/widgets/src/components/bridge-execute/bridge-execute-button.tsx +++ /dev/null @@ -1,47 +0,0 @@ -'use client'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { logger } from '@nexus/commons'; -import BridgeAndExecuteModal from './bridge-execute-modal'; -import { BridgeAndExecuteButtonProps } from '../../types'; - -export function BridgeAndExecuteButton({ - contractAddress, - contractAbi, - functionName, - buildFunctionParams, - prefill, - children, - className, - title, -}: BridgeAndExecuteButtonProps) { - const { startTransaction, activeTransaction } = useInternalNexus(); - - const isLoading = - activeTransaction?.status === 'processing' || activeTransaction?.reviewStatus === 'simulating'; - - if (!contractAddress || !contractAbi || !functionName || !buildFunctionParams) { - logger.warn('BridgeAndExecuteButton: Missing required contract props or builder'); - return null; - } - - const handleClick = () => { - const transactionData = { - ...(prefill || {}), - contractAddress, - contractAbi, - functionName, - buildFunctionParams, - }; - - startTransaction('bridgeAndExecute', transactionData); - }; - - return ( - <> -
- {children({ onClick: handleClick, isLoading, disabled: false })} -
- - - ); -} diff --git a/packages/widgets/src/components/bridge-execute/bridge-execute-modal.tsx b/packages/widgets/src/components/bridge-execute/bridge-execute-modal.tsx deleted file mode 100644 index b982fd3c..00000000 --- a/packages/widgets/src/components/bridge-execute/bridge-execute-modal.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { UnifiedTransactionModal } from '../shared/unified-transaction-modal'; -import { type BridgeAndExecuteParams, type BridgeAndExecuteSimulationResult } from '@nexus/commons'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import PrefilledInputs from '../shared/prefilled-inputs'; - -type InputData = { - toChainId?: number; - token?: string; - amount?: string | number; -}; - -function BridgeExecuteForm({ - inputData, - onUpdate, - disabled, - prefillFields = {}, -}: { - inputData: any; - onUpdate: (data: BridgeAndExecuteParams) => void; - disabled: boolean; - prefillFields?: { - toChainId?: boolean; - token?: boolean; - amount?: boolean; - }; -}) { - const { activeController } = useInternalNexus(); - - if (!activeController) return null; - - const requiredFields: (keyof InputData)[] = ['toChainId', 'token', 'amount']; - const hasEnoughInputs = requiredFields.every((field) => prefillFields[field] === true); - - if (hasEnoughInputs) { - return ; - } - - const handleUpdate = (data: any) => { - if (data.toChainId !== undefined) { - const updateData = { ...data, chainId: data.toChainId }; - onUpdate(updateData); - } else { - onUpdate(data); - } - }; - - return ( - - ); -} - -export default function BridgeAndExecuteModal({ title = 'Nexus Widget' }: { title?: string }) { - const getSimulationError = (simulationResult: BridgeAndExecuteSimulationResult) => { - if (!simulationResult) return true; - - // Check if the overall simulation failed - if (simulationResult.success === false || simulationResult.error) { - return true; - } - const isBridgeSkipped = simulationResult.metadata?.bridgeSkipped; - - if (!isBridgeSkipped) { - if (!simulationResult.bridgeSimulation || !simulationResult.bridgeSimulation.intent) { - return true; - } - } - if (simulationResult.executeSimulation && !simulationResult.executeSimulation.success) { - return true; - } - - return false; - }; - - const getMinimumAmount = (simulationResult: BridgeAndExecuteSimulationResult) => { - // If bridge was skipped, use the input amount instead of bridge simulation data - if (simulationResult?.metadata?.bridgeSkipped) { - return simulationResult.metadata.inputAmount || '0'; - } - - const bridgeSim = simulationResult?.bridgeSimulation; - return bridgeSim?.intent?.sourcesTotal || '0'; - }; - - const getSourceChains = ( - simulationResult: BridgeAndExecuteSimulationResult & { - allowance?: { - chainDetails?: Array<{ chainId: number; amount: string; needsApproval: boolean }>; - }; - }, - ) => { - // Use chainDetails from allowance if available (provides needsApproval info) - if (simulationResult?.allowance?.chainDetails) { - return simulationResult.allowance.chainDetails; - } - - // If bridge was skipped, return empty array since there's no bridge routing - if (simulationResult?.metadata?.bridgeSkipped) { - return []; - } - - const bridgeSim = simulationResult?.bridgeSimulation; - return bridgeSim?.intent?.sources?.map((s) => ({ chainId: s.chainID, amount: s.amount })) || []; - }; - - const transformInputData = (inputData: any) => { - if (!inputData) return {}; - return { - ...inputData, - toChainId: (inputData as BridgeAndExecuteParams).toChainId, - }; - }; - - return ( - - ); -} diff --git a/packages/widgets/src/components/bridge/bridge-button.tsx b/packages/widgets/src/components/bridge/bridge-button.tsx deleted file mode 100644 index 13f178bb..00000000 --- a/packages/widgets/src/components/bridge/bridge-button.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; -import type { BridgeButtonProps } from '../../types'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import BridgeModal from './bridge-modal'; - -export function BridgeButton({ prefill, children, className, title }: BridgeButtonProps) { - const { startTransaction, activeTransaction } = useInternalNexus(); - const isLoading = - activeTransaction.status === 'processing' || activeTransaction.reviewStatus === 'simulating'; - - const handleClick = () => { - startTransaction('bridge', prefill); - }; - - return ( - <> -
{children({ onClick: handleClick, isLoading })}
- - - ); -} diff --git a/packages/widgets/src/components/bridge/bridge-modal.tsx b/packages/widgets/src/components/bridge/bridge-modal.tsx deleted file mode 100644 index 90273438..00000000 --- a/packages/widgets/src/components/bridge/bridge-modal.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { UnifiedTransactionModal } from '../shared/unified-transaction-modal'; -import { type SimulationResult } from '@nexus/commons'; -import { UnifiedTransactionForm, UnifiedInputData } from '../shared/unified-transaction-form'; -import PrefilledInputs from '../shared/prefilled-inputs'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -// BridgeConfig removed - using type casting - -type InputData = { - chainId?: number; - token?: string; - amount?: string | number; -}; - -interface BridgeFormSectionProps { - inputData: InputData; - onUpdate: (data: UnifiedInputData) => void; - disabled?: boolean; - className?: string; - prefillFields?: { - chainId?: boolean; - token?: boolean; - amount?: boolean; - }; -} - -function BridgeFormSection({ - inputData, - onUpdate, - disabled = false, - className, - prefillFields = {}, -}: BridgeFormSectionProps) { - const { activeController } = useInternalNexus(); - - if (!activeController) return null; - const requiredFields: (keyof InputData)[] = ['chainId', 'token', 'amount']; - const hasEnoughInputs = requiredFields.every((field) => prefillFields[field] === true); - - if (hasEnoughInputs) { - return ; - } - - return ( - - ); -} - -export default function BridgeModal({ title = 'Nexus Widget' }: { title?: string }) { - const getSimulationError = (simulationResult: SimulationResult) => { - return ( - simulationResult && - 'bridgeSimulation' in simulationResult && - !simulationResult.bridgeSimulation - ); - }; - - const getMinimumAmount = (simulationResult: SimulationResult) => { - return simulationResult?.intent?.sourcesTotal || '0'; - }; - - const getSourceChains = ( - simulationResult: SimulationResult & { - allowance?: { - chainDetails?: Array<{ chainId: number; amount: string; needsApproval: boolean }>; - }; - }, - ) => { - // Use chainDetails from allowance if available (provides needsApproval info) - if (simulationResult?.allowance?.chainDetails) { - return simulationResult.allowance.chainDetails; - } - - // Fallback to original sources mapping - return ( - simulationResult?.intent?.sources?.map((source) => ({ - chainId: source.chainID, - amount: source.amount, - })) || [] - ); - }; - - return ( - - ); -} diff --git a/packages/widgets/src/components/icons/AvailLogo.tsx b/packages/widgets/src/components/icons/AvailLogo.tsx deleted file mode 100644 index d626626a..00000000 --- a/packages/widgets/src/components/icons/AvailLogo.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -interface AvailLogoProps { - className?: string; -} - -export const AvailLogo: React.FC = ({ className = 'h-[88px] w-[191px]' }) => ( - - - - - -); diff --git a/packages/widgets/src/components/icons/CheckIcon.tsx b/packages/widgets/src/components/icons/CheckIcon.tsx deleted file mode 100644 index e7a49e34..00000000 --- a/packages/widgets/src/components/icons/CheckIcon.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -interface CheckIconProps { - className?: string; - size?: number; -} - -export const CheckIcon: React.FC = ({ className, size = 16 }) => ( - - - -); diff --git a/packages/widgets/src/components/icons/ChevronDownIcon.tsx b/packages/widgets/src/components/icons/ChevronDownIcon.tsx deleted file mode 100644 index d7d1115c..00000000 --- a/packages/widgets/src/components/icons/ChevronDownIcon.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -interface ChevronDownIconProps { - className?: string; - size?: number; -} - -export const ChevronDownIcon: React.FC = ({ className, size = 16 }) => ( - - - -); diff --git a/packages/widgets/src/components/icons/ChevronUpIcon.tsx b/packages/widgets/src/components/icons/ChevronUpIcon.tsx deleted file mode 100644 index 39e0dad7..00000000 --- a/packages/widgets/src/components/icons/ChevronUpIcon.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -interface ChevronUpIconProps { - className?: string; - size?: number; -} - -export const ChevronUpIcon: React.FC = ({ className, size = 16 }) => ( - - - -); diff --git a/packages/widgets/src/components/icons/CircleX.tsx b/packages/widgets/src/components/icons/CircleX.tsx deleted file mode 100644 index c3d75db3..00000000 --- a/packages/widgets/src/components/icons/CircleX.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -interface CircleXProps { - className?: string; - size?: number; -} - -export const CircleX: React.FC = ({ className, size = 16 }) => ( - - - - - -); diff --git a/packages/widgets/src/components/icons/Clock.tsx b/packages/widgets/src/components/icons/Clock.tsx deleted file mode 100644 index 48027667..00000000 --- a/packages/widgets/src/components/icons/Clock.tsx +++ /dev/null @@ -1,14 +0,0 @@ -const Clock = () => { - return ( - - - - ); -}; - -export default Clock; diff --git a/packages/widgets/src/components/icons/ExternalLink.tsx b/packages/widgets/src/components/icons/ExternalLink.tsx deleted file mode 100644 index 3e071465..00000000 --- a/packages/widgets/src/components/icons/ExternalLink.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; - -interface ExternalLinkProps { - className?: string; - size?: number; -} - -export const ExternalLink: React.FC = ({ className, size = 16 }) => ( - - - - - -); diff --git a/packages/widgets/src/components/icons/Maximize.tsx b/packages/widgets/src/components/icons/Maximize.tsx deleted file mode 100644 index d28e945f..00000000 --- a/packages/widgets/src/components/icons/Maximize.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -interface MaximizeProps { - className?: string; - size?: number; -} - -export const Maximize: React.FC = ({ className, size = 24 }) => ( - - - - - -); diff --git a/packages/widgets/src/components/icons/Minimize.tsx b/packages/widgets/src/components/icons/Minimize.tsx deleted file mode 100644 index 4d3ee50b..00000000 --- a/packages/widgets/src/components/icons/Minimize.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; - -interface MinimizeProps { - className?: string; - size?: number; -} - -export const Minimize: React.FC = ({ className, size = 16 }) => ( - -); diff --git a/packages/widgets/src/components/icons/MoneyCircles.tsx b/packages/widgets/src/components/icons/MoneyCircles.tsx deleted file mode 100644 index 3134a6df..00000000 --- a/packages/widgets/src/components/icons/MoneyCircles.tsx +++ /dev/null @@ -1,22 +0,0 @@ -const MoneyCircles = () => { - return ( - - - - - ); -}; - -export default MoneyCircles; diff --git a/packages/widgets/src/components/icons/Plus.tsx b/packages/widgets/src/components/icons/Plus.tsx deleted file mode 100644 index 2aeb6653..00000000 --- a/packages/widgets/src/components/icons/Plus.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -interface PlusProps { - className?: string; - size?: number; -} - -export const Plus: React.FC = ({ className, size = 16 }) => ( - - - - -); diff --git a/packages/widgets/src/components/icons/SmallAvailLogo.tsx b/packages/widgets/src/components/icons/SmallAvailLogo.tsx deleted file mode 100644 index 013fa332..00000000 --- a/packages/widgets/src/components/icons/SmallAvailLogo.tsx +++ /dev/null @@ -1,550 +0,0 @@ -import React from 'react'; - -interface SmallAvailLogoProps { - className?: string; -} - -export const SmallAvailLogo: React.FC = ({ - className = 'w-[58px] h-[16px]', -}) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/packages/widgets/src/components/icons/SolarWallet.tsx b/packages/widgets/src/components/icons/SolarWallet.tsx deleted file mode 100644 index 37afdd26..00000000 --- a/packages/widgets/src/components/icons/SolarWallet.tsx +++ /dev/null @@ -1,18 +0,0 @@ -const SolarWallet = () => { - return ( - - - - - ); -}; - -export default SolarWallet; diff --git a/packages/widgets/src/components/icons/TwoCircles.tsx b/packages/widgets/src/components/icons/TwoCircles.tsx deleted file mode 100644 index b309794a..00000000 --- a/packages/widgets/src/components/icons/TwoCircles.tsx +++ /dev/null @@ -1,15 +0,0 @@ -const TwoCircles = () => { - return ( - - - - ); -}; - -export default TwoCircles; diff --git a/packages/widgets/src/components/icons/index.ts b/packages/widgets/src/components/icons/index.ts deleted file mode 100644 index 1dd4b4a2..00000000 --- a/packages/widgets/src/components/icons/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { Minimize } from './Minimize'; -export { CircleX } from './CircleX'; -export { ExternalLink } from './ExternalLink'; -export { CheckIcon } from './CheckIcon'; -export { ChevronDownIcon } from './ChevronDownIcon'; -export { ChevronUpIcon } from './ChevronUpIcon'; -export { Maximize } from './Maximize'; -export { Plus } from './Plus'; -export { AvailLogo } from './AvailLogo'; -export { SmallAvailLogo } from './SmallAvailLogo'; diff --git a/packages/widgets/src/components/motion/base-modal.tsx b/packages/widgets/src/components/motion/base-modal.tsx deleted file mode 100644 index f478cd63..00000000 --- a/packages/widgets/src/components/motion/base-modal.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { cn } from '../../utils/utils'; -import type { ModalProps } from '../../types'; -import { Dialog, DialogContent } from './dialog-motion'; -import { motion } from 'motion/react'; - -export function BaseModal({ isOpen, onClose, children, className }: Readonly) { - return ( - - - - {children} - - - - ); -} diff --git a/packages/widgets/src/components/motion/button-motion.tsx b/packages/widgets/src/components/motion/button-motion.tsx deleted file mode 100644 index d183909e..00000000 --- a/packages/widgets/src/components/motion/button-motion.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from 'react'; -import { motion } from 'motion/react'; -import { cn } from '../../utils/utils'; - -interface ButtonProps - extends Omit< - React.ButtonHTMLAttributes, - 'onDrag' | 'onDragEnd' | 'onDragStart' | 'onAnimationStart' | 'onAnimationEnd' - > { - variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'custom'; - size?: 'default' | 'sm' | 'lg' | 'icon' | 'custom'; - asChild?: boolean; - ref?: React.Ref; -} - -const buttonVariants = { - default: 'bg-nexus-primary-gray text-nexus-primary-foreground hover:bg-nexus-primary/90', - destructive: - 'bg-nexus-destructive text-nexus-destructive-foreground hover:bg-nexus-destructive/90', - outline: - 'border border-nexus-input bg-nexus-background hover:bg-nexus-accent hover:text-nexus-accent-foreground', - secondary: 'bg-nexus-secondary text-nexus-secondary-foreground hover:bg-nexus-secondary/80', - ghost: 'hover:bg-nexus-accent hover:text-nexus-accent-foreground', - link: 'text-nexus-primary underline-offset-4 hover:underline', - custom: '', -}; - -const buttonSizes = { - default: 'h-10 px-4 py-2', - sm: 'h-9 rounded-nexus-md px-3', - lg: 'h-11 rounded-nexus-md px-8', - icon: 'h-10 w-10', - custom: '', -}; - -const Button = React.forwardRef( - ({ className, variant = 'default', size = 'default', asChild = false, ...props }, ref) => { - return ( - - ); - }, -); - -Button.displayName = 'Button'; - -export { Button }; diff --git a/packages/widgets/src/components/motion/dialog-motion.tsx b/packages/widgets/src/components/motion/dialog-motion.tsx deleted file mode 100644 index 333bb4f6..00000000 --- a/packages/widgets/src/components/motion/dialog-motion.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import * as React from 'react'; -import { motion, AnimatePresence } from 'motion/react'; -import { cn } from '../../utils/utils'; -import { createPortal } from 'react-dom'; - -interface DialogContextType { - open: boolean; - onOpenChange: (open: boolean) => void; -} - -const DialogContext = React.createContext(null); - -function useDialog() { - const context = React.useContext(DialogContext); - if (!context) { - throw new Error('Dialog components must be used within a Dialog'); - } - return context; -} - -interface DialogProps { - open?: boolean; - onOpenChange?: (open: boolean) => void; - children: React.ReactNode; -} - -function Dialog({ open = false, onOpenChange, children }: Readonly) { - const handleOpenChange = React.useCallback( - (newOpen: boolean) => { - onOpenChange?.(newOpen); - }, - [onOpenChange], - ); - - const value = React.useMemo( - () => ({ open, onOpenChange: handleOpenChange }), - [open, handleOpenChange], - ); - - return {children}; -} - -interface DialogTriggerProps extends React.ButtonHTMLAttributes { - asChild?: boolean; -} - -function DialogTrigger({ asChild = false, onClick, ...props }: Readonly) { - const { onOpenChange } = useDialog(); - - const handleClick = React.useCallback( - (e: React.MouseEvent) => { - onClick?.(e); - onOpenChange(true); - }, - [onClick, onOpenChange], - ); - - if (asChild) { - return React.cloneElement(React.Children.only(props.children as React.ReactElement), { - onClick: handleClick, - 'data-slot': 'dialog-trigger', - } as any); - } - - return - )} - {(executionResult as BridgeAndExecuteResult)?.executeExplorerUrl && ( - - )} - - ); - } - - if (transactionType === 'swap') { - return ( -
- {explorerURLs?.source && ( - - )} - {explorerURLs?.destination && ( - - )} -
- ); - } - - if (explorerURL) { - return ( - - ); - } - - return null; - }, [transactionType, explorerURL, explorerURLs, executionResult]); - - return ( - <> - - { - lottieRef.current?.resize(); - }} - className="absolute top-16 left-1/2 -translate-x-1/2 pointer-events-none" - > -
- { - lottieRef.current = instance; - }} - /> -
-
- -
-
- {/* Chains Row */} - - {/* Sources */} -
-
- {Array.isArray(sourceChainMeta) && - sourceChainMeta - .slice(0, 3) - .map((chain, index) => ( - {chain?.name 0 ? '-ml-5' : '', - chain?.id !== SUPPORTED_CHAINS.BASE && - chain?.id !== SUPPORTED_CHAINS.BASE_SEPOLIA - ? 'rounded-nexus-full' - : '', - )} - style={{ zIndex: (sourceChainMeta?.length || 0) - index }} - /> - ))} -
-
-

- {sourceAmount()} -

-

- From {sourceChainMeta?.length ?? 0} chain - {(sourceChainMeta?.length ?? 0) > 1 ? 's' : ''} -

-
-
- {/* Progress */} - -
-
- -
-
-
- {/* Destination */} -
- {destChainMeta ? ( - <> - - {destChainMeta?.name - -
-

- {destinationAmount()} -

-

- To {destChainMeta?.name ?? ''} -

-
- - ) : ( -
- )} -
- - {/* Text & timer */} - - {error ? ( - - ) : ( - <> -
- - {Math.floor(timer)} - - - . - - - {String(Math.floor((timer % 1) * 1000)).padStart(3, '0')}s - -
-
- -
-

- {description} -

- - )} - {/* Explorer links */} - {renderExplorerLinks()} -
-
-
- {/* Footer */} - -
- {status === 'success' && ( - - - - )} -
- Powered By - -
-
- - ); -}; diff --git a/packages/widgets/src/components/processing/processor-mini-card.tsx b/packages/widgets/src/components/processing/processor-mini-card.tsx deleted file mode 100644 index 61676d48..00000000 --- a/packages/widgets/src/components/processing/processor-mini-card.tsx +++ /dev/null @@ -1,268 +0,0 @@ -import React, { useCallback } from 'react'; -import { motion } from 'motion/react'; -import SuccessRipple from '../motion/success-ripple'; -import { Maximize, ExternalLink } from '../icons'; -import { type BridgeAndExecuteResult, SUPPORTED_CHAINS, TOKEN_METADATA } from '@nexus/commons'; -import { WordsPullUp } from '../motion/pull-up-words'; -import { cn } from '../../utils/utils'; -import { ThreeStageProgress } from '../motion/three-stage-progress'; -import { Button } from '../motion/button-motion'; -import { EnhancedInfoMessage } from '../shared/enhanced-info-message'; -import { ProcessorCardProps, SwapSimulationResult } from '../../types'; -import { TokenIcon } from '../shared/icons'; - -export const ProcessorMiniCard: React.FC = ({ - status, - toggleTransactionCollapse, - sourceChainMeta, - destChainMeta, - tokenMeta, - transactionType, - simulationResult, - processing, - explorerURL, - explorerURLs, - description, - error, - executionResult, -}: ProcessorCardProps) => { - const renderTokenIcon = useCallback(() => { - const progress = processing?.animationProgress ?? 0; - - if (transactionType !== 'swap') { - return ( - - ); - } - - const swapResult = simulationResult as SwapSimulationResult; - const destSymbol = swapResult?.intent?.destination?.token?.symbol?.toUpperCase(); - const destIcon = destSymbol ? TOKEN_METADATA[destSymbol]?.icon : undefined; - const sourceIcon = tokenMeta?.icon; - - return ( -
- - - - = 50 ? 1 : 0, scale: progress >= 50 ? 1 : 1.05 }} - transition={{ duration: 0.25 }} - > - - -
- ); - }, [ - processing?.animationProgress, - tokenMeta?.icon, - tokenMeta?.symbol, - transactionType, - simulationResult, - ]); - - return ( - - {/* Header */} -
-
- {/* Sources */} -
- {Array.isArray(sourceChainMeta) && - sourceChainMeta - .slice(0, 3) - .map((chain, index) => ( - {chain?.name 0 ? '-ml-3' : '', - chain?.id !== SUPPORTED_CHAINS.BASE && - chain?.id !== SUPPORTED_CHAINS.BASE_SEPOLIA - ? 'rounded-nexus-full' - : '', - )} - style={{ zIndex: (sourceChainMeta?.length || 0) - index }} - /> - ))} -
- - {/* Progress */} - - - - - {/* Destination */} - {destChainMeta ? ( - - {destChainMeta?.name} - - ) : ( -
- )} -
- -
- - {/* Body */} - {status === 'error' ? ( -
- -
- ) : ( -
- - - - {status === 'success' && - (() => { - if (transactionType === 'swap') { - if (explorerURLs?.destination) { - return ( - - ); - } - if (explorerURLs?.source) { - return ( - - ); - } - return null; - } - if (transactionType !== 'bridgeAndExecute') { - if (!explorerURL) return null; - return ( - - ); - } - const executeUrl = (executionResult as BridgeAndExecuteResult)?.executeExplorerUrl; - if (executeUrl) { - return ( - - ); - } - return null; - })()} - {status !== 'success' && ( -

- {description} -

- )} -
- )} - - ); -}; diff --git a/packages/widgets/src/components/processing/transaction-processor-shell.tsx b/packages/widgets/src/components/processing/transaction-processor-shell.tsx deleted file mode 100644 index 13ffa8fd..00000000 --- a/packages/widgets/src/components/processing/transaction-processor-shell.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import { useEffect, useMemo, useState, useRef, memo } from 'react'; -import { motion, AnimatePresence } from 'motion/react'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { ProcessorMiniCard } from './processor-mini-card'; -import { ProcessorFullCard } from './processor-full-card'; -import { - type BridgeAndExecuteSimulationResult, - type SimulationResult, - CHAIN_METADATA, - logger, - TOKEN_METADATA, -} from '@nexus/commons'; -import { getOperationText, getTokenFromInputData } from '../../utils/utils'; -import { useDragConstraints } from '../motion/drag-constraints'; -import { TransactionType, SwapSimulationResult } from '../../types'; - -const COLLAPSED = { width: 400, height: 120, radius: 16 } as const; -const EXPANDED = { width: 480, height: 500, radius: 16 } as const; - -const TransactionProcessorShell = ({ disableCollapse = false }: { disableCollapse?: boolean }) => { - const lastLoggedProcessingState = useRef(''); - const { - activeTransaction, - processing, - explorerURL, - explorerURLs, - timer, - toggleTransactionCollapse, - isTransactionCollapsed, - cancelTransaction, - } = useInternalNexus(); - - const { type: transactionType, simulationResult } = activeTransaction; - - const sources = useMemo(() => { - if (!simulationResult) return [] as number[]; - - if (transactionType === 'bridge' || transactionType === 'transfer') { - return (simulationResult as SimulationResult)?.intent?.sources?.map((s) => s.chainID) || []; - } - - if (transactionType === 'swap') { - const swapResult = simulationResult as SwapSimulationResult; - // For swap, extract chain IDs from sources - return swapResult?.intent?.sources?.map((source) => source?.chain?.id) || []; - } - - const bridgeExecuteResult = simulationResult as BridgeAndExecuteSimulationResult; - - // If bridge was skipped, use the target chain as the source since we're executing directly - if (bridgeExecuteResult?.metadata?.bridgeSkipped) { - return [bridgeExecuteResult.metadata.targetChain]; - } - - return bridgeExecuteResult.bridgeSimulation?.intent?.sources?.map((s) => s.chainID) || []; - }, [simulationResult, transactionType]); - - const destination = useMemo(() => { - if (!simulationResult) return 0; - - if (transactionType === 'bridge' || transactionType === 'transfer') { - return (simulationResult as SimulationResult)?.intent?.destination?.chainID || 0; - } - - if (transactionType === 'swap') { - const swapResult = simulationResult as SwapSimulationResult; - // For swap, extract destination chain ID - return swapResult?.intent?.destination?.chain?.id ?? 0; - } - - const bridgeExecuteResult = simulationResult as BridgeAndExecuteSimulationResult; - - // If bridge was skipped, use the target chain as the destination - if (bridgeExecuteResult?.metadata?.bridgeSkipped) { - return bridgeExecuteResult.metadata.targetChain; - } - - return bridgeExecuteResult.bridgeSimulation?.intent?.destination?.chainID || 0; - }, [simulationResult, transactionType]); - - const token = getTokenFromInputData(activeTransaction.inputData) || ''; - const sourceChainMeta = sources - .filter((s): s is number => s != null && !isNaN(s)) - .map((s) => CHAIN_METADATA[s]) - .filter(Boolean); - - const destChainMeta = destination ? CHAIN_METADATA[destination] : null; - const tokenMeta = token ? TOKEN_METADATA[token] : null; - - const getDescription = () => { - if (activeTransaction?.type === 'swap') { - if (processing?.statusText === 'Swap is completed') { - return 'Transaction Completed Successfully'; - } - const destinationToken = (activeTransaction?.simulationResult as SwapSimulationResult)?.intent - ?.destination?.token; - const destinationTokenSymbol = destinationToken - ? destinationToken.symbol.toUpperCase() - : 'token'; - - return `${getOperationText(transactionType as TransactionType)} ${tokenMeta?.symbol || 'token'} to ${destinationTokenSymbol} on ${destChainMeta?.name || 'destination chain'}`; - } - if (activeTransaction?.executionResult?.success) return 'Transaction Completed Successfully'; - return `${getOperationText(transactionType as TransactionType)} ${tokenMeta?.symbol || 'token'} from ${sourceChainMeta.length > 1 ? 'multiple chains' : sourceChainMeta[0]?.name} to ${destChainMeta?.name || 'destination chain'}`; - }; - - const shellActive = ['processing', 'success', 'error'].includes(activeTransaction.status); - - const dragConstraints = useDragConstraints(); - - const [windowSize, setWindowSize] = useState({ width: 0, height: 0 }); - - useEffect(() => { - const update = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }); - update(); - window.addEventListener('resize', update); - return () => window.removeEventListener('resize', update); - }, []); - - const collapsedPos = { - x: Math.max(16, windowSize.width - COLLAPSED.width - 16), - y: 16, - }; - - const expandedPos = { - x: Math.max(0, (windowSize.width - EXPANDED.width) / 2), - y: Math.max(0, (windowSize.height - EXPANDED.height) / 2), - }; - - if (!shellActive || !transactionType || !simulationResult) { - return null; - } - - // Only log processing changes when state actually changes to reduce noise - const processingStateKey = `${processing?.currentStep}-${processing?.totalSteps}-${processing?.statusText}-${processing?.animationProgress}`; - if (lastLoggedProcessingState.current !== processingStateKey && processing) { - logger.info('processing from hook', processing); - lastLoggedProcessingState.current = processingStateKey; - } - - return ( - - <> - {/* Backdrop */} - {!isTransactionCollapsed && ( - - )} - - {/* Processor Card */} - - {isTransactionCollapsed ? ( - - ) : ( - - )} - - - - ); -}; -TransactionProcessorShell.displayName = 'TransactionProcessorShell'; - -export default memo(TransactionProcessorShell); diff --git a/packages/widgets/src/components/processing/transaction-simulation.tsx b/packages/widgets/src/components/processing/transaction-simulation.tsx deleted file mode 100644 index 1351ff36..00000000 --- a/packages/widgets/src/components/processing/transaction-simulation.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { type SimulationResult, type BridgeAndExecuteSimulationResult } from '@nexus/commons'; -import { InfoMessage } from '../shared/info-message'; -import { TransactionDetailsDrawer } from '../shared/transaction-details-drawer'; -import TextLoader from '../motion/text-loader'; -import { - OrchestratorStatus, - ReviewStatus, - TransactionType, - SwapSimulationResult, -} from '../../types'; - -interface TransactionSimulationProps { - isLoading: boolean; - simulationResult?: ( - | SimulationResult - | BridgeAndExecuteSimulationResult - | SwapSimulationResult - ) & { - allowance?: { needsApproval: boolean }; - }; - inputData?: { - token?: string; - amount?: string | number; - chainId?: number; - toChainId?: number; - }; - type?: TransactionType; - callback: () => void; - status: OrchestratorStatus; - reviewStatus: ReviewStatus; -} - -export function TransactionSimulation({ - isLoading, - simulationResult, - inputData, - type, - callback, - status, - reviewStatus, -}: Readonly) { - if (isLoading) { - return ( -
- -
- ); - } - - if (!simulationResult) { - return null; - } - - return ( -
- {simulationResult?.allowance?.needsApproval && ( -
- - You need to set allowance in your wallet first to continue. - -
- )} - -
- -
-
- ); -} diff --git a/packages/widgets/src/components/shared/action-buttons.tsx b/packages/widgets/src/components/shared/action-buttons.tsx deleted file mode 100644 index 03998cc3..00000000 --- a/packages/widgets/src/components/shared/action-buttons.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Button } from '../motion/button-motion'; -import { cn } from '../../utils/utils'; -import { SmallAvailLogo } from '../icons/SmallAvailLogo'; -import LoadingDots from '../motion/loading-dots'; - -interface ActionButtonsProps { - onCancel: () => void; - onPrimary: () => void; - primaryText?: string; - primaryLoading?: boolean; - primaryDisabled?: boolean; - className?: string; -} - -export function ActionButtons({ - onCancel, - onPrimary, - primaryText = 'Continue', - primaryLoading = false, - primaryDisabled = false, - className, -}: Readonly) { - return ( -
-
- - - -
-
- Powered By - -
-
- ); -} diff --git a/packages/widgets/src/components/shared/address-field.tsx b/packages/widgets/src/components/shared/address-field.tsx deleted file mode 100644 index e5217b88..00000000 --- a/packages/widgets/src/components/shared/address-field.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Input } from '../motion/input'; -import { cn } from '../../utils/utils'; - -interface AddressFieldProps { - value?: string; - onChange?: (value: string) => void; - disabled?: boolean; - placeholder?: string; - className?: string; - hasValidationError?: boolean; -} - -export function AddressField({ - value, - onChange, - disabled = false, - placeholder = '0x...', - className, - hasValidationError = false, -}: Readonly) { - return ( -
-
- onChange?.(e.target.value)} - disabled={disabled} - className={cn( - 'px-0 placeholder:font-nexus-primary text-nexus-black font-semibold text-base', - hasValidationError ? 'border-red-500 focus:border-red-500' : '', - )} - /> -
-
- ); -} diff --git a/packages/widgets/src/components/shared/allowance-form.tsx b/packages/widgets/src/components/shared/allowance-form.tsx deleted file mode 100644 index ed456bc5..00000000 --- a/packages/widgets/src/components/shared/allowance-form.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import { Fragment, useCallback, useEffect, useRef, useState } from 'react'; -import { cn, formatCost } from '../../utils/utils'; -import { EnhancedInfoMessage } from './enhanced-info-message'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { CHAIN_METADATA, SUPPORTED_CHAINS, TOKEN_METADATA, formatUnits } from '@nexus/commons'; -import { FormField } from '../motion/form-field'; -import { Input } from '../motion/input'; - -export interface AllowanceFormProps { - token: string; - minimumAmount: string; - inputAmount: string; - sourceChains: { chainId: number; amount: string; needsApproval?: boolean }[]; - onApprove: (amount: string, isMinimum: boolean) => void; - onCancel: () => void; - isLoading?: boolean; - error?: string | null; - // Expose form state for external button handling - onFormStateChange?: (isValid: boolean, approveHandler: () => void) => void; -} - -export function AllowanceForm({ - token, - minimumAmount, - inputAmount, - sourceChains, - onApprove, - onCancel: _onCancel, - isLoading = false, - error = null, - onFormStateChange, -}: Readonly) { - const [currentAllowance, setCurrentAllowance] = useState(null); - const [selectedType, setSelectedType] = useState<'minimum' | 'custom'>('minimum'); - const [customAmount, setCustomAmount] = useState(''); - const { sdk } = useInternalNexus(); - - // Keep latest form values in refs to avoid stale closures when parent stores handler - const latestValuesRef = useRef({ selectedType, customAmount, minimumAmount }); - latestValuesRef.current.selectedType = selectedType; - latestValuesRef.current.customAmount = customAmount; - latestValuesRef.current.minimumAmount = minimumAmount; - - const tokenMetadata = TOKEN_METADATA[token]; - - // Stable handler that reads latest values from refs, so parent always has a fresh handler - const stableApproveHandler = useCallback(() => { - const { selectedType, customAmount, minimumAmount } = latestValuesRef.current; - if (selectedType === 'minimum') { - onApprove(minimumAmount, true); - } else { - onApprove(customAmount, false); - } - }, [onApprove]); - - const validateCustomAmount = (amount: string): boolean => { - if (!amount) return false; - const numAmount = parseFloat(amount); - const numInputAmount = parseFloat(inputAmount); - return !isNaN(numAmount) && numAmount > 0 && numAmount >= numInputAmount; - }; - - const getCurrentAllowance = async () => { - // Find the first chain that actually needs allowance - const chainThatNeedsAllowance = sourceChains.find((chain) => chain.needsApproval === true); - - if (!chainThatNeedsAllowance) { - // If no chain needs approval, show allowance from first chain or 0 - const firstChain = sourceChains[0]; - if (firstChain) { - const allowance = await sdk.getAllowance(firstChain.chainId, [token]); - const decimals = Number(TOKEN_METADATA[token].decimals); - const formattedAllowance = formatUnits(allowance[0]?.allowance ?? 0n, decimals); - setCurrentAllowance(formattedAllowance); - } else { - setCurrentAllowance('0'); - } - return; - } - - // Get allowance from the chain that needs approval - const allowance = await sdk.getAllowance(chainThatNeedsAllowance.chainId, [token]); - const decimals = Number(TOKEN_METADATA[token].decimals); - const formattedAllowance = formatUnits(allowance[0]?.allowance ?? 0n, decimals); - setCurrentAllowance(formattedAllowance); - }; - - useEffect(() => { - if (!currentAllowance) { - getCurrentAllowance(); - } - }, [sourceChains, token]); - - const isCustomValid = selectedType === 'custom' ? validateCustomAmount(customAmount) : true; - const isFormValid = selectedType === 'minimum' || isCustomValid; - - // Notify parent of form state changes; avoid depending on onFormStateChange to prevent loops - useEffect(() => { - if (onFormStateChange) { - onFormStateChange(isFormValid, stableApproveHandler); - } - }, [isFormValid]); - - return ( -
-
- {/* Header */} -
-

- Allow access to {formatCost(minimumAmount)} {token} to complete your transaction. -

-
- - {/* Token Information */} -
-
- Token -
- {tokenMetadata?.icon && ( - {token} - )} - - {token} on - -
- {sourceChains - .filter((chain) => chain.needsApproval !== false) // Show chains that need approval or are undefined - .map((source, index, filteredChains) => { - const chainMeta = CHAIN_METADATA[source?.chainId]; - return ( - - {chainMeta?.name} 0 ? '-ml-5' : '', - chainMeta?.id !== SUPPORTED_CHAINS.BASE && - chainMeta?.id !== SUPPORTED_CHAINS.BASE_SEPOLIA - ? 'rounded-nexus-full ' - : '', - )} - style={{ zIndex: filteredChains.length - index }} - title={chainMeta?.name} - /> - - ); - })} - {sourceChains.filter((chain) => chain.needsApproval !== false).length > 1 && ( - - +{sourceChains.filter((chain) => chain.needsApproval !== false).length} chains - - )} -
-
-
- {currentAllowance && ( -
-
- - Current Allowance - - - {currentAllowance} - -
-
- )} -
- - {error ? ( - - ) : ( -
-
- {/* Minimum Option */} -
setSelectedType('minimum')} - > -
-
- setSelectedType('minimum')} - className="text-blue-600" - /> -
- Min: - - {formatCost(minimumAmount)} - -
-
- - RECOMMENDED - -
-
- - {/* Custom Option */} -
-
setSelectedType('custom')} - > - setSelectedType('custom')} - className="text-blue-600" - /> - - Custom - -
-
-
- {selectedType === 'custom' && ( -
- - setCustomAmount(e.target.value)} - className={cn( - 'text-nexus-black text-base font-semibold font-nexus-primary leading-normal px-4 py-2 border border-nexus-input rounded-nexus-md', - customAmount && !isCustomValid ? 'border-red-500 focus:border-red-500' : '', - )} - /> - -
- )} -
- )} -
-
- ); -} diff --git a/packages/widgets/src/components/shared/amount-input.tsx b/packages/widgets/src/components/shared/amount-input.tsx deleted file mode 100644 index 757fc1de..00000000 --- a/packages/widgets/src/components/shared/amount-input.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import * as React from 'react'; -import { cn } from '../../utils/utils'; -import { Input } from '../motion/input'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { getFiatValue } from '../../utils/balance-utils'; - -interface AmountInputProps { - value?: string; - disabled?: boolean; - onChange?: (value: string) => void; - className?: string; - placeholder?: string; - debounceMs?: number; - token?: string; -} - -export function AmountInput({ - value, - disabled = false, - onChange, - className, - placeholder = '0.0', - debounceMs = 500, - token, -}: Readonly) { - const [localValue, setLocalValue] = React.useState(value || ''); - const timeoutRef = React.useRef(undefined); - const { exchangeRates } = useInternalNexus(); - - React.useEffect(() => { - setLocalValue(value || ''); - }, [value]); - - const validateNumberInput = (input: string): string => { - if (input === '') return ''; - if (input === '.') return '0.'; - // Remove non-numeric characters except dots - let cleaned = input.replace(/[^0-9.]/g, ''); - - // Handle case where input starts with decimal point - if (cleaned.startsWith('.')) { - cleaned = '0' + cleaned; - } - - // Handle multiple decimal points - keep only the first one - const decimalCount = (cleaned.match(/\./g) || []).length; - if (decimalCount > 1) { - const firstDecimalIndex = cleaned.indexOf('.'); - cleaned = - cleaned.substring(0, firstDecimalIndex + 1) + - cleaned.substring(firstDecimalIndex + 1).replace(/\./g, ''); - } - - if (cleaned.length > 1 && cleaned.startsWith('0')) { - const decimalIndex = cleaned.indexOf('.'); - if (decimalIndex === -1 || decimalIndex > 1) { - cleaned = cleaned.replace(/^0+/, ''); - if (cleaned === '' || cleaned.startsWith('.')) { - cleaned = '0' + cleaned; - } - } else if (decimalIndex === 1) { - if (cleaned.length > 2 && cleaned.substring(0, 2) === '00') { - cleaned = cleaned.replace(/^0+/, '0'); - } - } - } - - const decimalIndex = cleaned.indexOf('.'); - if (decimalIndex !== -1 && cleaned.length - decimalIndex > 19) { - cleaned = cleaned.substring(0, decimalIndex + 19); - } - - return cleaned; - }; - - const handleInputChange = (e: React.ChangeEvent) => { - const rawValue = e.target.value; - const validatedValue = validateNumberInput(rawValue); - - setLocalValue(validatedValue); - - if (!onChange) return; - - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - timeoutRef.current = setTimeout(() => { - if (validatedValue !== value) { - onChange(validatedValue); - } - }, debounceMs); - }; - - React.useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - }; - }, []); - - return ( -
-
- {onChange ? ( - - ) : ( -

- {value ?? 0} -

- )} -
- {token && value && ( -

- {getFiatValue(value, token, exchangeRates)} -

- )} -
- ); -} diff --git a/packages/widgets/src/components/shared/chain-select.tsx b/packages/widgets/src/components/shared/chain-select.tsx deleted file mode 100644 index d790510a..00000000 --- a/packages/widgets/src/components/shared/chain-select.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { useMemo } from 'react'; -import { ChainSelectProps } from '../../types'; -import { CHAIN_METADATA, DESTINATION_SWAP_TOKENS } from '@nexus/commons'; -import { ChainIcon } from './icons'; -import { cn } from '../../utils/utils'; -import { Button } from '../motion/button-motion'; -import { DrawerAutoClose } from '../motion/drawer'; -import { getFilteredChainsForToken } from '../../utils/token-utils'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import type { TransactionType } from '../../utils/balance-utils'; - -interface ChainSelectOption { - value: string; - label: string; - chainId: number; - logo: string; -} - -export function ChainSelect({ - value, - onValueChange, - disabled = false, - network = 'mainnet', - className, - hasValues, - isSource, - selectedToken, - transactionType, -}: ChainSelectProps & { - network?: 'mainnet' | 'testnet'; - selectedToken?: string; - transactionType?: TransactionType; -}) { - const { sdk } = useInternalNexus(); - const availableChainIds = useMemo(() => { - if (!sdk) return [] as number[]; - let ids: number[] = []; - if (network === 'testnet' && transactionType !== 'swap') { - ids = sdk?.utils?.getSupportedChains(0)?.map((chain) => chain?.id) ?? []; - } else if (transactionType === 'swap' && network !== 'testnet') { - ids = isSource - ? (sdk?.utils?.getSwapSupportedChainsAndTokens()?.map((chain) => chain?.id) ?? []) - : Array.from(DESTINATION_SWAP_TOKENS.keys()); - } else { - ids = sdk?.utils?.getSupportedChains()?.map((chain) => chain?.id) ?? []; - } - // Exclude Fuel (9889) and any chains without known metadata to avoid runtime errors - return ids.filter((id) => id !== 9889 && !!CHAIN_METADATA[id]); - }, [sdk, network, transactionType, isSource]); - - const filteredChainIds = useMemo(() => { - if (!availableChainIds?.length) return [] as number[]; - if (selectedToken && transactionType) { - return getFilteredChainsForToken( - selectedToken, - availableChainIds, - transactionType, - sdk, - !isSource, - ); - } - return availableChainIds; - }, [availableChainIds, selectedToken, transactionType, sdk, isSource]); - - const chainOptions: ChainSelectOption[] = filteredChainIds - .filter((chainId) => !!CHAIN_METADATA[chainId]) - .map((chainId) => { - const metadata = CHAIN_METADATA[chainId]; - return { - value: chainId.toString(), - label: metadata?.name ?? `Chain ${chainId}`, - chainId, - logo: metadata?.logo ?? '', - }; - }); - const selectedOption = useMemo( - () => chainOptions.find((opt) => opt.value === (value ?? '')), - [value, chainOptions], - ); - - const handleSelect = (chainId: string) => { - if (disabled) return; - onValueChange(chainId); - }; - - // Check if current selection is still valid after filtering - const isCurrentSelectionValid = useMemo(() => { - if (!value) return true; - return chainOptions.some((option) => option.value === value); - }, [value, chainOptions]); - - if (network === 'testnet' && transactionType === 'swap') { - throw new Error('Swap not supported on testnet'); - } - - return ( -
-
-

- {isSource ? 'Source' : 'Destination'} Chain -

- {selectedToken && transactionType && !isCurrentSelectionValid && ( -

- Current chain doesn't support {selectedToken} -

- )} -
-
- {chainOptions.map((chain, index) => ( - - - - ))} - - {/* Empty state */} - {chainOptions.length === 0 && ( -
-

No chains available for selected token

-
- )} -
-
- ); -} diff --git a/packages/widgets/src/components/shared/destination-drawer.tsx b/packages/widgets/src/components/shared/destination-drawer.tsx deleted file mode 100644 index 7cb51734..00000000 --- a/packages/widgets/src/components/shared/destination-drawer.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { ChainSelect } from './chain-select'; -import { TokenSelect } from './token-select'; -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from '../motion/drawer'; -import { ChevronDownIcon, CircleX } from '../icons'; -import { FormField } from '../motion/form-field'; -import { CHAIN_METADATA, SUPPORTED_CHAINS } from '@nexus/commons'; -import { cn } from '../../utils/utils'; -import { TokenIcon } from './icons'; -import type { TransactionType as BalanceTransactionType } from '../../utils/balance-utils'; - -interface DestinationDrawerProps { - chainValue?: string; - tokenValue?: string; - isChainSelectDisabled?: boolean; - isTokenSelectDisabled?: boolean; - network?: 'mainnet' | 'testnet'; - onChainValueChange: (chain: string) => void; - onTokenValueChange: (token: string, iconUrl?: string) => void; - fieldLabel?: string; - drawerTitle?: string; - type?: BalanceTransactionType; - isDestination?: boolean; - isSourceChain?: boolean; -} - -const DestinationTrigger = ({ - chainValue, - tokenValue, - fieldLabel = 'Destination', -}: { - chainValue?: string; - tokenValue?: string; - fieldLabel?: string; -}) => { - const chainId = chainValue ? parseInt(chainValue) : undefined; - - return ( - -
-
-
- {tokenValue ? ( - - ) : ( -
- )} - {chainId ? ( - {CHAIN_METADATA[chainId]?.name} - ) : ( -
- )} -
-
-

- {tokenValue ?? 'Token'} -

-

- {chainId ? CHAIN_METADATA[chainId]?.name : 'Chain'} -

-
-
- -
- - ); -}; - -const DestinationDrawer = ({ - chainValue, - tokenValue, - isChainSelectDisabled, - isTokenSelectDisabled, - network, - onChainValueChange, - onTokenValueChange, - fieldLabel, - drawerTitle = 'Select Destination Chain & Token', - type, - isDestination = false, - isSourceChain = false, -}: DestinationDrawerProps) => { - return ( - - - - - - -
- - {drawerTitle} - - - - -
-
- -
- - onTokenValueChange(token, iconUrl)} - disabled={isTokenSelectDisabled} - network={network} - className="w-full" - hasValues={!!chainValue} - type={type} - chainId={chainValue ? parseInt(chainValue) : undefined} - isDestination={isDestination} - /> -
-
-
- ); -}; - -export default DestinationDrawer; diff --git a/packages/widgets/src/components/shared/enhanced-info-message.tsx b/packages/widgets/src/components/shared/enhanced-info-message.tsx deleted file mode 100644 index 77a9f189..00000000 --- a/packages/widgets/src/components/shared/enhanced-info-message.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { useState } from 'react'; -import { InfoMessage } from './info-message'; -import { Button } from '../motion/button-motion'; -import { - isChainError, - extractChainIdFromError, - addChainToWallet, - formatErrorForUI, - cn, -} from '../../utils/utils'; -import { Plus } from '../icons'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import LoadingDots from '../motion/loading-dots'; -import { CHAIN_METADATA, SUPPORTED_CHAINS, logger } from '@nexus/commons'; - -interface EnhancedInfoMessageProps { - error: unknown; - context?: string; - className?: string; -} - -export function EnhancedInfoMessage({ - error, - context, - className, -}: Readonly) { - const [isAddingChain, setIsAddingChain] = useState(false); - const [chainAdded, setChainAdded] = useState(false); - const { sdk } = useInternalNexus(); - - const isChainRelatedError = isChainError(error); - const chainId = isChainRelatedError ? extractChainIdFromError(error) : null; - const chainMetadata = chainId ? CHAIN_METADATA[chainId] : null; - - const handleAddChain = async () => { - if (!chainId) return; - - setIsAddingChain(true); - try { - const provider = sdk.getEVMProviderWithCA(); - const success = await addChainToWallet(chainId, provider); - if (success) { - setChainAdded(true); - } - } catch (err) { - logger.error('Failed to add chain:', err as Error); - } finally { - setIsAddingChain(false); - } - }; - - const formattedError = formatErrorForUI(error, context); - - if (isChainRelatedError && chainMetadata && !chainAdded) { - return ( - -
-

{formattedError}

- -
- {chainMetadata.name} -
-

- {chainMetadata.name} -

-

- Chain ID: {chainId} -

-
- -
- -

- This will add {chainMetadata.name} network to your wallet so you can use it for - transactions. -

-
-
- ); - } - - if (chainAdded) { - return ( - -
-
- - - -
-
-

- {chainMetadata - ? `${chainMetadata.name} network added successfully!` - : 'Network added successfully!'} -

-

- You can now retry your transaction. -

-
-
-
- ); - } - - // Fallback to regular formatted error message - return ( - -

{formattedError}

-
- ); -} diff --git a/packages/widgets/src/components/shared/icons.tsx b/packages/widgets/src/components/shared/icons.tsx deleted file mode 100644 index b0233697..00000000 --- a/packages/widgets/src/components/shared/icons.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { - CHAIN_METADATA, - SUPPORTED_CHAINS, - TOKEN_METADATA, - DESTINATION_SWAP_TOKENS, - type ChainMetadata, -} from '@nexus/commons'; -import { cn } from '../../utils/utils'; - -// Additional token logos that might not be in TOKEN_METADATA -const ADDITIONAL_TOKEN_LOGOS: Record = { - WETH: 'https://assets.coingecko.com/coins/images/279/large/ethereum.png?1595348880', - USDS: 'https://assets.coingecko.com/coins/images/39926/standard/usds.webp?1726666683', - SOPH: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png', - KAIA: 'https://assets.coingecko.com/asset_platforms/images/9672/large/kaia.png', - BNB: 'https://assets.coingecko.com/coins/images/825/large/bnb-icon2_2x.png', - // Add ETH as fallback for any ETH-related tokens - ETH: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628', - // Add common token fallbacks - POL: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png', - AVAX: 'https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png', - FUEL: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png', - HYPE: 'https://assets.coingecko.com/asset_platforms/images/243/large/hyperliquid.png', - // Popular swap tokens - DAI: 'https://coin-images.coingecko.com/coins/images/9956/large/Badge_Dai.png?1696509996', - UNI: 'https://coin-images.coingecko.com/coins/images/12504/large/uni.jpg?1696512319', - AAVE: 'https://coin-images.coingecko.com/coins/images/12645/large/AAVE.png?1696512452', - LDO: 'https://coin-images.coingecko.com/coins/images/13573/large/Lido_DAO.png?1696513326', - PEPE: 'https://coin-images.coingecko.com/coins/images/29850/large/pepe-token.jpeg?1696528776', - OP: 'https://coin-images.coingecko.com/coins/images/25244/large/Optimism.png?1696524385', - ZRO: 'https://coin-images.coingecko.com/coins/images/28206/large/ftxG9_TJ_400x400.jpeg?1696527208', - OM: 'https://assets.coingecko.com/coins/images/12151/standard/OM_Token.png?1696511991', - KAITO: 'https://assets.coingecko.com/coins/images/54411/standard/Qm4DW488_400x400.jpg', -}; - -export const ChainIcon = ({ chainId }: { chainId: string }) => { - const chain = Object.values(CHAIN_METADATA).find( - (c: ChainMetadata) => c.id.toString() === chainId, - ); - const iconUrl = chain?.logo; - - if (!iconUrl) { - return
; - } - - return ( - {chainId} - ); -}; - -export const TokenIcon = ({ - tokenSymbol, - iconUrl, - className = 'w-6 h-6 rounded-nexus-full', -}: { - tokenSymbol: string; - iconUrl?: string; - className?: string; -}) => { - let finalIconUrl = iconUrl; - - // Comprehensive icon resolution logic - if (!finalIconUrl) { - // 1. First check additional token logos (prioritize over TOKEN_METADATA for better icons) - finalIconUrl = ADDITIONAL_TOKEN_LOGOS[tokenSymbol]; - - // 2. Then check standard TOKEN_METADATA - if (!finalIconUrl) { - const standardToken = TOKEN_METADATA[tokenSymbol]; - finalIconUrl = standardToken?.icon; - } - - // 3. Check destination swap tokens - if (!finalIconUrl) { - const allDestinationTokens = Array.from(DESTINATION_SWAP_TOKENS.values()).flat(); - const destinationToken = allDestinationTokens.find((token) => token.symbol === tokenSymbol); - finalIconUrl = destinationToken?.logo; - } - - // 4. Special handling for wrapped tokens - if (!finalIconUrl && tokenSymbol.startsWith('W') && tokenSymbol.length > 1) { - const baseSymbol = tokenSymbol.substring(1); // Remove 'W' prefix - finalIconUrl = ADDITIONAL_TOKEN_LOGOS[baseSymbol]; - } - - // 5. ETH fallback for any ethereum-related tokens - if (!finalIconUrl && (tokenSymbol.includes('ETH') || tokenSymbol === 'WETH')) { - finalIconUrl = ADDITIONAL_TOKEN_LOGOS['ETH']; - } - } - - // Fallback placeholder with first letter of token symbol - if (!finalIconUrl) { - return ( -
- {tokenSymbol.charAt(0).toUpperCase()} -
- ); - } - - return {tokenSymbol}; -}; diff --git a/packages/widgets/src/components/shared/info-message.tsx b/packages/widgets/src/components/shared/info-message.tsx deleted file mode 100644 index 9c86796a..00000000 --- a/packages/widgets/src/components/shared/info-message.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import { cn } from '../../utils/utils'; -import { cva, type VariantProps } from 'class-variance-authority'; - -const infoMessageVariants = cva( - 'px-2 py-3 rounded-nexus-md overflow-hidden font-nexus-primary font-semibold text-sm leading-[18px] backdrop-blur-[48px] border border-nexus-black/80', - { - variants: { - variant: { - success: 'bg-gradient-to-r from-[#86DF00]/16 to-[#73BF01]/16 text-nexus-black', - info: 'bg-blue-50 text-nexus-black', - warning: 'bg-gradient-to-r from-[#DFC200]/16 to-[#DFC200]/16 text-nexus-black', - error: 'bg-[#C03C541A] text-[#C03C54] border border-[#C03C541A]', - }, - }, - defaultVariants: { - variant: 'success', - }, - }, -); - -interface InfoMessageProps extends VariantProps { - children: React.ReactNode; - className?: string; -} - -export function InfoMessage({ variant, children, className }: Readonly) { - return ( -
-
-
-
- {children} -
-
-
-
- ); -} diff --git a/packages/widgets/src/components/shared/prefilled-inputs.tsx b/packages/widgets/src/components/shared/prefilled-inputs.tsx deleted file mode 100644 index 56539327..00000000 --- a/packages/widgets/src/components/shared/prefilled-inputs.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { CHAIN_METADATA, SUPPORTED_CHAINS, TOKEN_METADATA } from '@nexus/commons'; -import { cn, formatCost, truncateAddress } from '../../utils/utils'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { getFiatValue } from '../../utils/balance-utils'; - -interface PrefilledInputsProps { - inputData: { - chainId?: number; - toChainId?: number; - token?: string; - amount?: string | number; - recipient?: string; - }; - className?: string; -} - -const PrefilledInputs = ({ inputData, className = '' }: PrefilledInputsProps) => { - const { exchangeRates } = useInternalNexus(); - const destinationChain = - CHAIN_METADATA[inputData?.chainId ?? inputData?.toChainId ?? SUPPORTED_CHAINS.ETHEREUM]; - const destinationToken = TOKEN_METADATA[inputData?.token ?? 'ETH']; - return ( -
-
-

Sending

-
-
- {destinationToken?.name} -
-

- {formatCost(inputData?.amount as string)} -

-

- {inputData?.token} -

-
-
-
-

To

- {destinationChain?.shortName} -

- {destinationChain?.name} -

-
-
-
- {inputData?.amount && inputData?.token && ( -

- {getFiatValue(inputData?.amount, inputData?.token, exchangeRates)} -

- )} - {inputData?.recipient && ( -
-

To

-

- {truncateAddress(inputData?.recipient, 4, 4)} -

-
- )} -
- ); -}; - -export default PrefilledInputs; diff --git a/packages/widgets/src/components/shared/swap-prefilled-inputs.tsx b/packages/widgets/src/components/shared/swap-prefilled-inputs.tsx deleted file mode 100644 index 40eb7b6d..00000000 --- a/packages/widgets/src/components/shared/swap-prefilled-inputs.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { CHAIN_METADATA, SUPPORTED_CHAINS, formatBalance } from '@nexus/commons'; -import { cn } from '../../utils/utils'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { getFiatValue } from '../../utils/balance-utils'; -import { SwapInputData, SwapSimulationResult } from '../../types'; -import { TokenIcon } from './icons'; - -interface SwapPrefilledInputsProps { - inputData: Omit; - className?: string; -} - -const SwapPrefilledInputs = ({ inputData, className = '' }: SwapPrefilledInputsProps) => { - const { exchangeRates, activeTransaction } = useInternalNexus(); - const sourceChain = CHAIN_METADATA[inputData?.fromChainID ?? SUPPORTED_CHAINS.ETHEREUM]; - const destinationChain = CHAIN_METADATA[inputData?.toChainID ?? SUPPORTED_CHAINS.ETHEREUM]; - const transactionIntent = (activeTransaction?.simulationResult as SwapSimulationResult)?.intent; - - return ( -
-
-
-

- Swapping -

-
-
-
- -

- {inputData?.fromAmount} {inputData?.fromTokenAddress} -

- {sourceChain?.shortName} -
-
-

↓

-
-
- -

- {transactionIntent - ? formatBalance( - transactionIntent?.destination?.amount, - transactionIntent?.destination?.token?.decimals, - 6, - ) - : '...'}{' '} - {inputData?.toTokenAddress} -

- {destinationChain?.shortName} -
-
-
- {inputData?.fromAmount && inputData?.fromTokenAddress && ( -

- {getFiatValue(inputData?.fromAmount, inputData?.fromTokenAddress, exchangeRates)} -

- )} -
- ); -}; - -export default SwapPrefilledInputs; diff --git a/packages/widgets/src/components/shared/token-select.tsx b/packages/widgets/src/components/shared/token-select.tsx deleted file mode 100644 index 8d444aa6..00000000 --- a/packages/widgets/src/components/shared/token-select.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { useMemo } from 'react'; -import { TOKEN_METADATA, TESTNET_TOKEN_METADATA } from '@nexus/commons'; -import { TokenIcon } from './icons'; -import { cn } from '../../utils/utils'; -import { Button } from '../motion/button-motion'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { DrawerAutoClose } from '../motion/drawer'; -import type { SwapInputData, TokenSelectProps } from '../../types'; -import { useAvailableTokens, type TokenSelectOption } from '../../utils/token-utils'; - -export function TokenSelect({ - value, - onValueChange, - disabled = false, - network = 'mainnet', - className, - hasValues, - type, - chainId, - isDestination = false, -}: TokenSelectProps & { - network?: 'mainnet' | 'testnet'; - chainId?: number; - isDestination?: boolean; -}) { - const { unifiedBalance, sdk, isSdkInitialized, activeTransaction } = useInternalNexus(); - - const tokenOptions = useAvailableTokens({ - chainId, - type: type ?? 'bridge', - network, - isDestination, - sdk: isSdkInitialized ? sdk : undefined, - }); - - // Fallback to legacy logic if no type provided (backward compatibility) - const legacyTokenOptions: TokenSelectOption[] = useMemo(() => { - if (type) return []; // Use enhanced logic when type is available - - const tokenMetadata = network === 'testnet' ? TESTNET_TOKEN_METADATA : TOKEN_METADATA; - return Object.values(tokenMetadata).map((token) => ({ - value: token.symbol, - label: token.symbol, - icon: token.icon, - metadata: { - ...token, - contractAddress: undefined, - }, - })); - }, [network, type]); - - const finalTokenOptions = useMemo(() => { - const tokens = type ? tokenOptions : legacyTokenOptions; - const inputData = activeTransaction?.inputData as SwapInputData; - if (inputData && inputData?.fromTokenAddress) { - return tokens.filter((token) => token?.value !== inputData?.fromTokenAddress); - } - return tokens; - }, [type, tokenOptions, legacyTokenOptions]); - - const tokenBalanceBreakdown = useMemo(() => { - let breakdown: Record = {}; - unifiedBalance?.map((balance) => { - const key = balance?.symbol; - breakdown[key] = { - bal: parseFloat(balance?.balance) > 0 ? balance?.balance : '00', - chains: `${balance?.breakdown?.length > 1 ? balance?.breakdown?.length + ' chains' : balance?.breakdown?.length > 0 ? balance?.breakdown?.length + ' chain' : '-'}`, - }; - }); - return breakdown; - }, [unifiedBalance]); - - const selectedOption = useMemo( - () => finalTokenOptions.find((opt) => opt.value === (value ?? '')), - [finalTokenOptions, value], - ); - - const handleSelect = (token: string) => { - if (disabled) return; - onValueChange(token); - }; - - return ( -
-

- {type !== 'swap' - ? 'Destination Token' - : isDestination - ? 'Destination Token' - : 'Source Token'} -

-
- {finalTokenOptions.map((token, index) => ( - - - - ))} - - {/* Empty state */} - {finalTokenOptions.length === 0 && ( -
-

No tokens available

-
- )} -
-
- ); -} diff --git a/packages/widgets/src/components/shared/transaction-details-drawer.tsx b/packages/widgets/src/components/shared/transaction-details-drawer.tsx deleted file mode 100644 index 160a76cf..00000000 --- a/packages/widgets/src/components/shared/transaction-details-drawer.tsx +++ /dev/null @@ -1,390 +0,0 @@ -import { - type SimulationResult, - type BridgeAndExecuteSimulationResult, - type ReadableIntent as Intent, - CHAIN_METADATA, - SUPPORTED_CHAINS, -} from '@nexus/commons'; -import { SwapSimulationResult } from '../../types'; -import { cn, formatCost, getPrimaryButtonText, truncateAddress } from '../../utils/utils'; -import { - Drawer, - DrawerTrigger, - DrawerContent, - DrawerHeader, - DrawerTitle, - DrawerClose, - DrawerFooter, -} from '../motion/drawer'; -import { CircleX } from '../icons'; -import Clock from '../icons/Clock'; -import TwoCircles from '../icons/TwoCircles'; -import MoneyCircles from '../icons/MoneyCircles'; -import { Button } from '../motion/button-motion'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { getFiatValue } from '../../utils/balance-utils'; -import type { OrchestratorStatus, ReviewStatus, TransactionType } from '../../types'; - -interface TransactionDetailsDrawerProps { - simulationResult?: ( - | SimulationResult - | BridgeAndExecuteSimulationResult - | SwapSimulationResult - ) & { - allowance?: { needsApproval: boolean }; - }; - inputData?: { - token?: string; - amount?: string | number; - chainId?: number; - toChainId?: number; - }; - callback: () => void; - triggerClassname?: string; - type?: TransactionType; - status: OrchestratorStatus; - reviewStatus: ReviewStatus; -} - -interface ChainInfo { - amount: string; - chainID: number; - chainLogo?: string; - chainName: string; - contractAddress?: string; -} - -interface FeesInfo { - caGas: string; - gasSupplied: string; - protocol: string; - solver: string; - total: string; -} - -interface TokenInfo { - decimals: number; - logo?: string; - name: string; - symbol: string; -} - -interface SimulationData { - contractAddress?: string; - functionName?: string; - destination: ChainInfo; - sources: ChainInfo[]; - fees: FeesInfo; - token: TokenInfo; - sourcesTotal: string; -} - -export function TransactionDetailsDrawer({ - simulationResult, - inputData, - callback, - triggerClassname = '', - type, - status, - reviewStatus, -}: Readonly) { - const { exchangeRates } = useInternalNexus(); - const getSimulationData = (): SimulationData | null => { - if (!simulationResult) return null; - console.log('Original simulationResult', simulationResult); - - // Handle swap simulation result - if ('swapMetadata' in simulationResult) { - const swapSim = simulationResult as SwapSimulationResult; - const intent = swapSim.intent; - if (!intent) return null; - const destinationChain = CHAIN_METADATA[intent?.destination?.chain.id]; - const sources = intent.sources.map((source) => { - const sourceChain = CHAIN_METADATA[source.chain.id]; - return { - chainID: sourceChain?.id, - chainName: sourceChain?.name || 'Unknown', - chainLogo: sourceChain?.logo, - amount: source.amount, - } as ChainInfo; - }); - - return { - destination: { - chainID: destinationChain?.id, - chainName: destinationChain?.name || 'Unknown', - chainLogo: destinationChain?.logo, - amount: swapSim?.intent?.destination?.amount, - } as ChainInfo, - sources, - fees: { - total: '0', // Swap fees are typically handled differently - caGas: '0', - gasSupplied: '0', - protocol: '0', - solver: '0', - } as FeesInfo, - token: { - symbol: intent?.sources?.[0]?.token?.symbol || 'Unknown', - name: intent?.sources?.[0]?.token?.symbol, - decimals: intent?.sources?.[0]?.token?.decimals, - } as TokenInfo, - sourcesTotal: intent.sources?.[0]?.amount || '0', - }; - } - - // Check if bridge was skipped in bridge & execute flow - if ( - 'metadata' in simulationResult && - (simulationResult as BridgeAndExecuteSimulationResult)?.metadata?.bridgeSkipped - ) { - const simulation = simulationResult as BridgeAndExecuteSimulationResult; - const metadata = simulation?.metadata; - - if (!metadata) return null; - - return { - contractAddress: metadata?.contractAddress ?? '', - functionName: metadata?.functionName ?? '', - destination: { - chainID: metadata?.targetChain, - chainName: CHAIN_METADATA[metadata?.targetChain]?.name || 'Unknown', - chainLogo: CHAIN_METADATA[metadata?.targetChain]?.logo, - amount: metadata?.inputAmount, - } as ChainInfo, - sources: [ - { - chainName: CHAIN_METADATA[metadata?.targetChain]?.name, - chainID: metadata?.targetChain, - chainLogo: CHAIN_METADATA[metadata?.targetChain]?.logo, - amount: metadata?.inputAmount, - }, - ], - fees: { - total: simulation?.executeSimulation?.gasUsed ?? '0', - bridge: '0', - caGas: '0', - gasSupplied: '0', - protocol: '0', - solver: '0', - } as FeesInfo, - token: { name: simulationResult?.metadata?.token || 'Unknown' } as TokenInfo, - sourcesTotal: metadata?.inputAmount || '0', - }; - } - - // Handle bridge & execute result where intent is nested - let intent: Intent | undefined = undefined; - if ('intent' in simulationResult) { - intent = (simulationResult as SimulationResult)?.intent; - } else if ('bridgeSimulation' in simulationResult && simulationResult?.bridgeSimulation) { - const simulation = simulationResult as BridgeAndExecuteSimulationResult; - intent = simulation?.bridgeSimulation?.intent; - const fees = { - total: simulation?.totalEstimatedCost?.total ?? '0', - ...simulation?.bridgeSimulation?.intent?.fees, - } as FeesInfo; - - return { - contractAddress: simulation?.executeSimulation?.contractAddress ?? '', - functionName: simulation?.executeSimulation?.functionName ?? '', - destination: intent?.destination as ChainInfo, - sources: (intent?.sources || []) as ChainInfo[], - fees: fees, - token: intent?.token as TokenInfo, - sourcesTotal: intent?.sourcesTotal as string, - }; - } - - if (!intent) return null; - - return { - destination: intent?.destination as ChainInfo, - sources: (intent?.sources || []) as ChainInfo[], - fees: intent?.fees as FeesInfo, - token: intent?.token as TokenInfo, - sourcesTotal: intent?.sourcesTotal ?? '0', - }; - }; - - const data = getSimulationData(); - - console.log('Simulation data', data); - - const getDestinationChain = () => { - if (inputData?.toChainId) return inputData.toChainId; - if (inputData?.chainId) return inputData.chainId; - return data?.destination?.chainID; - }; - - const destinationChainId = getDestinationChain(); - const destinationChain = destinationChainId ? CHAIN_METADATA[destinationChainId] : null; - - if (!data) return null; - - return ( - - - View Full Transaction Details - - - - - Transaction Details - - - - - -
- {/* Estimated Time */} -
-
- -

- Estimated Transaction time -

-
- - ~{type === 'bridgeAndExecute' ? '1.5 mins' : '30 seconds'} - -
- - {/* Total Fees */} -
-
- -

- Total Fees -

-
-
- - {formatCost(data.fees.total)} {inputData?.token || data.token.symbol} - - {inputData?.token && ( -

- {getFiatValue(data.fees.total, inputData?.token, exchangeRates)} -

- )} -
-
- - {/* Contract Address */} - {data?.contractAddress && data?.functionName && ( -
-
- -

- {data?.functionName} to -

-
- - {truncateAddress(data?.contractAddress, 4, 4)} - -
- )} - - {/* Sending */} -
-
- -

- Sending -

-
-
-
-

- {inputData?.amount || data.sourcesTotal} {inputData?.token || data.token.symbol} -

- {inputData?.token && ( -

- {getFiatValue(data.sourcesTotal, inputData?.token, exchangeRates)} -

- )} -
- {destinationChain && ( - <> -

on

- {destinationChain.name} - - {destinationChain.name} - - - )} -
-
- - {/* From Section */} - {Array.isArray(data.sources) && data.sources.length > 0 && ( -
-

From

-
- {data.sources.map((source) => { - const chainMeta = CHAIN_METADATA[source.chainID]; - return ( -
-
- {chainMeta?.name -
-
- {inputData?.token || data.token.symbol} -
-
- on {chainMeta?.name || 'Unknown Chain'} -
-
-
-
-
- {source.amount} -
-
- {inputData?.token && - getFiatValue(source.amount, inputData?.token, exchangeRates)} -
-
-
- ); - })} -
-
- )} -
- - - - - -
-
- ); -} diff --git a/packages/widgets/src/components/shared/unified-balance.tsx b/packages/widgets/src/components/shared/unified-balance.tsx deleted file mode 100644 index bcd83406..00000000 --- a/packages/widgets/src/components/shared/unified-balance.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import { useMemo } from 'react'; -import { - Drawer, - DrawerContent, - DrawerTrigger, - DrawerHeader, - DrawerTitle, - DrawerClose, -} from '../motion/drawer'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { - type SUPPORTED_TOKENS, - type UserAsset, - CHAIN_METADATA, - SUPPORTED_CHAINS, -} from '@nexus/commons'; -import SolarWallet from '../icons/SolarWallet'; -import { ChevronDownIcon, CircleX } from '../icons'; -import { cn, getTokenFromInputData } from '../../utils/utils'; -import { TokenIcon } from './icons'; - -const BalanceTrigger = ({ balance, token }: { balance?: UserAsset; token?: SUPPORTED_TOKENS }) => { - return ( -
-
- - {balance && token ? ( -
-

Total {token}

-

- accross {balance?.breakdown?.length} chains -

-
- ) : ( -

Select token to view cross chain balance

- )} -
- {balance && token && ( -
-
-

- {parseFloat(balance?.balance).toFixed(6)} {token} -

-

- β‰ˆ ${balance?.balanceInFiat} -

-
- -
- )} -
- ); -}; - -const AllBalancesTrigger = ({ balances }: { balances: UserAsset[] }) => { - const totalFiat = useMemo(() => { - return balances?.reduce((sum, asset) => sum + (asset?.balanceInFiat || 0), 0) || 0; - }, [balances]); - - const { tokenCount, uniqueChainCount } = useMemo(() => { - const chains = new Set(); - balances?.forEach((asset) => { - asset?.breakdown?.forEach((b) => { - if (b?.chain?.id != null) chains.add(b.chain.id); - }); - }); - return { tokenCount: balances?.length || 0, uniqueChainCount: chains.size }; - }, [balances]); - - return ( -
-
- -
-

Unified Balance

-

- across {tokenCount} tokens β€’ {uniqueChainCount} chains -

-
-
-
-
-

- β‰ˆ ${totalFiat.toFixed(2)} -

-
- -
-
- ); -}; - -const ChainBalance = ({ - balance, - symbol, -}: { - balance: { - balance: string; - balanceInFiat: number; - chain: { - id: number; - logo: string; - name: string; - }; - contractAddress: `0x${string}`; - decimals: number; - isNative?: boolean; - }; - symbol: string; -}) => { - return ( -
-
- {CHAIN_METADATA[balance.chain.id]?.name} -
-

- {symbol} -

-

- on {balance.chain?.name || `Chain ${balance.chain?.id}`} -

-
-
-
-

- {parseFloat(balance.balance).toFixed(2)} -

-

- ${balance.balanceInFiat} -

-
-
- ); -}; - -const UnifiedBalance = () => { - const { unifiedBalance, activeTransaction } = useInternalNexus(); - const { inputData } = activeTransaction; - const tokenSymbol = getTokenFromInputData(inputData); - - const relevantBalance = useMemo(() => { - if (!unifiedBalance || !tokenSymbol) return [] as UserAsset[]; - return unifiedBalance.filter((balance) => balance?.symbol === tokenSymbol); - }, [tokenSymbol, unifiedBalance]); - - const tokenBalance = relevantBalance[0]; - - if (!unifiedBalance) return null; - - if (!tokenSymbol) - return ( - - - - - - -
- Balances Across Tokens - - - -
-
- -
- {unifiedBalance.map((asset) => { - return ( -
-
-
- {asset.symbol ? ( - - ) : null} -

- Total {asset.symbol} -

-
-
-

- {parseFloat(asset.balance) > 0 - ? parseFloat(asset.balance).toFixed(2) - : '0.00'}{' '} - {asset.symbol} -

-

- β‰ˆ ${asset.balanceInFiat} -

-
-
- - {parseFloat(asset.balance) > 0 && ( -
- {asset.breakdown?.map((breakdownBalance, index: number) => ( - - ))} -
- )} -
- ); - })} -
-
-
- ); - - if (!tokenBalance) return null; - - return ( - - - - - - -
- Balance Across Chains - - - -
-
- -
- {/* Total Balance */} -
-
- -

- Total {tokenSymbol} -

-
-
-

- {parseFloat(tokenBalance.balance).toFixed(6)} {tokenSymbol} -

-

- β‰ˆ ${tokenBalance.balanceInFiat} -

-
-
- - {/* Individual Chain Balances */} - {parseFloat(tokenBalance.balance) > 0 && ( -
- {tokenBalance.breakdown?.map((breakdownBalance, index: number) => ( - - ))} -
- )} -
-
-
- ); -}; - -export default UnifiedBalance; diff --git a/packages/widgets/src/components/shared/unified-transaction-form.tsx b/packages/widgets/src/components/shared/unified-transaction-form.tsx deleted file mode 100644 index abc68b54..00000000 --- a/packages/widgets/src/components/shared/unified-transaction-form.tsx +++ /dev/null @@ -1,378 +0,0 @@ -import { AmountInput } from './amount-input'; -import { AddressField } from './address-field'; -import { cn } from '../../utils/utils'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { type TransactionType } from '../../utils/balance-utils'; -import { useMemo, useEffect } from 'react'; -import { CHAIN_METADATA, NexusNetwork } from '@nexus/commons'; -import { FormField } from '../motion/form-field'; -import DestinationDrawer from './destination-drawer'; -import { isAddress } from 'viem'; -import { SwapSimulationResult, SwapInputData } from 'src/types'; -import { isTokenChainCombinationValid } from '../../utils/token-utils'; - -export interface UnifiedInputData { - chainId?: number; - toChainId?: number; - token?: string; - inputToken?: string; - outputToken?: string; - amount?: string | number; - recipient?: string; -} - -interface UnifiedTransactionFormProps { - type: TransactionType; - inputData: UnifiedInputData; - onUpdate: (data: UnifiedInputData) => void; - disabled?: boolean; - className?: string; - prefillFields?: { - chainId?: boolean; - toChainId?: boolean; - token?: boolean; - inputToken?: boolean; - outputToken?: boolean; - amount?: boolean; - recipient?: boolean; - }; -} - -interface SwapTransactionFormProps { - inputData: SwapInputData; - onUpdate: (data: SwapInputData) => void; - disabled?: boolean; - className?: string; - prefillFields?: { - fromChainID?: boolean; - toChainID?: boolean; - fromTokenAddress?: boolean; - toTokenAddress?: boolean; - fromAmount?: boolean; - toAmount?: boolean; - }; -} - -interface SwapFormProps { - title: string; - inputData: SwapInputData; - isAmountDisabled?: boolean; - handleUpdate: (data: Partial) => void; - isChainSelectDisabled?: boolean; - isTokenSelectDisabled?: boolean; - isOutputTokenSelectDisabled?: boolean; - network?: NexusNetwork; - destinationAmount?: string; -} - -const FORM_CONFIG = { - bridge: { - chainLabel: 'Destination Network', - tokenLabel: 'Token to be transferred', - chainField: 'chainId', - showRecipient: false, - showOutputToken: false, - showDestinationAmount: false, - }, - bridgeAndExecute: { - chainLabel: 'Destination Network', - tokenLabel: 'Token to be deposited', - chainField: 'toChainId', - showRecipient: false, - showOutputToken: false, - showDestinationAmount: false, - }, - transfer: { - chainLabel: 'Source Network', - tokenLabel: 'Token to transfer', - chainField: 'chainId', - showRecipient: true, - showOutputToken: false, - showDestinationAmount: false, - }, - swap: { - chainLabel: 'Source Network', - tokenLabel: 'Input Token', - outputTokenLabel: 'Output Token', - chainField: 'fromChainID', - toChainField: 'toChainID', - showRecipient: false, - showDestinationAmount: true, - showOutputToken: true, - showDestinationChain: true, - }, -} as const; - -const SwapForm = ({ - title, - inputData, - isAmountDisabled, - handleUpdate, - isChainSelectDisabled, - isTokenSelectDisabled, - isOutputTokenSelectDisabled, - network = 'mainnet', - destinationAmount, -}: SwapFormProps) => { - return ( -
-
- - handleUpdate({ fromAmount: value, toAmount: value }) - } - token={inputData?.fromTokenAddress} - debounceMs={1000} - /> - - - { - if (isChainSelectDisabled) return; - handleUpdate({ fromChainID: parseInt(chainId, 10) as any }); - }} - onTokenValueChange={(token) => { - if (!isTokenSelectDisabled) { - handleUpdate({ fromTokenAddress: token as any }); - } - }} - isTokenSelectDisabled={isTokenSelectDisabled} - isChainSelectDisabled={isChainSelectDisabled} - network={network} - drawerTitle="Select Source Chain & Token" - fieldLabel="Source" - type="swap" - isSourceChain={true} - /> -
-
- - - - - { - if (isChainSelectDisabled) return; - handleUpdate({ toChainID: parseInt(chainId, 10) as any }); - }} - onTokenValueChange={(token) => { - if (!isOutputTokenSelectDisabled) { - handleUpdate({ toTokenAddress: token as any }); - } - }} - isTokenSelectDisabled={isOutputTokenSelectDisabled} - isChainSelectDisabled={isChainSelectDisabled} - network={network} - drawerTitle="Select Destination Chain & Token" - fieldLabel="Destination" - type="swap" - isDestination={true} - isSourceChain={false} - /> -
-
- ); -}; - -export function SwapTransactionForm({ - inputData, - onUpdate, - disabled = false, - className, - prefillFields = {}, -}: Readonly) { - const { config, isSimulating, activeTransaction } = useInternalNexus(); - - const isInputDisabled = disabled || isSimulating; - const isChainSelectDisabled = isInputDisabled || prefillFields.fromChainID; - const isTokenSelectDisabled = isInputDisabled || prefillFields.fromTokenAddress; - const isOutputTokenSelectDisabled = isInputDisabled || prefillFields.toTokenAddress; - const isAmountDisabled = isInputDisabled || prefillFields.fromAmount; - - const title = useMemo(() => { - const fromToken = inputData?.fromTokenAddress; - const toToken = inputData?.toTokenAddress; - if (fromToken && toToken) { - return `Swapping (${fromToken} β†’ ${toToken})`; - } - return 'Swap'; - }, [inputData?.fromTokenAddress, inputData?.toTokenAddress]); - - const handleUpdate = (data: Partial) => { - onUpdate({ ...inputData, ...data }); - }; - - // Reset token when chain changes to invalid combination (disabled for swaps to prevent aggressive resets) - useEffect(() => { - // For swaps, we allow users to make selections and validate at execution time - // This prevents tokens from being reset when switching between valid chains - if (inputData.fromChainID && inputData.fromTokenAddress) { - // Skip validation for swaps to maintain user selections - const shouldReset = false; - if (shouldReset) { - handleUpdate({ fromTokenAddress: undefined }); - } - } - }, [inputData.fromChainID]); - - useEffect(() => { - // For swaps, we allow users to make selections and validate at execution time - if (inputData.toChainID && inputData.toTokenAddress) { - // Skip validation for swaps to maintain user selections - const shouldReset = false; - if (shouldReset) { - handleUpdate({ toTokenAddress: undefined }); - } - } - }, [inputData.toChainID]); - - const destinationAmount = useMemo(() => { - const intent = (activeTransaction?.simulationResult as SwapSimulationResult)?.intent; - if (intent?.destination?.amount) { - return parseFloat(intent.destination.amount).toFixed(6); - } - return '0'; - }, [activeTransaction?.simulationResult]); - - return ( -
- -
- ); -} - -export function UnifiedTransactionForm({ - type, - inputData, - onUpdate, - disabled = false, - className, - prefillFields = {}, -}: Readonly) { - const { config, isSimulating } = useInternalNexus(); - - const formConfig = FORM_CONFIG[type]; - const isInputDisabled = disabled || isSimulating; - const isChainSelectDisabled = - isInputDisabled || prefillFields[formConfig.chainField as keyof typeof prefillFields]; - const isTokenSelectDisabled = isInputDisabled || prefillFields.token || prefillFields.inputToken; - const isAmountDisabled = isInputDisabled || prefillFields.amount; - const isReceipientDisabled = isInputDisabled || prefillFields.recipient; - - const title = useMemo(() => { - const chainId = inputData?.chainId || inputData?.toChainId; - const token = inputData?.token || inputData?.inputToken; - - if (chainId && token) { - return `Sending (${token} to ${CHAIN_METADATA[chainId]?.name})`; - } - return 'Sending'; - }, [inputData, type]); - - const hasValidationError = useMemo( - () => inputData?.recipient && !isAddress(inputData?.recipient ?? ''), - [inputData?.recipient], - ); - - const handleUpdate = (data: UnifiedInputData) => { - onUpdate(data); - }; - - // Reset token when chain changes to invalid combination for bridge/bridgeAndExecute - useEffect(() => { - if (type === 'bridge' || type === 'bridgeAndExecute') { - const chainId = type === 'bridgeAndExecute' ? inputData.toChainId : inputData.chainId; - if (chainId && inputData.token) { - if (!isTokenChainCombinationValid(inputData.token, chainId, type)) { - handleUpdate({ token: undefined }); - } - } - } - }, [inputData.chainId, inputData.toChainId, type]); - - return ( -
-
-
- - handleUpdate({ amount: value })} - token={inputData?.token || inputData?.inputToken} - debounceMs={1000} - /> - - - { - if (isChainSelectDisabled) return; - const fieldName = formConfig.chainField; - handleUpdate({ [fieldName]: parseInt(chainId, 10) }); - }} - onTokenValueChange={(token) => { - if (!isTokenSelectDisabled) { - handleUpdate({ token }); - } - }} - isTokenSelectDisabled={isTokenSelectDisabled} - isChainSelectDisabled={isChainSelectDisabled} - network={config?.network ?? 'mainnet'} - /> -
- - {formConfig.showRecipient && ( - - { - if (!isReceipientDisabled) { - handleUpdate({ recipient: value }); - } - }} - disabled={isReceipientDisabled} - /> - - )} -
-
- ); -} diff --git a/packages/widgets/src/components/shared/unified-transaction-modal.tsx b/packages/widgets/src/components/shared/unified-transaction-modal.tsx deleted file mode 100644 index 00a0b2cf..00000000 --- a/packages/widgets/src/components/shared/unified-transaction-modal.tsx +++ /dev/null @@ -1,324 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import { BaseModal } from '../motion/base-modal'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { - cn, - getContentKey, - getModalTitle, - getPrimaryButtonText, - getTokenFromInputData, - getAmountFromInputData, -} from '../../utils/utils'; -import { type TransactionType } from '../../utils/balance-utils'; -import { TransactionSimulation } from '../processing/transaction-simulation'; -import { AvailLogo } from '../icons/AvailLogo'; -import UnifiedBalance from './unified-balance'; -import { InfoMessage } from './info-message'; -import { AllowanceForm } from './allowance-form'; -import { DialogFooter, DialogHeader, DialogTitle } from '../motion/dialog-motion'; -import { SlideTransition } from '../motion/slide-transition'; -import { EnhancedInfoMessage } from './enhanced-info-message'; -import { ActionButtons } from './action-buttons'; -import type { UnifiedInputData, SwapInputData } from '../../types'; - -interface UnifiedTransactionModalProps { - transactionType: TransactionType; - modalTitle: string; - FormComponent: React.ComponentType<{ - inputData: UnifiedInputData | SwapInputData; - onUpdate: (data: UnifiedInputData | SwapInputData) => void; - disabled: boolean; - prefillFields?: any; - }>; - getSimulationError?: (simulationResult: any) => boolean; - getMinimumAmount?: (simulationResult: any) => string; - getSourceChains?: ( - simulationResult: any, - ) => { chainId: number; amount: string; needsApproval?: boolean }[]; - transformInputData?: (inputData: any) => any; -} - -export function UnifiedTransactionModal({ - transactionType, - modalTitle, - FormComponent, - getSimulationError, - getMinimumAmount, - getSourceChains, - transformInputData, -}: Readonly) { - const { - activeTransaction, - activeController, - updateInput, - confirmAndProceed, - cancelTransaction, - initializeSdk, - triggerSimulation, - retrySimulation, - isSdkInitialized, - isSimulating, - insufficientBalance, - allowanceError, - isSettingAllowance, - approveAllowance, - denyAllowance, - startAllowanceFlow, - initiateSwap, - proceedWithSwap, - } = useInternalNexus(); - - const { status, reviewStatus, inputData, simulationResult, type, prefillFields } = - activeTransaction; - const [isInitializing, setIsInitializing] = useState(false); - const [allowanceFormValid, setAllowanceFormValid] = useState(false); - const [allowanceApproveHandler, setAllowanceApproveHandler] = useState<(() => void) | null>(null); - - const handleAllowanceFormStateChange = useCallback( - (isValid: boolean, handler: () => void) => { - setAllowanceFormValid(isValid); - setAllowanceApproveHandler(() => handler); - }, - [setAllowanceFormValid, setAllowanceApproveHandler], - ); - - // Helper function to check sufficient input for both regular transactions and swaps - const checkHasSufficientInput = useCallback( - (inputData: any) => { - if (!inputData) return false; - - if (transactionType === 'swap') { - // For swaps, check input directly (since activeController is null) - const data = inputData as Partial; - return !!( - data.fromChainID && - data.toChainID && - data.fromTokenAddress && - data.toTokenAddress && - data.fromAmount && - parseFloat(data.fromAmount?.toString() || '0') > 0 - ); - } else { - // For regular transactions, use activeController - return activeController?.hasSufficientInput(inputData || {}) || false; - } - }, - [transactionType, activeController], - ); - - // Type guard - return null if wrong transaction type - if (type !== transactionType) { - return null; - } - - const isOpen = - status !== 'idle' && status !== 'processing' && status !== 'success' && status !== 'error'; - const isBusy = status === 'processing' || reviewStatus === 'simulating'; - - const handleInitialize = async () => { - try { - setIsInitializing(true); - await initializeSdk(); - } finally { - setIsInitializing(false); - } - }; - - const handleReviewGatheringInput = async () => { - if (!checkHasSufficientInput(inputData)) return; - if (transactionType === 'swap') { - await initiateSwap(inputData as SwapInputData); - return; - } - triggerSimulation(); - }; - - const handleReviewReady = () => { - if (transactionType === 'swap') { - proceedWithSwap(); - return; - } - confirmAndProceed(); - }; - - const handleSetAllowance = () => { - if (allowanceApproveHandler) allowanceApproveHandler(); - }; - - const handleButtonClick = async () => { - // Early-return guards to keep complexity low - if (status === 'initializing') return handleInitialize(); - if (status === 'simulation_error') return retrySimulation(); - - if (status === 'review') { - if (reviewStatus === 'gathering_input') return handleReviewGatheringInput(); - if (reviewStatus === 'ready') return handleReviewReady(); - if (reviewStatus === 'needs_allowance') return startAllowanceFlow(); - } - - if (status === 'set_allowance') return handleSetAllowance(); - - return confirmAndProceed(); - }; - - const debouncedClick = () => { - setTimeout(handleButtonClick, 500); - }; - - const hasSufficientInput = checkHasSufficientInput(inputData); - const shouldShowSimulation = isSdkInitialized && hasSufficientInput; - const transformedInputData = transformInputData ? transformInputData(inputData) : inputData; - - const renderAllowanceContent = () => { - if (!simulationResult || !inputData) return null; - - // Get minimum amount and source chains using provided functions or defaults - const minimumAmount = getMinimumAmount ? getMinimumAmount(simulationResult) : '0'; - const sourceChains = getSourceChains ? getSourceChains(simulationResult) : []; - - return ( - - ); - }; - - const showFooterButtons = status !== 'processing' && status !== 'success' && status !== 'error'; - - const preventClose = status === 'processing' || reviewStatus === 'simulating'; - - const showHeader = - activeTransaction?.status !== 'processing' && - activeTransaction?.status !== 'success' && - activeTransaction?.status !== 'error'; - - const isPrimaryLoading = - isBusy || isInitializing || (status === 'set_allowance' && isSettingAllowance); - - return ( - {} : cancelTransaction} - hideCloseButton={true} - > - {/* Header - Fixed at top */} - {showHeader && ( - - - - {getModalTitle(status, modalTitle)} - - - )} - - {/* Content - Flexible middle area */} -
- - {(status === 'initializing' || status === 'review' || status === 'simulation_error') && ( - <> - - - -
- {!isSdkInitialized && ( - - Sign a quick message to turn on cross-chain transfers. Don't worry - it's gasless & no funds will move yet. - - )} - - {isSdkInitialized && insufficientBalance && ( - -
-

- Insufficient {getTokenFromInputData(inputData)} balance -

-

- You don't have enough {getTokenFromInputData(inputData)} to complete this - transaction. - {transactionType === 'bridgeAndExecute' - ? ' Consider using a smaller amount or add more funds to your wallet.' - : ' Please add more funds to your wallet or reduce the transaction amount.'} -

-
-
- )} - {(activeTransaction?.error && status === 'simulation_error') || - (simulationResult && getSimulationError && getSimulationError(simulationResult)) ? ( - - ) : ( - shouldShowSimulation && - !insufficientBalance && - status !== 'simulation_error' && - type !== 'swap' && ( - - ) - )} -
- - )} - {status === 'set_allowance' && <>{renderAllowanceContent()}} -
-
- - {/* Footer - Fixed at bottom */} - {showFooterButtons && ( - - - - )} -
- ); -} diff --git a/packages/widgets/src/components/swap/swap-button.tsx b/packages/widgets/src/components/swap/swap-button.tsx deleted file mode 100644 index d68e92d6..00000000 --- a/packages/widgets/src/components/swap/swap-button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client'; -import { FC } from 'react'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import { SwapButtonProps } from '../../types'; -import SwapModal from './swap-modal'; - -export const SwapButton: FC = ({ prefill, children, className, title }) => { - const { startTransaction, activeTransaction, config } = useInternalNexus(); - - if (config?.network === 'testnet') { - throw new Error('Testnet is not supported'); - } - - const isLoading = - activeTransaction.status === 'processing' || activeTransaction.reviewStatus === 'simulating'; - - const handleClick = () => { - startTransaction('swap', prefill); - }; - - return ( - <> -
{children({ onClick: handleClick, isLoading })}
- - - ); -}; diff --git a/packages/widgets/src/components/swap/swap-modal.tsx b/packages/widgets/src/components/swap/swap-modal.tsx deleted file mode 100644 index 27403568..00000000 --- a/packages/widgets/src/components/swap/swap-modal.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { UnifiedTransactionModal } from '../shared/unified-transaction-modal'; -import { SwapTransactionForm, UnifiedInputData } from '../shared/unified-transaction-form'; -import { SwapSimulationResult, SwapInputData } from '../../types'; -import SwapPrefilledInputs from '../shared/swap-prefilled-inputs'; - -interface SwapFormSectionProps { - inputData: SwapInputData | UnifiedInputData; - onUpdate: (data: SwapInputData | UnifiedInputData) => void; - disabled: boolean; - prefillFields?: any; -} - -function SwapFormSection({ - inputData, - onUpdate, - disabled = false, - prefillFields = {}, -}: Readonly) { - const swapInputData = inputData as SwapInputData; - const requiredFields = [ - 'fromChainID', - 'toChainID', - 'fromTokenAddress', - 'toTokenAddress', - 'fromAmount', - ]; - // Check if fields are actually prefilled (boolean values in prefillFields indicate prefilled fields) - const hasPrefilledInputs = requiredFields.every((field) => prefillFields[field] === true); - - if (hasPrefilledInputs) { - return ; - } - - return ( - void} - disabled={disabled} - prefillFields={prefillFields} - /> - ); -} - -export default function SwapModal({ title = 'Nexus Widget' }: Readonly<{ title?: string }>) { - const getSimulationError = (simulationResult: SwapSimulationResult): boolean => { - if (!simulationResult) return true; - return ( - simulationResult.success === false || - Boolean(simulationResult.error) || - !simulationResult.intent - ); - }; - const transformInputData = (inputData: SwapInputData | null | undefined) => { - if (!inputData) return {}; - return inputData; - }; - - return ( - - ); -} diff --git a/packages/widgets/src/components/transfer/transfer-button.tsx b/packages/widgets/src/components/transfer/transfer-button.tsx deleted file mode 100644 index 6841a458..00000000 --- a/packages/widgets/src/components/transfer/transfer-button.tsx +++ /dev/null @@ -1,26 +0,0 @@ -'use client'; -import type { TransferButtonProps } from '../../types'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; -import TransferModal from './transfer-modal'; - -export function TransferButton({ - prefill, - children, - className, - title, -}: Readonly) { - const { startTransaction, activeTransaction } = useInternalNexus(); - const isLoading = - activeTransaction.status === 'processing' || activeTransaction.reviewStatus === 'simulating'; - - const handleClick = () => { - startTransaction('transfer', prefill); - }; - - return ( - <> -
{children({ onClick: handleClick, isLoading })}
- - - ); -} diff --git a/packages/widgets/src/components/transfer/transfer-modal.tsx b/packages/widgets/src/components/transfer/transfer-modal.tsx deleted file mode 100644 index 5f66bc1d..00000000 --- a/packages/widgets/src/components/transfer/transfer-modal.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { UnifiedTransactionModal } from '../shared/unified-transaction-modal'; -import { SimulationResult } from '@nexus/commons'; -import { UnifiedInputData, UnifiedTransactionForm } from '../shared/unified-transaction-form'; -import { SwapInputData } from '../../types'; -import PrefilledInputs from '../shared/prefilled-inputs'; -import { useInternalNexus } from '../../providers/InternalNexusProvider'; - -interface TransferFormSectionProps { - inputData: UnifiedInputData | SwapInputData; - onUpdate: (data: UnifiedInputData | SwapInputData) => void; - disabled: boolean; - prefillFields?: any; -} - -type InputData = { - chainId?: number; - token?: string; - amount?: string | number; - recipient?: string; -}; - -function TransferFormSection({ - inputData, - onUpdate, - disabled = false, - prefillFields = {}, -}: Readonly) { - const { activeController } = useInternalNexus(); - - if (!activeController) return null; - - // Cast to UnifiedInputData since transfer operations only use this type - const transferInputData = inputData as UnifiedInputData; - - const requiredFields: (keyof InputData)[] = ['chainId', 'token', 'amount', 'recipient']; - const hasEnoughInputs = requiredFields.every((field) => prefillFields[field] === true); - - if (hasEnoughInputs) { - return ; - } - - return ( - void} - disabled={disabled} - prefillFields={prefillFields} - /> - ); -} - -export default function TransferModal({ title = 'Nexus Widget' }: Readonly<{ title?: string }>) { - const getSimulationError = (simulationResult: SimulationResult) => { - return simulationResult && !simulationResult.intent; - }; - - const getMinimumAmount = (simulationResult: SimulationResult) => { - return simulationResult?.intent?.sourcesTotal || '0'; - }; - - const getSourceChains = ( - simulationResult: SimulationResult & { - allowance?: { - chainDetails?: Array<{ chainId: number; amount: string; needsApproval: boolean }>; - }; - }, - ) => { - // Use chainDetails from allowance if available (provides needsApproval info) - if (simulationResult?.allowance?.chainDetails) { - return simulationResult.allowance.chainDetails; - } - - // Fallback to original sources mapping - return ( - simulationResult?.intent?.sources?.map((source) => ({ - chainId: source.chainID, - amount: source.amount, - })) || [] - ); - }; - - return ( - - ); -} diff --git a/packages/widgets/src/controllers/BridgeAndExecuteController.tsx b/packages/widgets/src/controllers/BridgeAndExecuteController.tsx deleted file mode 100644 index 42f9ac4e..00000000 --- a/packages/widgets/src/controllers/BridgeAndExecuteController.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import React from 'react'; -import type { ITransactionController, ActiveTransaction } from '../types'; -import { NexusSDK } from '@avail-project/nexus-core'; -import { - UnifiedTransactionForm, - UnifiedInputData, -} from '../components/shared/unified-transaction-form'; - -import { - type DynamicParamBuilder, - type ExecuteParams, - type SUPPORTED_TOKENS, - type SUPPORTED_CHAINS_IDS, - type BridgeAndExecuteParams, - type BridgeAndExecuteResult, - type BridgeAndExecuteSimulationResult, - logger, -} from '@nexus/commons'; -import { Abi } from 'viem'; - -export interface BridgeAndExecuteConfig extends Partial {} - -const BridgeAndExecuteInputForm: React.FC<{ - prefill: Partial; - onUpdate: (data: Partial) => void; - isBusy: boolean; - prefillFields?: { - toChainId?: boolean; - token?: boolean; - amount?: boolean; - }; -}> = ({ prefill, onUpdate, isBusy, prefillFields = {} }) => { - // Transform BridgeAndExecuteConfig to UnifiedInputData - const unifiedInputData: UnifiedInputData = { - toChainId: prefill?.toChainId, - token: prefill?.token, - amount: prefill?.amount, - }; - - // Transform UnifiedInputData back to BridgeAndExecuteConfig - const handleUpdate = (data: UnifiedInputData) => { - // Only include defined values to avoid overwriting existing data - const transformedData: any = {}; - if (data.toChainId !== undefined) transformedData.toChainId = data.toChainId; - if (data.token !== undefined) transformedData.token = data.token; - if (data.amount !== undefined) transformedData.amount = data.amount; - - onUpdate(transformedData); - }; - - return ( - - ); -}; - -export class BridgeAndExecuteController implements ITransactionController { - InputForm = BridgeAndExecuteInputForm; - - hasSufficientInput(inputData: Partial): boolean { - const { - token, - amount, - toChainId, - contractAddress, - contractAbi, - functionName, - buildFunctionParams, - } = inputData as any; - - if (!token || !amount || !toChainId) return false; - if (!contractAddress || !contractAbi || !functionName || !buildFunctionParams) return false; - - const amt = parseFloat(amount.toString()); - return !isNaN(amt) && amt > 0; - } - - private buildExecute(inputData: { - token: SUPPORTED_TOKENS; - amount: string | number; - toChainId: SUPPORTED_CHAINS_IDS; - contractAddress: `0x${string}`; - contractAbi: Abi; - functionName: string; - buildFunctionParams: DynamicParamBuilder; - }): Omit { - // Return new callback-based execute params directly - return { - contractAddress: inputData.contractAddress, - contractAbi: inputData.contractAbi, - functionName: inputData.functionName, - buildFunctionParams: inputData.buildFunctionParams, - tokenApproval: - inputData.token !== 'ETH' - ? { - token: inputData.token, - amount: inputData.amount.toString(), - } - : undefined, - }; - } - - async runReview( - sdk: NexusSDK, - inputData: Partial, - ): Promise { - let params: BridgeAndExecuteParams = inputData as BridgeAndExecuteParams; - if (!params.execute) { - const execute = this.buildExecute(inputData as any); - params = { ...inputData, execute } as BridgeAndExecuteParams; - } - const simulationResult = await sdk.simulateBridgeAndExecute(params); - logger.info('bridgeAndExecute simulationResult', simulationResult); - - let needsApproval = false; - const chainDetails: Array<{ - chainId: number; - amount: string; - needsApproval: boolean; - }> = []; - - // Check if bridge part needs allowance (when bridge is NOT skipped) - if (simulationResult?.bridgeSimulation?.intent?.sources && inputData.token !== 'ETH') { - const sourcesData = simulationResult.bridgeSimulation.intent.sources; - - for (const source of sourcesData) { - const requiredAmount = sdk.utils.parseUnits( - source.amount, - sdk.utils.getTokenMetadata(inputData.token!)?.decimals ?? 18, - ); - - const allowances = await sdk.getAllowance(source.chainID, [inputData.token!]); - logger.info(`bridgeAndExecute bridge allowances for chain ${source.chainID}:`, allowances); - - const currentAllowance = allowances[0]?.allowance ?? 0n; - const chainNeedsApproval = currentAllowance < requiredAmount; - - if (chainNeedsApproval) { - needsApproval = true; - logger.info( - `BridgeAndExecute bridge allowance needed on chain ${source.chainID}: required=${requiredAmount.toString()}, current=${currentAllowance.toString()}`, - ); - } - - chainDetails.push({ - chainId: source.chainID, - amount: requiredAmount.toString(), - needsApproval: chainNeedsApproval, - }); - } - } - - // Also check if contract execution needs approval (when bridge is skipped) - // This is handled by the execute service internally, but we can inform the UI - const contractApprovalNeeded = !!simulationResult?.metadata?.approvalRequired; - if (contractApprovalNeeded) { - needsApproval = true; - } - - return { - ...simulationResult, - allowance: { - needsApproval, - chainDetails: chainDetails.length > 0 ? chainDetails : undefined, - }, - } as BridgeAndExecuteSimulationResult & { - allowance: { - needsApproval: boolean; - chainDetails?: Array<{ - chainId: number; - amount: string; - needsApproval: boolean; - }>; - }; - }; - } - - async confirmAndProceed( - sdk: NexusSDK, - inputData: Partial, - _simulationResult?: ActiveTransaction['simulationResult'], - ): Promise { - let params: BridgeAndExecuteParams = inputData as BridgeAndExecuteParams; - - if (!params.execute) { - const execute = this.buildExecute(inputData as any); - params = { ...inputData, execute } as BridgeAndExecuteParams; - } - - const result = await sdk.bridgeAndExecute(params); - return result; - } -} diff --git a/packages/widgets/src/controllers/BridgeController.tsx b/packages/widgets/src/controllers/BridgeController.tsx deleted file mode 100644 index 6b9e4e22..00000000 --- a/packages/widgets/src/controllers/BridgeController.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import type { ITransactionController, BridgeConfig, ActiveTransaction } from '../types'; -import { NexusSDK } from '@avail-project/nexus-core'; -import { type BridgeParams, type BridgeResult, logger } from '@nexus/commons'; -import { - UnifiedTransactionForm, - UnifiedInputData, -} from '../components/shared/unified-transaction-form'; - -const BridgeInputForm: React.FC<{ - prefill: Partial; - onUpdate: (data: Partial) => void; - isBusy: boolean; - prefillFields?: { - chainId?: boolean; - toChainId?: boolean; - token?: boolean; - amount?: boolean; - recipient?: boolean; - }; -}> = ({ prefill, onUpdate, isBusy, prefillFields = {} }) => { - // Transform BridgeConfig to UnifiedInputData - const unifiedInputData: UnifiedInputData = { - chainId: prefill?.chainId, - toChainId: prefill?.chainId, // Bridge uses same source chain - token: prefill?.token, - amount: prefill?.amount, - }; - - // Transform UnifiedInputData back to BridgeConfig - const handleUpdate = (data: UnifiedInputData) => { - onUpdate({ - chainId: data.chainId as any, - token: data.token as any, - amount: data.amount, - }); - }; - - return ( - - ); -}; - -export class BridgeController implements ITransactionController { - InputForm = BridgeInputForm; - - hasSufficientInput(inputData: Partial): boolean { - if (!inputData.amount || !inputData.chainId || !inputData.token) { - return false; - } - - const amount = parseFloat(inputData.amount.toString()); - return !isNaN(amount) && amount > 0; - } - - async runReview( - sdk: NexusSDK, - inputData: BridgeParams, - ): Promise { - const simulationResult = await sdk.simulateBridge(inputData); - logger.info('bridge simulationResult', simulationResult); - - const sourcesData = simulationResult?.intent?.sources || []; - let needsApproval = false; - const chainDetails: Array<{ - chainId: number; - amount: string; - needsApproval: boolean; - }> = []; - - for (const source of sourcesData) { - if (inputData?.token === 'ETH') { - chainDetails.push({ - chainId: source.chainID, - amount: source.amount, - needsApproval: false, - }); - continue; - } - - const requiredAmount = sdk.utils.parseUnits( - source.amount, - sdk.utils.getTokenMetadata(inputData.token)?.decimals ?? 18, - ); - - const allowances = await sdk.getAllowance(source.chainID, [inputData.token]); - logger.info(`allowances for chain ${source.chainID}:`, allowances); - - const currentAllowance = allowances[0]?.allowance ?? 0n; - const chainNeedsApproval = currentAllowance < requiredAmount; - - if (chainNeedsApproval) { - needsApproval = true; - logger.info( - `Allowance needed on chain ${source.chainID}: required=${requiredAmount}, current=${currentAllowance}`, - ); - } - - chainDetails.push({ - chainId: source.chainID, - amount: requiredAmount.toString(), - needsApproval: chainNeedsApproval, - }); - } - - return { - ...simulationResult, - allowance: { - needsApproval, - chainDetails, - }, - }; - } - - async confirmAndProceed(sdk: NexusSDK, inputData: BridgeParams): Promise { - const result = await sdk.bridge(inputData); - return result; - } -} diff --git a/packages/widgets/src/controllers/TransferController.tsx b/packages/widgets/src/controllers/TransferController.tsx deleted file mode 100644 index 28b73aaf..00000000 --- a/packages/widgets/src/controllers/TransferController.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React from 'react'; -import type { ITransactionController, ActiveTransaction } from '../types'; -import { NexusSDK } from '@avail-project/nexus-core'; -import { type TransferParams, type TransferResult, logger } from '@nexus/commons'; -import { - UnifiedTransactionForm, - UnifiedInputData, -} from '../components/shared/unified-transaction-form'; - -export interface TransferConfig extends Partial {} - -const TransferInputForm: React.FC<{ - prefill: Partial; - onUpdate: (data: Partial) => void; - isBusy: boolean; - prefillFields?: { - chainId?: boolean; - toChainId?: boolean; - token?: boolean; - amount?: boolean; - recipient?: boolean; - }; -}> = ({ prefill, onUpdate, isBusy, prefillFields = {} }) => { - // Transform TransferConfig to UnifiedInputData - const unifiedInputData: UnifiedInputData = { - chainId: prefill?.chainId, - toChainId: prefill?.chainId, // Transfer uses same chain - token: prefill?.token, - amount: prefill?.amount, - recipient: prefill?.recipient, - }; - - // Transform UnifiedInputData back to TransferConfig - const handleUpdate = (data: UnifiedInputData) => { - onUpdate({ - chainId: data.chainId as any, - token: data.token as any, - amount: data.amount, - recipient: data.recipient as any, // Cast to proper hex string type - }); - }; - - return ( - - ); -}; - -export class TransferController implements ITransactionController { - InputForm = TransferInputForm; - - hasSufficientInput(inputData: Partial): boolean { - if (!inputData.amount || !inputData.chainId || !inputData.token || !inputData.recipient) { - return false; - } - - const amount = parseFloat(inputData.amount.toString()); - if (isNaN(amount) || amount <= 0) { - return false; - } - - if (!/^0x[a-fA-F0-9]{40}$/.test(inputData.recipient)) { - return false; - } - - return true; - } - - async runReview( - sdk: NexusSDK, - inputData: TransferParams, - ): Promise { - const simulationResult = await sdk.simulateTransfer(inputData); - logger.info('transfer simulationResult', simulationResult); - const sourcesData = simulationResult?.intent?.sources || []; - let needsApproval = false; - const chainDetails: Array<{ - chainId: number; - amount: string; - needsApproval: boolean; - }> = []; - - for (const source of sourcesData) { - if (inputData?.token === 'ETH') { - chainDetails.push({ - chainId: source.chainID, - amount: source.amount, - needsApproval: false, - }); - continue; - } - - const requiredAmount = sdk.utils.parseUnits( - source.amount, - sdk.utils.getTokenMetadata(inputData.token)?.decimals ?? 18, - ); - const allowances = await sdk.getAllowance(source.chainID, [inputData.token]); - logger.info(`transfer allowances for chain ${source.chainID}:`, allowances); - - const currentAllowance = allowances[0]?.allowance ?? 0n; - const chainNeedsApproval = currentAllowance < requiredAmount; - - if (chainNeedsApproval) { - needsApproval = true; - logger.info( - `Transfer allowance needed on chain ${source.chainID}: required=${requiredAmount.toString()}, current=${currentAllowance.toString()}`, - ); - } - - chainDetails.push({ - chainId: source.chainID, - amount: requiredAmount.toString(), - needsApproval: chainNeedsApproval, - }); - } - - return { - ...simulationResult, - allowance: { - needsApproval, - chainDetails, - }, - }; - } - - async confirmAndProceed(sdk: NexusSDK, inputData: TransferParams): Promise { - const result = await sdk.transfer(inputData); - return result; - } -} diff --git a/packages/widgets/src/hooks/useListenTransaction.tsx b/packages/widgets/src/hooks/useListenTransaction.tsx deleted file mode 100644 index 67c943e9..00000000 --- a/packages/widgets/src/hooks/useListenTransaction.tsx +++ /dev/null @@ -1,363 +0,0 @@ -import { useEffect, useState, useCallback } from 'react'; -import { NEXUS_EVENTS } from '@nexus/commons'; -import { getStatusText } from '../utils/utils'; -import { NexusSDK } from '@avail-project/nexus-core'; -import { ActiveTransaction } from '../types'; -import { ProgressStep, ProgressSteps, SwapStep } from '@avail-project/nexus-core'; - -// Swap-specific step handling -export const getTextFromSwapStep = (step: SwapStep): string => { - switch (step.type) { - case 'CREATE_PERMIT_EOA_TO_EPHEMERAL': - return `Creating permit for eoa to ephemeral for ${step.symbol} on ${step.chain?.name || 'chain'}`; - case 'CREATE_PERMIT_FOR_SOURCE_SWAP': - return `Creating permit for source swap for ${step.symbol} on ${step.chain?.name || 'chain'}`; - case 'DESTINATION_SWAP_BATCH_TX': - return `Creating destination swap transaction`; - case 'DESTINATION_SWAP_HASH': - return `Hash for destination swap on ${step.chain?.name || 'chain'}`; - case 'DETERMINING_SWAP': - return `Generating routes for XCS`; - case 'RFF_ID': - return `Chain abstracted intent`; - case 'SOURCE_SWAP_BATCH_TX': - return 'Creating source swap batch transactions'; - case 'SOURCE_SWAP_HASH': - return `Hash for source swap on ${step.chain?.name || 'chain'}`; - case 'SWAP_COMPLETE': - return `Swap is completed`; - case 'SWAP_START': - return 'Swap starting'; - default: - return 'Processing swap'; - } -}; - -const swapSteps = [ - { id: 0, type: 'SWAP_START', typeID: 'SWAP_START', name: 'Starting Swap' }, - { id: 1, type: 'DETERMINING_SWAP', typeID: 'DETERMINING_SWAP', name: 'Finding Best Route' }, - { - id: 2, - type: 'SOURCE_SWAP_BATCH_TX', - typeID: 'SOURCE_SWAP_BATCH_TX', - name: 'Source Transaction', - }, - { id: 3, type: 'SOURCE_SWAP_HASH', typeID: 'SOURCE_SWAP_HASH', name: 'Source Transaction hash' }, - { id: 4, type: 'RFF_ID', typeID: 'RFF_ID', name: 'Source Transaction hash' }, - { - id: 5, - type: 'DESTINATION_SWAP_BATCH_TX', - typeID: 'DESTINATION_SWAP_BATCH_TX', - name: 'Destination Transaction', - }, - { - id: 6, - type: 'DESTINATION_SWAP_HASH', - typeID: 'DESTINATION_SWAP_HASH', - name: 'Destination Transaction hash', - }, - { - id: 7, - type: 'CREATE_PERMIT_FOR_SOURCE_SWAP', - typeID: 'CREATE_PERMIT_FOR_SOURCE_SWAP', - name: 'Permit', - }, - - { - id: 8, - type: 'CREATE_PERMIT_EOA_TO_EPHEMERAL', - typeID: 'CREATE_PERMIT_EOA_TO_EPHEMERAL', - name: 'Permit Ephemeral', - }, - { id: 9, type: 'SWAP_COMPLETE', typeID: 'SWAP_COMPLETE', name: 'Swap Complete' }, -]; - -interface ProcessingStep { - id: number; - completed: boolean; - progress: number; // 0-100 - stepData?: ProgressStep | ProgressSteps | SwapStep; -} - -interface ProcessingState { - currentStep: number; - totalSteps: number; - steps: ProcessingStep[]; - statusText: string; - animationProgress: number; -} - -const useListenTransaction = ({ - sdk, - activeTransaction, -}: { - sdk: NexusSDK; - activeTransaction: ActiveTransaction; -}) => { - const { type } = activeTransaction; - const DEFAULT_INITIAL_STEPS = 10; - - const [processing, setProcessing] = useState(() => ({ - currentStep: 0, - totalSteps: DEFAULT_INITIAL_STEPS, - steps: Array.from({ length: DEFAULT_INITIAL_STEPS }, (_, i) => ({ - id: i, - completed: false, - progress: 0, - })), - statusText: 'Verifying Request', - animationProgress: 0, - })); - const [explorerURL, setExplorerURL] = useState(null); - const [explorerURLs, setExplorerURLs] = useState<{ source?: string; destination?: string }>({}); - - const resetProcessingState = useCallback(() => { - setProcessing({ - currentStep: 0, - totalSteps: DEFAULT_INITIAL_STEPS, - steps: Array.from({ length: DEFAULT_INITIAL_STEPS }, (_, i) => ({ - id: i, - completed: false, - progress: 0, - })), - statusText: 'Verifying Request', - animationProgress: 0, - }); - setExplorerURL(null); - setExplorerURLs({}); - }, []); - - useEffect(() => { - if (!sdk) return; - - // Special handling for swap transactions - if (type === 'swap') { - // For swap, we create our own progress steps since no expected_steps are emitted - - const initialSteps = swapSteps.map((step, index) => ({ - id: index, - completed: false, - progress: 0, - stepData: step as any, // Step structure for swap mock data - })); - - setProcessing({ - currentStep: 0, - totalSteps: swapSteps.length, - steps: initialSteps, - statusText: 'Preparing Swap', - animationProgress: 0, - }); - - const handleSwapStepComplete = (stepData: SwapStep) => { - setProcessing((prev) => { - // Find matching step by type - const stepIndex = swapSteps.findIndex((s) => s.typeID === stepData.type); - - if (stepIndex === -1) { - // Unknown step, just advance progress - const nextStep = Math.min(prev.currentStep + 1, prev.totalSteps); - return { - ...prev, - currentStep: nextStep, - animationProgress: (nextStep / prev.totalSteps) * 100, - statusText: getTextFromSwapStep(stepData), - }; - } - - const newSteps = [...prev.steps]; - - // Mark all steps up to and including current as completed - for (let i = 0; i <= stepIndex && i < newSteps.length; i++) { - newSteps[i] = { - ...newSteps[i], - completed: true, - progress: 100, - stepData: i === stepIndex ? stepData : newSteps[i].stepData, - }; - } - - const nextStep = Math.min(stepIndex + 1, prev.totalSteps); - const animationProgress = ((stepIndex + 1) / prev.totalSteps) * 100; - - return { - ...prev, - currentStep: nextStep, - steps: newSteps, - animationProgress: Math.min(animationProgress, 100), - statusText: getTextFromSwapStep(stepData), - }; - }); - - // Handle explorer URL extraction for swap - if (stepData.type === 'SOURCE_SWAP_HASH' && 'explorerURL' in stepData) { - setExplorerURLs((prev) => ({ ...prev, source: stepData.explorerURL })); - setExplorerURL(stepData.explorerURL); // Keep for backward compatibility - } else if (stepData.type === 'DESTINATION_SWAP_HASH' && 'explorerURL' in stepData) { - setExplorerURLs((prev) => ({ ...prev, destination: stepData.explorerURL })); - setExplorerURL(stepData.explorerURL); // Update to show latest - } - }; - - sdk?.nexusEvents?.on(NEXUS_EVENTS.SWAP_STEPS, handleSwapStepComplete); - - return () => { - sdk.nexusEvents?.off(NEXUS_EVENTS.SWAP_STEPS, handleSwapStepComplete); - }; - } - - // Regular handling for non-swap transactions - // Flag to know when we have received the complete expected-steps list - let expectedReceived = false; - // Queue to store stepComplete events that arrive before expected steps - const pendingSteps: ProgressStep[] = []; - const expectedEventType = - type === 'bridgeAndExecute' - ? NEXUS_EVENTS.BRIDGE_EXECUTE_EXPECTED_STEPS - : NEXUS_EVENTS.EXPECTED_STEPS; - - const completedEventType = - type === 'bridgeAndExecute' - ? NEXUS_EVENTS.BRIDGE_EXECUTE_COMPLETED_STEPS - : NEXUS_EVENTS.STEP_COMPLETE; - - const handleExpectedSteps = (expectedSteps: ProgressSteps[]) => { - expectedReceived = true; - const stepCount = Array.isArray(expectedSteps) ? expectedSteps.length : expectedSteps; - const steps = Array.isArray(expectedSteps) ? expectedSteps : []; - - // Build initial step objects from expected steps array - const initialSteps = Array.from({ length: stepCount }, (_, i) => ({ - id: i, - completed: false, - progress: 0, - stepData: steps[i] || null, - })); - - // Preserve any steps that were already completed before this event arrived - setProcessing((prev: ProcessingState) => { - const completedTypeIDs = prev.steps - .filter((s) => s.completed) - .map((s) => (s.stepData as ProgressStep)?.typeID) as string[]; - - const mergedSteps = initialSteps.map((step) => { - const typeID = (step.stepData as any)?.typeID as string | undefined; - if (typeID && completedTypeIDs.includes(typeID)) { - return { ...step, completed: true, progress: 100 }; - } - return step; - }); - - const completedCount = mergedSteps.filter((s) => s.completed).length; - - let newState: ProcessingState = { - ...prev, - totalSteps: stepCount, - steps: mergedSteps, - currentStep: completedCount, - animationProgress: (completedCount / stepCount) * 100, - statusText: 'Verifying Request', - }; - - // Now process any queued steps that arrived before expected steps - if (pendingSteps.length > 0) { - pendingSteps.forEach((queuedStep) => { - newState = processStep(newState, queuedStep); - }); - pendingSteps.length = 0; // clear queue - } - - return newState; - }); - }; - - // Helper to process a single step and return updated state (pure function) - const processStep = (prev: ProcessingState, stepData: ProgressStep): ProcessingState => { - const { type: stepType, typeID, data } = stepData; - - let stepIndex = prev.steps.findIndex((s) => { - const id = (s.stepData as any)?.typeID as string | undefined; - return id === typeID; - }); - - if (stepIndex === -1) { - stepIndex = Math.min(prev.currentStep, prev.totalSteps - 1); - } - - const newSteps = [...prev.steps]; - - for (let i = 0; i <= stepIndex && i < newSteps.length; i++) { - newSteps[i] = { - ...newSteps[i], - completed: true, - progress: 100, - stepData: i === stepIndex ? stepData : newSteps[i].stepData, - }; - } - - const nextStep = Math.min(stepIndex + 1, prev.totalSteps); - const animationProgress = ((stepIndex + 1) / prev.totalSteps) * 100; - - let description = getStatusText(stepData, type || 'bridge'); - if (stepType === 'INTENT_COLLECTION' && data) { - description = 'Collecting Confirmations'; - } - - return { - ...prev, - currentStep: nextStep, - steps: newSteps, - animationProgress: Math.min(animationProgress, 100), - statusText: description, - }; - }; - - const handleStepComplete = (stepData: ProgressStep) => { - const { typeID, data } = stepData; - - // Always advance progress for better UX - setProcessing((prev) => processStep(prev, stepData)); - - // Queue until we have real mapping - if (!expectedReceived) { - pendingSteps.push(stepData); - } - - if (typeID === 'IS' && data && 'explorerURL' in data) { - setExplorerURL((data as any)?.explorerURL as string); - } - }; - - sdk?.nexusEvents?.on(expectedEventType, handleExpectedSteps); - sdk?.nexusEvents?.on(completedEventType, handleStepComplete); - - return () => { - sdk.nexusEvents?.off(expectedEventType, handleExpectedSteps); - sdk.nexusEvents?.off(completedEventType, handleStepComplete); - }; - }, [sdk, type]); - - useEffect(() => { - if (!sdk) return; - const handleBeforeUnload = (e: BeforeUnloadEvent) => { - if ( - activeTransaction.status === 'processing' || - activeTransaction.status === 'set_allowance' - ) { - e.preventDefault(); - e.returnValue = 'A transaction is currently in progress. Are you sure you want to leave?'; - } - return 'A transaction is currently in progress. Are you sure you want to leave?'; - }; - - window.addEventListener('beforeunload', handleBeforeUnload); - - return () => { - window.removeEventListener('beforeunload', handleBeforeUnload); - }; - }, [activeTransaction.status]); - - return { processing, explorerURL, explorerURLs, resetProcessingState }; -}; - -export default useListenTransaction; diff --git a/packages/widgets/src/hooks/useNexus.tsx b/packages/widgets/src/hooks/useNexus.tsx deleted file mode 100644 index d7ddb48d..00000000 --- a/packages/widgets/src/hooks/useNexus.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useInternalNexus } from '../providers/InternalNexusProvider'; - -const useNexus = () => { - const { setProvider, sdk, isSdkInitialized, provider, initializeSdk, deinitializeSdk } = - useInternalNexus(); - return { - setProvider, - sdk, - isSdkInitialized, - provider, - initializeSdk, - deinitializeSdk, - }; -}; - -export default useNexus; diff --git a/packages/widgets/src/hooks/useOutsideClick.tsx b/packages/widgets/src/hooks/useOutsideClick.tsx deleted file mode 100644 index 0fe08606..00000000 --- a/packages/widgets/src/hooks/useOutsideClick.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { useEffect } from 'react'; - -const useOutsideClick = ( - ref: React.RefObject, - callback: (event: MouseEvent | TouchEvent) => void, -) => { - useEffect(() => { - const listener = (event: MouseEvent | TouchEvent) => { - if (!ref.current || !event.target || ref.current.contains(event.target as Node)) { - return; - } - callback(event); - }; - - document.addEventListener('mousedown', listener); - document.addEventListener('touchstart', listener); - - return () => { - document.removeEventListener('mousedown', listener); - document.removeEventListener('touchstart', listener); - }; - }, [ref]); -}; - -export default useOutsideClick; diff --git a/packages/widgets/src/index.ts b/packages/widgets/src/index.ts deleted file mode 100644 index 0f7553ee..00000000 --- a/packages/widgets/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -// UI SDK entry point - React components and providers -import './styles/globals.css'; - -export { default as NexusProvider } from './providers/NexusProvider'; -export { default as useNexus } from './hooks/useNexus'; - -// Button components (named exports) -export { BridgeButton } from './components/bridge/bridge-button'; -export { TransferButton } from './components/transfer/transfer-button'; -export { BridgeAndExecuteButton } from './components/bridge-execute/bridge-execute-button'; -export { SwapButton } from './components/swap/swap-button'; - -export * from '@nexus/commons'; diff --git a/packages/widgets/src/providers/InternalNexusProvider.tsx b/packages/widgets/src/providers/InternalNexusProvider.tsx deleted file mode 100644 index db24aa52..00000000 --- a/packages/widgets/src/providers/InternalNexusProvider.tsx +++ /dev/null @@ -1,1073 +0,0 @@ -'use client'; -import { - createContext, - useContext, - useState, - ReactNode, - useCallback, - useMemo, - useEffect, - useRef, -} from 'react'; -import { - NexusSDK, - EthereumProvider, - UserAsset, - BridgeParams, - TransferParams, - BridgeAndExecuteParams, - SimulationResult, - NexusNetwork, - BridgeAndExecuteSimulationResult, -} from '@avail-project/nexus-core'; -import type { - ActiveTransaction, - BridgeConfig, - NexusContextValue, - TransactionType, - ITransactionController, - SwapInputData, -} from '../types'; -import type { TransferConfig } from '../controllers/TransferController'; -import { BridgeController } from '../controllers/BridgeController'; -import { TransferController } from '../controllers/TransferController'; -import { BridgeAndExecuteController } from '../controllers/BridgeAndExecuteController'; -import TransactionProcessorShell from '../components/processing/transaction-processor-shell'; -import { LayoutGroup } from 'motion/react'; -import useListenTransaction from '../hooks/useListenTransaction'; -import { - logger, - SwapIntentHook, - parseUnits, - TOKEN_METADATA, - ExactInSwapInput, -} from '@nexus/commons'; -import { DragConstraintsProvider } from '../components/motion/drag-constraints'; -import { getTokenFromInputData, getAmountFromInputData, formatSwapError } from '../utils/utils'; -import { getTokenAddress } from '../utils/token-utils'; - -const controllers: Record, ITransactionController> = { - bridge: new BridgeController(), - transfer: new TransferController(), - bridgeAndExecute: new BridgeAndExecuteController(), -}; - -// Type guards - -const NexusContext = createContext(null); - -const initialState: ActiveTransaction = { - type: null, - status: 'idle', - reviewStatus: 'gathering_input', - inputData: null, - prefillFields: {}, - simulationResult: null, - executionResult: null, - error: null, -}; - -// Utility: extract chain identifier regardless of transaction type -function getInputChainId( - data: - | Partial - | Partial - | Partial - | Partial - | null - | undefined, -): number | undefined { - if (!data) return undefined; - if ('chainId' in data && data.chainId !== undefined) return data.chainId as number; - if ('toChainId' in data && data.toChainId !== undefined) return data.toChainId; - return undefined; -} - -export function InternalNexusProvider({ - config, - children, - disableCollapse, -}: Readonly<{ - config?: { network?: NexusNetwork; debug?: boolean }; - children: ReactNode; - disableCollapse?: boolean; -}>) { - const [sdk] = useState( - () => new NexusSDK({ network: config?.network ?? 'mainnet', debug: config?.debug ?? false }), - ); - - const [provider, setProvider] = useState(undefined); - const [isSdkInitialized, setIsSdkInitialized] = useState(false); - const [activeTransaction, setActiveTransaction] = useState(initialState); - const [unifiedBalance, setUnifiedBalance] = useState([]); - const [exchangeRates, setExchangeRates] = useState>({}); - const [isSimulating, setIsSimulating] = useState(false); - const [insufficientBalance, setInsufficientBalance] = useState(false); - const [isTransactionCollapsed, setIsTransactionCollapsed] = useState(false); - const [timer, setTimer] = useState(0); - const [allowanceError, setAllowanceError] = useState(null); - const [isSettingAllowance, setIsSettingAllowance] = useState(false); - - // Swap-specific state - const swapAllowCallbackRef = useRef<(() => void) | null>(null); - const [isSwapExecuting, setIsSwapExecuting] = useState(false); - - const timerRef = useRef(null); - const debounceTimeoutRef = useRef(null); - - // Keep a live ref of SDK initialized state to avoid stale closures in callbacks - const isSdkInitializedRef = useRef(false); - useEffect(() => { - isSdkInitializedRef.current = isSdkInitialized; - }, [isSdkInitialized]); - - const activeController = useMemo(() => { - if (!activeTransaction.type) return null; - if (activeTransaction.type === 'swap') return null; // Swaps handled directly in provider - return controllers[activeTransaction.type]; - }, [activeTransaction.type]); - - const { processing, explorerURL, explorerURLs, resetProcessingState } = useListenTransaction({ - sdk, - activeTransaction, - }); - - const fetchExchangeRates = useCallback(async () => { - try { - const response = await fetch('https://api.coinbase.com/v2/exchange-rates?currency=USD'); - const bnbExchangeRate = await fetch( - 'https://api.coingecko.com/api/v3/simple/price?ids=binancecoin&vs_currencies=usd', - ); - const bnbData = await bnbExchangeRate.json(); - const data = await response.json(); - const rates = (data?.data?.rates ?? {}) as Record; - logger.info('all rates', rates); - // Convert from "units per USD" to "USD per unit" for easier UI multiplication - const usdPerUnit: Record = { BNB: bnbData.binancecoin.usd }; - for (const [symbol, value] of Object.entries(rates)) { - const unitsPerUsd = parseFloat(value); - if (Number.isFinite(unitsPerUsd) && unitsPerUsd > 0) { - usdPerUnit[symbol] = 1 / unitsPerUsd; - } - } - - // Ensure common stablecoins have a sane fallback - ['USD', 'USDC', 'USDT'].forEach((stable) => { - if (usdPerUnit[stable] === undefined) usdPerUnit[stable] = 1; - }); - setExchangeRates(usdPerUnit); - } catch (error) { - logger.error('Error fetching exchange rates:', error as Error); - } - }, []); - - const fetchBalances = async () => { - const unifiedBalance = await sdk.getUnifiedBalances(); - logger.debug('Unified balance', { unifiedBalance }); - setUnifiedBalance(unifiedBalance); - }; - - const initializeSdk = async (ethProvider?: EthereumProvider) => { - if (isSdkInitialized) return true; - const eipProvider = ethProvider ?? provider; - if (!eipProvider) { - setActiveTransaction((prev) => ({ - ...prev, - status: 'simulation_error', - error: new Error('Wallet provider not connected.'), - })); - return false; - } - - if (!provider && eipProvider) { - setProvider(ethProvider); - } - - try { - setActiveTransaction((prev) => ({ ...prev, status: 'initializing' })); - await sdk.initialize(eipProvider); - await fetchExchangeRates(); - await fetchBalances(); - setIsSdkInitialized(sdk.isInitialized()); - isSdkInitializedRef.current = sdk.isInitialized(); - setActiveTransaction((prev) => ({ ...prev, status: 'review' })); - return true; - } catch (err) { - logger.error('SDK initialization failed:', err as Error); - const error = err instanceof Error ? err : new Error('SDK Initialization failed.'); - setActiveTransaction((prev) => ({ ...prev, status: 'simulation_error', error })); - return false; - } - }; - - const deinitializeSdk = async () => { - if (!isSdkInitialized) return; - try { - await sdk?.deinit(); - reset(); - } catch (e) { - logger.error('Error deinitializing SDK', e as Error); - } - }; - - const reset = () => { - setProvider(undefined); - setIsSdkInitialized(false); - isSdkInitializedRef.current = false; - setActiveTransaction(initialState); - setUnifiedBalance([]); - setIsSimulating(false); - setInsufficientBalance(false); - setIsTransactionCollapsed(true); - setTimer(0); - setAllowanceError(null); - setIsSettingAllowance(false); - }; - - const startTransaction = useCallback( - ( - type: TransactionType, - prefillData: - | Partial - | Partial - | Partial - | Partial = {}, - ) => { - // Track which fields were prefilled - const prefillFields: { - chainId?: boolean; - toChainId?: boolean; - token?: boolean; - amount?: boolean; - recipient?: boolean; - fromChainID?: boolean; - toChainID?: boolean; - fromTokenAddress?: boolean; - toTokenAddress?: boolean; - fromAmount?: boolean; - toAmount?: boolean; - } = {}; - - if (prefillData) { - if ('chainId' in prefillData && prefillData.chainId !== undefined) { - prefillFields.chainId = true; - if (type === 'bridgeAndExecute') { - prefillFields.toChainId = true; - } - } - if ( - type === 'bridgeAndExecute' && - 'toChainId' in prefillData && - prefillData.toChainId !== undefined - ) { - prefillFields.toChainId = true; - } - if ('token' in prefillData && prefillData.token !== undefined) { - prefillFields.token = true; - } - if ('amount' in prefillData && prefillData.amount !== undefined) { - prefillFields.amount = true; - } - if ('recipient' in prefillData && prefillData.recipient !== undefined) { - prefillFields.recipient = true; - } - // Handle swap-specific fields - if ('fromChainID' in prefillData && prefillData.fromChainID !== undefined) { - prefillFields.fromChainID = true; - } - if ('toChainID' in prefillData && prefillData.toChainID !== undefined) { - prefillFields.toChainID = true; - } - if ('fromTokenAddress' in prefillData && prefillData.fromTokenAddress !== undefined) { - prefillFields.fromTokenAddress = true; - } - if ('toTokenAddress' in prefillData && prefillData.toTokenAddress !== undefined) { - prefillFields.toTokenAddress = true; - } - if ('fromAmount' in prefillData && prefillData.fromAmount !== undefined) { - prefillFields.fromAmount = true; - } - if ('toAmount' in prefillData && prefillData.toAmount !== undefined) { - prefillFields.toAmount = true; - } - } - const normalizedPrefillData = - type === 'bridgeAndExecute' && - 'toChainId' in prefillData && - prefillData.toChainId !== undefined - ? { ...prefillData, chainId: prefillData.toChainId } - : prefillData; - - setActiveTransaction({ - ...initialState, - type, - status: isSdkInitializedRef.current ? 'review' : 'initializing', - inputData: normalizedPrefillData as any, - prefillFields, - }); - }, - [isSdkInitialized], - ); - - const cancelTransaction = useCallback(async () => { - setIsSimulating(false); - setInsufficientBalance(false); - setIsTransactionCollapsed(true); - setTimer(0); - setActiveTransaction(initialState); - resetProcessingState(); - if (isSdkInitialized && sdk) { - try { - const updatedBalance = await sdk.getUnifiedBalances(); - setUnifiedBalance(updatedBalance); - } catch (err) { - logger.warn('Failed to refetch unified balance after transaction completion:', err); - } - } - }, [isSdkInitialized, sdk, resetProcessingState]); - - const toggleTransactionCollapse = useCallback(() => { - setIsTransactionCollapsed((prev) => !prev); - }, []); - - const updateInput = useCallback( - ( - data: - | Partial - | Partial - | Partial - | Partial, - ) => { - setActiveTransaction((prev) => ({ - ...prev, - inputData: { ...prev.inputData, ...data } as any, - reviewStatus: 'gathering_input', - status: prev.status === 'simulation_error' ? 'review' : prev.status, - error: prev.status === 'simulation_error' ? null : prev.error, - })); - - setIsSimulating(false); - setInsufficientBalance(false); - }, - [], - ); - - const checkInsufficientBalance = useCallback( - (inputData: Partial | Partial | Partial) => { - const token = getTokenFromInputData(inputData); - const amount = getAmountFromInputData(inputData); - - if (!token || !amount || !unifiedBalance.length) { - return false; - } - - const tokenBalance = unifiedBalance.find((asset) => asset.symbol === token); - if (!tokenBalance) { - logger.warn('Token not found in unified balance:', { - requestedToken: token, - availableTokens: unifiedBalance.map((asset) => asset.symbol), - }); - return true; // Consider it insufficient if token not found - } - - const requestedAmount = parseFloat(amount.toString()); - const availableBalance = parseFloat(tokenBalance.balance); - - const isInsufficient = requestedAmount > availableBalance; - - if (isInsufficient) { - logger.warn('Insufficient balance detected:', { - token: token, - requested: requestedAmount, - available: availableBalance, - deficit: requestedAmount - availableBalance, - }); - } - - return isInsufficient; - }, - [unifiedBalance], - ); - - const retrySimulation = useCallback(() => { - setIsSimulating(false); - setActiveTransaction((prev) => ({ - ...prev, - status: 'review', - error: null, - reviewStatus: 'gathering_input', - })); - }, []); - - const triggerSimulation = useCallback(async () => { - if (debounceTimeoutRef.current) { - clearTimeout(debounceTimeoutRef.current); - debounceTimeoutRef.current = null; - } - - const conditions = { - isSdkInitialized, - statusOk: - activeTransaction.status === 'review' || activeTransaction.status === 'simulation_error', - reviewStatusOk: activeTransaction.reviewStatus === 'gathering_input', - hasController: !!activeController, - - hasSufficientInput: activeTransaction.inputData - ? (() => { - if (activeTransaction.type === 'swap') { - // For swaps, check if we have sufficient input directly - const data = activeTransaction.inputData as Partial; - return !!( - data.fromChainID && - data.toChainID && - data.fromTokenAddress && - data.toTokenAddress && - data.fromAmount && - parseFloat(data.fromAmount?.toString() || '0') > 0 - ); - } else if (activeController) { - return activeController.hasSufficientInput(activeTransaction.inputData as any); - } - return false; - })() - : false, - notSimulating: !isSimulating, - }; - - if ( - activeTransaction.inputData && - conditions.isSdkInitialized && - conditions.statusOk && - conditions.reviewStatusOk && - (activeController || activeTransaction.type === 'swap') && // Swaps don't use controller - conditions.hasSufficientInput && - conditions.notSimulating - ) { - const { inputData } = activeTransaction; - - const hasInsufficientBalance = checkInsufficientBalance(inputData as any); - setInsufficientBalance(hasInsufficientBalance); - - if (hasInsufficientBalance) { - // Clear simulation result and ensure we stay in review mode for insufficient balance - setActiveTransaction((prev) => ({ - ...prev, - simulationResult: null, - reviewStatus: 'gathering_input', - status: 'review', // Explicitly ensure we stay in review mode - })); - setIsSimulating(false); // Ensure simulation state is cleared - return; - } - - setIsSimulating(true); - - debounceTimeoutRef.current = setTimeout(async () => { - // Check if input has changed since this timeout was set (simple cancellation) - const currentInputData = activeTransaction.inputData; - if ( - getAmountFromInputData(currentInputData as any) !== - getAmountFromInputData(inputData as any) || - getTokenFromInputData(currentInputData as any) !== - getTokenFromInputData(inputData as any) || - getInputChainId(currentInputData as any) !== getInputChainId(inputData as any) - ) { - setIsSimulating(false); // Reset simulation state - return; - } - - // Clear previous simulation result when starting new simulation - setActiveTransaction((prev) => ({ - ...prev, - simulationResult: null, - reviewStatus: 'simulating', - status: 'review', // Explicitly maintain review status - })); - - try { - let simulationResult: any; - - if (activeTransaction.type === 'swap') { - // For swaps, we skip simulation here since it's handled by initiateSwap - // This code path should not be reached for swaps anymore - await initiateSwap(inputData as SwapInputData); - return; - } else if (activeController) { - // Handle regular transaction controllers - simulationResult = await activeController.runReview(sdk, inputData); - } else { - throw new Error('No controller available for transaction type'); - } - - // Final check before applying results - ensure input hasn't changed - const finalInputData = activeTransaction.inputData; - if ( - getAmountFromInputData(finalInputData as any) !== - getAmountFromInputData(inputData as any) || - getTokenFromInputData(finalInputData as any) !== - getTokenFromInputData(inputData as any) || - getInputChainId(finalInputData as any) !== getInputChainId(inputData as any) - ) { - setIsSimulating(false); - return; - } - - // Check if simulation failed - if ( - simulationResult && - (('success' in simulationResult && !simulationResult.success) || - ('error' in simulationResult && simulationResult.error) || - // For bridge simulation within BridgeAndExecuteSimulationResult - // Only consider null bridgeSimulation a failure if bridge wasn't intentionally skipped - ('bridgeSimulation' in simulationResult && - simulationResult.bridgeSimulation === null && - !(simulationResult as BridgeAndExecuteSimulationResult)?.metadata?.bridgeSkipped)) - ) { - setActiveTransaction((prev) => ({ - ...prev, - simulationResult, - status: 'simulation_error', - error: new Error( - 'error' in simulationResult - ? simulationResult.error || 'Simulation failed' - : 'Simulation failed', - ), - reviewStatus: 'gathering_input', - })); - return; - } - - setActiveTransaction((prev) => ({ - ...prev, - simulationResult, - reviewStatus: simulationResult?.allowance?.needsApproval ? 'needs_allowance' : 'ready', - status: 'review', - })); - } catch (err) { - logger.error('Simulation failed:', err as Error); - const error = err instanceof Error ? err : new Error('Simulation failed.'); - setActiveTransaction((prev) => ({ - ...prev, - status: 'simulation_error', - error, - reviewStatus: 'gathering_input', - })); - } finally { - setIsSimulating(false); - } - }, 2000); - } - }, [ - activeTransaction.status, - activeTransaction.reviewStatus, - activeTransaction.inputData, - activeController, - sdk, - isSdkInitialized, - checkInsufficientBalance, - ]); - - const confirmAndProceed = useCallback(async () => { - if (!activeController || !activeTransaction.inputData || !activeTransaction.simulationResult) - return; - - if (insufficientBalance) { - logger.warn('Attempted to process transaction with insufficient balance'); - return; - } - - if (isSimulating) { - logger.warn('Attempted to process transaction while simulation is running'); - return; - } - - if (activeTransaction.status !== 'review') { - logger.warn( - 'Attempted to process transaction from invalid status:', - activeTransaction.status, - ); - return; - } - - if ( - activeTransaction.reviewStatus !== 'ready' && - activeTransaction.reviewStatus !== 'needs_allowance' - ) { - logger.warn( - 'Attempted to process transaction with invalid review status:', - activeTransaction.reviewStatus, - ); - return; - } - - if (activeTransaction.type === 'swap') { - // Swaps should not use confirmAndProceed - they use proceedWithSwap instead - logger.error( - 'confirmAndProceed should not be called for swaps - use proceedWithSwap instead', - ); - throw new Error( - 'confirmAndProceed should not be called for swaps - use proceedWithSwap instead', - ); - } - - if (!activeController) { - throw new Error('No controller available for transaction type'); - } - - setActiveTransaction((prev) => ({ ...prev, status: 'processing' })); - try { - // Handle regular transaction controllers - const executionResult = await activeController.confirmAndProceed( - sdk, - activeTransaction.inputData, - activeTransaction.simulationResult, - ); - - // For non-swap transactions, use the traditional success/error handling - setActiveTransaction((prev) => ({ - ...prev, - status: executionResult?.success ? 'success' : 'error', - error: executionResult?.error ? new Error(executionResult.error) : null, - executionResult, - })); - } catch (err) { - logger.error('Transaction failed.', err as Error); - const error = err instanceof Error ? err : new Error('Transaction failed.'); - setActiveTransaction((prev) => ({ ...prev, status: 'error', error })); - } - }, [ - activeController, - sdk, - activeTransaction.inputData, - activeTransaction.simulationResult, - insufficientBalance, - isSimulating, - activeTransaction.status, - activeTransaction.reviewStatus, - ]); - - // Single function to handle entire swap flow - const initiateSwap = useCallback( - async (inputData: SwapInputData) => { - try { - logger.info('Swap Provider: Starting swap process', inputData); - - // Validate required fields - if ( - !inputData?.fromChainID || - !inputData?.toChainID || - !inputData?.toTokenAddress || - !inputData?.fromAmount || - !inputData?.fromTokenAddress - ) { - throw new Error('Missing required fields for swap'); - } - - // Convert SwapInputData to SwapInput format for SDK - const fromAmountStr = inputData.fromAmount ?? '0'; - const fromAmountNumber = parseFloat(fromAmountStr.toString()); - - if (isNaN(fromAmountNumber) || fromAmountNumber <= 0) { - throw new Error('Invalid amount provided for swap'); - } - - const actualFromTokenAddress = getTokenAddress( - inputData.fromTokenAddress, - inputData.fromChainID, - 'swap', - ); - const actualToTokenAddress = getTokenAddress( - inputData.toTokenAddress, - inputData.toChainID, - 'swap', - ); - - const swapInput: ExactInSwapInput = { - from: [ - { - chainId: inputData.fromChainID, - amount: parseUnits( - fromAmountStr.toString(), - TOKEN_METADATA[inputData?.fromTokenAddress]?.decimals, - ), - tokenAddress: actualFromTokenAddress as `0x${string}`, - }, - ], - toChainId: inputData.toChainID, - toTokenAddress: actualToTokenAddress as `0x${string}`, - }; - - logger.info('Swap Provider: Prepared swap input', swapInput); - - // Start the swap process - sdk - .swapWithExactIn(swapInput, { - swapIntentHook: async (data: Parameters[0]) => { - swapAllowCallbackRef.current = data.allow; - // Update UI with captured intent (simulation result) - setActiveTransaction((prev) => ({ - ...prev, - simulationResult: { - success: true, - intent: data.intent, - swapMetadata: { - type: 'swap' as const, - inputToken: actualFromTokenAddress as `0x${string}`, - outputToken: swapInput.toTokenAddress, - fromChainId: inputData?.fromChainID, - toChainId: inputData.toChainID, - inputAmount: inputData?.fromAmount ?? '', - outputAmount: data.intent.destination?.amount?.toString() ?? '0', - }, - allowance: { - needsApproval: false, - chainDetails: [], - }, - }, - reviewStatus: 'ready', - status: 'review', - })); - }, - }) - .then((result) => { - if (result.success) { - // Swap succeeded - let useListenTransaction handle the success state - logger.info('Swap Provider: Swap execution succeeded'); - setActiveTransaction((prev) => ({ - ...prev, - status: 'success', - })); - } else { - // Swap failed - this captures your error! - logger.error('Swap Provider: Swap execution failed:', result.error); - - // Set a flag to prevent success callbacks from overriding this error - setActiveTransaction((prev) => ({ - ...prev, - status: 'simulation_error', - reviewStatus: 'gathering_input', // Reset reviewStatus to stop loading state - error: new Error(result?.error ?? 'Swap execution failed'), - executionResult: result, - })); - - // Clear the allow callback to prevent further execution - swapAllowCallbackRef.current = null; - } - }) - .catch((error) => { - // Network/SDK errors - logger.error('Swap Provider: Swap SDK error:', error); - const errorMessage = formatSwapError(error); - setActiveTransaction((prev) => ({ - ...prev, - status: 'simulation_error', - reviewStatus: 'gathering_input', // Reset reviewStatus to stop loading state - error: new Error(errorMessage), - })); - }) - .finally(() => { - setIsSwapExecuting(false); - swapAllowCallbackRef.current = null; - }); - } catch (error) { - logger.error('Swap Provider: Swap initiation failed:', error as Error); - const errorMessage = formatSwapError(error); - setActiveTransaction((prev) => ({ - ...prev, - status: 'simulation_error', - error: new Error(errorMessage), - })); - } - }, - [sdk], - ); - - // Function called when user clicks "Swap" button - const proceedWithSwap = useCallback(() => { - if (swapAllowCallbackRef.current && !isSwapExecuting) { - logger.info('Swap Provider: User confirmed swap - executing'); - setIsSwapExecuting(true); - setActiveTransaction((prev) => ({ ...prev, status: 'processing' })); - - // This triggers the .then() block above - swapAllowCallbackRef.current(); - } else { - logger.warn('Swap Provider: No allow callback available or already executing', { - hasCallback: !!swapAllowCallbackRef.current, - isExecuting: isSwapExecuting, - }); - } - }, [isSwapExecuting]); - - const approveAllowance = useCallback( - async (amount: string, isMinimum: boolean) => { - if ( - !activeController || - !activeTransaction.inputData || - !activeTransaction.simulationResult - ) { - return; - } - - if (activeTransaction.status !== 'set_allowance') { - logger.warn( - 'Attempted to approve allowance from invalid status:', - activeTransaction.status, - ); - return; - } - - setIsSettingAllowance(true); - setAllowanceError(null); - - try { - // For each source chain that needs allowance, set it - const { inputData, simulationResult } = activeTransaction; - - // Use chain-specific allowance details if available - const chainDetails = simulationResult?.allowance?.chainDetails; - if (chainDetails && chainDetails.length > 0) { - // Use the new chain-specific approach - for (const chainDetail of chainDetails) { - if (chainDetail.needsApproval) { - const token = getTokenFromInputData(inputData); - if (!token) continue; - - const tokenMeta = sdk.utils.getTokenMetadata(token as any); - const amountToApprove = isMinimum - ? sdk.utils.parseUnits(amount, tokenMeta?.decimals ?? 18) - : sdk.utils.parseUnits(amount, tokenMeta?.decimals ?? 18); - - await sdk.setAllowance(chainDetail.chainId, [token as any], amountToApprove); - } - } - } else { - // Fallback to original approach for backward compatibility - let sourcesData: Array<{ chainID: number; amount: string }> = - (simulationResult as SimulationResult)?.intent?.sources || []; - - // If bridge & execute simulation, sources are inside bridgeSimulation - if (sourcesData.length === 0 && 'bridgeSimulation' in (simulationResult as any)) { - const bridgeSim = (simulationResult as any).bridgeSimulation as SimulationResult; - sourcesData = bridgeSim?.intent?.sources || []; - } - - for (const source of sourcesData) { - const token = getTokenFromInputData(inputData); - if (!token) continue; - - const tokenMeta = sdk.utils.getTokenMetadata(token as any); - const amountToApprove = isMinimum - ? sdk.utils.parseUnits(amount, tokenMeta?.decimals ?? 18) - : sdk.utils.parseUnits(amount, tokenMeta?.decimals ?? 18); - - await sdk.setAllowance(source.chainID, [token as any], amountToApprove); - } - } - - // After successful allowance setting, proceed directly to transaction - setActiveTransaction((prev) => ({ ...prev, status: 'processing' })); - - try { - const executionResult = await activeController.confirmAndProceed( - sdk, - inputData, - simulationResult, - ); - setActiveTransaction((prev) => ({ - ...prev, - status: executionResult?.success ? 'success' : 'error', - error: executionResult?.error ? new Error(executionResult.error) : null, - executionResult, - })); - } catch (execErr) { - logger.error('Transaction failed after allowance approval.', execErr as Error); - const error = execErr instanceof Error ? execErr : new Error('Transaction failed.'); - setActiveTransaction((prev) => ({ ...prev, status: 'error', error })); - } - } catch (err) { - logger.error('Allowance setting failed:', err as Error); - const error = err instanceof Error ? err : new Error('Failed to set allowance.'); - setAllowanceError(error.message); - } finally { - setIsSettingAllowance(false); - } - }, - [ - activeController, - sdk, - activeTransaction.inputData, - activeTransaction.simulationResult, - activeTransaction.status, - ], - ); - - const denyAllowance = useCallback(() => { - setActiveTransaction((prev) => ({ - ...prev, - status: 'review', - reviewStatus: 'needs_allowance', - })); - setAllowanceError(null); - }, []); - - const startAllowanceFlow = useCallback(() => { - if ( - activeTransaction.status !== 'review' || - activeTransaction.reviewStatus !== 'needs_allowance' - ) { - logger.warn('Attempted to start allowance flow from invalid state:', { - status: activeTransaction.status, - reviewStatus: activeTransaction.reviewStatus, - }); - return; - } - - setActiveTransaction((prev) => ({ - ...prev, - status: 'set_allowance', - })); - setAllowanceError(null); - }, [activeTransaction.status, activeTransaction.reviewStatus]); - - useEffect(() => { - if ( - activeTransaction.status === 'review' && - activeTransaction.reviewStatus === 'gathering_input' && - activeTransaction.inputData - ) { - triggerSimulation(); - } - }, [ - getAmountFromInputData(activeTransaction.inputData), - getTokenFromInputData(activeTransaction.inputData), - getInputChainId(activeTransaction.inputData), - activeTransaction.status, - activeTransaction.reviewStatus, - activeTransaction.type, - triggerSimulation, - initiateSwap, - ]); - - useEffect(() => { - return () => { - if (debounceTimeoutRef.current) { - clearTimeout(debounceTimeoutRef.current); - debounceTimeoutRef.current = null; - } - }; - }, []); - - useEffect(() => { - if (activeTransaction.status === 'processing') { - timerRef.current = setInterval(() => { - setTimer((prev) => prev + 0.1); - }, 100); - } - - return () => { - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - } - }; - }, [activeTransaction.status]); - - const value: NexusContextValue = useMemo( - () => ({ - // State - sdk, - activeTransaction, - isSdkInitialized, - activeController, - disableCollapse, - config, - provider, - unifiedBalance, - exchangeRates, - isSimulating, - insufficientBalance, - isTransactionCollapsed, - timer, - allowanceError, - isSettingAllowance, - - // Transaction processing state - processing, - explorerURL, - explorerURLs, - - // Actions - setProvider, - startTransaction, - updateInput, - confirmAndProceed, - cancelTransaction, - initializeSdk, - deinitializeSdk, - triggerSimulation, - retrySimulation, - toggleTransactionCollapse, - approveAllowance, - denyAllowance, - startAllowanceFlow, - - // Swap-specific functions - initiateSwap, - proceedWithSwap, - }), - [ - sdk, - activeTransaction, - isSdkInitialized, - activeController, - config, - provider, - setProvider, - startTransaction, - updateInput, - confirmAndProceed, - cancelTransaction, - initializeSdk, - deinitializeSdk, - triggerSimulation, - retrySimulation, - unifiedBalance, - exchangeRates, - isSimulating, - insufficientBalance, - isTransactionCollapsed, - toggleTransactionCollapse, - timer, - allowanceError, - isSettingAllowance, - processing, - explorerURL, - explorerURLs, - approveAllowance, - denyAllowance, - startAllowanceFlow, - initiateSwap, - proceedWithSwap, - ], - ); - - return ( - - - - {children} - - - - - ); -} - -export function useInternalNexus() { - const context = useContext(NexusContext); - if (!context) { - throw new Error('useInternalNexus must be used within a NexusProvider'); - } - return context; -} diff --git a/packages/widgets/src/providers/NexusProvider.tsx b/packages/widgets/src/providers/NexusProvider.tsx deleted file mode 100644 index 968f0810..00000000 --- a/packages/widgets/src/providers/NexusProvider.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; -import React from 'react'; -import { InternalNexusProvider } from './InternalNexusProvider'; -import { type NexusNetwork, logger } from '@nexus/commons'; - -const NexusProvider = ({ - config, - children, -}: { - config?: { network?: NexusNetwork; debug?: boolean }; - children: React.ReactNode; -}) => { - logger.debug('NexusProvider', { config }); - return ( - - {children} - - ); -}; - -export default NexusProvider; diff --git a/packages/widgets/src/styles/globals.css b/packages/widgets/src/styles/globals.css deleted file mode 100644 index f88da7fe..00000000 --- a/packages/widgets/src/styles/globals.css +++ /dev/null @@ -1,248 +0,0 @@ -@import 'tailwindcss'; - -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); - -@theme { - --color-nexus-backdrop: var(--nexus-backdrop); - --color-nexus-blue: var(--nexus-blue); - --color-nexus-gray: var(--nexus-color-gray); - --color-nexus-black: var(--nexus-color-black); - --color-nexus-primary-gray: var(--nexus-color-primary-hover); - --color-nexus-snow-white: var(--nexus-color-snow-white); - --color-nexus-success: var(--nexus-success); - --color-nexus-footer: var(--nexus-footer); - --color-nexus-footer-text: var(--nexus-footer-text); - - /* Core color system */ - --color-nexus-background: var(--nexus-color-background); - --color-nexus-foreground: var(--nexus-color-foreground); - --color-nexus-primary: var(--nexus-color-primary); - --color-nexus-primary-foreground: var(--nexus-color-primary-foreground); - --color-nexus-primary-hover: var(--nexus-color-primary-hover); - --color-nexus-secondary: var(--nexus-color-secondary); - --color-nexus-secondary-foreground: var(--nexus-color-secondary-foreground); - --color-nexus-secondary-background: var(--nexus-color-secondary-background); - --color-nexus-muted: var(--nexus-color-muted); - --color-nexus-muted-foreground: var(--nexus-color-muted-foreground); - --color-nexus-muted-secondary: var(--nexus-color-muted-secondary); - --color-nexus-accent: var(--nexus-color-accent); - --color-nexus-accent-green: var(--nexus-color-accent-green); - --color-nexus-accent-foreground: var(--nexus-color-accent-foreground); - --color-nexus-border: var(--nexus-color-border); - --color-nexus-border-secondary: var(--nexus-color-border-secondary); - --color-nexus-input: var(--nexus-color-input); - --color-nexus-ring: var(--nexus-color-ring); - --color-nexus-ring-offset: var(--nexus-color-background); - --color-nexus-destructive: var(--nexus-color-destructive); - --color-nexus-destructive-foreground: var(--nexus-color-destructive-foreground); - --color-nexus-destructive-secondary: var(--nexus-color-destructive-secondary); - --color-nexus-card: var(--nexus-color-card); - --color-nexus-card-foreground: var(--nexus-color-card-foreground); - - --font-nexus-primary: var(--nexus-font-primary); - --font-nexus-secondary: var(--nexus-font-secondary); - - /* Font Sizes */ - --font-size-xs: 12px; - --font-size-sm: 14px; - --font-size-base: 16px; - --font-size-lg: 20px; - --font-size-xl: 24px; - - /* Font Weights */ - --font-weight-base: 400; - --font-weight-medium: 500; - --font-weight-semibold: 600; - --font-weight-bold: 700; - - /* Border Radius */ - --radius-nexus-sm: var(--nexus-radius-sm); - --radius-nexus-md: var(--nexus-radius-md); - --radius-nexus-lg: var(--nexus-radius-lg); - --radius-nexus-xl: var(--nexus-radius-xl); - --radius-nexus-full: var(--nexus-radius-full); - - /* Shadows */ - --shadow-card: 0 4px 24px rgb(0 0 0 / 0.1); - --shadow-dropdown: 0 6px 6px rgb(0 0 0 / 0.25); - --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); - - /* Animation */ - --animate-fade-in: fade-in 0.2s ease-in-out; - --animate-slide-up: slide-up 0.3s ease-out; - --animate-slide-down: slide-down 0.3s ease-out; - --animate-in: in 0.2s ease-out; - --animate-out: out 0.2s ease-in; - --animate-fade-in-0: fade-in-0 0.2s ease-out; - --animate-fade-out-0: fade-out-0 0.2s ease-in; - --animate-zoom-in-95: zoom-in-95 0.2s ease-out; - --animate-zoom-out-95: zoom-out-95 0.2s ease-in; -} - -:root { - --nexus-color-white: #ffffff; - --nexus-color-gray: #eff0f2; - --nexus-color-snow-white: #fafafa; - --nexus-color-black: #000000; - --nexus-color-background: #f4f6f8; - --nexus-color-foreground: #1b1b1b; - --nexus-color-primary: #1b1b1b; - --nexus-color-primary-hover: #2b2b2b; - --nexus-color-primary-foreground: #ffffff; - --nexus-color-secondary: #565a60; - --nexus-color-secondary-foreground: #ffffff; - --nexus-color-secondary-background: #bed8ee66; - --nexus-color-muted: #808080; - --nexus-color-muted-secondary: #666666; - --nexus-color-muted-foreground: #565a60; - --nexus-color-accent: #0375d8; - --nexus-color-accent-green: #6b9826; - --nexus-color-accent-foreground: #ffffff; - --nexus-color-border: #e5e7e9; - --nexus-color-border-secondary: #425c72; - --nexus-color-input: #b3b3b3; - --nexus-color-ring: #2b80d7; - --nexus-color-destructive: #ef4444; - --nexus-color-destructive-secondary: #c03c54; - --nexus-color-destructive-foreground: #ffffff; - --nexus-color-card: #ffffff; - --nexus-color-card-foreground: #1b1b1b; - --nexus-color-success-base: #78c47b; - --nexus-color-neutral-50: #f4f6f8; - --nexus-color-neutral-100: #e8eaf0; - --nexus-color-neutral-200: #e5e7e9; - --nexus-color-brand-footer: #bed8ee; - --nexus-color-brand-footer-text: #4c4c4c; - --nexus-backdrop: #0e0e0e66; - --nexus-blue: #0375d8; - --nexus-success: #78c47b; - --nexus-footer: rgb(190 216 238 / 0.4); - --nexus-footer-text: rgb(76 76 76); - - --nexus-font-primary: - 'PP Mori', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; - --nexus-font-secondary: - 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; - - /* Nexus SDK Border Radius */ - --nexus-radius-sm: 4px; - --nexus-radius-md: 8px; - --nexus-radius-lg: 12px; - --nexus-radius-xl: 16px; - --nexus-radius-full: 9999px; -} - -@keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes slide-up { - from { - transform: translateY(10px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } -} - -@keyframes slide-down { - from { - transform: translateY(-10px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } -} - -@keyframes fade-out-scale { - 0% { - transform: scale(0.8); - opacity: 0.8; - } - 50% { - transform: scale(1.2); - opacity: 0.4; - } - 100% { - transform: scale(1.5); - opacity: 0; - } -} - -@keyframes in { - from { - opacity: 0; - transform: scale(0.95); - } - to { - opacity: 1; - transform: scale(1); - } -} - -@keyframes out { - from { - opacity: 1; - transform: scale(1); - } - to { - opacity: 0; - transform: scale(0.95); - } -} - -@keyframes fade-in-0 { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes fade-out-0 { - from { - opacity: 1; - } - to { - opacity: 0; - } -} - -@keyframes zoom-in-95 { - from { - transform: scale(0.95); - } - to { - transform: scale(1); - } -} - -@keyframes zoom-out-95 { - from { - transform: scale(1); - } - to { - transform: scale(0.95); - } -} - -.no-scrollbar { - overflow-y: scroll; - scrollbar-width: none; /* Firefox */ - -ms-overflow-style: none; /* Internet Explorer 10+ */ -} -.no-scrollbar::-webkit-scrollbar { - /* WebKit */ - width: 0; - height: 0; -} diff --git a/packages/widgets/src/types/index.ts b/packages/widgets/src/types/index.ts deleted file mode 100644 index d83b1a16..00000000 --- a/packages/widgets/src/types/index.ts +++ /dev/null @@ -1,422 +0,0 @@ -import { ReactNode } from 'react'; -import { NexusSDK } from '@avail-project/nexus-core'; - -// Only import essential parameter types, not all from root types -import type { - BridgeParams, - TransferParams, - BridgeAndExecuteParams, - SUPPORTED_TOKENS, - SUPPORTED_CHAINS_IDS, - DynamicParamBuilder, - SimulationResult, - SwapInputOptionalParams, - SwapIntent, - UserAsset, - EthereumProvider, - ExactInSwapInput, -} from '@nexus/commons'; - -import { Abi } from 'viem'; - -// Local result types for UI (to avoid importing all types) -interface BridgeResult { - success: boolean; - error?: string; - explorerUrl?: string; -} - -interface TransferResult { - success: boolean; - error?: string; - explorerUrl?: string; -} - -interface BridgeAndExecuteResult { - success: boolean; - error?: string; - executeTransactionHash?: string; - executeExplorerUrl?: string; - approvalTransactionHash?: string; - toChainId: number; -} - -interface BridgeAndExecuteSimulationResult { - success: boolean; - error?: string; - steps: any[]; - bridgeSimulation: SimulationResult | null; - executeSimulation?: any; -} - -export interface SwapInputData { - fromChainID?: 10 | 137 | 42161 | 534352 | 8453; - toChainID?: SUPPORTED_CHAINS_IDS; - fromTokenAddress?: 'USDC' | 'WETH' | 'DAI' | 'USDT' | 'USDS'; - toTokenAddress?: - | 'USDC' - | 'LDO' - | 'DAI' - | 'USDT' - | 'KAITO' - | 'ZRO' - | 'PEPE' - | 'ETH' - | 'OP' - | 'AAVE' - | 'UNI' - | 'OM'; - fromAmount?: string; - toAmount?: string; -} - -export interface UnifiedInputData { - chainId?: number; - toChainId?: number; - token?: string; - inputToken?: string; - outputToken?: string; - amount?: string | number; - recipient?: string; -} - -interface SwapResult { - success: boolean; - error?: string; - sourceExplorerUrl?: string; - destinationExplorerUrl?: string; -} - -export interface SwapSimulationResult { - success: boolean; - error?: string; - intent?: SwapIntent; - swapMetadata?: { - type: 'swap'; - inputToken: string; - outputToken: string; - fromChainId?: number; - toChainId?: number; - inputAmount: string; - outputAmount: string; - }; - allowance: { - needsApproval: false; - chainDetails: []; - }; -} - -// Local metadata types for UI -interface ChainMetadata { - id: number; - name: string; - shortName: string; - logo: string; - nativeCurrency: { - name: string; - symbol: string; - decimals: number; - }; - rpcUrls: string[]; - blockExplorerUrls: string[]; -} - -interface TokenMetadata { - symbol: string; - name: string; - decimals: number; - icon: string; - coingeckoId: string; - isNative?: boolean; -} - -// Local network type for UI -type NexusNetwork = 'mainnet' | 'testnet'; - -// # 1. High-Level State Machines - -export type TransactionType = 'bridge' | 'transfer' | 'bridgeAndExecute' | 'swap'; - -export type OrchestratorStatus = - | 'idle' - | 'initializing' - | 'review' - | 'processing' - | 'success' - | 'error' - | 'simulation_error' - | 'set_allowance'; - -export type ReviewStatus = 'gathering_input' | 'simulating' | 'needs_allowance' | 'ready'; - -// # 2. Generic Data Structures for UI - -export interface ActiveTransaction { - type: TransactionType | null; - status: OrchestratorStatus; - reviewStatus: ReviewStatus; - inputData: - | Partial - | Partial - | Partial - | Partial - | null; - prefillFields?: { - chainId?: boolean; - toChainId?: boolean; - token?: boolean; - inputToken?: boolean; - outputToken?: boolean; - amount?: boolean; - recipient?: boolean; - fromChainID?: boolean; - toChainID?: boolean; - fromTokenAddress?: boolean; - toTokenAddress?: boolean; - fromAmount?: boolean; - toAmount?: boolean; - }; - simulationResult: - | ((SimulationResult | BridgeAndExecuteSimulationResult | SwapSimulationResult) & { - allowance?: { - needsApproval: boolean; - chainDetails?: Array<{ - chainId: number; - amount: string; - needsApproval: boolean; - }>; - }; - }) - | null; - executionResult: BridgeResult | BridgeAndExecuteResult | TransferResult | SwapResult | null; - error: Error | null; -} - -export interface ITransactionController { - // The UI component for gathering inputs for this transaction type - InputForm: React.FC<{ - prefill: any; - onUpdate: (data: any) => void; - isBusy: boolean; - tokenBalance?: string; - prefillFields?: { - chainId?: boolean; - toChainId?: boolean; - token?: boolean; - amount?: boolean; - recipient?: boolean; - }; - }>; - - // The main action function that drives the review, simulation, and execution - confirmAndProceed( - sdk: NexusSDK, - inputData: any, - simulationResult: ActiveTransaction['simulationResult'], - ): Promise; - - // A helper to start the simulation and allowance check - runReview(sdk: NexusSDK, inputData: any): Promise; - - // A method to check if the controller has enough data to proceed with a review - hasSufficientInput(inputData: any): boolean; -} - -// # 4. Provider and Hook Types - -// Processing state interface from useListenTransaction -export interface ProcessingStep { - id: number; - completed: boolean; - progress: number; // 0-100 - stepData?: any; // Can be ProgressStep, ProgressSteps, SwapStep, etc. -} - -export interface ProcessingState { - currentStep: number; - totalSteps: number; - steps: ProcessingStep[]; - statusText: string; - animationProgress: number; -} - -export interface NexusContextValue { - // State - sdk: NexusSDK; - activeTransaction: ActiveTransaction; - isSdkInitialized: boolean; - activeController: ITransactionController | null; - config?: { network?: NexusNetwork; debug?: boolean }; - provider: EthereumProvider | undefined; - unifiedBalance: UserAsset[]; - isSimulating: boolean; - insufficientBalance: boolean; - isTransactionCollapsed: boolean; - timer: number; - allowanceError: string | null; - isSettingAllowance: boolean; - exchangeRates: Record; - // Transaction processing state (from useListenTransaction) - processing: ProcessingState; - explorerURL: string | null; - explorerURLs?: { source?: string; destination?: string }; - - // Actions - setProvider: (provider: EthereumProvider) => void; - initializeSdk: (ethProvider?: EthereumProvider) => Promise; - deinitializeSdk: () => Promise; - startTransaction: ( - type: TransactionType, - prefillData?: - | Partial - | Partial - | Partial - | Partial, - ) => void; - updateInput: ( - data: - | Partial - | Partial - | Partial - | Partial, - ) => void; - confirmAndProceed: () => void; - cancelTransaction: () => void; - triggerSimulation: () => Promise; - retrySimulation: () => void; - toggleTransactionCollapse: () => void; - approveAllowance: (amount: string, isMinimum: boolean) => Promise; - denyAllowance: () => void; - startAllowanceFlow: () => void; - - // Swap-specific functions - initiateSwap: (inputData: SwapInputData) => Promise; - proceedWithSwap: () => void; -} - -// # 5. Existing Widget Configuration Types (with minor updates) - -export interface BaseComponentProps { - title?: string; - className?: string; - hasValues?: boolean; -} - -export interface BridgeConfig extends Partial {} - -export interface BridgeButtonProps extends BaseComponentProps { - prefill?: BridgeConfig; - children: (props: { onClick: () => void; isLoading: boolean }) => ReactNode; -} - -export interface SwapConfig { - inputs: ExactInSwapInput; - options?: SwapInputOptionalParams; -} - -export interface SwapButtonProps extends BaseComponentProps { - prefill?: Omit; - children: (props: { onClick: () => void; isLoading: boolean }) => ReactNode; -} - -// Transfer Widget Types -export interface TransferConfig extends Partial {} - -export interface TransferButtonProps extends BaseComponentProps { - prefill?: TransferConfig; - children: (props: { onClick: () => void; isLoading: boolean }) => ReactNode; -} - -// Balance Widget Types -export interface BalanceWidgetProps extends BaseComponentProps { - showChains?: boolean; - showValue?: boolean; - format?: 'short' | 'full'; -} - -// Modal Types -export interface ModalProps extends BaseComponentProps { - isOpen: boolean; - onClose: () => void; - description?: string; - size?: 'sm' | 'md' | 'lg'; - children: ReactNode; - hideCloseButton?: boolean; -} - -// Form Types -export interface TokenSelectProps extends BaseComponentProps { - value?: string; - onValueChange: (token: string, iconUrl?: string) => void; - disabled?: boolean; - network?: 'mainnet' | 'testnet'; - type?: TransactionType; - chainId?: number; - isDestination?: boolean; -} - -export interface ChainSelectProps extends BaseComponentProps { - value?: string; - onValueChange: (chain: string) => void; - disabled?: boolean; - network?: 'mainnet' | 'testnet'; - isSource?: boolean; -} - -export interface AmountInputProps extends BaseComponentProps { - value?: string; - onValueChange: (amount: string) => void; - token?: string; - balance?: string; - disabled?: boolean; - placeholder?: string; -} - -// Transaction Types -export interface TransactionStep { - id: string; - title: string; - description?: string; - status: 'pending' | 'active' | 'completed' | 'error'; -} - -export interface TransactionProgressProps extends BaseComponentProps { - steps: TransactionStep[]; - currentStep: string; - collapsible?: boolean; -} - -export interface BridgeAndExecuteButtonProps extends BaseComponentProps { - contractAddress: `0x${string}`; - contractAbi: Abi; - functionName: string; - buildFunctionParams: DynamicParamBuilder; - prefill?: { - toChainId?: SUPPORTED_CHAINS_IDS; - token?: SUPPORTED_TOKENS; - amount?: string; - }; - children: (props: { onClick: () => void; isLoading: boolean; disabled: boolean }) => ReactNode; -} - -// Re-export DynamicParamBuilder for convenience -export type { DynamicParamBuilder }; - -export interface ProcessorCardProps { - status: OrchestratorStatus; - cancelTransaction: () => void; - toggleTransactionCollapse: () => void; - sourceChainMeta: ChainMetadata[]; - destChainMeta: ChainMetadata | null; - tokenMeta: TokenMetadata | null; - transactionType: TransactionType; - simulationResult: SimulationResult | BridgeAndExecuteSimulationResult | SwapSimulationResult; - processing: ProcessingState; - explorerURL: string | null; - explorerURLs?: { source?: string; destination?: string }; - timer: number; - description: string; - error: Error | null; - executionResult: BridgeResult | TransferResult | BridgeAndExecuteResult | SwapResult | null; - disableCollapse?: boolean; -} diff --git a/packages/widgets/src/utils/balance-utils.ts b/packages/widgets/src/utils/balance-utils.ts deleted file mode 100644 index 70156af3..00000000 --- a/packages/widgets/src/utils/balance-utils.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { UserAsset } from '@avail-project/nexus-core'; -import { SUPPORTED_CHAINS_IDS, SUPPORTED_TOKENS } from '@nexus/commons'; - -export type TransactionType = 'bridge' | 'transfer' | 'bridgeAndExecute' | 'swap'; - -export interface EffectiveBalanceParams { - unifiedBalance: UserAsset[] | null; - token?: SUPPORTED_TOKENS; - destinationChainId?: SUPPORTED_CHAINS_IDS; - type: TransactionType; -} - -export interface EffectiveBalanceResult { - effectiveBalance: string; - totalBalance: string; - contextualMessage: string; -} - -/** - * Calculate effective balance based on transaction context - * - Bridge: Total balance minus destination chain balance - * - Transfer: Balance available on source chain only - * - BridgeAndExecute: Total balance minus destination chain balance - */ -export function calculateEffectiveBalance({ - unifiedBalance, - token, - destinationChainId, - type, -}: EffectiveBalanceParams): EffectiveBalanceResult { - if (!unifiedBalance || !token) { - return { - effectiveBalance: '0', - totalBalance: '0', - contextualMessage: `Balance: 0 ${token || ''}`, - }; - } - - const tokenAsset = unifiedBalance.find((asset) => asset.symbol === token); - - if (!tokenAsset) { - return { - effectiveBalance: '0', - totalBalance: '0', - contextualMessage: `Balance: 0 ${token}`, - }; - } - - const totalBalance = tokenAsset.balance; - let effectiveBalance = totalBalance; - let contextualMessage = `Balance: ${parseFloat(totalBalance).toFixed(6)} ${token}`; - - if (type === 'bridgeAndExecute' || type === 'swap') - return { - effectiveBalance, - totalBalance, - contextualMessage, - }; - - if (destinationChainId) { - const destinationBalance = - tokenAsset.breakdown?.find((item) => item.chain.id === destinationChainId)?.balance || '0'; - - const effectiveBalanceNum = Math.max( - 0, - parseFloat(totalBalance) - parseFloat(destinationBalance), - ); - effectiveBalance = effectiveBalanceNum.toString(); - contextualMessage = `Balance: ${effectiveBalanceNum.toFixed(6)} ${token}`; - } - - return { - effectiveBalance, - totalBalance, - contextualMessage, - }; -} - -export function getFiatValue( - amount: string | number | bigint, - token: string, - exchangeRates: Record, -) { - const key = (token || '').toUpperCase(); - const rate = exchangeRates?.[key]; - const amountNum = - typeof amount === 'number' - ? amount - : parseFloat((typeof amount === 'bigint' ? amount.toString() : amount) || '0'); - - const isValid = Number.isFinite(amountNum) && Number.isFinite(rate); - const approx = isValid ? rate * amountNum : 0; - - return `β‰ˆ $${approx.toFixed(2)}`; -} diff --git a/packages/widgets/src/utils/token-utils.ts b/packages/widgets/src/utils/token-utils.ts deleted file mode 100644 index 36e860bb..00000000 --- a/packages/widgets/src/utils/token-utils.ts +++ /dev/null @@ -1,715 +0,0 @@ -import { useMemo } from 'react'; -import { - CHAIN_METADATA, - TOKEN_METADATA, - TESTNET_TOKEN_METADATA, - TOKEN_CONTRACT_ADDRESSES, - DESTINATION_SWAP_TOKENS, - type SupportedChainsResult, - type TokenMetadata, -} from '@nexus/commons'; -import type { TransactionType } from './balance-utils'; -import type { NexusSDK } from '@avail-project/nexus-core'; - -/** - * Enhanced token metadata for UI components - */ -export interface EnhancedTokenMetadata extends TokenMetadata { - contractAddress?: `0x${string}`; -} - -/** - * Token selection options for UI components - */ -export interface TokenSelectOption { - value: string; - label: string; - icon: string; - metadata: EnhancedTokenMetadata; -} - -/** - * Parameters for token resolution - */ -export interface TokenResolutionParams { - chainId?: number; - type: TransactionType; - network?: 'mainnet' | 'testnet'; - isDestination?: boolean; - sdk?: NexusSDK; -} - -/** - * SDK-provided swap support data structure (normalized from SupportedChainsResult) - */ -export interface TransactionSupportData { - chains: { id: number; name: string; logo: string }[]; - tokens: { - symbol: string; - address: string; - decimals: number; - name?: string; - logo?: string; - }[]; - chainTokenMap: Map; - tokenChainMap: Map; -} - -const LOGO_URLS: Record = { - WETH: 'https://assets.coingecko.com/coins/images/279/large/ethereum.png?1595348880', - USDS: 'https://assets.coingecko.com/coins/images/39926/standard/usds.webp?1726666683', - SOPH: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png', - KAIA: 'https://assets.coingecko.com/asset_platforms/images/9672/large/kaia.png', - BNB: 'https://assets.coingecko.com/coins/images/825/large/bnb-icon2_2x.png', - // Add ETH as fallback for any ETH-related tokens - ETH: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628', - // Add common token fallbacks - POL: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png', - AVAX: 'https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png', - FUEL: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png', - HYPE: 'https://assets.coingecko.com/asset_platforms/images/243/large/hyperliquid.png', - // Popular swap tokens - DAI: 'https://coin-images.coingecko.com/coins/images/9956/large/Badge_Dai.png?1696509996', - UNI: 'https://coin-images.coingecko.com/coins/images/12504/large/uni.jpg?1696512319', - AAVE: 'https://coin-images.coingecko.com/coins/images/12645/large/AAVE.png?1696512452', - LDO: 'https://coin-images.coingecko.com/coins/images/13573/large/Lido_DAO.png?1696513326', - PEPE: 'https://coin-images.coingecko.com/coins/images/29850/large/pepe-token.jpeg?1696528776', - OP: 'https://coin-images.coingecko.com/coins/images/25244/large/Optimism.png?1696524385', - ZRO: 'https://coin-images.coingecko.com/coins/images/28206/large/ftxG9_TJ_400x400.jpeg?1696527208', - OM: 'https://assets.coingecko.com/coins/images/12151/standard/OM_Token.png?1696511991', - KAITO: 'https://assets.coingecko.com/coins/images/54411/standard/Qm4DW488_400x400.jpg', -}; - -function _processSdkData(sdkData: SupportedChainsResult | null): TransactionSupportData | null { - if (!sdkData || !Array.isArray(sdkData)) return null; - - const chains = sdkData.map((chain) => ({ - id: chain.id, - name: chain.name, - logo: chain.logo, - })); - - const chainTokenMap = new Map(); - const tokenChainMap = new Map(); - const allTokens = new Map(); - - for (const chain of sdkData) { - const tokenSymbols: string[] = []; - // Guard against chains that might not have a tokens array - for (const token of chain.tokens || []) { - // Enhanced logo fallback logic - let finalLogo = token.logo; - if (!finalLogo) { - // First try direct lookup - finalLogo = LOGO_URLS[token.symbol]; - - // Handle wrapped tokens - if (!finalLogo && token.symbol.startsWith('W') && token.symbol.length > 1) { - const baseSymbol = token.symbol.substring(1); - finalLogo = LOGO_URLS[baseSymbol]; - } - - // ETH fallback for ethereum-related tokens - if (!finalLogo && (token.symbol.includes('ETH') || token.symbol === 'WETH')) { - finalLogo = LOGO_URLS['ETH']; - } - } - - // For native tokens (zero address), ensure they have proper logos - if (token.contractAddress === '0x0000000000000000000000000000000000000000') { - if (!finalLogo) { - // Use chain-specific native token logos - const nativeTokenLogos: Record = { - 137: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png', // POL - 43114: - 'https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png', // AVAX - 56: 'https://assets.coingecko.com/coins/images/825/large/bnb-icon2_2x.png', // BNB - 8217: 'https://assets.coingecko.com/asset_platforms/images/9672/large/kaia.png', // KAIA - 50104: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png', // SOPH - }; - finalLogo = nativeTokenLogos[chain.id] || ''; - } - } - - tokenSymbols.push(token.symbol); - - if (!tokenChainMap.has(token.symbol)) { - tokenChainMap.set(token.symbol, []); - } - tokenChainMap.get(token.symbol)!.push(chain.id); - - if (!allTokens.has(token.symbol)) { - allTokens.set(token.symbol, { - symbol: token.symbol, - address: token.contractAddress, - decimals: token.decimals, - name: token.name || token.symbol, - logo: finalLogo, - }); - } - } - chainTokenMap.set(chain.id, tokenSymbols); - } - - return { - chains, - tokens: Array.from(allTokens.values()), - chainTokenMap, - tokenChainMap, - }; -} - -/** - * Get base token metadata based on network - */ -function getBaseTokenMetadata( - _network: 'mainnet' | 'testnet' = 'mainnet', -): Record { - return _network === 'testnet' ? TESTNET_TOKEN_METADATA : TOKEN_METADATA; -} - -const transactionSupportDataCache = new Map(); - -/** - * Gets and processes support data for a given transaction type from the SDK. - * This function caches the processed data to avoid redundant calls and processing. - * @param sdk The NexusSDK instance. - * @param type The type of transaction. - * @returns Processed transaction support data or null. - */ -function getTransactionSupportData( - sdk: NexusSDK, - type: TransactionType, -): TransactionSupportData | null { - if (transactionSupportDataCache.has(type)) { - return transactionSupportDataCache.get(type)!; - } - - let rawData: SupportedChainsResult | null = null; - try { - if (type === 'swap') { - rawData = sdk?.utils?.getSwapSupportedChainsAndTokens?.(); - } else { - // getSupportedChains actually returns the same structure as getSwapSupportedChainsAndTokens - // despite what the TypeScript types say - rawData = sdk?.utils?.getSupportedChains?.() as SupportedChainsResult; - } - } catch (error) { - console.warn(`Failed to fetch support data for ${type} from SDK:`, error); - transactionSupportDataCache.set(type, null); - return null; - } - - const processedData = _processSdkData(rawData); - transactionSupportDataCache.set(type, processedData); - - return processedData; -} - -/** - * Convert destination swap token to standard token metadata format - */ -function convertDestinationTokenToMetadata( - destinationToken: NonNullable>[0], -): EnhancedTokenMetadata { - return { - symbol: destinationToken.symbol, - name: destinationToken.name, - decimals: destinationToken.decimals, - icon: destinationToken.logo, - coingeckoId: '', // Not provided in destination tokens - contractAddress: destinationToken.tokenAddress, - }; -} - -/** - * Get available tokens for a specific chain and transaction type. - * This function is the single source of truth for token resolution. - * It handles all transaction types and uses a caching mechanism for performance. - */ -export function getAvailableTokens(params: TokenResolutionParams): EnhancedTokenMetadata[] { - const { chainId, type, network = 'mainnet', isDestination = false, sdk } = params; - - // Handle swap destination tokens separately as they come from a different source (static list). - if (type === 'swap' && isDestination) { - const baseTokens = Object.values(getBaseTokenMetadata(network)); - let allDestinationTokens: ReturnType[] = []; - - if (chainId) { - const destinationTokens = DESTINATION_SWAP_TOKENS.get(chainId) || []; - allDestinationTokens = destinationTokens - .filter( - (destToken) => !baseTokens.some((baseToken) => baseToken.symbol === destToken.symbol), - ) - .map(convertDestinationTokenToMetadata); - } else { - const allChainTokens = Array.from(DESTINATION_SWAP_TOKENS.values()).flat(); - const uniqueTokens = new Map(); - allChainTokens.forEach((token) => { - if ( - !uniqueTokens.has(token.symbol) && - !baseTokens.some((baseToken) => baseToken.symbol === token.symbol) - ) { - uniqueTokens.set(token.symbol, token); - } - }); - allDestinationTokens = Array.from(uniqueTokens.values()).map( - convertDestinationTokenToMetadata, - ); - } - const enhancedBaseTokens = baseTokens.map((token) => ({ - ...token, - contractAddress: TOKEN_CONTRACT_ADDRESSES[token.symbol]?.[chainId || 0], - })); - const result = [...enhancedBaseTokens, ...allDestinationTokens]; - return result; - } - - // For swap source tokens, use only getSwapSupportedChainsAndTokens (ERC20 tokens only, no native tokens) - if (type === 'swap' && !isDestination && sdk) { - const supportData = getTransactionSupportData(sdk, type); - if (supportData) { - let tokensToDisplay = supportData.tokens; - - if (chainId) { - const supportedSymbols = supportData.chainTokenMap.get(chainId) || []; - tokensToDisplay = tokensToDisplay.filter((t) => supportedSymbols.includes(t.symbol)); - } - - const result = tokensToDisplay.map((token) => { - // Enhanced icon resolution for token options - let finalIcon = token.logo; - if (!finalIcon) { - finalIcon = LOGO_URLS[token.symbol]; - - // Handle wrapped tokens - if (!finalIcon && token.symbol.startsWith('W') && token.symbol.length > 1) { - const baseSymbol = token.symbol.substring(1); - finalIcon = LOGO_URLS[baseSymbol]; - } - - // ETH fallback for ethereum-related tokens - if (!finalIcon && (token.symbol.includes('ETH') || token.symbol === 'WETH')) { - finalIcon = LOGO_URLS['ETH']; - } - } - - return { - symbol: token.symbol, - name: token.name || token.symbol, - decimals: token.decimals, - icon: finalIcon || '', - coingeckoId: '', - contractAddress: token.address as `0x${string}`, - }; - }); - return result; - } - } - - // For all other cases (transfer, bridge, bridgeAndExecute), use the SDK data. - if (sdk) { - const supportData = getTransactionSupportData(sdk, type); - if (supportData) { - let tokensToDisplay = supportData.tokens; - - if (chainId) { - const supportedSymbols = supportData.chainTokenMap.get(chainId) || []; - tokensToDisplay = tokensToDisplay.filter((t) => supportedSymbols.includes(t.symbol)); - } - - const result = tokensToDisplay.map((token) => { - // Enhanced icon resolution for token options - let finalIcon = token.logo; - if (!finalIcon) { - finalIcon = LOGO_URLS[token.symbol]; - - // Handle wrapped tokens - if (!finalIcon && token.symbol.startsWith('W') && token.symbol.length > 1) { - const baseSymbol = token.symbol.substring(1); - finalIcon = LOGO_URLS[baseSymbol]; - } - - // ETH fallback for ethereum-related tokens - if (!finalIcon && (token.symbol.includes('ETH') || token.symbol === 'WETH')) { - finalIcon = LOGO_URLS['ETH']; - } - } - - return { - symbol: token.symbol, - name: token.name || token.symbol, - decimals: token.decimals, - icon: finalIcon || '', - coingeckoId: '', // Not provided by SDK - contractAddress: token.address as `0x${string}`, - }; - }); - return result; - } else { - } - } - - // Fallback for non-SDK or failed SDK calls. - const baseTokens = Object.values(getBaseTokenMetadata(network)); - // For non-swap transactions, include native tokens - if (type !== 'swap') { - const allNativeTokens: Array<{ - symbol: string; - name: string; - decimals: number; - icon: string; - coingeckoId: string; - }> = [ - { - symbol: 'ETH', - name: 'Ether', - decimals: 18, - icon: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png', - coingeckoId: 'ethereum', - }, - { - symbol: 'POL', - name: 'POL', - decimals: 18, - icon: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png', - coingeckoId: 'polygon-ecosystem-token', - }, - { - symbol: 'AVAX', - name: 'Avalanche', - decimals: 18, - icon: 'https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png', - coingeckoId: 'avalanche-2', - }, - { - symbol: 'BNB', - name: 'BNB', - decimals: 18, - icon: 'https://assets.coingecko.com/coins/images/825/large/bnb-icon2_2x.png', - coingeckoId: 'binancecoin', - }, - { - symbol: 'KAIA', - name: 'Kaia', - decimals: 18, - icon: 'https://assets.coingecko.com/asset_platforms/images/9672/large/kaia.png', - coingeckoId: 'kaia', - }, - { - symbol: 'SOPH', - name: 'Sophon', - decimals: 18, - icon: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png', - coingeckoId: 'sophon', - }, - { - symbol: 'FUEL', - name: 'Fuel', - decimals: 9, - icon: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png', - coingeckoId: 'ethereum', - }, - ]; - - // If a specific chain is selected, only include native token for that chain - if (chainId) { - const chainNativeTokens: Record = { - 1: 'ETH', // Ethereum - 10: 'ETH', // Optimism - 137: 'POL', // Polygon - 8453: 'ETH', // Base - 42161: 'ETH', // Arbitrum - 534352: 'ETH', // Scroll - 43114: 'AVAX', // Avalanche - 56: 'BNB', // BNB Chain - 8217: 'KAIA', // Kaia - 50104: 'SOPH', // Sophon - 9889: 'FUEL', // Fuel - }; - - const nativeSymbol = chainNativeTokens[chainId]; - if (nativeSymbol) { - const nativeToken = allNativeTokens.find((t) => t.symbol === nativeSymbol); - if (nativeToken) { - baseTokens.push(nativeToken); - } - } - } else { - // No chain selected, include all native tokens - baseTokens.push(...allNativeTokens); - } - } - - const result = baseTokens.map((token) => { - // Enhanced icon resolution for base tokens - let finalIcon = token.icon; - if (!finalIcon) { - finalIcon = LOGO_URLS[token.symbol]; - - // Handle wrapped tokens - if (!finalIcon && token.symbol.startsWith('W') && token.symbol.length > 1) { - const baseSymbol = token.symbol.substring(1); - finalIcon = LOGO_URLS[baseSymbol]; - } - - // ETH fallback for ethereum-related tokens - if (!finalIcon && (token.symbol.includes('ETH') || token.symbol === 'WETH')) { - finalIcon = LOGO_URLS['ETH']; - } - } - - return { - ...token, - icon: finalIcon || token.icon, - contractAddress: TOKEN_CONTRACT_ADDRESSES[token.symbol]?.[chainId || 0], - }; - }); - return result; -} - -/** - * Convert enhanced token metadata to UI selection options - * Follows Interface Segregation Principle - provides only what UI needs - */ -export function convertTokensToSelectOptions(tokens: EnhancedTokenMetadata[]): TokenSelectOption[] { - return tokens.map((token) => ({ - value: token.symbol, - label: token.symbol, - icon: token.icon, - metadata: token, - })); -} - -/** - * React hook for token resolution with memoization. - * This is the single hook for fetching available tokens for all transaction types. - */ -export function useAvailableTokens(params: TokenResolutionParams): TokenSelectOption[] { - // The useMemo hook is crucial for performance, preventing re-computation on every render. - return useMemo(() => { - const tokens = getAvailableTokens(params); - return convertTokensToSelectOptions(tokens); - }, [params.chainId, params.type, params.network, params.isDestination, params.sdk]); -} - -/** - * Get token contract address with enhanced resolution - * Follows Open/Closed Principle - extensible for new token sources - */ -export function getTokenAddress( - tokenSymbol: string, - chainId: number, - type: TransactionType = 'transfer', -): `0x${string}` { - // Try standard TOKEN_CONTRACT_ADDRESSES first - const standardAddress = TOKEN_CONTRACT_ADDRESSES[tokenSymbol]?.[chainId]; - if (standardAddress) { - return standardAddress; - } - - // For swaps, check DESTINATION_SWAP_TOKENS - if (type === 'swap') { - const chainTokens = DESTINATION_SWAP_TOKENS.get(chainId); - const destinationToken = chainTokens?.find((t) => t.symbol === tokenSymbol); - if (destinationToken) { - return destinationToken.tokenAddress; - } - } - - throw new Error(`Token ${tokenSymbol} not supported on chain ${CHAIN_METADATA[chainId]?.name}`); -} - -/** - * Check if a token is available on a specific chain - * Follows Liskov Substitution Principle - can be used wherever boolean is expected - */ -export function isTokenAvailableOnChain( - tokenSymbol: string, - chainId: number, - type: TransactionType = 'transfer', -): boolean { - // For swaps, be more permissive to avoid aggressive token resets - if (type === 'swap') { - // Check if token exists in either base tokens or destination swap tokens - const baseTokens = TOKEN_CONTRACT_ADDRESSES[tokenSymbol]; - if (baseTokens && baseTokens[chainId]) { - return true; - } - - const chainTokens = DESTINATION_SWAP_TOKENS.get(chainId); - if (chainTokens?.some((t) => t.symbol === tokenSymbol)) { - return true; - } - - // For swap source tokens, be even more permissive since SDK data might be loading - return true; - } - - try { - getTokenAddress(tokenSymbol, chainId, type); - return true; - } catch { - return false; - } -} - -/** - * Get token metadata by symbol with enhanced resolution - * Follows Dependency Inversion Principle - depends on abstractions, not concretions - */ -export function getTokenMetadata( - tokenSymbol: string, - chainId?: number, - type: TransactionType = 'transfer', - network: 'mainnet' | 'testnet' = 'mainnet', -): EnhancedTokenMetadata | null { - // Try base tokens first - const baseTokens = getBaseTokenMetadata(network); - const baseToken = baseTokens[tokenSymbol]; - - if (baseToken) { - return { - ...baseToken, - contractAddress: chainId ? TOKEN_CONTRACT_ADDRESSES[tokenSymbol]?.[chainId] : undefined, - }; - } - - // For swaps, check destination tokens - if (type === 'swap' && chainId) { - const chainTokens = DESTINATION_SWAP_TOKENS.get(chainId); - const destinationToken = chainTokens?.find((t) => t.symbol === tokenSymbol); - - if (destinationToken) { - return convertDestinationTokenToMetadata(destinationToken); - } - } - - return null; -} - -/** - * Filter tokens based on availability for a specific chain - * Utility function for component-level filtering - */ -export function filterTokensByChainAvailability( - tokens: EnhancedTokenMetadata[], - chainId: number, - type: TransactionType = 'transfer', -): EnhancedTokenMetadata[] { - return tokens.filter((token) => isTokenAvailableOnChain(token.symbol, chainId, type)); -} - -/** - * Get supported chain IDs for a specific token and transaction type. - * This is the single source of truth for chain resolution. - */ -export function getSupportedChainsForToken( - tokenSymbol: string, - type: TransactionType, - sdk?: NexusSDK, - isDestination?: boolean, -): number[] { - // For swap destination, use the static list + base tokens. - if (type === 'swap' && isDestination) { - const supportedChains = new Set(); - - // Check DESTINATION_SWAP_TOKENS - for (const [chainId, tokens] of DESTINATION_SWAP_TOKENS.entries()) { - if (tokens.some((token) => token.symbol === tokenSymbol)) { - supportedChains.add(chainId); - } - } - - // Check base tokens (TOKEN_CONTRACT_ADDRESSES) - const tokenContracts = TOKEN_CONTRACT_ADDRESSES[tokenSymbol]; - if (tokenContracts) { - Object.keys(tokenContracts).forEach((chainId) => supportedChains.add(Number(chainId))); - } - - return Array.from(supportedChains).sort((a, b) => a - b); - } - - // For swap source, use only getSwapSupportedChainsAndTokens - if (type === 'swap' && !isDestination && sdk) { - const supportData = getTransactionSupportData(sdk, type); - if (supportData) { - return (supportData.tokenChainMap.get(tokenSymbol) || []).sort((a, b) => a - b); - } - } - - // For all other cases (transfer, bridge, bridgeAndExecute), use the SDK data. - if (sdk) { - const supportData = getTransactionSupportData(sdk, type); - if (supportData) { - return (supportData.tokenChainMap.get(tokenSymbol) || []).sort((a, b) => a - b); - } - } - - // Fallback for non-SDK or failed SDK calls. - // For non-swap transactions, include both ERC20 contracts and native tokens on supported chains - const supportedChains = new Set(); - - // Add chains from TOKEN_CONTRACT_ADDRESSES (ERC20 tokens) - const tokenContracts = TOKEN_CONTRACT_ADDRESSES[tokenSymbol]; - if (tokenContracts) { - Object.keys(tokenContracts).forEach((chainId) => supportedChains.add(Number(chainId))); - } - - // Include native token chains if the token symbol matches a known native token - const nativeTokens: Record = { - ETH: [1, 10, 8453, 42161, 534352, 11155111, 84532, 421614, 11155420, 534351], // Ethereum networks - POL: [137, 80002], // Polygon - AVAX: [43114, 43113], // Avalanche - BNB: [56, 97], // BNB Chain - KAIA: [8217, 82170], // Kaia - SOPH: [50104], // Sophon - FUEL: [9889, 10143], // Fuel - }; - - const nativeChains = nativeTokens[tokenSymbol]; - if (nativeChains) { - nativeChains.forEach((chainId) => supportedChains.add(chainId)); - } - - return Array.from(supportedChains).sort((a, b) => a - b); -} - -/** - * Check if a token-chain combination is valid for swaps - * Used for validation and reset logic - */ -export function isTokenChainCombinationValid( - tokenSymbol?: string, - chainId?: number, - type: TransactionType = 'transfer', -): boolean { - if (!tokenSymbol || !chainId) return true; // Allow empty selections - - // For swaps, be more lenient with validation to avoid aggressive resets - // Let the user make selections and validate at execution time - if (type === 'swap') { - return true; - } - - return isTokenAvailableOnChain(tokenSymbol, chainId, type); -} - -/** - * Get chains that should be available based on a selected token. - * Filters a list of available chains against chains that support the token. - */ -export function getFilteredChainsForToken( - tokenSymbol: string | undefined, - availableChains: number[], - type: TransactionType, - sdk?: NexusSDK, - isDestination?: boolean, -): number[] { - if (!tokenSymbol) { - return availableChains; - } - - const supportedChains = getSupportedChainsForToken(tokenSymbol, type, sdk, isDestination); - return availableChains.filter((chainId) => supportedChains.includes(chainId)); -} diff --git a/packages/widgets/src/utils/utils.ts b/packages/widgets/src/utils/utils.ts deleted file mode 100644 index b341b4e7..00000000 --- a/packages/widgets/src/utils/utils.ts +++ /dev/null @@ -1,626 +0,0 @@ -import { clsx, type ClassValue } from 'clsx'; -import { twMerge } from 'tailwind-merge'; -import { OrchestratorStatus, ReviewStatus, SwapInputData } from '../types'; -import { Abi, isAddress } from 'viem'; -import { - type BridgeParams, - type TransferParams, - type BridgeAndExecuteParams, - CHAIN_METADATA, -} from '@nexus/commons'; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - -export const getButtonText = (status: OrchestratorStatus, reviewStatus: ReviewStatus) => { - if (status === 'initializing') return 'Sign'; - if (status === 'simulation_error') return 'Try Again'; - if (reviewStatus === 'gathering_input') return 'Start Transaction'; - if (reviewStatus === 'simulating') return 'Simulating...'; - if (reviewStatus === 'needs_allowance') return 'Approve and Continue'; - if (reviewStatus === 'ready') return 'Start Transaction'; - return 'Continue'; -}; - -export const getOperationText = (type: string) => { - switch (type) { - case 'bridge': - return 'Bridging'; - case 'transfer': - return 'Transferring'; - case 'bridgeAndExecute': - return 'Bridge & Execute'; - case 'swap': - return 'Swapping'; - default: - return 'Processing'; - } -}; - -export const getStatusText = (stepData: any, operationType: string) => { - if (!stepData) return 'Verifying Request'; - - const { type } = stepData; - const opText = getOperationText(operationType); - - switch (type) { - case 'INTENT_ACCEPTED': - return 'Intent Accepted'; - case 'INTENT_HASH_SIGNED': - return 'Signing Transaction'; - case 'INTENT_SUBMITTED': - return 'Submitting Transaction'; - case 'INTENT_COLLECTION': - return 'Collecting Confirmations'; - case 'INTENT_COLLECTION_COMPLETE': - return 'Confirmations Complete'; - case 'APPROVAL': - return 'Approving'; - case 'TRANSACTION_SENT': - return 'Sending Transaction'; - case 'RECEIPT_RECEIVED': - return 'Receipt Received'; - case 'TRANSACTION_CONFIRMED': - case 'INTENT_FULFILLED': - return `${opText} Complete`; - default: - return `Processing ${opText}`; - } -}; - -/** - * Common error patterns and their user-friendly messages - */ -const ERROR_PATTERNS = { - USER_REJECTED: [ - /user rejected/i, - /user denied/i, - /user cancelled/i, - /user refused/i, - /action_rejected/i, - /userRejectedRequest/i, - /transaction rejected by user/i, - /user rejected transaction/i, - /user canceled/i, - ], - NETWORK_ERROR: [ - /network error/i, - /connection failed/i, - /fetch failed/i, - /network request failed/i, - /rpc error/i, - /timeout/i, - /backend initialization failed/i, - /simulation client error/i, - ], - INSUFFICIENT_FUNDS: [ - /insufficient funds/i, - /insufficient balance/i, - /not enough/i, - /exceeds balance/i, - /balance too low/i, - /you don't have enough/i, - /sender doesn't have enough/i, - /transfer amount exceeds balance/i, - /erc20: transfer amount exceeds balance/i, - /erc20.*insufficient/i, - ], - GAS_ERROR: [ - /gas required exceeds allowance/i, - /out of gas/i, - /gas estimation failed/i, - /gas limit/i, - /intrinsic gas too low/i, - ], - TRANSACTION_FAILED: [ - /transaction failed/i, - /transaction reverted/i, - /execution reverted/i, - /transaction underpriced/i, - /nonce too low/i, - /call exception/i, - /transaction was reverted/i, - /replacement transaction underpriced/i, - /already known/i, - /reverted with reason/i, - /transaction rejected/i, - /transaction not mined within/i, - ], - ALLOWANCE_ERROR: [ - /allowance/i, - /approval/i, - /approve/i, - /token approval failed/i, - /insufficient allowance/i, - ], - CONTRACT_ERROR: [ - /contract/i, - /invalid address/i, - /abi/i, - /function not found/i, - /contract execution failed/i, - ], - CHAIN_ERROR: [ - /unrecognized chain id/i, - /unsupported chain/i, - /chain not found/i, - /invalid chain/i, - /try adding the chain using wallet_addEthereumChain/i, - /wrong network/i, - /switch to correct network/i, - ], - INIT_ERROR: [ - /initialization failed/i, - /sdk not initialized/i, - /provider not connected/i, - /wallet provider not connected/i, - /setup failed/i, - ], - BRIDGE_ERROR: [ - /bridge failed/i, - /bridging error/i, - /cross-chain error/i, - /bridge transaction failed/i, - ], - EXECUTE_ERROR: [ - /execute failed/i, - /execution error/i, - /execute phase failed/i, - /contract execution error/i, - ], - SWAP_ERROR: [ - /insufficient funds/i, - /insufficient balance/i, - /swap failed/i, - /swap error/i, - /vsc sbc tx/i, - /swap transaction failed/i, - /slippage/i, - /price impact/i, - /swap intent failed/i, - /swap execution failed/i, - /cot not present/i, - /chain of trust not present/i, - ], -} as const; - -/** - * User-friendly error messages - */ -const USER_FRIENDLY_MESSAGES = { - USER_REJECTED: "Transaction was cancelled. Please try again when you're ready to proceed.", - NETWORK_ERROR: 'Network connection issue. Please check your internet connection and try again.', - INSUFFICIENT_FUNDS: "You don't have enough balance to complete this transaction.", - GAS_ERROR: 'Transaction fee estimation failed. Please try again or adjust the gas settings.', - TRANSACTION_FAILED: 'Transaction failed to execute. This might be a temporary network issue.', - ALLOWANCE_ERROR: 'Token approval failed. Please try approving the token again.', - CONTRACT_ERROR: 'Smart contract interaction failed. Please try again.', - CHAIN_ERROR: 'This network is not added to your wallet. Please add it to continue.', - INIT_ERROR: 'Wallet connection issue. Please make sure your wallet is connected and try again.', - BRIDGE_ERROR: 'Cross-chain transfer failed. Please try again.', - EXECUTE_ERROR: 'Smart contract execution failed. Please try again.', - SWAP_ERROR: - 'Swap transaction failed. The destination chain may not support this token pair. Please try a different route.', - UNKNOWN: 'An unexpected error occurred. Please try again.', -} as const; - -/** - * Extract meaningful information from error messages while removing technical details - */ -function cleanErrorMessage(message: string): string { - // Remove version and package information - message = message.replace(/Version: [^\s]+/gi, ''); - message = message.replace(/viem@[^\s]+/gi, ''); - message = message.replace(/arcana@[^\s]+/gi, ''); - - // Remove common error prefixes that create noise - message = message.replace(/^Error: /gi, ''); - message = message.replace(/^RPC Error: /gi, ''); - - // Handle chained error messages - extract the most meaningful part - // Pattern: "Operation failed: Phase failed: Actual error" - const chainedErrorMatch = message.match( - /([^:]+operation failed|[^:]+phase failed|[^:]+error):\s*(.+)/i, - ); - if (chainedErrorMatch) { - const [, , actualError] = chainedErrorMatch; - // If the actual error is meaningful, use it; otherwise keep the chain - if (actualError && actualError.length > 10 && !actualError.includes('failed')) { - message = actualError.trim(); - } - } - - // Remove redundant "failed" phrases that pile up - message = message.replace(/\b(operation|phase|transaction|execution)\s+failed:\s*/gi, ''); - message = message.replace(/\bfailed:\s*/gi, ''); - - // Split by common delimiters and clean up - const lines = message.split(/[.\n]/).filter((line) => line.trim()); - const uniqueLines = [...new Set(lines.map((line) => line.trim()))]; - - // Take the first meaningful line if we have multiple - const meaningfulLine = - uniqueLines.find( - (line) => - line.length > 5 && - !line.toLowerCase().includes('operation failed') && - !line.toLowerCase().includes('phase failed'), - ) || uniqueLines[0]; - - return meaningfulLine?.trim() || message.trim(); -} - -/** - * Determine the error category based on the error message - */ -function categorizeError(errorMessage: string): keyof typeof USER_FRIENDLY_MESSAGES { - const message = errorMessage.toLowerCase(); - - for (const [category, patterns] of Object.entries(ERROR_PATTERNS)) { - if (patterns.some((pattern) => pattern.test(message))) { - return category as keyof typeof USER_FRIENDLY_MESSAGES; - } - } - - return 'UNKNOWN'; -} - -/** - * Format error messages to be user-friendly for display in UI components - * - * @param error - The error object or string from various sources (viem, Arcana SDK, etc.) - * @param context - Optional context about where the error occurred (e.g., 'simulation', 'bridge', 'execute') - * @returns A user-friendly error message - */ -function getRawErrorMessage(error: unknown): string { - if (error instanceof Error) return error.message; - if (typeof error === 'string') return error; - if (error && typeof error === 'object') { - const e = error as any; - return e.message || e.error || e.details || String(error); - } - return String(error); -} - -function handleUnknownCategory(cleanedMessage: string, context?: string): string | null { - if (cleanedMessage && cleanedMessage.length < 100 && cleanedMessage.length > 5) { - if ( - !cleanedMessage.includes('0x') && - !cleanedMessage.includes('viem@') && - !cleanedMessage.includes('Error:') - ) { - return cleanedMessage; - } - } - if (context === 'simulation') - return 'Unable to simulate this transaction. Please verify your inputs and try again.'; - if (context === 'transaction') - return 'Transaction could not be completed. Please check your wallet and try again.'; - if (context === 'bridge') - return 'Cross-chain transfer failed. Please check network connectivity and try again.'; - if (context === 'execute') - return 'Smart contract execution failed. Please verify the transaction details.'; - return null; -} - -export function formatErrorForUI(error: unknown, context?: string): string { - const errorMessage = getRawErrorMessage(error); - - console.error('Error being formatted for UI:', { error, errorMessage, context }); - - if (errorMessage.includes('COT not present') || errorMessage.includes('COT not available')) { - return 'This token pair is not supported on the selected destination chain. Please try a different token or destination chain.'; - } - - const cleanedMessage = cleanErrorMessage(errorMessage); - const category = categorizeError(cleanedMessage); - const userFriendlyMessage = USER_FRIENDLY_MESSAGES[category]; - - if (category === 'UNKNOWN') { - const alt = handleUnknownCategory(cleanedMessage, context); - if (alt) return alt; - - console.warn('Unknown error category detected:', { - cleanedMessage, - originalError: error, - context, - }); - } - - if (context && category !== 'USER_REJECTED') { - const contextual = getContextualErrorMessage(category, context); - if (contextual) return contextual; - } - - return userFriendlyMessage; -} - -/** - * Get context-specific error messages for better user experience - */ -function getContextualErrorMessage( - category: keyof typeof USER_FRIENDLY_MESSAGES, - context: string, -): string | null { - const contextMap: Record>> = { - simulation: { - NETWORK_ERROR: 'Unable to simulate transaction. Please check your connection and try again.', - INSUFFICIENT_FUNDS: 'Simulation shows insufficient balance for this transaction.', - GAS_ERROR: 'Unable to estimate transaction fees. Please try again.', - CONTRACT_ERROR: 'Contract simulation failed. Please verify the contract details.', - CHAIN_ERROR: 'Simulation failed due to network issues. Please add the required network.', - INIT_ERROR: 'Please connect your wallet to simulate transactions.', - }, - bridge: { - NETWORK_ERROR: 'Bridge service is temporarily unavailable. Please try again.', - INSUFFICIENT_FUNDS: 'Insufficient balance for cross-chain transfer.', - BRIDGE_ERROR: 'Cross-chain transfer failed. Please try again.', - CHAIN_ERROR: 'Source or destination network not supported in your wallet.', - }, - execute: { - CONTRACT_ERROR: 'Smart contract execution failed. Please verify the contract is correct.', - GAS_ERROR: 'Execution failed due to gas issues. Please try again.', - EXECUTE_ERROR: 'Contract interaction failed. Please try again.', - ALLOWANCE_ERROR: 'Token approval required before execution.', - }, - swap: { - NETWORK_ERROR: 'Swap service is temporarily unavailable. Please try again.', - INSUFFICIENT_FUNDS: 'Insufficient balance to complete the swap.', - SWAP_ERROR: 'Swap failed. Please verify your token selection and amount.', - GAS_ERROR: 'Swap failed due to gas issues. Please try again.', - CONTRACT_ERROR: 'Swap contract interaction failed. Please try again.', - ALLOWANCE_ERROR: 'Token approval required for swap.', - }, - allowance: { - ALLOWANCE_ERROR: 'Token approval transaction failed. Please try again.', - GAS_ERROR: 'Approval failed due to insufficient gas. Please try again.', - CONTRACT_ERROR: 'Token contract approval failed. Please verify the token.', - }, - initialization: { - INIT_ERROR: 'Wallet setup failed. Please reconnect your wallet and try again.', - NETWORK_ERROR: 'Unable to connect to Nexus services. Please try again.', - CHAIN_ERROR: 'Unsupported network. Please switch to a supported network.', - }, - }; - - const contextMessages = contextMap[context]; - return contextMessages?.[category] || null; -} - -/** - * Check if an error indicates user rejection/cancellation - */ -export function isUserRejectionError(error: unknown): boolean { - const errorMessage = error instanceof Error ? error.message : String(error); - return ERROR_PATTERNS.USER_REJECTED.some((pattern) => pattern.test(errorMessage)); -} - -/** - * Check if an error is related to an unrecognized chain - */ -export function isChainError(error: unknown): boolean { - const errorMessage = error instanceof Error ? error.message : String(error); - return ERROR_PATTERNS.CHAIN_ERROR.some((pattern) => pattern.test(errorMessage)); -} - -/** - * Format swap-specific error messages for better user experience - * - * @param error - The error object or string from swap operations - * @returns A user-friendly error message specific to swap operations - */ -export function formatSwapError(error: unknown): string { - // Use the general error formatter with swap context - return formatErrorForUI(error, 'swap'); -} - -/** - * Check if an error is swap-related - */ -export function isSwapError(error: unknown): boolean { - const errorMessage = error instanceof Error ? error.message : String(error); - return ERROR_PATTERNS.SWAP_ERROR.some((pattern) => pattern.test(errorMessage)); -} - -/** - * Extract chain ID from error message - * Supports both hex (0x...) and decimal formats - */ -export function extractChainIdFromError(error: unknown): number | null { - const errorMessage = error instanceof Error ? error.message : String(error); - const hexMatch = errorMessage.match(/(?:chain id|chainid)\s*["']?(0x[a-f0-9]+)["']?/i); - if (hexMatch) { - const chainId = parseInt(hexMatch[1], 16); - return isNaN(chainId) ? null : chainId; - } - const decimalMatch = errorMessage.match(/(?:chain id|chainid)\s*["']?(\d+)["']?/i); - if (decimalMatch) { - const chainId = parseInt(decimalMatch[1], 10); - return isNaN(chainId) ? null : chainId; - } - - return null; -} - -/** - * Add a chain to the user's wallet using wallet_addEthereumChain - */ -export async function addChainToWallet( - chainId: number, - provider: { request: (args: { method: string; params?: any[] }) => Promise }, -): Promise { - const chainMetadata = CHAIN_METADATA[chainId]; - if (!chainMetadata) { - console.error(`Chain metadata not found for chain ID: ${chainId}`); - return false; - } - - if (!provider) { - console.error('No provider available'); - return false; - } - - try { - await provider.request({ - method: 'wallet_addEthereumChain', - params: [ - { - chainId: `0x${chainId.toString(16)}`, - chainName: chainMetadata.name, - nativeCurrency: chainMetadata.nativeCurrency, - rpcUrls: chainMetadata.rpcUrls, - blockExplorerUrls: chainMetadata.blockExplorerUrls, - iconUrls: [chainMetadata.logo], - }, - ], - }); - return true; - } catch (error) { - console.error('Failed to add chain to wallet:', error); - return false; - } -} - -/** - * Find ABI fragment for given function name (optionally matching parameter count) - */ -export function findAbiFragment(abi: Abi, functionName: string, paramCount?: number) { - return (abi as any[]).find( - (item) => - item.type === 'function' && - item.name === functionName && - (paramCount === undefined || (item.inputs || []).length === paramCount), - ); -} - -export const getContentKey = (status: string, additionalStates?: string[]): string => { - if (['processing', 'success', 'error'].includes(status)) { - return 'processor'; - } - - if (status === 'set_allowance') { - return 'allowance'; - } - - if (additionalStates?.includes(status)) { - return status; - } - - return 'review'; -}; - -export const formatCost = (cost: string | number | bigint) => { - const costStr = typeof cost === 'bigint' ? cost.toString() : String(cost); - const numCost = parseFloat(costStr); - if (isNaN(numCost)) return 'Invalid'; - if (numCost < 0) return 'Invalid'; - if (numCost === 0) return 'Free'; - if (numCost < 0.001) return '< 0.001'; - return numCost.toFixed(6); -}; - -export function truncateAddress( - address: string, - startLength: number = 6, - endLength: number = 4, -): string { - if (!isAddress(address)) return address; - - if (address.length <= startLength + endLength + 2) return address; - - return `${address.slice(0, startLength)}...${address.slice(-endLength)}`; -} - -export const getModalTitle = (status: OrchestratorStatus, modalTitle: string) => { - if (status === 'set_allowance') return 'Approve Token Allowance'; - return modalTitle; -}; - -export const getPrimaryButtonText = (status: OrchestratorStatus, reviewStatus: ReviewStatus) => { - if (status === 'set_allowance') return 'Approve & Continue'; - return getButtonText(status, reviewStatus); -}; - -// Union type utility functions for handling different transaction input structures -type TransactionInputData = - | Partial - | Partial - | Partial - | Partial - | null - | undefined; - -/** - * Safely extract token field from union transaction input data - */ -export function getTokenFromInputData(data: TransactionInputData): string | undefined { - if (!data) return undefined; - - // For SwapInputData, check fromTokenAddress first, then other fields - if ('fromTokenAddress' in data && typeof data.fromTokenAddress === 'string') { - return data.fromTokenAddress; - } - - // For SwapConfig, check nested inputs structure - if ('inputs' in data && data.inputs) { - const inputs = data.inputs as any; - return inputs.inputToken || inputs.fromToken || inputs.token || inputs.fromTokenAddress; - } - - // For other transaction types, access directly - if ('token' in data && typeof data.token === 'string') { - return data.token; - } - - return undefined; -} - -/** - * Safely extract amount field from union transaction input data - */ -export function getAmountFromInputData(data: TransactionInputData): string | number | undefined { - if (!data) return undefined; - - // For SwapInputData, check fromAmount first, then amount - if ('fromAmount' in data && data.fromAmount !== undefined) { - return data.fromAmount; - } - - // For SwapConfig, check nested inputs structure - if ('inputs' in data && data.inputs) { - const inputs = data.inputs as any; - return inputs.amount || inputs.fromAmount; - } - - // For other transaction types, access directly - if ('amount' in data) { - return data.amount; - } - - return undefined; -} - -/** - * Safely extract chainId from union transaction input data - */ -export function getChainIdFromInputData(data: TransactionInputData): number | undefined { - if (!data) return undefined; - - // For SwapConfig, check nested inputs structure first - if ('inputs' in data && data.inputs) { - const inputs = data.inputs as any; - return inputs.chainId || inputs.toChainID; - } - - // For other transaction types, access directly - if ('chainId' in data && typeof data.chainId === 'number') { - return data.chainId; - } - - if ('toChainId' in data && typeof data.toChainId === 'number') { - return data.toChainId; - } - - return undefined; -} diff --git a/packages/widgets/tsconfig.json b/packages/widgets/tsconfig.json deleted file mode 100644 index 781bfb12..00000000 --- a/packages/widgets/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./dist", - "baseUrl": ".", - "jsx": "react-jsx", - "lib": ["dom", "dom.iterable", "esnext"], - "paths": { - "@nexus/commons": ["../commons/index.ts"], - "@nexus/commons/*": ["../commons/*"], - "@avail-project/nexus-core": ["../core/index.ts"], - "@avail-project/nexus-core/*": ["../core/*"], - "sdk": ["../core/sdk/index.ts"], - "sdk/*": ["../core/sdk/*"], - "adapters/*": ["../core/adapters/*"], - "integrations/*": ["../core/integrations/*"] - } - }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"], - "exclude": ["dist", "node_modules"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 86e102bc..00000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,5925 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@rollup/plugin-alias': - specifier: ^5.1.1 - version: 5.1.1(rollup@4.50.2) - '@types/node': - specifier: ^20.0.0 - version: 20.19.17 - husky: - specifier: ^8.0.0 - version: 8.0.3 - prettier: - specifier: ^3.0.0 - version: 3.6.2 - rimraf: - specifier: ^5.0.0 - version: 5.0.10 - typescript: - specifier: ^5.0.0 - version: 5.9.2 - - packages/commons: - dependencies: - '@arcana/ca-common': - specifier: 1.0.1-alpha.6 - version: 1.0.1-alpha.6(@cosmjs/proto-signing@0.34.0)(@cosmjs/stargate@0.34.0)(axios@1.12.2)(decimal.js@10.6.0)(fuels@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)))(google-protobuf@3.21.4)(long@5.3.2)(msgpackr@1.11.5)(viem@2.37.6(typescript@5.9.2)) - '@cosmjs/proto-signing': - specifier: ^0.34.0 - version: 0.34.0 - decimal.js: - specifier: ^10.6.0 - version: 10.6.0 - fuels: - specifier: 0.101.1 - version: 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - viem: - specifier: ^2.31.7 - version: 2.37.6(typescript@5.9.2) - devDependencies: - '@rollup/plugin-commonjs': - specifier: ^25.0.0 - version: 25.0.8(rollup@4.50.2) - '@rollup/plugin-json': - specifier: 6.1.0 - version: 6.1.0(rollup@4.50.2) - '@rollup/plugin-node-resolve': - specifier: ^15.0.0 - version: 15.3.1(rollup@4.50.2) - '@rollup/plugin-typescript': - specifier: ^11.0.0 - version: 11.1.6(rollup@4.50.2)(tslib@2.8.1)(typescript@5.9.2) - rollup: - specifier: ^4.0.0 - version: 4.50.2 - rollup-plugin-dts: - specifier: ^6.0.0 - version: 6.2.3(rollup@4.50.2)(typescript@5.9.2) - typescript: - specifier: ^5.0.0 - version: 5.9.2 - - packages/core: - dependencies: - '@arcana/ca-common': - specifier: 1.0.1-alpha.6 - version: 1.0.1-alpha.6(@cosmjs/proto-signing@0.34.0)(@cosmjs/stargate@0.34.0)(axios@1.12.2)(decimal.js@10.6.0)(fuels@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)))(google-protobuf@3.21.4)(long@5.3.2)(msgpackr@1.11.5)(viem@2.37.6(typescript@5.9.2)) - '@cosmjs/proto-signing': - specifier: ^0.34.0 - version: 0.34.0 - '@cosmjs/stargate': - specifier: ^0.34.0 - version: 0.34.0 - '@metamask/safe-event-emitter': - specifier: 3.1.2 - version: 3.1.2 - '@starkware-industries/starkware-crypto-utils': - specifier: ^0.2.1 - version: 0.2.1 - axios: - specifier: ^1.7.7 - version: 1.12.2 - decimal.js: - specifier: ^10.6.0 - version: 10.6.0 - es-toolkit: - specifier: ^1.39.8 - version: 1.39.10 - fuels: - specifier: 0.101.1 - version: 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - it-ws: - specifier: ^6.1.5 - version: 6.1.5 - long: - specifier: ^5.3.2 - version: 5.3.2 - msgpackr: - specifier: ^1.11.4 - version: 1.11.5 - tslib: - specifier: 2.8.1 - version: 2.8.1 - viem: - specifier: ^2.0.0 - version: 2.37.6(typescript@5.9.2) - devDependencies: - '@rollup/plugin-commonjs': - specifier: ^25.0.0 - version: 25.0.8(rollup@4.50.2) - '@rollup/plugin-json': - specifier: 6.1.0 - version: 6.1.0(rollup@4.50.2) - '@rollup/plugin-node-resolve': - specifier: ^15.0.0 - version: 15.3.1(rollup@4.50.2) - '@rollup/plugin-typescript': - specifier: ^11.0.0 - version: 11.1.6(rollup@4.50.2)(tslib@2.8.1)(typescript@5.9.2) - rollup: - specifier: ^4.0.0 - version: 4.50.2 - rollup-plugin-dts: - specifier: ^6.0.0 - version: 6.2.3(rollup@4.50.2)(typescript@5.9.2) - rollup-plugin-typescript2: - specifier: 0.36.0 - version: 0.36.0(rollup@4.50.2)(typescript@5.9.2) - typescript: - specifier: ^5.0.0 - version: 5.9.2 - - packages/widgets: - dependencies: - '@lottiefiles/dotlottie-react': - specifier: 0.14.2 - version: 0.14.2(react@19.1.1) - '@nexus/commons': - specifier: workspace:* - version: link:../commons - '@nexus/core': - specifier: workspace:* - version: link:../core - class-variance-authority: - specifier: 0.7.1 - version: 0.7.1 - clsx: - specifier: 2.1.1 - version: 2.1.1 - decimal.js: - specifier: 10.4.3 - version: 10.4.3 - motion: - specifier: 12.23.0 - version: 12.23.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: - specifier: '>=16.8.0' - version: 19.1.1 - react-dom: - specifier: '>=16.8.0' - version: 19.1.1(react@19.1.1) - tailwind-merge: - specifier: 3.3.1 - version: 3.3.1 - viem: - specifier: ^2.0.0 - version: 2.37.6(typescript@5.9.2) - devDependencies: - '@rollup/plugin-commonjs': - specifier: ^25.0.0 - version: 25.0.8(rollup@4.50.2) - '@rollup/plugin-json': - specifier: 6.1.0 - version: 6.1.0(rollup@4.50.2) - '@rollup/plugin-node-resolve': - specifier: ^15.0.0 - version: 15.3.1(rollup@4.50.2) - '@rollup/plugin-typescript': - specifier: ^11.0.0 - version: 11.1.6(rollup@4.50.2)(tslib@2.8.1)(typescript@5.9.2) - '@tailwindcss/postcss': - specifier: 4.1.10 - version: 4.1.10 - '@types/react': - specifier: 19.1.8 - version: 19.1.8 - '@types/react-dom': - specifier: 19.1.6 - version: 19.1.6(@types/react@19.1.8) - autoprefixer: - specifier: 10.4.21 - version: 10.4.21(postcss@8.5.6) - postcss: - specifier: 8.5.6 - version: 8.5.6 - postcss-import: - specifier: 16.1.1 - version: 16.1.1(postcss@8.5.6) - postcss-nesting: - specifier: 13.0.2 - version: 13.0.2(postcss@8.5.6) - rollup: - specifier: ^4.0.0 - version: 4.50.2 - rollup-plugin-dts: - specifier: ^6.0.0 - version: 6.2.3(rollup@4.50.2)(typescript@5.9.2) - rollup-plugin-postcss: - specifier: 4.0.2 - version: 4.0.2(postcss@8.5.6) - rollup-plugin-typescript2: - specifier: 0.36.0 - version: 0.36.0(rollup@4.50.2)(typescript@5.9.2) - tailwindcss: - specifier: 4.1.10 - version: 4.1.10 - typescript: - specifier: ^5.0.0 - version: 5.9.2 - -packages: - - '@adraffy/ens-normalize@1.11.0': - resolution: {integrity: sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==} - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@arcana/ca-common@1.0.1-alpha.6': - resolution: {integrity: sha512-v7+aaqPDOncpXAU/KkhM7G66zPnzrPzRO3HbCfvcGtMDEbil6KK5xB3Ayd5w6OzlWWHMBpRiqEfH29h0RQVupQ==} - peerDependencies: - '@cosmjs/proto-signing': ^0.34.0 - '@cosmjs/stargate': ^0.34.0 - axios: ^1.10.0 - decimal.js: ^10.6.0 - fuels: 0.101.1 - long: ^5.3.2 - msgpackr: ^1.11.4 - viem: ^2.31.7 - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@bufbuild/protobuf@2.8.0': - resolution: {integrity: sha512-r1/0w5C9dkbcdjyxY8ZHsC5AOWg4Pnzhm2zu7LO4UHSounp2tMm6Y+oioV9zlGbLveE7YaWRDUk48WLxRDgoqg==} - - '@cosmjs/amino@0.34.0': - resolution: {integrity: sha512-wvVMmsr5cM7BSY1Z6QkOuJOjWaC4u5xjvfEO9tSpFhxjXeYlkZapU+Zp88pK6hG/UJUkGD301MN+STFbfWW2xA==} - - '@cosmjs/crypto@0.34.0': - resolution: {integrity: sha512-hn8Z1RYS9bhT5mbitGhPYF5CMcln9r2BVZ7nXIpfpI7TdhUEmNnHVI2ddodxFun0uOg8kokOM6X/etD/MZ6NFA==} - - '@cosmjs/encoding@0.34.0': - resolution: {integrity: sha512-oWUA9VTnr74GHMdMCvaaCfP0g66Y9iT7TA8vkWB1sfd+fO5FznAcMACEiF+sBE0TBoKGr02tYCJDCe9XNp2gOg==} - - '@cosmjs/json-rpc@0.34.0': - resolution: {integrity: sha512-2j0kmz1l3ftVkSRjt1d3H0iHlP5s02ULGz4CBF+Da/2u93ghudxfC38i0QiWKIjIGtqUv5w9ryd0YqIgnmuEew==} - - '@cosmjs/math@0.34.0': - resolution: {integrity: sha512-E/7dxu/hhbVEz1NNGJi+gPAadEtlk4N1ONm4CRgTnVWmPSLHNFgATF+UANAVUVAOfy6OpB0t94gAHRLYnEZYeA==} - - '@cosmjs/proto-signing@0.34.0': - resolution: {integrity: sha512-1/f4JNSAhsP5lr7fdCJxT+qkWqeDq8vViwCilqMIkqvxLAcf6FxEkvmTOpYBAdOT5fVe3+5nZ5GX5FYMq1tdfA==} - - '@cosmjs/socket@0.34.0': - resolution: {integrity: sha512-smIYDsRVLkP/q/Rkxq7Lutrxly3uJOisKvcdpNGkG9PVENwYdF5imHwNy/pLhOIRfk8AGE2s03ag0b2HYwxSzQ==} - - '@cosmjs/stargate@0.34.0': - resolution: {integrity: sha512-FU/A0OdkNKfqQ4d7CC8KceTVGoCy3BemFgVjbXL/K5FnAafEjqqWInQ531oNvBejpMdm1NSkfbp97CfILeTW7A==} - - '@cosmjs/stream@0.34.0': - resolution: {integrity: sha512-87pWCl4g1Cm11cX0iK8nSQYs7oswPUShlwOF8feIrxwC+bqLJh/oEtl7yjjXD2ie8UgtPZAvo9GQ2OTCMpK3Ww==} - - '@cosmjs/tendermint-rpc@0.34.0': - resolution: {integrity: sha512-riUuEG8VG90zJAe6r+mklRSHDZ64fb9JGU/JtQM8YIiwakrkruK21vtiejjgU9da5PHziabta0+N5SYUvHV+bA==} - - '@cosmjs/utils@0.34.0': - resolution: {integrity: sha512-yj8ET2NKCHTFodo8guyEFvE3ZAu1eyp/LiH/oyesNoR6g2Se+aG4ViMMrD4ApoGf2bRtQTC4JWWODQSNUVteJg==} - - '@csstools/selector-resolve-nested@3.1.0': - resolution: {integrity: sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==} - engines: {node: '>=18'} - peerDependencies: - postcss-selector-parser: ^7.0.0 - - '@csstools/selector-specificity@5.0.0': - resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} - engines: {node: '>=18'} - peerDependencies: - postcss-selector-parser: ^7.0.0 - - '@esbuild/aix-ppc64@0.25.1': - resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.1': - resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.1': - resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.1': - resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.1': - resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.1': - resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.1': - resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.1': - resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.1': - resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.1': - resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.1': - resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.1': - resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.1': - resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.1': - resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.1': - resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.1': - resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.1': - resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.1': - resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.1': - resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.1': - resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.1': - resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.1': - resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.1': - resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.1': - resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.1': - resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@fuel-ts/abi-coder@0.101.1': - resolution: {integrity: sha512-PgKO4BLo8dzwdJqHIMmOtoOiV/a8OIqPju9h3maOLXMDwgVXxL/NLku33iLP8CDuFu91AssAOg5XURTHDfQ1aQ==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/abi-typegen@0.101.1': - resolution: {integrity: sha512-4s4Zf+5Ohdym9bl/Cebl7kwufaKJ9C2nJNt5EB+0bXmVArl8zOpP67U+cQSVXawMUVryBVM3mY7Ay2p/WD9wDg==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - hasBin: true - - '@fuel-ts/account@0.101.1': - resolution: {integrity: sha512-x+UfuBaCvb9KYT+wIJba3RL21nR4JH0qZevDs/jzw9cLMsLl8AYLKMg2wS9rhR5OCoa9PbsOe9DDrDI+y3BpVA==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/address@0.101.1': - resolution: {integrity: sha512-l+PvQ2kB/zS/TW7S3/UjjaJ95UNflWizmKr97M13gkOdP99UuI2InYu9zjH72Azbt3LR/RMilHMTyZeWRSV42w==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/contract@0.101.1': - resolution: {integrity: sha512-UBOjDIYqO1EY8qirjxpEUsW0K2+fR8mC0xDI8k0c1Aes3YVAZyMmpf6ZWvjF5BVElu4kO8pFXr6xcQoDn6TuMw==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/crypto@0.101.1': - resolution: {integrity: sha512-Dy6Q1NbdGojyT0q3mrZu72hSTlXfNprKA6A6vJHKkwRcwFphnrZHAubVfjzus4ZeQf9fdcqZrfWLUG76/F6r9g==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/errors@0.101.1': - resolution: {integrity: sha512-BPp3/tD3YyxbV/qGujwrUOluyB4abEHOD1GIgvUGKiLy9S3TNjBzIPLYG0ARVcswvYTEh8r7/hoZcRKtpNpcEQ==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/hasher@0.101.1': - resolution: {integrity: sha512-diLLZbMvwy6ivkZEBDzh6HXkqPzxCVJov29A4A+ILwvKcXHultJ/36bxj3S415cj5DtgVj8KBoqeo1Sw7cg+rg==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/math@0.101.1': - resolution: {integrity: sha512-F1bGLZN71DmL5h1/znlXgWahL8A28RMur4B2MscTp/sFyqQ9tHlpEDjdF2ajr3lSxlMWohDJbElCNramLqE/Tg==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/merkle@0.101.1': - resolution: {integrity: sha512-JJEdTQ2BxWHjX09caf42Ebfc32J0dHx/dv9pXfvpxc3BUgdRE8gM0Wvrqq/cu6S2XD/Y6vQp/qhOq9PXWJUuKg==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/program@0.101.1': - resolution: {integrity: sha512-6ManClwCW7NI4jE3BoaNWStHyGEWcrD7hkK+9T5+hESY0ckGiBGMuKvR3VhVaTsTRVfqMx/m1kPSu2O5BY/vrA==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/recipes@0.101.1': - resolution: {integrity: sha512-DVQfE7pnoFBmTNwBPrL2qN5jlp8w9rCD9aQKwvBaPwvi4UYiTg1elcWlX5/mEhUAnGlDIYOQ4XFuxLZAgmDUww==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/script@0.101.1': - resolution: {integrity: sha512-6s6SKciwOYM/MK1DAE7Cd19hTL5FOG+FtPP9IvZ+UwmNz7zydD2LXHdTcOjTPZnhmzvhBrlfH2CUAsOqhMHGpg==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/transactions@0.101.1': - resolution: {integrity: sha512-VLCtwOO5PD31rxSnGbBaJuQO8AwvqUwwRfXrE3fLzrreJKyUM5K2XZHsfQq9dtd/RWaaOUPNMp7ztVQzKebfUQ==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - - '@fuel-ts/utils@0.101.1': - resolution: {integrity: sha512-H7j38/quroMccPrjFrnn+Cuui6iPpyH15NKdBT2XVv96rk9XX2DG5aBIGn+nYqPTA8+W+a7sp3U65sgckyZ4Rg==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - peerDependencies: - vitest: 3.0.9 - - '@fuel-ts/versions@0.101.1': - resolution: {integrity: sha512-3/tYZBbCaShkzfrVEBulm83f3MJaUpOCK4q3BpAxvbhWHsKS9AxbLhHWQ0RZUXII5OJmGnSpWfy0Bhe7FYo93A==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - hasBin: true - - '@fuels/vm-asm@0.60.2': - resolution: {integrity: sha512-wkCu63jTGJWpRZQirTaB8S4/gyoebEJLk3AKfnykt/lgWp1U9iHOcCICVHQP547i+y8jEVKwk18+huINFyYVFQ==} - - '@graphql-typed-document-node/core@3.2.0': - resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - - '@improbable-eng/grpc-web@0.15.0': - resolution: {integrity: sha512-ERft9/0/8CmYalqOVnJnpdDry28q+j+nAlFFARdjyxXDJ+Mhgv9+F600QC8BR9ygOfrXRlAk6CvST2j+JCpQPg==} - peerDependencies: - google-protobuf: ^3.14.0 - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@lottiefiles/dotlottie-react@0.14.2': - resolution: {integrity: sha512-RR4r0HrKQbOAw6iS6C3mRARS2iu+yI+G1vICoUsRMHzlUUk1/26l3WyAjhcG+KoaGoKmORx8FgHjTNr4Sr/2Ug==} - peerDependencies: - react: ^17 || ^18 || ^19 - - '@lottiefiles/dotlottie-web@0.47.0': - resolution: {integrity: sha512-YN6wSB4iYZBYEAFKEs/taufrPH3rfNlUA632Ib61WoR58TALAJ1ZX8yDIGUBT28byMJhZR4+xdpRX4v7X8OeBQ==} - - '@metamask/safe-event-emitter@3.1.2': - resolution: {integrity: sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==} - engines: {node: '>=12.0.0'} - - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': - resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} - cpu: [arm64] - os: [darwin] - - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': - resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} - cpu: [x64] - os: [darwin] - - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': - resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} - cpu: [arm64] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': - resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} - cpu: [arm] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': - resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} - cpu: [x64] - os: [linux] - - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': - resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} - cpu: [x64] - os: [win32] - - '@noble/ciphers@1.3.0': - resolution: {integrity: sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@1.8.1': - resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@1.9.1': - resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@1.9.7': - resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.7.1': - resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@rollup/plugin-alias@5.1.1': - resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-commonjs@25.0.8': - resolution: {integrity: sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.68.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-json@6.1.0': - resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-node-resolve@15.3.1': - resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-typescript@11.1.6': - resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.14.0||^3.0.0||^4.0.0 - tslib: '*' - typescript: '>=3.7.0' - peerDependenciesMeta: - rollup: - optional: true - tslib: - optional: true - - '@rollup/pluginutils@4.2.1': - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/rollup-android-arm-eabi@4.50.2': - resolution: {integrity: sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.50.2': - resolution: {integrity: sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.50.2': - resolution: {integrity: sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.50.2': - resolution: {integrity: sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.50.2': - resolution: {integrity: sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.50.2': - resolution: {integrity: sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.50.2': - resolution: {integrity: sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.50.2': - resolution: {integrity: sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.50.2': - resolution: {integrity: sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.50.2': - resolution: {integrity: sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loong64-gnu@4.50.2': - resolution: {integrity: sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.50.2': - resolution: {integrity: sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.50.2': - resolution: {integrity: sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.50.2': - resolution: {integrity: sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.50.2': - resolution: {integrity: sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.50.2': - resolution: {integrity: sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.50.2': - resolution: {integrity: sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-openharmony-arm64@4.50.2': - resolution: {integrity: sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.50.2': - resolution: {integrity: sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.50.2': - resolution: {integrity: sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.50.2': - resolution: {integrity: sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==} - cpu: [x64] - os: [win32] - - '@scure/base@1.2.6': - resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - - '@scure/bip32@1.7.0': - resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} - - '@scure/bip39@1.6.0': - resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} - - '@starkware-industries/starkware-crypto-utils@0.2.1': - resolution: {integrity: sha512-rA5O9b53zaoBOQwQxBd0cbumFbQoBm9NH/vfu+o0Cq3oouEbNPALneLlLjOmFEId2/WOJ5ecC64rFLI/PwuIPQ==} - - '@tailwindcss/node@4.1.10': - resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==} - - '@tailwindcss/oxide-android-arm64@4.1.10': - resolution: {integrity: sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.1.10': - resolution: {integrity: sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.1.10': - resolution: {integrity: sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.1.10': - resolution: {integrity: sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10': - resolution: {integrity: sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.10': - resolution: {integrity: sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-musl@4.1.10': - resolution: {integrity: sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-gnu@4.1.10': - resolution: {integrity: sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-musl@4.1.10': - resolution: {integrity: sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-wasm32-wasi@4.1.10': - resolution: {integrity: sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.10': - resolution: {integrity: sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.1.10': - resolution: {integrity: sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.1.10': - resolution: {integrity: sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==} - engines: {node: '>= 10'} - - '@tailwindcss/postcss@4.1.10': - resolution: {integrity: sha512-B+7r7ABZbkXJwpvt2VMnS6ujcDoR2OOcFaqrLIo1xbcdxje4Vf+VgJdBzNNbrAjBj/rLZ66/tlQ1knIGNLKOBQ==} - - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - - '@types/bn.js@5.1.6': - resolution: {integrity: sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w==} - - '@types/bn.js@5.2.0': - resolution: {integrity: sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/node@20.19.17': - resolution: {integrity: sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==} - - '@types/pbkdf2@3.1.2': - resolution: {integrity: sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==} - - '@types/react-dom@19.1.6': - resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} - peerDependencies: - '@types/react': ^19.0.0 - - '@types/react@19.1.8': - resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} - - '@types/resolve@1.20.2': - resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - - '@types/secp256k1@4.0.6': - resolution: {integrity: sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==} - - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@vitest/expect@3.0.9': - resolution: {integrity: sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==} - - '@vitest/mocker@3.0.9': - resolution: {integrity: sha512-ryERPIBOnvevAkTq+L1lD+DTFBRcjueL9lOUfXsLfwP92h4e+Heb+PjiqS3/OURWPtywfafK0kj++yDFjWUmrA==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@3.0.9': - resolution: {integrity: sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==} - - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - - '@vitest/runner@3.0.9': - resolution: {integrity: sha512-NX9oUXgF9HPfJSwl8tUZCMP1oGx2+Sf+ru6d05QjzQz4OwWg0psEzwY6VexP2tTHWdOkhKHUIZH+fS6nA7jfOw==} - - '@vitest/snapshot@3.0.9': - resolution: {integrity: sha512-AiLUiuZ0FuA+/8i19mTYd+re5jqjEc2jZbgJ2up0VY0Ddyyxg/uUtBDpIFAy4uzKaQxOW8gMgBdAJJ2ydhu39A==} - - '@vitest/spy@3.0.9': - resolution: {integrity: sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==} - - '@vitest/utils@3.0.9': - resolution: {integrity: sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==} - - abitype@1.1.0: - resolution: {integrity: sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==} - peerDependencies: - typescript: '>=5.0.4' - zod: ^3.22.0 || ^4.0.0 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - - aes-js@3.1.2: - resolution: {integrity: sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - asn1.js@4.10.1: - resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==} - - assert@2.1.0: - resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - async@2.6.4: - resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - axios@1.12.2: - resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base-x@3.0.11: - resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.8.6: - resolution: {integrity: sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==} - hasBin: true - - bech32@1.1.4: - resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bip39@3.1.0: - resolution: {integrity: sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==} - - blakejs@1.2.1: - resolution: {integrity: sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==} - - bn.js@4.12.2: - resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} - - bn.js@5.2.1: - resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} - - bn.js@5.2.2: - resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} - - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - brorand@1.1.0: - resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - - browser-headers@0.4.1: - resolution: {integrity: sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==} - - browserify-aes@1.2.0: - resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} - - browserify-cipher@1.0.1: - resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==} - - browserify-des@1.0.2: - resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} - - browserify-rsa@4.1.1: - resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==} - engines: {node: '>= 0.10'} - - browserify-sign@4.2.3: - resolution: {integrity: sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==} - engines: {node: '>= 0.12'} - - browserslist@4.26.2: - resolution: {integrity: sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bs58@4.0.1: - resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} - - bs58check@2.1.2: - resolution: {integrity: sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==} - - buffer-xor@1.0.3: - resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - - caniuse-lite@1.0.30001743: - resolution: {integrity: sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==} - - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} - engines: {node: '>=18'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - - cipher-base@1.0.6: - resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} - engines: {node: '>= 0.10'} - - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - - cli-table@0.3.11: - resolution: {integrity: sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==} - engines: {node: '>= 0.2.0'} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - - colors@1.0.3: - resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==} - engines: {node: '>=0.1.90'} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@13.1.0: - resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} - engines: {node: '>=18'} - - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - - concat-with-sourcemaps@1.1.0: - resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cosmjs-types@0.9.0: - resolution: {integrity: sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==} - - create-ecdh@4.0.4: - resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} - - create-hash@1.1.3: - resolution: {integrity: sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==} - - create-hash@1.2.0: - resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} - - create-hmac@1.1.7: - resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} - - cross-fetch@3.2.0: - resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} - - cross-fetch@4.1.0: - resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - crypto-browserify@3.12.1: - resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==} - engines: {node: '>= 0.10'} - - css-declaration-sorter@6.4.1: - resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} - engines: {node: ^10 || ^12 || >=14} - peerDependencies: - postcss: ^8.0.9 - - css-select@4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - - css-tree@1.1.3: - resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} - engines: {node: '>=8.0.0'} - - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssnano-preset-default@5.2.14: - resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano-utils@3.1.0: - resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano@5.1.15: - resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - csso@4.2.0: - resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} - engines: {node: '>=8.0.0'} - - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js@10.4.3: - resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - des.js@1.1.0: - resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} - - detect-libc@2.1.0: - resolution: {integrity: sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==} - engines: {node: '>=8'} - - diffie-hellman@5.0.3: - resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} - - dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} - - domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - electron-to-chromium@1.5.221: - resolution: {integrity: sha512-/1hFJ39wkW01ogqSyYoA4goOXOtMRy6B+yvA1u42nnsEGtHzIzmk93aPISumVQeblj47JUHLC9coCjUxb1EvtQ==} - - elliptic@6.6.1: - resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - enc-utils@3.0.0: - resolution: {integrity: sha512-e57t/Z2HzWOLwOp7DZcV0VMEY8t7ptWwsxyp6kM2b2zrk6JqIpXxzkruHAMiBsy5wg9jp/183GdiRXCvBtzsYg==} - - enhanced-resolve@5.18.3: - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} - engines: {node: '>=10.13.0'} - - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-toolkit@1.39.10: - resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==} - - esbuild@0.25.1: - resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.25.10: - resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - estree-walker@0.6.1: - resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - ethereum-cryptography@0.1.3: - resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==} - - ethereumjs-util@7.1.5: - resolution: {integrity: sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==} - engines: {node: '>=10.0.0'} - - ethereumjs-wallet@1.0.2: - resolution: {integrity: sha512-CCWV4RESJgRdHIvFciVQFnCHfqyhXWchTPlkfp28Qc53ufs+doi5I/cV2+xeK9+qEo25XCWfP9MiL+WEPAZfdA==} - deprecated: 'New package name format for new versions: @ethereumjs/wallet. Please update.' - - event-iterator@2.0.0: - resolution: {integrity: sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==} - - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - evp_bytestokey@1.0.3: - resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} - - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-cache-dir@3.3.2: - resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} - engines: {node: '>=8'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - form-data@4.0.4: - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} - engines: {node: '>= 6'} - - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - - framer-motion@12.23.15: - resolution: {integrity: sha512-MBxUEHjWr/fRQ2aHg2CgdcjJpHMJ9ttHiPeClHDGiOJOYgmde3OXZUrbWDeeE8yvFrWA62hsPS4+rKQ9OJaoQA==} - peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/is-prop-valid': - optional: true - react: - optional: true - react-dom: - optional: true - - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - fuels@0.101.1: - resolution: {integrity: sha512-/0LwAynHrKZZn4aw7QkyVc+Af+MIwlg82eCVSdAUVDJ+/pQ6Oma+aZyqjynmsKEuOlvkzw+jhUReGoWqexF4tg==} - engines: {node: ^18.20.3 || ^20.0.0 || ^22.0.0} - hasBin: true - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - generic-names@4.0.0: - resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - google-protobuf@3.21.4: - resolution: {integrity: sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphql-request@6.1.0: - resolution: {integrity: sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==} - peerDependencies: - graphql: 14 - 16 - - graphql-tag@2.12.6: - resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} - engines: {node: '>=10'} - peerDependencies: - graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - - graphql@16.10.0: - resolution: {integrity: sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hash-base@2.0.2: - resolution: {integrity: sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==} - - hash-base@3.0.5: - resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==} - engines: {node: '>= 0.10'} - - hash.js@1.1.7: - resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hmac-drbg@1.0.1: - resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - - husky@8.0.3: - resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} - engines: {node: '>=14'} - hasBin: true - - icss-replace-symbols@1.1.0: - resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} - - icss-utils@5.1.0: - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - import-cwd@3.0.0: - resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} - engines: {node: '>=8'} - - import-from@3.0.0: - resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} - engines: {node: '>=8'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - is-arguments@1.2.0: - resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} - engines: {node: '>= 0.4'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - - is-nan@1.3.2: - resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-reference@1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isomorphic-ws@4.0.1: - resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} - peerDependencies: - ws: '*' - - isows@1.0.7: - resolution: {integrity: sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==} - peerDependencies: - ws: '*' - - it-stream-types@2.0.2: - resolution: {integrity: sha512-Rz/DEZ6Byn/r9+/SBCuJhpPATDF9D+dz5pbgSUyBsCDtza6wtNATrz/jz1gDyNanC3XdLboriHnOC925bZRBww==} - - it-ws@6.1.5: - resolution: {integrity: sha512-uWjMtpy5HqhSd/LlrlP3fhYrr7rUfJFFMABv0F5d6n13Q+0glhZthwUKpEAVhDrXY95Tb1RB5lLqqef+QbVNaw==} - engines: {node: '>=16.0.0', npm: '>=7.0.0'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jiti@2.5.1: - resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} - hasBin: true - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - js-sha3@0.8.0: - resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - keccak@3.0.4: - resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} - engines: {node: '>=10.0.0'} - - libsodium-sumo@0.7.15: - resolution: {integrity: sha512-5tPmqPmq8T8Nikpm1Nqj0hBHvsLFCXvdhBFV7SGOitQPZAA6jso8XoL0r4L7vmfKXr486fiQInvErHtEvizFMw==} - - libsodium-wrappers-sumo@0.7.15: - resolution: {integrity: sha512-aSWY8wKDZh5TC7rMvEdTHoyppVq/1dTSAeAR7H6pzd6QRT3vQWcT5pGwCotLcpPEOLXX6VvqihSPkpEhYAjANA==} - - lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.30.1: - resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.30.1: - resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.30.1: - resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.30.1: - resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.30.1: - resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.30.1: - resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.30.1: - resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.30.1: - resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} - engines: {node: '>= 12.0.0'} - - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - loader-utils@3.3.1: - resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} - engines: {node: '>= 12.13.0'} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - long@5.3.2: - resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} - - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - md5.js@1.3.5: - resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} - - mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} - - miller-rabin@4.0.1: - resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} - hasBin: true - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@3.0.2: - resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} - engines: {node: '>= 18'} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - - motion-dom@12.23.12: - resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==} - - motion-utils@12.23.6: - resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} - - motion@12.23.0: - resolution: {integrity: sha512-PPNwblArRH9GRC4F3KtOTiIaYd+mtp324vYq3HIL+ueseoAVqPRK5TPFTAQBcIprfVd0NWo3DLzZSiyWaYFXXQ==} - peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/is-prop-valid': - optional: true - react: - optional: true - react-dom: - optional: true - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - msgpackr-extract@3.0.3: - resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} - hasBin: true - - msgpackr@1.11.5: - resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==} - - multiformats@13.4.1: - resolution: {integrity: sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - node-addon-api@2.0.2: - resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} - - node-addon-api@5.1.0: - resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-gyp-build-optional-packages@5.2.2: - resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} - hasBin: true - - node-gyp-build@4.8.4: - resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} - hasBin: true - - node-releases@2.0.21: - resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - - object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - ox@0.9.3: - resolution: {integrity: sha512-KzyJP+fPV4uhuuqrTZyok4DC7vFzi7HLUFiUNEmpbyh59htKWkOC98IONC1zgXJPbHAhQgqs6B0Z6StCGhmQvg==} - peerDependencies: - typescript: '>=5.4.0' - peerDependenciesMeta: - typescript: - optional: true - - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} - - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parse-asn1@5.1.7: - resolution: {integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==} - engines: {node: '>= 0.10'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - - pbkdf2@3.1.3: - resolution: {integrity: sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==} - engines: {node: '>=0.12'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - pify@5.0.0: - resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} - engines: {node: '>=10'} - - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - - portfinder@1.0.32: - resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==} - engines: {node: '>= 0.12.0'} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss-calc@8.2.4: - resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} - peerDependencies: - postcss: ^8.2.2 - - postcss-colormin@5.3.1: - resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-convert-values@5.1.3: - resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-comments@5.1.2: - resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-duplicates@5.1.0: - resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-empty@5.1.1: - resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-overridden@5.1.0: - resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-import@16.1.1: - resolution: {integrity: sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - postcss: ^8.0.0 - - postcss-load-config@3.1.4: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss-merge-longhand@5.1.7: - resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-merge-rules@5.1.4: - resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-font-values@5.1.0: - resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-gradients@5.1.1: - resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-params@5.1.4: - resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-selectors@5.2.1: - resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-modules-extract-imports@3.1.0: - resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-local-by-default@4.2.0: - resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-scope@3.2.1: - resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-values@4.0.0: - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules@4.3.1: - resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} - peerDependencies: - postcss: ^8.0.0 - - postcss-nesting@13.0.2: - resolution: {integrity: sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==} - engines: {node: '>=18'} - peerDependencies: - postcss: ^8.4 - - postcss-normalize-charset@5.1.0: - resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-display-values@5.1.0: - resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-positions@5.1.1: - resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-repeat-style@5.1.1: - resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-string@5.1.0: - resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-timing-functions@5.1.0: - resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-unicode@5.1.1: - resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-url@5.1.0: - resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-whitespace@5.1.1: - resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-ordered-values@5.1.3: - resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-initial@5.1.2: - resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-transforms@5.1.0: - resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-selector-parser@7.1.0: - resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} - engines: {node: '>=4'} - - postcss-svgo@5.1.0: - resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-unique-selectors@5.1.1: - resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} - engines: {node: '>=14'} - hasBin: true - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - promise.series@0.2.0: - resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==} - engines: {node: '>=0.12'} - - property-expr@2.0.6: - resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - public-encrypt@4.0.3: - resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} - - ramda@0.30.1: - resolution: {integrity: sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==} - - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - - randomfill@1.0.4: - resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} - - react-dom@19.1.1: - resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} - peerDependencies: - react: ^19.1.1 - - react@19.1.1: - resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} - engines: {node: '>=0.10.0'} - - read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - readonly-date@1.0.0: - resolution: {integrity: sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ==} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - - rimraf@5.0.10: - resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} - hasBin: true - - ripemd160@2.0.1: - resolution: {integrity: sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==} - - ripemd160@2.0.2: - resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} - - rlp@2.2.7: - resolution: {integrity: sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==} - hasBin: true - - rollup-plugin-dts@6.2.3: - resolution: {integrity: sha512-UgnEsfciXSPpASuOelix7m4DrmyQgiaWBnvI0TM4GxuDh5FkqW8E5hu57bCxXB90VvR1WNfLV80yEDN18UogSA==} - engines: {node: '>=16'} - peerDependencies: - rollup: ^3.29.4 || ^4 - typescript: ^4.5 || ^5.0 - - rollup-plugin-postcss@4.0.2: - resolution: {integrity: sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==} - engines: {node: '>=10'} - peerDependencies: - postcss: 8.x - - rollup-plugin-typescript2@0.36.0: - resolution: {integrity: sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw==} - peerDependencies: - rollup: '>=1.26.3' - typescript: '>=2.4.0' - - rollup-pluginutils@2.8.2: - resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} - - rollup@4.50.2: - resolution: {integrity: sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-identifier@0.4.2: - resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - - scrypt-js@3.0.1: - resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - - secp256k1@4.0.4: - resolution: {integrity: sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==} - engines: {node: '>=18.0.0'} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - - sha.js@2.4.12: - resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} - engines: {node: '>= 0.10'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - stable@0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - stream-browserify@3.0.0: - resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} - - string-hash@1.1.3: - resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} - - style-inject@0.3.0: - resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} - - stylehacks@5.1.1: - resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - svgo@2.8.0: - resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} - engines: {node: '>=10.13.0'} - hasBin: true - - symbol-observable@2.0.3: - resolution: {integrity: sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==} - engines: {node: '>=0.10'} - - tailwind-merge@3.3.1: - resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} - - tailwindcss@4.1.10: - resolution: {integrity: sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==} - - tapable@2.2.3: - resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} - engines: {node: '>=6'} - - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} - engines: {node: '>=18'} - - tiny-case@1.0.3: - resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} - engines: {node: '>=14.0.0'} - - to-buffer@1.2.1: - resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} - engines: {node: '>= 0.4'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toml@3.0.0: - resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} - - toposort@2.0.2: - resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - - type-fest@4.34.1: - resolution: {integrity: sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==} - engines: {node: '>=16'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} - engines: {node: '>=14.17'} - hasBin: true - - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - - uint8arrays@5.1.0: - resolution: {integrity: sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - utf8@3.0.0: - resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - util@0.12.5: - resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - viem@2.37.6: - resolution: {integrity: sha512-b+1IozQ8TciVQNdQUkOH5xtFR0z7ZxR8pyloENi/a+RA408lv4LoX12ofwoiT3ip0VRhO5ni1em//X0jn/eW0g==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true - - vite-node@3.0.9: - resolution: {integrity: sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite@6.3.6: - resolution: {integrity: sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@3.0.9: - resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.0.9 - '@vitest/ui': 3.0.9 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xstream@11.14.0: - resolution: {integrity: sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw==} - - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - yup@1.6.1: - resolution: {integrity: sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==} - -snapshots: - - '@adraffy/ens-normalize@1.11.0': {} - - '@alloc/quick-lru@5.2.0': {} - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@arcana/ca-common@1.0.1-alpha.6(@cosmjs/proto-signing@0.34.0)(@cosmjs/stargate@0.34.0)(axios@1.12.2)(decimal.js@10.6.0)(fuels@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)))(google-protobuf@3.21.4)(long@5.3.2)(msgpackr@1.11.5)(viem@2.37.6(typescript@5.9.2))': - dependencies: - '@bufbuild/protobuf': 2.8.0 - '@cosmjs/proto-signing': 0.34.0 - '@cosmjs/stargate': 0.34.0 - '@improbable-eng/grpc-web': 0.15.0(google-protobuf@3.21.4) - axios: 1.12.2 - browser-headers: 0.4.1 - decimal.js: 10.6.0 - es-toolkit: 1.39.10 - fuels: 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - long: 5.3.2 - msgpackr: 1.11.5 - tslib: 2.8.1 - viem: 2.37.6(typescript@5.9.2) - transitivePeerDependencies: - - google-protobuf - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - optional: true - - '@babel/helper-validator-identifier@7.27.1': - optional: true - - '@bufbuild/protobuf@2.8.0': {} - - '@cosmjs/amino@0.34.0': - dependencies: - '@cosmjs/crypto': 0.34.0 - '@cosmjs/encoding': 0.34.0 - '@cosmjs/math': 0.34.0 - '@cosmjs/utils': 0.34.0 - - '@cosmjs/crypto@0.34.0': - dependencies: - '@cosmjs/encoding': 0.34.0 - '@cosmjs/math': 0.34.0 - '@cosmjs/utils': 0.34.0 - '@noble/curves': 1.9.7 - '@noble/hashes': 1.8.0 - bn.js: 5.2.2 - libsodium-wrappers-sumo: 0.7.15 - - '@cosmjs/encoding@0.34.0': - dependencies: - base64-js: 1.5.1 - bech32: 1.1.4 - readonly-date: 1.0.0 - - '@cosmjs/json-rpc@0.34.0': - dependencies: - '@cosmjs/stream': 0.34.0 - xstream: 11.14.0 - - '@cosmjs/math@0.34.0': - dependencies: - bn.js: 5.2.2 - - '@cosmjs/proto-signing@0.34.0': - dependencies: - '@cosmjs/amino': 0.34.0 - '@cosmjs/crypto': 0.34.0 - '@cosmjs/encoding': 0.34.0 - '@cosmjs/math': 0.34.0 - '@cosmjs/utils': 0.34.0 - cosmjs-types: 0.9.0 - - '@cosmjs/socket@0.34.0': - dependencies: - '@cosmjs/stream': 0.34.0 - isomorphic-ws: 4.0.1(ws@7.5.10) - ws: 7.5.10 - xstream: 11.14.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@cosmjs/stargate@0.34.0': - dependencies: - '@cosmjs/amino': 0.34.0 - '@cosmjs/encoding': 0.34.0 - '@cosmjs/math': 0.34.0 - '@cosmjs/proto-signing': 0.34.0 - '@cosmjs/stream': 0.34.0 - '@cosmjs/tendermint-rpc': 0.34.0 - '@cosmjs/utils': 0.34.0 - cosmjs-types: 0.9.0 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@cosmjs/stream@0.34.0': - dependencies: - xstream: 11.14.0 - - '@cosmjs/tendermint-rpc@0.34.0': - dependencies: - '@cosmjs/crypto': 0.34.0 - '@cosmjs/encoding': 0.34.0 - '@cosmjs/json-rpc': 0.34.0 - '@cosmjs/math': 0.34.0 - '@cosmjs/socket': 0.34.0 - '@cosmjs/stream': 0.34.0 - '@cosmjs/utils': 0.34.0 - cross-fetch: 4.1.0 - readonly-date: 1.0.0 - xstream: 11.14.0 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - - '@cosmjs/utils@0.34.0': {} - - '@csstools/selector-resolve-nested@3.1.0(postcss-selector-parser@7.1.0)': - dependencies: - postcss-selector-parser: 7.1.0 - - '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.0)': - dependencies: - postcss-selector-parser: 7.1.0 - - '@esbuild/aix-ppc64@0.25.1': - optional: true - - '@esbuild/aix-ppc64@0.25.10': - optional: true - - '@esbuild/android-arm64@0.25.1': - optional: true - - '@esbuild/android-arm64@0.25.10': - optional: true - - '@esbuild/android-arm@0.25.1': - optional: true - - '@esbuild/android-arm@0.25.10': - optional: true - - '@esbuild/android-x64@0.25.1': - optional: true - - '@esbuild/android-x64@0.25.10': - optional: true - - '@esbuild/darwin-arm64@0.25.1': - optional: true - - '@esbuild/darwin-arm64@0.25.10': - optional: true - - '@esbuild/darwin-x64@0.25.1': - optional: true - - '@esbuild/darwin-x64@0.25.10': - optional: true - - '@esbuild/freebsd-arm64@0.25.1': - optional: true - - '@esbuild/freebsd-arm64@0.25.10': - optional: true - - '@esbuild/freebsd-x64@0.25.1': - optional: true - - '@esbuild/freebsd-x64@0.25.10': - optional: true - - '@esbuild/linux-arm64@0.25.1': - optional: true - - '@esbuild/linux-arm64@0.25.10': - optional: true - - '@esbuild/linux-arm@0.25.1': - optional: true - - '@esbuild/linux-arm@0.25.10': - optional: true - - '@esbuild/linux-ia32@0.25.1': - optional: true - - '@esbuild/linux-ia32@0.25.10': - optional: true - - '@esbuild/linux-loong64@0.25.1': - optional: true - - '@esbuild/linux-loong64@0.25.10': - optional: true - - '@esbuild/linux-mips64el@0.25.1': - optional: true - - '@esbuild/linux-mips64el@0.25.10': - optional: true - - '@esbuild/linux-ppc64@0.25.1': - optional: true - - '@esbuild/linux-ppc64@0.25.10': - optional: true - - '@esbuild/linux-riscv64@0.25.1': - optional: true - - '@esbuild/linux-riscv64@0.25.10': - optional: true - - '@esbuild/linux-s390x@0.25.1': - optional: true - - '@esbuild/linux-s390x@0.25.10': - optional: true - - '@esbuild/linux-x64@0.25.1': - optional: true - - '@esbuild/linux-x64@0.25.10': - optional: true - - '@esbuild/netbsd-arm64@0.25.1': - optional: true - - '@esbuild/netbsd-arm64@0.25.10': - optional: true - - '@esbuild/netbsd-x64@0.25.1': - optional: true - - '@esbuild/netbsd-x64@0.25.10': - optional: true - - '@esbuild/openbsd-arm64@0.25.1': - optional: true - - '@esbuild/openbsd-arm64@0.25.10': - optional: true - - '@esbuild/openbsd-x64@0.25.1': - optional: true - - '@esbuild/openbsd-x64@0.25.10': - optional: true - - '@esbuild/openharmony-arm64@0.25.10': - optional: true - - '@esbuild/sunos-x64@0.25.1': - optional: true - - '@esbuild/sunos-x64@0.25.10': - optional: true - - '@esbuild/win32-arm64@0.25.1': - optional: true - - '@esbuild/win32-arm64@0.25.10': - optional: true - - '@esbuild/win32-ia32@0.25.1': - optional: true - - '@esbuild/win32-ia32@0.25.10': - optional: true - - '@esbuild/win32-x64@0.25.1': - optional: true - - '@esbuild/win32-x64@0.25.10': - optional: true - - '@fuel-ts/abi-coder@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/crypto': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/hasher': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/math': 0.101.1 - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - type-fest: 4.34.1 - transitivePeerDependencies: - - vitest - - '@fuel-ts/abi-typegen@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/versions': 0.101.1 - commander: 13.1.0 - glob: 10.4.5 - handlebars: 4.7.8 - mkdirp: 3.0.1 - ramda: 0.30.1 - rimraf: 5.0.10 - transitivePeerDependencies: - - vitest - - '@fuel-ts/account@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/abi-coder': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/address': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/crypto': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/hasher': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/math': 0.101.1 - '@fuel-ts/merkle': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/transactions': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/versions': 0.101.1 - '@fuels/vm-asm': 0.60.2 - '@noble/curves': 1.8.1 - events: 3.3.0 - graphql: 16.10.0 - graphql-request: 6.1.0(graphql@16.10.0) - graphql-tag: 2.12.6(graphql@16.10.0) - ramda: 0.30.1 - transitivePeerDependencies: - - encoding - - vitest - - '@fuel-ts/address@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/crypto': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@noble/hashes': 1.7.1 - transitivePeerDependencies: - - vitest - - '@fuel-ts/contract@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/abi-coder': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/account': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/crypto': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/hasher': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/math': 0.101.1 - '@fuel-ts/merkle': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/program': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/transactions': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuels/vm-asm': 0.60.2 - ramda: 0.30.1 - transitivePeerDependencies: - - encoding - - vitest - - '@fuel-ts/crypto@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@noble/hashes': 1.7.1 - transitivePeerDependencies: - - vitest - - '@fuel-ts/errors@0.101.1': - dependencies: - '@fuel-ts/versions': 0.101.1 - - '@fuel-ts/hasher@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/crypto': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@noble/hashes': 1.7.1 - transitivePeerDependencies: - - vitest - - '@fuel-ts/math@0.101.1': - dependencies: - '@fuel-ts/errors': 0.101.1 - '@types/bn.js': 5.1.6 - bn.js: 5.2.1 - - '@fuel-ts/merkle@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/hasher': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/math': 0.101.1 - transitivePeerDependencies: - - vitest - - '@fuel-ts/program@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/abi-coder': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/account': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/address': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/math': 0.101.1 - '@fuel-ts/transactions': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuels/vm-asm': 0.60.2 - ramda: 0.30.1 - transitivePeerDependencies: - - encoding - - vitest - - '@fuel-ts/recipes@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/abi-coder': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/abi-typegen': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/account': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/address': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/contract': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/program': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/transactions': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - transitivePeerDependencies: - - encoding - - vitest - - '@fuel-ts/script@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/abi-coder': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/account': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/math': 0.101.1 - '@fuel-ts/program': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/transactions': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - transitivePeerDependencies: - - encoding - - vitest - - '@fuel-ts/transactions@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/abi-coder': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/address': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/hasher': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/math': 0.101.1 - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - transitivePeerDependencies: - - vitest - - '@fuel-ts/utils@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/math': 0.101.1 - '@fuel-ts/versions': 0.101.1 - fflate: 0.8.2 - vitest: 3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1) - - '@fuel-ts/versions@0.101.1': - dependencies: - chalk: 4.1.2 - cli-table: 0.3.11 - - '@fuels/vm-asm@0.60.2': {} - - '@graphql-typed-document-node/core@3.2.0(graphql@16.10.0)': - dependencies: - graphql: 16.10.0 - - '@improbable-eng/grpc-web@0.15.0(google-protobuf@3.21.4)': - dependencies: - browser-headers: 0.4.1 - google-protobuf: 3.21.4 - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@lottiefiles/dotlottie-react@0.14.2(react@19.1.1)': - dependencies: - '@lottiefiles/dotlottie-web': 0.47.0 - react: 19.1.1 - - '@lottiefiles/dotlottie-web@0.47.0': {} - - '@metamask/safe-event-emitter@3.1.2': {} - - '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': - optional: true - - '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': - optional: true - - '@noble/ciphers@1.3.0': {} - - '@noble/curves@1.8.1': - dependencies: - '@noble/hashes': 1.7.1 - - '@noble/curves@1.9.1': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/curves@1.9.7': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/hashes@1.7.1': {} - - '@noble/hashes@1.8.0': {} - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@rollup/plugin-alias@5.1.1(rollup@4.50.2)': - optionalDependencies: - rollup: 4.50.2 - - '@rollup/plugin-commonjs@25.0.8(rollup@4.50.2)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.50.2) - commondir: 1.0.1 - estree-walker: 2.0.2 - glob: 8.1.0 - is-reference: 1.2.1 - magic-string: 0.30.19 - optionalDependencies: - rollup: 4.50.2 - - '@rollup/plugin-json@6.1.0(rollup@4.50.2)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.50.2) - optionalDependencies: - rollup: 4.50.2 - - '@rollup/plugin-node-resolve@15.3.1(rollup@4.50.2)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.50.2) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-module: 1.0.0 - resolve: 1.22.10 - optionalDependencies: - rollup: 4.50.2 - - '@rollup/plugin-typescript@11.1.6(rollup@4.50.2)(tslib@2.8.1)(typescript@5.9.2)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.50.2) - resolve: 1.22.10 - typescript: 5.9.2 - optionalDependencies: - rollup: 4.50.2 - tslib: 2.8.1 - - '@rollup/pluginutils@4.2.1': - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - - '@rollup/pluginutils@5.3.0(rollup@4.50.2)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.50.2 - - '@rollup/rollup-android-arm-eabi@4.50.2': - optional: true - - '@rollup/rollup-android-arm64@4.50.2': - optional: true - - '@rollup/rollup-darwin-arm64@4.50.2': - optional: true - - '@rollup/rollup-darwin-x64@4.50.2': - optional: true - - '@rollup/rollup-freebsd-arm64@4.50.2': - optional: true - - '@rollup/rollup-freebsd-x64@4.50.2': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.50.2': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.50.2': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.50.2': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.50.2': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.50.2': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.50.2': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.50.2': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.50.2': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.50.2': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.50.2': - optional: true - - '@rollup/rollup-linux-x64-musl@4.50.2': - optional: true - - '@rollup/rollup-openharmony-arm64@4.50.2': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.50.2': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.50.2': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.50.2': - optional: true - - '@scure/base@1.2.6': {} - - '@scure/bip32@1.7.0': - dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@scure/bip39@1.6.0': - dependencies: - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - - '@starkware-industries/starkware-crypto-utils@0.2.1': - dependencies: - assert: 2.1.0 - bip39: 3.1.0 - bn.js: 4.12.2 - brorand: 1.1.0 - buffer: 6.0.3 - crypto-browserify: 3.12.1 - elliptic: 6.6.1 - enc-utils: 3.0.0 - ethereumjs-wallet: 1.0.2 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - js-sha3: 0.8.0 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - stream-browserify: 3.0.0 - - '@tailwindcss/node@4.1.10': - dependencies: - '@ampproject/remapping': 2.3.0 - enhanced-resolve: 5.18.3 - jiti: 2.5.1 - lightningcss: 1.30.1 - magic-string: 0.30.19 - source-map-js: 1.2.1 - tailwindcss: 4.1.10 - - '@tailwindcss/oxide-android-arm64@4.1.10': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.1.10': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.1.10': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.1.10': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.10': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.1.10': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.1.10': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.1.10': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.1.10': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.10': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.1.10': - optional: true - - '@tailwindcss/oxide@4.1.10': - dependencies: - detect-libc: 2.1.0 - tar: 7.4.3 - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.10 - '@tailwindcss/oxide-darwin-arm64': 4.1.10 - '@tailwindcss/oxide-darwin-x64': 4.1.10 - '@tailwindcss/oxide-freebsd-x64': 4.1.10 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.10 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.10 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.10 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.10 - '@tailwindcss/oxide-linux-x64-musl': 4.1.10 - '@tailwindcss/oxide-wasm32-wasi': 4.1.10 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.10 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.10 - - '@tailwindcss/postcss@4.1.10': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.10 - '@tailwindcss/oxide': 4.1.10 - postcss: 8.5.6 - tailwindcss: 4.1.10 - - '@trysound/sax@0.2.0': {} - - '@types/bn.js@5.1.6': - dependencies: - '@types/node': 20.19.17 - - '@types/bn.js@5.2.0': - dependencies: - '@types/node': 20.19.17 - - '@types/estree@1.0.8': {} - - '@types/node@20.19.17': - dependencies: - undici-types: 6.21.0 - - '@types/pbkdf2@3.1.2': - dependencies: - '@types/node': 20.19.17 - - '@types/react-dom@19.1.6(@types/react@19.1.8)': - dependencies: - '@types/react': 19.1.8 - - '@types/react@19.1.8': - dependencies: - csstype: 3.1.3 - - '@types/resolve@1.20.2': {} - - '@types/secp256k1@4.0.6': - dependencies: - '@types/node': 20.19.17 - - '@types/ws@8.18.1': - dependencies: - '@types/node': 20.19.17 - - '@vitest/expect@3.0.9': - dependencies: - '@vitest/spy': 3.0.9 - '@vitest/utils': 3.0.9 - chai: 5.3.3 - tinyrainbow: 2.0.0 - - '@vitest/mocker@3.0.9(vite@6.3.6(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1))': - dependencies: - '@vitest/spy': 3.0.9 - estree-walker: 3.0.3 - magic-string: 0.30.19 - optionalDependencies: - vite: 6.3.6(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1) - - '@vitest/pretty-format@3.0.9': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/pretty-format@3.2.4': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/runner@3.0.9': - dependencies: - '@vitest/utils': 3.0.9 - pathe: 2.0.3 - - '@vitest/snapshot@3.0.9': - dependencies: - '@vitest/pretty-format': 3.0.9 - magic-string: 0.30.19 - pathe: 2.0.3 - - '@vitest/spy@3.0.9': - dependencies: - tinyspy: 3.0.2 - - '@vitest/utils@3.0.9': - dependencies: - '@vitest/pretty-format': 3.0.9 - loupe: 3.2.1 - tinyrainbow: 2.0.0 - - abitype@1.1.0(typescript@5.9.2): - optionalDependencies: - typescript: 5.9.2 - - aes-js@3.1.2: {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - asn1.js@4.10.1: - dependencies: - bn.js: 4.12.2 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - - assert@2.1.0: - dependencies: - call-bind: 1.0.8 - is-nan: 1.3.2 - object-is: 1.1.6 - object.assign: 4.1.7 - util: 0.12.5 - - assertion-error@2.0.1: {} - - async@2.6.4: - dependencies: - lodash: 4.17.21 - - asynckit@0.4.0: {} - - autoprefixer@10.4.21(postcss@8.5.6): - dependencies: - browserslist: 4.26.2 - caniuse-lite: 1.0.30001743 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - axios@1.12.2: - dependencies: - follow-redirects: 1.15.11 - form-data: 4.0.4 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - balanced-match@1.0.2: {} - - base-x@3.0.11: - dependencies: - safe-buffer: 5.2.1 - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.8.6: {} - - bech32@1.1.4: {} - - binary-extensions@2.3.0: {} - - bip39@3.1.0: - dependencies: - '@noble/hashes': 1.8.0 - - blakejs@1.2.1: {} - - bn.js@4.12.2: {} - - bn.js@5.2.1: {} - - bn.js@5.2.2: {} - - boolbase@1.0.0: {} - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - brorand@1.1.0: {} - - browser-headers@0.4.1: {} - - browserify-aes@1.2.0: - dependencies: - buffer-xor: 1.0.3 - cipher-base: 1.0.6 - create-hash: 1.2.0 - evp_bytestokey: 1.0.3 - inherits: 2.0.4 - safe-buffer: 5.2.1 - - browserify-cipher@1.0.1: - dependencies: - browserify-aes: 1.2.0 - browserify-des: 1.0.2 - evp_bytestokey: 1.0.3 - - browserify-des@1.0.2: - dependencies: - cipher-base: 1.0.6 - des.js: 1.1.0 - inherits: 2.0.4 - safe-buffer: 5.2.1 - - browserify-rsa@4.1.1: - dependencies: - bn.js: 5.2.2 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - - browserify-sign@4.2.3: - dependencies: - bn.js: 5.2.2 - browserify-rsa: 4.1.1 - create-hash: 1.2.0 - create-hmac: 1.1.7 - elliptic: 6.6.1 - hash-base: 3.0.5 - inherits: 2.0.4 - parse-asn1: 5.1.7 - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - - browserslist@4.26.2: - dependencies: - baseline-browser-mapping: 2.8.6 - caniuse-lite: 1.0.30001743 - electron-to-chromium: 1.5.221 - node-releases: 2.0.21 - update-browserslist-db: 1.1.3(browserslist@4.26.2) - - bs58@4.0.1: - dependencies: - base-x: 3.0.11 - - bs58check@2.1.2: - dependencies: - bs58: 4.0.1 - create-hash: 1.2.0 - safe-buffer: 5.2.1 - - buffer-xor@1.0.3: {} - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bundle-require@5.1.0(esbuild@0.25.1): - dependencies: - esbuild: 0.25.1 - load-tsconfig: 0.2.5 - - cac@6.7.14: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.8: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - caniuse-api@3.0.0: - dependencies: - browserslist: 4.26.2 - caniuse-lite: 1.0.30001743 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - - caniuse-lite@1.0.30001743: {} - - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - check-error@2.1.1: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chownr@3.0.0: {} - - cipher-base@1.0.6: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - - cli-table@0.3.11: - dependencies: - colors: 1.0.3 - - clsx@2.1.1: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colord@2.9.3: {} - - colors@1.0.3: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - commander@13.1.0: {} - - commander@7.2.0: {} - - commondir@1.0.1: {} - - concat-with-sourcemaps@1.1.0: - dependencies: - source-map: 0.6.1 - - core-util-is@1.0.3: {} - - cosmjs-types@0.9.0: {} - - create-ecdh@4.0.4: - dependencies: - bn.js: 4.12.2 - elliptic: 6.6.1 - - create-hash@1.1.3: - dependencies: - cipher-base: 1.0.6 - inherits: 2.0.4 - ripemd160: 2.0.1 - sha.js: 2.4.12 - - create-hash@1.2.0: - dependencies: - cipher-base: 1.0.6 - inherits: 2.0.4 - md5.js: 1.3.5 - ripemd160: 2.0.2 - sha.js: 2.4.12 - - create-hmac@1.1.7: - dependencies: - cipher-base: 1.0.6 - create-hash: 1.2.0 - inherits: 2.0.4 - ripemd160: 2.0.2 - safe-buffer: 5.2.1 - sha.js: 2.4.12 - - cross-fetch@3.2.0: - dependencies: - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - - cross-fetch@4.1.0: - dependencies: - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - crypto-browserify@3.12.1: - dependencies: - browserify-cipher: 1.0.1 - browserify-sign: 4.2.3 - create-ecdh: 4.0.4 - create-hash: 1.2.0 - create-hmac: 1.1.7 - diffie-hellman: 5.0.3 - hash-base: 3.0.5 - inherits: 2.0.4 - pbkdf2: 3.1.3 - public-encrypt: 4.0.3 - randombytes: 2.1.0 - randomfill: 1.0.4 - - css-declaration-sorter@6.4.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - css-select@4.3.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.1.1 - - css-tree@1.1.3: - dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 - - css-what@6.2.2: {} - - cssesc@3.0.0: {} - - cssnano-preset-default@5.2.14(postcss@8.5.6): - dependencies: - css-declaration-sorter: 6.4.1(postcss@8.5.6) - cssnano-utils: 3.1.0(postcss@8.5.6) - postcss: 8.5.6 - postcss-calc: 8.2.4(postcss@8.5.6) - postcss-colormin: 5.3.1(postcss@8.5.6) - postcss-convert-values: 5.1.3(postcss@8.5.6) - postcss-discard-comments: 5.1.2(postcss@8.5.6) - postcss-discard-duplicates: 5.1.0(postcss@8.5.6) - postcss-discard-empty: 5.1.1(postcss@8.5.6) - postcss-discard-overridden: 5.1.0(postcss@8.5.6) - postcss-merge-longhand: 5.1.7(postcss@8.5.6) - postcss-merge-rules: 5.1.4(postcss@8.5.6) - postcss-minify-font-values: 5.1.0(postcss@8.5.6) - postcss-minify-gradients: 5.1.1(postcss@8.5.6) - postcss-minify-params: 5.1.4(postcss@8.5.6) - postcss-minify-selectors: 5.2.1(postcss@8.5.6) - postcss-normalize-charset: 5.1.0(postcss@8.5.6) - postcss-normalize-display-values: 5.1.0(postcss@8.5.6) - postcss-normalize-positions: 5.1.1(postcss@8.5.6) - postcss-normalize-repeat-style: 5.1.1(postcss@8.5.6) - postcss-normalize-string: 5.1.0(postcss@8.5.6) - postcss-normalize-timing-functions: 5.1.0(postcss@8.5.6) - postcss-normalize-unicode: 5.1.1(postcss@8.5.6) - postcss-normalize-url: 5.1.0(postcss@8.5.6) - postcss-normalize-whitespace: 5.1.1(postcss@8.5.6) - postcss-ordered-values: 5.1.3(postcss@8.5.6) - postcss-reduce-initial: 5.1.2(postcss@8.5.6) - postcss-reduce-transforms: 5.1.0(postcss@8.5.6) - postcss-svgo: 5.1.0(postcss@8.5.6) - postcss-unique-selectors: 5.1.1(postcss@8.5.6) - - cssnano-utils@3.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - cssnano@5.1.15(postcss@8.5.6): - dependencies: - cssnano-preset-default: 5.2.14(postcss@8.5.6) - lilconfig: 2.1.0 - postcss: 8.5.6 - yaml: 1.10.2 - - csso@4.2.0: - dependencies: - css-tree: 1.1.3 - - csstype@3.1.3: {} - - debug@3.2.7: - dependencies: - ms: 2.1.3 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decimal.js@10.4.3: {} - - decimal.js@10.6.0: {} - - deep-eql@5.0.2: {} - - deepmerge@4.3.1: {} - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - delayed-stream@1.0.0: {} - - des.js@1.1.0: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - - detect-libc@2.1.0: {} - - diffie-hellman@5.0.3: - dependencies: - bn.js: 4.12.2 - miller-rabin: 4.0.1 - randombytes: 2.1.0 - - dom-serializer@1.4.1: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - - domelementtype@2.3.0: {} - - domhandler@4.3.1: - dependencies: - domelementtype: 2.3.0 - - domutils@2.8.0: - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - eastasianwidth@0.2.0: {} - - electron-to-chromium@1.5.221: {} - - elliptic@6.6.1: - dependencies: - bn.js: 4.12.2 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - enc-utils@3.0.0: - dependencies: - is-typedarray: 1.0.0 - typedarray-to-buffer: 3.1.5 - - enhanced-resolve@5.18.3: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.3 - - entities@2.2.0: {} - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-module-lexer@1.7.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - es-toolkit@1.39.10: {} - - esbuild@0.25.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.1 - '@esbuild/android-arm': 0.25.1 - '@esbuild/android-arm64': 0.25.1 - '@esbuild/android-x64': 0.25.1 - '@esbuild/darwin-arm64': 0.25.1 - '@esbuild/darwin-x64': 0.25.1 - '@esbuild/freebsd-arm64': 0.25.1 - '@esbuild/freebsd-x64': 0.25.1 - '@esbuild/linux-arm': 0.25.1 - '@esbuild/linux-arm64': 0.25.1 - '@esbuild/linux-ia32': 0.25.1 - '@esbuild/linux-loong64': 0.25.1 - '@esbuild/linux-mips64el': 0.25.1 - '@esbuild/linux-ppc64': 0.25.1 - '@esbuild/linux-riscv64': 0.25.1 - '@esbuild/linux-s390x': 0.25.1 - '@esbuild/linux-x64': 0.25.1 - '@esbuild/netbsd-arm64': 0.25.1 - '@esbuild/netbsd-x64': 0.25.1 - '@esbuild/openbsd-arm64': 0.25.1 - '@esbuild/openbsd-x64': 0.25.1 - '@esbuild/sunos-x64': 0.25.1 - '@esbuild/win32-arm64': 0.25.1 - '@esbuild/win32-ia32': 0.25.1 - '@esbuild/win32-x64': 0.25.1 - - esbuild@0.25.10: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.10 - '@esbuild/android-arm': 0.25.10 - '@esbuild/android-arm64': 0.25.10 - '@esbuild/android-x64': 0.25.10 - '@esbuild/darwin-arm64': 0.25.10 - '@esbuild/darwin-x64': 0.25.10 - '@esbuild/freebsd-arm64': 0.25.10 - '@esbuild/freebsd-x64': 0.25.10 - '@esbuild/linux-arm': 0.25.10 - '@esbuild/linux-arm64': 0.25.10 - '@esbuild/linux-ia32': 0.25.10 - '@esbuild/linux-loong64': 0.25.10 - '@esbuild/linux-mips64el': 0.25.10 - '@esbuild/linux-ppc64': 0.25.10 - '@esbuild/linux-riscv64': 0.25.10 - '@esbuild/linux-s390x': 0.25.10 - '@esbuild/linux-x64': 0.25.10 - '@esbuild/netbsd-arm64': 0.25.10 - '@esbuild/netbsd-x64': 0.25.10 - '@esbuild/openbsd-arm64': 0.25.10 - '@esbuild/openbsd-x64': 0.25.10 - '@esbuild/openharmony-arm64': 0.25.10 - '@esbuild/sunos-x64': 0.25.10 - '@esbuild/win32-arm64': 0.25.10 - '@esbuild/win32-ia32': 0.25.10 - '@esbuild/win32-x64': 0.25.10 - - escalade@3.2.0: {} - - estree-walker@0.6.1: {} - - estree-walker@2.0.2: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - ethereum-cryptography@0.1.3: - dependencies: - '@types/pbkdf2': 3.1.2 - '@types/secp256k1': 4.0.6 - blakejs: 1.2.1 - browserify-aes: 1.2.0 - bs58check: 2.1.2 - create-hash: 1.2.0 - create-hmac: 1.1.7 - hash.js: 1.1.7 - keccak: 3.0.4 - pbkdf2: 3.1.3 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - scrypt-js: 3.0.1 - secp256k1: 4.0.4 - setimmediate: 1.0.5 - - ethereumjs-util@7.1.5: - dependencies: - '@types/bn.js': 5.2.0 - bn.js: 5.2.2 - create-hash: 1.2.0 - ethereum-cryptography: 0.1.3 - rlp: 2.2.7 - - ethereumjs-wallet@1.0.2: - dependencies: - aes-js: 3.1.2 - bs58check: 2.1.2 - ethereum-cryptography: 0.1.3 - ethereumjs-util: 7.1.5 - randombytes: 2.1.0 - scrypt-js: 3.0.1 - utf8: 3.0.0 - uuid: 8.3.2 - - event-iterator@2.0.0: {} - - eventemitter3@4.0.7: {} - - eventemitter3@5.0.1: {} - - events@3.3.0: {} - - evp_bytestokey@1.0.3: - dependencies: - md5.js: 1.3.5 - safe-buffer: 5.2.1 - - expect-type@1.2.2: {} - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - fflate@0.8.2: {} - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-cache-dir@3.3.2: - dependencies: - commondir: 1.0.1 - make-dir: 3.1.0 - pkg-dir: 4.2.0 - - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - follow-redirects@1.15.11: {} - - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - form-data@4.0.4: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - - fraction.js@4.3.7: {} - - framer-motion@12.23.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1): - dependencies: - motion-dom: 12.23.12 - motion-utils: 12.23.6 - tslib: 2.8.1 - optionalDependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - fuels@0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)): - dependencies: - '@fuel-ts/abi-coder': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/abi-typegen': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/account': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/address': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/contract': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/crypto': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/errors': 0.101.1 - '@fuel-ts/hasher': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/math': 0.101.1 - '@fuel-ts/program': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/recipes': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/script': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/transactions': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/utils': 0.101.1(vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@fuel-ts/versions': 0.101.1 - '@fuels/vm-asm': 0.60.2 - bundle-require: 5.1.0(esbuild@0.25.1) - chalk: 4.1.2 - chokidar: 3.6.0 - commander: 13.1.0 - esbuild: 0.25.1 - glob: 10.4.5 - handlebars: 4.7.8 - joycon: 3.1.1 - lodash.camelcase: 4.3.0 - portfinder: 1.0.32 - toml: 3.0.0 - uglify-js: 3.19.3 - yup: 1.6.1 - transitivePeerDependencies: - - encoding - - supports-color - - vitest - - function-bind@1.1.2: {} - - generic-names@4.0.0: - dependencies: - loader-utils: 3.3.1 - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - glob@8.1.0: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - google-protobuf@3.21.4: {} - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - graphql-request@6.1.0(graphql@16.10.0): - dependencies: - '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) - cross-fetch: 3.2.0 - graphql: 16.10.0 - transitivePeerDependencies: - - encoding - - graphql-tag@2.12.6(graphql@16.10.0): - dependencies: - graphql: 16.10.0 - tslib: 2.8.1 - - graphql@16.10.0: {} - - handlebars@4.7.8: - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 - - has-flag@4.0.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hash-base@2.0.2: - dependencies: - inherits: 2.0.4 - - hash-base@3.0.5: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - - hash.js@1.1.7: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - hmac-drbg@1.0.1: - dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - husky@8.0.3: {} - - icss-replace-symbols@1.1.0: {} - - icss-utils@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - ieee754@1.2.1: {} - - import-cwd@3.0.0: - dependencies: - import-from: 3.0.0 - - import-from@3.0.0: - dependencies: - resolve-from: 5.0.0 - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - is-arguments@1.2.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-callable@1.2.7: {} - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-generator-function@1.1.0: - dependencies: - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-module@1.0.0: {} - - is-nan@1.3.2: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - - is-number@7.0.0: {} - - is-reference@1.2.1: - dependencies: - '@types/estree': 1.0.8 - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.19 - - is-typedarray@1.0.0: {} - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isexe@2.0.0: {} - - isomorphic-ws@4.0.1(ws@7.5.10): - dependencies: - ws: 7.5.10 - - isows@1.0.7(ws@8.18.3): - dependencies: - ws: 8.18.3 - - it-stream-types@2.0.2: {} - - it-ws@6.1.5: - dependencies: - '@types/ws': 8.18.1 - event-iterator: 2.0.0 - it-stream-types: 2.0.2 - uint8arrays: 5.1.0 - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jiti@2.5.1: {} - - joycon@3.1.1: {} - - js-sha3@0.8.0: {} - - js-tokens@4.0.0: - optional: true - - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - keccak@3.0.4: - dependencies: - node-addon-api: 2.0.2 - node-gyp-build: 4.8.4 - readable-stream: 3.6.2 - - libsodium-sumo@0.7.15: {} - - libsodium-wrappers-sumo@0.7.15: - dependencies: - libsodium-sumo: 0.7.15 - - lightningcss-darwin-arm64@1.30.1: - optional: true - - lightningcss-darwin-x64@1.30.1: - optional: true - - lightningcss-freebsd-x64@1.30.1: - optional: true - - lightningcss-linux-arm-gnueabihf@1.30.1: - optional: true - - lightningcss-linux-arm64-gnu@1.30.1: - optional: true - - lightningcss-linux-arm64-musl@1.30.1: - optional: true - - lightningcss-linux-x64-gnu@1.30.1: - optional: true - - lightningcss-linux-x64-musl@1.30.1: - optional: true - - lightningcss-win32-arm64-msvc@1.30.1: - optional: true - - lightningcss-win32-x64-msvc@1.30.1: - optional: true - - lightningcss@1.30.1: - dependencies: - detect-libc: 2.1.0 - optionalDependencies: - lightningcss-darwin-arm64: 1.30.1 - lightningcss-darwin-x64: 1.30.1 - lightningcss-freebsd-x64: 1.30.1 - lightningcss-linux-arm-gnueabihf: 1.30.1 - lightningcss-linux-arm64-gnu: 1.30.1 - lightningcss-linux-arm64-musl: 1.30.1 - lightningcss-linux-x64-gnu: 1.30.1 - lightningcss-linux-x64-musl: 1.30.1 - lightningcss-win32-arm64-msvc: 1.30.1 - lightningcss-win32-x64-msvc: 1.30.1 - - lilconfig@2.1.0: {} - - load-tsconfig@0.2.5: {} - - loader-utils@3.3.1: {} - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - - lodash.camelcase@4.3.0: {} - - lodash.memoize@4.1.2: {} - - lodash.uniq@4.5.0: {} - - lodash@4.17.21: {} - - long@5.3.2: {} - - loupe@3.2.1: {} - - lru-cache@10.4.3: {} - - magic-string@0.30.19: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - make-dir@3.1.0: - dependencies: - semver: 6.3.1 - - math-intrinsics@1.1.0: {} - - md5.js@1.3.5: - dependencies: - hash-base: 3.0.5 - inherits: 2.0.4 - safe-buffer: 5.2.1 - - mdn-data@2.0.14: {} - - miller-rabin@4.0.1: - dependencies: - bn.js: 4.12.2 - brorand: 1.1.0 - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - minimalistic-assert@1.0.1: {} - - minimalistic-crypto-utils@1.0.1: {} - - minimatch@5.1.6: - dependencies: - brace-expansion: 2.0.2 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - - minimist@1.2.8: {} - - minipass@7.1.2: {} - - minizlib@3.0.2: - dependencies: - minipass: 7.1.2 - - mkdirp@0.5.6: - dependencies: - minimist: 1.2.8 - - mkdirp@3.0.1: {} - - motion-dom@12.23.12: - dependencies: - motion-utils: 12.23.6 - - motion-utils@12.23.6: {} - - motion@12.23.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): - dependencies: - framer-motion: 12.23.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - tslib: 2.8.1 - optionalDependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - - ms@2.1.3: {} - - msgpackr-extract@3.0.3: - dependencies: - node-gyp-build-optional-packages: 5.2.2 - optionalDependencies: - '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 - '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 - optional: true - - msgpackr@1.11.5: - optionalDependencies: - msgpackr-extract: 3.0.3 - - multiformats@13.4.1: {} - - nanoid@3.3.11: {} - - neo-async@2.6.2: {} - - node-addon-api@2.0.2: {} - - node-addon-api@5.1.0: {} - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-gyp-build-optional-packages@5.2.2: - dependencies: - detect-libc: 2.1.0 - optional: true - - node-gyp-build@4.8.4: {} - - node-releases@2.0.21: {} - - normalize-path@3.0.0: {} - - normalize-range@0.1.2: {} - - normalize-url@6.1.0: {} - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - - object-is@1.1.6: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - - object-keys@1.1.1: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - ox@0.9.3(typescript@5.9.2): - dependencies: - '@adraffy/ens-normalize': 1.11.0 - '@noble/ciphers': 1.3.0 - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.9.2) - eventemitter3: 5.0.1 - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - zod - - p-finally@1.0.0: {} - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - - p-try@2.2.0: {} - - package-json-from-dist@1.0.1: {} - - parse-asn1@5.1.7: - dependencies: - asn1.js: 4.10.1 - browserify-aes: 1.2.0 - evp_bytestokey: 1.0.3 - hash-base: 3.0.5 - pbkdf2: 3.1.3 - safe-buffer: 5.2.1 - - path-exists@4.0.0: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - pathe@2.0.3: {} - - pathval@2.0.1: {} - - pbkdf2@3.1.3: - dependencies: - create-hash: 1.1.3 - create-hmac: 1.1.7 - ripemd160: 2.0.1 - safe-buffer: 5.2.1 - sha.js: 2.4.12 - to-buffer: 1.2.1 - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.3: {} - - pify@2.3.0: {} - - pify@5.0.0: {} - - pkg-dir@4.2.0: - dependencies: - find-up: 4.1.0 - - portfinder@1.0.32: - dependencies: - async: 2.6.4 - debug: 3.2.7 - mkdirp: 0.5.6 - transitivePeerDependencies: - - supports-color - - possible-typed-array-names@1.1.0: {} - - postcss-calc@8.2.4(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-selector-parser: 6.1.2 - postcss-value-parser: 4.2.0 - - postcss-colormin@5.3.1(postcss@8.5.6): - dependencies: - browserslist: 4.26.2 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-convert-values@5.1.3(postcss@8.5.6): - dependencies: - browserslist: 4.26.2 - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-discard-comments@5.1.2(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - postcss-discard-duplicates@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - postcss-discard-empty@5.1.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - postcss-discard-overridden@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - postcss-import@16.1.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.10 - - postcss-load-config@3.1.4(postcss@8.5.6): - dependencies: - lilconfig: 2.1.0 - yaml: 1.10.2 - optionalDependencies: - postcss: 8.5.6 - - postcss-merge-longhand@5.1.7(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.5.6) - - postcss-merge-rules@5.1.4(postcss@8.5.6): - dependencies: - browserslist: 4.26.2 - caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.5.6) - postcss: 8.5.6 - postcss-selector-parser: 6.1.2 - - postcss-minify-font-values@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-minify-gradients@5.1.1(postcss@8.5.6): - dependencies: - colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.5.6) - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-minify-params@5.1.4(postcss@8.5.6): - dependencies: - browserslist: 4.26.2 - cssnano-utils: 3.1.0(postcss@8.5.6) - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-minify-selectors@5.2.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-selector-parser: 6.1.2 - - postcss-modules-extract-imports@3.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - postcss-modules-local-by-default@4.2.0(postcss@8.5.6): - dependencies: - icss-utils: 5.1.0(postcss@8.5.6) - postcss: 8.5.6 - postcss-selector-parser: 7.1.0 - postcss-value-parser: 4.2.0 - - postcss-modules-scope@3.2.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-selector-parser: 7.1.0 - - postcss-modules-values@4.0.0(postcss@8.5.6): - dependencies: - icss-utils: 5.1.0(postcss@8.5.6) - postcss: 8.5.6 - - postcss-modules@4.3.1(postcss@8.5.6): - dependencies: - generic-names: 4.0.0 - icss-replace-symbols: 1.1.0 - lodash.camelcase: 4.3.0 - postcss: 8.5.6 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) - postcss-modules-scope: 3.2.1(postcss@8.5.6) - postcss-modules-values: 4.0.0(postcss@8.5.6) - string-hash: 1.1.3 - - postcss-nesting@13.0.2(postcss@8.5.6): - dependencies: - '@csstools/selector-resolve-nested': 3.1.0(postcss-selector-parser@7.1.0) - '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.0) - postcss: 8.5.6 - postcss-selector-parser: 7.1.0 - - postcss-normalize-charset@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - - postcss-normalize-display-values@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-normalize-positions@5.1.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-normalize-repeat-style@5.1.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-normalize-string@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-normalize-timing-functions@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-normalize-unicode@5.1.1(postcss@8.5.6): - dependencies: - browserslist: 4.26.2 - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-normalize-url@5.1.0(postcss@8.5.6): - dependencies: - normalize-url: 6.1.0 - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-normalize-whitespace@5.1.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-ordered-values@5.1.3(postcss@8.5.6): - dependencies: - cssnano-utils: 3.1.0(postcss@8.5.6) - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-reduce-initial@5.1.2(postcss@8.5.6): - dependencies: - browserslist: 4.26.2 - caniuse-api: 3.0.0 - postcss: 8.5.6 - - postcss-reduce-transforms@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-selector-parser@7.1.0: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-svgo@5.1.0(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - svgo: 2.8.0 - - postcss-unique-selectors@5.1.1(postcss@8.5.6): - dependencies: - postcss: 8.5.6 - postcss-selector-parser: 6.1.2 - - postcss-value-parser@4.2.0: {} - - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prettier@3.6.2: {} - - process-nextick-args@2.0.1: {} - - promise.series@0.2.0: {} - - property-expr@2.0.6: {} - - proxy-from-env@1.1.0: {} - - public-encrypt@4.0.3: - dependencies: - bn.js: 4.12.2 - browserify-rsa: 4.1.1 - create-hash: 1.2.0 - parse-asn1: 5.1.7 - randombytes: 2.1.0 - safe-buffer: 5.2.1 - - ramda@0.30.1: {} - - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - - randomfill@1.0.4: - dependencies: - randombytes: 2.1.0 - safe-buffer: 5.2.1 - - react-dom@19.1.1(react@19.1.1): - dependencies: - react: 19.1.1 - scheduler: 0.26.0 - - react@19.1.1: {} - - read-cache@1.0.0: - dependencies: - pify: 2.3.0 - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - readonly-date@1.0.0: {} - - resolve-from@5.0.0: {} - - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - rimraf@5.0.10: - dependencies: - glob: 10.4.5 - - ripemd160@2.0.1: - dependencies: - hash-base: 2.0.2 - inherits: 2.0.4 - - ripemd160@2.0.2: - dependencies: - hash-base: 3.0.5 - inherits: 2.0.4 - - rlp@2.2.7: - dependencies: - bn.js: 5.2.2 - - rollup-plugin-dts@6.2.3(rollup@4.50.2)(typescript@5.9.2): - dependencies: - magic-string: 0.30.19 - rollup: 4.50.2 - typescript: 5.9.2 - optionalDependencies: - '@babel/code-frame': 7.27.1 - - rollup-plugin-postcss@4.0.2(postcss@8.5.6): - dependencies: - chalk: 4.1.2 - concat-with-sourcemaps: 1.1.0 - cssnano: 5.1.15(postcss@8.5.6) - import-cwd: 3.0.0 - p-queue: 6.6.2 - pify: 5.0.0 - postcss: 8.5.6 - postcss-load-config: 3.1.4(postcss@8.5.6) - postcss-modules: 4.3.1(postcss@8.5.6) - promise.series: 0.2.0 - resolve: 1.22.10 - rollup-pluginutils: 2.8.2 - safe-identifier: 0.4.2 - style-inject: 0.3.0 - transitivePeerDependencies: - - ts-node - - rollup-plugin-typescript2@0.36.0(rollup@4.50.2)(typescript@5.9.2): - dependencies: - '@rollup/pluginutils': 4.2.1 - find-cache-dir: 3.3.2 - fs-extra: 10.1.0 - rollup: 4.50.2 - semver: 7.7.2 - tslib: 2.8.1 - typescript: 5.9.2 - - rollup-pluginutils@2.8.2: - dependencies: - estree-walker: 0.6.1 - - rollup@4.50.2: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.50.2 - '@rollup/rollup-android-arm64': 4.50.2 - '@rollup/rollup-darwin-arm64': 4.50.2 - '@rollup/rollup-darwin-x64': 4.50.2 - '@rollup/rollup-freebsd-arm64': 4.50.2 - '@rollup/rollup-freebsd-x64': 4.50.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.50.2 - '@rollup/rollup-linux-arm-musleabihf': 4.50.2 - '@rollup/rollup-linux-arm64-gnu': 4.50.2 - '@rollup/rollup-linux-arm64-musl': 4.50.2 - '@rollup/rollup-linux-loong64-gnu': 4.50.2 - '@rollup/rollup-linux-ppc64-gnu': 4.50.2 - '@rollup/rollup-linux-riscv64-gnu': 4.50.2 - '@rollup/rollup-linux-riscv64-musl': 4.50.2 - '@rollup/rollup-linux-s390x-gnu': 4.50.2 - '@rollup/rollup-linux-x64-gnu': 4.50.2 - '@rollup/rollup-linux-x64-musl': 4.50.2 - '@rollup/rollup-openharmony-arm64': 4.50.2 - '@rollup/rollup-win32-arm64-msvc': 4.50.2 - '@rollup/rollup-win32-ia32-msvc': 4.50.2 - '@rollup/rollup-win32-x64-msvc': 4.50.2 - fsevents: 2.3.3 - - safe-buffer@5.1.2: {} - - safe-buffer@5.2.1: {} - - safe-identifier@0.4.2: {} - - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - scheduler@0.26.0: {} - - scrypt-js@3.0.1: {} - - secp256k1@4.0.4: - dependencies: - elliptic: 6.6.1 - node-addon-api: 5.1.0 - node-gyp-build: 4.8.4 - - semver@6.3.1: {} - - semver@7.7.2: {} - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - setimmediate@1.0.5: {} - - sha.js@2.4.12: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - to-buffer: 1.2.1 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - siginfo@2.0.0: {} - - signal-exit@4.1.0: {} - - source-map-js@1.2.1: {} - - source-map@0.6.1: {} - - stable@0.1.8: {} - - stackback@0.0.2: {} - - std-env@3.9.0: {} - - stream-browserify@3.0.0: - dependencies: - inherits: 2.0.4 - readable-stream: 3.6.2 - - string-hash@1.1.3: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.2: - dependencies: - ansi-regex: 6.2.2 - - style-inject@0.3.0: {} - - stylehacks@5.1.1(postcss@8.5.6): - dependencies: - browserslist: 4.26.2 - postcss: 8.5.6 - postcss-selector-parser: 6.1.2 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - svgo@2.8.0: - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 4.3.0 - css-tree: 1.1.3 - csso: 4.2.0 - picocolors: 1.1.1 - stable: 0.1.8 - - symbol-observable@2.0.3: {} - - tailwind-merge@3.3.1: {} - - tailwindcss@4.1.10: {} - - tapable@2.2.3: {} - - tar@7.4.3: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.0.2 - mkdirp: 3.0.1 - yallist: 5.0.0 - - tiny-case@1.0.3: {} - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@3.0.2: {} - - to-buffer@1.2.1: - dependencies: - isarray: 2.0.5 - safe-buffer: 5.2.1 - typed-array-buffer: 1.0.3 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - toml@3.0.0: {} - - toposort@2.0.2: {} - - tr46@0.0.3: {} - - tslib@2.8.1: {} - - type-fest@2.19.0: {} - - type-fest@4.34.1: {} - - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typedarray-to-buffer@3.1.5: - dependencies: - is-typedarray: 1.0.0 - - typescript@5.9.2: {} - - uglify-js@3.19.3: {} - - uint8arrays@5.1.0: - dependencies: - multiformats: 13.4.1 - - undici-types@6.21.0: {} - - universalify@2.0.1: {} - - update-browserslist-db@1.1.3(browserslist@4.26.2): - dependencies: - browserslist: 4.26.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - utf8@3.0.0: {} - - util-deprecate@1.0.2: {} - - util@0.12.5: - dependencies: - inherits: 2.0.4 - is-arguments: 1.2.0 - is-generator-function: 1.1.0 - is-typed-array: 1.1.15 - which-typed-array: 1.1.19 - - uuid@8.3.2: {} - - viem@2.37.6(typescript@5.9.2): - dependencies: - '@noble/curves': 1.9.1 - '@noble/hashes': 1.8.0 - '@scure/bip32': 1.7.0 - '@scure/bip39': 1.6.0 - abitype: 1.1.0(typescript@5.9.2) - isows: 1.0.7(ws@8.18.3) - ox: 0.9.3(typescript@5.9.2) - ws: 8.18.3 - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - - vite-node@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.3.6(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite@6.3.6(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1): - dependencies: - esbuild: 0.25.10 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.50.2 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 20.19.17 - fsevents: 2.3.3 - jiti: 2.5.1 - lightningcss: 1.30.1 - - vitest@3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1): - dependencies: - '@vitest/expect': 3.0.9 - '@vitest/mocker': 3.0.9(vite@6.3.6(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.0.9 - '@vitest/snapshot': 3.0.9 - '@vitest/spy': 3.0.9 - '@vitest/utils': 3.0.9 - chai: 5.3.3 - debug: 4.4.3 - expect-type: 1.2.2 - magic-string: 0.30.19 - pathe: 2.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 6.3.6(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1) - vite-node: 3.0.9(@types/node@20.19.17)(jiti@2.5.1)(lightningcss@1.30.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 20.19.17 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - which-typed-array@1.1.19: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - wordwrap@1.0.0: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - - wrappy@1.0.2: {} - - ws@7.5.10: {} - - ws@8.18.3: {} - - xstream@11.14.0: - dependencies: - globalthis: 1.0.4 - symbol-observable: 2.0.3 - - yallist@5.0.0: {} - - yaml@1.10.2: {} - - yup@1.6.1: - dependencies: - property-expr: 2.0.6 - tiny-case: 1.0.3 - toposort: 2.0.2 - type-fest: 2.19.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index 4340350e..00000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - 'packages/*' \ No newline at end of file diff --git a/packages/core/rollup.config.mjs b/rollup.config.mjs similarity index 67% rename from packages/core/rollup.config.mjs rename to rollup.config.mjs index d1797a64..bcf95809 100644 --- a/packages/core/rollup.config.mjs +++ b/rollup.config.mjs @@ -2,11 +2,9 @@ import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import typescript from 'rollup-plugin-typescript2'; import json from '@rollup/plugin-json'; -import alias from '@rollup/plugin-alias'; import dts from 'rollup-plugin-dts'; import { defineConfig } from 'rollup'; -import { createRequire } from 'module'; - +import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const packageJson = require('./package.json'); @@ -15,12 +13,8 @@ const shouldGenerateSourceMaps = false; // Base configuration for core (no React, no CSS) const baseConfig = { - input: 'index.ts', + input: 'src/index.ts', plugins: [ - alias({ - // Alias is not used for externals but kept for future non-externalized builds - entries: [{ find: '@nexus/commons', replacement: './commons' }], - }), json(), resolve({ browser: true, @@ -41,24 +35,26 @@ const baseConfig = { // Peer dependencies that consumers should install ...Object.keys(packageJson.peerDependencies || {}), /^viem/, + 'buffer', // External dependencies that consumers should install - '@arcana/ca-common', + '@avail-project/ca-common', + '@tronweb3/tronwallet-abstract-adapter', + // Ensure TronWeb is not bundled to preserve its side-effectful proto setup + 'tronweb', '@cosmjs/proto-signing', '@cosmjs/stargate', '@starkware-industries/starkware-crypto-utils', '@metamask/safe-event-emitter', - '@nexus/commons', 'decimal.js', - 'fuels', 'long', 'msgpackr', 'tslib', 'axios', 'es-toolkit', - './commons', ], treeshake: { - moduleSideEffects: false, + // Preserve side effects for external deps like tronweb that rely on global proto init + moduleSideEffects: 'no-external', propertyReadSideEffects: false, unknownGlobalSideEffects: false, }, @@ -75,40 +71,21 @@ export default defineConfig([ sourcemap: shouldGenerateSourceMaps, exports: 'named', interop: 'auto', - paths: { - '@nexus/commons': './commons', - '@nexus/commons/constants': './commons/constants', - }, }, { file: 'dist/index.esm.js', format: 'esm', sourcemap: shouldGenerateSourceMaps, exports: 'named', - paths: { - '@nexus/commons': './commons', - '@nexus/commons/constants': './commons/constants', - }, }, ], }, // TypeScript declarations { - input: 'index.ts', + input: 'src/index.ts', output: [{ file: 'dist/index.d.ts', format: 'esm' }], - plugins: [ - dts(), - // Rewrite import specifiers in generated d.ts - { - name: 'rewrite-commons-imports-dts', - renderChunk(code) { - return code - .replace(/@nexus\/commons\/constants/g, './commons/constants') - .replace(/@nexus\/commons/g, './commons'); - }, - }, - ], + plugins: [dts()], external: [ ...Object.keys(packageJson.peerDependencies || {}), /^viem/, @@ -116,15 +93,14 @@ export default defineConfig([ /^@cosmjs/, /^@starkware-industries/, '@metamask/safe-event-emitter', + '@tronweb3/tronwallet-abstract-adapter', + 'tronweb', 'decimal.js', - 'fuels', 'long', 'msgpackr', 'tslib', - '@nexus/commons', 'axios', 'es-toolkit', - './commons', ], }, ]); diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index c8248675..00000000 --- a/scripts/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# Release Scripts - -This directory contains release scripts for the Nexus SDK monorepo packages. - -## Available Scripts - -### Individual Package Releases - -#### Core Package (`@avail-project/nexus-core`) - -```bash -# Development release (default) -./scripts/release-core.sh dev [patch|minor|major] -pnpm run release:core:dev - -# Production release -./scripts/release-core.sh prod [patch|minor|major] -pnpm run release:core:prod -``` - -#### Widgets Package (`@avail-project/nexus-widgets`) - -```bash -# Development release (default) -./scripts/release-widgets.sh dev [patch|minor|major] -pnpm run release:widgets:dev - -# Production release -./scripts/release-widgets.sh prod [patch|minor|major] -pnpm run release:widgets:prod -``` - -### Local Tarballs (No Publish) - -```bash -# Build and create .tgz files for local installs -./scripts/local-pack.sh - -# In another project -pnpm add /absolute/path/to/dist-tarballs/avail-project-nexus-core-*.tgz \ - /absolute/path/to/dist-tarballs/avail-project-nexus-widgets-*.tgz -``` - -## Release Types - -### Development Releases (`dev`) - -- Creates SemVer prereleases with your chosen tag (e.g., `beta`, `alpha`, `dev`). -- Prerelease numbering policy: increments 0β†’9, then rolls to the next patch. - - Example: `0.0.2-beta.0 β†’ 0.0.2-beta.1 … β†’ 0.0.2-beta.9 β†’ 0.0.3-beta.0`. -- Widgets depends on the most recently published Core prerelease for the same tag by publish timestamp (not by semver magnitude). -- No branch restrictions. - -### Production Releases (`prod`) - -- Publishes clean version numbers (e.g., `1.2.3`). -- Tagged with `latest` on npm (default install). -- Checks for `main` branch (can be overridden with `--yes`). -- Creates git tags and pushes to remote. -- Interactive confirmation unless `--yes` is passed. - -## Version Bump Types - -- **patch**: Bug fixes (1.0.0 β†’ 1.0.1) -- **minor**: New features (1.0.0 β†’ 1.1.0) -- **major**: Breaking changes (1.0.0 β†’ 2.0.0) - -## Examples - -```bash -# Core – interactive dev prerelease (choose tag: beta/alpha/dev) -./scripts/release-core.sh - -# Core – non-interactive dev prerelease (beta), dry-run -./scripts/release-core.sh dev patch beta --yes --dry-run - -# Core – non-interactive dev prerelease (beta), publish -./scripts/release-core.sh dev patch beta --yes - -# Core – production release (patch) from current branch -./scripts/release-core.sh prod patch --yes - -# Widgets – interactive dev prerelease (requires matching core prerelease on npm) -./scripts/release-widgets.sh - -# Widgets – non-interactive dev prerelease (beta), resolves latest core beta by timestamp, dry-run -./scripts/release-widgets.sh dev patch beta --yes --dry-run - -# Widgets – production release (patch) -./scripts/release-widgets.sh prod patch --yes -``` - -## Prerequisites - -- Clean git working directory (no uncommitted changes) -- All dependencies installed (`pnpm install`) -- Valid npm authentication for publishing - -## What the Scripts Do - -1. **Validation**: Check git status, dependencies, and prerequisites -2. **Type Checking**: Run `pnpm run typecheck` to ensure code quality -3. **Building**: Clean and build all necessary packages -4. **Version Bump**: Interactive wizard chooses dev/prod and tag; dev follows 0–9 rollover policy; prod bumps patch/minor/major -5. **Git Operations**: Commit version changes, create tags -6. **Publishing**: Publish to npm with correct tags and access -7. **Cleanup**: Push changes and tags to remote repository - -## Dependency Order - -The widgets package depends on the core package, so: - -- For production releases, core must be published before widgets. -- For dev prereleases, the widgets script resolves the most recently published core prerelease for the same tag by npm publish time. - -## Output - -All scripts provide colored, detailed output showing: - -- Current operation status -- Build results and warnings -- Publication confirmation -- Installation instructions -- Links to published packages - -## Error Handling - -Scripts will exit with error codes if: - -- Git repository has uncommitted changes. -- Type checking fails. -- Build process fails. -- Publication fails. -- Required dependencies are missing. - -## Flags - -- `--yes` or `--ci`: skip interactive prompts (useful in CI; also bypasses the main-branch prompt on prod). -- `--dry-run` or `-n`: simulate publish (runs `npm pack` instead of `npm publish`; skips git push/tag). - -## Internal Commons - -- `@nexus/commons` is internal and kept out of published dependencies. -- Both scripts bundle `commons` into `dist/commons` so consumers never install it directly. diff --git a/scripts/local-pack.sh b/scripts/local-pack.sh deleted file mode 100755 index c8511153..00000000 --- a/scripts/local-pack.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash - -# Create local installable tarballs for @nexus/core and @nexus/widgets without publishing. -# This script builds packages, rewrites workspace deps for packaging, packs to dist-tarballs/, and restores files. -set -e - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -info() { echo -e "${GREEN}[INFO]${NC} $1"; } -warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -err() { echo -e "${RED}[ERROR]${NC} $1"; } - -ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd) -DEST_DIR="$ROOT_DIR/dist-tarballs" - -cd "$ROOT_DIR" - -# Pre-flight -if [[ ! -f package.json ]] || [[ ! -d packages ]]; then - err "Run from repo root" - exit 1 -fi - -mkdir -p "$DEST_DIR" - -info "Cleaning and building packages..." -pnpm run clean -pnpm -F @nexus/commons build -pnpm -F @avail-project/nexus-core build -pnpm -F @avail-project/nexus-widgets build - -# Pack core (already named @avail-project/nexus-core; remove workspace-only deps) -info "Packing core as @avail-project/nexus-core (local tarball)..." -pushd packages/core >/dev/null -cp package.json package.json.backup - -# Remove @nexus/commons (bundled into dist) -node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));if(p.dependencies){delete p.dependencies['@nexus/commons'];}fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n');" - -CORE_TARBALL=$(npm pack --pack-destination "$DEST_DIR" --silent) -mv package.json.backup package.json -popd >/dev/null - -info "Created core tarball: $DEST_DIR/$CORE_TARBALL (name: @avail-project/nexus-core)" - -# Pack widgets (already named @avail-project/nexus-widgets; remove workspace-only deps; pin @avail-project/nexus-core to current version) -info "Packing widgets as @avail-project/nexus-widgets (local tarball)..." -pushd packages/widgets >/dev/null -cp package.json package.json.backup - -CORE_VERSION=$(node -p "require('../core/package.json').version") -export CORE_VERSION - -node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.dependencies=p.dependencies||{};if(p.dependencies['@nexus/commons']){delete p.dependencies['@nexus/commons'];}p.dependencies['@avail-project/nexus-core']=process.env.CORE_VERSION;fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n');" - -WIDGETS_TARBALL=$(npm pack --pack-destination "$DEST_DIR" --silent) -mv package.json.backup package.json -popd >/dev/null - -info "Created widgets tarball: $DEST_DIR/$WIDGETS_TARBALL (name: @avail-project/nexus-widgets)" - -info "Done. Tarballs are in $DEST_DIR" -echo "" -echo "Install in a project with:" -echo " pnpm add $DEST_DIR/$CORE_TARBALL $DEST_DIR/$WIDGETS_TARBALL" -echo "or" -echo " npm i $DEST_DIR/$CORE_TARBALL $DEST_DIR/$WIDGETS_TARBALL" - - diff --git a/scripts/release-core.sh b/scripts/release-core.sh deleted file mode 100755 index 2789f036..00000000 --- a/scripts/release-core.sh +++ /dev/null @@ -1,338 +0,0 @@ -#!/bin/bash - -# Nexus Core SDK Release Script -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to print colored output -print_status() { - echo -e "${GREEN}[INFO]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -print_header() { - echo -e "${BLUE}[CORE RELEASE]${NC} $1" -} - -# Flags -NON_INTERACTIVE=0 -for arg in "$@"; do - if [[ "$arg" == "--yes" || "$arg" == "-y" || "$arg" == "--ci" ]]; then - NON_INTERACTIVE=1 - fi -done -DRY_RUN=0 -for arg in "$@"; do - if [[ "$arg" == "--dry-run" || "$arg" == "-n" ]]; then - DRY_RUN=1 - fi -done - -# Check if we're in a git repository -if ! git rev-parse --git-dir > /dev/null 2>&1; then - print_error "Not in a git repository" - exit 1 -fi - -# Check if we're in the root directory -if [[ ! -f "package.json" ]] || [[ ! -d "packages/core" ]]; then - print_error "Please run this script from the monorepo root directory" - exit 1 -fi - -# Check for uncommitted changes -if ! git diff-index --quiet HEAD --; then - print_error "There are uncommitted changes. Please commit or stash them first." - exit 1 -fi - -# Get the release type from command line argument (positional defaults) -RELEASE_TYPE=${1:-"dev"} -VERSION_TYPE=${2:-"patch"} -PRERELEASE_ID=${3:-"dev"} - -# Interactive wizard (skipped with --yes) -if [[ $NON_INTERACTIVE -eq 0 ]]; then - echo "" - print_header "Interactive release wizard" - echo "This script will help you publish @avail-project/nexus-core." - echo "" - echo "Examples:" - echo " dev prerelease: 0.0.2-beta.0 -> 0.0.2-beta.1 ... -> 0.0.2-beta.9 -> 0.0.3-beta.0" - echo " prod release: 0.0.2 -> 0.0.3 (patch), 0.1.0 (minor), 1.0.0 (major)" - echo "" - read -p "Release type [dev|prod] (default: $RELEASE_TYPE): " _rt - if [[ -n "$_rt" ]]; then RELEASE_TYPE="$_rt"; fi - if [[ "$RELEASE_TYPE" != "dev" && "$RELEASE_TYPE" != "prod" ]]; then - print_error "Invalid release type. Use 'dev' or 'prod'" - exit 1 - fi - if [[ "$RELEASE_TYPE" == "dev" ]]; then - read -p "Pre-release tag (e.g. beta, alpha, dev) (default: $PRERELEASE_ID): " _pre - if [[ -n "$_pre" ]]; then PRERELEASE_ID="$_pre"; fi - echo "" - echo "Base bump is applied ONLY when starting a new $PRERELEASE_ID series." - echo "Examples: start at 0.0.2-$PRERELEASE_ID.0 (patch), or 0.1.0-$PRERELEASE_ID.0 (minor)." - read -p "Base bump for new series [patch|minor|major] (default: $VERSION_TYPE): " _vt - if [[ -n "$_vt" ]]; then VERSION_TYPE="$_vt"; fi - else - read -p "Version bump [patch|minor|major] (default: $VERSION_TYPE): " _vtp - if [[ -n "$_vtp" ]]; then VERSION_TYPE="$_vtp"; fi - fi -fi - -if [[ "$RELEASE_TYPE" != "dev" && "$RELEASE_TYPE" != "prod" ]]; then - print_error "Invalid release type. Use 'dev' or 'prod'" - echo "Usage: $0 [dev|prod] [patch|minor|major] [prerelease-id]" - echo "Examples:" - echo " $0 dev patch alpha # Creates 1.0.1-alpha.0" - echo " $0 dev minor beta # Creates 1.1.0-beta.0" - echo " $0 dev patch # Creates 1.0.1-dev.0 (default)" - exit 1 -fi - -if [[ "$VERSION_TYPE" != "patch" && "$VERSION_TYPE" != "minor" && "$VERSION_TYPE" != "major" ]]; then - print_error "Invalid version type. Use 'patch', 'minor', or 'major'" - echo "Usage: $0 [dev|prod] [patch|minor|major] [prerelease-id]" - exit 1 -fi - -print_header "Starting @avail-project/nexus-core $RELEASE_TYPE release ($VERSION_TYPE)..." - -# Run type checking -print_status "Running type check..." -pnpm run typecheck - -# Clean previous builds -print_status "Cleaning previous builds..." -pnpm run clean - -# Build commons (dependency) -print_status "Building commons package..." -pnpm run build:commons - -# Build core package -print_status "Building @avail-project/nexus-core package..." -pnpm run build:core - -if [[ "$RELEASE_TYPE" == "prod" ]]; then - print_header "Creating production release..." - - # Ensure we're on main branch for production releases - CURRENT_BRANCH=$(git branch --show-current) - if [[ "$CURRENT_BRANCH" != "main" ]]; then - print_warning "Not on main branch. Current branch: $CURRENT_BRANCH" - if [[ $NON_INTERACTIVE -eq 0 ]]; then - read -p "Do you want to continue with production release from this branch? (y/N): " confirm - if [[ $confirm != [yY] ]]; then - print_error "Aborting production release. Switch to main branch first." - exit 1 - fi - else - print_status "--yes provided. Continuing from $CURRENT_BRANCH." - fi - fi - - # Version bump - print_status "Bumping version ($VERSION_TYPE)..." - cd packages/core - npm version $VERSION_TYPE --no-git-tag-version - CORE_VERSION=$(node -p "require('./package.json').version") - cd ../.. - - # Update root package.json version to match - npm version $CORE_VERSION --no-git-tag-version --allow-same-version - - # Commit version changes (skip if no changes) - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: would git add/commit version bumps for v$CORE_VERSION" - else - git add packages/core/package.json package.json - if git diff --cached --quiet; then - print_status "No version changes to commit (prod)." - else - git commit -m "chore(core): release v$CORE_VERSION" - fi - # Create tag (only if it doesn't exist) - if git tag --list | grep -q "^core-v$CORE_VERSION$"; then - print_warning "Tag core-v$CORE_VERSION already exists, skipping tag creation" - else - git tag "core-v$CORE_VERSION" - fi - fi - - # Temporarily rename package for publishing - print_status "Preparing package for publishing as @avail-project/nexus-core..." - cd packages/core - - # Backup original package.json - cp package.json package.json.backup - - # Package already named @avail-project/nexus-core; no rename needed - - # Remove workspace-only dependencies that should be bundled (e.g., @nexus/commons) - node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));if(p.dependencies&&p.dependencies['@nexus/commons']){delete p.dependencies['@nexus/commons'];}fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n');" - - # Bundle internal commons into dist (imports already aliased by Rollup) - print_status "Bundling internal @nexus/commons into dist..." - mkdir -p dist/commons - cp -R ../commons/dist/* dist/commons/ - - # Publish to npm (or pack in dry-run) - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: npm pack (skipping publish) for @avail-project/nexus-core@$CORE_VERSION" - npm pack >/dev/null 2>&1 || true - else - print_status "Publishing @avail-project/nexus-core@$CORE_VERSION to npm..." - npm publish --access public - fi - - # Restore original package.json - mv package.json.backup package.json - cd ../.. - - # Push changes and tags - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: skipping git push of branch and tag core-v$CORE_VERSION" - else - print_status "Pushing changes to git..." - git push origin $CURRENT_BRANCH - git push origin "core-v$CORE_VERSION" - fi - - print_header "βœ… Production release completed!" - print_status "πŸš€ @avail-project/nexus-core@$CORE_VERSION published successfully!" - print_status "πŸ“¦ Install with: npm install @avail-project/nexus-core" - -else - print_header "Creating development release..." - - # Compute next prerelease version with 0-9 rollover by publication time - print_status "Computing next $PRERELEASE_ID version with rollover logic..." - cd packages/core - export PRERELEASE_ID - export VERSION_TYPE - export PKG='@avail-project/nexus-core' - PRERELEASE_VERSION=$(node -e ' -const cp=require("child_process"); -const fs=require("fs"); -const pkg=process.env.PKG; -const pre=process.env.PRERELEASE_ID||"dev"; -const bump=process.env.VERSION_TYPE||"patch"; -const current=JSON.parse(fs.readFileSync("package.json","utf8")).version; -function exec(cmd){try{return cp.execSync(cmd,{stdio:["pipe","pipe","ignore"]}).toString().trim();}catch(e){return "";}} -function parse(v){const m=v&&v.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+)\.(\d+))?$/);if(!m) return null;return {M:+m[1],m:+m[2],p:+m[3],pre:m[4],n:m[5]?+m[5]:null};} -function cmpBase(a,b){if(a.M!==b.M) return a.M-b.M; if(a.m!==b.m) return a.m-b.m; return a.p-b.p;} -function bumpBase(base,t){if(t==="major") return {M:base.M+1,m:0,p:0}; if(t==="minor") return {M:base.M,m:base.m+1,p:0}; return {M:base.M,m:base.m,p:base.p+1};} -function toStr(b,preid,idx){return `${b.M}.${b.m}.${b.p}-${preid}.${idx}`} -let timesJSON = exec(`npm view ${pkg} time --json`); -let times={};try{times=JSON.parse(timesJSON||"{}");}catch(_){times={};} -let preEntries=Object.entries(times).filter(([v])=>new RegExp(`^\\d+\\.\\d+\\.\\d+-${pre}\\.\\d+$`).test(v)); -preEntries.sort((a,b)=>new Date(a[1]) - new Date(b[1])); -let latestPre = preEntries.length? preEntries[preEntries.length-1][0] : ""; -let latestStable = exec(`npm view ${pkg} version 2>/dev/null`) || ""; -let next; -if(latestPre){ - const lp=parse(latestPre); - if(lp.n<9){ next=toStr({M:lp.M,m:lp.m,p:lp.p},pre,lp.n+1); } - else { next=toStr({M:lp.M,m:lp.m,p:lp.p+1},pre,0); } -}else{ - const curr=parse(current); - const stable=parse(latestStable)||curr; - let base = cmpBase(stable,curr) >= 0 ? stable : curr; - base = bumpBase(base,bump); - next = toStr(base,pre,0); -} -console.log(next); -') - export PRERELEASE_VERSION - npm version "$PRERELEASE_VERSION" --no-git-tag-version --allow-same-version - cd ../.. - - # Update root package.json to match - npm version "$PRERELEASE_VERSION" --no-git-tag-version --allow-same-version - - # Commit version changes (skip if no changes) - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: would git add/commit dev bump to v$PRERELEASE_VERSION and tag core-v$PRERELEASE_VERSION" - else - git add packages/core/package.json package.json - if git diff --cached --quiet; then - print_status "No version changes to commit (dev)." - else - git commit -m "chore(core): $PRERELEASE_ID release v$PRERELEASE_VERSION" - fi - # Create tag (only if it doesn't exist) - if git tag --list | grep -q "^core-v$PRERELEASE_VERSION$"; then - print_warning "Tag core-v$PRERELEASE_VERSION already exists, skipping tag creation" - else - git tag "core-v$PRERELEASE_VERSION" - fi - fi - - # Temporarily rename package for publishing - print_status "Preparing package for publishing as @avail-project/nexus-core..." - cd packages/core - - # Backup original package.json - cp package.json package.json.backup - - # Update package name for publishing - sed -i.tmp 's/"name": "@nexus\/core"/"name": "@avail-project\/nexus-core"/' package.json - rm package.json.tmp 2>/dev/null || true - - # Remove workspace-only dependencies that should be bundled (e.g., @nexus/commons) - node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));if(p.dependencies&&p.dependencies['@nexus/commons']){delete p.dependencies['@nexus/commons'];}fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n');" - - # Bundle internal commons into dist (imports already aliased by Rollup) - print_status "Bundling internal @nexus/commons into dist..." - mkdir -p dist/commons - cp -R ../commons/dist/* dist/commons/ - - # Publish to npm with prerelease tag (or pack in dry-run) - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: npm pack (skipping publish) for @avail-project/nexus-core@$PRERELEASE_VERSION" - npm pack >/dev/null 2>&1 || true - else - print_status "Publishing @avail-project/nexus-core@$PRERELEASE_VERSION to npm ($PRERELEASE_ID tag)..." - npm publish --access public --tag $PRERELEASE_ID - # Add incremental tag (e.g., alpha-1, beta-2) matching pre-release number - INCREMENTAL_TAG=$(node -e "const v=process.env.PRERELEASE_VERSION||'';const m=v.match(/$PRERELEASE_ID\\.(\\d+)/);console.log(m ? ('$PRERELEASE_ID-' + m[1]) : '$PRERELEASE_ID')") - if [ -n "$INCREMENTAL_TAG" ] && [ "$INCREMENTAL_TAG" != "$PRERELEASE_ID" ]; then - print_status "Adding dist-tag $INCREMENTAL_TAG for @avail-project/nexus-core@$PRERELEASE_VERSION..." - npm dist-tag add @avail-project/nexus-core@$PRERELEASE_VERSION $INCREMENTAL_TAG || true - fi - fi - - # Restore original package.json - mv package.json.backup package.json - cd ../.. - - # Push changes and tags - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: skipping git push of branch and tag core-v$PRERELEASE_VERSION" - else - print_status "Pushing changes to git..." - git push origin $(git branch --show-current) - git push origin "core-v$PRERELEASE_VERSION" - fi - - print_header "βœ… Development release completed!" - print_status "πŸš€ @avail-project/nexus-core@$PRERELEASE_VERSION published successfully!" - print_status "πŸ“¦ Install with: npm install @avail-project/nexus-core@$PRERELEASE_ID" -fi - -print_header "πŸŽ‰ @avail-project/nexus-core release process completed successfully!" diff --git a/scripts/release-widgets.sh b/scripts/release-widgets.sh deleted file mode 100755 index 4d044dcf..00000000 --- a/scripts/release-widgets.sh +++ /dev/null @@ -1,361 +0,0 @@ -#!/bin/bash - -# Nexus Widgets SDK Release Script -set -e - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -PURPLE='\033[0;35m' -NC='\033[0m' # No Color - -# Function to print colored output -print_status() { - echo -e "${GREEN}[INFO]${NC} $1" -} - -print_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -print_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -print_header() { - echo -e "${PURPLE}[WIDGETS RELEASE]${NC} $1" -} - -# Flags -NON_INTERACTIVE=0 -for arg in "$@"; do - if [[ "$arg" == "--yes" || "$arg" == "-y" || "$arg" == "--ci" ]]; then - NON_INTERACTIVE=1 - fi -done -DRY_RUN=0 -for arg in "$@"; do - if [[ "$arg" == "--dry-run" || "$arg" == "-n" ]]; then - DRY_RUN=1 - fi -done - -# Check if we're in a git repository -if ! git rev-parse --git-dir > /dev/null 2>&1; then - print_error "Not in a git repository" - exit 1 -fi - -# Check if we're in the root directory -if [[ ! -f "package.json" ]] || [[ ! -d "packages/widgets" ]]; then - print_error "Please run this script from the monorepo root directory" - exit 1 -fi - -# Check for uncommitted changes -if ! git diff-index --quiet HEAD --; then - print_error "There are uncommitted changes. Please commit or stash them first." - exit 1 -fi - -# Get the release type from command line argument (positional defaults) -RELEASE_TYPE=${1:-"dev"} -VERSION_TYPE=${2:-"patch"} -PRERELEASE_ID=${3:-"dev"} - -# Interactive wizard (skipped with --yes) -if [[ $NON_INTERACTIVE -eq 0 ]]; then - echo "" - print_header "Interactive release wizard" - echo "This will publish @avail-project/nexus-widgets." - echo "" - echo "Examples:" - echo " dev prerelease: 0.0.2-beta.0 -> 0.0.2-beta.1 ... -> 0.0.2-beta.9 -> 0.0.3-beta.0" - echo " prod release: 0.0.2 -> 0.0.3 (patch), 0.1.0 (minor), 1.0.0 (major)" - echo "" - read -p "Release type [dev|prod] (default: $RELEASE_TYPE): " _rt - if [[ -n "$_rt" ]]; then RELEASE_TYPE="$_rt"; fi - if [[ "$RELEASE_TYPE" != "dev" && "$RELEASE_TYPE" != "prod" ]]; then - print_error "Invalid release type. Use 'dev' or 'prod'" - exit 1 - fi - if [[ "$RELEASE_TYPE" == "dev" ]]; then - read -p "Pre-release tag (e.g. beta, alpha, dev) (default: $PRERELEASE_ID): " _pre - if [[ -n "$_pre" ]]; then PRERELEASE_ID="$_pre"; fi - echo "" - echo "Dev prereleases are ANCHORED to the latest stable version." - echo "New series begin at -$PRERELEASE_ID.0 and increment 0..9, then roll to next patch." - else - read -p "Version bump [patch|minor|major] (default: $VERSION_TYPE): " _vtp - if [[ -n "$_vtp" ]]; then VERSION_TYPE="$_vtp"; fi - fi -fi - -if [[ "$RELEASE_TYPE" != "dev" && "$RELEASE_TYPE" != "prod" ]]; then - print_error "Invalid release type. Use 'dev' or 'prod'" - echo "Usage: $0 [dev|prod] [patch|minor|major] [prerelease-id]" - echo "Examples:" - echo " $0 dev patch alpha # Creates 0.1.1-alpha.0" - echo " $0 dev minor beta # Creates 0.2.0-beta.0" - echo " $0 dev patch # Creates 0.1.1-dev.0 (default)" - exit 1 -fi - -if [[ "$VERSION_TYPE" != "patch" && "$VERSION_TYPE" != "minor" && "$VERSION_TYPE" != "major" ]]; then - print_error "Invalid version type. Use 'patch', 'minor', or 'major'" - echo "Usage: $0 [dev|prod] [patch|minor|major] [prerelease-id]" - exit 1 -fi - -print_header "Starting @avail-project/nexus-widgets $RELEASE_TYPE release ($VERSION_TYPE)..." - -# Run type checking -print_status "Running type check..." -pnpm run typecheck - -# Clean previous builds -print_status "Cleaning previous builds..." -pnpm run clean - -# Build dependencies and widgets package -print_status "Building dependencies and @avail-project/nexus-widgets package..." -pnpm run build:widgets - -if [[ "$RELEASE_TYPE" == "prod" ]]; then - print_header "Creating production release..." - - # Ensure we're on main branch for production releases - CURRENT_BRANCH=$(git branch --show-current) - if [[ "$CURRENT_BRANCH" != "main" ]]; then - print_warning "Not on main branch. Current branch: $CURRENT_BRANCH" - if [[ $NON_INTERACTIVE -eq 0 ]]; then - read -p "Do you want to continue with production release from this branch? (y/N): " confirm - if [[ $confirm != [yY] ]]; then - print_error "Aborting production release. Switch to main branch first." - exit 1 - fi - else - print_status "--yes provided. Continuing from $CURRENT_BRANCH." - fi - fi - - # Check if @avail-project/nexus-core is published and available - print_status "Checking @avail-project/nexus-core dependency..." - if ! npm view @avail-project/nexus-core > /dev/null 2>&1; then - print_error "@avail-project/nexus-core is not published. Please release core package first." - print_status "Run: ./scripts/release-core.sh prod" - exit 1 - fi - - # Fetch latest version from npm to ensure proper increment - print_status "Fetching latest version from npm..." - LATEST_WIDGETS_VERSION=$(npm view @avail-project/nexus-widgets version 2>/dev/null || echo "0.0.0") - print_status "Latest widgets version: $LATEST_WIDGETS_VERSION" - - # Version bump - print_status "Bumping version ($VERSION_TYPE)..." - cd packages/widgets - - # Set current version to latest version to ensure proper increment - npm version "$LATEST_WIDGETS_VERSION" --no-git-tag-version --allow-same-version - npm version $VERSION_TYPE --no-git-tag-version - - WIDGETS_VERSION=$(node -p "require('./package.json').version") - cd ../.. - - # Commit version changes - git add packages/widgets/package.json - git commit -m "chore(widgets): release v$WIDGETS_VERSION" - - # Create tag (remove existing if present, then create new) - if git tag -l | grep -q "^widgets-v$WIDGETS_VERSION$"; then - print_warning "Tag widgets-v$WIDGETS_VERSION already exists, removing it..." - git tag -d "widgets-v$WIDGETS_VERSION" - fi - git tag "widgets-v$WIDGETS_VERSION" - - # Temporarily rewrite package for publishing - print_status "Preparing package for publishing as @avail-project/nexus-widgets..." - cd packages/widgets - - # Backup original package.json - cp package.json package.json.backup - - # Resolve published core version (prod) - CORE_PUBLISHED_VERSION=$(npm view @avail-project/nexus-core version 2>/dev/null || true) - export CORE_PUBLISHED_VERSION - if [[ -z "$CORE_PUBLISHED_VERSION" ]]; then - print_error "@avail-project/nexus-core is not published or version could not be resolved. Release core first." - exit 1 - fi - - # Ensure deps pin @avail-project/nexus-core to published version; remove internal commons from deps - node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.dependencies=p.dependencies||{};if(p.dependencies['@nexus/commons']){delete p.dependencies['@nexus/commons'];}p.dependencies['@avail-project/nexus-core']=process.env.CORE_PUBLISHED_VERSION;fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n');" - - # Bundle internal commons into dist (imports already aliased by Rollup) - print_status "Bundling internal @nexus/commons into widgets dist..." - mkdir -p dist/commons - cp -R ../commons/dist/* dist/commons/ - - # Publish to npm (explicitly avoid marking as latest) - print_status "Publishing @avail-project/nexus-widgets@$WIDGETS_VERSION to npm (tag: legacy)..." - npm publish --access public --tag latest - - # Restore original package.json - mv package.json.backup package.json - cd ../.. - - # Push changes and tags - print_status "Pushing changes to git..." - git push origin $CURRENT_BRANCH - git push origin "widgets-v$WIDGETS_VERSION" - - print_header "βœ… Production release completed!" - print_status "πŸš€ @avail-project/nexus-widgets@$WIDGETS_VERSION published successfully!" - print_status "πŸ“¦ Install with: npm install @avail-project/nexus-widgets" - print_status "🎨 Includes React components for cross-chain transactions" - -else - print_header "Creating development release..." - - # Compute next prerelease version with 0-9 rollover by publication time - print_status "Computing next $PRERELEASE_ID version with rollover logic..." - cd packages/widgets - export PRERELEASE_ID - export PKG='@avail-project/nexus-widgets' - PRERELEASE_VERSION=$(node -e ' -const cp=require("child_process"); -const fs=require("fs"); -const pkg=process.env.PKG; -const pre=process.env.PRERELEASE_ID||"dev"; -const current=JSON.parse(fs.readFileSync("package.json","utf8")).version; -function exec(cmd){try{return cp.execSync(cmd,{stdio:["pipe","pipe","ignore"]}).toString().trim();}catch(e){return "";}} -function parse(v){const m=v&&v.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+)\.(\d+))?$/);if(!m) return null;return {M:+m[1],m:+m[2],p:+m[3],pre:m[4],n:m[5]?+m[5]:null};} -function toStr(b,preid,idx){return `${b.M}.${b.m}.${b.p}-${preid}.${idx}`} -let timesJSON = exec(`npm view ${pkg} time --json`); -let times={};try{times=JSON.parse(timesJSON||"{}");}catch(_){times={};} -// Determine base: prefer current prerelease channel if matching, otherwise use latest stable -let latestStable = exec(`npm view ${pkg} version 2>/dev/null`) || ""; -const parsedCurrent = parse(current); -let base = parse(latestStable) || (parsedCurrent ? {M:parsedCurrent.M,m:parsedCurrent.m,p:parsedCurrent.p} : {M:0,m:0,p:0}); -let next; -// Continue from current package version if already on requested prerelease channel -if(parsedCurrent && parsedCurrent.pre === pre){ - if(typeof parsedCurrent.n === "number" && parsedCurrent.n < 9){ - next = toStr({M:parsedCurrent.M,m:parsedCurrent.m,p:parsedCurrent.p}, pre, parsedCurrent.n + 1); - }else{ - // rollover to next patch from current base - next = toStr({M:parsedCurrent.M,m:parsedCurrent.m,p:parsedCurrent.p + 1}, pre, 0); - } -}else{ - // Find prereleases for THIS base only - let preEntries = Object.entries(times).filter(([v])=> new RegExp(`^${base.M}\\.${base.m}\\.${base.p}-${pre}\\.\\d+$`).test(v)); - preEntries.sort((a,b)=>new Date(a[1]) - new Date(b[1])); - if(preEntries.length){ - const lp = parse(preEntries[preEntries.length-1][0]); - if(lp && typeof lp.n === "number" && lp.n < 9){ - next = toStr({M:base.M,m:base.m,p:base.p}, pre, lp.n + 1); - }else{ - // rollover to next patch from base - next = toStr({M:base.M,m:base.m,p:base.p + 1}, pre, 0); - } - }else{ - next = toStr(base, pre, 0); - } -} -console.log(next); -') - export PRERELEASE_VERSION - npm version "$PRERELEASE_VERSION" --no-git-tag-version --allow-same-version - cd ../.. - - # Commit version changes - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: would git add/commit dev bump to v$PRERELEASE_VERSION and tag widgets-v$PRERELEASE_VERSION" - else - git add packages/widgets/package.json - git commit -m "chore(widgets): $PRERELEASE_ID release v$PRERELEASE_VERSION" || print_status "No version changes to commit (dev)." - fi - - # Create tag (remove existing if present, then create new) - if git tag -l | grep -q "^widgets-v$PRERELEASE_VERSION$"; then - print_warning "Tag widgets-v$PRERELEASE_VERSION already exists, removing it..." - git tag -d "widgets-v$PRERELEASE_VERSION" - fi - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: would create tag widgets-v$PRERELEASE_VERSION" - else - git tag "widgets-v$PRERELEASE_VERSION" - fi - - # Temporarily rewrite package for publishing - print_status "Preparing package for publishing as @avail-project/nexus-widgets..." - cd packages/widgets - - # Backup original package.json - cp package.json package.json.backup - - # Resolve latest published core version for the same prerelease tag by time (not semver) - export PRERELEASE_ID - CORE_PUBLISHED_VERSION=$(node -e ' -const cp=require("child_process"); -function exec(cmd){try{return cp.execSync(cmd,{stdio:["pipe","pipe","ignore"]}).toString().trim();}catch(e){return "";}} -const pre=process.env.PRERELEASE_ID||"dev"; -let times={}; -try{ times=JSON.parse(exec("npm view @avail-project/nexus-core time --json")||"{}"); }catch(_){ times={}; } -let entries=Object.entries(times).filter(([v])=>new RegExp(`^\\d+\\.\\d+\\.\\d+-${pre}\\.\\d+$`).test(v)); -entries.sort((a,b)=>new Date(a[1]) - new Date(b[1])); -let latest=entries.length? entries[entries.length-1][0] : ""; -process.stdout.write(latest); -') - export CORE_PUBLISHED_VERSION - if [[ -z "$CORE_PUBLISHED_VERSION" ]]; then - print_error "@avail-project/nexus-core@$PRERELEASE_ID is not published. Please release core $PRERELEASE_ID first (./scripts/release-core.sh dev patch $PRERELEASE_ID)." - exit 1 - fi - - # Ensure deps pin @avail-project/nexus-core to published dev version; remove internal commons from deps - node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync('package.json','utf8'));p.dependencies=p.dependencies||{};if(p.dependencies['@nexus/commons']){delete p.dependencies['@nexus/commons'];}p.dependencies['@avail-project/nexus-core']=process.env.CORE_PUBLISHED_VERSION;fs.writeFileSync('package.json',JSON.stringify(p,null,2)+'\n');" - - # Bundle internal commons into dist (imports already aliased by Rollup) - print_status "Bundling internal @nexus/commons into widgets dist..." - mkdir -p dist/commons - cp -R ../commons/dist/* dist/commons/ - - # Publish to npm with prerelease tag (or pack in dry-run) - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: npm pack (skipping publish) for @avail-project/nexus-widgets@$PRERELEASE_VERSION" - npm pack >/dev/null 2>&1 || true - else - print_status "Publishing @avail-project/nexus-widgets@$PRERELEASE_VERSION to npm ($PRERELEASE_ID tag)..." - npm publish --access public --tag $PRERELEASE_ID - # Add incremental tag (e.g., alpha-1, beta-2) matching pre-release number - INCREMENTAL_TAG=$(node -e "const v=process.env.PRERELEASE_VERSION||'';const m=v.match(/$PRERELEASE_ID\\.(\\d+)/);console.log(m ? ('$PRERELEASE_ID-' + m[1]) : '$PRERELEASE_ID')") - if [ -n "$INCREMENTAL_TAG" ] && [ "$INCREMENTAL_TAG" != "$PRERELEASE_ID" ]; then - print_status "Adding dist-tag $INCREMENTAL_TAG for @avail-project/nexus-widgets@$PRERELEASE_VERSION..." - npm dist-tag add @avail-project/nexus-widgets@$PRERELEASE_VERSION $INCREMENTAL_TAG || true - fi - fi - - # Restore original package.json - mv package.json.backup package.json - cd ../.. - - # Push changes and tags - if [[ $DRY_RUN -eq 1 ]]; then - print_status "DRY RUN: skipping git push of branch and tag widgets-v$PRERELEASE_VERSION" - else - print_status "Pushing changes to git..." - git push origin $(git branch --show-current) - git push origin "widgets-v$PRERELEASE_VERSION" - fi - - print_header "βœ… Development release completed!" - print_status "πŸš€ @avail-project/nexus-widgets@$PRERELEASE_VERSION published successfully!" - print_status "πŸ“¦ Install with: npm install @avail-project/nexus-widgets@$PRERELEASE_ID" - print_status "🎨 Includes React components for cross-chain transactions" -fi - -print_header "πŸŽ‰ @avail-project/nexus-widgets release process completed successfully!" diff --git a/src/_polyfill.ts b/src/_polyfill.ts new file mode 100644 index 00000000..ae60c385 --- /dev/null +++ b/src/_polyfill.ts @@ -0,0 +1,62 @@ +import { Buffer as _Buffer } from 'buffer'; + +// Ensure Buffer is on globalThis +if (!(globalThis as any).Buffer) { + (globalThis as any).Buffer = _Buffer; +} + +// Add polyfills for missing methods +const proto = _Buffer.prototype as any; +if (proto && typeof proto.writeUint32BE !== 'function') { + if (typeof proto.writeUInt32BE === 'function') { + // Alias the capital I versions + proto.writeUint32BE = proto.writeUInt32BE; + proto.writeUint32LE = proto.writeUInt32LE; + proto.readUint32BE = proto.readUInt32BE; + proto.readUint32LE = proto.readUInt32LE; + } else { + // Fallback implementations + proto.writeUint32BE = function (value: number, offset: number = 0) { + offset = offset >>> 0; + const normalized = Number(value) >>> 0; + (this as any)[offset] = (normalized >>> 24) & 0xff; + (this as any)[offset + 1] = (normalized >>> 16) & 0xff; + (this as any)[offset + 2] = (normalized >>> 8) & 0xff; + (this as any)[offset + 3] = normalized & 0xff; + return offset + 4; + }; + proto.writeUint32LE = function (value: number, offset: number = 0) { + offset = offset >>> 0; + const normalized = Number(value) >>> 0; + (this as any)[offset] = normalized & 0xff; + (this as any)[offset + 1] = (normalized >>> 8) & 0xff; + (this as any)[offset + 2] = (normalized >>> 16) & 0xff; + (this as any)[offset + 3] = (normalized >>> 24) & 0xff; + return offset + 4; + }; + proto.readUint32BE = function (offset: number = 0) { + offset = offset >>> 0; + return ( + ((this as any)[offset] * 0x1000000 + + (((this as any)[offset + 1] << 16) | + ((this as any)[offset + 2] << 8) | + (this as any)[offset + 3])) >>> + 0 + ); + }; + proto.readUint32LE = function (offset: number = 0) { + offset = offset >>> 0; + return ( + ((this as any)[offset] | + ((this as any)[offset + 1] << 8) | + ((this as any)[offset + 2] << 16) | + ((this as any)[offset + 3] * 0x1000000)) >>> + 0 + ); + }; + } +} + +if (!(globalThis as any).process) { + (globalThis as any).process = { env: { NODE_ENV: 'production' } }; +} diff --git a/packages/commons/constants/index.ts b/src/commons/constants/index.ts similarity index 87% rename from packages/commons/constants/index.ts rename to src/commons/constants/index.ts index 274b1504..3aef378b 100644 --- a/packages/commons/constants/index.ts +++ b/src/commons/constants/index.ts @@ -1,7 +1,6 @@ import { ChainMetadata, TokenMetadata } from '../types'; -export const SUPPORTED_CHAINS = { - // Mainnet chains +export const MAINNET_CHAIN_IDS = { ETHEREUM: 1, BASE: 8453, ARBITRUM: 42161, @@ -13,14 +12,24 @@ export const SUPPORTED_CHAINS = { KAIA: 8217, BNB: 56, HYPEREVM: 999, + // TRON: 728126428, + MONAD: 143, +} as const; - // Testnet chains +export const TESTNET_CHAIN_IDS = { SEPOLIA: 11155111, BASE_SEPOLIA: 84532, ARBITRUM_SEPOLIA: 421614, OPTIMISM_SEPOLIA: 11155420, POLYGON_AMOY: 80002, MONAD_TESTNET: 10143, + // TRON_SHASTA: 2494104990, + // VALIDIUM_TESTNET: 567, +} as const; + +export const SUPPORTED_CHAINS = { + ...MAINNET_CHAIN_IDS, + ...TESTNET_CHAIN_IDS, } as const; const BASE_TOKEN_METADATA = { @@ -28,7 +37,7 @@ const BASE_TOKEN_METADATA = { symbol: 'ETH', name: 'Ethereum', decimals: 18, - icon: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628', + icon: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png', coingeckoId: 'ethereum', isNative: true, }, @@ -43,7 +52,7 @@ const BASE_TOKEN_METADATA = { symbol: 'USDC', name: 'USD Coin', decimals: 6, - icon: 'https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694', + icon: 'https://coin-images.coingecko.com/coins/images/6319/large/usdc.png', coingeckoId: 'usd-coin', }, } as const; @@ -56,10 +65,15 @@ export const TESTNET_TOKEN_METADATA: Record = { USDC: { ...BASE_TOKEN_METADATA.USDC, name: 'Test USD Coin' }, } as const; +/** + * Chain metadata + * @returns Chain metadata + */ + export const CHAIN_METADATA: Record = { // Mainnet chains [SUPPORTED_CHAINS.ETHEREUM]: { - id: 1, + id: SUPPORTED_CHAINS.ETHEREUM, name: 'Ethereum', shortName: 'eth', logo: 'https://assets.coingecko.com/coins/images/279/small/ethereum.png', @@ -67,8 +81,17 @@ export const CHAIN_METADATA: Record = { rpcUrls: ['https://eth.merkle.io'], blockExplorerUrls: ['https://etherscan.io'], }, + [SUPPORTED_CHAINS.MONAD]: { + id: SUPPORTED_CHAINS.MONAD, + name: 'Monad', + shortName: 'monad', + logo: 'https://assets.coingecko.com/coins/images/38927/large/monad.jpg', + nativeCurrency: { name: 'Monad', symbol: 'MON', decimals: 18 }, + rpcUrls: ['https://rpcs.avail.so/monad'], + blockExplorerUrls: ['https://monadvision.com'], + }, [SUPPORTED_CHAINS.BASE]: { - id: 8453, + id: SUPPORTED_CHAINS.BASE, name: 'Base', shortName: 'base', logo: 'https://pbs.twimg.com/profile_images/1945608199500910592/rnk6ixxH_400x400.jpg', @@ -77,7 +100,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://basescan.org'], }, [SUPPORTED_CHAINS.ARBITRUM]: { - id: 42161, + id: SUPPORTED_CHAINS.ARBITRUM, name: 'Arbitrum One', shortName: 'arb1', logo: 'https://assets.coingecko.com/coins/images/16547/small/photo_2023-03-29_21.47.00.jpeg', @@ -89,7 +112,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://arbiscan.io'], }, [SUPPORTED_CHAINS.OPTIMISM]: { - id: 10, + id: SUPPORTED_CHAINS.OPTIMISM, name: 'Optimism', shortName: 'oeth', logo: 'https://assets.coingecko.com/coins/images/25244/small/Optimism.png', @@ -98,7 +121,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://optimistic.etherscan.io'], }, [SUPPORTED_CHAINS.POLYGON]: { - id: 137, + id: SUPPORTED_CHAINS.POLYGON, name: 'Polygon', shortName: 'matic', logo: 'https://assets.coingecko.com/coins/images/4713/small/polygon.png', @@ -107,7 +130,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://polygonscan.com'], }, [SUPPORTED_CHAINS.AVALANCHE]: { - id: 43114, + id: SUPPORTED_CHAINS.AVALANCHE, name: 'Avalanche', shortName: 'avax', logo: 'https://assets.coingecko.com/coins/images/12559/small/Avalanche_Circle_RedWhite_Trans.png', @@ -116,7 +139,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://snowtrace.io'], }, [SUPPORTED_CHAINS.SCROLL]: { - id: 534352, + id: SUPPORTED_CHAINS.SCROLL, name: 'Scroll', shortName: 'scroll', logo: 'https://assets.coingecko.com/coins/images/50571/standard/scroll.jpg?1728376125', @@ -125,7 +148,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://scrollscan.com'], }, [SUPPORTED_CHAINS.SOPHON]: { - id: 50104, + id: SUPPORTED_CHAINS.SOPHON, name: 'Sophon', shortName: 'sophon', logo: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png', @@ -134,7 +157,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://explorer.sophon.xyz'], }, [SUPPORTED_CHAINS.KAIA]: { - id: 8217, + id: SUPPORTED_CHAINS.KAIA, name: 'Kaia Mainnet', shortName: 'kaia', logo: 'https://assets.coingecko.com/asset_platforms/images/9672/large/kaia.png', @@ -143,7 +166,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://kaiascan.io'], }, [SUPPORTED_CHAINS.BNB]: { - id: 56, + id: SUPPORTED_CHAINS.BNB, name: 'BNB Smart Chain', shortName: 'bnb', logo: 'https://assets.coingecko.com/asset_platforms/images/1/large/bnb_smart_chain.png', @@ -163,7 +186,7 @@ export const CHAIN_METADATA: Record = { // Testnet chains [SUPPORTED_CHAINS.SEPOLIA]: { - id: 11155111, + id: SUPPORTED_CHAINS.SEPOLIA, name: 'Sepolia', shortName: 'sepolia', logo: 'https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png?1706606803', @@ -172,7 +195,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://sepolia.etherscan.io'], }, [SUPPORTED_CHAINS.BASE_SEPOLIA]: { - id: 84532, + id: SUPPORTED_CHAINS.BASE_SEPOLIA, name: 'Base Sepolia', shortName: 'base-sepolia', logo: 'https://pbs.twimg.com/profile_images/1945608199500910592/rnk6ixxH_400x400.jpg', @@ -181,7 +204,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://sepolia.basescan.org'], }, [SUPPORTED_CHAINS.MONAD_TESTNET]: { - id: 10143, + id: SUPPORTED_CHAINS.MONAD_TESTNET, name: 'Monad Testnet', shortName: 'monad-testnet', logo: 'https://assets.coingecko.com/coins/images/38927/standard/monad.jpg', @@ -190,7 +213,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://testnet.monadexplorer.com/'], }, [SUPPORTED_CHAINS.ARBITRUM_SEPOLIA]: { - id: 421614, + id: SUPPORTED_CHAINS.ARBITRUM_SEPOLIA, name: 'Arbitrum Sepolia', shortName: 'arb-sepolia', logo: 'https://assets.coingecko.com/coins/images/16547/small/photo_2023-03-29_21.47.00.jpeg', @@ -199,7 +222,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://sepolia.arbiscan.io'], }, [SUPPORTED_CHAINS.OPTIMISM_SEPOLIA]: { - id: 11155420, + id: SUPPORTED_CHAINS.OPTIMISM_SEPOLIA, name: 'Optimism Sepolia', shortName: 'op-sepolia', logo: 'https://assets.coingecko.com/coins/images/25244/small/Optimism.png', @@ -208,7 +231,7 @@ export const CHAIN_METADATA: Record = { blockExplorerUrls: ['https://sepolia-optimism.etherscan.io'], }, [SUPPORTED_CHAINS.POLYGON_AMOY]: { - id: 80002, + id: SUPPORTED_CHAINS.POLYGON_AMOY, name: 'Polygon Amoy', shortName: 'amoy', logo: 'https://assets.coingecko.com/coins/images/4713/small/polygon.png', @@ -218,17 +241,20 @@ export const CHAIN_METADATA: Record = { }, } as const; -// Event name constants to prevent typos +/** + * Event name constants to prevent typos + * @returns Event name constants + */ export const NEXUS_EVENTS = { - STEP_COMPLETE: 'step_complete', - EXPECTED_STEPS: 'expected_steps', - SWAP_STEPS: 'swap_step', - // Modular event names - BRIDGE_EXECUTE_EXPECTED_STEPS: 'bridge_execute_expected_steps', - BRIDGE_EXECUTE_COMPLETED_STEPS: 'bridge_execute_completed_steps', + STEP_COMPLETE: 'STEP_COMPLETE', + SWAP_STEP_COMPLETE: 'SWAP_STEP_COMPLETE', + STEPS_LIST: 'STEPS_LIST', } as const; -// Helper constants for mainnet and testnet chain categorization +/** + * Mainnet chains + * @returns Mainnet chains + */ export const MAINNET_CHAINS = [ SUPPORTED_CHAINS.ETHEREUM, SUPPORTED_CHAINS.BASE, @@ -241,8 +267,13 @@ export const MAINNET_CHAINS = [ SUPPORTED_CHAINS.KAIA, SUPPORTED_CHAINS.BNB, SUPPORTED_CHAINS.HYPEREVM, + // SUPPORTED_CHAINS.TRON, ] as const; +/** + * Testnet chains + * @returns Testnet chains + */ export const TESTNET_CHAINS = [ SUPPORTED_CHAINS.SEPOLIA, SUPPORTED_CHAINS.BASE_SEPOLIA, @@ -250,13 +281,14 @@ export const TESTNET_CHAINS = [ SUPPORTED_CHAINS.OPTIMISM_SEPOLIA, SUPPORTED_CHAINS.POLYGON_AMOY, SUPPORTED_CHAINS.MONAD_TESTNET, + // SUPPORTED_CHAINS.TRON_SHASTA, ] as const; /** * Token contract addresses per chain * This registry contains the contract addresses for supported tokens across different chains */ -export const TOKEN_CONTRACT_ADDRESSES: Record> = { +export const TOKEN_CONTRACT_ADDRESSES = { USDC: { [SUPPORTED_CHAINS.ETHEREUM]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', [SUPPORTED_CHAINS.BASE]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', @@ -267,14 +299,17 @@ export const TOKEN_CONTRACT_ADDRESSES: Record + ({ + type: 'INTENT_SUBMITTED', + typeID: 'IS', + data: { + explorerURL, + intentID, + }, + }) as const; + +const INTENT_FULFILLED = { + type: 'INTENT_FULFILLED', + typeID: 'IF', +} as const; + +const ALLOWANCE_APPROVAL_REQUEST = (chain: { id: number; name?: string }) => + ({ + type: 'ALLOWANCE_USER_APPROVAL', + typeID: `AUA_${chain.id}`, + data: { + chainID: chain.id, + chainName: chain.name, + }, + }) as const; + +const ALLOWANCE_APPROVAL_MINED = (chain: { id: number; name?: string }) => + ({ + type: 'ALLOWANCE_APPROVAL_MINED', + typeID: `AAM_${chain.id}`, + data: { + chainID: chain.id, + chainName: chain.name, + }, + }) as const; + +const ALLOWANCE_COMPLETE = { + type: 'ALLOWANCE_ALL_DONE', + typeID: 'AAD', +} as const; + +const INTENT_DEPOSIT_REQUEST = ( + id: number, + amount: Decimal, + chain: { id: number; name?: string }, +) => + ({ + type: 'INTENT_DEPOSIT', + typeID: `ID_${id}`, + data: { + amount: amount.toFixed(), + chainID: chain.id, + chainName: chain.name, + }, + }) as const; + +const INTENT_DEPOSITS_CONFIRMED = { + type: 'INTENT_DEPOSITS_CONFIRMED', + typeID: 'UIDC', +} as const; + +const INTENT_COLLECTION_COMPLETE = { + type: 'INTENT_COLLECTION_COMPLETE', + typeID: 'ICC', +} as const; + +const INTENT_COLLECTION = (id: number, total: number) => + ({ + type: 'INTENT_COLLECTION', + typeID: `IC_${id}`, + data: { + confirmed: id, + total, + }, + }) as const; + +const EXECUTE_APPROVAL_STEP = { + type: 'APPROVAL', + typeID: 'AP', +} as const; + +const EXECUTE_TRANSACTION_SENT = { + type: 'TRANSACTION_SENT', + typeID: 'TS', +} as const; + +const EXECUTE_TRANSACTION_CONFIRMED = { + type: 'TRANSACTION_CONFIRMED', + typeID: 'CN', +} as const; + +const BRIDGE_STEPS = { + INTENT_ACCEPTED, + ALLOWANCE_APPROVAL_REQUEST, + ALLOWANCE_APPROVAL_MINED, + ALLOWANCE_COMPLETE, + INTENT_COLLECTION, + INTENT_HASH_SIGNED, + INTENT_COLLECTION_COMPLETE, + INTENT_DEPOSITS_CONFIRMED, + INTENT_DEPOSIT_REQUEST, + INTENT_FULFILLED, + INTENT_SUBMITTED, + EXECUTE_APPROVAL_STEP, + EXECUTE_TRANSACTION_CONFIRMED, + EXECUTE_TRANSACTION_SENT, +}; + +type BridgeStepType = + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | typeof INTENT_ACCEPTED + | typeof INTENT_HASH_SIGNED + | typeof INTENT_DEPOSITS_CONFIRMED + | typeof INTENT_COLLECTION_COMPLETE + | typeof INTENT_FULFILLED + | typeof ALLOWANCE_COMPLETE + | typeof EXECUTE_APPROVAL_STEP + | typeof EXECUTE_TRANSACTION_CONFIRMED + | typeof EXECUTE_TRANSACTION_SENT; + +export { BridgeStepType, BRIDGE_STEPS }; diff --git a/src/commons/types/contract-types.ts b/src/commons/types/contract-types.ts new file mode 100644 index 00000000..bba798ea --- /dev/null +++ b/src/commons/types/contract-types.ts @@ -0,0 +1,14 @@ +import { Hex } from 'viem'; + +export type GetAllowanceParams = { + contractAddress: Hex; + spender: Hex; + owner: Hex; +}; + +export type SetAllowanceParams = { + contractAddress: Hex; + spender: Hex; + owner: Hex; + amount: bigint; +}; diff --git a/packages/commons/types/index.ts b/src/commons/types/index.ts similarity index 68% rename from packages/commons/types/index.ts rename to src/commons/types/index.ts index 9bc2c771..46550a23 100644 --- a/packages/commons/types/index.ts +++ b/src/commons/types/index.ts @@ -1,22 +1,23 @@ import { SUPPORTED_CHAINS } from '../constants'; -import { Abi, TransactionReceipt, ByteArray, Hex, WalletClient } from 'viem'; -import { ChainDatum, Environment, PermitVariant, Universe } from '@arcana/ca-common'; -import * as ServiceTypes from './service-types'; +import { TransactionReceipt, ByteArray, Hex, WalletClient } from 'viem'; +import { ChainDatum, Environment, PermitVariant, Universe } from '@avail-project/ca-common'; import Decimal from 'decimal.js'; import { SwapIntent } from './swap-types'; -import { FuelConnector, Provider, TransactionRequestLike } from 'fuels'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { AdapterProps } from '@tronweb3/tronwallet-abstract-adapter'; +import { SwapStepType } from './swap-steps'; +import { BridgeStepType } from './bridge-steps'; +import { FormatTokenBalanceOptions, FormattedParts } from '../utils/format'; +import { SigningStargateClient } from '@cosmjs/stargate'; type TokenInfo = { contractAddress: `0x${string}`; decimals: number; - logo?: string; + logo: string; name: string; - platform?: string; symbol: string; }; -type NexusNetwork = 'mainnet' | 'testnet'; +type NexusNetwork = 'mainnet' | 'canary' | 'testnet' | NetworkConfig; export interface BlockTransaction { hash?: string; @@ -83,9 +84,9 @@ export type SUPPORTED_CHAINS_IDS = (typeof SUPPORTED_CHAINS)[keyof typeof SUPPOR * This allows for dynamic parameter generation based on actual bridged amounts and user context */ export type DynamicParamBuilder = ( - token: SUPPORTED_TOKENS, + token: string, amount: string, - chainId: SUPPORTED_CHAINS_IDS, + chainId: number, userAddress: `0x${string}`, ) => { functionParams: readonly unknown[]; @@ -97,50 +98,52 @@ export type DynamicParamBuilder = ( * Parameters for bridging tokens between chains. */ export interface BridgeParams { - token: SUPPORTED_TOKENS; - amount: number | string; - chainId: SUPPORTED_CHAINS_IDS; + recipient?: Hex; + token: string; + amount: bigint; + toChainId: number; gas?: bigint; sourceChains?: number[]; } +export type BridgeMaxResult = { + amountRaw: bigint; + amount: string; + symbol: string; + sourceChainIds: number[]; +}; + /** * Result structure for bridge transactions. */ -export type BridgeResult = - | { success: false; error: string } - | { - success: true; - explorerUrl: string; - transactionHash?: string; - }; +export type BridgeResult = { + explorerUrl: string; +}; /** * Result structure for transfer transactions. */ -export type TransferResult = - | { - success: true; - transactionHash: string; - explorerUrl: string; - } - | { - success: false; - error: string; - }; +export type TransferResult = { + transactionHash: string; + explorerUrl: string; +}; export interface SimulationResult { intent: ReadableIntent; token: TokenInfo; } +export type TronAdapter = AdapterProps & { + isMobile?: boolean; +}; + /** * Parameters for transferring tokens. */ export interface TransferParams { - token: SUPPORTED_TOKENS; - amount: number | string; - chainId: SUPPORTED_CHAINS_IDS; + token: string; + amount: bigint; + toChainId: number; recipient: `0x${string}`; sourceChains?: number[]; } @@ -160,12 +163,12 @@ export interface TokenBalance { // Enhanced modular parameters for execute functionality with dynamic parameter building export interface ExecuteParams { - toChainId: SUPPORTED_CHAINS_IDS; - contractAddress: string; - contractAbi: Abi; - functionName: string; - buildFunctionParams: DynamicParamBuilder; - value?: string; // Can be overridden by callback + toChainId: number; + to: Hex; + value?: bigint; + data?: Hex; + gas?: bigint; + gasPrice?: bigint; enableTransactionPolling?: boolean; transactionTimeout?: number; // Transaction receipt confirmation options @@ -173,14 +176,10 @@ export interface ExecuteParams { receiptTimeout?: number; requiredConfirmations?: number; tokenApproval?: { - token: SUPPORTED_TOKENS; - amount: string; + token: string; + amount: bigint; + spender: Hex; }; - /** - * Optional approval buffer in basis points (bps). Defaults to 100 (1%). - * Use 0 to disable buffer (e.g., for bridge+execute flows where exact balances are used). - */ - approvalBufferBps?: number; } export interface ExecuteResult { @@ -195,14 +194,14 @@ export interface ExecuteResult { approvalTransactionHash?: string; } -export interface ExecuteSimulation { - contractAddress: string; - functionName: string; - gasUsed: string; - success: boolean; - error?: string; - gasCostEth?: string; -} +export type ExecuteSimulation = { + gasUsed: bigint; + gasPrice: bigint; + /** + * gasFee = gasUsed * gasPrice + */ + gasFee: bigint; +}; // New types for improved approval simulation export interface ApprovalInfo { @@ -231,101 +230,63 @@ export interface SimulationStep { description: string; } -interface SimulationMetadata { - contractAddress: string; - functionName: string; - bridgeReceiveAmount: string; - bridgeFee: string; - inputAmount: string; - optimalBridgeAmount?: string; - targetChain: number; - approvalRequired: boolean; - bridgeSkipped?: boolean; - token?: SUPPORTED_TOKENS; -} +export type EventListenerType = { + onEvent: (eventName: string, ...args: any[]) => void; +}; -export interface BridgeAndExecuteSimulationResult { - steps: SimulationStep[]; +export type BridgeAndExecuteSimulationResult = { bridgeSimulation: SimulationResult | null; - executeSimulation?: ExecuteSimulation; - totalEstimatedCost?: { - total: string; - breakdown: { - bridge: string; - execute: string; - }; - }; - success: boolean; - error?: string; - metadata?: SimulationMetadata; -} + executeSimulation: ExecuteSimulation; +}; export interface BridgeAndExecuteParams { - toChainId: SUPPORTED_CHAINS_IDS; - token: SUPPORTED_TOKENS; - amount: number | string; - recipient?: `0x${string}`; + toChainId: number; + token: string; + amount: bigint; sourceChains?: number[]; - execute?: Omit; + execute: Omit; enableTransactionPolling?: boolean; transactionTimeout?: number; - // Global options for transaction confirmation waitForReceipt?: boolean; receiptTimeout?: number; requiredConfirmations?: number; - // Optional recent approval transaction hash to consider in simulation recentApprovalTxHash?: string; } -export interface BridgeAndExecuteResult { - executeTransactionHash?: string; - executeExplorerUrl?: string; +export type CosmosOptions = { + address: string; + client: SigningStargateClient; +}; + +export type IBridgeOptions = { + cosmos: CosmosOptions; + evm: { + address: `0x${string}`; + client: WalletClient; + provider: EthereumProvider; + }; + tron?: { + address: string; + adapter: TronAdapter; + }; + hooks: { + onAllowance: OnAllowanceHook; + onIntent: OnIntentHook; + }; + emit?: OnEventParam['onEvent']; + networkConfig: NetworkConfig; + chainList: ChainListType; +}; + +export type BridgeAndExecuteResult = { + executeTransactionHash: string; + executeExplorerUrl: string; approvalTransactionHash?: string; - bridgeTransactionHash?: string; // undefined when bridge is skipped bridgeExplorerUrl?: string; // undefined when bridge is skipped toChainId: number; - success: boolean; - error?: string; bridgeSkipped: boolean; // indicates if bridge was skipped due to sufficient funds -} - -/** - * Smart contract call parameters - */ -export interface ContractCallParams { - to: `0x${string}`; - data: `0x${string}`; - value?: bigint; - gas?: bigint; - gasPrice?: bigint; -} - -export type BridgeQueryInput = { - amount: number | string; - chainId: number; - gas?: bigint; - sourceChains?: number[]; - token: string; }; -export interface CA { - createEVMHandler( - tx: EVMTransaction, - options: Partial, - ): Promise; - - createFuelHandler( - tx: TransactionRequestLike, - options: Partial, - ): Promise; - - getChainID(): Promise; - - init(): Promise; - - switchChain(chainID: number): Promise; -} - export type Chain = { blockExplorers?: { default: { @@ -347,6 +308,7 @@ export type Chain = { }; rpcUrls: { default: { + grpc?: string[]; http: string[]; publicHttp?: string[]; webSocket: string[]; @@ -355,11 +317,6 @@ export type Chain = { universe: Universe; }; -export interface CreateHandlerResponse { - handler: IRequestHandler | null; - processTx: () => Promise; -} - interface EthereumProvider { on(eventName: string | symbol, listener: (...args: any[]) => void): this; @@ -407,8 +364,6 @@ export type FeeStoreData = { }[]; }; -export type FeeUniverse = 'ETHEREUM' | 'FUEL'; - export type Intent = { allSources: IntentSource[]; destination: IntentDestination; @@ -420,6 +375,7 @@ export type Intent = { protocol: string; solver: string; }; + recipientAddress: Hex; isAvailableBalanceInsufficient: boolean; sources: IntentSource[]; }; @@ -437,6 +393,7 @@ export type IntentSource = { chainID: number; tokenContract: `0x${string}`; universe: Universe; + holderAddress: Hex; }; export type IntentSourceForAllowance = { @@ -446,26 +403,13 @@ export type IntentSourceForAllowance = { token: TokenInfo; }; -export interface IRequestHandler { - buildIntent(sourceChains: number[]): Promise< - | { - intent: Intent; - token: TokenInfo; - } - | undefined - >; - process(): Promise<{ explorerURL: string } | undefined>; -} - type Network = Extract; export type NetworkConfig = { COSMOS_URL: string; EXPLORER_URL: string; - FAUCET_URL: string; GRPC_URL: string; NETWORK_HINT: Environment; - SIMULATION_URL: string; VSC_DOMAIN: string; }; @@ -489,7 +433,9 @@ type OnAllowanceHook = (data: OnAllowanceHookData) => void; export type onAllowanceHookSource = { allowance: { current: string; + currentRaw: bigint; minimum: string; + minimumRaw: bigint; }; chain: { id: number; @@ -556,77 +502,66 @@ type RequestArguments = { readonly params?: object | readonly unknown[]; }; -export type RequestHandler = new (i: RequestHandlerInput) => IRequestHandler; - export type ChainListType = { chains: Chain[]; getVaultContractAddress(chainID: number): `0x${string}`; getTokenInfoBySymbol(chainID: number, symbol: string): TokenInfo | undefined; + getChainAndTokenFromSymbol( + chainID: number, + tokenSymbol: string, + ): { + chain: Chain; + token: (TokenInfo & { isNative: boolean }) | undefined; + }; getTokenByAddress(chainID: number, address: `0x${string}`): TokenInfo | undefined; + getChainAndTokenByAddress( + chainID: number, + address: `0x${string}`, + ): + | { + chain: Chain; + token: TokenInfo | undefined; + } + | undefined; getNativeToken(chainID: number): TokenInfo; getChainByID(id: number): Chain | undefined; getAnkrNameList(): string[]; }; -export type RequestHandlerInput = { - chain: Chain; - chainList: ChainListType; - cosmosWallet: DirectSecp256k1Wallet; - evm: { - address: `0x${string}`; - client: WalletClient; - tx?: EVMTransaction; - }; - fuel?: { - address: string; - connector: FuelConnector; - provider: Provider; - tx?: TransactionRequestLike; - }; - hooks: { - onAllowance: OnAllowanceHook; - onIntent: OnIntentHook; - }; - options: { - emit: (eventName: string, ...args: any[]) => void; - networkConfig: NetworkConfig; - } & TxOptions; -}; +type EventUnion = + | { name: 'STEPS_LIST'; args: BridgeStepType[] } + | { name: 'SWAP_STEP_COMPLETE'; args: SwapStepType } + | { name: 'STEP_COMPLETE'; args: BridgeStepType }; -export type RequestHandlerResponse = { - buildIntent(): Promise< - | { - intent: Intent; - token: TokenInfo; - } - | undefined - >; - input: RequestHandlerInput; - process(): Promise; -} | null; +export type OnEventParam = { + onEvent?: (event: EventUnion) => void; +}; export type RFF = { + explorerUrl: string; deposited: boolean; - destinationChainID: number; - destinations: { tokenAddress: Hex; value: bigint }[]; - destinationUniverse: string; + destinationChain: { id: number; name: string; logo: string; universe: string }; + destinations: { + token: { address: Hex; symbol: string; decimals: number }; + value: string; + valueRaw: bigint; + }[]; expiry: number; fulfilled: boolean; id: number; refunded: boolean; sources: { - chainID: number; - tokenAddress: Hex; - universe: string; - value: bigint; + chain: { id: number; name: string; logo: string; universe: string }; + valueRaw: bigint; + value: string; + token: { + address: Hex; + symbol: string; + decimals: number; + }; }[]; }; -type SDKConfig = { - debug?: boolean; - network?: Network | NetworkConfig; -}; - type SetAllowanceInput = { amount: bigint; chainID: number; @@ -673,28 +608,6 @@ export type SponsoredApprovalData = { export type SponsoredApprovalDataArray = SponsoredApprovalData[]; -export type Step = { - data?: - | { - amount: string; - chainName: string; - symbol: string; - } - | { - chainID: number; - chainName: string; - } - | { confirmed: number; total: number } - | { explorerURL: string; intentID: number }; -} & StepInfo; - -export type StepInfo = { - type: string; - typeID: string; -}; - -export type Steps = Step[]; - export type Token = { contractAddress: `0x${string}`; decimals: number; @@ -702,17 +615,6 @@ export type Token = { symbol: string; }; -export type TransferQueryInput = { - to: Hex; -} & Omit; - -export type TxOptions = { - bridge: boolean; - gas: bigint; - skipTx: boolean; - sourceChains: number[]; -}; - export type UnifiedBalanceResponseData = { chain_id: Uint8Array; currencies: { @@ -722,6 +624,7 @@ export type UnifiedBalanceResponseData = { }[]; total_usd: string; universe: Universe; + errored: boolean; }; export type UserAssetDatum = { @@ -746,24 +649,26 @@ export type UserAssetDatum = { symbol: string; }; +export type BeforeExecuteHook = { + beforeExecute?: () => Promise<{ value?: bigint; data?: Hex; gas?: bigint }>; +}; + export type { - ServiceTypes, OnIntentHook, OnAllowanceHookData, OnIntentHookData, OnAllowanceHook, EthereumProvider, RequestArguments, - Step as ProgressStep, - Steps as ProgressSteps, onAllowanceHookSource as AllowanceHookSource, Network, UserAssetDatum as UserAsset, TokenInfo, RFF as RequestForFunds, - SDKConfig, NexusNetwork, TransactionReceipt, SwapIntent, SetAllowanceInput, + FormatTokenBalanceOptions, + FormattedParts, }; diff --git a/packages/commons/types/integration-types.ts b/src/commons/types/integration-types.ts similarity index 100% rename from packages/commons/types/integration-types.ts rename to src/commons/types/integration-types.ts diff --git a/packages/core/sdk/ca-base/swap/steps.ts b/src/commons/types/swap-steps.ts similarity index 50% rename from packages/core/sdk/ca-base/swap/steps.ts rename to src/commons/types/swap-steps.ts index 84b63824..dfd3a303 100644 --- a/packages/core/sdk/ca-base/swap/steps.ts +++ b/src/commons/types/swap-steps.ts @@ -1,33 +1,25 @@ +import { ChainListType } from '.'; import { Hex } from 'viem'; -import { Chain } from '@nexus/commons'; -import { ChainListType } from '@nexus/commons'; +import { Errors } from '../../sdk/ca-base/errors'; -export type SwapStep = - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | ReturnType - | typeof SWAP_COMPLETE - | typeof SWAP_START; - -export const SWAP_START = { +const SWAP_START = { completed: true, type: 'SWAP_START', typeID: 'SWAP_START', } as const; -export const DETERMINING_SWAP = (completed: boolean = false) => +const DETERMINING_SWAP = (completed: boolean = false) => ({ completed, type: 'DETERMINING_SWAP', typeID: DETERMINING_SWAP, }) as const; -export const CREATE_PERMIT_EOA_TO_EPHEMERAL = (completed: boolean, symbol: string, chain: Chain) => +const CREATE_PERMIT_EOA_TO_EPHEMERAL = ( + completed: boolean, + symbol: string, + chain: { id: number; name?: string }, +) => ({ chain: { id: chain.id, @@ -39,7 +31,11 @@ export const CREATE_PERMIT_EOA_TO_EPHEMERAL = (completed: boolean, symbol: strin typeID: `CREATE_PERMIT_EOA_TO_EPHEMERAL_${chain.id}_${symbol}`, }) as const; -export const CREATE_PERMIT_FOR_SOURCE_SWAP = (completed: boolean, symbol: string, chain: Chain) => +const CREATE_PERMIT_FOR_SOURCE_SWAP = ( + completed: boolean, + symbol: string, + chain: { id: number; name?: string }, +) => ({ chain: { id: chain.id, @@ -51,18 +47,18 @@ export const CREATE_PERMIT_FOR_SOURCE_SWAP = (completed: boolean, symbol: string typeID: `CREATE_PERMIT_FOR_SOURCE_SWAP_${chain.id}_${symbol}`, }) as const; -export const SOURCE_SWAP_BATCH_TX = (completed: boolean) => +const SOURCE_SWAP_BATCH_TX = (completed: boolean) => ({ completed, type: 'SOURCE_SWAP_BATCH_TX', typeID: 'SOURCE_SWAP_BATCH_TX', }) as const; -export const SOURCE_SWAP_HASH = (ops: [bigint, Hex], chainList: ChainListType) => { +const SOURCE_SWAP_HASH = (ops: [bigint, Hex], chainList: ChainListType) => { const chainID = ops[0]; const chain = chainList.getChainByID(Number(ops[0])); if (!chain) { - throw new Error(`Unknown chain: ${ops[0]}`); + throw Errors.chainNotFound(chainID); } return { @@ -77,7 +73,7 @@ export const SOURCE_SWAP_HASH = (ops: [bigint, Hex], chainList: ChainListType) = } as const; }; -export const RFF_ID = (id: number) => +const RFF_ID = (id: number) => ({ completed: true, data: id, @@ -85,23 +81,24 @@ export const RFF_ID = (id: number) => typeID: 'RFF_ID', }) as const; -export const DESTINATION_SWAP_BATCH_TX = (completed: boolean) => +const DESTINATION_SWAP_BATCH_TX = (completed: boolean) => ({ completed, type: 'DESTINATION_SWAP_BATCH_TX', typeID: 'DESTINATION_SWAP_BATCH_TX', }) as const; -export const SWAP_COMPLETE = { +const SWAP_COMPLETE = { completed: true, type: 'SWAP_COMPLETE', typeID: 'SWAP_COMPLETE', } as const; -export const DESTINATION_SWAP_HASH = (op: [bigint, Hex], chainList: ChainListType) => { - const chain = chainList.getChainByID(Number(op[0])); +const DESTINATION_SWAP_HASH = (op: [bigint, Hex], chainList: ChainListType) => { + const chainID = Number(op[0]) + const chain = chainList.getChainByID(chainID); if (!chain) { - throw new Error(`Unknown chain: ${op[0]}`); + throw Errors.chainNotFound(chainID); } return { chain: { @@ -114,3 +111,28 @@ export const DESTINATION_SWAP_HASH = (op: [bigint, Hex], chainList: ChainListTyp typeID: `DESTINATION_SWAP_HASH_${chain.id}`, } as const; }; + +export const SWAP_STEPS = { + SWAP_START, + CREATE_PERMIT_EOA_TO_EPHEMERAL, + CREATE_PERMIT_FOR_SOURCE_SWAP, + DESTINATION_SWAP_BATCH_TX, + DESTINATION_SWAP_HASH, + DETERMINING_SWAP, + RFF_ID, + SOURCE_SWAP_BATCH_TX, + SOURCE_SWAP_HASH, + SWAP_COMPLETE, +}; + +export type SwapStepType = + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | typeof SWAP_STEPS.SWAP_COMPLETE + | typeof SWAP_STEPS.SWAP_START; diff --git a/packages/commons/types/swap-types.ts b/src/commons/types/swap-types.ts similarity index 92% rename from packages/commons/types/swap-types.ts rename to src/commons/types/swap-types.ts index ff74a022..3a2d940d 100644 --- a/packages/commons/types/swap-types.ts +++ b/src/commons/types/swap-types.ts @@ -1,9 +1,10 @@ -import { Universe } from '@arcana/ca-common'; +import { Universe } from '@avail-project/ca-common'; import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; import Decimal from 'decimal.js'; import { type Hex, PrivateKeyAccount, WalletClient } from 'viem'; -import { NetworkConfig, TokenInfo, ChainListType } from '../index'; +import { NetworkConfig, ChainListType, OnEventParam, TokenInfo } from '../index'; +import { SigningStargateClient } from '@cosmjs/stargate'; export type AuthorizationList = { address: Uint8Array; @@ -110,15 +111,17 @@ export type SwapIntent = { }[]; }; -export type SwapIntentHook = (data: { +export type OnSwapIntentHookData = { allow: () => void; deny: () => void; intent: SwapIntent; refresh: () => Promise; -}) => unknown; +}; + +export type OnSwapIntentHook = (data: OnSwapIntentHookData) => unknown; export type SwapParams = { - emit: (stepID: string, step: unknown) => void; + onSwapIntent: OnSwapIntentHook; chainList: ChainListType; address: { cosmos: string; @@ -126,19 +129,15 @@ export type SwapParams = { ephemeral: Hex; }; wallet: { - cosmos: DirectSecp256k1Wallet; + cosmos: SigningStargateClient; ephemeral: PrivateKeyAccount; eoa: WalletClient; }; networkConfig: NetworkConfig; -} & SwapInputOptionalParams; - -export type SwapInputOptionalParams = { - swapIntentHook?: SwapIntentHook; -}; +} & OnEventParam; export interface ExactInSwapInput { - from?: { + from: { chainId: number; amount: bigint; tokenAddress: Hex; @@ -269,6 +268,12 @@ export type SupportedChainsResult = { id: number; logo: string; name: string; +}[]; + +export type SupportedChainsAndTokensResult = { + id: number; + logo: string; + name: string; tokens: TokenInfo[]; }[]; @@ -276,6 +281,7 @@ export type Tx = { data: Hex; to: Hex; value: bigint; + gas?: bigint; }; // export type UserAsset = { diff --git a/src/commons/utils/format.ts b/src/commons/utils/format.ts new file mode 100644 index 00000000..1c64976f --- /dev/null +++ b/src/commons/utils/format.ts @@ -0,0 +1,350 @@ +/** + * Token balance formatter with zero-compression for tiny values. + * + * Examples: + * - 1.234567 -> "1.2346" + * - 0.00008509 -> "~0.0β‚„8509" (shows 4 leading zeros after decimal) + * - 0.000000000000000123 -> "~0.0₁₅123" + * + * Notes: + * - Uses Unicode subscript digits for the zero count. + * - Returns a single string optimized for UI display. + * - If you need richer rendering (e.g., separate parts to style the subscript), + * use `formatTokenBalanceParts` which returns structured parts. + */ + +import { formatUnits, isAddress } from 'viem'; + +/** + * Truncate an address for display purposes + */ +export function truncateAddress( + address: string, + startLength: number = 6, + endLength: number = 4, +): string { + if (!isAddress(address)) return address; + + if (address.length <= startLength + endLength + 2) return address; + + return `${address.slice(0, startLength)}...${address.slice(-endLength)}`; +} + +export interface FormatTokenBalanceOptions { + decimals?: number; // when value is base units (bigint) + symbol?: string; // e.g., "ETH" + significantDigits?: number; // digits after the first non-zero in tiny values + maxFractionDigits?: number; // for "normal" values + tinyThresholdPower?: number; // threshold exponent, default -4 => 10^-4 + zeroCompress?: boolean; // show 0.0β‚– for small values + thousandSeparator?: boolean; // e.g., 12,345.67 + trimTrailingZeros?: boolean; // remove trailing 0s + approxTilde?: boolean; // prefix "~" when rounding/truncating +} + +export interface FormattedParts { + text: string; // final string + approx: boolean; + integer: string; // "0" or "12" + zeroCount?: number; // number of zeros after decimal for tiny values + zeroSubscript?: string; // "₆" + significant?: string; // e.g., "8509" + fraction?: string; // normal fraction for non-tiny values + symbol?: string; +} + +const SUBSCRIPT_DIGITS: Record = { + '0': 'β‚€', + '1': '₁', + '2': 'β‚‚', + '3': '₃', + '4': 'β‚„', + '5': 'β‚…', + '6': '₆', + '7': '₇', + '8': 'β‚ˆ', + '9': '₉', +}; + +function toSubscript(num: number): string { + return String(num) + .split('') + .map((d) => SUBSCRIPT_DIGITS[d] ?? d) + .join(''); +} + +function expandExponential(n: string): string { + // Handles strings like "1e-7" or "1.23e+5" into normal decimal strings + if (!/e/i.test(n)) return n; + const isNeg = n.startsWith('-'); + const isPos = !isNeg && n.startsWith('+'); + const unsigned = isNeg || isPos ? n.slice(1) : n; + const [mantissa, expStr] = unsigned.toLowerCase().split('e'); + const exp = Number(expStr); + if (!Number.isFinite(exp)) return n; + const [intPart, fracPart = ''] = mantissa.split('.'); + const digits = intPart + fracPart; + if (exp === 0) return (isNeg ? '-' : '') + mantissa; + if (exp > 0) { + // Move decimal right + const pad = Math.max(0, exp - fracPart.length); + const left = digits + '0'.repeat(pad); + const idx = intPart.length + exp; + const whole = left.slice(0, idx); + const frac = left.slice(idx); + const out = frac.length ? `${whole}.${frac}` : whole; + return (isNeg ? '-' : '') + out; + } else { + // Move decimal left + const k = Math.abs(exp); + const pad = Math.max(0, k - intPart.length); + const left = '0'.repeat(pad) + digits; + const idx = left.length - k; + const whole = left.slice(0, idx) || '0'; + const frac = left.slice(idx); + const out = `${whole}.${frac}`; + return (isNeg ? '-' : '') + out; + } +} + +function bigIntToDecimalString(value: bigint, decimals: number): string { + const raw = formatUnits(value, decimals); + if (raw.includes('.')) { + const [i, f] = raw.split('.'); + const f2 = f.replace(/0+$/, ''); + return f2.length ? `${i}.${f2}` : i; + } + return raw; +} + +type InputValue = string | number | bigint; + +function insertThousands(n: string): string { + const neg = n.startsWith('-'); + const s = neg ? n.slice(1) : n; + const [i, f] = s.split('.'); + const withSep = i.replaceAll(/\B(?=(\d{3})+(?!\d))/g, ','); + const suffix = f ? `.${f}` : ''; + if (neg) { + return `-${withSep}${suffix}`; + } + return `${withSep}${suffix}`; +} + +function stripTrailingZeros(s: string): string { + if (s === '') return s; + let end = s.length; + while (end > 0 && s.charAt(end - 1) === '0') end--; + return end === s.length ? s : s.slice(0, end); +} + +function normalizeValue( + value: InputValue, + decimals?: number, +): { negative: boolean; intPart: string; fracRaw: string } { + let decimalStr: string; + if (typeof value === 'bigint') { + if (typeof decimals !== 'number') { + throw new TypeError('decimals is required when formatting bigint amounts'); + } + decimalStr = bigIntToDecimalString(value, decimals); + } else { + decimalStr = expandExponential(String(value)); + } + let negative = false; + if (decimalStr.startsWith('-')) { + negative = true; + decimalStr = decimalStr.slice(1); + } + if (decimalStr === '' || decimalStr === '.') decimalStr = '0'; + const [intRaw, fracRawRaw = ''] = decimalStr.split('.'); + const intPart = intRaw === '' ? '0' : intRaw; + const fracRaw = fracRawRaw; + return { negative, intPart, fracRaw }; +} + +function formatZero(symbol?: string): FormattedParts { + const base = '0'; + const text = symbol ? `${base} ${symbol}` : base; + return { text, approx: false, integer: '0', fraction: '', symbol }; +} + +interface NormalFormatOpts { + maxFractionDigits: number; + trimTrailingZeros: boolean; + thousandSeparator: boolean; + approxTilde: boolean; + symbol?: string; +} + +function formatNormal( + negative: boolean, + intPart: string, + fracRaw: string, + opts: NormalFormatOpts, +): FormattedParts { + let fraction = fracRaw.slice(0, opts.maxFractionDigits); + if (opts.trimTrailingZeros) fraction = stripTrailingZeros(fraction); + let base = fraction ? `${intPart}.${fraction}` : intPart; + if (opts.thousandSeparator) base = insertThousands(base); + const prefix = negative ? '-' : ''; + const withSymbol = opts.symbol ? `${base} ${opts.symbol}` : base; + const text = `${prefix}${withSymbol}`; + const approx = fraction.length < fracRaw.length && opts.approxTilde; + const integer = negative ? `-${intPart}` : intPart; + return { text, approx, integer, fraction, symbol: opts.symbol }; +} + +function countLeadingZeros(fracRaw: string): number { + let count = 0; + for (const ch of fracRaw) { + if (ch === '0') count++; + else break; + } + return count; +} + +interface TinyFormatOpts { + significantDigits: number; + zeroCompress: boolean; + approxTilde: boolean; + symbol?: string; +} + +function formatTiny( + negative: boolean, + fracRaw: string, + zeros: number, + opts: TinyFormatOpts, +): FormattedParts { + const sig = fracRaw.slice(zeros, zeros + opts.significantDigits); + const hasMore = fracRaw.length > zeros + sig.length; + const approx = opts.approxTilde && hasMore; + if (opts.zeroCompress) { + const zeroDisplay = Math.min(zeros, 99); + const zeroSub = toSubscript(zeroDisplay); + const core = `0.0${zeroSub}${sig || '0'}`; + const prefix = approx ? '~' : ''; + const sign = negative ? '-' : ''; + const body = opts.symbol ? `${core} ${opts.symbol}` : core; + const text = `${prefix}${sign}${body}`; + return { + text, + approx, + integer: negative ? '-0' : '0', + zeroCount: zeros, + zeroSubscript: zeroSub, + significant: sig || '0', + symbol: opts.symbol, + }; + } + const shown = `0.${'0'.repeat(zeros)}${sig || '0'}`; + const prefix = approx ? '~' : ''; + const sign = negative ? '-' : ''; + const body = opts.symbol ? `${shown} ${opts.symbol}` : shown; + const text = `${prefix}${sign}${body}`; + return { + text, + approx, + integer: negative ? '-0' : '0', + fraction: shown.slice(2), + symbol: opts.symbol, + }; +} + +/** + * Format a token balance parts + * @param value - The value to format + * @param options - The options to format the balance + * @returns The formatted balance parts + * Examples: + * - 1.234567 -> "1.2346" + * - 0.00008509 -> "~0.0β‚„8509" (shows 4 leading zeros after decimal) + * - 0.000000000000000123 -> "~0.0₁₅123" + * + * Notes: + * - Uses Unicode subscript digits for the zero count. + * - Returns a structured object with parts of the balance. + */ + +export function formatTokenBalanceParts( + value: InputValue, + { + decimals, + symbol, + significantDigits = 4, + maxFractionDigits = 4, + tinyThresholdPower = -4, + zeroCompress = true, + thousandSeparator = false, + trimTrailingZeros = true, + approxTilde = true, + }: FormatTokenBalanceOptions = {}, +): FormattedParts { + const normalized = normalizeValue(value, decimals); + const { negative, intPart, fracRaw } = normalized; + const isZero = intPart === '0' && /^0*$/.test(fracRaw); + + if (isZero) { + return formatZero(symbol); + } + const normalOpts = { + maxFractionDigits, + trimTrailingZeros, + thousandSeparator, + approxTilde, + symbol, + } as const; + + // String-based classification to avoid Number underflow/overflow + const isIntegerNonZero = intPart !== '0'; + if (isIntegerNonZero && tinyThresholdPower < 0) { + return formatNormal(negative, intPart, fracRaw, normalOpts); + } + if (isIntegerNonZero && tinyThresholdPower >= 0) { + const intDigits = intPart.replace(/^0+/, '').length; + if (intDigits - 1 >= tinyThresholdPower) { + return formatNormal(negative, intPart, fracRaw, normalOpts); + } + // else treat as tiny + } + + let zeros = isIntegerNonZero ? 0 : countLeadingZeros(fracRaw); + if (!isIntegerNonZero && tinyThresholdPower < 0) { + const limitZeros = Math.max(0, Math.abs(tinyThresholdPower) - 1); + if (zeros <= limitZeros) { + return formatNormal(negative, intPart, fracRaw, normalOpts); + } + } + + const tinyOpts = { + significantDigits, + zeroCompress, + approxTilde, + symbol, + } as const; + return formatTiny(negative, fracRaw, zeros, tinyOpts); +} + +/** + * Format a token balance + * @param value - The value to format + * @param options - The options to format the balance + * @returns The formatted balance + * Examples: + * - 1.234567 -> "1.2346" + * - 0.00008509 -> "~0.0β‚„8509" (shows 4 leading zeros after decimal) + * - 0.000000000000000123 -> "~0.0₁₅123" + * + * Notes: + * - Uses Unicode subscript digits for the zero count. + * - Returns a single string optimized for UI display. + * - If you need richer rendering (e.g., separate parts to style the subscript), + * use `formatTokenBalanceParts` which returns structured parts. + */ +export function formatTokenBalance( + value: string | number | bigint, + options?: FormatTokenBalanceOptions, +): string { + return formatTokenBalanceParts(value, options).text; +} diff --git a/src/commons/utils/index.ts b/src/commons/utils/index.ts new file mode 100644 index 00000000..b945270a --- /dev/null +++ b/src/commons/utils/index.ts @@ -0,0 +1,2 @@ +export * from './format'; +export * from './logger'; diff --git a/packages/commons/utils/logger.ts b/src/commons/utils/logger.ts similarity index 73% rename from packages/commons/utils/logger.ts rename to src/commons/utils/logger.ts index 94d5455e..18edab7e 100644 --- a/packages/commons/utils/logger.ts +++ b/src/commons/utils/logger.ts @@ -1,3 +1,5 @@ +import { telemetryLogger } from '../../sdk/ca-base/telemetry'; + export const LOG_LEVEL = { DEBUG: 1, ERROR: 4, @@ -6,6 +8,14 @@ export const LOG_LEVEL = { WARNING: 3, } as const; +export const LOG_LEVEL_NAME: Record = { + [LOG_LEVEL.DEBUG]: 'DEBUG', + [LOG_LEVEL.ERROR]: 'ERROR', + [LOG_LEVEL.INFO]: 'INFO', + [LOG_LEVEL.NOLOGS]: 'NOLOGS', + [LOG_LEVEL.WARNING]: 'WARNING', +}; + type LogLevel = (typeof LOG_LEVEL)[keyof typeof LOG_LEVEL]; type ExceptionReporter = (message: string) => void; @@ -66,9 +76,9 @@ class Logger { this.internalLog(LOG_LEVEL.DEBUG, message, params); } - error(message: string, err?: Error | string) { + error(message: string, err?: unknown, params: unknown = {}) { if (err instanceof Error) { - this.internalLog(LOG_LEVEL.ERROR, message, err.message); + this.internalLog(LOG_LEVEL.ERROR, message, params); sendException(JSON.stringify({ error: err.message, message })); return; } @@ -87,6 +97,24 @@ class Logger { internalLog(level: LogLevel, message: string, params?: unknown) { const logMessage = `[${this.prefix}] Msg: ${message}\n`; + if (level == LOG_LEVEL.ERROR || level == LOG_LEVEL.WARNING) { + const cause = + params && typeof params === 'object' && 'cause' in params + ? (params as any).cause + : 'unknown|not_mapped'; + try { + telemetryLogger?.emit({ + body: message, + severityNumber: level, + severityText: LOG_LEVEL_NAME[level], + attributes: { + cause: cause, + }, + }); + } catch (error) { + console.error('Failed to send telemetry logs: ', error); + } + } this.consoleLog(level, logMessage, params); } diff --git a/packages/core/index.ts b/src/index.ts similarity index 83% rename from packages/core/index.ts rename to src/index.ts index c7eeaed4..629a1147 100644 --- a/packages/core/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ +import './_polyfill'; // Core SDK entry point - headless, no React dependencies export { NexusSDK } from './sdk/index'; - +export { NexusError, NexusErrorData, ERROR_CODES } from './sdk/ca-base/nexusError'; // Re-export types from commons for convenience export type { BridgeParams, @@ -31,9 +32,7 @@ export type { SUPPORTED_TOKENS, ChainMetadata, TokenMetadata, - ProgressStep, - ProgressSteps, -} from '@nexus/commons'; +} from './commons'; export { CHAIN_METADATA, @@ -45,9 +44,9 @@ export { MAINNET_CHAINS, TOKEN_CONTRACT_ADDRESSES, DESTINATION_SWAP_TOKENS, -} from '@nexus/commons'; - -export type { SwapStep } from './sdk/ca-base'; + BRIDGE_STEPS, + SWAP_STEPS, +} from './commons'; // Re-export everything from commons (includes constants, utils, and types) -export * from '@nexus/commons'; +export * from './commons'; diff --git a/src/integrations/tenderly.ts b/src/integrations/tenderly.ts new file mode 100644 index 00000000..b00393d9 --- /dev/null +++ b/src/integrations/tenderly.ts @@ -0,0 +1,232 @@ +import { + type ApiResponse, + type BackendConfig, + type ChainSupportResponse, + type HealthCheckResponse, + type ServiceStatusResponse, + type BundleSimulationRequest, + type BackendBundleResponse, +} from './types'; +import { logger } from '../commons'; +import axios from 'axios'; +import { Errors } from '../sdk/ca-base/errors'; + +/** + * Backend simulation result interface + */ +export interface BackendSimulationResult { + gasUsed: string; + gasPrice: string; + maxFeePerGas?: string; + maxPriorityFeePerGas?: string; + success: boolean; + errorMessage?: string; + estimatedCost: { + totalFee: string; + }; +} + +const BACKEND_URL = 'https://nexus-backend.avail.so'; + +/** + * Backend client for gas estimation using new API + */ + +export class BackendSimulationClient { + private readonly baseUrl: string; + + constructor(config: BackendConfig) { + this.baseUrl = config.baseUrl; + } + + /** + * Check if a specific chain is supported + */ + async isChainSupported(chainId: number): Promise { + try { + const response = await fetch(`${this.baseUrl}/api/gas-estimation/check-chain/${chainId}`); + if (!response.ok) return false; + + const result: ApiResponse = await response.json(); + return result.success && result.data?.supported === true; + } catch (error) { + logger.warn(`Error checking chain support for ${chainId}:`, error); + return false; + } + } + + /** + * Get all supported chains + */ + async getSupportedChains(): Promise | null> { + try { + const response = await fetch(`${this.baseUrl}/api/gas-estimation/supported-chains`); + if (!response.ok) return null; + + const result: ApiResponse> = await response.json(); + return result.success ? result.data || null : null; + } catch (error) { + logger.warn('Error fetching supported chains:', error); + return null; + } + } + + /** + * Get service status + */ + async getServiceStatus(): Promise { + try { + const response = await fetch(`${this.baseUrl}/api/gas-estimation/status`); + if (!response.ok) return null; + + const result: ApiResponse = await response.json(); + return result.success ? result.data || null : null; + } catch (error) { + logger.warn('Error fetching service status:', error); + return null; + } + } + + /** + * Health check + */ + async healthCheck(): Promise { + try { + const response = await fetch(`${this.baseUrl}/api/health`); + if (!response.ok) return null; + + const result: ApiResponse = await response.json(); + return result.success ? result.data || null : null; + } catch (error) { + logger.warn('Error performing health check:', error); + return null; + } + } + + /** + * Test connectivity and service health + */ + async testConnection(): Promise { + try { + const health = await this.healthCheck(); + return health?.status === 'ok'; + } catch (error) { + logger.warn('Connection test failed:', error); + return false; + } + } + + /** + * Get detailed service information + */ + async getServiceInfo(): Promise<{ + healthy: boolean; + configured: boolean; + supportedChains: number; + version?: string; + uptime?: number; + }> { + try { + const [health, status] = await Promise.all([this.healthCheck(), this.getServiceStatus()]); + + return { + healthy: health?.status === 'ok', + configured: status?.configured || false, + supportedChains: status?.supportedChainsCount || 0, + version: health?.version, + uptime: health?.uptime, + }; + } catch (error) { + logger.warn('Error getting service info:', error); + return { + healthy: false, + configured: false, + supportedChains: 0, + }; + } + } + + async simulateBundleV2(request: BundleSimulationRequest) { + logger.debug('DEBUG simulateBundle - request:', JSON.stringify(request, null, 2)); + + const { data } = await axios.post( + new URL(`/api/gas-estimation/bundle`, this.baseUrl).href, + request, + ); + + if (!data.success || !data.data) { + throw Errors.simulationError(data.message ?? 'Bundle simulation failed'); + } + + return { gas: data.data.map((d) => BigInt(d.gasLimit)) }; + } +} + +/** + * Factory function to create a backend simulation client + */ +export function createBackendSimulationClient(config: BackendConfig): BackendSimulationClient { + return new BackendSimulationClient(config); +} + +/** + * Default backend simulation client instance + */ +let defaultSimulationClient: BackendSimulationClient | null = null; + +/** + * Configure the default simulation client + */ +export function configureSimulationBackend(config: BackendConfig): void { + defaultSimulationClient = new BackendSimulationClient(config); +} + +/** + * Get the default simulation client + */ +export function getSimulationClient(): BackendSimulationClient | null { + return defaultSimulationClient; +} + +/** + * Check if simulation backend is configured + */ +export function isSimulationConfigured(): boolean { + return defaultSimulationClient !== null; +} + +/** + * Initialize simulation client with health check + */ +export async function initializeSimulationClient(baseUrl: string = BACKEND_URL): Promise<{ + success: boolean; + error?: string; +}> { + try { + const client = new BackendSimulationClient({ baseUrl }); + + // Test the connection + const isHealthy = await client.testConnection(); + if (!isHealthy) { + return { + success: false, + error: `Backend service at ${baseUrl} is not responding or unhealthy`, + }; + } + + // Configure as default client + defaultSimulationClient = client; + + return { + success: true, + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown initialization error', + }; + } +} + +// Initialize with BACKEND_URL by default +configureSimulationBackend({ baseUrl: BACKEND_URL }); diff --git a/packages/core/integrations/types.ts b/src/integrations/types.ts similarity index 100% rename from packages/core/integrations/types.ts rename to src/integrations/types.ts diff --git a/packages/core/sdk/ca-base/abi/erc20.ts b/src/sdk/ca-base/abi/erc20.ts similarity index 100% rename from packages/core/sdk/ca-base/abi/erc20.ts rename to src/sdk/ca-base/abi/erc20.ts diff --git a/packages/core/sdk/ca-base/abi/gasOracle.ts b/src/sdk/ca-base/abi/gasOracle.ts similarity index 100% rename from packages/core/sdk/ca-base/abi/gasOracle.ts rename to src/sdk/ca-base/abi/gasOracle.ts diff --git a/packages/core/sdk/ca-base/abi/misc.ts b/src/sdk/ca-base/abi/misc.ts similarity index 100% rename from packages/core/sdk/ca-base/abi/misc.ts rename to src/sdk/ca-base/abi/misc.ts diff --git a/src/sdk/ca-base/abi/vault.ts b/src/sdk/ca-base/abi/vault.ts new file mode 100644 index 00000000..5f5b26b5 --- /dev/null +++ b/src/sdk/ca-base/abi/vault.ts @@ -0,0 +1,27 @@ +const FillEvent = { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'requestHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'solver', + type: 'address', + }, + ], + name: 'Fulfilment', + type: 'event', +} as const; + +export { FillEvent }; diff --git a/src/sdk/ca-base/ca.ts b/src/sdk/ca-base/ca.ts new file mode 100644 index 00000000..480de06d --- /dev/null +++ b/src/sdk/ca-base/ca.ts @@ -0,0 +1,574 @@ +import { + createCosmosClient, + createCosmosWallet, + Environment, + Universe, +} from '@avail-project/ca-common'; +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { keyDerivation } from '@starkware-industries/starkware-crypto-utils'; +import { createWalletClient, custom, Hex, UserRejectedRequestError, WalletClient } from 'viem'; +import { privateKeyToAccount, PrivateKeyAccount } from 'viem/accounts'; +import { createSiweMessage } from 'viem/siwe'; +import { ChainList } from './chains'; +import { getNetworkConfig } from './config'; +import { + ChainListType, + EthereumProvider, + ExactInSwapInput, + ExactOutSwapInput, + NetworkConfig, + NexusNetwork, + OnAllowanceHook, + OnIntentHook, + SwapMode, + SwapParams, + BridgeAndExecuteParams, + ExecuteParams, + OnEventParam, + OnSwapIntentHook, + TronAdapter, + getLogger, + LOG_LEVEL, + setLogLevel, + Chain, + TransferParams, + BridgeParams, + BeforeExecuteHook, + CosmosOptions, + SUPPORTED_CHAINS, +} from '../../commons'; +import { createBridgeParams } from './requestHandlers/helpers'; +import { + cosmosFeeGrant, + fetchMyIntents, + getSupportedChains, + minutesToMs, + refundExpiredIntents, + tronHexToEvmAddress, + getBalances, + retrieveSIWESignatureFromLocalStorage, + storeSIWESignatureToLocalStorage, + getBalancesForSwap, + switchChain, + intentTransform, + mulDecimals, + getCosmosURL, +} from './utils'; +import { swap } from './swap/swap'; +import { getSwapSupportedChains } from './swap/utils'; +import { utils } from 'tronweb'; +import BridgeHandler from './requestHandlers/bridge'; +import { BridgeAndExecuteQuery } from './query/bridgeAndExecute'; +import { + BackendSimulationClient, + createBackendSimulationClient, +} from '../../integrations/tenderly'; +import { createBridgeAndTransferParams } from './query/bridgeAndTransfer'; +import getMaxValueForBridge from './requestHandlers/bridgeMax'; +import { Errors } from './errors'; +import { setLoggerProvider } from './telemetry'; + +setLogLevel(LOG_LEVEL.NOLOGS); +const logger = getLogger(); + +enum INIT_STATUS { + CREATED, + RUNNING, + DONE, +} + +const SIWE_STATEMENT = 'Sign in to enable Nexus'; + +export class CA { + static readonly getSupportedChains = getSupportedChains; + #cosmos?: CosmosOptions & { + wallet: DirectSecp256k1Wallet; + }; + #ephemeralWallet?: PrivateKeyAccount; + public chainList: ChainListType; + private readonly _siweChain; + protected _evm?: { + client: WalletClient; + provider: EthereumProvider; + address: Hex; + }; + protected _tron?: { + address: string; + adapter: TronAdapter; + }; + protected _hooks: { + onAllowance: OnAllowanceHook; + onIntent: OnIntentHook; + onSwapIntent: OnSwapIntentHook; + } = { + onAllowance: (data) => data.allow(data.sources.map(() => 'min')), + onIntent: (data) => data.allow(), + onSwapIntent: (data) => data.allow(), + }; + protected _initStatus = INIT_STATUS.CREATED; + protected _networkConfig: NetworkConfig; + protected _refundInterval: number | undefined; + protected _initPromise: Promise | null = null; + private readonly simulationClient: BackendSimulationClient; + + protected constructor( + config: { network?: NexusNetwork; debug?: boolean; siweChain?: number } = { + debug: false, + network: 'testnet', + siweChain: 1, + }, + ) { + this._networkConfig = getNetworkConfig(config.network); + this.chainList = new ChainList(this._networkConfig.NETWORK_HINT); + this.simulationClient = createBackendSimulationClient({ + baseUrl: 'https://nexus-backend.avail.so', + }); + + this._siweChain = + config?.siweChain ?? this._networkConfig.NETWORK_HINT === Environment.FOLLY + ? SUPPORTED_CHAINS.SEPOLIA + : SUPPORTED_CHAINS.ETHEREUM; + + if (config.debug) { + setLogLevel(LOG_LEVEL.DEBUG); + } + } + + protected _createBridgeHandler = (input: BridgeParams, options?: OnEventParam) => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + + const params = createBridgeParams(input, this.chainList); + this.universeCheck(params.dstChain); + + const bridgeHandler = new BridgeHandler(params, { + chainList: this.chainList, + cosmos: this.#cosmos!, + evm: this._evm, + hooks: this._hooks, + tron: this._tron, + networkConfig: this._networkConfig, + emit: options?.onEvent, + }); + + return bridgeHandler; + }; + + protected _calculateMaxForBridge = async (params: Omit) => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + + return getMaxValueForBridge(params, { + chainList: this.chainList, + evm: this._evm, + tron: this._tron, + networkConfig: this._networkConfig, + }); + }; + + protected _deinit = () => { + this.#cosmos = undefined; + + if (this._evm) { + this._evm.provider.removeListener('accountsChanged', this._onAccountsChanged); + } + + if (this._refundInterval) { + clearInterval(this._refundInterval); + this._refundInterval = undefined; + } + + this._initStatus = INIT_STATUS.CREATED; + }; + + protected _getMyIntents = async (page = 1) => { + const { wallet } = await this._getCosmosWallet(); + const address = (await wallet.getAccounts())[0].address; + const rffList = await fetchMyIntents(address, this._networkConfig.GRPC_URL, page); + return intentTransform(rffList, this._networkConfig.EXPLORER_URL, this.chainList); + }; + + protected _getUnifiedBalances = async (includeSwappableBalances = false) => { + if (!this._evm || this._initStatus !== INIT_STATUS.DONE) { + throw Errors.sdkNotInitialized(); + } + + const { assets } = await getBalances({ + networkHint: this._networkConfig.NETWORK_HINT, + evmAddress: (await this._evm.client.requestAddresses())[0], + chainList: this.chainList, + filter: false, + isCA: includeSwappableBalances === false, + vscDomain: this._networkConfig.VSC_DOMAIN, + tronAddress: this._tron?.address, + }); + return assets; + }; + + protected _getBalancesForSwap = async () => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + + const balances = await getBalancesForSwap({ + evmAddress: (await this._evm.client.requestAddresses())[0], + chainList: this.chainList, + }); + return balances; + }; + + protected _isInitialized = () => { + return this._initStatus === INIT_STATUS.DONE; + }; + + protected _swapWithExactIn = async (input: ExactInSwapInput, options?: OnEventParam) => { + return swap( + { + mode: SwapMode.EXACT_IN, + data: input, + }, + await this._getSwapOptions(options), + ); + }; + + protected _swapWithExactOut = async (input: ExactOutSwapInput, options?: OnEventParam) => { + return swap( + { + mode: SwapMode.EXACT_OUT, + data: input, + }, + await this._getSwapOptions(options), + ); + }; + + private _getSwapOptions = async (options?: OnEventParam): Promise => { + return { + onSwapIntent: this._hooks.onSwapIntent, + onEvent: options?.onEvent, + chainList: this.chainList, + address: { + cosmos: this.#cosmos!.address, + eoa: (await this._evm!.client.getAddresses())[0], + ephemeral: this.#ephemeralWallet!.address, + }, + wallet: { + cosmos: this.#cosmos!.client, + ephemeral: this.#ephemeralWallet!, + eoa: this._evm!.client, + }, + networkConfig: this._networkConfig, + ...options, + }; + }; + + protected _init = () => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + // Return existing promise if initialization already started or done + if (this._initStatus === INIT_STATUS.RUNNING || this._initStatus === INIT_STATUS.DONE) { + return this._initPromise!; + } + + // Prevent concurrent initializations + if (this._initStatus !== INIT_STATUS.CREATED) { + throw Errors.sdkInitStateNotExpected(this._initStatus); + } + + this._initStatus = INIT_STATUS.RUNNING; + + this._initPromise = (async () => { + try { + setLoggerProvider(this._networkConfig); + this._setProviderHooks(); + this.#cosmos = await this._createCosmosWallet(); + this._checkPendingRefunds(); + this._initStatus = INIT_STATUS.DONE; + } catch (e) { + this._initStatus = INIT_STATUS.CREATED; + logger.error('Error initializing CA', e, { cause: 'SDK_NOT_INITIALIZED' }); + throw e; + } + })(); + + return this._initPromise; + }; + + protected _onAccountsChanged = (accounts: Array<`0x${string}`>) => { + this._deinit(); + if (accounts.length !== 0) { + if (this._evm) { + this._evm.address = accounts[0]; + } + this._init(); + } + }; + + protected _setEVMProvider = async (provider: EthereumProvider) => { + if (this._evm?.provider === provider) { + return; + } + const client = createWalletClient({ + transport: custom({ ...provider, request: provider.request.bind(provider) }), + }); + + const address = (await client.getAddresses())[0]; + + this._evm = { + client, + provider, + address, + }; + }; + + protected _setTronAdapter = async (adapter: TronAdapter) => { + if (this._tron) { + logger.debug('Already has tron adapter, so skip', { + adapter, + classVal: this._tron, + }); + return; + } + + if (!adapter.connected) { + await adapter.connect(); + } + + logger.debug('setTronAdapter', { + address: adapter.address, + classVal: this._tron, + }); + + this._tron = { + adapter, + address: tronHexToEvmAddress(utils.address.toHex(adapter.address as string)), + }; + }; + + protected _setOnAllowanceHook = (hook: OnAllowanceHook) => { + this._hooks.onAllowance = hook; + }; + + protected _setOnIntentHook = (hook: OnIntentHook) => { + this._hooks.onIntent = hook; + }; + + protected _setOnSwapIntentHook = (hook: OnSwapIntentHook) => { + this._hooks.onSwapIntent = hook; + }; + + protected _bridgeAndTransfer = async (input: TransferParams, options?: OnEventParam) => { + const params = createBridgeAndTransferParams(input, this.chainList); + return this._bridgeAndExecute(params, options); + }; + + protected _simulateBridgeAndTransfer = async (input: TransferParams) => { + const params = createBridgeAndTransferParams(input, this.chainList); + return this._simulateBridgeAndExecute(params); + }; + + protected _checkPendingRefunds = async () => { + await this._init(); + const evmAddress = await this._getEVMAddress(); + try { + await refundExpiredIntents({ + evmAddress, + address: this.#cosmos!.address, + client: this.#cosmos!.client, + }); + + this._refundInterval = window.setInterval(async () => { + await refundExpiredIntents({ + evmAddress, + address: this.#cosmos!.address, + client: this.#cosmos!.client, + }); + }, minutesToMs(10)); + } catch (e) { + logger.error('Error checking pending refunds', e, { cause: 'REFUND_CHECK_ERROR' }); + } + }; + + protected _createCosmosWallet = async () => { + let sig = retrieveSIWESignatureFromLocalStorage(this._evm!.address, this._siweChain); + if (!sig) { + sig = await this._signatureForLogin(); + storeSIWESignatureToLocalStorage(this._evm!.address, this._siweChain, sig); + } + + const pvtKey = keyDerivation.getPrivateKeyFromEthSignature(sig); + const wallet = await createCosmosWallet(`0x${pvtKey.padStart(64, '0')}`); + + this.#ephemeralWallet = privateKeyToAccount(`0x${pvtKey.padStart(64, '0')}`); + + const address = (await wallet.getAccounts())[0].address; + await cosmosFeeGrant(this._networkConfig.COSMOS_URL, this._networkConfig.VSC_DOMAIN, address); + + const client = await createCosmosClient( + wallet, + getCosmosURL(this._networkConfig.COSMOS_URL, 'rpc'), + { broadcastPollIntervalMs: 250 }, + ); + + return { wallet, address, client }; + }; + + protected _getCosmosWallet = async () => { + if (!this.#cosmos) { + this.#cosmos = await this._createCosmosWallet(); + } + return this.#cosmos; + }; + + protected _getEVMAddress = async () => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + return (await this._evm.client.requestAddresses())[0]; + }; + + protected _setProviderHooks = async () => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + if (this._evm.provider) { + this._evm.provider.on('accountsChanged', this._onAccountsChanged); + } + }; + + protected _signatureForLogin = async () => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + + const chain = this.chainList.getChainByID(this._siweChain); + if (!chain) { + throw Errors.chainNotFound(this._siweChain); + } + + const scheme = window.location.protocol.slice(0, -1); + const domain = window.location.host; + const origin = window.location.origin; + const address = await this._getEVMAddress(); + const message = createSiweMessage({ + address, + chainId: chain.id, + domain, + issuedAt: new Date('2024-12-16T12:17:43.182Z'), // this remains same to arrive at same pvt key + nonce: 'iLjYWC6s8frYt4l8w', // maybe this can be shortened hash of address + scheme, + statement: SIWE_STATEMENT, + uri: origin, + version: '1', + }); + const currentChain = await this._evm.client.getChainId(); + try { + await switchChain(this._evm.client, chain); + const res = await this._evm.client + .signMessage({ + account: address, + message, + }) + .catch((e) => { + if (e instanceof UserRejectedRequestError) { + throw Errors.userRejectedSIWESignature(); + } + throw e; + }); + return res; + } finally { + await this._evm.client.switchChain({ id: currentChain }); + } + }; + + protected _getSwapSupportedChains = () => { + return getSwapSupportedChains(this.chainList); + }; + + protected _simulateBridgeAndExecute = (params: BridgeAndExecuteParams) => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + + const handler = new BridgeAndExecuteQuery( + this.chainList, + this._evm.client, + this._createBridgeHandler, + this._getUnifiedBalances, + this.simulationClient, + ); + + return handler.simulateBridgeAndExecute(params); + }; + + protected _bridgeAndExecute = ( + params: BridgeAndExecuteParams, + options?: OnEventParam & BeforeExecuteHook, + ) => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + + const handler = new BridgeAndExecuteQuery( + this.chainList, + this._evm.client, + this._createBridgeHandler, + this._getUnifiedBalances, + this.simulationClient, + ); + + return handler.bridgeAndExecute(params, options); + }; + + protected _execute = async (params: ExecuteParams, options?: OnEventParam) => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + + const handler = new BridgeAndExecuteQuery( + this.chainList, + this._evm.client, + this._createBridgeHandler, + this._getUnifiedBalances, + this.simulationClient, + ); + + return handler.execute(params, options); + }; + + protected _simulateExecute = (params: ExecuteParams) => { + if (!this._evm) { + throw Errors.sdkNotInitialized(); + } + + const handler = new BridgeAndExecuteQuery( + this.chainList, + this._evm.client, + this._createBridgeHandler, + this._getUnifiedBalances, + this.simulationClient, + ); + + return handler.simulateExecute(params, this._evm.address); + }; + + private readonly universeCheck = (dstChain: Chain) => { + if (dstChain.universe === Universe.TRON && !this._tron) { + throw Errors.walletNotConnected('Tron'); + } + }; + + protected _convertTokenReadableAmountToBigInt = ( + amount: string, + tokenSymbol: string, + chainId: number, + ) => { + const token = this.chainList.getTokenInfoBySymbol(chainId, tokenSymbol); + if (!token) { + throw Errors.tokenNotFound(tokenSymbol, chainId); + } + return mulDecimals(amount, token.decimals); + }; +} diff --git a/packages/core/sdk/ca-base/chains.ts b/src/sdk/ca-base/chains.ts similarity index 69% rename from packages/core/sdk/ca-base/chains.ts rename to src/sdk/ca-base/chains.ts index aca9c520..8fff5a09 100644 --- a/packages/core/sdk/ca-base/chains.ts +++ b/src/sdk/ca-base/chains.ts @@ -4,39 +4,30 @@ import { getVaultContractMap, OmniversalChainID, Universe, -} from '@arcana/ca-common'; -// import { CHAIN_IDS } from 'fuels'; - -import { - // FUEL_BASE_ASSET_ID, - // FUEL_NETWORK_URL, - getLogoFromSymbol, - HYPEREVM_CHAIN_ID, - KAIA_CHAIN_ID, - MONAD_TESTNET_CHAIN_ID, - SOPHON_CHAIN_ID, - ZERO_ADDRESS, -} from './constants'; -import { Chain, TokenInfo } from '@nexus/commons'; +} from '@avail-project/ca-common'; +import { getLogoFromSymbol, ZERO_ADDRESS } from './constants'; +import { Chain, SUPPORTED_CHAINS, TOKEN_CONTRACT_ADDRESSES, TokenInfo } from '../../commons'; import { convertToHexAddressByUniverse, equalFold } from './utils'; +import { Errors } from './errors'; +import { Hex } from 'viem'; class ChainList { public chains: Chain[]; - private vcm: ChainIDKeyedMap>; + private readonly vcm: ChainIDKeyedMap>; constructor(env: Environment) { switch (env) { - case Environment.CERISE: + case Environment.JADE: case Environment.CORAL: this.chains = MAINNET_CHAINS; break; case Environment.FOLLY: this.chains = TESTNET_CHAINS; break; - case Environment.JADE: - throw new Error('Jade environment not supported yet'); + case Environment.CERISE: + throw Errors.environmentNotSupported('Jade'); default: - throw new Error('Unknown environment'); + throw Errors.environmentNotKnown(); } this.vcm = getVaultContractMap(env); } @@ -48,7 +39,7 @@ class ChainList { public getNativeToken(chainID: number): TokenInfo { const chain = this.getChainByID(chainID); if (!chain) { - throw new Error('chain not found'); + throw Errors.chainNotFound(chainID); } return { @@ -61,6 +52,14 @@ class ChainList { } public getTokenByAddress(chainID: number, address: `0x${string}`) { + const result = this.getChainAndTokenByAddress(chainID, address); + if (result) { + return result.token; + } + return undefined; + } + + public getChainAndTokenByAddress(chainID: number, address: Hex) { const chain = this.getChainByID(chainID); if (!chain) { return undefined; @@ -69,10 +68,10 @@ class ChainList { if (!token) { if (equalFold(address, ZERO_ADDRESS)) { - return this.getNativeToken(chainID); + return { chain, token: this.getNativeToken(chainID) }; } } - return token; + return { chain, token }; } public getTokenInfoBySymbol(chainID: number, symbol: string) { @@ -96,17 +95,45 @@ class ChainList { return token; } + public getChainAndTokenFromSymbol( + chainID: number, + tokenSymbol: string, + ): { chain: Chain; token: (TokenInfo & { isNative: boolean }) | undefined } { + const chain = this.getChainByID(chainID); + if (!chain) { + throw Errors.chainNotFound(chainID); + } + + const token = chain.custom.knownTokens.find((t) => equalFold(t.symbol, tokenSymbol)); + if (!token) { + if (equalFold(chain.nativeCurrency.symbol, tokenSymbol)) { + return { + token: { + contractAddress: ZERO_ADDRESS, + decimals: chain.nativeCurrency.decimals, + logo: chain.custom.icon, + name: chain.nativeCurrency.name, + symbol: chain.nativeCurrency.symbol, + isNative: true, + }, + chain, + }; + } + } + return { chain, token: token ? { ...token, isNative: false } : undefined }; + } + public getVaultContractAddress(chainID: number) { const chain = this.getChainByID(chainID); if (!chain) { - throw new Error('chain not supported'); + throw Errors.chainNotFound(chainID); } const omniversalChainID = new OmniversalChainID(chain.universe, chainID); const vc = this.vcm.get(omniversalChainID); if (!vc) { - throw new Error('vault contract not found'); + throw Errors.vaultContractNotFound(chainID); } return convertToHexAddressByUniverse(vc, chain.universe); @@ -118,6 +145,43 @@ class ChainList { } const TESTNET_CHAINS: Chain[] = [ + // { + // blockExplorers: { + // default: { + // name: 'TronScan', + // url: 'https://shasta.tronscan.org', + // }, + // }, + // custom: { + // icon: 'https://assets.coingecko.com/asset_platforms/images/1094/large/TRON_LOGO.png', + // knownTokens: [ + // { + // contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.TRON_SHASTA], + // decimals: 6, + // logo: getLogoFromSymbol('USDT'), + // name: 'Tether USD', + // symbol: 'USDT', + // }, + // ], + // }, + // id: SUPPORTED_CHAINS.TRON_SHASTA, + // ankrName: '', + // name: 'Tron Shasta', + // nativeCurrency: { + // decimals: 6, + // name: 'TRX', + // symbol: 'TRX', + // }, + // rpcUrls: { + // default: { + // http: ['https://api.shasta.trongrid.io/jsonrpc'], + // grpc: ['https://api.shasta.trongrid.io'], + // publicHttp: ['https://api.shasta.trongrid.io/jsonrpc'], + // webSocket: [], + // }, + // }, + // universe: Universe.TRON, + // }, { blockExplorers: { default: { @@ -126,17 +190,17 @@ const TESTNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/coins/images/16547/large/arb.jpg?1721358242', + icon: 'https://assets.coingecko.com/coins/images/16547/large/arb.jpg', knownTokens: [ { - contractAddress: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.ARBITRUM_SEPOLIA], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', symbol: 'USDC', }, { - contractAddress: '0xF954d4A5859b37De88a91bdbb8Ad309056FB04B1', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.ARBITRUM_SEPOLIA], decimals: 18, logo: getLogoFromSymbol('USDT'), name: 'Testing USD', @@ -144,7 +208,7 @@ const TESTNET_CHAINS: Chain[] = [ }, ], }, - id: 421614, + id: SUPPORTED_CHAINS.ARBITRUM_SEPOLIA, name: 'Arbitrum Sepolia', ankrName: '', nativeCurrency: { @@ -172,17 +236,17 @@ const TESTNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/coins/images/25244/large/Optimism.png?1696524385', + icon: 'https://assets.coingecko.com/coins/images/25244/large/Optimism.png', knownTokens: [ { - contractAddress: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.OPTIMISM_SEPOLIA], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', symbol: 'USDC', }, { - contractAddress: '0x6462693c2F21AC0E517f12641D404895030F7426', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.OPTIMISM_SEPOLIA], decimals: 18, logo: getLogoFromSymbol('USDT'), name: 'Testing USD', @@ -190,7 +254,7 @@ const TESTNET_CHAINS: Chain[] = [ }, ], }, - id: 11155420, + id: SUPPORTED_CHAINS.OPTIMISM_SEPOLIA, name: 'OP Sepolia', ankrName: '', nativeCurrency: { @@ -218,10 +282,10 @@ const TESTNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/asset_platforms/images/15/large/polygon_pos.png?1706606645', + icon: 'https://assets.coingecko.com/asset_platforms/images/15/large/polygon_pos.png', knownTokens: [ { - contractAddress: '0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.POLYGON_AMOY], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -229,7 +293,7 @@ const TESTNET_CHAINS: Chain[] = [ }, ], }, - id: 80002, + id: SUPPORTED_CHAINS.POLYGON_AMOY, name: 'Amoy', ankrName: '', nativeCurrency: { @@ -257,10 +321,10 @@ const TESTNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/asset_platforms/images/131/large/base-network.png?1720533039', + icon: 'https://assets.coingecko.com/asset_platforms/images/131/large/base-network.png', knownTokens: [ { - contractAddress: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.BASE_SEPOLIA], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -268,7 +332,7 @@ const TESTNET_CHAINS: Chain[] = [ }, ], }, - id: 84532, + id: SUPPORTED_CHAINS.BASE_SEPOLIA, name: 'Base Sepolia', ankrName: '', nativeCurrency: { @@ -299,14 +363,14 @@ const TESTNET_CHAINS: Chain[] = [ icon: 'https://assets.coingecko.com/coins/images/38927/standard/monad.jpg', knownTokens: [ { - contractAddress: '0xf817257fed379853cDe0fa4F97AB987181B1E5Ea', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.MONAD_TESTNET], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', symbol: 'USDC', }, { - contractAddress: '0x1c56F176D6735888fbB6f8bD9ADAd8Ad7a023a0b', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.MONAD_TESTNET], decimals: 18, logo: getLogoFromSymbol('USDT'), name: 'Testing USDT', @@ -314,7 +378,7 @@ const TESTNET_CHAINS: Chain[] = [ }, ], }, - id: MONAD_TESTNET_CHAIN_ID, + id: SUPPORTED_CHAINS.MONAD_TESTNET, name: 'Monad Testnet', ankrName: '', nativeCurrency: { @@ -339,10 +403,10 @@ const TESTNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png?1706606803', + icon: 'https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png', knownTokens: [ { - contractAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.SEPOLIA], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -350,7 +414,7 @@ const TESTNET_CHAINS: Chain[] = [ }, ], }, - id: 11155111, + id: SUPPORTED_CHAINS.SEPOLIA, name: 'Ethereum Sepolia', ankrName: '', nativeCurrency: { @@ -370,58 +434,45 @@ const TESTNET_CHAINS: Chain[] = [ }, universe: Universe.ETHEREUM, }, -]; - -const MAINNET_CHAINS: Chain[] = [ // { // blockExplorers: { // default: { - // name: 'Fuel Network Explorer', - // url: 'https://app.fuel.network/', + // name: 'Validium Testnet Explorer', + // url: 'https://testnet.explorer.validium.network', // }, // }, // custom: { - // icon: 'https://avatars.githubusercontent.com/u/55993183', + // icon: 'https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png', // knownTokens: [ // { - // contractAddress: FUEL_BASE_ASSET_ID, - // decimals: 9, - // logo: getLogoFromSymbol('ETH'), - // name: 'Ether', - // symbol: 'ETH', - // }, - // { - // contractAddress: '0x286c479da40dc953bddc3bb4c453b608bba2e0ac483b077bd475174115395e6b', + // contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.VALIDIUM_TESTNET], // decimals: 6, // logo: getLogoFromSymbol('USDC'), // name: 'USD Coin', // symbol: 'USDC', // }, - // { - // contractAddress: '0xa0265fb5c32f6e8db3197af3c7eb05c48ae373605b8165b6f4a51c5b0ba4812e', - // decimals: 6, - // logo: getLogoFromSymbol('USDT'), - // name: 'Tether USD', - // symbol: 'USDT', - // }, // ], // }, - // id: CHAIN_IDS.fuel.mainnet, - // name: 'Fuel Network', + // id: SUPPORTED_CHAINS.VALIDIUM_TESTNET, + // name: 'Validium Testnet', // ankrName: '', // nativeCurrency: { - // decimals: 9, - // name: 'Ether', - // symbol: 'ETH', + // decimals: 18, + // name: 'VLDM', + // symbol: 'VLDM', // }, // rpcUrls: { // default: { - // http: [FUEL_NETWORK_URL], - // webSocket: [], + // http: ['https://testnet.l2.rpc.validium.network'], + // publicHttp: ['https://testnet.l2.rpc.validium.network'], + // webSocket: ['wss://testnet.l2.rpc.validium.network/ws'], // }, // }, - // universe: Universe.FUEL, + // universe: Universe.ETHEREUM, // }, +]; + +const MAINNET_CHAINS: Chain[] = [ { blockExplorers: { default: { @@ -433,14 +484,14 @@ const MAINNET_CHAINS: Chain[] = [ icon: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png', knownTokens: [ { - contractAddress: '0x6386da73545ae4e2b2e0393688fa8b65bb9a7169', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.SOPHON], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', symbol: 'USDT', }, { - contractAddress: '0x9aa0f72392b5784ad86c6f3e899bcc053d00db4f', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.SOPHON], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -455,7 +506,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: SOPHON_CHAIN_ID, + id: SUPPORTED_CHAINS.SOPHON, name: 'Sophon', ankrName: '', nativeCurrency: { @@ -483,7 +534,7 @@ const MAINNET_CHAINS: Chain[] = [ icon: 'https://assets.coingecko.com/asset_platforms/images/9672/large/kaia.png', knownTokens: [ { - contractAddress: '0xd077a400968890eacc75cdc901f0356c943e4fdb', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.KAIA], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', @@ -491,7 +542,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: KAIA_CHAIN_ID, + id: SUPPORTED_CHAINS.KAIA, name: 'Kaia Mainnet', ankrName: '', nativeCurrency: { @@ -516,17 +567,17 @@ const MAINNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png?1706606803', + icon: 'https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png', knownTokens: [ { - contractAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.ETHEREUM], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', symbol: 'USDT', }, { - contractAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.ETHEREUM], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -534,9 +585,9 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: 1, + id: SUPPORTED_CHAINS.ETHEREUM, name: 'Ethereum Mainnet', - ankrName: 'eth', + ankrName: '', nativeCurrency: { decimals: 18, name: 'Ether', @@ -551,6 +602,42 @@ const MAINNET_CHAINS: Chain[] = [ }, universe: Universe.ETHEREUM, }, + { + blockExplorers: { + default: { + name: 'Monad Vision', + url: 'https://monadvision.com', + }, + }, + custom: { + icon: 'https://assets.coingecko.com/coins/images/38927/large/monad.jpg', + knownTokens: [ + { + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.MONAD], + decimals: 6, + logo: getLogoFromSymbol('USDC'), + name: 'USD Coin', + symbol: 'USDC', + }, + ], + }, + id: SUPPORTED_CHAINS.MONAD, + name: 'Monad', + ankrName: '', + nativeCurrency: { + decimals: 18, + name: 'Monad', + symbol: 'MON', + }, + rpcUrls: { + default: { + http: ['https://rpcs.avail.so/monad'], + publicHttp: [], + webSocket: ['wss://rpcs.avail.so/monad'], + }, + }, + universe: Universe.ETHEREUM, + }, { blockExplorers: { default: { @@ -559,17 +646,17 @@ const MAINNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/coins/images/25244/large/Optimism.png?1696524385', + icon: 'https://assets.coingecko.com/coins/images/25244/large/Optimism.png', knownTokens: [ { - contractAddress: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.OPTIMISM], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', symbol: 'USDT', }, { - contractAddress: '0x0b2c639c533813f4aa9d7837caf62653d097ff85', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.OPTIMISM], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -577,7 +664,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: 10, + id: SUPPORTED_CHAINS.OPTIMISM, name: 'OP Mainnet', ankrName: 'optimism', nativeCurrency: { @@ -602,17 +689,17 @@ const MAINNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/asset_platforms/images/15/large/polygon_pos.png?1706606645', + icon: 'https://assets.coingecko.com/asset_platforms/images/15/large/polygon_pos.png', knownTokens: [ { - contractAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.POLYGON], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', symbol: 'USDT', }, { - contractAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.POLYGON], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -620,7 +707,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: 137, + id: SUPPORTED_CHAINS.POLYGON, name: 'Polygon PoS', ankrName: 'polygon', nativeCurrency: { @@ -645,10 +732,10 @@ const MAINNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/asset_platforms/images/131/large/base-network.png?1720533039', + icon: 'https://assets.coingecko.com/asset_platforms/images/131/large/base-network.png', knownTokens: [ { - contractAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.BASE], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -656,7 +743,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: 8453, + id: SUPPORTED_CHAINS.BASE, name: 'Base', ankrName: 'base', nativeCurrency: { @@ -681,17 +768,17 @@ const MAINNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/coins/images/16547/large/arb.jpg?1721358242', + icon: 'https://assets.coingecko.com/coins/images/16547/large/arb.jpg', knownTokens: [ { - contractAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.ARBITRUM], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', symbol: 'USDT', }, { - contractAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.ARBITRUM], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -699,7 +786,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: 42161, + id: SUPPORTED_CHAINS.ARBITRUM, name: 'Arbitrum One', ankrName: 'arbitrum', nativeCurrency: { @@ -724,17 +811,17 @@ const MAINNET_CHAINS: Chain[] = [ }, }, custom: { - icon: 'https://assets.coingecko.com/asset_platforms/images/153/large/scroll.jpeg?1706606782', + icon: 'https://assets.coingecko.com/asset_platforms/images/153/large/scroll.jpeg', knownTokens: [ { - contractAddress: '0xf55bec9cafdbe8730f096aa55dad6d22d44099df', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.SCROLL], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', symbol: 'USDT', }, { - contractAddress: '0x06efdbff2a14a7c8e15944d1f4a48f9f95f663a4', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.SCROLL], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -742,7 +829,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: 534352, + id: SUPPORTED_CHAINS.SCROLL, name: 'Scroll', ankrName: 'scroll', nativeCurrency: { @@ -770,14 +857,14 @@ const MAINNET_CHAINS: Chain[] = [ icon: 'https://assets.coingecko.com/asset_platforms/images/12/large/avalanche.png', knownTokens: [ { - contractAddress: '0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.AVALANCHE], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', symbol: 'USDC', }, { - contractAddress: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.AVALANCHE], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', @@ -785,7 +872,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: 43114, + id: SUPPORTED_CHAINS.AVALANCHE, ankrName: 'avalanche', name: 'Avalanche C-Chain', nativeCurrency: { @@ -813,14 +900,14 @@ const MAINNET_CHAINS: Chain[] = [ icon: 'https://assets.coingecko.com/asset_platforms/images/1/large/bnb_smart_chain.png', knownTokens: [ { - contractAddress: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.BNB], decimals: 18, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', symbol: 'USDC', }, { - contractAddress: '0x55d398326f99059fF775485246999027B3197955', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.BNB], decimals: 18, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', @@ -835,7 +922,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: 0x38, + id: SUPPORTED_CHAINS.BNB, name: 'BNB Smart Chain', ankrName: 'bsc', nativeCurrency: { @@ -863,14 +950,14 @@ const MAINNET_CHAINS: Chain[] = [ icon: 'https://assets.coingecko.com/asset_platforms/images/243/large/hyperliquid.png', knownTokens: [ { - contractAddress: '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.HYPEREVM], decimals: 6, logo: getLogoFromSymbol('USDT'), name: 'Tether USD', symbol: 'USDT', }, { - contractAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f', + contractAddress: TOKEN_CONTRACT_ADDRESSES['USDC'][SUPPORTED_CHAINS.HYPEREVM], decimals: 6, logo: getLogoFromSymbol('USDC'), name: 'USD Coin', @@ -878,7 +965,7 @@ const MAINNET_CHAINS: Chain[] = [ }, ], }, - id: HYPEREVM_CHAIN_ID, + id: SUPPORTED_CHAINS.HYPEREVM, ankrName: '', name: 'HyperEVM', nativeCurrency: { @@ -895,6 +982,43 @@ const MAINNET_CHAINS: Chain[] = [ }, universe: Universe.ETHEREUM, }, + // { + // blockExplorers: { + // default: { + // name: 'TronScan', + // url: 'https://tronscan.org', + // }, + // }, + // custom: { + // icon: 'https://assets.coingecko.com/asset_platforms/images/1094/large/TRON_LOGO.png', + // knownTokens: [ + // { + // contractAddress: TOKEN_CONTRACT_ADDRESSES['USDT'][SUPPORTED_CHAINS.TRON], + // decimals: 6, + // logo: getLogoFromSymbol('USDT'), + // name: 'Tether USD', + // symbol: 'USDT', + // }, + // ], + // }, + // id: SUPPORTED_CHAINS.TRON, + // ankrName: '', + // name: 'Tron mainnet', + // nativeCurrency: { + // decimals: 6, + // name: 'TRX', + // symbol: 'TRX', + // }, + // rpcUrls: { + // default: { + // http: ['https://api.trongrid.io/jsonrpc'], + // grpc: ['https://api.trongrid.io'], + // publicHttp: ['https://api.trongrid.io/jsonrpc', 'https://tron.therpc.io/jsonrpc'], + // webSocket: ['wss://tron.drpc.org'], + // }, + // }, + // universe: Universe.TRON, + // }, ]; -export { ChainList, KAIA_CHAIN_ID, SOPHON_CHAIN_ID }; +export { ChainList }; diff --git a/src/sdk/ca-base/config.ts b/src/sdk/ca-base/config.ts new file mode 100644 index 00000000..9f1f7b44 --- /dev/null +++ b/src/sdk/ca-base/config.ts @@ -0,0 +1,67 @@ +import { Environment } from '@avail-project/ca-common'; + +import { NetworkConfig, NexusNetwork } from '../../commons'; + +// Mainnet +const JADE_CONFIG: NetworkConfig = { + COSMOS_URL: 'https://cosmos-mainnet.availproject.org', + EXPLORER_URL: 'https://nexus-explorer.availproject.org', + GRPC_URL: 'https://grpcproxy-mainnet.availproject.org', + NETWORK_HINT: Environment.JADE, + VSC_DOMAIN: 'vsc-mainnet.availproject.org', +}; + +// Canary +const CORAL_CONFIG: NetworkConfig = { + COSMOS_URL: 'https://cosmos01-testnet.arcana.network', + EXPLORER_URL: 'https://explorer.nexus.availproject.org', + GRPC_URL: 'https://grpcproxy-testnet.arcana.network', + NETWORK_HINT: Environment.CORAL, + VSC_DOMAIN: 'vsc1-testnet.arcana.network', +}; + +// Testnet +const FOLLY_CONFIG: NetworkConfig = { + COSMOS_URL: 'https://cosmos04-dev.arcana.network', + EXPLORER_URL: 'https://explorer.nexus-folly.availproject.org', + GRPC_URL: 'https://grpc-folly.arcana.network', + NETWORK_HINT: Environment.FOLLY, + VSC_DOMAIN: 'vsc1-folly.arcana.network', +}; + +const isNetworkConfig = (config?: Environment | NetworkConfig): config is NetworkConfig => { + if (typeof config !== 'object') { + return false; + } + if ( + !( + config.VSC_DOMAIN && + config.COSMOS_URL && + config.EXPLORER_URL && + config.GRPC_URL && + config.NETWORK_HINT + ) + ) { + return false; + } + if (config.NETWORK_HINT === undefined) { + return false; + } + return true; +}; + +const getNetworkConfig = (network?: NexusNetwork): NetworkConfig => { + if (typeof network === 'object' && isNetworkConfig(network)) { + return network; + } + switch (network) { + case 'canary': + return CORAL_CONFIG; + case 'testnet': + return FOLLY_CONFIG; + default: + return JADE_CONFIG; + } +}; + +export { getNetworkConfig }; diff --git a/src/sdk/ca-base/constants.ts b/src/sdk/ca-base/constants.ts new file mode 100644 index 00000000..9742e229 --- /dev/null +++ b/src/sdk/ca-base/constants.ts @@ -0,0 +1,42 @@ +import { Universe } from '@avail-project/ca-common'; + +const SymbolToLogo: { [k: string]: string } = { + BNB: 'https://assets.coingecko.com/coins/images/825/large/bnb-icon2_2x.png', + AVAX: 'https://assets.coingecko.com/coins/images/12559/standard/Avalanche_Circle_RedWhite_Trans.png', + ETH: 'https://coin-images.coingecko.com/coins/images/279/large/ethereum.png', + KAIA: 'https://assets.coingecko.com/coins/images/39901/large/KAIA.png', + MATIC: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png', + MON: 'https://assets.coingecko.com/coins/images/38927/large/monad.jpg', + POL: 'https://coin-images.coingecko.com/coins/images/32440/standard/polygon.png', + SOPH: 'https://assets.coingecko.com/coins/images/38680/large/sophon_logo_200.png', + USDC: 'https://coin-images.coingecko.com/coins/images/6319/large/usdc.png', + USDT: 'https://coin-images.coingecko.com/coins/images/35023/large/USDT.png', + WETH: 'https://coin-images.coingecko.com/coins/images/2518/standard/weth.png', + HYPE: 'https://assets.coingecko.com/coins/images/50882/large/hyperliquid.jpg', +}; + +const getLogoFromSymbol = (symbol: string) => { + const logo = SymbolToLogo[symbol]; + if (!logo) { + return ''; + } + + return logo; +}; + +const isNativeAddress = (universe: Universe, address: `0x${string}`) => { + if (universe === Universe.ETHEREUM || universe === Universe.TRON) { + return address === ZERO_ADDRESS || address === ZERO_ADDRESS_BYTES_32; + } + + // Handle other universes or return false by default + return false; +}; + +const INTENT_EXPIRY = 15 * 60 * 1000; + +const ZERO_ADDRESS: `0x${string}` = '0x0000000000000000000000000000000000000000'; + +const ZERO_ADDRESS_BYTES_32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +export { getLogoFromSymbol, INTENT_EXPIRY, isNativeAddress, ZERO_ADDRESS }; diff --git a/src/sdk/ca-base/errors.ts b/src/sdk/ca-base/errors.ts new file mode 100644 index 00000000..64e73e4e --- /dev/null +++ b/src/sdk/ca-base/errors.ts @@ -0,0 +1,147 @@ +import { Hex } from 'viem'; +import { ERROR_CODES, createError } from './nexusError'; + +export const Errors = { + sdkNotInitialized: () => createError(ERROR_CODES.SDK_NOT_INITIALIZED, 'SDK is not initialized()'), + sdkInitStateNotExpected: (state: string) => + createError(ERROR_CODES.SDK_INIT_STATE_NOT_EXPECTED, 'Unexpected init SDK state', { + details: { state }, + }), + accountConnectionFailed: () => + createError(ERROR_CODES.CONNECT_ACCOUNT_FAILED, 'Account failed to connect from connector'), + environmentNotSupported: (environment: string) => + createError(ERROR_CODES.ENVIRONMENT_NOT_SUPPORTED, 'Environment not supported yet', { + details: { environment }, + }), + environmentNotKnown: () => + createError(ERROR_CODES.ENVIRONMENT_NOT_KNOWN, 'Environment not known/mapped'), + invalidAllowance: (expected: number, got: number) => + createError( + ERROR_CODES.INVALID_VALUES_ALLOWANCE_HOOK, + 'Invalid allowance values passed. The length of allowances should equal input lengths.', + { + context: 'onAllowance:allow()', + details: { expectedLength: expected, receivedLength: got }, + }, + ), + chainNotFound: (chainId: number | bigint) => + createError(ERROR_CODES.CHAIN_NOT_FOUND, `Chain not found: ${chainId}`, { + details: { chainId }, + }), + chainDataNotFound: (chainId: number | bigint) => + createError(ERROR_CODES.CHAIN_DATA_NOT_FOUND, `Chain data not found for chain: ${chainId}`, { + details: { chainId }, + }), + assetNotFound: (tokenSymbol: string) => + createError(ERROR_CODES.ASSET_NOT_FOUND, `Asset not found in UserAssets: ${tokenSymbol}`, { + details: { tokenSymbol }, + }), + internal: (msg: string, details?: Record) => + createError(ERROR_CODES.INTERNAL_ERROR, `Internal error: ${msg}`, { + details, + }), + tokenNotSupported: (address?: string, chainId?: number, additionalMessage?: string) => + createError( + ERROR_CODES.TOKEN_NOT_SUPPORTED, + `Token/Asset with address ${address} is not supported on chain ${chainId}.\n${additionalMessage}`, + { + details: { address, chainId }, + }, + ), + universeNotSupported: () => + createError(ERROR_CODES.UNIVERSE_NOT_SUPPORTED, 'Universe not supported'), + tokenNotFound: (symbol: string, chainId: number) => + createError( + ERROR_CODES.TOKEN_NOT_SUPPORTED, + `Token with symbol ${symbol} not found on chain ${chainId}`, + { + details: { symbol, chainId }, + }, + ), + + tronDepositFailed: (result: unknown) => + createError(ERROR_CODES.TRON_DEPOSIT_FAIL, 'Tron deposit transaction failed.', { + details: { result }, + }), + + tronApprovalFailed: (result: unknown) => + createError(ERROR_CODES.TRON_APPROVAL_FAIL, 'Tron approval transaction failed.', { + details: { result }, + }), + + liquidityTimeout: () => + createError(ERROR_CODES.LIQUIDITY_TIMEOUT, 'Timed out waiting for fulfilment.'), + + userDeniedIntent: () => createError(ERROR_CODES.USER_DENIED_INTENT, 'User rejected the intent.'), + + userRejectedAllowance: () => + createError(ERROR_CODES.USER_DENIED_ALLOWANCE, 'User rejected the allowance.'), + + userRejectedIntentSignature: () => + createError(ERROR_CODES.USER_DENIED_INTENT_SIGNATURE, 'User rejected signing the intent hash.'), + + insufficientBalance: (msg?: string) => + createError(ERROR_CODES.INSUFFICIENT_BALANCE, `Insufficient balance to proceed. ${msg}`), + + walletNotConnected: (walletType: string) => + createError(ERROR_CODES.WALLET_NOT_CONNECTED, `Wallet is not connected for ${walletType}`), + + userRejectedSIWESignature: () => + createError(ERROR_CODES.USER_DENIED_SIWE_SIGNATURE, `User rejected SIWE signature.`), + + vscError: (msg: string, data?: unknown) => + createError(ERROR_CODES.INTERNAL_ERROR, `VSC: ${msg}`, { + details: { + data, + }, + }), + + cosmosError: (msg: string) => createError(ERROR_CODES.COSMOS_ERROR, `COSMOS: ${msg}`), + gasPriceError: (result: unknown) => + createError(ERROR_CODES.FETCH_GAS_PRICE_FAILED, `rpc: estimateMaxFeePerGas failed`, { + details: { + result, + }, + }), + unknownSignatureType: () => createError(ERROR_CODES.UNKNOWN_SIGNATURE, 'Unknown signature type'), + quoteFailed: (message: string) => + createError(ERROR_CODES.QUOTE_FAILED, `Quote failed: ${message}`), + swapFailed: (message: string) => createError(ERROR_CODES.SWAP_FAILED, `Swap failed: ${message}`), + ratesChangedBeyondTolerance: (rate: number | bigint, tolerance: number | bigint) => + createError( + ERROR_CODES.RATES_CHANGED_BEYOND_TOLERANCE, + `Rates changed beyond tolerance. Rate: ${rate}\nTolerance:${tolerance}`, + ), + slippageError: (msg: string) => + createError(ERROR_CODES.SLIPPAGE_EXCEEDED_ALLOWANCE, `rpc: slippage exceeded - ${msg}`), + vaultContractNotFound: (chainId: number | bigint) => + createError( + ERROR_CODES.VAULT_CONTRACT_NOT_FOUND, + `vault contract not found for chain ${chainId.toString()}`, + ), + simulationError: (msg: string) => + createError(ERROR_CODES.SIMULATION_FAILED, `tenderly simulation failed: ${msg}`), + rFFFeeExpired: () => createError(ERROR_CODES.RFF_FEE_EXPIRED, `fee is not adequate`), + destinationRequestHashNotFound: () => + createError( + ERROR_CODES.DESTINATION_REQUEST_HASH_NOT_FOUND, + 'requestHash not found for destination', + ), + transactionTimeout: (timeout: number) => + createError( + ERROR_CODES.TRANSACTION_TIMEOUT, + `⏰ Timeout: Transaction not confirmed within ${timeout}s`, + ), + transactionReverted: (txHash: string) => + createError(ERROR_CODES.TRANSACTION_REVERTED, `Transaction reverted: ${txHash}`), + invalidInput: (msg: string) => createError(ERROR_CODES.INVALID_INPUT, `input invalid: ${msg}`), + invalidAddressLength: (addressType: string, additionalMessage?: string) => + createError( + ERROR_CODES.INVALID_ADDRESS_LENGTH, + `Invalid ${addressType} address length: ${additionalMessage}`, + { details: { type: addressType } }, + ), + noBalanceForAddress: (address: Hex) => { + createError(ERROR_CODES.NO_BALANCE_FOR_ADDRESS, `no balance found for user: ${address}`); + }, +}; diff --git a/src/sdk/ca-base/index.ts b/src/sdk/ca-base/index.ts new file mode 100644 index 00000000..744fe553 --- /dev/null +++ b/src/sdk/ca-base/index.ts @@ -0,0 +1,18 @@ +export { CA } from './ca'; + +export type { + AllowanceHookSources, + EthereumProvider, + ReadableIntent as Intent, + NetworkConfig, + OnAllowanceHook, + onAllowanceHookSource, + OnIntentHook, + RequestArguments, + RFF, + UserAssetDatum as UserAsset, + BridgeStepType, + SwapStepType, +} from '../../commons'; + +export { Environment as Network, RequestForFunds } from '@avail-project/ca-common'; diff --git a/src/sdk/ca-base/nexusError.ts b/src/sdk/ca-base/nexusError.ts new file mode 100644 index 00000000..3afc23bb --- /dev/null +++ b/src/sdk/ca-base/nexusError.ts @@ -0,0 +1,137 @@ +import { AnyValueMap, SeverityNumber } from '@opentelemetry/api-logs'; +import { telemetryLogger } from './telemetry'; + +export interface NexusErrorData { + context?: string; // Where or why it happened + cause?: unknown; // Optional nested error + details?: Record; // Specific structured info +} + +export class NexusError extends Error { + readonly code: ErrorCode; + readonly data?: NexusErrorData; + + constructor(code: ErrorCode, message: string, data?: NexusErrorData) { + super(message); + this.name = 'NexusError'; + this.code = code; + this.data = data; + } + + toJSON() { + return { + name: this.name, + code: this.code, + message: this.message, + data: this.data, + }; + } +} + +export const ERROR_CODES = { + INVALID_VALUES_ALLOWANCE_HOOK: 'INVALID_VALUES_ALLOWANCE_HOOK', + SDK_NOT_INITIALIZED: 'SDK_NOT_INITIALIZED', + SDK_INIT_STATE_NOT_EXPECTED: 'SDK_INIT_STATE_NOT_EXPECTED', + CHAIN_NOT_FOUND: 'CHAIN_NOT_FOUND', + CHAIN_DATA_NOT_FOUND: 'CHAIN_DATA_NOT_FOUND', + RATES_CHANGED_BEYOND_TOLERANCE: 'RATES_CHANGED_BEYOND_TOLERANCE', + ASSET_NOT_FOUND: 'ASSET_NOT_FOUND', + COSMOS_ERROR: 'COSMOS_ERROR', + INTERNAL_ERROR: 'INTERNAL_ERROR', + TOKEN_NOT_SUPPORTED: 'TOKEN_NOT_SUPPORTED', + UNIVERSE_NOT_SUPPORTED: 'UNIVERSE_NOT_SUPPORTED', + ENVIRONMENT_NOT_SUPPORTED: 'ENVIRONMENT_NOT_SUPPORTED', + ENVIRONMENT_NOT_KNOWN: 'ENVIRONMENT_NOT_KNOWN', + UNKNOWN_SIGNATURE: 'UNKNOWN_SIGNATURE', + TRON_DEPOSIT_FAIL: 'TRON_DEPOSIT_FAIL', + TRON_APPROVAL_FAIL: 'TRON_APPROVAL_FAIL', + LIQUIDITY_TIMEOUT: 'LIQUIDITY_TIMEOUT', + USER_DENIED_INTENT: 'USER_DENIED_INTENT', + USER_DENIED_ALLOWANCE: 'USER_DENIED_ALLOWANCE', + USER_DENIED_INTENT_SIGNATURE: 'USER_DENIED_INTENT_SIGNATURE', + INSUFFICIENT_BALANCE: 'INSUFFICIENT_BALANCE', + REFUND_FAILED: 'REFUND_FAILED', + WALLET_NOT_CONNECTED: 'WALLET_NOT_CONNECTED', + USER_DENIED_SIWE_SIGNATURE: 'USER_DENIED_SIWE_SIGNATURE', + FETCH_GAS_PRICE_FAILED: 'FETCH_GAS_PRICE_FAILED', + SIMULATION_FAILED: 'SIMULATION_FAILED', + QUOTE_FAILED: 'QUOTE_FAILED', + SWAP_FAILED: 'SWAP_FAILED', + CONNECT_ACCOUNT_FAILED: 'CONNECT_ACCOUNT_FAILED', + VAULT_CONTRACT_NOT_FOUND: 'VAULT_CONTRACT_NOT_FOUND', + SLIPPAGE_EXCEEDED_ALLOWANCE: 'SLIPPAGE_EXCEEDED_ALLOWANCE', + ALLOWANCE_SETTING_ERROR: 'ALLOWANCE_SETTING_ERROR', + REFUND_CHECK_ERROR: 'REFUND_CHECK_ERROR', + DESTINATION_REQUEST_HASH_NOT_FOUND: 'DESTINATION_REQUEST_HASH_NOT_FOUND', + DESTINATION_SWEEP_ERROR: 'DESTINATION_SWEEP_ERROR', + RFF_FEE_EXPIRED: 'RFF_FEE_EXPIRED', + FEE_GRANT_REQUESTED: 'FEE_GRANT_REQUESTED', + INVALID_INPUT: 'INVALID_INPUT', + INVALID_ADDRESS_LENGTH: 'INVALID_ADDRESS_LENGTH', + NO_BALANCE_FOR_ADDRESS: 'NO_BALANCE_FOR_ADDRESS', + TRANSACTION_TIMEOUT: 'TRANSACTION_TIMEOUT', + TRANSACTION_REVERTED: 'TRANSACTION_REVERTED', + TRANSACTION_CHECK_ERROR: 'TRANSACTION_CHECK_ERROR', +} as const; + +export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES]; + +export function createError(code: ErrorCode, message: string, data?: NexusErrorData): NexusError { + const nexusError = new NexusError(code, message, data); + try { + telemetryLogger?.emit({ + body: message, + severityNumber: SeverityNumber.ERROR, + severityText: 'ERROR', + attributes: { + data: nexusError.data, + cause: code, + stackTrace: nexusError.stack, + } as AnyValueMap, + }); + } catch {} + return nexusError; +} + +/* --- Expected handling --- + +function handleNexusError(err: unknown) { + if (err instanceof NexusError) { + console.error(`[${err.code}] ${err.message}`); + + if (err.data?.context) { + console.error(`Context: ${err.data.context}`); + } + + if (err.data?.details) { + console.error('Details:', err.data.details); + } + + switch (err.code) { + case ERROR_CODES.USER_DENIED_INTENT: + case ERROR_CODES.USER_DENIED_ALLOWANCE: + alert('You rejected the transaction. Please try again.'); + break; + + case ERROR_CODES.INSUFFICIENT_BALANCE: + alert('Your wallet does not have enough funds.'); + break; + + case ERROR_CODES.TRON_DEPOSIT_FAIL: + console.warn('Deposit failed'); + // Possibly ask user to retry + break; + + default: + // Unknown but typed error + console.error('Unexpected NexusError:', err.toJSON()); + } + + // Optional: + logErrorToService(err.toJSON()); + } else { + // Non-Nexus errors (network, library, etc.) + console.error('Unexpected error:', err); + } +} +*/ diff --git a/src/sdk/ca-base/query/bridgeAndExecute.ts b/src/sdk/ca-base/query/bridgeAndExecute.ts new file mode 100644 index 00000000..d343cf97 --- /dev/null +++ b/src/sdk/ca-base/query/bridgeAndExecute.ts @@ -0,0 +1,690 @@ +import { + BridgeAndExecuteParams, + BridgeAndExecuteResult, + BridgeResult, + logger, + Tx, + Chain, + ChainListType, + BridgeParams, + UserAssetDatum, + ExecuteParams, + ExecuteResult, + ExecuteSimulation, + OnEventParam, + BridgeAndExecuteSimulationResult, + NEXUS_EVENTS, + BRIDGE_STEPS, + BridgeStepType, + BeforeExecuteHook, + ReadableIntent, + TokenInfo, +} from '../../../commons'; +import { + createPublicClient, + Hex, + http, + parseGwei, + PublicClient, + serializeTransaction, + toHex, + WalletClient, +} from 'viem'; +import { + createExplorerTxURL, + mulDecimals, + UserAssets, + waitForTxReceipt, + generateStateOverride, + switchChain, + erc20GetAllowance, + pctAdditionToBigInt, + getL1Fee, + // divideBigInt, + getPctGasBufferByChain, +} from '../utils'; +import { packERC20Approve } from '../swap/utils'; +import { BackendSimulationClient } from '../../../integrations/tenderly'; +import BridgeHandler from '../requestHandlers/bridge'; +import { Errors } from '../errors'; +import { isNativeAddress } from '../constants'; +import { Universe } from '@avail-project/ca-common'; + +class BridgeAndExecuteQuery { + constructor( + private readonly chainList: ChainListType, + private readonly evmClient: WalletClient, + private readonly bridge: (input: BridgeParams, options?: OnEventParam) => BridgeHandler, + private readonly getUnifiedBalances: () => Promise, + private readonly simulationClient: BackendSimulationClient, + ) {} + + private async estimateBridgeAndExecute(params: BridgeAndExecuteParams) { + const { toChainId, token: tokenSymbol, amount, execute } = params; + + const { token, chain: dstChain } = this.chainList.getChainAndTokenFromSymbol( + params.toChainId, + tokenSymbol, + ); + if (!token) { + throw Errors.tokenNotFound(tokenSymbol, toChainId); + } + + const address = (await this.evmClient.getAddresses())[0]; + let txs: Tx[] = []; + + const { tx, approvalTx, dstPublicClient } = await this.createTxsForExecute( + { ...execute, toChainId: params.toChainId }, + address, + ); + + logger.debug('BridgeAndExecute:2', { + tx, + approvalTx, + }); + + if (approvalTx) { + txs.push(approvalTx); + } + + txs.push(tx); + + const determineGasUsed = params.execute.gas + ? Promise.resolve({ approvalGas: approvalTx ? 70_000n : 0n, txGas: params.execute.gas }) + : this.simulateBundle({ + txs, + amount: params.amount, + userAddress: address, + chainId: dstChain.id, + tokenAddress: token.contractAddress, + tokenSymbol: params.token ?? 'ETH', + }).then(({ gas }) => { + if (approvalTx) { + return { + approvalGas: gas[0], + txGas: gas[1], + }; + } else { + return { + approvalGas: 0n, + txGas: gas[0], + }; + } + }); + + const determineGasFee = params.execute.gasPrice + ? Promise.resolve({ + maxFeePerGas: params.execute.gasPrice, + gasPrice: params.execute.gasPrice, + maxPriorityFeePerGas: 0n, + }) + : dstPublicClient.estimateFeesPerGas(); + + // 5. simulate approval(?) and execution + fetch gasPrice + fetch unified balance + const [gasUsed, gasFeeEstimate, balances, l1Fee] = await Promise.all([ + determineGasUsed, + determineGasFee, + this.getUnifiedBalances(), + getL1Fee( + dstChain, + serializeTransaction({ + chainId: dstChain.id, + data: execute.data ?? '0x', + value: execute.value, + to: execute.to, + type: 'eip1559', + }), + ), + ]); + + // gasLimit = 1.3 * gasUsed (30% buffer) + const pctBuffer = getPctGasBufferByChain(dstChain.id); + const approvalGas = pctAdditionToBigInt(gasUsed.approvalGas, pctBuffer); + const txGas = pctAdditionToBigInt(gasUsed.txGas, pctBuffer); + + let gasPrice = gasFeeEstimate.maxFeePerGas ?? gasFeeEstimate.gasPrice ?? 0n; + if (gasPrice === 0n) { + throw Errors.gasPriceError({ + chainId: dstChain.id, + }); + } + + // Giving more is not an issue since it will be refunded - other than monad + // gasPrice = (baseFee + 5 * maxPriorityFeePerGas) + gasPrice += + (gasFeeEstimate.maxPriorityFeePerGas === 0n + ? parseGwei('2') + : gasFeeEstimate.maxPriorityFeePerGas) * 4n; + + const gasFee = (approvalGas + txGas) * gasPrice + l1Fee; + + logger.debug('BridgeAndExecute:3', { + increasedGas: approvalGas + txGas, + approvalGas, + txGas, + gasFeeEstimate, + gasPrice, + balances, + l1Fee, + }); + + // 6. Determine gas or token needed via bridge + const { skipBridge, tokenAmount, gasAmount } = await this.calculateOptimalBridgeAmount( + dstChain, + token.contractAddress, + token.decimals, + amount, + gasFee, + balances, + ); + + return { + dstPublicClient, + dstChain, + amount: { + token: tokenAmount, + gas: gasAmount, + }, + skipBridge, + tx, + approvalTx, + gas: { + tx: txGas, + approval: approvalGas, + }, + token, + address, + gasFee, + gasPrice, + }; + } + + public async simulateBridgeAndExecute( + params: BridgeAndExecuteParams, + ): Promise { + const { gasFee, token, skipBridge, amount, gas, gasPrice } = + await this.estimateBridgeAndExecute(params); + + logger.debug('BridgeAndExecute:4:CalculateOptimalBridgeAmount', { + skipBridge, + amount, + gas, + }); + + let bridgeResult: null | { + intent: ReadableIntent; + token: TokenInfo; + } = null; + + // 7. If bridge is required then simulate bridge + if (!skipBridge) { + bridgeResult = await this.simulateBridgeWrapper({ + token: token.symbol, + amount: amount.token, + toChainId: params.toChainId, + sourceChains: params.sourceChains, + gas: amount.gas, + }); + } + + // 8. Return result + const result: BridgeAndExecuteSimulationResult = { + bridgeSimulation: bridgeResult, + executeSimulation: { + gasUsed: gas.approval + gas.tx, + gasPrice, + gasFee, + }, + }; + + return result; + } + + /** + * Bridge and execute operation - combines bridge and execute with proper sequencing + * Checks balance and gas present on destination chain & bridges (required - available) token + gas. + * Simulates using tenderly for gas and gasPrice if gas and gasPrice not provided. + */ + public async bridgeAndExecute( + params: BridgeAndExecuteParams, + options?: OnEventParam & BeforeExecuteHook, + ): Promise { + const { + dstPublicClient, + dstChain, + address, + token, + skipBridge, + tx, + approvalTx, + amount, + gas, + gasPrice, + } = await this.estimateBridgeAndExecute(params); + + logger.debug('BridgeAndExecute:4:CalculateOptimalBridgeAmount', { + skipBridge, + amount, + approval: { + tx: approvalTx, + gas: gas.approval, + }, + tx: { + tx, + gas: gas.tx, + }, + gasPrice, + }); + + const executeSteps: BridgeStepType[] = [ + BRIDGE_STEPS.EXECUTE_TRANSACTION_SENT, + BRIDGE_STEPS.EXECUTE_TRANSACTION_CONFIRMED, + ]; + + // Approval and execute + if (approvalTx) { + executeSteps.unshift(BRIDGE_STEPS.EXECUTE_APPROVAL_STEP); + approvalTx.gas = gas.approval; + } + + tx.gas = gas.tx; + + let bridgeResult: BridgeResult = { + explorerUrl: '', + }; + + // 7. If bridge is required then bridge + if (!skipBridge) { + bridgeResult = await this.bridgeWrapper( + { + token: token.symbol, + amount: amount.token, + toChainId: params.toChainId, + sourceChains: params.sourceChains, + gas: amount.gas, + }, + { + onEvent: (event) => { + if (options && options.onEvent) { + if (event.name === NEXUS_EVENTS.STEPS_LIST) { + options.onEvent({ + name: NEXUS_EVENTS.STEPS_LIST, + args: event.args.concat(executeSteps), + }); + } else { + options.onEvent(event); + } + } + }, + }, + ); + } else { + if (options && options.onEvent) { + options.onEvent({ name: NEXUS_EVENTS.STEPS_LIST, args: executeSteps }); + } + } + + if (options?.beforeExecute) { + const response = await options.beforeExecute(); + logger.debug('BeforeExecuteHook', { + response, + }); + if (response.data) { + tx.data = response.data; + } + + if (response.value) { + tx.value = response.value; + } + + if (response.gas && response.gas !== 0n) { + tx.gas = response.gas; + } + } + + // 8. Execute the transaction + const executeResponse = await this.sendTx( + { + approvalTx, + tx, + gasPrice, + }, + { + emit: options?.onEvent, + chain: dstChain, + dstPublicClient, + address, + receiptTimeout: params.receiptTimeout, + requiredConfirmations: params.requiredConfirmations, + waitForReceipt: params.waitForReceipt, + client: this.evmClient, + }, + ); + + logger.debug('BridgeAndExecute:5', { + executeResponse, + }); + + // 9. Return result + const result: BridgeAndExecuteResult = { + executeTransactionHash: executeResponse.txHash, + executeExplorerUrl: createExplorerTxURL( + executeResponse.txHash, + dstChain.blockExplorers!.default.url, + ), + approvalTransactionHash: executeResponse.approvalHash, + bridgeExplorerUrl: bridgeResult.explorerUrl, + toChainId: params.toChainId, + bridgeSkipped: skipBridge, + }; + + return result; + } + + private async createTxsForExecute(params: ExecuteParams, address: Hex) { + // 1. Check if dst chain data is available + const dstChain = this.chainList.getChainByID(params.toChainId); + if (!dstChain) { + throw Errors.chainNotFound(params.toChainId); + } + + const dstPublicClient = createPublicClient({ + transport: http(dstChain.rpcUrls.default.http[0]), + }); + + // 2. Check if token is supported + let approvalTx: Tx | null = null; + if (params.tokenApproval) { + const token = this.chainList.getTokenInfoBySymbol( + params.toChainId, + params.tokenApproval.token, + ); + if (!token) { + throw Errors.tokenNotFound(params.tokenApproval.token, params.toChainId); + } + const spender = params.tokenApproval.spender; + const currentAllowance = await erc20GetAllowance( + { + contractAddress: token.contractAddress, + spender: params.tokenApproval.spender, + owner: address, + }, + dstPublicClient, + ); + + const requiredAllowance = BigInt(params.tokenApproval.amount); + if (currentAllowance < requiredAllowance) { + approvalTx = { + to: token.contractAddress, + data: packERC20Approve(spender, requiredAllowance), + value: 0n, + }; + } + } + + // 4. Encode execute tx + const tx: Tx = { + to: params.to, + value: params.value ?? 0n, + data: params.data ?? '0x', + }; + + return { tx, approvalTx, dstChain, dstPublicClient }; + } + + public async execute(params: ExecuteParams, options?: OnEventParam) { + const address = (await this.evmClient.getAddresses())[0]; + const { dstPublicClient, dstChain, approvalTx, tx } = await this.createTxsForExecute( + params, + address, + ); + + // 1. Execute the transaction + const executeResponse = await this.sendTx( + { + approvalTx, + tx, + }, + { + emit: options?.onEvent, + chain: dstChain, + dstPublicClient, + address, + receiptTimeout: params.receiptTimeout, + requiredConfirmations: params.requiredConfirmations, + waitForReceipt: params.waitForReceipt, + client: this.evmClient, + }, + ); + + const result: ExecuteResult = { + chainId: params.toChainId, + explorerUrl: createExplorerTxURL( + executeResponse.txHash, + dstChain.blockExplorers!.default.url, + ), + transactionHash: executeResponse.txHash, + approvalTransactionHash: executeResponse.approvalHash, + receipt: executeResponse.receipt, + confirmations: params.requiredConfirmations, + effectiveGasPrice: String(0), + gasUsed: String(executeResponse.receipt?.gasUsed ?? 0n), + }; + + return result; + } + + public async simulateExecute(params: ExecuteParams, address: Hex): Promise { + const { dstPublicClient, tx } = await this.createTxsForExecute(params, address); + + const [gasUsed, feeEstimate] = await Promise.all([ + dstPublicClient.estimateGas({ + to: tx.to, + data: tx.data, + value: tx.value, + account: address, + }), + dstPublicClient.estimateFeesPerGas(), + ]); + + const gasPrice = feeEstimate.maxFeePerGas ?? feeEstimate.gasPrice ?? 0n; + if (gasPrice === 0n) { + throw Errors.gasPriceError({}); + } + + return { + gasUsed: gasUsed, + gasPrice: gasPrice, + gasFee: gasUsed * gasPrice, + }; + } + + /** + * Calculate optimal bridge amount based on destination chain balance + * Returns the exact amount needed to bridge, or indicates if bridge can be skipped entirely + */ + private async calculateOptimalBridgeAmount( + chain: Chain, + tokenAddress: Hex, + tokenDecimals: number, + requiredTokenAmount: bigint, + requiredGasAmount: bigint, + assets: UserAssetDatum[], + ): Promise<{ skipBridge: boolean; tokenAmount: bigint; gasAmount: bigint }> { + let skipBridge = true; + let tokenAmount = requiredTokenAmount; + let gasAmount = requiredGasAmount; + const assetList = new UserAssets(assets); + const { destinationAssetBalance, destinationGasBalance } = assetList.getAssetDetails( + chain, + tokenAddress, + ); + + const destinationTokenAmount = mulDecimals(destinationAssetBalance, tokenDecimals); + const destinationGasAmount = mulDecimals(destinationGasBalance, chain.nativeCurrency.decimals); + + logger.debug('calculateOptimalBridgeAmount', { + destinationTokenAmount, + requiredTokenAmount, + destinationGasAmount, + requiredGasAmount, + }); + if (isNativeAddress(Universe.ETHEREUM, tokenAddress)) { + const totalRequired = requiredGasAmount + requiredTokenAmount; + if (destinationGasAmount < totalRequired) { + skipBridge = false; + // Total missing native amount + const difference = totalRequired - destinationGasAmount; + + // First cover missing TOKEN + const missingToken = + requiredTokenAmount > destinationTokenAmount + ? requiredTokenAmount - destinationTokenAmount + : 0n; + + // Then cover missing GAS out of the remaining deficit + const gasPart = difference > missingToken ? difference - missingToken : 0n; + + tokenAmount = missingToken; + gasAmount = gasPart; + } + } else { + const isGasBridgeRequired = destinationGasAmount < requiredGasAmount; + const isTokenBridgeRequired = destinationTokenAmount < requiredTokenAmount; + + if (isGasBridgeRequired || isTokenBridgeRequired) { + skipBridge = false; + + tokenAmount = + destinationTokenAmount < requiredTokenAmount + ? requiredTokenAmount - destinationTokenAmount + : 0n; + + gasAmount = + destinationGasAmount < requiredGasAmount ? requiredGasAmount - destinationGasAmount : 0n; + } + } + return { + skipBridge, + tokenAmount, + gasAmount, + }; + } + + private async simulateBundle(input: { + tokenSymbol: string; + tokenAddress: Hex; + amount: bigint; + txs: Tx[]; + chainId: number; + userAddress: Hex; + }) { + const overrides = generateStateOverride(input); + return this.simulationClient.simulateBundleV2({ + chainId: String(input.chainId), + simulations: input.txs.map((tx, i) => ({ + type: 'something', + from: input.userAddress, + to: tx.to, + data: tx.data, + value: toHex(tx.value), + stepId: `sim_${i}`, + enableStateOverride: true, // ???????? + stateOverride: overrides, + })), + }); + } + + private async sendTx( + params: { + tx: Tx; + approvalTx: Tx | null; + gasPrice?: bigint; + }, + options: { + emit?: OnEventParam['onEvent']; + chain: Chain; + dstPublicClient: PublicClient; + address: Hex; + client: WalletClient; + waitForReceipt?: boolean; + receiptTimeout?: number; + requiredConfirmations?: number; + }, + ) { + const { waitForReceipt = true, receiptTimeout = 300000, requiredConfirmations = 1 } = options; + await switchChain(options.client, options.chain); + + let approvalHash; + if (params.approvalTx) { + approvalHash = await options.client.sendTransaction({ + ...params.approvalTx, + account: options.address, + chain: options.chain, + }); + + await waitForTxReceipt(approvalHash, options.dstPublicClient, 1); + if (options.emit) { + options.emit({ + name: NEXUS_EVENTS.STEP_COMPLETE, + args: BRIDGE_STEPS.EXECUTE_APPROVAL_STEP, + }); + } + } + + const txHash = await options.client.sendTransaction({ + ...params.tx, + account: options.address, + chain: options.chain, + }); + + if (options.emit) { + options.emit({ + name: NEXUS_EVENTS.STEP_COMPLETE, + args: BRIDGE_STEPS.EXECUTE_TRANSACTION_SENT, + }); + } + + let receipt; + if (waitForReceipt) { + receipt = await waitForTxReceipt( + txHash, + options.dstPublicClient, + requiredConfirmations, + receiptTimeout, + ); + + if (options.emit) { + options.emit({ + name: NEXUS_EVENTS.STEP_COMPLETE, + args: BRIDGE_STEPS.EXECUTE_TRANSACTION_CONFIRMED, + }); + } + } + + return { + txHash, + receipt, + approvalHash, + }; + } + + private readonly bridgeWrapper = async ( + params: BridgeParams, + options?: OnEventParam, + ): Promise => { + const handler = this.bridge(params, options); + const result = await handler.execute(); + return { + explorerUrl: result.explorerURL, + }; + }; + + private readonly simulateBridgeWrapper = async (params: BridgeParams) => { + const handler = this.bridge(params); + const result = await handler.simulate(); + return result; + }; +} + +export { BridgeAndExecuteQuery }; diff --git a/src/sdk/ca-base/query/bridgeAndTransfer.ts b/src/sdk/ca-base/query/bridgeAndTransfer.ts new file mode 100644 index 00000000..7c86876d --- /dev/null +++ b/src/sdk/ca-base/query/bridgeAndTransfer.ts @@ -0,0 +1,41 @@ +import { ChainListType, BridgeAndExecuteParams, Tx, TransferParams } from '../../../commons'; +import { encodeFunctionData } from 'viem'; +import { ERC20ABI } from '@avail-project/ca-common'; +import { Errors } from '../errors'; + +const createBridgeAndTransferParams = ( + input: TransferParams, + chainList: ChainListType, +): BridgeAndExecuteParams => { + const { token } = chainList.getChainAndTokenFromSymbol(input.toChainId, input.token); + if (!token) { + throw Errors.tokenNotFound(input.token, input.toChainId); + } + + const tx: Tx = token.isNative + ? { + to: input.recipient, + value: input.amount, + data: '0x', + gas: 21_000n, + } + : { + to: token.contractAddress, + value: 0n, + data: encodeFunctionData({ + abi: ERC20ABI, + functionName: 'transfer', + args: [input.recipient, input.amount], + }), + gas: 80_000n, + }; + + return { + toChainId: input.toChainId, + amount: input.amount, + token: input.token, + execute: tx, + }; +}; + +export { createBridgeAndTransferParams }; diff --git a/src/sdk/ca-base/query/index.ts b/src/sdk/ca-base/query/index.ts new file mode 100644 index 00000000..4d3eacf5 --- /dev/null +++ b/src/sdk/ca-base/query/index.ts @@ -0,0 +1 @@ +export * from "./bridgeAndTransfer"; diff --git a/src/sdk/ca-base/requestHandlers/bridge.ts b/src/sdk/ca-base/requestHandlers/bridge.ts new file mode 100644 index 00000000..ca08897c --- /dev/null +++ b/src/sdk/ca-base/requestHandlers/bridge.ts @@ -0,0 +1,1020 @@ +import { + ChaindataMap, + ERC20ABI, + EVMVaultABI, + OmniversalChainID, + PermitVariant, + Universe, +} from '@avail-project/ca-common'; +import Decimal from 'decimal.js'; +import Long from 'long'; +import { + ContractFunctionExecutionError, + createPublicClient, + encodeFunctionData, + Hex, + hexToBytes, + JsonRpcAccount, + maxUint256, + parseSignature, + toHex, + TransactionReceipt, + UserRejectedRequestError, + webSocket, +} from 'viem'; +import { isNativeAddress } from '../constants'; +import { createSteps } from '../steps'; +import { + Intent, + onAllowanceHookSource, + SetAllowanceInput, + SponsoredApprovalDataArray, + Chain, + TokenInfo, + getLogger, + IBridgeOptions, + NEXUS_EVENTS, + BridgeStepType, + BRIDGE_STEPS, +} from '../../../commons'; +import { + convertGasToToken, + convertIntent, + convertTo32Bytes, + cosmosCreateRFF, + createDepositDoubleCheckTx, + createPublicClientWithFallback, + equalFold, + FeeStore, + fetchPriceOracle, + getAllowances, + getExplorerURL, + getFeeStore, + mulDecimals, + removeIntentHashFromStore, + signPermitForAddressAndValue, + storeIntentHashToStore, + switchChain, + vscCreateRFF, + vscCreateSponsoredApprovals, + vscPublishRFF, + waitForTxReceipt, + UserAssets, + waitForTronDepositTxConfirmation, + waitForTronApprovalTxConfirmation, + divDecimals, + requestTimeout, + cosmosFillCheck, + waitForIntentFulfilment, + createRFFromIntent, + retrieveAddress, + getBalances, + // createDeadlineFromNow, +} from '../utils'; +import { TronWeb } from 'tronweb'; +import { Errors } from '../errors'; +import { ERROR_CODES, NexusError } from '../nexusError'; + +type Params = { + recipient?: Hex; + dstChain: Chain; + dstToken: TokenInfo; + tokenAmount: bigint; + nativeAmount: bigint; + sourceChains: number[]; +}; + +const logger = getLogger(); + +class BridgeHandler { + protected steps: BridgeStepType[] = []; + protected params: Required; + constructor(params: Params, readonly options: IBridgeOptions) { + this.params = { + ...params, + recipient: params.recipient ?? retrieveAddress(params.dstChain.universe, options), + }; + console.log({ params: this.params, options }); + } + + public async simulate() { + const intent = await this.buildIntent(this.params.sourceChains); + return { + intent: convertIntent(intent, this.params.dstToken, this.options.chainList), + token: this.params.dstToken, + }; + } + + private readonly buildIntent = async (sourceChains: number[] = []) => { + console.time('process:preIntentSteps'); + + console.time('preIntentSteps:API'); + const [balances, oraclePrices, feeStore] = await Promise.all([ + getBalances({ + networkHint: this.options.networkConfig.NETWORK_HINT, + vscDomain: this.options.networkConfig.VSC_DOMAIN, + evmAddress: this.options.evm.address, + chainList: this.options.chainList, + tronAddress: this.options.tron?.address, + isCA: true, + }), + fetchPriceOracle(this.options.networkConfig.GRPC_URL), + getFeeStore(this.options.networkConfig.GRPC_URL), + ]); + + logger.debug('Step 0: BuildIntent', { + balances, + oraclePrices, + feeStore, + }); + + console.timeEnd('preIntentSteps:API'); + logger.debug('Step 1:', { + balances, + feeStore, + oraclePrices, + }); + + console.time('preIntentSteps: Parse'); + + const { assets } = balances; + // Step 2: parse simulation results + + const userAssets = new UserAssets(assets); + + console.time('preIntentSteps: CalculateGas'); + + const nativeAmountInDecimal = divDecimals( + this.params.nativeAmount, + this.params.dstChain.nativeCurrency.decimals, + ); + + const tokenAmountInDecimal = divDecimals( + this.params.tokenAmount, + this.params.dstToken.decimals, + ); + + const gasInToken = convertGasToToken( + this.params.dstToken, + oraclePrices, + this.params.dstChain.id, + this.params.dstChain.universe, + nativeAmountInDecimal, + ); + + console.timeEnd('preIntentSteps: CalculateGas'); + + logger.debug('preIntent:1', { + gasInNative: nativeAmountInDecimal.toFixed(), + gasInToken: gasInToken.toFixed(), + }); + + // Step 4: create intent + console.time('preIntentSteps: CreateIntent'); + const intent = await this.createIntent({ + amount: tokenAmountInDecimal, + assets: userAssets, + feeStore, + gas: nativeAmountInDecimal, + gasInToken, + sourceChains, + token: this.params.dstToken, + }); + console.timeEnd('preIntentSteps: CreateIntent'); + console.timeEnd('process:preIntentSteps'); + + if (intent.isAvailableBalanceInsufficient) { + throw Errors.insufficientBalance(); + } + + return intent; + }; + + private filterInsufficientAllowanceSources( + intent: Intent, + allowances: Awaited>, + ) { + const sources: onAllowanceHookSource[] = []; + for (const s of intent.sources) { + if ( + s.chainID === intent.destination.chainID || + isNativeAddress(s.universe, s.tokenContract) + ) { + continue; + } + + const chain = this.options.chainList.getChainByID(s.chainID); + if (!chain) { + throw Errors.chainNotFound(s.chainID); + } + + const token = this.options.chainList.getTokenByAddress(s.chainID, s.tokenContract); + if (!token) { + throw Errors.tokenNotSupported(s.tokenContract, s.chainID); + } + + const requiredAllowance = mulDecimals(s.amount, token.decimals); + const currentAllowance = allowances[s.chainID] ?? 0n; + + logger.debug('getUnallowedSources:1', { + currentAllowance: currentAllowance.toString(), + requiredAllowance: requiredAllowance.toString(), + token, + }); + + if (requiredAllowance > currentAllowance) { + const d = { + allowance: { + current: divDecimals(allowances[s.chainID], token.decimals).toFixed(token.decimals), + currentRaw: currentAllowance, + minimum: s.amount.toFixed(token.decimals), + minimumRaw: requiredAllowance, + }, + chain: { + id: chain.id, + logo: chain.custom.icon, + name: chain.name, + }, + token: { + contractAddress: token.contractAddress, + decimals: token.decimals, + logo: token.logo || '', + name: token.name, + symbol: token.symbol, + }, + }; + sources.push(d); + } + } + return sources; + } + + public execute = async ( + shouldRetryOnFailure = true, + ): Promise<{ explorerURL: string; intentID: Long }> => { + let intent = await this.buildIntent(this.params.sourceChains); + + const allowances = await getAllowances(intent.allSources, this.options.chainList); + + let insufficientAllowanceSources = this.filterInsufficientAllowanceSources(intent, allowances); + this.createExpectedSteps(intent, insufficientAllowanceSources); + + let accepted = false; + const refresh = async (sourceChains?: number[]) => { + if (accepted) { + logger.warn('Intent refresh called after acceptance'); + return convertIntent(intent, this.params.dstToken, this.options.chainList); + } + + intent = await this.buildIntent(sourceChains); + if (intent.isAvailableBalanceInsufficient) { + throw Errors.insufficientBalance(); + } + insufficientAllowanceSources = this.filterInsufficientAllowanceSources(intent, allowances); + this.createExpectedSteps(intent, insufficientAllowanceSources); + + return convertIntent(intent, this.params.dstToken, this.options.chainList); + }; + + // wait for intent acceptance hook + await new Promise((resolve, reject) => { + const allow = () => { + accepted = true; + return resolve('User allowed intent'); + }; + + const deny = () => { + return reject(Errors.userDeniedIntent()); + }; + + this.options.hooks.onIntent({ + allow, + deny, + intent: convertIntent(intent, this.params.dstToken, this.options.chainList), + refresh, + }); + }); + + this.markStepDone(BRIDGE_STEPS.INTENT_ACCEPTED); + + console.time('process:AllowanceHook'); + + // Step 5: set allowance if not set + await this.waitForOnAllowanceHook(insufficientAllowanceSources); + console.timeEnd('process:AllowanceHook'); + + // Step 6: Process intent + logger.debug('intent', { intent }); + + const response = await this.processRFF(intent); + if (response.retry) { + logger.debug('rff fee expired, going to rebuild intent...'); + if (shouldRetryOnFailure) { + // If fee expired go back and rebuild intent if first time + return this.execute(false); + } else { + // Something else is wrong, retries probably wont fix it - so just throw + throw Errors.rFFFeeExpired(); + } + } + + const { explorerURL, intentID, requestHash, waitForDoubleCheckTx } = response; + + // Step 7: Wait for fill + storeIntentHashToStore(this.options.evm.address, intentID.toNumber()); + await this.waitForFill(requestHash, intentID, waitForDoubleCheckTx); + removeIntentHashFromStore(this.options.evm.address, intentID); + + this.markStepDone(BRIDGE_STEPS.INTENT_FULFILLED); + + if (this.params.dstChain.universe === Universe.ETHEREUM) { + await switchChain(this.options.evm.client, this.params.dstChain); + } + + return { explorerURL, intentID }; + }; + + private async waitForFill( + requestHash: `0x${string}`, + intentID: Long, + waitForDoubleCheckTx: () => Promise, + ) { + waitForDoubleCheckTx(); + + const ac = new AbortController(); + let promisesToRace = [ + requestTimeout(3, ac), + cosmosFillCheck( + intentID, + this.options.networkConfig.GRPC_URL, + this.options.networkConfig.COSMOS_URL, + ac, + ), + ]; + + // Use eth_subscribe to read fill events if destination is EVM - usually the fastest + if (this.params.dstChain.universe === Universe.ETHEREUM) { + promisesToRace.push( + waitForIntentFulfilment( + createPublicClient({ + transport: webSocket(this.params.dstChain.rpcUrls.default.webSocket[0]), + }), + this.options.chainList.getVaultContractAddress(this.params.dstChain.id), + requestHash, + ac, + ), + ); + } + await Promise.race(promisesToRace); + logger.debug('Fill completed'); + } + + private async processRFF(intent: Intent): Promise< + | { retry: true } + | { + retry: false; + explorerURL: string; + intentID: Long; + requestHash: Hex; + waitForDoubleCheckTx: () => any; + } + > { + const { msgBasicCosmos, omniversalRFF, signatureData, sources, universes } = + await createRFFromIntent(intent, this.options, this.params.dstChain.universe); + + this.markStepDone(BRIDGE_STEPS.INTENT_HASH_SIGNED); + + logger.debug('processRFF:3', { msgBasicCosmos }); + + const intentID = await cosmosCreateRFF({ + address: this.options.cosmos.address, + msg: msgBasicCosmos, + client: this.options.cosmos.client, + }); + + const explorerURL = getExplorerURL(this.options.networkConfig.EXPLORER_URL, intentID); + this.markStepDone(BRIDGE_STEPS.INTENT_SUBMITTED(explorerURL, intentID.toNumber())); + + const tokenCollections: number[] = []; + for (const [i, s] of sources.entries()) { + if (!isDeposit(s.universe, s.tokenAddress)) { + tokenCollections.push(i); + } + } + + const evmDeposits: Promise[] = []; + const tronDeposits: Promise[] = []; + + const evmSignatureData = signatureData.find((d) => d.universe === Universe.ETHEREUM); + + if (!evmSignatureData && universes.has(Universe.ETHEREUM)) { + throw Errors.internal('ethereum in universe list but no signature data present'); + } + + const tronSignatureData = signatureData.find((d) => d.universe === Universe.TRON); + + if (!tronSignatureData && universes.has(Universe.TRON)) { + throw Errors.internal('tron in universe list but no signature data present'); + } + + const doubleCheckTxs = []; + + for (const [i, s] of sources.entries()) { + const chain = this.options.chainList.getChainByID(Number(s.chainID)); + if (!chain) { + throw Errors.chainNotFound(s.chainID); + } + + if (s.universe === Universe.ETHEREUM && isNativeAddress(s.universe, s.tokenAddress)) { + await switchChain(this.options.evm.client, chain); + + const publicClient = createPublicClientWithFallback(chain); + + const { request } = await publicClient.simulateContract({ + abi: EVMVaultABI, + account: this.options.evm.address, + address: this.options.chainList.getVaultContractAddress(chain.id), + args: [omniversalRFF.asEVMRFF(), toHex(evmSignatureData!.signature), BigInt(i)], + chain: chain, + functionName: 'deposit', + value: s.valueRaw, + }); + const hash = await this.options.evm.client.writeContract(request); + this.markStepDone(BRIDGE_STEPS.INTENT_DEPOSIT_REQUEST(i + 1, s.value, chain)); + + evmDeposits.push(waitForTxReceipt(hash, publicClient)); + } else if (s.universe === Universe.TRON) { + const provider = new TronWeb({ + fullHost: chain.rpcUrls.default.grpc![0], + }); + const vaultContractAddress = this.options.chainList.getVaultContractAddress( + Number(s.chainID), + ); + const txWrap = await provider.transactionBuilder.triggerSmartContract( + TronWeb.address.fromHex(vaultContractAddress), + '', + { + txLocal: true, + input: encodeFunctionData({ + abi: EVMVaultABI, + functionName: 'deposit', + args: [omniversalRFF.asEVMRFF(), toHex(tronSignatureData!.signature), BigInt(i)], + }), + }, + [], + TronWeb.address.fromHex(this.options.tron!.address), + ); + + const signedTx = await this.options.tron!.adapter.signTransaction(txWrap.transaction); + + logger.debug('tron deposit signTransaction result', { + signedTx, + }); + + if (!this.options.tron!.adapter.isMobile) { + const txResult = await provider.trx.sendRawTransaction(signedTx); + + logger.debug('tron deposit tx result', { + txResult, + }); + if (!txResult.result) { + throw Errors.tronDepositFailed(txResult); + } + } + + tronDeposits.push( + (async () => { + await waitForTronDepositTxConfirmation( + tronSignatureData!.requestHash, + vaultContractAddress, + provider, + this.options.tron!.address as Hex, + ); + })(), + ); + } + doubleCheckTxs.push( + createDepositDoubleCheckTx(convertTo32Bytes(chain.id), this.options.cosmos, intentID), + ); + } + + if (evmDeposits.length || tronDeposits.length) { + await Promise.all([Promise.all(evmDeposits), Promise.all(tronDeposits)]); + this.markStepDone(BRIDGE_STEPS.INTENT_DEPOSITS_CONFIRMED); + } + + logger.debug('PostIntentSubmission: Intent ID', { + id: intentID.toNumber(), + }); + + if (tokenCollections.length > 0) { + logger.debug('processRFF', { + intentID: intentID.toString(), + message: 'going to create RFF', + tokenCollections, + }); + try { + await vscCreateRFF( + this.options.networkConfig.VSC_DOMAIN, + intentID, + this.markStepDone, + tokenCollections, + ); + } catch (e) { + logger.debug('vscCreateRFF', { + 'e instanceof NexusError?': e instanceof NexusError, + error: e, + }); + if (e instanceof NexusError && e.code === ERROR_CODES.RFF_FEE_EXPIRED) { + // Send back to process again + return { retry: true }; + } + throw e; + } + } else { + logger.debug('processRFF', { + message: 'going to publish RFF', + }); + await vscPublishRFF(this.options.networkConfig.VSC_DOMAIN, intentID); + } + + const destinationSigData = signatureData.find( + (s) => s.universe === intent.destination.universe, + ); + + if (!destinationSigData) { + throw Errors.destinationRequestHashNotFound(); + } + + return { + retry: false, + explorerURL, + intentID, + requestHash: destinationSigData.requestHash, + waitForDoubleCheckTx: waitForDoubleCheckTx(doubleCheckTxs), + }; + } + + private async setAllowances(input: Array) { + const originalChain = this.params.dstChain.id; + logger.debug('setAllowances', { originalChain, input }); + + const sponsoredApprovals: SponsoredApprovalDataArray = []; + const unsponsoredApprovals: Promise[] = []; + try { + for (const source of input) { + const chain = this.options.chainList.getChainByID(source.chainID); + if (!chain) { + throw Errors.chainNotFound(source.chainID); + } + + const publicClient = createPublicClientWithFallback(chain); + + const vc = this.options.chainList.getVaultContractAddress(chain.id); + + const chainId = new OmniversalChainID(chain.universe, source.chainID); + const chainDatum = ChaindataMap.get(chainId); + if (!chainDatum) { + throw Errors.internal('Chain data not found', { + chainId: source.chainID, + universe: chain.universe, + }); + } + + const currency = chainDatum.CurrencyMap.get(convertTo32Bytes(source.tokenContract)); + if (!currency) { + throw Errors.internal('currency not found', { + chainId: source.chainID, + tokenContractAddress: source.tokenContract, + }); + } + + if (chain.universe == Universe.ETHEREUM) { + logger.debug(`Switching chain to ${chain.id}`); + + await switchChain(this.options.evm.client, chain); + } + + if (currency.permitVariant === PermitVariant.Unsupported || chain.id === 1) { + if (chain.universe === Universe.ETHEREUM) { + const h = await this.options.evm.client + .writeContract({ + abi: ERC20ABI, + account: this.options.evm.address, + address: source.tokenContract, + args: [vc, BigInt(source.amount)], + chain, + functionName: 'approve', + }) + .catch((e) => { + if (e instanceof ContractFunctionExecutionError) { + const isUserRejectedRequestError = + e.walk((e) => e instanceof UserRejectedRequestError) instanceof + UserRejectedRequestError; + if (isUserRejectedRequestError) { + throw Errors.userRejectedAllowance(); + } + } + throw e; + }); + + this.markStepDone(BRIDGE_STEPS.ALLOWANCE_APPROVAL_REQUEST(chain)); + + unsponsoredApprovals.push(waitForTxReceipt(h, publicClient)); + } else if (chain.universe === Universe.TRON) { + if (!this.options.tron) { + throw Errors.internal('Tron is available in sources but has no adapter/provider'); + } + + const provider = new TronWeb({ + fullHost: chain.rpcUrls.default.grpc![0], + }); + const tx = await provider.transactionBuilder.triggerSmartContract( + TronWeb.address.fromHex(source.tokenContract), + 'approve(address,uint256)', + { + txLocal: true, + }, + [ + { type: 'address', value: TronWeb.address.fromHex(vc) }, + { type: 'uint256', value: source.amount.toString() }, + ], + TronWeb.address.fromHex(this.options.tron?.address), + ); + const signedTx = await this.options.tron.adapter.signTransaction(tx.transaction); + logger.debug('tron approval signTransaction result', { + signedTx, + }); + + if (!this.options.tron.adapter.isMobile) { + const txResult = await provider.trx.sendRawTransaction(signedTx); + + logger.debug('tron tx result', { + txResult, + }); + if (!txResult.result) { + throw Errors.tronApprovalFailed(txResult); + } + } + + await waitForTronApprovalTxConfirmation( + source.amount, + this.options.tron.address as Hex, + vc, + source.tokenContract, + provider, + ); + } + + this.markStepDone(BRIDGE_STEPS.ALLOWANCE_APPROVAL_MINED(chain)); + } else { + const account: JsonRpcAccount = { + address: this.options.evm.address, + type: 'json-rpc', + }; + + const signed = parseSignature( + await signPermitForAddressAndValue( + currency, + this.options.evm.client, + publicClient, + account, + vc, + source.amount, + ).catch((e) => { + if (e instanceof ContractFunctionExecutionError) { + const isUserRejectedRequestError = + e.walk((e) => e instanceof UserRejectedRequestError) instanceof + UserRejectedRequestError; + if (isUserRejectedRequestError) { + throw Errors.userRejectedAllowance(); + } + } + throw e; + }), + ); + + this.markStepDone(BRIDGE_STEPS.ALLOWANCE_APPROVAL_REQUEST(chain)); + + sponsoredApprovals.push({ + address: convertTo32Bytes(account.address), + chain_id: chainDatum.ChainID32, + operations: [ + { + sig_r: hexToBytes(signed.r), + sig_s: hexToBytes(signed.s), + sig_v: signed.yParity < 27 ? signed.yParity + 27 : signed.yParity, + token_address: currency.tokenAddress, + value: convertTo32Bytes(source.amount), + variant: currency.permitVariant === PermitVariant.PolygonEMT ? 2 : 1, + }, + ], + universe: chainDatum.Universe, + }); + } + } + + if (sponsoredApprovals.length) { + logger.debug('setAllowances:sponsoredApprovals', { + sponsoredApprovals, + }); + const approvalHashes = await vscCreateSponsoredApprovals( + this.options.networkConfig.VSC_DOMAIN, + sponsoredApprovals, + ); + + await Promise.all( + approvalHashes.map(async (approval) => { + const chain = this.options.chainList.getChainByID(approval.chainId); + if (!chain) { + throw Errors.chainNotFound(approval.chainId); + } + + const publicClient = createPublicClientWithFallback(chain); + await waitForTxReceipt(approval.hash, publicClient); + BRIDGE_STEPS.ALLOWANCE_APPROVAL_MINED({ + id: approval.chainId, + }); + return; + }), + ); + } + if (unsponsoredApprovals.length) { + await Promise.all(unsponsoredApprovals); + } + this.markStepDone(BRIDGE_STEPS.ALLOWANCE_COMPLETE); + } catch (e) { + logger.error('Error setting allowances', e, { cause: 'ALLOWANCE_SETTING_ERROR' }); + throw e; + } finally { + if (this.params.dstChain.universe === Universe.ETHEREUM) { + await switchChain(this.options.evm.client, this.params.dstChain); + } + } + } + + private async waitForOnAllowanceHook(sources: onAllowanceHookSource[]): Promise { + if (sources.length === 0) { + return false; + } + + await new Promise((resolve, reject) => { + const allow = (allowances: Array<'max' | 'min' | bigint | string>) => { + if (sources.length !== allowances.length) { + return reject(Errors.invalidAllowance(sources.length, allowances.length)); + } + + logger.debug('CA:BaseRequest:Allowances', { + allowances, + sources, + }); + const val: Array = []; + for (let i = 0; i < sources.length; i++) { + const source = sources[i]; + const allowance = allowances[i]; + let amount = 0n; + if (typeof allowance === 'string' && equalFold(allowance, 'max')) { + amount = maxUint256; + } else if (typeof allowance === 'string' && equalFold(allowance, 'min')) { + amount = source.allowance.minimumRaw; + } else if (typeof allowance === 'string') { + amount = mulDecimals(allowance, source.token.decimals); + } else { + amount = allowance; + } + val.push({ + amount, + chainID: source.chain.id, + tokenContract: source.token.contractAddress, + }); + } + this.setAllowances(val).then(resolve).catch(reject); + }; + + const deny = () => { + return reject(Errors.userRejectedAllowance()); + }; + + this.options.hooks.onAllowance({ + allow, + deny, + sources, + }); + }); + + return true; + } + + private createExpectedSteps( + intent: Intent, + insufficientAllowanceSources?: onAllowanceHookSource[], + ) { + this.steps = createSteps(intent, this.options.chainList, insufficientAllowanceSources); + if (this.options.emit) { + this.options.emit({ name: NEXUS_EVENTS.STEPS_LIST, args: this.steps }); + } + logger.debug('BridgeSteps', this.steps); + } + + private async createIntent(input: { + amount: Decimal; + assets: UserAssets; + feeStore: FeeStore; + gas: Decimal; + gasInToken: Decimal; + sourceChains: number[]; + token: TokenInfo; + }) { + const { amount, assets, feeStore, gas, gasInToken, token } = input; + const intent: Intent = { + allSources: [], + destination: { + amount: new Decimal('0'), + chainID: this.params.dstChain.id, + decimals: token.decimals, + gas: 0n, + tokenContract: token.contractAddress, + universe: this.params.dstChain.universe, + }, + fees: { + caGas: '0', + collection: '0', + fulfilment: '0', + gasSupplied: input.gasInToken.toFixed(), + protocol: '0', + solver: '0', + }, + isAvailableBalanceInsufficient: false, + sources: [], + recipientAddress: this.params.recipient, + }; + + const asset = assets.find(token.symbol); + if (!asset) { + throw Errors.assetNotFound(token.symbol); + } + + const allSources = (await asset.iterate(this.options.chainList)).map((v) => { + const chain = this.options.chainList.getChainByID(v.chainID); + if (!chain) { + throw Errors.chainNotFound(v.chainID); + } + + return { ...v, amount: v.balance, holderAddress: retrieveAddress(v.universe, this.options) }; + }); + + intent.allSources = allSources; + + const destinationBalance = asset.getBalanceOnChain( + this.params.dstChain.id, + token.contractAddress, + ); + + const borrow = amount; + + const protocolFee = feeStore.calculateProtocolFee(borrow); + intent.fees.protocol = protocolFee.toFixed(); + + let borrowWithFee = borrow.add(gasInToken).add(protocolFee); + + logger.debug('createIntent:0', { + borrow: borrow.toFixed(), + borrowWithFee: borrowWithFee.toFixed(), + destinationBalance, + gasInToken: gasInToken.toFixed(), + protocolFee: protocolFee.toFixed(), + }); + + const fulfilmentFee = feeStore.calculateFulfilmentFee({ + decimals: token.decimals, + destinationChainID: this.params.dstChain.id, + destinationTokenAddress: token.contractAddress, + }); + logger.debug('createIntent:1', { fulfilmentFee }); + + intent.fees.fulfilment = fulfilmentFee.toFixed(); + + borrowWithFee = borrowWithFee.add(fulfilmentFee); + + let accountedAmount = new Decimal(0); + + const allowedSources = allSources.filter((b) => { + if (input.sourceChains.length === 0) { + return true; + } + return input.sourceChains.includes(b.chainID); + }); + + logger.debug('createIntent:1.1', { allowedSources }); + + for (const assetC of allowedSources) { + if (accountedAmount.greaterThanOrEqualTo(borrowWithFee)) { + break; + } + + if (assetC.chainID === this.params.dstChain.id) { + continue; + } + + // Now collectionFee is a fixed amount - applicable to all + const collectionFee = feeStore.calculateCollectionFee({ + decimals: assetC.decimals, + sourceChainID: assetC.chainID, + sourceTokenAddress: assetC.tokenContract, + }); + + intent.fees.collection = collectionFee.add(intent.fees.collection).toFixed(); + borrowWithFee = borrowWithFee.add(collectionFee); + + logger.debug('createIntent:2', { collectionFee }); + + const unaccountedAmount = borrowWithFee.minus(accountedAmount); + + let borrowFromThisChain = new Decimal(assetC.balance).lessThanOrEqualTo(unaccountedAmount) + ? new Decimal(assetC.balance) + : unaccountedAmount; + + logger.debug('createIntent:2.1', { + accountedAmount: accountedAmount.toFixed(), + asset: assetC, + balance: assetC.balance.toFixed(), + borrowFromThisChain: borrowFromThisChain.toFixed(), + unaccountedAmount: unaccountedAmount.toFixed(), + }); + + const solverFee = feeStore.calculateSolverFee({ + borrowAmount: borrowFromThisChain, + decimals: assetC.decimals, + destinationChainID: this.params.dstChain.id, + destinationTokenAddress: token.contractAddress, + sourceChainID: assetC.chainID, + sourceTokenAddress: assetC.tokenContract, + }); + intent.fees.solver = solverFee.add(intent.fees.solver).toFixed(); + + logger.debug('createIntent:3', { solverFee }); + + borrowWithFee = borrowWithFee.add(solverFee); + + const unaccountedBalance = borrowWithFee.minus(accountedAmount); + + borrowFromThisChain = new Decimal(assetC.balance).lessThanOrEqualTo(unaccountedBalance) + ? new Decimal(assetC.balance) + : unaccountedBalance; + + intent.sources.push({ + amount: borrowFromThisChain, + chainID: assetC.chainID, + tokenContract: assetC.tokenContract, + universe: assetC.universe, + holderAddress: assetC.holderAddress, + }); + + accountedAmount = accountedAmount.add(borrowFromThisChain); + } + + intent.destination.amount = borrow; + + if (accountedAmount.lt(borrowWithFee)) { + throw Errors.insufficientBalance( + `required: ${borrowWithFee.toFixed()}, available: ${accountedAmount.toFixed()}`, + ); + } + + if (!gas.equals(0)) { + intent.destination.gas = mulDecimals(gas, this.params.dstChain.nativeCurrency.decimals); + } + + logger.debug('createIntent:4', { intent }); + + return intent; + } + + private readonly markStepDone = (step: BridgeStepType) => { + if (this.options.emit) { + const s = this.steps.find((s) => s.typeID === step.typeID); + if (s) { + this.options.emit({ + name: NEXUS_EVENTS.STEP_COMPLETE, + args: step, + }); + } + } + }; +} + +const isDeposit = (universe: Universe, tokenAddress: Hex) => { + if (universe === Universe.ETHEREUM) { + return isNativeAddress(universe, tokenAddress); + } + + return true; +}; + +const waitForDoubleCheckTx = (input: Array<() => Promise>) => { + return async () => { + await Promise.allSettled(input.map((i) => i())); + }; +}; + +export default BridgeHandler; diff --git a/src/sdk/ca-base/requestHandlers/bridgeMax.ts b/src/sdk/ca-base/requestHandlers/bridgeMax.ts new file mode 100644 index 00000000..f25312dc --- /dev/null +++ b/src/sdk/ca-base/requestHandlers/bridgeMax.ts @@ -0,0 +1,49 @@ +import { BridgeParams, IBridgeOptions } from '../../../commons'; +import { getBalances, calculateMaxBridgeFee, getFeeStore, mulDecimals, UserAssets } from '../utils'; +import { Errors } from '../errors'; + +const getMaxValueForBridge = async ( + params: Omit, + options: Omit, +) => { + const token = options.chainList.getTokenInfoBySymbol(params.toChainId, params.token); + if (!token) { + throw Errors.tokenNotFound(params.token, params.toChainId); + } + + const [balances, feeStore] = await Promise.all([ + getBalances({ + networkHint: options.networkConfig.NETWORK_HINT, + vscDomain: options.networkConfig.VSC_DOMAIN, + evmAddress: options.evm.address, + chainList: options.chainList, + tronAddress: options.tron?.address, + isCA: true, + }), + getFeeStore(options.networkConfig.GRPC_URL), + ]); + + const assets = new UserAssets(balances.assets); + + // FIXME: error in asset.find use NexusError and better messaging. + const tokenAsset = assets.find(params.token); + + const { maxAmount, sourceChainIds } = calculateMaxBridgeFee({ + assets: tokenAsset.getBridgeAssets(params.toChainId), + feeStore: feeStore, + dst: { + chainId: params.toChainId, + tokenAddress: token.contractAddress, + decimals: token.decimals, + }, + }); + + return { + sourceChainIds, + amountRaw: mulDecimals(maxAmount, token.decimals), + amount: maxAmount, + symbol: token.symbol, + }; +}; + +export default getMaxValueForBridge; diff --git a/src/sdk/ca-base/requestHandlers/helpers.ts b/src/sdk/ca-base/requestHandlers/helpers.ts new file mode 100644 index 00000000..bd8fb77f --- /dev/null +++ b/src/sdk/ca-base/requestHandlers/helpers.ts @@ -0,0 +1,29 @@ +import { Errors } from '../errors'; +import { BridgeParams, ChainListType } from '../../../commons'; + +const createBridgeParams = (input: BridgeParams, chainList: ChainListType) => { + if (input.amount === 0n && (!input.gas || input.gas === 0n)) { + throw Errors.invalidInput(`input.amount & input.gas can't be 0`); + } + + const { chain: dstChain, token: dstToken } = chainList.getChainAndTokenFromSymbol( + input.toChainId, + input.token, + ); + if (!dstToken) { + throw Errors.tokenNotFound(input.token, input.toChainId); + } + + const params = { + tokenAmount: input.amount, + nativeAmount: input.gas ?? 0n, + dstToken, + dstChain, + recipient: input.recipient, + sourceChains: input.sourceChains ?? [], + }; + + return params; +}; + +export { createBridgeParams }; diff --git a/src/sdk/ca-base/steps.ts b/src/sdk/ca-base/steps.ts new file mode 100644 index 00000000..69a9998b --- /dev/null +++ b/src/sdk/ca-base/steps.ts @@ -0,0 +1,65 @@ +import { isNativeAddress } from './constants'; +import { + BridgeStepType, + BRIDGE_STEPS, + ChainListType, + Intent, + onAllowanceHookSource, +} from '../../commons'; +import { Errors } from './errors'; + +const INTENT_FINISH_STEPS = [BRIDGE_STEPS.INTENT_FULFILLED]; + +const createSteps = ( + intent: Intent, + chainList: ChainListType, + unallowedSources?: onAllowanceHookSource[], +) => { + const steps: BridgeStepType[] = []; + + steps.push(BRIDGE_STEPS.INTENT_ACCEPTED); + if (unallowedSources && unallowedSources?.length > 0) { + for (const source of unallowedSources) { + steps.push( + BRIDGE_STEPS.ALLOWANCE_APPROVAL_REQUEST(source.chain), + BRIDGE_STEPS.ALLOWANCE_APPROVAL_MINED(source.chain), + ); + } + steps.push(BRIDGE_STEPS.ALLOWANCE_COMPLETE); + } + + steps.push(BRIDGE_STEPS.INTENT_HASH_SIGNED, BRIDGE_STEPS.INTENT_SUBMITTED()); + + const sources = intent.sources.filter((s) => s.chainID !== intent.destination.chainID); + + let collections = 0, + deposits = 0; + for (const [i, s] of sources.entries()) { + const isNative = isNativeAddress(s.universe, s.tokenContract); + if (isNative) { + deposits++; + const chain = chainList.getChainByID(s.chainID); + if (!chain) { + throw Errors.chainNotFound(s.chainID); + } + + steps.push(BRIDGE_STEPS.INTENT_DEPOSIT_REQUEST(i + 1, s.amount, chain)); + } else { + collections++; + steps.push(BRIDGE_STEPS.INTENT_COLLECTION(i + 1, sources.length)); + } + } + + if (collections > 0) { + steps.push(BRIDGE_STEPS.INTENT_COLLECTION_COMPLETE); + } + + if (deposits > 0) { + steps.push(BRIDGE_STEPS.INTENT_DEPOSITS_CONFIRMED); + } + + steps.push(...INTENT_FINISH_STEPS); + return steps; +}; + +export { createSteps }; diff --git a/packages/core/sdk/ca-base/swap/abi.ts b/src/sdk/ca-base/swap/abi.ts similarity index 100% rename from packages/core/sdk/ca-base/swap/abi.ts rename to src/sdk/ca-base/swap/abi.ts diff --git a/packages/core/sdk/ca-base/swap/calibur.abi.ts b/src/sdk/ca-base/swap/calibur.abi.ts similarity index 100% rename from packages/core/sdk/ca-base/swap/calibur.abi.ts rename to src/sdk/ca-base/swap/calibur.abi.ts diff --git a/packages/core/sdk/ca-base/swap/constants.ts b/src/sdk/ca-base/swap/constants.ts similarity index 100% rename from packages/core/sdk/ca-base/swap/constants.ts rename to src/sdk/ca-base/swap/constants.ts diff --git a/packages/core/sdk/ca-base/swap/data.ts b/src/sdk/ca-base/swap/data.ts similarity index 81% rename from packages/core/sdk/ca-base/swap/data.ts rename to src/sdk/ca-base/swap/data.ts index bbfdabd5..59b1a19c 100644 --- a/packages/core/sdk/ca-base/swap/data.ts +++ b/src/sdk/ca-base/swap/data.ts @@ -1,24 +1,26 @@ -import { Bytes, PermitVariant, Universe } from '@arcana/ca-common'; -import { Hex } from 'viem'; +import { Bytes, PermitVariant, Universe } from '@avail-project/ca-common'; +import { Hex, PublicClient } from 'viem'; import { toHex } from 'viem/utils'; import { ChainList } from '../chains'; -import { TokenInfo } from '@nexus/commons'; +import { TokenInfo } from '../../../commons'; import { convertTo32BytesHex, equalFold } from '../utils'; import { EADDRESS } from './constants'; -import { convertToEVMAddress } from './utils'; +import { convertToEVMAddress, determinePermitVariantAndVersion } from './utils'; +import { Errors } from '../errors'; export enum CurrencyID { - AVAX = 5, - DAI = 6, - ETH = 3, + USDC = 0x1, + USDT = 0x2, + ETH = 0x3, + POL = 0x4, + AVAX = 0x5, + BNB = 0x6, + WETH = 0x7, + DAI = 0x8, HYPE = 0x10, KAIA = 0x11, - POL = 4, - USDC = 1, USDS = 99, - USDT = 2, - WETH = 7, } const chainData: Map< @@ -274,6 +276,61 @@ const chainData: Map< }, ], ], + [ + 56, + [ + { + CurrencyID: CurrencyID.USDC, + IsGasToken: false, + Name: CurrencyID[CurrencyID.USDC], + PermitVariant: PermitVariant.Unsupported, + PermitContractVersion: 0, + TokenContractAddress: convertTo32BytesHex('0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d'), + TokenDecimals: 18, + }, + { + CurrencyID: CurrencyID.BNB, + IsGasToken: true, + Name: CurrencyID[CurrencyID.BNB], + PermitVariant: PermitVariant.Unsupported, + PermitContractVersion: 0, + TokenContractAddress: convertTo32BytesHex(EADDRESS), + TokenDecimals: 18, + }, + ], + ], + [ + 1, + [ + { + CurrencyID: CurrencyID.USDC, + IsGasToken: false, + Name: CurrencyID[CurrencyID.USDC], + PermitVariant: PermitVariant.EIP2612Canonical, + PermitContractVersion: 2, + TokenContractAddress: convertTo32BytesHex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'), + TokenDecimals: 6, + }, + { + CurrencyID: CurrencyID.USDT, + IsGasToken: false, + Name: CurrencyID[CurrencyID.USDT], + PermitVariant: PermitVariant.Unsupported, + PermitContractVersion: 0, + TokenContractAddress: convertTo32BytesHex('0xdac17f958d2ee523a2206206994597c13d831ec7'), + TokenDecimals: 6, + }, + { + CurrencyID: CurrencyID.ETH, + IsGasToken: true, + Name: CurrencyID[CurrencyID.ETH], + PermitVariant: PermitVariant.Unsupported, + PermitContractVersion: 0, + TokenContractAddress: convertTo32BytesHex(EADDRESS), + TokenDecimals: 18, + }, + ], + ], ]); const getSwapSupportedChains = (chainList: ChainList) => { @@ -326,6 +383,7 @@ export type FlatBalance = { tokenAddress: `0x${string}`; universe: Universe; value: number; + logo: string; }; const filterSupportedTokens = (tokens: FlatBalance[]) => { @@ -343,15 +401,11 @@ const filterSupportedTokens = (tokens: FlatBalance[]) => { return true; } - // if (token.PermitVariant === PermitVariant.Unsupported) { - // return false; - // } - return true; }); }; -const getTokenVersion = (tokenAddress: Hex) => { +const getTokenVersion = async (tokenAddress: Hex, client: PublicClient) => { for (const [, tokens] of chainData.entries()) { const t = tokens.find((t) => equalFold(convertTo32BytesHex(tokenAddress), t.TokenContractAddress), @@ -360,17 +414,19 @@ const getTokenVersion = (tokenAddress: Hex) => { return { variant: t.PermitVariant, version: t.PermitContractVersion }; } } - throw new Error('token not available or has no version'); + + const { variant, version } = await determinePermitVariantAndVersion(client, tokenAddress); + return { variant, version }; }; export const getTokenDecimals = (chainID: number | string, contractAddress: Bytes) => { const cData = chainData.get(Number(chainID)); if (!cData) { - throw new Error(`chain data not found for chain:${chainID}`); + throw Errors.chainDataNotFound(Number(chainID)); } const token = cData.find((c) => equalFold(toHex(contractAddress), c.TokenContractAddress)); if (!token) { - throw new Error(`token not found: ${toHex(contractAddress)}`); + throw Errors.assetNotFound(toHex(contractAddress)); } return { decimals: token.TokenDecimals, diff --git a/packages/core/sdk/ca-base/swap/ob.ts b/src/sdk/ca-base/swap/ob.ts similarity index 73% rename from packages/core/sdk/ca-base/swap/ob.ts rename to src/sdk/ca-base/swap/ob.ts index c2eba89b..42902ec1 100644 --- a/packages/core/sdk/ca-base/swap/ob.ts +++ b/src/sdk/ca-base/swap/ob.ts @@ -7,30 +7,20 @@ import { CurrencyID, Holding, liquidateInputHoldings, - OmniversalChainID, Quote, QuoteRequestExactInput, Universe, -} from '@arcana/ca-common'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +} from '@avail-project/ca-common'; import Decimal from 'decimal.js'; import { orderBy, retry } from 'es-toolkit'; import Long from 'long'; -import { ByteArray, Hex, PrivateKeyAccount, toBytes, WalletClient } from 'viem'; -import { getLogger } from '../logger'; +import { Hex, PrivateKeyAccount, WalletClient } from 'viem'; import { divDecimals, equalFold, minutesToMs, waitForTxReceipt } from '../utils'; import { EADDRESS, SWEEPER_ADDRESS } from './constants'; import { getTokenDecimals } from './data'; import { createBridgeRFF } from './rff'; import { caliburExecute, checkAuthCodeSet, createSBCTxFromCalls, waitForSBCTxReceipt } from './sbc'; -import { - CREATE_PERMIT_FOR_SOURCE_SWAP, - DESTINATION_SWAP_BATCH_TX, - RFF_ID, - SOURCE_SWAP_HASH, - SWAP_COMPLETE, - SwapStep, -} from './steps'; + import { bytesEqual, Cache, @@ -41,7 +31,7 @@ import { EADDRESS_32_BYTES, EXPECTED_CALIBUR_CODE, getAllowanceCacheKey, - getTxsFromQuote, + parseQuote, isNativeAddress, performDestinationSwap, PublicClientList, @@ -50,13 +40,19 @@ import { vscSBCTx, } from './utils'; import { + getLogger, + SWAP_STEPS, + SwapStepType, ChainListType, BridgeAsset, EoaToEphemeralCallMap, RFFDepositCallMap, SBCTx, Tx, -} from '@nexus/commons'; +} from '../../../commons'; +import { SwapRoute } from './route'; +import { Errors } from '../errors'; +import { SigningStargateClient } from '@cosmjs/stargate'; type Options = { address: { @@ -73,7 +69,7 @@ type Options = { }; destinationChainID: number; emitter: { - emit: (step: SwapStep) => void; + emit: (step: SwapStepType) => void; }; networkConfig: { COSMOS_URL: string; @@ -83,7 +79,7 @@ type Options = { publicClientList: PublicClientList; slippage: number; wallet: { - cosmos: DirectSecp256k1Wallet; + cosmos: SigningStargateClient; eoa: WalletClient; ephemeral: PrivateKeyAccount; }; @@ -93,32 +89,13 @@ type SwapInput = { agg: Aggregator; cfee: bigint; cur: Currency; - originalHolding: Holding; + originalHolding: Holding & { decimals: number; symbol: string }; quote: Quote; req: QuoteRequestExactInput; }; const logger = getLogger(); -type DDSInput = { - aggregator: Aggregator; - createdAt: number; - dstChainCOT: Currency; - dstEOAToEphTx: { - amount: bigint; - contractAddress: Hex; - } | null; - inputAmount: Decimal; - inputAmountWithBuffer: Decimal; - outputAmount: bigint; - quote: null | Quote; - req: { - chain: OmniversalChainID; - inputToken: Buffer; - outputToken: ByteArray; - }; -}; - class BridgeHandler { private depositCalls: RFFDepositCallMap = {}; private eoaToEphCalls: EoaToEphemeralCallMap = {}; @@ -132,14 +109,14 @@ class BridgeHandler { promise: Promise.resolve(), }; constructor( - private input: { + private readonly input: { amount: Decimal; assets: BridgeAsset[]; chainID: number; decimals: number; tokenAddress: `0x${string}`; } | null, - private options: Options, + private readonly options: Options, ) { if (input) { for (const asset of input.assets) { @@ -160,7 +137,7 @@ class BridgeHandler { for (const c in this.depositCalls) { const chain = this.options.chainList.getChainByID(Number(c)); if (!chain) { - throw new Error('chain not found'); + throw Errors.chainNotFound(Number(c)); } const publicClient = this.options.publicClientList.get(c); @@ -213,7 +190,7 @@ class BridgeHandler { if (sbcTx.length) { const ops = await vscSBCTx(sbcTx, this.options.networkConfig.VSC_DOMAIN); ops.forEach((op) => { - this.options.emitter.emit(SOURCE_SWAP_HASH(op, this.options.chainList)); + this.options.emitter.emit(SWAP_STEPS.SOURCE_SWAP_HASH(op, this.options.chainList)); }); waitingPromises.push( ...ops.map(([chainID, hash]) => @@ -251,7 +228,7 @@ class BridgeHandler { chainList: this.options.chainList, cosmos: { address: this.options.address.cosmos, - wallet: this.options.wallet.cosmos, + client: this.options.wallet.cosmos, }, evm: { address: this.options.address.ephemeral, @@ -292,7 +269,7 @@ class BridgeHandler { ); metadata.rff_id = BigInt(this.status.intentID.toNumber()); - this.options.emitter.emit(RFF_ID(this.status.intentID.toNumber())); + this.options.emitter.emit(SWAP_STEPS.RFF_ID(this.status.intentID.toNumber())); // will just resolve immediately if no CA was required logger.debug('Fill wait start'); @@ -317,25 +294,25 @@ class BridgeHandler { } class DestinationSwapHandler { - private destinationCalls: Tx[] = []; + private eoaToEphCalls: Tx[] = []; constructor( - private dstSwap: { getDDS: () => Promise } & DDSInput, - private dstTokenInfo: { + private data: SwapRoute['destination'], + private readonly dstTokenInfo: { contractAddress: `0x${string}`; decimals: number; symbol: string; }, - private dst: { + private readonly dst: { amount?: bigint; chainID: number; token: `0x${string}`; }, - private options: Options, + private readonly options: Options, ) { - if (dstSwap.dstEOAToEphTx) { + if (data.swap.dstEOAToEphTx) { options.cache.addAllowanceQuery({ chainID: dst.chainID, - contractAddress: dstSwap.dstEOAToEphTx.contractAddress, + contractAddress: data.swap.dstEOAToEphTx.contractAddress, owner: options.address.eoa, spender: options.address.ephemeral, }); @@ -360,29 +337,29 @@ class DestinationSwapHandler { options.cache.addAllowanceQuery({ chainID: dst.chainID, - contractAddress: convertToEVMAddress(dstSwap.req.inputToken), + contractAddress: convertToEVMAddress(data.swap.req.inputToken), owner: options.address.ephemeral, spender: SWEEPER_ADDRESS, }); } async createPermit() { - if (this.dstSwap.dstEOAToEphTx) { + if (this.data.swap.dstEOAToEphTx) { const txs = await createPermitAndTransferFromTx({ - amount: this.dstSwap.dstEOAToEphTx.amount, + amount: this.data.swap.dstEOAToEphTx.amount, cache: this.options.cache, chain: this.options.chainList.getChainByID(this.dst.chainID)!, - contractAddress: this.dstSwap.dstEOAToEphTx.contractAddress, + contractAddress: this.data.swap.dstEOAToEphTx.contractAddress, owner: this.options.address.eoa, ownerWallet: this.options.wallet.eoa, publicClient: this.options.publicClientList.get(this.dst.chainID), spender: this.options.address.ephemeral, }); - this.destinationCalls = this.destinationCalls.concat(txs); + this.eoaToEphCalls = txs; } } - // FIXME: Need to add retry and reqoute + // Retry only once, can't keep user waiting. async process( metadata: SwapMetadata, // inputAmount = this.dstSwap.quote?.inputAmount, @@ -390,46 +367,78 @@ class DestinationSwapHandler { await this.options.wallet.eoa.switchChain({ id: Number(this.options.destinationChainID), }); + try { + await this.executeSwap(metadata); + } catch (error) { + logger.warn('Destination swap failed, attempting single requote & retry.', { + error: (error as Error)?.message ?? error, + }); + + await this.requoteIfRequired(true); + try { + await this.executeSwap(metadata); + } catch (retryError) { + logger.error( + 'Destination swap failed even after retry.', + { + error: (retryError as Error)?.message ?? retryError, + }, + { cause: 'SWAP_FAILED' }, + ); + throw retryError; + } + } + } + + /** + * Executes swap + sweeper steps + */ + private async executeSwap(metadata: SwapMetadata) { + await this.requoteIfRequired(false); + + const { swap } = this.data; - let hasDestinationSwap = false; - if (this.dstSwap.quote) { - hasDestinationSwap = true; - await this.requoteIfRequired(/*inputAmount*/); + let calls: Tx[] = []; - const txs = getTxsFromQuote( - this.dstSwap.aggregator, - this.dstSwap.quote!, - this.dstSwap.req.inputToken, + if (this.eoaToEphCalls.length > 0) { + calls = calls.concat(this.eoaToEphCalls); + } + + if (swap.quote) { + const quote = parseQuote( + { + agg: swap.aggregator, + originalHolding: swap.originalHolding, + quote: swap.quote, + req: swap.req, + }, true, ); - if (txs.approval) { - this.destinationCalls.push(txs.approval); + if (quote.swap.approval) { + calls.push(quote.swap.approval); } - - this.destinationCalls.push(txs.swap); + calls.push(quote.swap.tx); logger.debug('swap:destinationCalls', { - destinationCalls: this.destinationCalls, + destinationCalls: calls, }); metadata.dst.swaps.push({ agg: 0, - input_amt: toBytes(txs.amount), - input_contract: this.dstSwap.req.inputToken, - input_decimals: this.dstSwap.dstChainCOT.decimals, + input_amt: convertTo32Bytes(quote.input.amount), + input_contract: swap.req.inputToken, + input_decimals: swap.dstChainCOT.decimals, output_amt: convertTo32Bytes(this.dst.amount ?? 0), output_contract: convertTo32Bytes(this.dst.token), output_decimals: this.dstTokenInfo.decimals, }); - } - if (hasDestinationSwap) { - this.options.emitter.emit(DESTINATION_SWAP_BATCH_TX(false)); + this.options.emitter.emit(SWAP_STEPS.DESTINATION_SWAP_BATCH_TX(false)); } - // So whatever amount is swapped gets transferred ephemeral -> eoa - this.destinationCalls = this.destinationCalls.concat( + // Add sweeper tx + calls = calls.concat( createSweeperTxs({ cache: this.options.cache, chainID: this.dst.chainID, @@ -440,85 +449,109 @@ class DestinationSwapHandler { }), ); - // Destination swap batched tx to VSC and waiting for receipt (sweep after) + // Execute batched destination tx const hash = await performDestinationSwap({ actualAddress: this.options.address.eoa, cache: this.options.cache, - calls: this.destinationCalls, + calls, chain: this.options.chainList.getChainByID(this.dst.chainID)!, chainList: this.options.chainList, COT: this.options.cot.currencyID, emitter: this.options.emitter, ephemeralAddress: this.options.address.ephemeral, ephemeralWallet: this.options.wallet.ephemeral, - hasDestinationSwap, + hasDestinationSwap: true, publicClientList: this.options.publicClientList, vscDomain: this.options.networkConfig.VSC_DOMAIN, }); - if (hasDestinationSwap) { - this.options.emitter.emit(DESTINATION_SWAP_BATCH_TX(true)); - } + this.options.emitter.emit(SWAP_STEPS.DESTINATION_SWAP_BATCH_TX(true)); + this.options.emitter.emit(SWAP_STEPS.SWAP_COMPLETE); - this.options.emitter.emit(SWAP_COMPLETE); performance.mark('xcs-ops-end'); - logger.debug('before dst metadata', { - metadata, - }); - + logger.debug('before dst metadata', { metadata }); metadata.dst.tx_hash = convertTo32Bytes(hash); } - async requoteIfRequired() { - let requote = false; + /** + * Requote if expired or invalid. + * If `force` = true, always requote regardless of expiry check. + */ + private async requoteIfRequired(force = false) { + // There wasn't any quote to begin with so nothing to requote. + // Happens when dst token is COT, just need to send from ephemeral -> EOA + // Maybe FIXME: Bridge can directly send to EOA in these cases - save gas. + if (!this.data.swap.quote) { + return; + } - if (this.dstSwap.aggregator instanceof BebopAggregator) { - const quote = this.dstSwap.quote as BebopQuote; - if (quote.originalResponse.quote.expiry * 1000 < Date.now()) { - logger.debug('DDS: BEBOP', { - expiry: quote.originalResponse.quote.expiry * 1000, - now: Date.now(), - }); + const { swap } = this.data; + let requote = force; + + if (!force) { + if (swap.aggregator instanceof BebopAggregator) { + const quote = swap.quote as BebopQuote; + if (quote.originalResponse.quote.expiry * 1000 < Date.now()) requote = true; + } else if (Date.now() - swap.creationTime > minutesToMs(0.4)) { requote = true; } - } else if (Date.now() - this.dstSwap.createdAt > minutesToMs(0.4)) { - requote = true; } - // else if (this.dstSwap.quote?.inputAmount !== inputAmount) { - // requote = true; - // } - - if (requote) { - const ddsResponse = await this.dstSwap.getDDS(); - if (!ddsResponse.quote) { - throw new Error('could not requote DS'); - } - logger.debug('reqoutedDstSwap', { - inputAmountWithBuffer: this.dstSwap.inputAmountWithBuffer.toFixed(), - newInputAmount: ddsResponse.inputAmount.toFixed(), - }); - const isExactIn = this.dst.amount == undefined; - if (!isExactIn && ddsResponse.inputAmount.gt(this.dstSwap.inputAmountWithBuffer)) { - throw new Error( - `Rates changed for destination swap and could not be filled even with buffer. Before: ${this.dstSwap.inputAmountWithBuffer.toFixed()} ,After: ${ddsResponse.inputAmount.toFixed()}`, - ); - } - this.dstSwap = { ...ddsResponse, getDDS: this.dstSwap.getDDS }; + if (!requote) { + return; + } + + logger.debug('Requoting destination swap...'); + const newSwap = await this.data.fetchDestinationSwapDetails(); + if (!newSwap.quote) { + throw Errors.quoteFailed('Failed to requote destination swap.'); + } + + const isExactIn = this.data.type === 'EXACT_IN'; + logger.debug('destinationSwap Requote', { + isExactIn, + 'newSwap.min': newSwap.inputAmount.min.toFixed(), + 'newSwap.max': newSwap.inputAmount.max.toFixed(), + 'swap.min': swap.inputAmount.min.toFixed(), + 'swap.max': swap.inputAmount.max.toFixed(), + }); + + // EXACT_IN: min and max are same and input doesnt change so dont check here, + // In source swap failure cases it might have an issue. FIXME + // EXACT_OUT: min is the actual amount required so as long as it is within the range + // of previous [max, min] it should be executable. + if ( + !isExactIn && + !( + newSwap.inputAmount.min.gte(swap.inputAmount.min) && + newSwap.inputAmount.min.lte(swap.inputAmount.max) + ) + ) { + const rate = swap.inputAmount.min.toNumber(); + const tolerance = swap.inputAmount.min.toNumber() - swap.inputAmount.max.toNumber(); + throw Errors.ratesChangedBeyondTolerance(rate, tolerance); } + + this.data = { + type: this.data.type, + swap: newSwap, + fetchDestinationSwapDetails: this.data.fetchDestinationSwapDetails, + }; + + logger.debug('Destination swap requoted successfully.', { + before: swap.inputAmount.min.toFixed(), + after: newSwap.inputAmount.min.toFixed(), + }); } } class SourceSwapsHandler { private disposableCache: { [k: string]: Tx } = {}; - private swaps: Map; - constructor( - quotes: SwapInput[], - private options: Options, - ) { - this.swaps = this.groupAndOrder(quotes); - for (const [chainID, swapQuotes] of this.iterate(this.swaps)) { + private readonly swapsData: Map; + constructor(data: SwapRoute['source'], private readonly options: Options) { + this.swapsData = this.groupAndOrder(data.swaps); + for (const [chainID, swapQuotes] of this.iterate(this.swapsData)) { this.options.cache.addSetCodeQuery({ address: this.options.address.ephemeral, chainID: Number(chainID), @@ -542,30 +575,28 @@ class SourceSwapsHandler { } } - getSwapsAndMetadata(input: Swap[]) { - const swaps: { - amount: bigint; - approval: null | Tx; - inputToken: Bytes; - outputAmount: bigint; - outputToken: Bytes; - swap: { - data: Hex; - to: Hex; - value: bigint; + getQuotesAndMetadata(input: Swap[]) { + const quotes: { + input: { + amount: bigint; + token: Bytes; + decimals: number; + symbol: string; }; + output: { amount: bigint; token: Bytes }; + swap: { approval: Tx | null; tx: Tx }; }[] = []; const metadata: SwapMetadataTx['swaps'] = []; - for (const swap of input) { - const td = swap.getTxsData(); - const md = swap.getMetadata(); + for (const quoteData of input) { + const td = quoteData.getParsedQuote(); + const md = quoteData.getMetadata(); metadata.push(md); - swaps.push(td); + quotes.push(td); } - return { metadata, swaps }; + return { metadata, quotes }; } *iterate(input: Map) { @@ -577,7 +608,7 @@ class SourceSwapsHandler { async process( metadata: SwapMetadata, - input = this.swaps, + input = this.swapsData, retry = true, ): Promise<{ amount: Decimal; chainID: number; tokenAddress: `0x${string}` }[]> { logger.debug('sourceSwapsHandler', { @@ -605,43 +636,39 @@ class SourceSwapsHandler { tx_hash: new Uint8Array(), univ: Universe.ETHEREUM, }; - const { metadata: mtd, swaps } = this.getSwapsAndMetadata(swapQuotes); + const { metadata: mtd, quotes } = this.getQuotesAndMetadata(swapQuotes); const publicClient = this.options.publicClientList.get(chainID); const chain = this.options.chainList.getChainByID(Number(chainID)); if (!chain) { - throw new Error(`chain not found: ${chainID}`); + throw Errors.chainNotFound(chainID); } - logger.debug('srcSwapHandler:process', { - swaps, - mtd, - }); - metadataTx.swaps = metadataTx.swaps.concat(mtd); // 1. Source swap calls let amount = 0n; { - for (const swap of swaps) { - amount += swap.outputAmount; - const { symbol } = getTokenDecimals(Number(chainID), swap.inputToken); - if (isNativeAddress(convertToEVMAddress(swap.inputToken))) { - sbcCalls.value += swap.amount; + for (const quote of quotes) { + amount += quote.output.amount; + if (isNativeAddress(convertToEVMAddress(quote.input.token))) { + sbcCalls.value += quote.input.amount; } else { - this.options.emitter.emit(CREATE_PERMIT_FOR_SOURCE_SWAP(false, symbol, chain)); + this.options.emitter.emit( + SWAP_STEPS.CREATE_PERMIT_FOR_SOURCE_SWAP(false, quote.input.symbol, chain), + ); const allowanceCacheKey = getAllowanceCacheKey({ chainID: chain.id, - contractAddress: convertToEVMAddress(swap.inputToken), + contractAddress: convertToEVMAddress(quote.input.token), owner: this.options.address.eoa, spender: this.options.address.ephemeral, }); const txs = await createPermitAndTransferFromTx({ - amount: swap.amount, + amount: quote.input.amount, approval: this.disposableCache[allowanceCacheKey], cache: this.options.cache, chain, - contractAddress: convertToEVMAddress(swap.inputToken), + contractAddress: convertToEVMAddress(quote.input.token), owner: this.options.address.eoa, ownerWallet: this.options.wallet.eoa, publicClient, @@ -654,19 +681,21 @@ class SourceSwapsHandler { this.disposableCache[allowanceCacheKey] = approvalTx; } - this.options.emitter.emit(CREATE_PERMIT_FOR_SOURCE_SWAP(true, symbol, chain)); + this.options.emitter.emit( + SWAP_STEPS.CREATE_PERMIT_FOR_SOURCE_SWAP(true, quote.input.symbol, chain), + ); logger.debug('sourceSwap', { chainID, permitCalls: txs, - swap, + quote, }); sbcCalls.calls.push(...txs); } - if (swap.approval) { - sbcCalls.calls.push(swap.approval); + if (quote.swap.approval) { + sbcCalls.calls.push(quote.swap.approval); } - sbcCalls.calls.push(swap.swap); + sbcCalls.calls.push(quote.swap.tx); } } @@ -730,7 +759,7 @@ class SourceSwapsHandler { }); metadataTx.tx_hash = convertTo32Bytes(hash); this.options.emitter.emit( - SOURCE_SWAP_HASH([BigInt(chain.id), hash], this.options.chainList), + SWAP_STEPS.SOURCE_SWAP_HASH([BigInt(chain.id), hash], this.options.chainList), ); waitingPromises.push(wrap(Number(chainID), waitForTxReceipt(hash, publicClient, 2))); @@ -758,7 +787,9 @@ class SourceSwapsHandler { const [chainID, hash] = ops[0]; metadataTx.tx_hash = convertTo32Bytes(hash); - this.options.emitter.emit(SOURCE_SWAP_HASH([chainID, hash], this.options.chainList)); + this.options.emitter.emit( + SWAP_STEPS.SOURCE_SWAP_HASH([chainID, hash], this.options.chainList), + ); return wrap( Number(chainID), @@ -772,10 +803,10 @@ class SourceSwapsHandler { assets.push({ amount: divDecimals( amount, - getTokenDecimals(Number(chainID), swaps[0].outputToken).decimals, + getTokenDecimals(Number(chainID), quotes[0].output.token).decimals, ), chainID: Number(chainID), - tokenAddress: convertToEVMAddress(swaps[0].outputToken), + tokenAddress: convertToEVMAddress(quotes[0].output.token), }); metadata.src.push(metadataTx); @@ -834,10 +865,10 @@ class SourceSwapsHandler { } catch { // TODO: What to do here? Store it or something? } - throw new Error('source swap failed'); + throw Errors.swapFailed('source swap failed'); } } else { - throw new Error('some source swap failed even after retry'); + throw Errors.swapFailed('some source swap failed even after retry'); } } @@ -857,7 +888,7 @@ class SourceSwapsHandler { // if it comes to retry it should be set to 0 oldTotalOutputAmount = 0n; for (const fChain of failedChains) { - const oldQuotes = this.swaps.get(fChain); + const oldQuotes = this.swapsData.get(fChain); if (!oldQuotes) { logger.debug('how can old quote not be there???? we are iterating on it'); continue; @@ -888,7 +919,15 @@ class SourceSwapsHandler { returnData: {}, }); - return nq.quotes[0]; + const q = nq.quotes[0]; + return { + ...q, + originalHolding: { + ...q.originalHolding, + decimals: oq.originalHolding.decimals, + symbol: oq.originalHolding.symbol, + }, + }; }), ); } @@ -915,7 +954,7 @@ class SourceSwapsHandler { slippage: this.options.slippage, }) ) { - throw new Error('slippage greater than max slippage'); + throw Errors.slippageError('source swap retry slippage exceeded max'); } } @@ -963,68 +1002,32 @@ class SourceSwapsHandler { } class Swap { - txs: { - amount: bigint; - approval: null | Tx; - inputToken: Bytes; - outputAmount: bigint; - outputToken: Bytes; - swap: { - data: Hex; - to: Hex; - value: bigint; - }; - } | null = null; constructor(public input: SwapInput) {} getMetadata() { - const txs = this.getTxsData(); - const { decimals: inputDecimals } = getTokenDecimals( - Number(this.input.req.chain.chainID), - this.input.req.inputToken, - ); + const txs = this.getParsedQuote(); const { decimals: outputDecimals } = getTokenDecimals( Number(this.input.req.chain.chainID), this.input.req.outputToken, ); + return { agg: 1, input_amt: convertTo32Bytes(this.input.req.inputAmount), input_contract: this.input.req.inputToken, - input_decimals: inputDecimals, - output_amt: convertTo32Bytes(txs.amount), + input_decimals: txs.input.decimals, + output_amt: convertTo32Bytes(txs.input.amount), output_contract: this.input.req.outputToken, output_decimals: outputDecimals, }; } - getTxsData() { - return { - ...getTxsFromQuote( - this.input.agg, - this.input.quote, - this.input.req.inputToken, - !bytesEqual(EADDRESS_32_BYTES, this.input.req.inputToken), - ), - outputToken: this.input.req.outputToken, - }; + getParsedQuote() { + const data = parseQuote(this.input, !bytesEqual(EADDRESS_32_BYTES, this.input.req.inputToken)); + return { ...data, output: { ...data.output, token: this.input.req.outputToken } }; } } -// class SwapGroup { -// requoted = true; -// constructor( -// public swaps: Swap[], -// public chainID: number, -// ) {} - -// execute() { -// // Requote -// // Execute -// } - -// requote() {} -// } const wrap = async (chainID: number, promise: Promise) => { await promise; diff --git a/packages/core/sdk/ca-base/swap/rff.ts b/src/sdk/ca-base/swap/rff.ts similarity index 77% rename from packages/core/sdk/ca-base/swap/rff.ts rename to src/sdk/ca-base/swap/rff.ts index ab180b9a..48472942 100644 --- a/packages/core/sdk/ca-base/swap/rff.ts +++ b/src/sdk/ca-base/swap/rff.ts @@ -1,30 +1,18 @@ -import { - DepositVEPacket, - EVMRFF, - EVMVaultABI, - MsgDoubleCheckTx, - Universe, -} from '@arcana/ca-common'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { DepositVEPacket, EVMVaultABI, MsgDoubleCheckTx, Universe } from '@avail-project/ca-common'; import Decimal from 'decimal.js'; import Long from 'long'; import { bytesToNumber, createPublicClient, - encodeAbiParameters, encodeFunctionData, - getAbiItem, - keccak256, + Hex, PrivateKeyAccount, - toBytes, toHex, webSocket, } from 'viem'; -import { ErrorInsufficientBalance } from '../errors'; -import { getLogger } from '../logger'; -import { createRFFromIntent } from '../utils'; -import { Intent, NetworkConfig } from '@nexus/commons'; +import { Errors } from '../errors'; import { + createRFFromIntent, convertAddressByUniverse, evmWaitForFill, FeeStore, @@ -33,53 +21,33 @@ import { mulDecimals, removeIntentHashFromStore, storeIntentHashToStore, + cosmosCreateDoubleCheckTx, + cosmosCreateRFF, } from '../utils'; -import { cosmosCreateDoubleCheckTx, cosmosCreateRFF, packERC20Approve } from './utils'; +import { packERC20Approve } from './utils'; import { + getLogger, + Intent, + NetworkConfig, BridgeAsset, EoaToEphemeralCallMap, RFFDepositCallMap, Tx, ChainListType, -} from '@nexus/commons'; + CosmosOptions, +} from '../../../commons'; const logger = getLogger(); -const createEmptyIntent = ({ - chainID, - decimals, -}: { - decimals: number; - chainID: number; -}): Intent => ({ - allSources: [], - destination: { - amount: new Decimal(0), - chainID, - decimals, - gas: 0n, - tokenContract: '0x', - universe: Universe.ETHEREUM, - }, - fees: { - caGas: '0', - collection: '0', - fulfilment: '0', - gasSupplied: '0', - protocol: '0', - solver: '0', - }, - isAvailableBalanceInsufficient: false, - sources: [], -}); - export const createIntent = ({ assets, feeStore, output, + address, }: { assets: BridgeAsset[]; feeStore: FeeStore; + address: Hex; output: { amount: Decimal; chainID: number; @@ -88,7 +56,28 @@ export const createIntent = ({ }; }) => { const eoaToEphemeralCalls: EoaToEphemeralCallMap = {}; - const intent = createEmptyIntent({ chainID: output.chainID, decimals: output.decimals }); + const intent: Intent = { + allSources: [], + recipientAddress: address, + destination: { + amount: new Decimal(0), + chainID: output.chainID, + decimals: output.decimals, + gas: 0n, + tokenContract: '0x', + universe: Universe.ETHEREUM, + }, + fees: { + caGas: '0', + collection: '0', + fulfilment: '0', + gasSupplied: '0', + protocol: '0', + solver: '0', + }, + isAvailableBalanceInsufficient: false, + sources: [], + }; let borrow = output.amount; intent.destination.amount = borrow; @@ -132,6 +121,15 @@ export const createIntent = ({ break; } + const collectionFee = feeStore.calculateCollectionFee({ + decimals: asset.decimals, + sourceChainID: asset.chainID, + sourceTokenAddress: asset.contractAddress, + }); + + intent.fees.collection = collectionFee.add(intent.fees.collection).toFixed(); + borrow = borrow.add(collectionFee); + const unaccountedBalance = borrow.minus(accountedBalance); const estimatedBorrowFromThisChain = Decimal.add( @@ -201,6 +199,8 @@ export const createIntent = ({ chainID: asset.chainID, tokenContract: asset.contractAddress, universe: Universe.ETHEREUM, + // FIXME: + holderAddress: '0x', }); accountedBalance = accountedBalance.add(borrowFromThisChain); } @@ -219,10 +219,7 @@ export const createBridgeRFF = async ({ }: { config: { chainList: ChainListType; - cosmos: { - address: string; - wallet: DirectSecp256k1Wallet; - }; + cosmos: CosmosOptions; evm: { address: `0x${string}`; client: PrivateKeyAccount; @@ -249,24 +246,19 @@ export const createBridgeRFF = async ({ assets: input.assets, feeStore, output, + address: config.evm.address, }); if (intent.isAvailableBalanceInsufficient) { - throw ErrorInsufficientBalance; + throw Errors.insufficientBalance(); } const { msgBasicCosmos, omniversalRFF, signatureData, sources } = await createRFFromIntent( intent, { chainList: config.chainList, - cosmos: { - address: config.cosmos.address, - client: config.cosmos.wallet, - }, - evm: { - address: config.evm.address, - client: config.evm.client, - }, + cosmos: config.cosmos, + evm: config.evm, }, Universe.ETHEREUM, ); @@ -278,21 +270,19 @@ export const createBridgeRFF = async ({ const createRFF = async () => { intentID = await cosmosCreateRFF({ address: config.cosmos.address, - cosmosURL: config.network.COSMOS_URL, + client: config.cosmos.client, msg: msgBasicCosmos, - wallet: config.cosmos.wallet, }); storeIntentHashToStore(config.evm.address, intentID.toNumber()); const doubleCheckTxMap: Record Promise> = {}; - omniversalRFF.protobufRFF.sources.map((s) => { + omniversalRFF.protobufRFF.sources.forEach((s) => { doubleCheckTxMap[bytesToNumber(s.chainID)] = createDoubleCheckTx( s.chainID, config.cosmos, intentID, - config.network.COSMOS_URL, ); }); @@ -315,31 +305,31 @@ export const createBridgeRFF = async ({ intent.sources.map((s) => ({ chainID: s.chainID, tokenContract: s.tokenContract, + holderAddress: config.evm.address, })), - config.evm.address, config.chainList, ); for (const [index, source] of sources.entries()) { const evmSignatureData = signatureData.find((s) => s.universe === Universe.ETHEREUM); if (!evmSignatureData) { - throw new Error('Unknown signature type'); + throw Errors.unknownSignatureType(); } const chain = config.chainList.getChainByID(Number(source.chainID)); if (!chain) { - throw new Error('chain not found'); + throw Errors.chainNotFound(source.chainID); } const allowance = allowances[Number(source.chainID)]; logger.debug('allowances', { allowance, chainID: Number(source.chainID) }); if (allowance == null) { - throw new Error('Allowance not applicable'); + throw Errors.internal('Allowance not applicable'); } const tx: Tx[] = []; - if (allowance < source.value) { + if (allowance < source.valueRaw) { const allowanceTx = { data: packERC20Approve(config.chainList.getVaultContractAddress(Number(source.chainID))), to: convertAddressByUniverse(source.tokenAddress, Universe.ETHEREUM), @@ -367,7 +357,7 @@ export const createBridgeRFF = async ({ }); depositCalls[Number(source.chainID)] = { - amount: source.value, + amount: source.valueRaw, tokenAddress: convertAddressByUniverse(source.tokenAddress, source.universe), tx: tx, }; @@ -375,7 +365,7 @@ export const createBridgeRFF = async ({ const chain = config.chainList.getChainByID(Number(output.chainID)); if (!chain) { - throw new Error('Unknown destination chain'); + throw Errors.chainNotFound(output.chainID); } const ws = webSocket(chain.rpcUrls.default.webSocket[0]); @@ -386,7 +376,7 @@ export const createBridgeRFF = async ({ const waitForFill = () => { const s = signatureData.find((s) => s.universe === Universe.ETHEREUM); if (!s) { - throw new Error('Unknown signature type'); + throw Errors.unknownSignatureType(); } logger.debug(`Waiting for fill: ${intentID}`); @@ -419,36 +409,7 @@ export const createBridgeRFF = async ({ }; }; -export const createRequestEVMSignature = async (evmRFF: EVMRFF, account: PrivateKeyAccount) => { - const abi = getAbiItem({ abi: EVMVaultABI, name: 'deposit' }); - const msg = encodeAbiParameters(abi.inputs[0].components, [ - evmRFF.sources, - evmRFF.destinationUniverse, - evmRFF.destinationChainID, - evmRFF.destinations, - evmRFF.nonce, - evmRFF.expiry, - evmRFF.parties, - ]); - const hash = keccak256(msg, 'bytes'); - const signature = toBytes( - await account.signMessage({ - message: { raw: hash }, - }), - ); - - return { requestHash: hash, signature }; -}; - -export const createDoubleCheckTx = ( - chainID: Uint8Array, - cosmos: { - address: string; - wallet: DirectSecp256k1Wallet; - }, - intentID: Long, - cosmosURL: string, -) => { +export const createDoubleCheckTx = (chainID: Uint8Array, cosmos: CosmosOptions, intentID: Long) => { const msg = MsgDoubleCheckTx.create({ creator: cosmos.address, packet: { @@ -465,9 +426,8 @@ export const createDoubleCheckTx = ( return () => { return cosmosCreateDoubleCheckTx({ address: cosmos.address, - cosmosURL, msg, - wallet: cosmos.wallet, + client: cosmos.client, }); }; }; diff --git a/packages/core/sdk/ca-base/swap/route.ts b/src/sdk/ca-base/swap/route.ts similarity index 60% rename from packages/core/sdk/ca-base/swap/route.ts rename to src/sdk/ca-base/swap/route.ts index 06e524e5..4f9cda1a 100644 --- a/packages/core/sdk/ca-base/swap/route.ts +++ b/src/sdk/ca-base/swap/route.ts @@ -2,116 +2,75 @@ import { Aggregator, autoSelectSources, ChaindataMap, + Currency, CurrencyID, destinationSwapWithExactIn, determineDestinationSwaps, - Environment, + Holding, liquidateInputHoldings, OmniversalChainID, + Quote, + QuoteRequestExactInput, Universe, - // ZeroExAggregator, -} from '@arcana/ca-common'; +} from '@avail-project/ca-common'; import Decimal from 'decimal.js'; -import { Hex, toBytes } from 'viem'; +import { ByteArray, Hex, toBytes } from 'viem'; import { ZERO_ADDRESS } from '../constants'; -import { getLogger } from '../logger'; import { + getLogger, + OraclePriceResponse, ExactInSwapInput, ExactOutSwapInput, SwapData, SwapMode, SwapParams, -} from '@nexus/commons'; + BridgeAsset, +} from '../../../commons'; + import { convertTo32BytesHex, divDecimals, equalFold, - FeeStore, fetchPriceOracle, - getEVMBalancesForAddress, getFeeStore, - getFuelBalancesForAddress, mulDecimals, + getBalances, + calculateMaxBridgeFee, } from '../utils'; import { EADDRESS } from './constants'; -import { filterSupportedTokens, FlatBalance, getTokenDecimals } from './data'; -import { - ErrorChainDataNotFound, - ErrorCOTNotFound, - ErrorInsufficientBalance, - // ErrorInsufficientBalance, - // ErrorSingleSourceHasNoSource, - ErrorTokenNotFound, -} from './errors'; +import { FlatBalance } from './data'; import { createIntent } from './rff'; -import { - balancesToAssets, - calculateValue, - convertTo32Bytes, - convertToEVMAddress, - getAnkrBalances, - toFlatBalance, -} from './utils'; -import { ChainListType, BridgeAsset } from '@nexus/commons'; +import { calculateValue, convertTo32Bytes, convertToEVMAddress } from './utils'; +import { Errors } from '../errors'; const logger = getLogger(); -export const getBalances = async (input: { - evmAddress: Hex; - chainList: ChainListType; - removeTransferFee?: boolean; - filter?: boolean; - fuelAddress?: string; - isCA?: boolean; - vscDomain: string; - networkHint: Environment; -}) => { - const isCA = input.isCA ?? false; - const removeTransferFee = input.removeTransferFee ?? false; - const filter = input.filter ?? true; - const [ankrBalances, evmBalances, fuelBalances] = await Promise.all([ - input.networkHint === Environment.FOLLY - ? Promise.resolve([]) - : getAnkrBalances(input.evmAddress, input.chainList, removeTransferFee), - getEVMBalancesForAddress(input.vscDomain, input.evmAddress), - input.fuelAddress - ? getFuelBalancesForAddress(input.vscDomain, input.fuelAddress as `0x${string}`) - : Promise.resolve([]), - ]); - const assets = balancesToAssets(ankrBalances, evmBalances, fuelBalances, input.chainList, isCA); - let balances = toFlatBalance(assets); - if (filter) { - balances = filterSupportedTokens(balances); - } - - logger.debug('getBalances', { - assets, - balances, - removeTransferFee, - }); - - return { assets, balances }; -}; - export const determineSwapRoute = async ( input: SwapData, options: SwapParams & { aggregators: Aggregator[]; cotCurrencyID: CurrencyID }, -) => { +): Promise => { logger.debug('determineSwapRoute', { input, options, }); - if (input.mode === SwapMode.EXACT_OUT) { - return _exactOutRoute(input.data, options); - } else { - return _exactInRoute(input.data, options); - } + return input.mode === SwapMode.EXACT_OUT + ? _exactOutRoute(input.data, options) + : _exactInRoute(input.data, options); }; +export const applyBuffer = (amount: Decimal, bufferPercent: number): Decimal => + amount.mul(1 + bufferPercent / 100); + +// COT = currency of transfer +// DEF: The common currency which is supported by bridge on every supported chain and acts as a transient currency for swaps. +// FLOW: Source tokens get converted to COT, COT is bridged across chains to a destination chain, +// COT is then changed to desired destination token +// Currently COT is USDC. + const _exactOutRoute = async ( input: ExactOutSwapInput, params: SwapParams & { aggregators: Aggregator[]; cotCurrencyID: CurrencyID }, -) => { +): Promise => { const [feeStore, { assets, balances }, oraclePrices] = await Promise.all([ getFeeStore(params.networkConfig.GRPC_URL), getBalances({ @@ -125,16 +84,11 @@ const _exactOutRoute = async ( fetchPriceOracle(params.networkConfig.GRPC_URL), ]); - // Any existing COT balance on dst chain - let dstEOAToEphTx: { - amount: bigint; - contractAddress: Hex; - } | null = null; - logger.debug('determineSwapRoute', { assets, balances, input }); - const userAddressInBytes = convertTo32Bytes(params.address.ephemeral); const dstOmniversalChainID = new OmniversalChainID(Universe.ETHEREUM, input.toChainId); + logger.debug('determineSwapRoute', { assets, balances, input }); + logger.debug('determineSwapRoute:destinationSwapInput', { dstOmniversalChainID, s: { @@ -144,38 +98,46 @@ const _exactOutRoute = async ( userAddressInBytes, }); + // ------------------------------ + // 2. Fetch chain & COT information + // ------------------------------ const dstChainDataMap = ChaindataMap.get(dstOmniversalChainID); if (!dstChainDataMap) { - throw ErrorChainDataNotFound; + throw Errors.internal(`chain data not found for chain ${input.toChainId}`); } const cotSymbol = CurrencyID[params.cotCurrencyID]; const dstChainCOT = dstChainDataMap.Currencies.find((c) => c.currencyID === params.cotCurrencyID); if (!dstChainCOT) { - throw ErrorCOTNotFound(input.toChainId); + throw Errors.internal(`COT not found for chain ${input.toChainId}`); } const dstChainCOTAddress = convertToEVMAddress(dstChainCOT.tokenAddress); + const dstChainCOTBalance = balances.find( (b) => b.chainID === Number(input.toChainId) && equalFold(convertToEVMAddress(b.tokenAddress), dstChainCOTAddress), ); - const getDDS = async () => { - let dds: Awaited> = { + // Track any existing COT that must be moved to ephemeral for swaps + let dstEOAToEphTx: { amount: bigint; contractAddress: Hex } | null = null; + + // Since its exact out, we start with desired destination amount and work our + // way backward from there + const fetchDestinationSwapDetails = async (): Promise => { + let destinationSwap: Awaited> = { aggregator: params.aggregators[0], inputAmount: divDecimals(input.toAmount, dstChainCOT.decimals), outputAmount: 0n, quote: null, }; - // If output token is not COT then only destination swap should exist + // If output token is not COT, calculate the actual destination swap if (!equalFold(input.toTokenAddress, dstChainCOTAddress)) { - dds = await determineDestinationSwaps( + destinationSwap = await determineDestinationSwaps( userAddressInBytes, - null, dstOmniversalChainID, { amount: BigInt(input.toAmount), @@ -187,21 +149,40 @@ const _exactOutRoute = async ( const createdAt = Date.now(); - // If destination has COT then need to send it to ephemeral so that it can be used in swap + // If user has existing COT on destination chain, it must be moved to ephemeral if (new Decimal(dstChainCOTBalance?.amount ?? 0).gt(0)) { dstEOAToEphTx = { amount: mulDecimals(dstChainCOTBalance?.amount ?? 0, dstChainCOTBalance?.decimals ?? 0), contractAddress: dstChainCOTAddress, }; } + + // min is what is actually needed for dst swap, we add 1% for bridge related fees and 1% buffer for source swaps. + // so we are charging min + 2% from the user, we add the buffer so the swap definitely happens and any pending amounts are sent back to the user. + const min = destinationSwap.inputAmount; + // Apply 2% buffer to destination input amount + const max = applyBuffer(destinationSwap.inputAmount, 2).toDP( + dstChainCOT.decimals, + Decimal.ROUND_CEIL, + ); + return { - ...dds, - createdAt, + ...destinationSwap, + originalHolding: { + chainID: dstOmniversalChainID, + tokenAddress: dstChainCOT.tokenAddress, + amount: mulDecimals(destinationSwap.inputAmount, dstChainCOT.decimals), + value: 0, + decimals: dstChainCOT.decimals, + symbol: CurrencyID[dstChainCOT.currencyID], + }, + creationTime: createdAt, dstChainCOT: dstChainCOT, dstEOAToEphTx, - inputAmountWithBuffer: dds.inputAmount - .mul(1.02) - .toDP(dstChainCOT.decimals, Decimal.ROUND_CEIL), + inputAmount: { + min, + max, + }, req: { chain: dstOmniversalChainID, inputToken: dstChainCOT.tokenAddress, @@ -210,36 +191,29 @@ const _exactOutRoute = async ( }; }; - const destinationSwap = await getDDS(); + const destinationSwap = await fetchDestinationSwapDetails(); logger.debug('destination swaps', destinationSwap); + // ------------------------------ + // 4. Compute source availability + // ------------------------------ + const cotAsset = assets.find((asset) => { return asset.abstracted && equalFold(asset.symbol, cotSymbol); }); - - const dstSwapInputAmountInDecimal = destinationSwap.inputAmount - .mul(1.02) - .toDP(dstChainCOT.decimals, Decimal.ROUND_CEIL); - - logger.debug('determineSwapRoute:3', { - cotAsset, - dstChainCOTAddress, - dstChainCOTBalance, - }); - + const dstSwapInputAmountInDecimal = destinationSwap.inputAmount.max; const cotTotalBalance = new Decimal(cotAsset?.balance ?? '0'); - - const fulfilmentFee = feeStore.calculateFulfilmentFee({ + const fees = feeStore.calculateFulfilmentFee({ decimals: dstChainCOT.decimals, destinationChainID: Number(input.toChainId), destinationTokenAddress: dstChainCOTAddress, }); - const fees = fulfilmentFee; - - logger.debug('determineSwapRoute:4', { + logger.debug('exact-out:3', { cotAsset, + dstChainCOTAddress, + dstChainCOTBalance, cotTotalBalance: cotTotalBalance.toFixed(), diff: fees.toFixed(), dstSwapInputAmountInDecimal: dstSwapInputAmountInDecimal.toFixed(), @@ -247,46 +221,64 @@ const _exactOutRoute = async ( console.log({ cotAsset, dstChainCOTBalance }); - let sourceSwaps: Awaited> = []; - let sourceSwapsRequired = false; - if (!dstChainCOTBalance) { - sourceSwapsRequired = true; - } - if (!cotAsset || new Decimal(cotAsset.balance).lt(dstSwapInputAmountInDecimal)) { - sourceSwapsRequired = true; - } + // ------------------------------ + // 5. Determine if source swaps are required + // ------------------------------ + + let sourceSwaps: QuoteResponse = []; + const sourceSwapsRequired = + !dstChainCOTBalance || + !cotAsset || + new Decimal(cotAsset.balance).lt(dstSwapInputAmountInDecimal); if (sourceSwapsRequired) { - sourceSwaps = await autoSelectSources( - userAddressInBytes, - balances.map((balance) => ({ - amount: mulDecimals(balance.amount, balance.decimals), - chainID: new OmniversalChainID(balance.universe, balance.chainID), - tokenAddress: toBytes(balance.tokenAddress), - value: balance.value, - })), - dstSwapInputAmountInDecimal - .add(fees) - .mul(1.01) - .minus(cotAsset?.balance ?? '0'), - params.aggregators, - feeStore.data.fee.collection.map((f) => ({ - ...f, - chainID: convertTo32Bytes(Number(f.chainID)), - fee: convertTo32Bytes(BigInt(f.fee)), - tokenAddress: convertTo32Bytes(f.tokenAddress as Hex), - })), - ); + sourceSwaps = ( + await autoSelectSources( + userAddressInBytes, + balances.map((balance) => ({ + amount: mulDecimals(balance.amount, balance.decimals), + chainID: new OmniversalChainID(balance.universe, balance.chainID), + tokenAddress: toBytes(balance.tokenAddress), + value: balance.value, + })), + applyBuffer(dstSwapInputAmountInDecimal.add(fees), 1).minus(cotAsset?.balance ?? '0'), + params.aggregators, + feeStore.data.fee.collection.map((f) => ({ + ...f, + chainID: convertTo32Bytes(Number(f.chainID)), + fee: convertTo32Bytes(BigInt(f.fee)), + tokenAddress: convertTo32Bytes(f.tokenAddress as Hex), + })), + ) + ).map((v) => { + const balance = balances.find((b) => + equalFold(b.tokenAddress, convertTo32BytesHex(v.req.inputToken)), + ); + if (!balance) { + throw Errors.internal('mapping error: balance for quote input not found'); + } + return { + ...v, + originalHolding: { + ...v.originalHolding, + decimals: balance.decimals, + symbol: balance.symbol, + }, + }; + }); } const sourceSwapCreationTime = Date.now(); - console.log({ + logger.debug('exact-out:4', { dstChainCOTBalance, inequality: new Decimal(dstChainCOTBalance?.amount ?? 0).lt( dstSwapInputAmountInDecimal.add(fees), ), }); + // ------------------------------ + // 6. Bridge input calculation (account for already existing COT + COT from swaps) + // ------------------------------ let bridgeInput: { amount: Decimal; assets: BridgeAsset[]; @@ -322,7 +314,7 @@ const _exactOutRoute = async ( convertToEVMAddress(swap.req.outputToken), ); if (!token) { - throw ErrorTokenNotFound( + throw Errors.tokenNotFound( convertToEVMAddress(swap.req.outputToken), Number(swap.req.chain.chainID), ); @@ -375,6 +367,10 @@ const _exactOutRoute = async ( } } + // ------------------------------ + // 7. Prepare assets used to show in intent + // ------------------------------ + const assetsUsed: { amount: string; chainID: number; @@ -384,17 +380,12 @@ const _exactOutRoute = async ( }[] = []; for (const swap of sourceSwaps) { - const { decimals, symbol } = getTokenDecimals( - Number(swap.req.chain.chainID), - swap.req.inputToken, - ); - assetsUsed.push({ - amount: divDecimals(swap.quote.inputAmount, decimals).toFixed(), + amount: divDecimals(swap.quote.inputAmount, swap.originalHolding.decimals).toFixed(), chainID: Number(swap.req.chain.chainID), contractAddress: convertToEVMAddress(swap.req.inputToken), - decimals, - symbol, + decimals: swap.originalHolding.decimals, + symbol: swap.originalHolding.symbol, }); } @@ -403,6 +394,7 @@ const _exactOutRoute = async ( assets: bridgeAssets, feeStore, output: bridgeInput, + address: params.address.ephemeral, }); for (const chain in eoaToEphemeralCalls) { @@ -418,18 +410,77 @@ const _exactOutRoute = async ( }); } } - return { - aggregators: params.aggregators, - assetsUsed, - balances, - bridgeInput, - cotSymbol, - destinationSwap, - getDDS, - oraclePrices, - sourceSwapCreationTime, - sourceSwaps, + source: { + swaps: sourceSwaps, + creationTime: sourceSwapCreationTime, + }, + bridge: bridgeInput, + destination: { + type: 'EXACT_OUT', + swap: destinationSwap, + fetchDestinationSwapDetails, + }, + extras: { + aggregators: params.aggregators, + oraclePrices, + balances, + assetsUsed, + cotSymbol, + }, + }; +}; + +type DestinationSwap = { + creationTime: number; + dstChainCOT: Currency; + dstEOAToEphTx: { + amount: bigint; + contractAddress: Hex; + } | null; + inputAmount: { min: Decimal; max: Decimal }; + req: { + chain: OmniversalChainID; + inputToken: Buffer; + outputToken: ByteArray; + }; + quote: Quote | null; + aggregator: Aggregator; + originalHolding: Holding & { symbol: string; decimals: number }; + outputAmount: bigint; +}; + +export type SwapRoute = { + source: { + swaps: ({ + req: QuoteRequestExactInput; + cfee: bigint; + originalHolding: Holding & { decimals: number; symbol: string }; + cur: Currency; + } & { + quote: Quote; + agg: Aggregator; + })[]; + creationTime: number; + }; + bridge: BridgeInput; + destination: { + type: 'EXACT_IN' | 'EXACT_OUT'; + swap: DestinationSwap; + fetchDestinationSwapDetails: () => Promise; + }; + extras: { + assetsUsed: { + amount: string; + chainID: number; + contractAddress: Hex; + decimals: number; + symbol: string; + }[]; + aggregators: Aggregator[]; + oraclePrices: OraclePriceResponse; + balances: FlatBalance[]; + cotSymbol: string; }; }; @@ -449,69 +500,28 @@ type BridgeInput = { tokenAddress: `0x${string}`; } | null; -const calculateMaxBridgeFees = ({ - assets, - feeStore, - dst, -}: { - dst: { - chainId: number; - tokenAddress: Hex; - decimals: number; - }; - assets: BridgeAsset[]; - feeStore: FeeStore; -}) => { - const borrow = assets.reduce((accumulator, asset) => { - return accumulator.add(Decimal.add(asset.eoaBalance, asset.ephemeralBalance)); - }, new Decimal(0)); - - const protocolFee = feeStore.calculateProtocolFee(new Decimal(borrow)); - let borrowWithFee = borrow.add(protocolFee); - - const fulfilmentFee = feeStore.calculateFulfilmentFee({ - decimals: dst.decimals, - destinationChainID: dst.chainId, - destinationTokenAddress: dst.tokenAddress, - }); - borrowWithFee = borrowWithFee.add(fulfilmentFee); - - logger.debug('calculateMaxBridgeFees:1', { - borrow: borrow.toFixed(), - protocolFee: protocolFee.toFixed(), - fulfilmentFee: fulfilmentFee.toFixed(), - borrowWithFee: borrowWithFee.toFixed(), - }); - - for (const asset of assets) { - const solverFee = feeStore.calculateSolverFee({ - borrowAmount: Decimal.add(asset.eoaBalance, asset.ephemeralBalance), - decimals: asset.decimals, - destinationChainID: dst.chainId, - destinationTokenAddress: dst.tokenAddress, - sourceChainID: asset.chainID, - sourceTokenAddress: convertToEVMAddress(asset.contractAddress), - }); - - borrowWithFee = borrowWithFee.add(solverFee); - logger.debug('calculateMaxBridgeFees:2', { - borrow: borrow.toFixed(), - borrowWithFee: borrowWithFee.toFixed(), - solverFee: solverFee.toFixed(), - }); - } +type QuoteResponse = { + agg: Aggregator; + quote: Quote; + req: QuoteRequestExactInput; + cfee: bigint; + originalHolding: Holding & { decimals: number; symbol: string }; + cur: Currency; +}[]; - return borrowWithFee.minus(borrow); -}; +// Helper to normalize token comparison tokens (preserve EADDRESS vs ZERO_ADDRESS handling) +const normalizeToComparisonAddr = (tokenHex: Hex) => + convertTo32BytesHex(equalFold(tokenHex, ZERO_ADDRESS) ? EADDRESS : tokenHex); const _exactInRoute = async ( input: ExactInSwapInput, params: SwapParams & { aggregators: Aggregator[]; cotCurrencyID: CurrencyID }, -) => { +): Promise => { logger.debug('exactInRoute', { input, params, }); + const [feeStore, balanceResponse, oraclePrices] = await Promise.all([ getFeeStore(params.networkConfig.GRPC_URL), getBalances({ @@ -519,10 +529,17 @@ const _exactInRoute = async ( evmAddress: params.address.eoa, chainList: params.chainList, removeTransferFee: true, + filter: false, vscDomain: params.networkConfig.VSC_DOMAIN, }), fetchPriceOracle(params.networkConfig.GRPC_URL), - ]); + ]).catch((e) => { + throw Errors.internal('Error fetching fee, balance or oracle', { cause: e }); + }); + + if (balanceResponse.balances.length === 0) { + throw Errors.noBalanceForAddress(params.address.eoa); + } let { balances } = balanceResponse; @@ -532,29 +549,37 @@ const _exactInRoute = async ( const assetsUsed: AssetUsed = []; let srcBalances: FlatBalance[] = []; - if (input.from) { + + if (input.from.length > 0) { + // Filter out sources user requested to be used for (const f of input.from) { - const srcBalance = balances.find((b) => { - logger.debug('ExactIN:2:input.src', { - a: b.tokenAddress, - b: convertTo32BytesHex(f.tokenAddress), - }); + if (typeof f.amount !== 'bigint') { + throw new TypeError('input.from.amount must be bigint'); + } - // We are keeping ZERO_ADDRESS as EAddress so have to make the comparisonAddr like this - let comparisonTokenAddress = convertTo32BytesHex(f.tokenAddress); - if (equalFold(comparisonTokenAddress, ZERO_ADDRESS)) { - comparisonTokenAddress = EADDRESS; - } + const comparison = normalizeToComparisonAddr(f.tokenAddress); - return equalFold(b.tokenAddress, comparisonTokenAddress) && f.chainId === b.chainID; + const srcBalance = balances.find((b) => { + logger.debug('ExactIn: from comparison', { + balanceTokenAddress: b.tokenAddress, + inputTokenAddress: f.tokenAddress, + comparisonTokenAddress: comparison, + }); + return equalFold(b.tokenAddress, comparison) && f.chainId === b.chainID; }); if (!srcBalance) { - throw ErrorInsufficientBalance(f.amount.toString(), '0'); + logger.error('ExactIN: no src balance found', { + token: f.tokenAddress, + chainId: f.chainId, + }); + throw Errors.insufficientBalance(`available: 0, required: ${f.amount.toString()}`); } const requiredBalance = divDecimals(f.amount, srcBalance.decimals); if (requiredBalance.gt(srcBalance.amount)) { - throw ErrorInsufficientBalance(srcBalance.amount, requiredBalance.toFixed()); + throw Errors.insufficientBalance( + `available: ${srcBalance.amount}, required: ${requiredBalance.toFixed()}`, + ); } srcBalances.push({ @@ -571,31 +596,22 @@ const _exactInRoute = async ( symbol: srcBalance.symbol, }); } - // } else { - // throw new Error('should have gone to single source swap route'); - // } } else { - srcBalances = balances; + srcBalances = balances.slice(); } - logger.debug('ExactIN:3', { - srcBalances, - assetsUsed, - }); - const userAddressInBytes = convertTo32Bytes(params.address.ephemeral); const dstOmniversalChainID = new OmniversalChainID(Universe.ETHEREUM, input.toChainId); const dstChainDataMap = ChaindataMap.get(dstOmniversalChainID); if (!dstChainDataMap) { - throw new Error('chaindataMap not found'); + throw Errors.internal(`chain data not found for chain ${input.toChainId}`); } const cotSymbol = CurrencyID[params.cotCurrencyID]; - const dstChainCOT = dstChainDataMap.Currencies.find((c) => c.currencyID === params.cotCurrencyID); if (!dstChainCOT) { - throw ErrorCOTNotFound(input.toChainId); + throw Errors.internal(`COT not found for chain ${input.toChainId}`); } const dstChainCOTAddress = convertToEVMAddress(dstChainCOT.tokenAddress); @@ -615,6 +631,7 @@ const _exactInRoute = async ( ) { cotSources.push(source); cotCombinedBalance = cotCombinedBalance.add(source.amount); + bridgeAssets.push({ chainID: source.chainID, contractAddress: convertToEVMAddress(source.tokenAddress), @@ -634,7 +651,6 @@ const _exactInRoute = async ( // Check if source swap is required (if all source balances are not COT currencyID) const isSrcSwapRequired = cotSources.length !== srcBalances.length; - // Check if bridge is required (if all source balances are not on destination chain) const isBridgeRequired = !srcBalances.every((b) => b.chainID === input.toChainId); @@ -643,7 +659,7 @@ const _exactInRoute = async ( isBridgeRequired, }); - let sourceSwaps: Awaited>['quotes'] = []; + let sourceSwaps: QuoteResponse = []; if (isSrcSwapRequired) { const response = await liquidateInputHoldings( userAddressInBytes, @@ -662,7 +678,29 @@ const _exactInRoute = async ( })), ); - sourceSwaps = response.quotes; + if (!response.quotes.length) { + throw Errors.quoteFailed('source swap returned no quotes'); + } + + sourceSwaps = response.quotes.map((oq) => { + const balance = balances.find((b) => + equalFold(b.tokenAddress, convertTo32BytesHex(oq.req.inputToken)), + ); + if (!balance) { + logger.error('ExactIN: failed to map quote originalHolding to balance', { + quoteReq: oq.req, + }); + throw Errors.internal('mapping error: balance for quote input not found'); + } + return { + ...oq, + originalHolding: { + ...oq.originalHolding, + decimals: balance.decimals, + symbol: balance.symbol, + }, + }; + }); } const sourceSwapCreationTime = Date.now(); @@ -674,7 +712,7 @@ const _exactInRoute = async ( outputTokenAddress, ); if (!token) { - throw ErrorTokenNotFound(outputTokenAddress, Number(swap.req.chain.chainID)); + throw Errors.tokenNotFound(outputTokenAddress, Number(swap.req.chain.chainID)); } const bridgeAsset = bridgeAssets.find((b) => equalFold(b.contractAddress, outputTokenAddress)); const outputAmountInDecimal = divDecimals(swap.quote.outputAmountMinimum, token.decimals); @@ -702,8 +740,8 @@ const _exactInRoute = async ( let bridgeInput: BridgeInput = null; if (isBridgeRequired) { - const maxFee = calculateMaxBridgeFees({ - assets: bridgeAssets, + const { fee: maxFee } = calculateMaxBridgeFee({ + assets: bridgeAssets.map((b) => ({ ...b, balance: b.eoaBalance.add(b.ephemeralBalance) })), dst: { chainId: input.toChainId, tokenAddress: dstChainCOTAddress, @@ -712,11 +750,15 @@ const _exactInRoute = async ( feeStore, }); - dstSwapInputAmountInDecimal = dstSwapInputAmountInDecimal.minus(maxFee).mul(0.98); + dstSwapInputAmountInDecimal = dstSwapInputAmountInDecimal.minus(maxFee); logger.debug('ExactIN:7', { dstSwapInputAmountInDecimal: dstSwapInputAmountInDecimal.toFixed(), maxFee: maxFee.toFixed(), }); + if (dstSwapInputAmountInDecimal.isNegative()) { + throw Errors.internal('bridge fees exceeds source amount'); + } + bridgeInput = { amount: dstSwapInputAmountInDecimal, assets: bridgeAssets, @@ -730,8 +772,8 @@ const _exactInRoute = async ( dstSwapInputAmountInDecimal: dstSwapInputAmountInDecimal.toFixed(), }); - const getDDS = async () => { - let dds: Awaited> = { + const fetchDestinationSwapDetails = async () => { + let destinationSwap: Awaited> = { aggregator: params.aggregators[0], inputAmount: dstSwapInputAmountInDecimal, outputAmount: mulDecimals(dstSwapInputAmountInDecimal, dstChainCOT.decimals), @@ -749,7 +791,7 @@ const _exactInRoute = async ( // If toTokenAddress is not same as cot then create dstSwap if (!equalFold(input.toTokenAddress, dstChainCOTAddress)) { - dds = await destinationSwapWithExactIn( + destinationSwap = await destinationSwapWithExactIn( userAddressInBytes, dstOmniversalChainID, mulDecimals(dstSwapInputAmountInDecimal, dstChainCOT.decimals), @@ -759,7 +801,7 @@ const _exactInRoute = async ( ); } - const createdAt = Date.now(); + // const createdAt = Date.now(); let dstEOAToEphTx: { amount: bigint; contractAddress: Hex; @@ -776,39 +818,55 @@ const _exactInRoute = async ( } logger.debug('ExactIN: getDDS: SingleSrcSwap: After', { - dds, + destinationSwap, dstSwapInputAmountInDecimal: dstSwapInputAmountInDecimal.toFixed(), }); return { - ...dds, - createdAt, + ...destinationSwap, + originalHolding: { + chainID: dstOmniversalChainID, + tokenAddress: dstChainCOT.tokenAddress, + amount: mulDecimals(dstSwapInputAmountInDecimal, dstChainCOT.decimals), + value: 0, + decimals: dstChainCOT.decimals, + symbol: CurrencyID[dstChainCOT.currencyID], + }, dstChainCOT: dstChainCOT, dstEOAToEphTx, - inputAmountWithBuffer: dstSwapInputAmountInDecimal, + inputAmount: { min: dstSwapInputAmountInDecimal, max: dstSwapInputAmountInDecimal }, req: { chain: dstOmniversalChainID, inputToken: dstChainCOT.tokenAddress, outputToken: toBytes(input.toTokenAddress), }, + creationTime: Date.now(), }; }; - const destinationSwap = await getDDS(); + const destinationSwap = await fetchDestinationSwapDetails(); logger.debug('getSwapRoute: ExactIN: After', { destinationSwap, dstSwapInputAmountInDecimal: dstSwapInputAmountInDecimal.toFixed(), }); + return { - aggregators: params.aggregators, - assetsUsed, - balances, - bridgeInput, - cotSymbol, - destinationSwap, - getDDS, - oraclePrices, - sourceSwapCreationTime, - sourceSwaps, + source: { + swaps: sourceSwaps, + creationTime: sourceSwapCreationTime, + }, + bridge: bridgeInput, + destination: { + type: 'EXACT_IN', + swap: destinationSwap, + fetchDestinationSwapDetails, + }, + extras: { + assetsUsed, + aggregators: params.aggregators, + oraclePrices, + balances, + cotSymbol, + }, }; }; diff --git a/packages/core/sdk/ca-base/swap/sbc.ts b/src/sdk/ca-base/swap/sbc.ts similarity index 91% rename from packages/core/sdk/ca-base/swap/sbc.ts rename to src/sdk/ca-base/swap/sbc.ts index 76b925ef..a068d53d 100644 --- a/packages/core/sdk/ca-base/swap/sbc.ts +++ b/src/sdk/ca-base/swap/sbc.ts @@ -1,10 +1,9 @@ -import { Universe } from '@arcana/ca-common'; +import { Universe } from '@avail-project/ca-common'; import { bytesToBigInt, Chain, encodeAbiParameters, Hex, - maxUint256, PrivateKeyAccount, PublicClient, SignAuthorizationReturnType, @@ -13,12 +12,11 @@ import { WalletClient, } from 'viem'; -import { getLogger } from '../logger'; -import { waitForTxReceipt } from '../utils'; +import { createDeadlineFromNow, waitForTxReceipt } from '../utils'; import CaliburABI from './calibur.abi'; import { CALIBUR_ADDRESS, CALIBUR_EIP712, ZERO_BYTES_20, ZERO_BYTES_32 } from './constants'; import { Cache, convertTo32Bytes, isAuthorizationCodeSet, PublicClientList } from './utils'; -import { ChainListType, CaliburSBCTypes, SBCTx, Tx } from '@nexus/commons'; +import { getLogger, ChainListType, CaliburSBCTypes, SBCTx, Tx } from '../../../commons'; const logger = getLogger(); @@ -28,6 +26,7 @@ export const createBatchedCallSignature = ( chain: bigint, address: `0x${string}`, account: PrivateKeyAccount, + deadline: bigint, ) => { return account.signTypedData({ domain: { @@ -42,7 +41,7 @@ export const createBatchedCallSignature = ( calls: batchedCalls, revertOnFailure: true, }, - deadline: maxUint256, + deadline, executor: toHex(ZERO_BYTES_20), keyHash: toHex(ZERO_BYTES_32), nonce, @@ -87,20 +86,20 @@ export const createSBCTxFromCalls = async ({ publicClient: PublicClient; }) => { const nonce = bytesToBigInt(window.crypto.getRandomValues(new Uint8Array(24))) << 64n; - + const deadline = createDeadlineFromNow(3n); const signature = await createBatchedCallSignature( calls, nonce, BigInt(chainID), ephemeralAddress, ephemeralWallet, + deadline, ); let authorization: null | SignAuthorizationReturnType = null; if (!(await isAuthorizationCodeSet(chainID, ephemeralAddress, cache))) { const nonce = await publicClient.getTransactionCount({ address: ephemeralAddress, - blockTag: 'pending', }); // create authorization for calibur @@ -120,7 +119,7 @@ export const createSBCTxFromCalls = async ({ value: convertTo32Bytes(c.value), })), chain_id: convertTo32Bytes(chainID), - deadline: toBytes(maxUint256), + deadline: convertTo32Bytes(deadline), key_hash: ZERO_BYTES_32, nonce: convertTo32Bytes(nonce), revert_on_failure: true, @@ -164,13 +163,14 @@ export const caliburExecute = async ({ value: bigint; }) => { const nonce = bytesToBigInt(window.crypto.getRandomValues(new Uint8Array(24))) << 64n; - + const deadline = createDeadlineFromNow(3n); const signature = await createBatchedCallSignature( calls, nonce, BigInt(chain.id), ephemeralAddress, ephemeralWallet, + deadline, ); return actualWallet.writeContract({ @@ -183,7 +183,7 @@ export const caliburExecute = async ({ calls, revertOnFailure: true, }, - deadline: maxUint256, + deadline, executor: toHex(ZERO_BYTES_20), keyHash: toHex(ZERO_BYTES_32), nonce, diff --git a/packages/core/sdk/ca-base/swap/swap.ts b/src/sdk/ca-base/swap/swap.ts similarity index 67% rename from packages/core/sdk/ca-base/swap/swap.ts rename to src/sdk/ca-base/swap/swap.ts index 5b5b1954..67ac5c36 100644 --- a/packages/core/sdk/ca-base/swap/swap.ts +++ b/src/sdk/ca-base/swap/swap.ts @@ -4,16 +4,23 @@ import { CurrencyID, LiFiAggregator, Universe, -} from '@arcana/ca-common'; +} from '@avail-project/ca-common'; -import { SwapMode, type SwapData, type SwapParams, SuccessfulSwapResult } from '@nexus/commons'; - -import { getLogger } from '../logger'; +import { + SwapMode, + type SwapData, + type SwapParams, + SuccessfulSwapResult, + NEXUS_EVENTS, + SWAP_STEPS, + SwapStepType, +} from '../../../commons'; + +import { getLogger } from '../../../commons'; import { divDecimals } from '../utils'; import { BEBOP_API_KEY, LIFI_API_KEY, ZERO_BYTES_32 } from './constants'; import { BridgeHandler, DestinationSwapHandler, SourceSwapsHandler } from './ob'; import { determineSwapRoute } from './route'; -import { DETERMINING_SWAP, SWAP_START, SwapStep } from './steps'; import { Cache, convertMetadataToSwapResult, @@ -24,6 +31,7 @@ import { PublicClientList, SwapMetadata, } from './utils'; +import { Errors } from '../errors'; const logger = getLogger(); @@ -40,24 +48,26 @@ export const swap = async ( const publicClientList = new PublicClientList(options.chainList); const cache = new Cache(publicClientList); const dstChain = options.chainList.getChainByID(input.data.toChainId); - if (!dstChain) { - throw new Error('destination chain not supported'); + throw Errors.chainNotFound(input.data.toChainId); } + performance.mark('swap-start'); const emitter = { - emit: (step: SwapStep) => { - options.emit('swap_step', step); + emit: (step: SwapStepType) => { + if (options.onEvent) { + options.onEvent({ name: NEXUS_EVENTS.SWAP_STEP_COMPLETE, args: step }); + } }, }; - emitter.emit(SWAP_START); + emitter.emit(SWAP_STEPS.SWAP_START); logger.debug('swapBegin', { options, input }); performance.mark('determine-swaps-start'); - emitter.emit(DETERMINING_SWAP()); + emitter.emit(SWAP_STEPS.DETERMINING_SWAP()); const aggregators: Aggregator[] = [ new LiFiAggregator(LIFI_API_KEY), @@ -77,77 +87,73 @@ export const swap = async ( swapRoute, }); - let { assetsUsed, bridgeInput, destinationSwap, sourceSwaps } = swapRoute; + let { source, destination, bridge, extras } = swapRoute; logger.debug('initial-swap-route', { - assetsUsed, - bridgeInput, - destinationSwap, + source, + destination, + bridge, + extras, dstTokenInfo, - sourceSwaps, swapRoute, }); - emitter.emit(DETERMINING_SWAP(true)); + emitter.emit(SWAP_STEPS.DETERMINING_SWAP(true)); performance.mark('determine-swaps-end'); performance.mark('xcs-ops-start'); // Swap Intent hook handling { - if (options?.swapIntentHook) { - const hook = options?.swapIntentHook; - - const destination = { - amount: divDecimals( - input.mode === SwapMode.EXACT_OUT ? input.data.toAmount : destinationSwap.outputAmount, - dstTokenInfo.decimals, - ).toFixed(), - chainID: input.data.toChainId, - contractAddress: input.data.toTokenAddress, - decimals: dstTokenInfo.decimals, - symbol: dstTokenInfo.symbol, + const destinationTokenDetails = { + amount: divDecimals( + input.mode === SwapMode.EXACT_OUT ? input.data.toAmount : destination.swap.outputAmount, + dstTokenInfo.decimals, + ).toFixed(), + chainID: input.data.toChainId, + contractAddress: input.data.toTokenAddress, + decimals: dstTokenInfo.decimals, + symbol: dstTokenInfo.symbol, + }; + + let accepted = false; + + const refresh = async () => { + if (accepted) { + logger.warn('Swap Intent refresh called after acceptance'); + return createSwapIntent(extras.assetsUsed, destinationTokenDetails, options.chainList); + } + + const swapRouteResponse = await determineSwapRoute(input, swapRouteParams); + + source = swapRouteResponse.source; + extras = swapRouteResponse.extras; + destination = swapRouteResponse.destination; + bridge = swapRouteResponse.bridge; + logger.debug('refresh-swap-route', { + dstTokenInfo, + swapRoute: swapRouteResponse, + }); + return createSwapIntent(extras.assetsUsed, destinationTokenDetails, options.chainList); + }; + // wait for intent acceptance hook + await new Promise((resolve, reject) => { + const allow = () => { + accepted = true; + return resolve('User allowed intent'); }; - let accepted = false; - - const refresh = async () => { - if (accepted) { - logger.warn('Swap Intent refresh called after acceptance'); - return createSwapIntent(assetsUsed, destination, options.chainList); - } - - const swapRouteResponse = await determineSwapRoute(input, swapRouteParams); - - sourceSwaps = swapRouteResponse.sourceSwaps; - assetsUsed = swapRouteResponse.assetsUsed; - destinationSwap = swapRouteResponse.destinationSwap; - bridgeInput = swapRouteResponse.bridgeInput; - logger.debug('refresh-swap-route', { - dstTokenInfo, - swapRoute: swapRouteResponse, - }); - return createSwapIntent(assetsUsed, destination, options.chainList); + const deny = () => { + return reject(ErrorUserDeniedIntent); }; - // wait for intent acceptance hook - await new Promise((resolve, reject) => { - const allow = () => { - accepted = true; - return resolve('User allowed intent'); - }; - - const deny = () => { - return reject(ErrorUserDeniedIntent); - }; - - hook({ - allow, - deny, - intent: createSwapIntent(assetsUsed, destination, options.chainList), - refresh, - }); + + options.onSwapIntent({ + allow, + deny, + intent: createSwapIntent(extras.assetsUsed, destinationTokenDetails, options.chainList), + refresh, }); - } + }); } const metadata: SwapMetadata = { @@ -179,16 +185,16 @@ export const swap = async ( wallet: options.wallet, }; - const srcSwapsHandler = new SourceSwapsHandler(sourceSwaps, opt); - const bridgeHandler = new BridgeHandler(bridgeInput, opt); + const srcSwapsHandler = new SourceSwapsHandler(source, opt); + const bridgeHandler = new BridgeHandler(bridge, opt); const dstSwapHandler = new DestinationSwapHandler( - { ...destinationSwap, getDDS: swapRoute.getDDS }, + destination, dstTokenInfo, { chainID: input.data.toChainId, token: input.data.toTokenAddress, amount: - input.mode === SwapMode.EXACT_OUT ? input.data.toAmount : destinationSwap.outputAmount, + input.mode === SwapMode.EXACT_OUT ? input.data.toAmount : destination.swap.outputAmount, }, opt, ); @@ -220,7 +226,7 @@ export const swap = async ( }); logger.debug('SwapID', { id }); } catch (e) { - logger.error('postSwap', e); + logger.error('postSwap', e, {cause: 'SWAP_FAILED'}); } calculatePerformance(); @@ -248,7 +254,7 @@ const calculatePerformance = () => { const entries = performance.getEntries(); - if (entries.find((entry) => entry.name === 'source-swap-tx-start')) { + if (entries.some((entry) => entry.name === 'source-swap-tx-start')) { measures.push( performance.measure( 'source-swap-tx-duration', diff --git a/packages/core/sdk/ca-base/swap/utils.ts b/src/sdk/ca-base/swap/utils.ts similarity index 64% rename from packages/core/sdk/ca-base/swap/utils.ts rename to src/sdk/ca-base/swap/utils.ts index ebb56509..d808d410 100644 --- a/packages/core/sdk/ca-base/swap/utils.ts +++ b/src/sdk/ca-base/swap/utils.ts @@ -4,25 +4,18 @@ import { BebopQuote, Bytes, ChaindataMap, - createCosmosClient, CurrencyID, ERC20ABI, - EVMRFF, - EVMVaultABI, + Holding, LiFiAggregator, LiFiQuote, - MsgCreateRequestForFunds, - MsgCreateRequestForFundsResponse, - MsgDoubleCheckTx, msgpackableAxios, OmniversalChainID, PermitVariant, Quote, Universe, -} from '@arcana/ca-common'; +} from '@avail-project/ca-common'; import CaliburABI from './calibur.abi'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; -import { isDeliverTxFailure } from '@cosmjs/stargate'; import axios from 'axios'; import Decimal from 'decimal.js'; import { retry } from 'es-toolkit'; @@ -34,14 +27,11 @@ import { bytesToNumber, concat, createPublicClient, - encodeAbiParameters, encodeFunctionData, - getAbiItem, getContract, Hex, hexToBigInt, http, - keccak256, maxUint256, pad, parseSignature, @@ -50,37 +40,40 @@ import { toBytes, toHex, WalletClient, - WebSocketTransport, } from 'viem'; - import { ERC20PermitABI, ERC20PermitEIP2612PolygonType, ERC20PermitEIP712Type } from '../abi/erc20'; -import { FillEvent } from '../abi/vault'; import { getLogoFromSymbol, ZERO_ADDRESS } from '../constants'; -import { getLogger } from '../logger'; import { + getLogger, Chain, SuccessfulSwapResult, - TokenInfo, UnifiedBalanceResponseData, UserAssetDatum, -} from '@nexus/commons'; + SWAP_STEPS, + SwapStepType, + AnkrAsset, + AnkrBalances, + SBCTx, + SwapIntent, + Tx, + ChainListType, +} from '../../../commons'; import { convertAddressByUniverse, convertTo32BytesHex, + createDeadlineFromNow, divDecimals, equalFold, - getCosmosURL, getExplorerURL, getVSCURL, waitForTxReceipt, } from '../utils'; import { SWEEP_ABI } from './abi'; import { CALIBUR_ADDRESS, EADDRESS, SWEEPER_ADDRESS } from './constants'; -import { chainData, getTokenVersion } from './data'; +import { chainData, FlatBalance, getTokenVersion } from './data'; import { createSBCTxFromCalls, waitForSBCTxReceipt } from './sbc'; -import { DESTINATION_SWAP_HASH, SwapStep } from './steps'; -import { AnkrAsset, AnkrBalances, SBCTx, SwapIntent, Tx, ChainListType } from '@nexus/commons'; import Long from 'long'; +import { Errors } from '../errors'; const logger = getLogger(); @@ -115,7 +108,7 @@ export const convertToEVMAddress = (address: Hex | Uint8Array) => { return toHex(address.subarray(12)); } - throw new Error('Invalid address'); + throw Errors.invalidAddressLength('evm'); }; export const bytesEqual = (bytes1: Uint8Array, bytes2: Uint8Array): boolean => { @@ -182,6 +175,7 @@ export const createPermitSignature = async ( walletAddress: Hex, variant: PermitVariant, version: number, + deadline: bigint, ) => { const contract = getContract({ abi: ERC20ABI, @@ -204,7 +198,7 @@ export const createPermitSignature = async ( version, }, message: { - deadline: maxUint256, + deadline, nonce, owner: walletAddress, spender: spender, @@ -226,7 +220,7 @@ export const createPermitSignature = async ( version: version.toString(), }, message: { - deadline: maxUint256, + deadline, nonce, owner: walletAddress, spender: spender, @@ -263,7 +257,7 @@ export const createPermitSignature = async ( }; } default: { - throw new Error('Token Not supported: (2612 details not found)'); + throw Errors.tokenNotSupported(undefined, undefined, '(2612 details not found)'); } } }; @@ -288,7 +282,7 @@ export const vscSBCTx = async (input: SBCTx[], vscDomain: string) => { logger.debug('vscSBCTx', { data }); if (data.errored) { - throw new Error('Error in VSC SBC Tx'); + throw Errors.internal('Error in VSC SBC Tx'); } ops.push([bytesToBigInt(input[data.part_idx].chain_id), toHex(data.tx_hash)]); @@ -305,103 +299,6 @@ export const vscSBCTx = async (input: SBCTx[], vscDomain: string) => { return ops; }; -export const createRequestEVMSignature = async (evmRFF: EVMRFF, client: WalletClient) => { - const account = (await client.getAddresses())[0]; - const abi = getAbiItem({ abi: EVMVaultABI, name: 'deposit' }); - const msg = encodeAbiParameters(abi.inputs[0].components, [ - evmRFF.sources, - evmRFF.destinationUniverse, - evmRFF.destinationChainID, - evmRFF.destinations, - evmRFF.nonce, - evmRFF.expiry, - evmRFF.parties, - ]); - const hash = keccak256(msg, 'bytes'); - const signature = toBytes( - await client.signMessage({ - account, - message: { raw: hash }, - }), - ); - - return { requestHash: hash, signature }; -}; - -export const cosmosCreateRFF = async ({ - address, - cosmosURL, - msg, - wallet, -}: { - address: string; - cosmosURL: string; - msg: MsgCreateRequestForFunds; - wallet: DirectSecp256k1Wallet; -}) => { - const client = await createCosmosClient(wallet, getCosmosURL(cosmosURL, 'rpc'), { - broadcastPollIntervalMs: 250, - }); - - const res = await client.signAndBroadcast( - address, - [ - { - typeUrl: '/xarchain.chainabstraction.MsgCreateRequestForFunds', - value: msg, - }, - ], - { - amount: [], - gas: 100_000n.toString(10), - }, - ); - - if (isDeliverTxFailure(res)) { - throw new Error('Error creating RFF'); - } - - const decoded = MsgCreateRequestForFundsResponse.decode(res.msgResponses[0].value); - return decoded.id; -}; -export const cosmosCreateDoubleCheckTx = async ({ - address, - cosmosURL, - msg, - wallet, -}: { - address: string; - cosmosURL: string; - msg: MsgDoubleCheckTx; - wallet: DirectSecp256k1Wallet; -}) => { - const client = await createCosmosClient(wallet, getCosmosURL(cosmosURL, 'rpc'), { - broadcastPollIntervalMs: 250, - }); - - logger.debug('cosmosCreateDoubleCheckTx:1', { doubleCheckMsg: msg }); - - const res = await client.signAndBroadcast( - address, - [ - { - typeUrl: '/xarchain.chainabstraction.MsgDoubleCheckTx', - value: msg, - }, - ], - { - amount: [], - gas: 100_000n.toString(10), - }, - ); - - if (isDeliverTxFailure(res)) { - throw new Error('Error creating MsgDoubleCheckTx'); - } - - logger.debug('cosmosCreateDoubleCheckTx:2', { doubleCheckTx: res }); -}; - export const EXPECTED_CALIBUR_CODE = concat(['0xef0100', CALIBUR_ADDRESS]); export const isAuthorizationCodeSet = async ( @@ -485,7 +382,7 @@ export const createPermitAndTransferFromTx = async ({ logger.debug('createPermitTx', { allowance, amount }); if (allowance < amount) { - const { variant, version } = getTokenVersion(contractAddress); + const { variant, version } = await getTokenVersion(contractAddress, publicClient); if (variant === PermitVariant.Unsupported) { const { request } = await publicClient.simulateContract({ chain, @@ -535,6 +432,116 @@ export const createPermitAndTransferFromTx = async ({ return txList; }; +export const determinePermitVariantAndVersion = async ( + client: PublicClient, + contractAddress: Hex, +) => { + const standardPermitData = encodeFunctionData({ + abi: [ + { + type: 'function', + name: 'permit', + inputs: [ + { type: 'address', name: 'owner' }, + { type: 'address', name: 'spender' }, + { type: 'uint256', name: 'value' }, + { type: 'uint256', name: 'deadline' }, + { type: 'uint8', name: 'v' }, + { type: 'bytes32', name: 'r' }, + { type: 'bytes32', name: 's' }, + ], + }, + ], + functionName: 'permit', + args: [ + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + 0n, + 0n, + 0, + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ], + }); + + // Dummy data for DAI-style permit (holder=spender=zero, nonce=0, expiry=0, allowed=true, v=0, r=0, s=0) + const daiPermitData = encodeFunctionData({ + abi: [ + { + type: 'function', + name: 'permit', + inputs: [ + { type: 'address', name: 'holder' }, + { type: 'address', name: 'spender' }, + { type: 'uint256', name: 'nonce' }, + { type: 'uint256', name: 'expiry' }, + { type: 'bool', name: 'allowed' }, + { type: 'uint8', name: 'v' }, + { type: 'bytes32', name: 'r' }, + { type: 'bytes32', name: 's' }, + ], + }, + ], + functionName: 'permit', + args: [ + '0x0000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000', + 0n, + 0n, + true, + 0, + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', + ], + }); + + const promises = [ + functionExists(client, contractAddress, standardPermitData), + functionExists(client, contractAddress, daiPermitData), + getVersion(client, contractAddress), + ]; + const [canonicalPermitResponse, daiPermitResponse, versionResponse] = await Promise.allSettled( + promises, + ); + + let variant = PermitVariant.Unsupported; + if (canonicalPermitResponse.status === 'fulfilled') { + variant = PermitVariant.EIP2612Canonical; + } else if (daiPermitResponse.status === 'fulfilled') { + variant = PermitVariant.DAI; + } + + return { + variant, + version: versionResponse.status === 'fulfilled' ? Number(versionResponse.value) : 1, + }; +}; + +async function getVersion(client: PublicClient, token: `0x${string}`): Promise { + try { + const result = await client.readContract({ + address: token, + abi: [ + { + type: 'function', + name: 'version', + inputs: [], + stateMutability: 'view', + outputs: [{ name: '', type: 'string' }], + }, + ] as const, + functionName: 'version', + }); + return result; + } catch { + return '1'; + } +} + +async function functionExists(client: PublicClient, token: `0x${string}`, data: `0x${string}`) { + return client.call({ to: token, data }); +} + export const createPermitApprovalTx = async ({ contractAddress, owner, @@ -550,6 +557,7 @@ export const createPermitApprovalTx = async ({ variant: PermitVariant; version: number; }) => { + const deadline = createDeadlineFromNow(3n); const { signature } = await createPermitSignature( contractAddress, ownerWallet, @@ -557,11 +565,12 @@ export const createPermitApprovalTx = async ({ owner, variant, version, + deadline, ); const { r, s, v } = parseSignature(signature); if (!v) { - throw new Error('invalid signature: v is not present'); + throw Errors.internal('invalid signature: v is not present'); } return { @@ -574,7 +583,7 @@ export const createPermitApprovalTx = async ({ }) : encodeFunctionData({ abi: ERC20PermitABI, - args: [owner, spender, maxUint256, maxUint256, Number(v), r, s], + args: [owner, spender, maxUint256, deadline, Number(v), r, s], functionName: 'permit', }), to: contractAddress, @@ -613,21 +622,18 @@ export const getAnkrBalances = async ( totalBalanceUsd: string; totalCount: number; }; - }>( - 'https://rpc.ankr.com/multichain/269e541dd5773dac3204831e29b9538284dd3e9591d2b7cb2ac47d85eae213b9/', - { - id: Decimal.random(2).mul(100).toNumber(), - jsonrpc: '2.0', - method: 'ankr_getAccountBalance', - params: { - blockchain: chainList.getAnkrNameList(), - onlyWhitelisted: true, - pageSize: 500, - walletAddress: walletAddress, - }, + }>('https://rpcs.avail.so/multichain', { + id: Decimal.random(2).mul(100).toNumber(), + jsonrpc: '2.0', + method: 'ankr_getAccountBalance', + params: { + blockchain: chainList.getAnkrNameList(), + onlyWhitelisted: true, + pageSize: 500, + walletAddress: walletAddress, }, - ); - if (!res.data?.result) throw new Error('balances cannot be retrieved'); + }); + if (!res.data?.result) throw Errors.internal('balances cannot be retrieved'); const filteredAssets = res.data.result.assets.filter( (asset) => @@ -682,8 +688,7 @@ export const getAnkrBalances = async ( balance, balanceUSD: asset.balanceUsd, chainID: AnkrChainIdMapping.get(asset.blockchain)!, - tokenAddress: - asset.tokenType === 'ERC20' ? asset.contractAddress : (ZERO_ADDRESS as `0x${string}`), + tokenAddress: asset.tokenType === 'ERC20' ? asset.contractAddress : ZERO_ADDRESS, tokenData: { decimals: asset.tokenDecimals, icon: asset.thumbnail, @@ -708,29 +713,31 @@ export function getTokenSymbol(symbol: string) { export const toFlatBalance = ( assets: UserAssetDatum[], + convertAddressToBytes32 = true, currentChainID?: number, selectedTokenAddress?: `0x${string}`, -) => { +): FlatBalance[] => { logger.debug('toFlatBalance', { assets, }); return assets - .map((a) => + .flatMap((a) => a.breakdown.map((b) => { + const tokenAddress = equalFold(b.contractAddress, ZERO_ADDRESS) + ? EADDRESS + : b.contractAddress; return { amount: b.balance, chainID: b.chain.id, decimals: b.decimals, symbol: a.symbol, - tokenAddress: convertTo32BytesHex( - b.contractAddress === ZERO_ADDRESS ? EADDRESS : b.contractAddress, - ), + tokenAddress: convertAddressToBytes32 ? convertTo32BytesHex(tokenAddress) : tokenAddress, universe: b.universe, value: b.balanceInFiat, + logo: a.icon ?? '', }; }), ) - .flat() .filter((b) => { return !(b.chainID === currentChainID && equalFold(b.tokenAddress, selectedTokenAddress)); }) @@ -743,19 +750,19 @@ export const toFlatBalance = ( }; export const balancesToAssets = ( + isCA: boolean, ankrBalances: AnkrBalances, - evmBalances: UnifiedBalanceResponseData[], - fuelBalances: UnifiedBalanceResponseData[], chainList: ChainListType, - isCA: boolean, + evmBalances: UnifiedBalanceResponseData[] = [], + tronBalances: UnifiedBalanceResponseData[] = [], ) => { const assets: UserAssetDatum[] = []; - const vscBalances = evmBalances.concat(fuelBalances); + const vscBalances = evmBalances.concat(tronBalances); logger.debug('balanceToAssets', { ankrBalances, evmBalances, - fuelBalances, + tronBalances, }); for (const balance of vscBalances) { for (const currency of balance.currencies) { @@ -771,7 +778,7 @@ export const balancesToAssets = ( const decimals = token ? token.decimals : chain.nativeCurrency.decimals; if (token) { - const asset = assets.find((s) => s.symbol === token.symbol); + const asset = assets.find((s) => equalFold(s.symbol, token.symbol)); if (asset) { asset.balance = new Decimal(asset.balance).add(currency.balance).toFixed(); asset.balanceInFiat = new Decimal(asset.balanceInFiat) @@ -801,8 +808,8 @@ export const balancesToAssets = ( balanceInFiat: new Decimal(currency.value).toDecimalPlaces(2).toNumber(), chain: { id: bytesToNumber(balance.chain_id), - logo: chain.custom.icon as string, - name: chain.name as string, + logo: chain.custom.icon, + name: chain.name, }, contractAddress: tokenAddress, decimals, @@ -842,7 +849,7 @@ export const balancesToAssets = ( const existingAsset = assets.find((a) => equalFold(a.symbol, asset.tokenData.symbol)); if (existingAsset) { if ( - !existingAsset.breakdown.find( + !existingAsset.breakdown.some( (t) => t.chain.id === chain.id && equalFold(t.contractAddress, asset.tokenAddress), ) ) { @@ -875,8 +882,8 @@ export const balancesToAssets = ( balanceInFiat: new Decimal(asset.balanceUSD).toDecimalPlaces(2).toNumber(), chain: { id: chain.id, - logo: chain.custom.icon as string, - name: chain.name as string, + logo: chain.custom.icon, + name: chain.name, }, contractAddress: asset.tokenAddress, decimals: asset.tokenData.decimals, @@ -885,7 +892,7 @@ export const balancesToAssets = ( ], decimals: asset.tokenData.decimals, icon: asset.tokenData.icon, - symbol: asset.tokenData.symbol as string, + symbol: asset.tokenData.symbol, }); } } @@ -897,37 +904,6 @@ export const balancesToAssets = ( return assets; }; -export const waitForIntentFulfilment = async ( - publicClient: PublicClient, - vaultContractAddr: `0x${string}`, - requestHash: `0x${string}`, -): Promise => { - logger.debug('waitForIntentFulfilment', { requestHash }); - return new Promise((resolve) => { - const unwatch = publicClient.watchContractEvent({ - abi: [FillEvent] as const, - address: vaultContractAddr, - args: { requestHash }, - eventName: 'Fill', - onLogs: (logs) => { - logger.debug('waitForIntentFulfilment', { logs }); - publicClient.transport.getRpcClient().then((c) => c.close()); - // ac?.abort(); - unwatch(); - return resolve(void 0); - }, - poll: false, - }); - // ac?.signal.addEventListener( - // "abort", - // () => { - // unwatch(); - // }, - // { once: true }, - // ); - }); -}; - export const average = (a: bigint, b: bigint) => { return (a & b) + ((a ^ b) >> 1n); }; @@ -948,11 +924,11 @@ export type SetCodeInput = { export class Cache { public allowanceValues: Map = new Map(); public setCodeValues: Map = new Map(); - private allowanceQueries: Set = new Set(); - private nativeAllowanceQueries: Set = new Set(); - private setCodeQueries: Set = new Set(); + private readonly allowanceQueries: Set = new Set(); + private readonly nativeAllowanceQueries: Set = new Set(); + private readonly setCodeQueries: Set = new Set(); - constructor(private publicClientList: PublicClientList) {} + constructor(private readonly publicClientList: PublicClientList) {} addAllowanceQuery(input: AllowanceInput) { this.allowanceQueries.add(input); @@ -1069,14 +1045,14 @@ export class Cache { // To remove duplication of publicClients export class PublicClientList { private list: Record = {}; - constructor(private chainList: ChainListType) {} + constructor(private readonly chainList: ChainListType) {} get(chainID: bigint | number | string) { let client = this.list[Number(chainID)]; if (!client) { const chain = this.chainList.getChainByID(Number(chainID)); if (!chain) { - throw new Error(`Chain not found: ${chainID}`); + throw Errors.chainNotFound(Number(chainID)); } client = createPublicClient({ transport: http(chain.rpcUrls.default.http[0]), @@ -1101,364 +1077,88 @@ export const getAllowanceCacheKey = ({ export const getSetCodeKey = (input: SetCodeInput) => ('a' + input.chainID + input.address).toLowerCase(); -// const APPROVE_GAS_LIMIT = 63_000n; - -// export const swapToGasIfPossible = async ({ -// actualAddress, -// aggregators, -// assetsUsed, -// balances, -// chainList, -// ephemeralAddress, -// oraclePrices, -// }: { -// actualAddress: Bytes; -// aggregators: Aggregator[]; -// assetsUsed: { -// amount: string; -// chainID: number; -// contractAddress: `0x${string}`; -// }[]; -// balances: Balances; -// chainList: ChainList; -// ephemeralAddress: Bytes; -// grpcURL: string; -// oraclePrices: OraclePriceResponse; -// }) => { -// const aci: CreateAllowanceCacheInput = new Set(); -// const blacklist: Hex[] = []; -// const data: { -// [k: number]: { -// amount: bigint; -// contractAddress: Hex; -// txs: Tx[]; -// unsupportedTokens: Hex[]; -// }; -// } = {}; - -// let requote = false; -// const chainToUnsupportedTokens: Record = {}; - -// const assetsGroupedByChain = Map.groupBy( -// assetsUsed, -// (asset) => asset.chainID, -// ); - -// for (const [chainID, swapQuotes] of assetsGroupedByChain) { -// for (const sQuote of swapQuotes) { -// if (!isEIP2612Supported(sQuote.contractAddress, BigInt(chainID))) { -// if (!chainToUnsupportedTokens[Number(chainID)]) { -// chainToUnsupportedTokens[Number(chainID)] = []; -// } -// aci.add({ -// chainID: Number(chainID), -// contractAddress: sQuote.contractAddress, -// owner: convertToEVMAddress(actualAddress), -// spender: convertToEVMAddress(ephemeralAddress), -// }); -// chainToUnsupportedTokens[Number(chainID)].push(sQuote.contractAddress); -// } -// } -// } -// logger.debug("checkAndSupplyGasForApproval:1", { -// assetsGroupedByChain, -// chainToUnsupportedTokens, -// }); - -// const allowanceCache = await createAllowanceCache(aci, chainList); - -// if (Object.keys(chainToUnsupportedTokens).length === 0) { -// return { blacklist, data, requote: false }; -// } - -// for (const chainID in chainToUnsupportedTokens) { -// const tokens: Hex[] = []; -// for (const token of chainToUnsupportedTokens[chainID]) { -// const allowance = allowanceCache.gget({ -// chainID: Number(chainID), -// owner: convertToEVMAddress(actualAddress), -// spender: convertToEVMAddress(ephemeralAddress), -// tokenAddress: token, -// }); -// if (!allowance || allowance < 100000000n) { -// tokens.push(token); -// } -// } -// if (tokens.length) { -// chainToUnsupportedTokens[chainID] = tokens; -// } else { -// delete chainToUnsupportedTokens[chainID]; -// } - -// const quotes = assetsGroupedByChain.get(Number(chainID)); -// const balancesOnChain = balances.filter( -// (b) => -// b.chain_id === Number(chainID) && -// isEIP2612Supported(b.token_address, BigInt(chainID)), -// ); - -// const chain = chainList.getChainByID(Number(chainID)); -// if (!chain) { -// throw new Error(`chain not found: ${chainID}`); -// } - -// const publicClient = createPublicClient({ -// transport: http(chain.rpcUrls.default.http[0]), -// }); - -// const gasPrice = await publicClient.estimateFeesPerGas(); - -// const gas = -// APPROVE_GAS_LIMIT * -// gasPrice.maxFeePerGas * -// BigInt(chainToUnsupportedTokens[chainID].length) * -// 3n; - -// const nativeBalance = balances.find( -// (b) => -// b.chain_id === Number(chainID) && equalFold(b.token_address, EADDRESS), -// ); - -// logger.debug("checkAndSupplyGasForApproval:2", { -// gas, -// gasPrice, -// nativeBalance, -// }); - -// if (new Decimal(nativeBalance?.amount ?? 0).gte(gas)) { -// data[Number(chainID)] = { -// // Since txs.length == 0, amount and contractAddress should not get used, only unsupported token -// amount: 0n, -// contractAddress: "0x", -// txs: [], -// unsupportedTokens: chainToUnsupportedTokens[chainID], -// }; -// continue; -// } - -// let done = false; - -// // Split between sources included and excluded in source swaps -// const split = splitBalanceByQuotes(balancesOnChain, quotes!); -// logger.debug("checkAndSupplyGasForApproval:3", { -// chainID, -// split, -// }); -// for (const s of split.excluded) { -// const gasInToken = convertGasToToken( -// { -// contractAddress: s.token_address, -// decimals: s.decimals, -// priceUSD: s.priceUSD, -// }, -// oraclePrices, -// chain.id, -// divDecimals(gas, chain.nativeCurrency.decimals), -// ); - -// logger.debug("checkAndSupplyGasForApproval:3:excluded", { -// amount: s.amount, -// gasInToken: gasInToken.toFixed(), -// token: s, -// }); - -// if (gasInToken.lt(s.amount)) { -// const res = await swapToGasQuote( -// ephemeralAddress, -// actualAddress, -// new OmniversalChainID(Universe.ETHEREUM, chainID), -// { -// tokenAddress: EADDRESS_32_BYTES, -// }, -// aggregators, -// { -// amount: mulDecimals(gasInToken, s.decimals), -// decimals: s.decimals, -// tokenAddress: convertTo32Bytes(s.token_address), -// }, -// ); -// if (res.quote) { -// const txs = getTxsFromQuote( -// res.aggregator, -// res.quote, -// convertTo32Bytes(s.token_address), -// ); -// data[Number(chainID)] = { -// amount: mulDecimals(gasInToken, s.decimals), -// contractAddress: s.token_address, -// txs: [txs.approval!, txs.swap], -// unsupportedTokens: chainToUnsupportedTokens[chainID], -// }; -// done = true; -// break; -// } -// } -// } - -// if (!done) { -// for (const s of split.included) { -// const gasInToken = convertGasToToken( -// { -// contractAddress: s.token_address, -// decimals: s.decimals, -// priceUSD: s.priceUSD, -// }, -// oraclePrices, -// chain.id, -// divDecimals(gas, chain.nativeCurrency.decimals), -// ); - -// logger.debug("checkAndSupplyGasForApproval:3:included", { -// amount: s.amount, -// gasInToken: gasInToken.toFixed(), -// }); - -// if (gasInToken.gte(s.amount)) { -// const res = await swapToGasQuote( -// ephemeralAddress, -// actualAddress, -// new OmniversalChainID(Universe.ETHEREUM, chainID), -// { -// tokenAddress: EADDRESS_32_BYTES, -// }, -// aggregators, -// { -// amount: mulDecimals(gasInToken, s.decimals), -// decimals: s.decimals, -// tokenAddress: convertTo32Bytes(s.token_address), -// }, -// ); -// if (res.quote) { -// const txs = getTxsFromQuote( -// res.aggregator, -// res.quote, -// convertTo32Bytes(s.token_address), -// ); -// data[Number(chainID)] = { -// amount: mulDecimals(gasInToken, s.decimals), -// contractAddress: s.token_address, -// txs: [txs.approval!, txs.swap], -// unsupportedTokens: chainToUnsupportedTokens[chainID], -// }; -// // since we had to use source swap token for gas -// // TODO: Check if we have enough if we swap for gas otherwise throw error -// done = true; -// requote = true; -// break; -// } -// } -// } -// } - -// if (!done) { -// throw new Error(`could not swap token for gas on chain: ${chainID}`); -// } -// } - -// return { -// blacklist, -// data, -// requote, -// }; -// }; - -// const convertGasToToken = ( -// token: { contractAddress: Hex; decimals: number; priceUSD: string }, -// oraclePrices: OraclePriceResponse, -// destinationChainID: number, -// gas: Decimal, -// ) => { -// const gasTokenPerUSD = -// oraclePrices -// .find( -// (rate) => -// rate.chainId === destinationChainID && -// equalFold(rate.tokenAddress, ZERO_ADDRESS), -// ) -// ?.tokensPerUsd.toString() ?? "0"; -// const transferTokenPerUSD = Decimal.div(1, token.priceUSD); - -// logger.debug("convertGasToToken", { -// gas: gas.toFixed(), -// gasTokenPerUSD, -// transferTokenPerUSD, -// }); - -// const gasInUSD = new Decimal(1).div(gasTokenPerUSD).mul(gas); -// const totalRequired = new Decimal(gasInUSD).div(transferTokenPerUSD); - -// return totalRequired.toDP(token.decimals, Decimal.ROUND_CEIL); -// }; - -export const getTxsFromQuote = ( - aggregator: Aggregator, - quote: Quote, - inputToken: Bytes, +export const parseQuote = ( + input: { + agg: Aggregator; + originalHolding: Holding & { decimals: number; symbol: string }; + quote: Quote; + req: { inputToken: Bytes }; + }, createApproval = true, ) => { logger.debug('getTxsFromQuote', { - aggregator, createApproval, - inputToken, - quote, + input, }); - if (aggregator instanceof LiFiAggregator) { - const originalResponse = (quote as LiFiQuote).originalResponse; + if (input.agg instanceof LiFiAggregator) { + const originalResponse = (input.quote as LiFiQuote).originalResponse; const tx = originalResponse.transactionRequest; - logger.debug('getTxsFromQuote', { - 'approval.amount': quote.inputAmount, - 'approval.target': originalResponse.estimate.approvalAddress, - tx: tx, - 'tx.amount': quote.inputAmount, - 'tx.inputToken': inputToken, - 'tx.outputAmount': quote.outputAmountMinimum, - }); const val = { - amount: quote.inputAmount, - approval: null as null | Tx, - inputToken, - outputAmount: quote.outputAmountMinimum, + input: { + amount: input.quote.inputAmount, + token: input.req.inputToken, + decimals: input.originalHolding.decimals, + symbol: input.originalHolding.symbol, + }, + output: { + amount: input.quote.outputAmountMinimum, + }, swap: { - data: tx.data as Hex, - to: tx.to as Hex, - value: BigInt(tx.value), + approval: null as null | Tx, + tx: { + data: tx.data as Hex, + to: tx.to as Hex, + value: BigInt(tx.value), + }, }, }; if (createApproval) { - val.approval = { - data: packERC20Approve(originalResponse.estimate.approvalAddress as Hex, quote.inputAmount), - to: convertToEVMAddress(inputToken), + val.swap.approval = { + data: packERC20Approve( + originalResponse.estimate.approvalAddress as Hex, + input.quote.inputAmount, + ), + to: convertToEVMAddress(input.req.inputToken), value: 0n, }; } return val; - } else if (aggregator instanceof BebopAggregator) { - const originalResponse = (quote as BebopQuote).originalResponse; + } else if (input.agg instanceof BebopAggregator) { + const originalResponse = (input.quote as BebopQuote).originalResponse; const tx = originalResponse.quote.tx; logger.debug('getTxsFromQuote', { - 'approval.amount': quote.inputAmount, + 'approval.amount': input.quote.inputAmount, 'approval.target': originalResponse.quote.approvalTarget, tx: tx, - 'tx.amount': quote.inputAmount, - 'tx.inputToken': inputToken, - 'tx.outputAmount': quote.outputAmountMinimum, + 'tx.amount': input.quote.inputAmount, + 'tx.inputToken': input.req.inputToken, + 'tx.outputAmount': input.quote.outputAmountMinimum, }); const val = { - amount: quote.inputAmount, - approval: null as null | Tx, - inputToken, - outputAmount: quote.outputAmountMinimum, + input: { + amount: input.quote.inputAmount, + token: input.req.inputToken, + decimals: input.originalHolding.decimals, + symbol: input.originalHolding.symbol, + }, + output: { amount: input.quote.outputAmountMinimum }, swap: { - data: tx.data, - to: tx.to, - value: BigInt(tx.value), + approval: null as null | Tx, + tx: { + data: tx.data, + to: tx.to, + value: BigInt(tx.value), + }, }, }; if (createApproval) { - val.approval = { - data: packERC20Approve(originalResponse.quote.approvalTarget as Hex, quote.inputAmount), - to: convertToEVMAddress(inputToken), + val.swap.approval = { + data: packERC20Approve( + originalResponse.quote.approvalTarget as Hex, + input.quote.inputAmount, + ), + to: convertToEVMAddress(input.req.inputToken), value: 0n, }; } @@ -1466,78 +1166,9 @@ export const getTxsFromQuote = ( return val; } - throw new Error('Unknown aggregator'); + throw Errors.internal('Unknown aggregator'); }; -// const splitBalanceByQuotes = ( -// balances: Balances, -// quotes: { -// amount: string; -// chainID: number; -// contractAddress: `0x${string}`; -// }[], -// ) => { -// const [included, excluded] = partition(balances, (b) => { -// return !!quotes.find((q) => equalFold(q.contractAddress, b.token_address)); -// }); - -// return { -// excluded, -// included, -// }; -// }; - -// export async function swapToGasQuote( -// userAddress: Bytes, -// receiverAddress: Bytes | null, -// chainID: OmniversalChainID, -// requirement: { -// tokenAddress: Bytes; -// }, -// aggregators: Aggregator[], -// cur: { -// amount: bigint; -// decimals: number; -// tokenAddress: Bytes; -// }, -// ): Promise<{ -// aggregator: Aggregator; -// inputAmount: Decimal; -// quote: null | Quote; -// }> { -// // We spray and pray -// const buyQuoteResult = await aggregateAggregators( -// [ -// { -// chain: chainID, -// inputAmount: cur.amount, -// inputToken: cur.tokenAddress, -// outputToken: requirement.tokenAddress, -// receiverAddress, -// type: QuoteType.ExactIn, -// userAddress, -// }, -// ], -// aggregators, -// 0, -// ); -// if (buyQuoteResult.length !== 1) { -// throw new AutoSelectionError("???"); -// } - -// const buyQuote = buyQuoteResult[0]; -// if (buyQuote.quote == null) { -// throw new AutoSelectionError("Couldn't get buy quote"); -// } - -// return { -// ...buyQuote, -// inputAmount: convertBigIntToDecimal(buyQuote.quote.inputAmount).div( -// Decimal.pow(10, cur.decimals), -// ), -// }; -// } - /** * Creates Tx object depending on contractAddress being native or ERC20 */ @@ -1592,7 +1223,7 @@ export const createSwapIntent = ( ): SwapIntent => { const chain = chainList.getChainByID(destination.chainID); if (!chain) { - throw new Error(`chain not found: ${destination.chainID}`); + throw Errors.chainNotFound(destination.chainID); } const intent: SwapIntent = { @@ -1615,7 +1246,7 @@ export const createSwapIntent = ( for (const source of sources) { const chain = chainList.getChainByID(source.chainID); if (!chain) { - throw new Error(`chain not found: ${source.chainID}`); + throw Errors.chainNotFound(source.chainID); } intent.sources.push({ @@ -1813,7 +1444,7 @@ export const postSwap = async ({ }); const rffIDN = Number(metadata.rff_id); - // @ts-ignore + // @ts-expect-error delete metadata.rff_id; const res = await metadataAxios<{ value: number }>({ @@ -1851,7 +1482,7 @@ export const createSweeperTxs = ({ )!.Currencies.find((c) => c.currencyID === COTCurrencyID); if (!currency) { - throw new Error(`cot not found on chain ${chainID}`); + throw Errors.internal(`cot not found on chain ${chainID}`); } tokenAddress = convertToEVMAddress(currency.tokenAddress); @@ -1939,7 +1570,7 @@ export const performDestinationSwap = async ({ chainList: ChainListType; COT: CurrencyID; emitter: { - emit: (step: SwapStep) => void; + emit: (step: SwapStepType) => void; }; ephemeralAddress: Hex; ephemeralWallet: PrivateKeyAccount; @@ -1972,7 +1603,7 @@ export const performDestinationSwap = async ({ performance.mark('destination-swap-end'); if (hasDestinationSwap) { - emitter.emit(DESTINATION_SWAP_HASH(ops[0], chainList)); + emitter.emit(SWAP_STEPS.DESTINATION_SWAP_HASH(ops[0], chainList)); } performance.mark('destination-swap-mining-start'); @@ -1982,7 +1613,7 @@ export const performDestinationSwap = async ({ }, 2); return hash; } catch (e) { - logger.error('destination swap failed twice, sweeping to eoa', e); + logger.error('destination swap failed twice, sweeping to eoa', e, { cause: 'SWAP_FAILED' }); await vscSBCTx( [ await createSBCTxFromCalls({ @@ -2002,50 +1633,18 @@ export const performDestinationSwap = async ({ ], vscDomain, ).catch((e) => { - logger.error('error during destination sweep', e); + logger.error('error during destination sweep', e, { cause: 'DESTINATION_SWEEP_ERROR' }); }); throw e; } }; export const getSwapSupportedChains = (chainList: ChainListType) => { - const chains: { - id: number; - logo: string; - name: string; - tokens: TokenInfo[]; - }[] = []; - for (const c of chainData.keys()) { - const chain = chainList.getChainByID(c); - if (!chain) { - continue; - } - - const data = { + return chainList.chains + .filter((chain) => chain.ankrName !== '') + .map((chain) => ({ id: chain.id, - logo: chain.custom.icon, name: chain.name, - tokens: [] as TokenInfo[], - }; - - const tokens = chainData.get(c); - if (!tokens) { - continue; - } - - tokens.forEach((t) => { - if (t.PermitVariant !== PermitVariant.Unsupported) { - data.tokens.push({ - contractAddress: convertToEVMAddress(t.TokenContractAddress), - decimals: t.TokenDecimals, - logo: '', - name: t.Name, - symbol: t.Name, - }); - } - }); - - chains.push(data); - } - return chains; + logo: chain.custom.icon, + })); }; diff --git a/src/sdk/ca-base/telemetry.ts b/src/sdk/ca-base/telemetry.ts new file mode 100644 index 00000000..7ee7b9fb --- /dev/null +++ b/src/sdk/ca-base/telemetry.ts @@ -0,0 +1,53 @@ +import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; +import { Logger, logs } from '@opentelemetry/api-logs'; +import { resourceFromAttributes } from '@opentelemetry/resources'; +import { Environment } from '@avail-project/ca-common'; +import { toHex } from 'viem/utils'; +import { NetworkConfig } from '../../commons'; + + +let telemetryLogger: Logger | null = null; + +function getOrGenerateClientId(): string { + const KEY = 'nexus-client-id'; + let clientId = window.localStorage.getItem(KEY); + + if (!clientId) { + const bytes = new Uint8Array(32); + clientId = toHex(window.crypto.getRandomValues(bytes)); + window.localStorage.setItem(KEY, clientId); + } + return clientId; +} + +function getNetworkName(networkConfig: NetworkConfig): string { + return Environment[networkConfig.NETWORK_HINT]; +} + +const setLoggerProvider = (networkConfig: NetworkConfig) => { + if (!telemetryLogger) { + const loggerProvider = new LoggerProvider({ + resource: resourceFromAttributes({ + 'service.name': 'nexus-sdk-internal-logs', + 'client.id': getOrGenerateClientId(), + 'origin': window.origin, + 'host': window.location.host, + 'hostname': window.location.hostname, + 'network': getNetworkName(networkConfig), + }), + processors: [ + new BatchLogRecordProcessor( + new OTLPLogExporter({ + url: 'https://otel.avail.so/v1/logs', + headers: { 'x-otlp-force-fetch': '1' }, + }), + ), + ], + }); + logs.setGlobalLoggerProvider(loggerProvider); + telemetryLogger = logs.getLogger('nexus-telemetry-logger'); + } +}; + +export { setLoggerProvider, telemetryLogger }; diff --git a/packages/core/sdk/ca-base/utils/api.utils.ts b/src/sdk/ca-base/utils/api.utils.ts similarity index 56% rename from packages/core/sdk/ca-base/utils/api.utils.ts rename to src/sdk/ca-base/utils/api.utils.ts index 68e392ee..3249a67d 100644 --- a/packages/core/sdk/ca-base/utils/api.utils.ts +++ b/src/sdk/ca-base/utils/api.utils.ts @@ -1,27 +1,37 @@ -import { Bytes, GrpcWebImpl, QueryClientImpl, RequestForFunds, Universe } from '@arcana/ca-common'; +import { + Bytes, + GrpcWebImpl, + QueryClientImpl, + RequestForFunds, + Universe, +} from '@avail-project/ca-common'; import axios, { AxiosInstance } from 'axios'; import Decimal from 'decimal.js'; import { connect } from 'it-ws/client'; import Long from 'long'; import { pack, unpack } from 'msgpackr'; -import { bytesToBigInt, bytesToNumber, toHex } from 'viem'; -import { getLogger } from '../logger'; -import { ALLOWANCE_APPROVAL_MINED, INTENT_COLLECTION, INTENT_COLLECTION_COMPLETE } from '../steps'; +import { bytesToBigInt, bytesToNumber, Hex, toHex } from 'viem'; import { + BRIDGE_STEPS, + BridgeStepType, + getLogger, FeeStoreData, OraclePriceResponse, RFF, SponsoredApprovalDataArray, - StepInfo, UnifiedBalanceResponseData, -} from '@nexus/commons'; + ChainListType, +} from '../../../commons'; import { convertAddressByUniverse, convertToHexAddressByUniverse, divDecimals, equalFold, + getExplorerURL, minutesToMs, } from './common.utils'; +import { Errors } from '../errors'; +import { remove, retry } from 'es-toolkit'; const logger = getLogger(); @@ -47,44 +57,93 @@ async function fetchMyIntents(address: string, grpcURL: string, page = 1) { reverse: true, }, }); - return intentTransform(response.requestForFunds); + return response.requestForFunds; } catch (error) { logger.error('Failed to fetch intents', error); - throw new Error('Failed to fetch intents'); + throw Errors.cosmosError('Failed to fetch intents'); } } -const intentTransform = (input: RequestForFunds[]): RFF[] => { - return input.map((rff) => ({ - deposited: rff.deposited, - destinationChainID: bytesToNumber(rff.destinationChainID), - destinations: rff.destinations.map((d) => ({ - tokenAddress: convertToHexAddressByUniverse(d.tokenAddress, rff.destinationUniverse), - value: bytesToBigInt(d.value), - })), - destinationUniverse: Universe[rff.destinationUniverse], - expiry: rff.expiry.toNumber(), - fulfilled: rff.fulfilled, - id: rff.id.toNumber(), - refunded: rff.refunded, - sources: rff.sources.map((s) => ({ - chainID: bytesToNumber(s.chainID), - tokenAddress: convertToHexAddressByUniverse(s.tokenAddress, s.universe), - universe: Universe[s.universe], - value: bytesToBigInt(s.value), - })), - })); +export const intentTransform = ( + input: RequestForFunds[], + explorerBaseURL: string, + chainList: ChainListType, +): RFF[] => { + return input.map((rff) => { + const dstChainId = bytesToNumber(rff.destinationChainID); + const dstChain = chainList.getChainByID(dstChainId); + if (!dstChain) { + throw Errors.chainNotFound(dstChainId); + } + return { + explorerUrl: getExplorerURL(explorerBaseURL, rff.id), + deposited: rff.deposited, + destinationChain: { + id: dstChain.id, + name: dstChain.name, + logo: dstChain.custom.icon, + universe: Universe[rff.destinationUniverse], + }, + destinations: rff.destinations.map((d) => { + const contractAddress = convertToHexAddressByUniverse( + d.contractAddress, + rff.destinationUniverse, + ); + const token = chainList.getTokenByAddress(dstChainId, contractAddress); + if (!token) { + throw Errors.tokenNotSupported(contractAddress, dstChainId); + } + const valueRaw = bytesToBigInt(d.value); + return { + token: { + address: contractAddress, + symbol: token.symbol, + decimals: token.decimals, + }, + valueRaw, + value: divDecimals(valueRaw, token.decimals).toFixed(token.decimals), + }; + }), + fulfilledAt: rff.fulfilledAt.toNumber(), + expiry: rff.expiry.toNumber(), + fulfilled: rff.fulfilled, + id: rff.id.toNumber(), + refunded: rff.refunded, + sources: rff.sources.map((s) => { + const chainId = bytesToNumber(s.chainID); + const contractAddress = convertToHexAddressByUniverse(s.contractAddress, s.universe); + const result = chainList.getChainAndTokenByAddress(chainId, contractAddress); + if (!result?.token) { + throw Errors.tokenNotSupported(contractAddress, chainId); + } + const valueRaw = bytesToBigInt(s.value); + return { + chain: { + id: result.chain.id, + name: result.chain.name, + logo: result.chain.custom.icon, + universe: Universe[s.universe], + }, + value: divDecimals(valueRaw, result.token.decimals).toFixed(result.token.decimals), + valueRaw, + token: { + address: contractAddress, + symbol: result.token.symbol, + decimals: result.token.decimals, + }, + }; + }), + }; + }); }; async function fetchProtocolFees(grpcURL: string) { try { - const response = await getCosmosQueryClient(grpcURL).ProtocolFees({ - Universe: Universe.FUEL, - }); + const response = await getCosmosQueryClient(grpcURL).ProtocolFees({}); return response; } catch (error) { logger.error('Failed to fetch protocol fees', error); - throw new Error('Failed to fetch protocol fees'); + throw Errors.cosmosError('Failed to fetch protocol fees'); } } @@ -94,7 +153,7 @@ async function fetchSolverData(grpcURL: string) { return response; } catch (error) { logger.error('Failed to fetch solver data', error); - throw new Error('Failed to fetch solver data'); + throw Errors.cosmosError('Failed to fetch solver data'); } } @@ -111,7 +170,7 @@ const fetchPriceOracle = async (grpcURL: string) => { })); return oracleRates; } - throw new Error('InternalError: No price data found.'); + throw Errors.internal('No price data found.'); }; const coinbasePrices = { @@ -130,10 +189,10 @@ const getCoinbasePrices = async () => { coinbasePrices.rates = exchange.data.data.rates; coinbasePrices.lastUpdatedAt = Date.now(); } catch (error) { - logger.error('Failed to fetch Coinbase prices', error); + logger.error('Failed to fetch Coinbase prices', error, { cause: 'INTERNAL_ERROR' }); // Return cached rates if available, otherwise throw if (Object.keys(coinbasePrices.rates).length === 0) { - throw new Error('Failed to fetch exchange rates and no cache available'); + throw Errors.internal('Failed to fetch exchange rates and no cache available'); } } } @@ -290,43 +349,42 @@ const getVSCURL = (vscDomain: string, protocol: 'https' | 'wss') => { let vscReq: AxiosInstance | null = null; const getVscReq = (vscDomain: string) => { - if (!vscReq) { - vscReq = axios.create({ - baseURL: new URL('/api/v1', getVSCURL(vscDomain, 'https')).toString(), - headers: { - Accept: 'application/msgpack', + vscReq ??= axios.create({ + baseURL: new URL('/api/v1', getVSCURL(vscDomain, 'https')).toString(), + headers: { + Accept: 'application/msgpack', + }, + responseType: 'arraybuffer', + transformRequest: [ + function (data, headers) { + if (['get', 'head'].includes((this.method as string).toLowerCase())) return; + headers['Content-Type'] = 'application/msgpack'; + return pack(data); }, - responseType: 'arraybuffer', - transformRequest: [ - function (data, headers) { - if (['get', 'head'].includes((this.method as string).toLowerCase())) return; - headers['Content-Type'] = 'application/msgpack'; - return pack(data); - }, - ], - transformResponse: [(data) => unpack(data)], - }); - } + ], + transformResponse: [(data) => unpack(data)], + }); return vscReq; }; export const getBalancesFromVSC = async ( vscDomain: string, address: `0x${string}`, - namespace: 'ETHEREUM' | 'FUEL' = 'ETHEREUM', + namespace: 'ETHEREUM' | 'TRON' = 'ETHEREUM', ) => { const response = await getVscReq(vscDomain).get<{ balances: UnifiedBalanceResponseData[]; }>(`/get-balance/${namespace}/${address}`); - return response.data.balances; + logger.debug('getBalancesFromVSC', { response }); + return response.data.balances.filter((b) => b.errored !== true); }; export const getEVMBalancesForAddress = async (vscDomain: string, address: `0x${string}`) => { return getBalancesFromVSC(vscDomain, address); }; -export const getFuelBalancesForAddress = async (vscDomain: string, address: `0x${string}`) => { - return getBalancesFromVSC(vscDomain, address, 'FUEL'); +export const getTronBalancesForAddress = async (vscDomain: string, address: `0x${string}`) => { + return getBalancesFromVSC(vscDomain, address, 'TRON'); }; const vscCreateFeeGrant = async (vscDomain: string, address: string) => { @@ -348,6 +406,7 @@ type CreateSponsoredApprovalResponse = | { error: string; errored: true; + msg: string; part_idx: number; } | { error: true; msg: string } // why error not same struct? @@ -360,7 +419,6 @@ type CreateSponsoredApprovalResponse = const vscCreateSponsoredApprovals = async ( vscDomain: string, input: SponsoredApprovalDataArray, - msd?: (s: StepInfo, data?: { [k: string]: unknown }) => void, ) => { const connection = connect( new URL('/api/v1/create-sponsored-approvals', getVSCURL(vscDomain, 'wss')).toString(), @@ -368,104 +426,154 @@ const vscCreateSponsoredApprovals = async ( await connection.connected(); + const approvalHashes: { chainId: number; hash: Hex }[] = []; + try { connection.socket.send(pack(input)); - let count = 0; for await (const resp of connection.source) { const data: CreateSponsoredApprovalResponse = unpack(resp); logger.debug('vscCreateSponsoredApprovals', { data }); if ('errored' in data && data.errored) { - throw new Error(data.error); + throw Errors.vscError( + `failed to create sponsored approvals: ${data.msg ?? 'Backend sent failure.'}`, + ); } if ('error' in data && data.error) { - throw new Error(data.msg); + throw Errors.vscError( + `failed to create sponsored approvals: ${data.msg ?? 'Backend sent failure.'}`, + ); } - if (msd) { - msd(ALLOWANCE_APPROVAL_MINED(bytesToNumber(input[data.part_idx].chain_id))); - } + const inputData = input[data.part_idx]; + + approvalHashes.push({ + chainId: bytesToNumber(inputData.chain_id), + hash: toHex(data.tx_hash), + }); - count += 1; - if (count == input.length) { + if (approvalHashes.length == input.length) { break; } } - return 'ok'; + + return approvalHashes; } finally { connection.close(); } }; type VSCCreateRFFResponse = + // Global | { - error: string; + error: true; errored: true; - idx: number; - status: 26; + code: 0x13; // Fee changed + } + | { + error: true; + errored: true; + code: 0x12; // Already deposited everything } + | { status: 0xff; idx: 0; errored: false } // transmission complete, if no global error + // Local | { errored: false; idx: number; - status: 16; + status: 0x10; // Success } - | { status: 255 }; + | { + errored: true; + idx: number; + status: 0x1a; // could not collect + }; const vscCreateRFF = async ( vscDomain: string, id: Long, - msd: (s: StepInfo, data?: { [k: string]: unknown }) => void, - expectedCollectionIndexes: number[], + msd: (s: BridgeStepType) => void, + expectedCollections: number[], ) => { - const receivedCollectionsACKs = []; - const connection = connect(new URL('/api/v1/create-rff', getVSCURL(vscDomain, 'wss')).toString()); - await connection.connected(); - - logger.debug('vscCreateRFF', { - expectedCollectionIndexes, - }); - - try { - connection.socket.send(pack({ id: id.toNumber() })); - - for await (const resp of connection.source) { - const data: VSCCreateRFFResponse = unpack(resp); - - logger.debug('vscCreateRFF:response', { data }); - - if (data.status === 255) { - if (expectedCollectionIndexes.length === receivedCollectionsACKs.length) { - msd(INTENT_COLLECTION_COMPLETE); - break; - } else { - logger.debug('(vsc)create-rff:collections failed', { - expectedCollectionIndexes, - receivedCollectionsACKs, - }); - throw new Error('(vsc)create-rff: collections failed'); - } - } else if (data.status === 16) { - if (expectedCollectionIndexes.includes(data.idx)) { - receivedCollectionsACKs.push(data.idx); - } - msd(INTENT_COLLECTION(receivedCollectionsACKs.length), { - confirmed: receivedCollectionsACKs.length, - total: expectedCollectionIndexes.length, - }); - } else { - if (expectedCollectionIndexes.includes(data.idx)) { - throw new Error(`(vsc)create-rff: ${data.error}`); - } else { - logger.debug('vscCreateRFF:ExpectedError:ignore', { data }); + const controller = new AbortController(); + const pendingCollections = expectedCollections.slice(); + const completedCollections: number[] = []; + await retry( + async () => { + const connection = connect( + new URL('/api/v1/create-rff', getVSCURL(vscDomain, 'wss')).toString(), + ); + try { + await connection.connected(); + connection.socket.send(pack({ id: id.toNumber() })); + responseLoop: for await (const resp of connection.source) { + const data: VSCCreateRFFResponse = unpack(resp); + + logger.debug('vscCreateRFF:response', { data }); + if ('idx' in data) { + // local msg + switch (data.status) { + // Will be called at the end of all calls, regardless of status + case 0xff: { + if (pendingCollections.length === 0) { + msd(BRIDGE_STEPS.INTENT_COLLECTION_COMPLETE); + break responseLoop; + } else { + logger.debug('(vsc)create-rff:collections failed', { + expectedCollections, + completedCollections, + }); + throw Errors.vscError( + `create-rff: collections failed. expected = ${expectedCollections}, got = ${completedCollections}`, + ); + } + } + // Collection successful for a chain + case 0x10: { + if (pendingCollections.includes(data.idx)) { + completedCollections.push(data.idx); + remove(pendingCollections, (d) => d === data.idx); + } + msd( + BRIDGE_STEPS.INTENT_COLLECTION( + completedCollections.length, + expectedCollections.length, + ), + ); + break; + } + + // Collection failed or is not applicable(say for native) + default: { + if (pendingCollections.includes(data.idx)) { + logger.debug(`vsc:create-rff:failed`, { data }); + } else { + logger.debug('vsc:create-rff:expectedError:ignore', { data }); + } + } + } + } else { + if (data.code === 0x13) { + controller.abort(Errors.rFFFeeExpired()); + throw Errors.rFFFeeExpired(); + } else if (data.code === 0x12) { + break; + } else { + throw Errors.vscError('create-rff: unhandled error', data); + } + } } + } finally { + connection.close(); } - } - } finally { - connection.close(); - } + }, + { + retries: 3, + signal: controller.signal, + }, + ); }; const checkIntentFilled = async (intentID: Long, grpcURL: string) => { @@ -473,10 +581,11 @@ const checkIntentFilled = async (intentID: Long, grpcURL: string) => { id: intentID, }); if (response.requestForFunds?.fulfilled) { + logger.debug('intent already filled', { response }); return 'ok'; } - throw new Error('not filled yet'); + throw Errors.internal('not filled yet'); }; export { diff --git a/src/sdk/ca-base/utils/balance.utils.ts b/src/sdk/ca-base/utils/balance.utils.ts new file mode 100644 index 00000000..14c77b02 --- /dev/null +++ b/src/sdk/ca-base/utils/balance.utils.ts @@ -0,0 +1,152 @@ +import { Environment } from '@avail-project/ca-common'; +import { ChainListType, logger, SUPPORTED_CHAINS } from '../../../commons'; +import { equalFold, getEVMBalancesForAddress, getTronBalancesForAddress } from '.'; +import { encodePacked, Hex, keccak256, pad, toHex } from 'viem'; +import { balancesToAssets, getAnkrBalances, toFlatBalance } from '../swap/utils'; +import { filterSupportedTokens } from '../swap/data'; + +export const getBalancesForSwap = async (input: { evmAddress: Hex; chainList: ChainListType }) => { + const assets = balancesToAssets( + false, + await getAnkrBalances(input.evmAddress, input.chainList, true), + input.chainList, + ); + let balances = toFlatBalance(assets, false); + return { assets, balances }; +}; + +export const getBalances = async (input: { + evmAddress: Hex; + chainList: ChainListType; + removeTransferFee?: boolean; + filter?: boolean; + tronAddress?: string; + isCA?: boolean; + vscDomain: string; + networkHint: Environment; +}) => { + const isCA = input.isCA ?? false; + const removeTransferFee = input.removeTransferFee ?? false; + const filter = input.filter ?? true; + + const [ankrBalances, evmBalances, tronBalances] = await Promise.all([ + input.networkHint === Environment.FOLLY || isCA + ? Promise.resolve([]) + : getAnkrBalances(input.evmAddress, input.chainList, removeTransferFee), + getEVMBalancesForAddress(input.vscDomain, input.evmAddress), + input.tronAddress + ? getTronBalancesForAddress(input.vscDomain, input.tronAddress as Hex) + : Promise.resolve([]), + ]); + + const assets = balancesToAssets(isCA, ankrBalances, input.chainList, evmBalances, tronBalances); + + let balances = toFlatBalance(assets); + if (filter) { + balances = filterSupportedTokens(balances); + } + + logger.debug('getBalances', { + assets, + balances, + removeTransferFee, + }); + + return { assets, balances }; +}; + +const getBalanceSlot = ({ + tokenSymbol, + chainId, + userAddress, +}: { + tokenSymbol: string; + chainId: number; + userAddress: Hex; +}) => { + const balanceSlot = getBalanceStorageSlot(tokenSymbol, chainId); + + // Calculate storage slot for user's balance: keccak256(user_address . balances_slot) + const userBalanceSlot = keccak256( + encodePacked(['bytes32', 'uint256'], [pad(userAddress, { size: 32 }), BigInt(balanceSlot)]), + ); + + logger.debug('getBalanceSlot', { + tokenSymbol, + chainId, + userAddress, + balanceSlot: userBalanceSlot, + }); + + return userBalanceSlot; +}; + +export const generateStateOverride = (params: { + tokenSymbol: string; + tokenAddress: Hex; + chainId: number; + userAddress: Hex; + amount: bigint; +}) => { + const amountInHex = toHex(params.amount * 2n); + // FIXME: it should estimate for any other native token also + if (equalFold(params.tokenSymbol, 'ETH')) { + return { + [params.userAddress]: { + balance: amountInHex, + }, + }; + } + const balanceSlot = getBalanceSlot(params); + + return { + [params.tokenAddress]: { + storage: { + [balanceSlot]: pad(amountInHex, { size: 32 }), + }, + }, + [params.userAddress]: { + balance: toHex(100000n), + }, + }; +}; + +const DEFAULT_SLOT = { + ETH: 0, + USDC: 9, + USDT: 2, +} as const; + +function getBalanceStorageSlot(token: string, chainId: number): number { + const storageSlotMapping: Record> = { + [SUPPORTED_CHAINS.ETHEREUM]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.BASE]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.ARBITRUM]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.OPTIMISM]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.POLYGON]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.AVALANCHE]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.SCROLL]: DEFAULT_SLOT, + // Testnets + [SUPPORTED_CHAINS.BASE_SEPOLIA]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.ARBITRUM_SEPOLIA]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.OPTIMISM_SEPOLIA]: DEFAULT_SLOT, + [SUPPORTED_CHAINS.POLYGON_AMOY]: DEFAULT_SLOT, + }; + + const chainMapping = storageSlotMapping[chainId]; + if (chainMapping) { + const slot = chainMapping[token]; + if (slot) { + logger.info(`Using storage slot ${slot} for ${token} on chain ${chainId}`); + return slot; + } + } + + logger.warn(`Unsupported chain ${chainId}, falling back to defaults`); + + return token === 'USDC' + ? DEFAULT_SLOT.USDC + : token === 'USDT' + ? DEFAULT_SLOT.USDT + : DEFAULT_SLOT.ETH; +} diff --git a/packages/core/sdk/ca-base/utils/common.utils.ts b/src/sdk/ca-base/utils/common.utils.ts similarity index 55% rename from packages/core/sdk/ca-base/utils/common.utils.ts rename to src/sdk/ca-base/utils/common.utils.ts index 739ab35f..d1bde9c6 100644 --- a/packages/core/sdk/ca-base/utils/common.utils.ts +++ b/src/sdk/ca-base/utils/common.utils.ts @@ -1,54 +1,59 @@ import { - ArcanaVault, + Bytes, DepositVEPacket, Environment, + ERC20ABI, EVMRFF, EVMVaultABI, MsgDoubleCheckTx, Universe, -} from '@arcana/ca-common'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +} from '@avail-project/ca-common'; import Decimal from 'decimal.js'; -import { arrayify, CHAIN_IDS, FuelConnector, hexlify, Provider } from 'fuels'; import Long from 'long'; import { ByteArray, bytesToHex, + bytesToNumber, encodeAbiParameters, + encodeFunctionData, getAbiItem, hashMessage, Hex, + hexToBigInt, keccak256, pad, PrivateKeyAccount, PublicClient, toBytes, toHex, + UserRejectedRequestError, WalletClient, WebSocketTransport, } from 'viem'; - +import { TronWeb, Types, utils } from 'tronweb'; import { ChainList } from '../chains'; -import { FUEL_BASE_ASSET_ID, isNativeAddress, ZERO_ADDRESS } from '../constants'; -import { getLogger } from '../logger'; +import { isNativeAddress, ZERO_ADDRESS } from '../constants'; import { - EthereumProvider, + getLogger, + IBridgeOptions, + SupportedChainsAndTokensResult, Intent, - Network, - NetworkConfig, OraclePriceResponse, ReadableIntent, - SDKConfig, TokenInfo, - TxOptions, ChainListType, - NexusNetwork, UserAssetDatum, Chain, -} from '@nexus/commons'; -import { FeeStore } from './api.utils'; -import { requestTimeout, waitForIntentFulfilment } from './contract.utils'; + CosmosOptions, +} from '../../../commons'; +import { + createPublicClientWithFallback, + requestTimeout, + waitForIntentFulfilment, +} from './contract.utils'; import { cosmosCreateDoubleCheckTx, cosmosFillCheck, cosmosRefundIntent } from './cosmos.utils'; +import { AdapterProps } from '@tronweb3/tronwallet-abstract-adapter'; +import { Errors } from '../errors'; const logger = getLogger(); @@ -59,7 +64,7 @@ function convertAddressByUniverse(input: ByteArray | Hex, universe: Universe) { const inputIsString = typeof input === 'string'; const bytes = inputIsString ? toBytes(input) : input; - if (universe === Universe.ETHEREUM) { + if (universe === Universe.ETHEREUM || universe === Universe.TRON) { if (bytes.length === 20) { return inputIsString ? input : bytes; } @@ -67,22 +72,7 @@ function convertAddressByUniverse(input: ByteArray | Hex, universe: Universe) { return inputIsString ? toHex(bytes.subarray(12)) : bytes.subarray(12); } - throw new Error('invalid length of input'); - } - - if (universe === Universe.FUEL) { - if (bytes.length === 32) { - return inputIsString ? input : bytes; - } - if (bytes.length === 20) { - const padded = pad(bytes, { - dir: 'left', - size: 32, - }); - return inputIsString ? toHex(padded) : padded; - } - - throw new Error('invalid length of input'); + throw Errors.invalidAddressLength('evm|tron'); } return toHex(input); @@ -145,19 +135,19 @@ const getExpiredIntents = (address: string) => { return expiredIntents; }; -const refundExpiredIntents = async ( - address: string, - cosmosURL: string, - wallet: DirectSecp256k1Wallet, -) => { +const refundExpiredIntents = async ({ + address, + evmAddress, + client, +}: CosmosOptions & { evmAddress: string }) => { logger.debug('Starting check for expired intents at ', new Date()); - const expIntents = getExpiredIntents(address); + const expIntents = getExpiredIntents(evmAddress); const failedRefunds: IntentD[] = []; for (const intent of expIntents) { logger.debug(`Starting refund for: ${intent.id}`); try { - await cosmosRefundIntent(cosmosURL, intent.id, wallet); + await cosmosRefundIntent({ client, intentID: intent.id, address }); } catch (e) { logger.debug('Refund failed', e); failedRefunds.push({ @@ -169,7 +159,7 @@ const refundExpiredIntents = async ( if (failedRefunds.length > 0) { for (const failed of failedRefunds) { - storeIntentHashToStore(address, failed.id, failed.createdAt); + storeIntentHashToStore(evmAddress, failed.id, failed.createdAt); } } }; @@ -181,26 +171,6 @@ const equalFold = (a?: string, b?: string) => { return a.toLowerCase() === b.toLowerCase(); }; -const createRequestFuelSignature = async ( - fuelVaultAddress: string, - provider: Provider, - connector: FuelConnector, - fuelRFF: Parameters[0], -) => { - const account = await connector.currentAccount(); - if (!account) { - throw new Error('Fuel connector is not connected.'); - } - - const vault = new ArcanaVault(hexlify(fuelVaultAddress), provider); - const { value: hash } = await vault.functions.hash_request(fuelRFF).get(); - const signature = await connector.signMessage(account, { - personalSign: arrayify(hash), - }); - - return { requestHash: hash as Hex, signature: arrayify(signature) }; -}; - const getExplorerURL = (baseURL: string, id: Long) => { return new URL(`/intent/${id.toNumber()}`, baseURL).toString(); }; @@ -234,7 +204,7 @@ const convertIntent = ( for (const s of intent.sources) { const chainInfo = chainList.getChainByID(s.chainID); if (!chainInfo) { - throw new Error('chain not supported'); + throw Errors.chainNotFound(s.chainID); } sources.push({ amount: s.amount.toFixed(), @@ -250,7 +220,7 @@ const convertIntent = ( for (const s of intent.allSources) { const chainInfo = chainList.getChainByID(s.chainID); if (!chainInfo) { - throw new Error('chain not supported'); + throw Errors.chainNotFound(s.chainID); } allSources.push({ amount: s.amount.toFixed(), @@ -263,7 +233,7 @@ const convertIntent = ( const destinationChainInfo = chainList.getChainByID(intent.destination.chainID); if (!destinationChainInfo) { - throw new Error('chain not supported'); + throw Errors.chainNotFound(intent.destination.chainID); } const destination = { @@ -307,7 +277,9 @@ const hexTo0xString = (hex: string): `0x${string}` => { return `0x${hex}`; }; -const getSupportedChains = (env: Environment = Environment.CORAL) => { +const getSupportedChains = ( + env: Environment = Environment.CORAL, +): SupportedChainsAndTokensResult => { const chainList = new ChainList(env); return chainList.chains.map((chain) => { return { @@ -319,13 +291,6 @@ const getSupportedChains = (env: Environment = Environment.CORAL) => { }); }; -const isArcanaWallet = (p: EthereumProvider) => { - if ('isArcana' in p && p.isArcana) { - return true; - } - return false; -}; - const createRequestEVMSignature = async ( evmRFF: EVMRFF, evmAddress: `0x${string}`, @@ -337,22 +302,59 @@ const createRequestEVMSignature = async ( evmRFF.sources, evmRFF.destinationUniverse, evmRFF.destinationChainID, + evmRFF.recipientAddress, evmRFF.destinations, evmRFF.nonce, evmRFF.expiry, evmRFF.parties, ]); + const hash = keccak256(msg, 'bytes'); const signature = toBytes( - await client.signMessage({ - account: evmAddress, - message: { raw: hash }, - }), + await client + .signMessage({ + account: evmAddress, + message: { raw: hash }, + }) + .catch((e) => { + if (e instanceof UserRejectedRequestError) { + throw Errors.userRejectedIntentSignature(); + } + throw e; + }), ); return { requestHash: hashMessage({ raw: hash }), signature }; }; +const createRequestTronSignature = async (evmRFF: EVMRFF, client: AdapterProps) => { + logger.debug('createReqEVMSignature', { evmRFF }); + const abi = getAbiItem({ abi: EVMVaultABI, name: 'deposit' }); + const msg = encodeAbiParameters(abi.inputs[0].components, [ + evmRFF.sources, + evmRFF.destinationUniverse, + evmRFF.destinationChainID, + evmRFF.recipientAddress, + evmRFF.destinations, + evmRFF.nonce, + evmRFF.expiry, + evmRFF.parties, + ]); + const hash = toHex(keccak256(msg, 'bytes')); + + // FIXME: Hack - since tron doesn't supports binary decode of hex before signing + // const uppercaseHash = convertToUpperCaseHash(toHex(hash)); + const sig = await client.signMessage(hash); + return { + requestHash: utils.message.hashMessage(hash) as Hex, + signature: toBytes(sig), + }; +}; + +// const convertToUpperCaseHash = (input: Hex) => { +// return `0x${input.substring(2).toUpperCase()}`; +// }; + const convertGasToToken = ( token: TokenInfo, oraclePrices: OraclePriceResponse, @@ -360,17 +362,14 @@ const convertGasToToken = ( destinationUniverse: Universe, gas: Decimal, ) => { - if (isNativeAddress(destinationUniverse, token.contractAddress)) { + if (gas.isZero() || isNativeAddress(destinationUniverse, token.contractAddress)) { return gas; } const gasTokenInUSD = oraclePrices .find( - (rate) => - rate.chainId === destinationChainID && - (equalFold(rate.tokenAddress, ZERO_ADDRESS) || - equalFold(rate.tokenAddress, FUEL_BASE_ASSET_ID)), + (rate) => rate.chainId === destinationChainID && equalFold(rate.tokenAddress, ZERO_ADDRESS), ) ?.priceUsd.toFixed() ?? '0'; @@ -380,8 +379,9 @@ const convertGasToToken = ( rate.chainId === destinationChainID && equalFold(rate.tokenAddress, token.contractAddress), ) ?.priceUsd.toFixed(); + if (!transferTokenInUSD) { - throw new Error('could not find token in price oracle'); + throw Errors.internal('could not find token in price oracle'); } const usdValue = gas.mul(gasTokenInUSD); @@ -406,60 +406,47 @@ const evmWaitForFill = async ( ]); }; -const convertTo32Bytes = (value: bigint | Hex | number) => { +const convertTo32Bytes = (value: bigint | Hex | number | Bytes) => { if (typeof value == 'bigint' || typeof value === 'number') { return toBytes(value, { size: 32, }); - } - - if (typeof value === 'string') { + } else if (typeof value === 'string') { return pad(toBytes(value), { dir: 'left', size: 32, }); + } else { + return pad(value, { + dir: 'left', + size: 32, + }); } - - throw new Error('invalid type'); }; -const convertTo32BytesHex = (value: Hex) => { +const convertTo32BytesHex = (value: Hex | Bytes) => { const bytes = convertTo32Bytes(value); return toHex(bytes); }; const convertToHexAddressByUniverse = (address: Uint8Array, universe: Universe) => { - if (universe === Universe.FUEL) { - if (address.length === 32) { - return bytesToHex(address); - } else { - throw new Error('fuel: invalid address length'); - } - } else if (universe === Universe.ETHEREUM) { + if (universe === Universe.ETHEREUM || universe === Universe.TRON) { if (address.length === 20) { return bytesToHex(address); } else if (address.length === 32) { if (!address.subarray(0, 12).every((b) => b === 0)) { - throw new Error('evm: non-zero-padded 32-byte address'); + throw Errors.invalidAddressLength('evm', 'non-zero-padded 32-byte address'); } return bytesToHex(address.subarray(12)); } else { - throw new Error('evm: invalid address length'); + throw Errors.invalidAddressLength('evm'); } } else { - throw new Error('unsupported universe'); + throw Errors.universeNotSupported(); } }; -const createDepositDoubleCheckTx = ( - chainID: Uint8Array, - cosmos: { - address: string; - wallet: DirectSecp256k1Wallet; - }, - intentID: Long, - network: NetworkConfig, -) => { +const createDepositDoubleCheckTx = (chainID: Uint8Array, cosmos: CosmosOptions, intentID: Long) => { const msg = MsgDoubleCheckTx.create({ creator: cosmos.address, packet: { @@ -476,60 +463,12 @@ const createDepositDoubleCheckTx = ( return () => { return cosmosCreateDoubleCheckTx({ address: cosmos.address, - cosmosURL: network.COSMOS_URL, + client: cosmos.client, msg, - wallet: cosmos.wallet, }); }; }; -const getSDKConfig = (c: { network?: NexusNetwork; debug?: boolean }): Required => { - const config = { - debug: c.debug ?? false, - network: Environment.CORAL as Network, - }; - - switch (c.network) { - case 'testnet': { - config.network = Environment.FOLLY; - break; - } - case 'mainnet': { - config.network = Environment.CORAL; - break; - } - } - - return config; -}; - -const getTxOptions = (options?: Partial) => { - const defaultOptions: TxOptions = { - bridge: false, - gas: 0n, - skipTx: false, - sourceChains: [], - }; - - if (options?.bridge !== undefined) { - defaultOptions.bridge = options.bridge; - } - - if (options?.gas !== undefined) { - defaultOptions.gas = options.gas; - } - - if (options?.skipTx !== undefined) { - defaultOptions.skipTx = options.skipTx; - } - - if (options?.sourceChains !== undefined) { - defaultOptions.sourceChains = options.sourceChains; - } - - return defaultOptions; -}; - class UserAsset { get balance() { return this.value.balance; @@ -537,6 +476,19 @@ class UserAsset { constructor(public value: UserAssetDatum) {} + getBridgeAssets(dstChainId: number) { + return this.value.breakdown + .filter((b) => b.chain.id !== dstChainId) + .map((b) => { + return { + chainID: b.chain.id, + contractAddress: b.contractAddress, + decimals: b.decimals, + balance: new Decimal(b.balance), + }; + }); + } + getBalanceOnChain(chainID: number, tokenAddress?: `0x${string}`) { return ( this.value.breakdown.find((b) => { @@ -553,15 +505,11 @@ class UserAsset { return equalFold(tokenAddress, ZERO_ADDRESS); } - if (universe === Universe.FUEL) { - return true; - } - return false; } - iterate(feeStore: FeeStore) { - return this.value.breakdown + async iterate(chainList: ChainListType) { + const values = this.value.breakdown .filter((b) => new Decimal(b.balance).gt(0)) .sort((a, b) => { if (a.chain.id === 1) { @@ -571,37 +519,46 @@ class UserAsset { return -1; } return Decimal.sub(b.balance, a.balance).toNumber(); - }) - .map((b) => { - let balance = new Decimal(b.balance); - if (this.isDeposit(b.contractAddress, b.universe)) { - const collectionFee = feeStore.calculateCollectionFee({ - decimals: b.decimals, - sourceChainID: b.chain.id, - sourceTokenAddress: b.contractAddress, - }); - - let estimatedGasForDeposit = collectionFee.mul(b.chain.id === 1 ? 2 : 4); - - if (b.contractAddress === FUEL_BASE_ASSET_ID && b.chain.id === CHAIN_IDS.fuel.mainnet) { - // Estimating this amount of gas is required for fuel -> vault - estimatedGasForDeposit = new Decimal('0.000_003'); - } - - if (new Decimal(b.balance).lessThan(estimatedGasForDeposit)) { - balance = new Decimal(0); - } else { - balance = new Decimal(b.balance).minus(estimatedGasForDeposit); - } + }); + + const balances = []; + + for (const b of values) { + let balance = new Decimal(b.balance); + if (this.isDeposit(b.contractAddress, b.universe)) { + const ESTIMATED_DEPOSIT_GAS = 200_000n; + + const chain = chainList.getChainByID(b.chain.id); + if (!chain) { + throw Errors.chainNotFound(b.chain.id); } - return { - balance, - chainID: b.chain.id, - decimals: b.decimals, - tokenContract: b.contractAddress, - universe: b.universe, - }; + + const publicClient = createPublicClientWithFallback(chain); + const gasEstimate = await publicClient.estimateFeesPerGas(); + const gasUnitPrice = gasEstimate.maxFeePerGas ?? gasEstimate.gasPrice; + + const estimatedGasForDeposit = divDecimals( + ESTIMATED_DEPOSIT_GAS * gasUnitPrice, + chain.nativeCurrency.decimals, + ); + + if (new Decimal(b.balance).lessThan(estimatedGasForDeposit)) { + balance = new Decimal(0); + } else { + balance = new Decimal(b.balance).minus(estimatedGasForDeposit); + } + } + + balances.push({ + balance, + chainID: b.chain.id, + decimals: b.decimals, + tokenContract: b.contractAddress, + universe: b.universe, }); + } + + return balances; } } class UserAssets { @@ -617,7 +574,7 @@ class UserAssets { return new UserAsset(asset); } } - throw new Error('Asset is not supported.'); + throw Errors.tokenNotSupported(); } findOnChain(chainID: number, address: `0x${string}`) { @@ -685,7 +642,220 @@ class UserAssets { } } +// CHATGPT function below +async function waitForTronTxConfirmation( + txid: string, + tronWeb: TronWeb, + options: { timeout?: number; interval?: number } = {}, +): Promise { + const { timeout = 120000, interval = 3000 } = options; + + const startTime = Date.now(); + + logger.debug(`πŸ“‘ Waiting for confirmation of tron tx`); + + while (Date.now() - startTime < timeout) { + try { + const txInfo = await tronWeb.trx.getTransactionInfo(txid); + + logger.debug(`tx info:`, { + txInfo, + }); + + if (txInfo?.receipt) { + const result = txInfo.receipt.result; + if (result === 'FAILED') { + throw Errors.transactionReverted(txid); + } else { + return txInfo; + } + } + } catch (err) { + logger.error(`⚠️ Error while checking transaction:`, err, { + cause: 'TRANSACTION_CHECK_ERROR', + }); + // Don’t throw yet; continue polling + } + + logger.debug('⏳ Still waiting...'); + await new Promise((resolve) => setTimeout(resolve, interval)); + } + + throw Errors.transactionTimeout(timeout / 1000); +} + +async function waitForTronDepositTxConfirmation( + hash: Hex, + vaultContractAddress: Hex, + tronWeb: TronWeb, + owner: Hex, + options: { timeout?: number; interval?: number } = {}, +): Promise { + const { timeout = 120000, interval = 3000 } = options; + + const startTime = Date.now(); + + logger.debug(`πŸ“‘ Waiting for confirmation of tron tx`); + + const input = encodeFunctionData({ + abi: EVMVaultABI, + functionName: 'requestState', + args: [hash], + }); + while (Date.now() - startTime < timeout) { + try { + const result = await tronWeb.transactionBuilder.triggerConstantContract( + tronWeb.utils.address.fromHex(vaultContractAddress), + '', + { + input, + }, + [], + tronWeb.utils.address.fromHex(owner), + ); + + logger.debug('requestHashWitnessedOnTron', { + result, + }); + if (result.Error) { + throw Errors.internal(result.Error); + } + + const requestState = bytesToNumber(result.constant_result[0]); + if (requestState === 0) { + throw Errors.internal('Request not witnessed yet.'); + } + + return; + } catch (err) { + logger.error(`⚠️ Error while checking transaction:`, err, { + cause: 'TRANSACTION_CHECK_ERROR', + }); + // Don’t throw yet; continue polling + } + + logger.debug('⏳ Still waiting...'); + await new Promise((resolve) => setTimeout(resolve, interval)); + } + + throw Errors.transactionTimeout(timeout / 1000); +} + +function pctAdditionToBigInt(base: bigint, percentage: number) { + return base + BigInt(new Decimal(base).mul(percentage).toFixed(0)); +} + +function divideBigInt(base: bigint, divisor: number) { + if (base === 0n) { + return base; + } + + return BigInt(new Decimal(base).div(divisor).toFixed(0)); +} + +async function waitForTronApprovalTxConfirmation( + amount: bigint, + owner: Hex, + spender: Hex, + contractAddress: Hex, + tronWeb: TronWeb, + options: { timeout?: number; interval?: number } = {}, +): Promise { + const { timeout = 120000, interval = 3000 } = options; + + const startTime = Date.now(); + + logger.debug(`πŸ“‘ Waiting for confirmation of tron approval tx`); + + const input = encodeFunctionData({ + abi: ERC20ABI, + functionName: 'allowance', + args: [owner, spender], + }); + + while (Date.now() - startTime < timeout) { + try { + const result = await tronWeb.transactionBuilder.triggerConstantContract( + tronWeb.utils.address.fromHex(contractAddress), + '', + { + input, + }, + [], + tronWeb.utils.address.fromHex(owner), + ); + + logger.debug('waitForTronApprovalTxConfirmation', { + result, + }); + + if (result.Error) { + throw Errors.internal(result.Error); + } + + const allowance = hexToBigInt(`0x${result.constant_result[0]}`); + if (allowance < amount) { + throw Errors.internal('Allowance not set yet.'); + } + + return; + } catch (err) { + logger.error(`⚠️ Error while checking transaction:`, err, { + cause: 'TRANSACTION_CHECK_ERROR', + }); + // Don’t throw yet; continue polling + } + + logger.debug('⏳ Still waiting...'); + await new Promise((resolve) => setTimeout(resolve, interval)); + } + + throw Errors.transactionTimeout(timeout / 1000); +} + +const createExplorerTxURL = (txHash: Hex, explorerURL: string) => { + return new URL(`/tx/${txHash}`, explorerURL).href; +}; + +const retrieveAddress = (universe: Universe, input: Pick): Hex => { + if (universe === Universe.ETHEREUM) { + return input.evm.address; + } else if (universe === Universe.TRON) { + if (!input.tron) { + throw Errors.internal('tron source but no tron input'); + } + + return input.tron.address as Hex; + } + + throw Errors.internal('unknown universe'); +}; + +const SIWE_KEY = '_siwe_sig'; + +const storeSIWESignatureToLocalStorage = (address: Hex, siweChain: number, signature: string) => { + window.localStorage.setItem(`${SIWE_KEY}-${address}-${siweChain}`, signature); +}; + +const retrieveSIWESignatureFromLocalStorage = (address: Hex, siweChain: number) => { + return window.localStorage.getItem(`${SIWE_KEY}-${address}-${siweChain}`); +}; + +const createDeadlineFromNow = (minutes: bigint = 3n): bigint => { + const nowInSeconds = BigInt(Math.floor(Date.now() / 1000)); + return nowInSeconds + minutes * 60n; +}; + export { + divideBigInt, + createDeadlineFromNow, + pctAdditionToBigInt, + retrieveSIWESignatureFromLocalStorage, + storeSIWESignatureToLocalStorage, + retrieveAddress, + createExplorerTxURL, + waitForTronApprovalTxConfirmation, + waitForTronDepositTxConfirmation, UserAsset, UserAssets, convertAddressByUniverse, @@ -696,20 +866,18 @@ export { convertToHexAddressByUniverse, createDepositDoubleCheckTx, createRequestEVMSignature, - createRequestFuelSignature, divDecimals, equalFold, evmWaitForFill, getExpiredIntents, getExplorerURL, - getSDKConfig, getSupportedChains, - getTxOptions, hexTo0xString, - isArcanaWallet, minutesToMs, mulDecimals, refundExpiredIntents, removeIntentHashFromStore, storeIntentHashToStore, + createRequestTronSignature, + waitForTronTxConfirmation, }; diff --git a/packages/core/sdk/ca-base/utils/contract.utils.ts b/src/sdk/ca-base/utils/contract.utils.ts similarity index 67% rename from packages/core/sdk/ca-base/utils/contract.utils.ts rename to src/sdk/ca-base/utils/contract.utils.ts index 7d2f0a48..05bfa378 100644 --- a/packages/core/sdk/ca-base/utils/contract.utils.ts +++ b/src/sdk/ca-base/utils/contract.utils.ts @@ -1,13 +1,5 @@ -import { - ChaindataMap, - Currency, - OmniversalChainID, - PermitCreationError, - PermitVariant, - Universe, -} from '@arcana/ca-common'; -import { ERC20ABI as ERC20ABIC } from '@arcana/ca-common'; -import { CHAIN_IDS } from 'fuels'; +import { Currency, PermitCreationError, PermitVariant } from '@avail-project/ca-common'; +import { ERC20ABI as ERC20ABIC } from '@avail-project/ca-common'; import { Account, Address, @@ -19,54 +11,25 @@ import { getContract, Hex, hexToBigInt, - hexToBytes, http, - JsonRpcAccount, maxUint256, pad, - parseSignature, PublicClient, - SwitchChainError, WalletClient, WebSocketTransport, } from 'viem'; - import ERC20ABI from '../abi/erc20'; import gasOracleABI from '../abi/gasOracle'; import { FillEvent } from '../abi/vault'; import { ZERO_ADDRESS } from '../constants'; -import { ErrorLiquidityTimeout } from '../errors'; -import { getLogger } from '../logger'; -import { - ChainListType, - Chain, - EVMTransaction, - NetworkConfig, - SponsoredApprovalData, -} from '@nexus/commons'; -import { vscCreateSponsoredApprovals } from './api.utils'; -import { convertTo32Bytes, equalFold, minutesToMs } from './common.utils'; +import { Errors } from '../errors'; +import { getLogger } from '../../../commons'; +import { ChainListType, Chain, GetAllowanceParams, SetAllowanceParams } from '../../../commons'; +import { equalFold, minutesToMs } from './common.utils'; const logger = getLogger(); -const isEVMTx = (tx: unknown): tx is EVMTransaction => { - logger.debug('isEVMTx', tx); - if (typeof tx !== 'object') { - return false; - } - if (!tx) { - return false; - } - if (!('to' in tx)) { - return false; - } - if (!('data' in tx || 'value' in tx)) { - return false; - } - return true; -}; - -const getAllowance = ( +const getAllowance = async ( chain: Chain, address: `0x${string}`, tokenContract: `0x${string}`, @@ -75,6 +38,8 @@ const getAllowance = ( logger.debug('getAllowance', { tokenContract, ZERO_ADDRESS, + chain, + address, }); if (equalFold(ZERO_ADDRESS, tokenContract)) { @@ -83,11 +48,38 @@ const getAllowance = ( const publicClient = createPublicClientWithFallback(chain); - return publicClient.readContract({ + try { + const allowance = erc20GetAllowance( + { + contractAddress: tokenContract, + spender: chainList.getVaultContractAddress(chain.id), + owner: address, + }, + publicClient, + ); + return allowance; + } catch { + return 0n; + } +}; + +const erc20GetAllowance = (params: GetAllowanceParams, client: PublicClient) => { + return client.readContract({ + address: params.contractAddress, abi: ERC20ABI, - address: tokenContract, - args: [address, chainList.getVaultContractAddress(chain.id)], functionName: 'allowance', + args: [params.owner, params.spender], + }); +}; + +const erc20SetAllowance = (params: SetAllowanceParams & { chain: Chain }, client: WalletClient) => { + return client.writeContract({ + address: params.contractAddress, + abi: ERC20ABI, + functionName: 'approve', + args: [params.spender, params.amount], + chain: params.chain, + account: params.owner, }); }; @@ -95,22 +87,18 @@ const getAllowances = async ( input: { chainID: number; tokenContract: `0x${string}`; + holderAddress: `0x${string}`; }[], - address: `0x${string}`, chainList: ChainListType, ) => { const values: { [k: number]: bigint } = {}; const promises = []; for (const i of input) { - if (i.chainID === CHAIN_IDS.fuel.mainnet) { - promises.push(Promise.resolve(0n)); - } else { - const chain = chainList.getChainByID(i.chainID); - if (!chain) { - throw new Error('chain not found'); - } - promises.push(getAllowance(chain, address, i.tokenContract, chainList)); + const chain = chainList.getChainByID(i.chainID); + if (!chain) { + throw Errors.chainNotFound(i.chainID); } + promises.push(getAllowance(chain, i.holderAddress, i.tokenContract, chainList)); } const result = await Promise.all(promises); for (const i in result) { @@ -131,7 +119,7 @@ const waitForIntentFulfilment = async ( abi: [FillEvent] as const, address: vaultContractAddr, args: { requestHash }, - eventName: 'Fill', + eventName: 'Fulfilment', onLogs: (logs) => { logger.debug('waitForIntentFulfilment', { logs }); ac.abort(); @@ -142,6 +130,7 @@ const waitForIntentFulfilment = async ( ac.signal.addEventListener( 'abort', () => { + logger.debug('waitForIntentFulfilment: got abort, going to unwatch'); unwatch(); return resolve('ok from outside'); }, @@ -154,7 +143,7 @@ const requestTimeout = (timeout: number, ac: AbortController) => { return new Promise((_, reject) => { const t = window.setTimeout(() => { ac.abort(); - return reject(ErrorLiquidityTimeout); + return reject(Errors.liquidityTimeout()); }, minutesToMs(timeout)); ac.signal.addEventListener( 'abort', @@ -179,101 +168,6 @@ const getTokenTxFunction = (data: `0x${string}`) => { } }; -const setAllowances = async ( - tokenContractAddresses: Array<`0x${string}`>, - client: WalletClient, - networkConfig: NetworkConfig, - chainList: ChainListType, - chain: Chain, - amount: bigint, -) => { - const vaultAddr = chainList.getVaultContractAddress(chain.id); - const p = []; - const address = (await client.getAddresses())[0]; - - const chainId = new OmniversalChainID(Universe.ETHEREUM, chain.id); - const chainDatum = ChaindataMap.get(chainId); - if (!chainDatum) { - throw new Error('Chain data not found'); - } - - const account: JsonRpcAccount = { - address, - type: 'json-rpc', - }; - const publicClient = createPublicClientWithFallback(chain); - - const sponsoredApprovalParams: SponsoredApprovalData = { - address: hexToBytes( - pad(address, { - dir: 'left', - size: 32, - }), - ), - chain_id: chainDatum.ChainID32, - operations: [], - universe: chainDatum.Universe, - }; - - for (const addr of tokenContractAddresses) { - const currency = chainDatum.CurrencyMap.get(convertTo32Bytes(addr)); - if (!currency) { - throw new Error('Currency not found'); - } - - if (currency.permitVariant === PermitVariant.Unsupported) { - const hash = await client.writeContract({ - abi: ERC20ABI, - account: address, - address: addr, - args: [vaultAddr, amount], - chain, - functionName: 'approve', - }); - p.push( - (async function () { - const result = await publicClient.waitForTransactionReceipt({ - confirmations: 2, - hash, - }); - if (result.status === 'reverted') { - throw new Error('setAllowance failed with tx revert'); - } - })(), - ); - } else { - const signed = parseSignature( - await signPermitForAddressAndValue( - currency, - client, - publicClient, - account, - vaultAddr, - amount, - ), - ); - sponsoredApprovalParams.operations.push({ - sig_r: hexToBytes(signed.r), - sig_s: hexToBytes(signed.s), - sig_v: signed.yParity < 27 ? signed.yParity + 27 : signed.yParity, - token_address: currency.tokenAddress, - value: convertTo32Bytes(amount), - variant: currency.permitVariant === PermitVariant.PolygonEMT ? 2 : 1, - }); - } - } - - if (p.length) { - await Promise.all(p); - } - - if (sponsoredApprovalParams.operations.length) { - await vscCreateSponsoredApprovals(networkConfig.VSC_DOMAIN, [sponsoredApprovalParams]); - } - - return; -}; - const DEFAULT_GAS_ORACLE_ADDRESS = '0x420000000000000000000000000000000000000F'; const L1_GAS_ORACLES: Record = { @@ -310,28 +204,41 @@ const waitForTxReceipt = async ( hash: `0x${string}`, publicClient: PublicClient, confirmations = 1, + timeout = 60000, ) => { const r = await publicClient.waitForTransactionReceipt({ confirmations, hash, + timeout, }); if (r.status === 'reverted') { - throw new Error(`Transaction reverted: ${hash}`); + throw Errors.transactionReverted(hash); } + + return r; }; const switchChain = async (client: WalletClient, chain: Chain) => { + const current = await client.getChainId(); + if (current === chain.id) return; + try { await client.switchChain({ id: chain.id }); - } catch (e) { - if (e instanceof SwitchChainError && e.code === SwitchChainError.code) { - await client.addChain({ - chain, - }); + } catch (outerErr) { + logger.error(`switchChain failed, trying addChain`, outerErr); + try { + await client.addChain({ chain }); await client.switchChain({ id: chain.id }); - return; + } catch (inner) { + logger.error('Unable to add/switch chain', inner); + throw inner; } - throw e; + } + + const after = await client.getChainId(); + if (after !== chain.id) { + logger.error(`Wallet did not switch chains even though no error was thrown`); + throw Errors.internal('wallet did not switch chain - no error thrown'); } }; @@ -375,7 +282,7 @@ async function signPermitForAddressAndValue( return ''; }); })(), - client.request({ method: 'eth_chainId' }, { dedupe: true }), + client.request({ method: 'eth_chainId' }), ]; switch (cur.permitVariant) { @@ -526,25 +433,31 @@ async function signPermitForAddressAndValue( } const createPublicClientWithFallback = (chain: Chain): PublicClient => { - if (chain.rpcUrls.default.http.length === 1) { - return createPublicClient({ - transport: http(chain.rpcUrls.default.http[0]), - }); - } return createPublicClient({ - transport: fallback(chain.rpcUrls.default.http.map((s) => http(s))), + transport: fallback( + chain.rpcUrls.default.http.concat(chain.rpcUrls.default.publicHttp ?? []).map((s) => http(s)), + ), }); }; +const getPctGasBufferByChain = (_: number) => { + // if (chainId === TESTNET_CHAIN_IDS.MONAD_TESTNET || chainId === MAINNET_CHAIN_IDS.MONAD) { + // return 0.05; + // } + + return 0.3; +}; + export { + getPctGasBufferByChain, + erc20GetAllowance, + erc20SetAllowance, createPublicClientWithFallback, getAllowance, getAllowances, getL1Fee, getTokenTxFunction, - isEVMTx, requestTimeout, - setAllowances, signPermitForAddressAndValue, switchChain, waitForIntentFulfilment, diff --git a/packages/core/sdk/ca-base/utils/cosmos.utils.ts b/src/sdk/ca-base/utils/cosmos.utils.ts similarity index 76% rename from packages/core/sdk/ca-base/utils/cosmos.utils.ts rename to src/sdk/ca-base/utils/cosmos.utils.ts index 365916f4..2bd4421f 100644 --- a/packages/core/sdk/ca-base/utils/cosmos.utils.ts +++ b/src/sdk/ca-base/utils/cosmos.utils.ts @@ -1,26 +1,23 @@ import { - createCosmosClient, MsgCreateRequestForFunds, MsgCreateRequestForFundsResponse, MsgDoubleCheckTx, MsgRefundReq, MsgRefundReqResponse, -} from '@arcana/ca-common'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +} from '@avail-project/ca-common'; import { isDeliverTxFailure, isDeliverTxSuccess } from '@cosmjs/stargate'; import axios from 'axios'; import { connect } from 'it-ws/client'; import Long from 'long'; - -import { getLogger } from '../logger'; +import { CosmosOptions, getLogger } from '../../../commons'; import { checkIntentFilled, vscCreateFeeGrant } from './api.utils'; +import { Errors } from '../errors'; const logger = getLogger(); const getCosmosURL = (cosmosURL: string, kind: 'rest' | 'rpc') => { const u = new URL(cosmosURL); if (kind === 'rpc') { - // FIXME: don't hardcode port here u.port = '26650'; } return u.toString(); @@ -32,7 +29,7 @@ const cosmosFeeGrant = async (cosmosURL: string, vscDomain: string, address: str baseURL: getCosmosURL(cosmosURL, 'rest'), }); } catch (e) { - logger.error('Requesting a fee grant', e); + logger.error('Requesting a fee grant', e, { cause: 'FEE_GRANT_REQUESTED' }); const response = await vscCreateFeeGrant(vscDomain, address); logger.debug('Fee grant response', response.data); return; @@ -41,18 +38,11 @@ const cosmosFeeGrant = async (cosmosURL: string, vscDomain: string, address: str const cosmosCreateRFF = async ({ address, - cosmosURL, + client, msg, - wallet, -}: { - address: string; - cosmosURL: string; +}: CosmosOptions & { msg: MsgCreateRequestForFunds; - wallet: DirectSecp256k1Wallet; }) => { - const client = await createCosmosClient(wallet, getCosmosURL(cosmosURL, 'rpc'), { - broadcastPollIntervalMs: 250, - }); try { const res = await client.signAndBroadcast( address, @@ -69,7 +59,7 @@ const cosmosCreateRFF = async ({ ); if (isDeliverTxFailure(res)) { - throw new Error(`Error creating RFF – code=${res.code} log=${res.rawLog ?? 'n/a'}`); + throw Errors.cosmosError(`Error creating RFF – code=${res.code} log=${res.rawLog ?? 'n/a'}`); } const decoded = MsgCreateRequestForFundsResponse.decode(res.msgResponses[0].value); @@ -79,15 +69,13 @@ const cosmosCreateRFF = async ({ } }; -const cosmosRefundIntent = async ( - cosmosURL: string, - intentID: number, - wallet: DirectSecp256k1Wallet, -) => { - const address = (await wallet.getAccounts())[0].address; - const client = await createCosmosClient(wallet, getCosmosURL(cosmosURL, 'rpc'), { - broadcastPollIntervalMs: 250, - }); +const cosmosRefundIntent = async ({ + address, + client, + intentID, +}: CosmosOptions & { + intentID: number; +}) => { try { const resp = await client.signAndBroadcast( address, @@ -102,7 +90,7 @@ const cosmosRefundIntent = async ( ], { amount: [], - gas: 100_000n.toString(10), + gas: 200_000n.toString(10), }, ); logger.debug('Refund response', { resp }); @@ -118,12 +106,12 @@ const cosmosRefundIntent = async ( ) { return resp; } - throw new Error('RFF is not expired yet.'); + throw Errors.cosmosError('RFF is not expired yet.'); } else { - throw new Error('unknown error'); + throw Errors.cosmosError(`unknown error: ${JSON.stringify(resp)}`); } } catch (e) { - logger.error('Refund failed', e); + logger.error('Refund failed', e, { cause: 'REFUND_FAILED' }); throw e; } } finally { @@ -133,19 +121,11 @@ const cosmosRefundIntent = async ( const cosmosCreateDoubleCheckTx = async ({ address, - cosmosURL, + client, msg, - wallet, -}: { - address: string; - cosmosURL: string; +}: CosmosOptions & { msg: MsgDoubleCheckTx; - wallet: DirectSecp256k1Wallet; }) => { - const client = await createCosmosClient(wallet, getCosmosURL(cosmosURL, 'rpc'), { - broadcastPollIntervalMs: 250, - }); - try { logger.debug('cosmosCreateDoubleCheckTx', { doubleCheckMsg: msg }); @@ -164,7 +144,7 @@ const cosmosCreateDoubleCheckTx = async ({ ); if (isDeliverTxFailure(res)) { - throw new Error('Error creating MsgDoubleCheckTx'); + throw Errors.cosmosError('double check tx failed'); } logger.debug('double check response', { doubleCheckTx: res }); @@ -219,6 +199,9 @@ const waitForCosmosFillEvent = async (intentID: Long, cosmosURL: string, ac: Abo ); for await (const resp of connection.source) { + logger.debug('waitForCosmosFillEvent', { + resp, + }); const decodedResponse = JSON.parse(decoder.decode(resp)); if ( decodedResponse.result.events && @@ -230,7 +213,7 @@ const waitForCosmosFillEvent = async (intentID: Long, cosmosURL: string, ac: Abo } } - throw new Error('waitForCosmosFillEvent: out of loop but no events'); + throw Errors.cosmosError('waitForCosmosFillEvent: out of loop but no events'); } finally { connection.close(); } diff --git a/src/sdk/ca-base/utils/index.ts b/src/sdk/ca-base/utils/index.ts new file mode 100644 index 00000000..ebf4ed61 --- /dev/null +++ b/src/sdk/ca-base/utils/index.ts @@ -0,0 +1,7 @@ +export * from './api.utils'; +export * from './common.utils'; +export * from './contract.utils'; +export * from './cosmos.utils'; +export * from './rff.utils'; +export * from './balance.utils'; +export * from './tron.utils'; diff --git a/packages/core/sdk/ca-base/utils/rff.utils.ts b/src/sdk/ca-base/utils/rff.utils.ts similarity index 51% rename from packages/core/sdk/ca-base/utils/rff.utils.ts rename to src/sdk/ca-base/utils/rff.utils.ts index 18106de0..cfe01073 100644 --- a/packages/core/sdk/ca-base/utils/rff.utils.ts +++ b/src/sdk/ca-base/utils/rff.utils.ts @@ -1,39 +1,39 @@ -import { MsgCreateRequestForFunds, OmniversalRFF, Universe } from '@arcana/ca-common'; -import { FUEL_BASE_ASSET_ID, INTENT_EXPIRY, isNativeAddress, ZERO_ADDRESS } from '../constants'; -import { getLogger } from '../logger'; -import { ChainListType, Intent } from '@nexus/commons'; +import { MsgCreateRequestForFunds, OmniversalRFF, Universe } from '@avail-project/ca-common'; +import { INTENT_EXPIRY, isNativeAddress, ZERO_ADDRESS } from '../constants'; +import { getLogger, ChainListType, Intent, IBridgeOptions } from '../../../commons'; import { convertTo32Bytes, convertTo32BytesHex, createRequestEVMSignature, - createRequestFuelSignature, + createRequestTronSignature, mulDecimals, } from './common.utils'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; import { Hex, PrivateKeyAccount, toBytes, WalletClient } from 'viem'; -import { CHAIN_IDS, FuelConnector, Provider } from 'fuels'; import Long from 'long'; +import { TronWeb } from 'tronweb'; +import { tronHexToEvmAddress } from './tron.utils'; +import { Errors } from '../errors'; +import { convertToEVMAddress } from '../swap/utils'; +import Decimal from 'decimal.js'; +import { FeeStore } from './api.utils'; type Destination = { - tokenAddress: `0x${string}`; + tokenAddress: Hex; universe: Universe; value: bigint; }; type Source = { chainID: bigint; - tokenAddress: `0x${string}`; + tokenAddress: Hex; universe: Universe; - value: bigint; + valueRaw: bigint; + value: Decimal; }; const logger = getLogger(); -const getSourcesAndDestinationsForRFF = ( - intent: Intent, - chainList: ChainListType, - destinationUniverse: Universe, -) => { +const getSourcesAndDestinationsForRFF = (intent: Intent, chainList: ChainListType, _: Universe) => { const sources: Source[] = []; const universes = new Set(); @@ -45,7 +45,7 @@ const getSourcesAndDestinationsForRFF = ( const token = chainList.getTokenByAddress(source.chainID, source.tokenContract); if (!token) { logger.error('Token not found', { source }); - throw new Error('token not found'); + throw Errors.tokenNotSupported(source.tokenContract, source.chainID); } universes.add(source.universe); @@ -54,7 +54,8 @@ const getSourcesAndDestinationsForRFF = ( chainID: BigInt(source.chainID), tokenAddress: convertTo32BytesHex(source.tokenContract), universe: source.universe, - value: mulDecimals(source.amount, token.decimals), + valueRaw: mulDecimals(source.amount, token.decimals), + value: source.amount, }); } @@ -73,9 +74,7 @@ const getSourcesAndDestinationsForRFF = ( destinations[0].value = destinations[0].value + intent.destination.gas; } else { destinations.push({ - tokenAddress: convertTo32BytesHex( - destinationUniverse === Universe.FUEL ? FUEL_BASE_ASSET_ID : ZERO_ADDRESS, - ), + tokenAddress: convertTo32BytesHex(ZERO_ADDRESS), universe: intent.destination.universe, value: intent.destination.gas, }); @@ -87,11 +86,11 @@ const getSourcesAndDestinationsForRFF = ( const createRFFromIntent = async ( intent: Intent, - options: { - chainList: ChainListType; - cosmos: { address: string; client: DirectSecp256k1Wallet }; - evm: { address: Hex; client: PrivateKeyAccount | WalletClient }; - fuel?: { address: string; connector: FuelConnector; provider: Provider }; + options: Pick & { + evm: { + address: `0x${string}`; + client: WalletClient | PrivateKeyAccount; + }; }, destinationUniverse: Universe, ) => { @@ -110,9 +109,12 @@ const createRFFromIntent = async ( }); } - if (universe === Universe.FUEL) { + if (universe === Universe.TRON) { + console.log({ tronAddress: TronWeb.address.toHex(options.tron!.address) }); parties.push({ - address: convertTo32BytesHex(options.fuel!.address as Hex), + address: convertTo32BytesHex( + tronHexToEvmAddress(TronWeb.address.toHex(options.tron!.address)), + ), universe, }); } @@ -128,23 +130,24 @@ const createRFFromIntent = async ( const omniversalRFF = new OmniversalRFF({ destinationChainID: convertTo32Bytes(intent.destination.chainID), destinations: destinations.map((dest) => ({ - tokenAddress: toBytes(dest.tokenAddress), + contractAddress: toBytes(dest.tokenAddress), value: toBytes(dest.value), })), + recipientAddress: convertTo32Bytes(intent.recipientAddress), destinationUniverse: intent.destination.universe, expiry: Long.fromString((BigInt(Date.now() + INTENT_EXPIRY) / 1000n).toString()), nonce: window.crypto.getRandomValues(new Uint8Array(32)), - // @ts-ignore + // @ts-expect-error signatureData: parties.map((p) => ({ address: toBytes(p.address), universe: p.universe, })), - // @ts-ignore + // @ts-expect-error sources: sources.map((source) => ({ chainID: convertTo32Bytes(source.chainID), - tokenAddress: convertTo32Bytes(source.tokenAddress), + contractAddress: convertTo32Bytes(source.tokenAddress), universe: source.universe, - value: toBytes(source.value), + value: toBytes(source.valueRaw), })), }); @@ -171,25 +174,23 @@ const createRFFromIntent = async ( }); } - if (universe === Universe.FUEL) { - if (!options.fuel?.address || !options.fuel?.provider || !options.fuel?.connector) { - logger.error('universe has fuel but not expected input', { - fuelInput: options.fuel, + if (universe === Universe.TRON) { + if (!options.tron) { + logger.error('universe has tron but not expected input', { + tronInput: options.tron, }); - throw new Error('universe has fuel but not expected input'); + throw Errors.internal('universe has tron but not expected input'); } - - const { requestHash, signature } = await createRequestFuelSignature( - options.chainList.getVaultContractAddress(CHAIN_IDS.fuel.mainnet), - options.fuel.provider, - options.fuel.connector, - omniversalRFF.asFuelRFF(), + const { requestHash, signature } = await createRequestTronSignature( + omniversalRFF.asEVMRFF(), + options.tron.adapter, ); + signatureData.push({ - address: toBytes(options.fuel.address), + address: convertTo32Bytes(tronHexToEvmAddress(TronWeb.address.toHex(options.tron.address))), requestHash, signature, - universe: Universe.FUEL, + universe, }); } } @@ -198,6 +199,7 @@ const createRFFromIntent = async ( destinationChainID: omniversalRFF.protobufRFF.destinationChainID, destinations: omniversalRFF.protobufRFF.destinations, destinationUniverse: omniversalRFF.protobufRFF.destinationUniverse, + recipientAddress: omniversalRFF.protobufRFF.recipientAddress, expiry: omniversalRFF.protobufRFF.expiry, nonce: omniversalRFF.protobufRFF.nonce, signatureData: signatureData.map((s) => ({ @@ -224,4 +226,83 @@ const createRFFromIntent = async ( }; }; -export { createRFFromIntent, getSourcesAndDestinationsForRFF }; +const calculateMaxBridgeFee = ({ + assets, + feeStore, + dst, +}: { + dst: { + chainId: number; + tokenAddress: Hex; + decimals: number; + }; + assets: { + chainID: number; + contractAddress: `0x${string}`; + decimals: number; + balance: Decimal; + }[]; + feeStore: FeeStore; +}) => { + const borrow = assets.reduce((accumulator, asset) => { + return accumulator.add(asset.balance); + }, new Decimal(0)); + + const sourceChainIds: number[] = []; + + const protocolFee = feeStore.calculateProtocolFee(new Decimal(borrow)); + let borrowWithFee = borrow.add(protocolFee); + + const fulfilmentFee = feeStore.calculateFulfilmentFee({ + decimals: dst.decimals, + destinationChainID: dst.chainId, + destinationTokenAddress: dst.tokenAddress, + }); + borrowWithFee = borrowWithFee.add(fulfilmentFee); + + logger.debug('calculateMaxBridgeFees:1', { + borrow: borrow.toFixed(), + protocolFee: protocolFee.toFixed(), + fulfilmentFee: fulfilmentFee.toFixed(), + borrowWithFee: borrowWithFee.toFixed(), + }); + + for (const asset of assets) { + if (!asset.balance.gt(0)) { + continue; + } + sourceChainIds.push(asset.chainID); + const collectionFee = feeStore.calculateCollectionFee({ + decimals: asset.decimals, + sourceChainID: asset.chainID, + sourceTokenAddress: asset.contractAddress, + }); + + borrowWithFee = borrowWithFee.add(collectionFee); + + const solverFee = feeStore.calculateSolverFee({ + borrowAmount: asset.balance, + decimals: asset.decimals, + destinationChainID: dst.chainId, + destinationTokenAddress: dst.tokenAddress, + sourceChainID: asset.chainID, + sourceTokenAddress: convertToEVMAddress(asset.contractAddress), + }); + + borrowWithFee = borrowWithFee.add(solverFee); + logger.debug('calculateMaxBridgeFees:2', { + borrow: borrow.toFixed(), + borrowWithFee: borrowWithFee.toFixed(), + solverFee: solverFee.toFixed(), + }); + } + + const fee = borrowWithFee.minus(borrow); + const maxAmount = fee.lt(borrow) + ? borrow.minus(fee).toFixed(dst.decimals, Decimal.ROUND_FLOOR) + : '0'; + + return { fee, maxAmount, sourceChainIds }; +}; + +export { createRFFromIntent, getSourcesAndDestinationsForRFF, calculateMaxBridgeFee }; diff --git a/src/sdk/ca-base/utils/tron.utils.ts b/src/sdk/ca-base/utils/tron.utils.ts new file mode 100644 index 00000000..420225c1 --- /dev/null +++ b/src/sdk/ca-base/utils/tron.utils.ts @@ -0,0 +1,16 @@ +import { Hex } from 'viem'; +import { Errors } from '../errors'; + +function tronHexToEvmAddress(tronHex: string): Hex { + const normalized = tronHex.toLowerCase().replace(/^0x/, ''); + + // Validate length and prefix + if (!/^41[a-f0-9]{40}$/.test(normalized)) { + throw Errors.internal(`Invalid TRON hex address: ${tronHex}`); + } + + // Extract last 20 bytes (40 hex chars) and return as EVM address + return `0x${normalized.slice(2)}`; +} + +export { tronHexToEvmAddress }; diff --git a/src/sdk/index.ts b/src/sdk/index.ts new file mode 100644 index 00000000..fc9aa13f --- /dev/null +++ b/src/sdk/index.ts @@ -0,0 +1,306 @@ +// src/core/sdk/index.ts +import { NexusUtils } from './utils'; +import type { + BridgeParams, + BridgeResult, + TransferParams, + TransferResult, + OnIntentHook, + OnAllowanceHook, + EthereumProvider, + UserAsset, + SimulationResult, + RequestForFunds, + NexusNetwork, + BridgeAndExecuteParams, + BridgeAndExecuteResult, + ExecuteParams, + ExecuteResult, + ExecuteSimulation, + BridgeAndExecuteSimulationResult, + SwapResult, + SupportedChainsResult, + ExactInSwapInput, + ExactOutSwapInput, + OnEventParam, + BridgeMaxResult, + OnSwapIntentHook, + BeforeExecuteHook, +} from '../commons'; +import { logger } from '../commons'; +import { CA } from './ca-base'; +// import { AdapterProps } from '@tronweb3/tronwallet-abstract-adapter'; + +export class NexusSDK extends CA { + public readonly utils: NexusUtils; + + constructor(config?: { network?: NexusNetwork; debug?: boolean; siweChain?: number }) { + super(config); + logger.debug('Nexus SDK initialized with config:', config); + this.utils = new NexusUtils(this.chainList); + } + + /** + * Initialize the SDK with a provider + * @param provider Ethereum provider + * @throws NexusError if the initialize fails + * @returns Promise resolving to void + */ + public async initialize(provider: EthereumProvider): Promise { + await this._setEVMProvider(provider); + await this._init(); + } + + /** + * Returns unified balance for tokens across all chains + * @deprecated use `getBalancesForBridge` for direct replacement. + * @returns unified balances across all chains + */ + public async getUnifiedBalances(includeSwappableBalances = false): Promise { + return this._getUnifiedBalances(includeSwappableBalances); + } + + /** + * Bridge to destination chain from auto-selected sources or provided source chains + * @param params bridge parameters + * @param options event parameters + * @throws NexusError if the bridge fails + * @returns bridge result with explorer URL + */ + public async bridge(params: BridgeParams, options?: OnEventParam): Promise { + const result = await this._createBridgeHandler(params, options).execute(); + return { + explorerUrl: result.explorerURL ?? '', + }; + } + + /** + * Calculates the maximum amount that can be bridged for a given token and destination chain + * @param params + * @returns + */ + public async calculateMaxForBridge( + params: Omit, + ): Promise { + return this._calculateMaxForBridge(params); + } + + /** + * Bridge & transfer to an address (Attribution) + * @param params transfer parameters + * @param options event parameters + * @throws NexusError if the bridge and transfer fails + * @returns transfer result with transaction hash and explorer URL + */ + public async bridgeAndTransfer( + params: TransferParams, + options?: OnEventParam, + ): Promise { + const result = await this._bridgeAndTransfer(params, options); + return { + transactionHash: result.executeTransactionHash, + explorerUrl: result.executeExplorerUrl, + }; + } + + /** + * Swap with exact in + * Useful when trying to swap with fixed sources and destination + * @param input swap input + * @param options event parameters + * @throws NexusError if the swap fails + * @returns swap result with success flag and result + */ + public async swapWithExactIn( + input: ExactInSwapInput, + options?: OnEventParam, + ): Promise { + const result = await this._swapWithExactIn(input, options); + return { + success: true, + result, + }; + } + + /** + * Swap with exact out + * Useful when trying to swap with a fixed destination. + * Sources are calculated automatically. + * @param input swap input + * @param options event parameters + * @throws NexusError if the swap fails + * @returns swap result with success flag and result + */ + public async swapWithExactOut( + input: ExactOutSwapInput, + options?: OnEventParam, + ): Promise { + const result = await this._swapWithExactOut(input, options); + return { + success: true, + result, + }; + } + + /** + * Simulate bridge transaction to get costs and fees + * @param params bridge parameters + * @returns simulation result with gas estimates + */ + public async simulateBridge(params: BridgeParams): Promise { + return this._createBridgeHandler(params).simulate(); + } + + /** + * Simulate bridge + transfer transaction to get costs and fees + * @param params transfer parameters + * @returns simulation result with gas estimates + */ + public async simulateBridgeAndTransfer( + params: TransferParams, + ): Promise { + return this._simulateBridgeAndTransfer(params); + } + + /** + * Get user's past intents with pagination + * @param page page number + * @returns list of intents + */ + public async getMyIntents(page: number = 1): Promise { + return this._getMyIntents(page); + } + + /** + * Set callback for intent status updates + * Useful for capturing intent and displaying information to the user + * Once set up, data will be automatically emitted, can be stored in a state or a variable for further use. + * @param callback intent status update callback + */ + public setOnIntentHook(callback: OnIntentHook): void { + this._setOnIntentHook(callback); + } + + /** + * Set callback for swap intent details + * Useful for capturing swap intent and displaying information to the user + * Once set up, data will be automatically emitted, can be stored in a state or a variable for further use. + * @param callback swap intent details callback + */ + public setOnSwapIntentHook(callback: OnSwapIntentHook): void { + this._setOnSwapIntentHook(callback); + } + + // public addTron(adapter: AdapterProps) { + // this._setTronAdapter(adapter); + // } + + /** + * Set callback for allowance approval events + * Useful for capturing allowance approval and displaying information to the user + * Once set up, data will be automatically emitted, can be stored in a state or a variable for further use. + * @param callback allowance approval event callback + */ + public setOnAllowanceHook(callback: OnAllowanceHook): void { + this._setOnAllowanceHook(callback); + } + + /** + * Deinitialize the SDK + * @returns Promise resolving to void + */ + public async deinit(): Promise { + return this._deinit(); + } + + /** + * Standalone function to execute funds into a smart contract + * @param params execute parameters including contract details and transaction settings + * @param options event parameters + * @throws NexusError if the execute fails + * @returns Promise resolving to execute result with transaction hash and explorer URL + */ + public async execute(params: ExecuteParams, options?: OnEventParam): Promise { + return this._execute(params, options); + } + + /** + * Simulate a standalone execute to estimate gas costs and validate parameters + * @param params execute parameters for simulation + * @throws NexusError if the simulate execute fails + * @returns Promise resolving to simulation result with gas estimates + */ + public async simulateExecute(params: ExecuteParams): Promise { + return this._simulateExecute(params); + } + + /** + * Bridge and execute function + * Starts with an optional bridge transaction if user doesn't have enough funds on the destination chain. + * Then executes the contract call on the destination chain. + * @param params bridge and execute parameters + * @param options event parameters + * @throws NexusError if the bridge and execute fails + * @returns Promise resolving to comprehensive operation result + */ + public async bridgeAndExecute( + params: BridgeAndExecuteParams, + options?: OnEventParam & BeforeExecuteHook, + ): Promise { + return this._bridgeAndExecute(params, options); + } + + /** + * Simulate bridge and execute operation using bridge output amounts for realistic execute cost estimation + * This method provides more accurate gas estimates by using the actual amount that will be + * received on the destination chain after bridging (accounting for fees, slippage, etc.) + * Includes detailed step-by-step breakdown with approval handling. + * @param params bridge and execute parameters + * @throws NexusError if the simulate bridge and execute fails + * @returns Promise resolving to simulation result with gas estimates + */ + public async simulateBridgeAndExecute( + params: BridgeAndExecuteParams, + ): Promise { + return this._simulateBridgeAndExecute(params); + } + + /** + * Tokens returned here should be used in `input` for exact in swap + * @throws NexusError if the get balances for swap fails + * @returns balances that can be used in swap operations + */ + public async getBalancesForSwap() { + const result = await this._getBalancesForSwap(); + + return result.assets; + } + + /** + * Tokens returned here should be used in bridge, bridgeAndTransfer and bridgeAndExecute operations + * @throws NexusError if the get balances for bridge fails + * @returns balances that can be used in bridge operations + */ + public getBalancesForBridge() { + return this._getUnifiedBalances(false); + } + + /** + * Get list of chains where swap is supported + * @returns list of chains where swap is supported + */ + public getSwapSupportedChains(): SupportedChainsResult { + return this._getSwapSupportedChains(); + } + + public isInitialized() { + return this._isInitialized(); + } + + /** + * Helper function to convert an input like "1.13" to 1_130_000n for input to other functions + * Number of decimals for a token depends on the chain. + * ex: USDC on BNB chain has 18 decimals and 6 decimals on most other chains. + */ + public convertTokenReadableAmountToBigInt = this._convertTokenReadableAmountToBigInt; +} diff --git a/src/sdk/utils.ts b/src/sdk/utils.ts new file mode 100644 index 00000000..77a3b551 --- /dev/null +++ b/src/sdk/utils.ts @@ -0,0 +1,94 @@ +import { + type SUPPORTED_CHAINS, + truncateAddress as utilTruncateAddress, + SupportedChainsResult, + Network, + ChainListType, + formatTokenBalance, + formatTokenBalanceParts, + SupportedChainsAndTokensResult, +} from '../commons'; +import { getCoinbasePrices, getSupportedChains } from './ca-base/utils'; +import { getSwapSupportedChains } from './ca-base/swap/utils'; +import { formatUnits, isAddress, parseUnits } from 'viem'; + +export class NexusUtils { + constructor(private readonly chainList: ChainListType) {} + formatTokenBalance = formatTokenBalance; + formatTokenBalanceParts = formatTokenBalanceParts; + /** + * Parse a value from the smallest unit to the base unit + * @param value - The value to parse + * @param decimals - The number of decimals to parse + * @returns The parsed value + */ + parseUnits = parseUnits; + /** + * Format a value from the base unit to the smallest unit + * @param value - The value to format + * @param decimals - The number of decimals to format + * @returns The formatted value + */ + formatUnits = formatUnits; + /** + * Check if the address is valid + * @param address - The address to check + * @returns boolean + */ + isValidAddress = isAddress; + /** + * Truncate an address + * @param address - The address to truncate + * @param startLength - The number of characters to keep from the start + * @param endLength - The number of characters to keep from the end + * @returns The truncated address + * Examples: + * - 0x1234567890123456789012345678901234567890 -> "0x123456...7890" + */ + truncateAddress = utilTruncateAddress; + + /** + * Get the coinbase rates for the supported tokens + * @returns Record + */ + getCoinbaseRates = async (): Promise> => { + return getCoinbasePrices(); + }; + + /** + * Get the supported chains and tokens for the network + * @param env - The network to get the supported chains and tokens for + * @returns SupportedChainsAndTokensResult + */ + getSupportedChains(env?: Network): SupportedChainsAndTokensResult { + return getSupportedChains(env); + } + + /** + * Get the supported chains and tokens for the network + * @returns SupportedChainsResult + */ + getSwapSupportedChainsAndTokens(): SupportedChainsResult { + return getSwapSupportedChains(this.chainList); + } + + /** + * Check if the chain is supported + * @param chainId - The chain ID to check + * @returns boolean + */ + + isSupportedChain(chainId: (typeof SUPPORTED_CHAINS)[keyof typeof SUPPORTED_CHAINS]): boolean { + return !!this.chainList.getChainByID(chainId); + } + + /** + * Check if the token is supported + * @param token - The token to check + * @returns boolean + */ + isSupportedToken(token: string): boolean { + const supportedTokens = ['ETH', 'USDC', 'USDT']; + return supportedTokens.includes(token.toUpperCase()); + } +} diff --git a/tsconfig.json b/tsconfig.json index 95dd3671..29316c44 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,8 @@ { "compilerOptions": { - "target": "es2020", + "target": "es2024", "module": "esnext", "lib": ["dom", "esnext"], - "importHelpers": true, "declaration": true, "sourceMap": true, "strict": true, @@ -16,15 +15,7 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "allowSyntheticDefaultImports": true, - "resolveJsonModule": true, - "baseUrl": ".", - "paths": { - "@avail-project/nexus-core": ["packages/core/index.ts"], - "@avail-project/nexus-core/*": ["packages/core/*"], - "@nexus/commons": ["packages/commons/index.ts"], - "@nexus/commons/*": ["packages/commons/*"] - } + "resolveJsonModule": true }, - "include": ["packages/**/*"], - "exclude": ["node_modules", "**/dist", "**/node_modules"] + "include": ["src/**/*"] } diff --git a/typedoc_html.json b/typedoc_html.json new file mode 100644 index 00000000..c69ec8c4 --- /dev/null +++ b/typedoc_html.json @@ -0,0 +1,12 @@ +{ + "entryPoints": ["src/index.ts"], + "entryPointStrategy": "expand", + "tsconfig": "tsconfig.json", + "name": "Nexus core SDK Reference", + "out": "html_docs", + "plugin": [], + "theme": "default", + "excludePrivate": true, + "excludeProtected": false, + "includeVersion": true +}