diff --git a/.agents/swap.md b/.agents/swap.md new file mode 100644 index 000000000..99c650c83 --- /dev/null +++ b/.agents/swap.md @@ -0,0 +1,148 @@ +# Transfer/Swap Feature + +Cross-chain transfer system enabling asset swaps between different networks via multiple providers. Multi-provider aggregation with best-rate selection. + +## Architecture + +### TransferServiceManager (`shared/services/transfer-service-manager.ts`) +Wraps N `ITransferService` implementations behind a single `ITransferService` interface. Zero UI changes needed when adding/removing providers. + +- `getAvailableAssets()` — union of all services' assets, deduplicated +- `getSupportedPairs()` — union of all services' pairs, deduplicated +- `getQuote()` — queries ALL candidate services in parallel (`Promise.allSettled`), picks best rate, tags with `serviceName`. Collects `serviceErrors` for partial/total failures. 5s timeout per provider. +- `executeTransfer()` — routes to correct service via `quote.serviceName` +- `getOngoingTransfers()` — aggregates from all services, sorted by `createdAt` desc + +Singleton via `useTransferService(storage)` hook (`shared/hooks/useTransferService.ts`). + +### ITransferService Interface (`shared/types/transfer.ts`) +``` +readonly name: string +getSupportedPairs(): TransferPair[] +getQuote(sendAsset, receiveAsset, amount): TransferQuote +executeTransfer(quote, accountNumber, settleAddress, fromAddress?): TransferExecution +commitTransfer(execution): void +getTimelineSteps(execution): TimelineStep[] +getOngoingTransfers(accountNumber): TransferExecution[] +// Optional: +getPairInfo?(send, receive): TransferPairInfo (min/max) +refreshTransferStatus?(executionId, accountNumber): TransferExecution +getTrackingUrl?(execution): string | undefined +``` + +### Key Types (`shared/types/transfer.ts`, `shared/types/asset.ts`) +- **AssetId** — strict union: `native:bitcoin`, `token:spark:usdb`, etc. +- **AssetInfo** — resolved metadata: network, ticker, decimals, tokenId +- **TransferQuote** — quote with `serviceName`, `serviceErrors?` +- **TransferExecution** — persisted transfer state with `depositAddress`, `depositTxid`, `confirmations`, `claimSwapJson`, `providerId`. Three variants: `DepositAddressExecution`, `NativeClaimExecution`, `InstantSwapExecution` +- **NativeClaimExecution** — extends base with `claimTxid`, `receiveTransferId`, `autoClaim`, `autoClaimAttempts`, `autoClaimError`, `lastAutoClaimAt`, `claimSwapJson` +- **TransferStatus** — `waiting | pending | confirming | claimable | completed | failed | refunded | expired` +- **getRelatedTxids(exec)** — collects `depositTxid` + `claimTxid` + `receiveTransferId` for tx history deduplication + +## Providers + +### SideShift (`shared/services/transfer-service-sideshift.ts`) +- **Pairs**: BTC, Liquid BTC, Liquid USDT, Rootstock RBTC, Stacks STX — all cross-pairs +- **Model**: Fixed quotes only. Deposit address flow. 15-min quote expiry. +- **API**: `shared/services/sideshift-api.ts` — `sideshift.ai/api/v2` +- **Mappings**: `shared/services/sideshift-mappings.ts` +- **Fee**: Real spread `(depositAmount * rate) - settleAmount` +- **Tracking**: `sideshift.ai/orders/{providerId}` +- Affiliate ID: `uYB9AagC9` + +### Garden Finance (`shared/services/transfer-service-garden.ts`) +- **Pairs**: BTC → Botanix only (reverse requires EVM tx signing — deferred) +- **Model**: Atomic swap deposit. Requires `fromAddress` for HTLC refund. +- **API**: `shared/services/garden-api.ts` — `api.garden.finance/v2`, auth via `garden-app-id` header +- **Mappings**: `shared/services/garden-mappings.ts` +- **Decimals**: bitcoin=8, botanix=18 (BigNumber.js conversion) +- **Tracking**: `explorer.garden.finance/order/{providerId}` +- Conditional on `EXPO_PUBLIC_GARDEN_APP_ID` env var + +### Symbiosis (`shared/services/transfer-service-symbiosis.ts`) +- **Pairs**: BTC → Rootstock (working), BTC → Citrea (registered, no route yet) +- **Model**: Combined quote+execute API (`/v1/swap`). Deposit address with expiration. +- **API**: `shared/services/symbiosis-api.ts` — `api.symbiosis.finance/crosschain`, no auth +- **Chain IDs**: Bitcoin=3652501241, Rootstock=30, Citrea=4114 +- **Tracking**: `explorer.symbiosis.finance/transactions/bitcoin/{txHash}` + +### Flashnet AMM (`shared/services/transfer-service-flashnet.ts`) +- **Pairs**: BTC <-> USDB on Spark (both directions) +- **Model**: Instant atomic swap via `@flashnet/sdk`. No deposit address. Executes atomically in `executeTransfer()`. +- **API**: `FlashnetClient.simulateSwap()` for quotes, `executeSwap()` for execution +- **SparkWallet access**: `SparkWallet.getSDKWalletForAccount(accountNumber)` static getter +- No tracking URL (instant) + +### NativeDeposit (`shared/services/transfer-service-native-deposit.ts`) +- **Pairs**: BTC → Ark, BTC → Spark +- **Model**: 1:1 quotes. Wallet-driven status via `swapsFetcher`. Boarding/deposit address as deposit. +- **Status flow**: `waiting → confirming → claimable → completed` (or `→ refunded`) +- **Auto-claim**: Monitors claimable transfers and claims automatically when enabled: + - `AutoClaimMonitor` component (`mobile/components/AutoClaimMonitor.tsx`) — renderless, mounts in root provider tree. Defers startup 5s + `requestIdleCallback`. Triggers on: app start, app foreground, 60s interval. + - ARK: passive — SDK `VtxoManager` auto-settles boarding UTXOs; executor polls `getSettledBoardingAmount()` and returns result when ready (no manual claim in auto path). Claim toggle hidden for ARK (always auto). + - Spark: active — executor calls `getDepositQuote()` + `claimDepositSpark()` and returns `{ receiveTransferId, creditAmountSats }` + - Retry: max 5 attempts, 5min cooldown. Transient errors (429, timeouts, fetch failures) don't count against limit. +- **Manual claim**: `SwapXArkClaim` screen with serialized CommonSwap (when `autoClaim=false` on Spark) +- No tracking URL + +### Fake (`shared/services/transfer-service-fake.ts`) +- **Pairs**: Liquid Testnet BTC <-> Botanix Testnet BTC +- **Model**: Dev/test stub. Instant completion. Throws error when amount=1. +- Only available in `__DEV__` mode + +## UI Flow (mobile) + +**Entry**: "Transfer" button on Home → `/transfer` + +### Screens (`mobile/app/transfer/`) +1. **`index.tsx`** — Input screen. Bidirectional quote (type in either field). 500ms debounce. Min/max validation via `getPairInfo`. Balance check before confirm (skipped for testnets). Shows `serviceErrors` warnings for partial provider failures. +2. **`select-asset.tsx`** — Asset picker modal. Filters testnet assets via settings. +3. **`confirm.tsx`** — Auto-prepares on mount (`executeTransfer` + `getSendQuote`). Shows rate, fee, est. time, expiry countdown, provider. Single "Confirm" tap. NativeDeposit: uses boarding address, auto/manual claim toggle (hidden for ARK — always auto). Flashnet: no deposit address, instant swap on prepare. +4. **`success.tsx`** — Pull-to-dismiss modal with checkmark animation. + +### Components (`mobile/components/transfer/`) +- `TransferAmountSection.tsx` — send/receive input with fiat toggle +- `TransferAssetIcon.tsx` — colored icon with network badge +- `AssetSelectorPill.tsx` — `[icon] [ticker] [chevron]` or "Select >" +- `AssetListItem.tsx` — row in asset picker +- `OngoingTransferList.tsx` — polls every 10s, shows recent transfers +- `OngoingTransferItem.tsx` — status display with fiat values + +### Detail Screen +- `mobile/app/TransferDetails.tsx` — Timeline from `getTimelineSteps()`. Detail rows: provider, status, transfer ID, addresses, deposit/claim txids. Claim button for NativeDeposit (disabled during auto-claim). "View Online" button when tracking URL available. + +## Shared Hooks +- `useTransferService(storage)` — singleton TransferServiceManager (`shared/hooks/useTransferService.ts`). Also exports: `setNativeDepositSwapsFetcher`, `setNativeDepositClaimExecutor`, `startAutoClaimMonitor`, `stopAutoClaimMonitor`, `processAutoClaimsNow` +- `useTransactionHistory(network, account)` — merges transfers into tx list, deduplicates (`shared/hooks/useTransactionHistory.ts`) +- `useAssetExchangeRate(assetId)` — fiat rate for transfer assets (`shared/hooks/useAssetExchangeRate.ts`) +- `useAssetBalance(assetId, account, bg)` — unified native/token balance (`shared/hooks/useAssetBalance.ts`) + +## Wallet Send Quote API +2-step API for sending on-chain funds to deposit addresses: +- **Types**: `SendQuoteRequest`, `SendQuote` (`shared/types/send-quote.ts`) +- **Interface**: `InterfaceSendQuotable` (`shared/class/wallets/interface-send-quotable.ts`) +- **Implementations**: `EvmWallet`, `BreezWallet` + +## Tests +- `shared/tests/unit-vi/transfer-service-sideshift.test.ts` +- `shared/tests/unit-vi/transfer-service-garden.test.ts` +- `shared/tests/unit-vi/transfer-service-symbiosis.test.ts` +- `shared/tests/unit-vi/transfer-service-flashnet.test.ts` +- `shared/tests/unit-vi/transfer-service-manager.test.ts` +- `shared/tests/unit-vi/transfer-service-native-deposit.test.ts` +- `shared/tests/unit-vi/sideshift-mappings.test.ts` +- `shared/tests/unit-vi/use-transaction-history.test.ts` +- `shared/tests/unit-vi/use-asset-balance.test.ts` +- `shared/tests/integration-vi/sideshift-transfer.test.ts` +- `shared/tests/integration-vi/garden-transfer.test.ts` +- `mobile/.maestro/swap.yml` — e2e flow with Fake service + +## Adding a New Transfer Service + +1. Create `shared/services/transfer-service-{name}.ts` implementing `ITransferService` +2. Create API client `shared/services/{name}-api.ts` if needed +3. Create mappings `shared/services/{name}-mappings.ts` for AssetId conversion +4. Add storage key in `shared/types/IStorage.ts` +5. Register in `shared/hooks/useTransferService.ts` — add to `TransferServiceManager` constructor +6. Add tests in `shared/tests/unit-vi/transfer-service-{name}.test.ts` +7. No UI changes needed — TransferServiceManager handles routing automatically diff --git a/ext/.gitignore b/ext/.gitignore index eefb32e79..4a3954270 100644 --- a/ext/.gitignore +++ b/ext/.gitignore @@ -12,6 +12,9 @@ # zip /zip +# Chrome for Testing (debug tooling) +/chrome + # misc .DS_Store .env.local diff --git a/ext/DEBUG_WITH_MCP.md b/ext/DEBUG_WITH_MCP.md new file mode 100644 index 000000000..034091343 --- /dev/null +++ b/ext/DEBUG_WITH_MCP.md @@ -0,0 +1,152 @@ +# Debugging the Extension with Chrome DevTools MCP + +This guide explains how to set up Chrome DevTools MCP so that Claude Code (or other AI coding agents) can interact with, debug, and take screenshots of the Layerz Wallet Chrome extension. + +## Why Chrome for Testing? + +Chrome 137+ removed `--load-extension` and related flags from branded Chrome builds for security reasons. Extensions cannot be loaded into automated Chrome sessions. **Chrome for Testing** is a dedicated build that retains all automation-friendly flags and is the recommended solution. + +## Prerequisites + +- Node.js v20.19+ +- npm +- Claude Code (or another MCP-compatible client) + +## Step 1: Install Chrome for Testing + +From the `ext/` directory: + +```bash +cd ext +npx @puppeteer/browsers install chrome@stable +``` + +This downloads Chrome for Testing into `ext/chrome/`. The binary path will be printed, e.g.: + +``` +chrome@145.0.7632.46 /Users//z/layerzwallet/ext/chrome/mac_arm-145.0.7632.46/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing +``` + +Save this path for the next step. + +> **Note:** The `ext/chrome/` directory is gitignored. + +## Step 2: Build the Extension + +Make sure you have a fresh build: + +```bash +cd ext +npm start # dev server (watches for changes) +# or +npm run build # one-time production build +``` + +The built extension lives in `ext/build/`. + +## Step 3: Configure Chrome DevTools MCP + +Add the MCP server to Claude Code (user-scoped so it works across projects): + +```bash +claude mcp add chrome-devtools --scope user -- npx chrome-devtools-mcp@latest \ + --executable-path="/Users//z/layerzwallet/ext/chrome/mac_arm-/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing" \ + --chrome-arg=--enable-unsafe-extension-debugging \ + --chrome-arg=--disable-extensions-except=/Users//z/layerzwallet/ext/build \ + --chrome-arg=--load-extension=/Users//z/layerzwallet/ext/build \ + --chrome-arg=--window-size=400,600 +``` + +Replace `` and `` with your actual username and Chrome for Testing version. + +Or manually edit `~/.claude.json` and add to the `mcpServers` section: + +```json +{ + "mcpServers": { + "chrome-devtools": { + "type": "stdio", + "command": "npx", + "args": [ + "chrome-devtools-mcp@latest", + "--executable-path=/Users//z/layerzwallet/ext/chrome/mac_arm-/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing", + "--chrome-arg=--enable-unsafe-extension-debugging", + "--chrome-arg=--disable-extensions-except=/Users//z/layerzwallet/ext/build", + "--chrome-arg=--load-extension=/Users//z/layerzwallet/ext/build", + "--chrome-arg=--window-size=400,600" + ], + "env": {} + } + } +} +``` + +### Config Explained + +| Flag | Purpose | +|------|---------| +| `--executable-path` | Points to Chrome for Testing binary instead of branded Chrome | +| `--enable-unsafe-extension-debugging` | Re-enables extension loading in automated Chrome | +| `--disable-extensions-except` | Whitelists only the Layerz Wallet extension | +| `--load-extension` | Auto-loads the extension from `ext/build/` on startup | +| `--window-size=400,600` | Sets viewport to approximate the extension popup size | + +## Step 4: Restart Claude Code + +After changing the MCP config, restart Claude Code for the changes to take effect. + +## Step 5: Use It + +Once configured, Claude Code can: + +- **Navigate** to the extension popup: `chrome-extension://jfkjdddajnobopldmhfpgblcidgohkak/popup.html` +- **Take screenshots** of the extension UI +- **Read console errors** and network requests +- **Execute JavaScript** in the extension context +- **Click buttons**, fill forms, and interact with the UI +- **Record performance traces** + +### Example Prompts + +``` +Open the Layerz Wallet extension and take a screenshot +``` + +``` +Check for console errors in the extension +``` + +``` +Navigate to the send screen and inspect the network requests +``` + +## Troubleshooting + +### Extension not loading (list empty on chrome://extensions) + +- Make sure you're using **Chrome for Testing**, not branded Chrome +- Verify `ext/build/` exists and contains `manifest.json` and `popup.html` +- Each `--chrome-arg` must be a **separate** array entry (don't combine multiple flags into one string) + +### `--chrome-arg` not working + +Test with a simple visual flag like `--chrome-arg=--window-size=400,600`. If the window size doesn't change, the args aren't being passed. + +### Extension ID different than expected + +When loading an unpacked extension, Chrome assigns the ID based on the extension's path. The ID `jfkjdddajnobopldmhfpgblcidgohkak` is stable as long as the `ext/build/` path stays the same. If the ID changes, navigate to `chrome://extensions` to find the new one. + +### Branded Chrome won't load extensions + +This is expected on Chrome 137+. Use Chrome for Testing as described above. See the [Chromium RFC](https://groups.google.com/a/chromium.org/g/chromium-extensions/c/aEHdhDZ-V0E) for background. + +### Updating Chrome for Testing + +To update to a newer version: + +```bash +cd ext +npx @puppeteer/browsers install chrome@stable +``` + +Then update the `--executable-path` in your MCP config to point to the new version. diff --git a/ext/package-lock.json b/ext/package-lock.json index fbc9b10e2..f7cd49175 100644 --- a/ext/package-lock.json +++ b/ext/package-lock.json @@ -23,6 +23,7 @@ "@stacks/blockchain-api-client": "8.13.6", "@stacks/transactions": "7.3.1", "@stacks/wallet-sdk": "7.2.0", + "@utexo/rgb-sdk": "1.0.4", "assert": "2.1.0", "bignumber.js": "9.3.1", "bip21": "3.0.0", @@ -210,7 +211,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -856,7 +856,6 @@ "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1782,7 +1781,6 @@ "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", @@ -2274,6 +2272,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@bitcoindevkit/bdk-wallet-node": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@bitcoindevkit/bdk-wallet-node/-/bdk-wallet-node-0.2.0.tgz", + "integrity": "sha512-YYYAjKzCiFw766dGfFbc/JeOwDbCFFc5PH6bvVcI6dcpdVShs8KYY0M4UPhy7iAjdjB3XzyLCW5TLTMNPrMeZA==", + "license": "MIT OR Apache-2.0" + }, + "node_modules/@bitcoindevkit/bdk-wallet-web": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@bitcoindevkit/bdk-wallet-web/-/bdk-wallet-web-0.2.0.tgz", + "integrity": "sha512-aa6AOJeYMpGakrA/a2JKMkIp6YF87RDILmFJ6CMHNq3zYKVZKkP6MQacyO7unMgME3uhxwHM9Fd97rvmY5XVSg==", + "license": "MIT OR Apache-2.0" + }, "node_modules/@bitcoinerlab/secp256k1": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@bitcoinerlab/secp256k1/-/secp256k1-1.2.0.tgz", @@ -3421,6 +3431,115 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -4452,12 +4571,99 @@ "node": ">= 8" } }, + "node_modules/@npmcli/agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", + "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "license": "ISC", + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "optional": true + }, + "node_modules/@npmcli/fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", + "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "license": "ISC", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "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==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -5102,6 +5308,16 @@ "node": ">=20.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", @@ -6428,7 +6644,6 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -6642,7 +6857,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -6681,7 +6895,6 @@ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -6894,7 +7107,6 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -7204,114 +7416,457 @@ "dev": true, "license": "ISC" }, - "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, + "node_modules/@utexo/rgb-lib": { + "version": "0.3.0-beta.8", + "resolved": "https://registry.npmjs.org/@utexo/rgb-lib/-/rgb-lib-0.3.0-beta.8.tgz", + "integrity": "sha512-pCSaBkvQCCkvMjVDano6Dfg+j6Zf807rR+oc043Tss0YJA4zuz7b+K8aplCXNDtoMD4fGK3xQldjrzGveDmD9A==", "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "optionalDependencies": { + "@utexo/rgb-lib-darwin-arm64": "0.3.0-beta.8", + "@utexo/rgb-lib-linux-arm64": "0.3.0-beta.8", + "@utexo/rgb-lib-linux-x64": "0.3.0-beta.8" } }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, + "node_modules/@utexo/rgb-lib-darwin-arm64": { + "version": "0.3.0-beta.8", + "resolved": "https://registry.npmjs.org/@utexo/rgb-lib-darwin-arm64/-/rgb-lib-darwin-arm64-0.3.0-beta.8.tgz", + "integrity": "sha512-m9ziiH4p9mrgfBT7vEW2B5KM2tSVdRQs0inmvupXp7oBj6moNAGCeOwFFw0jx07rXQ8T0gsWjuojVCPxp+TM9g==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "dependencies": { - "@types/estree": "^1.0.0" + "big.js": "^6.2.2", + "node-addon-api": "^8.3.0", + "node-gyp": "^11.0.0" } }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "dev": true, + "node_modules/@utexo/rgb-lib-darwin-arm64/node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" + "optional": true, + "engines": { + "node": "*" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/bigjs" } }, - "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "dev": true, + "node_modules/@utexo/rgb-lib-darwin-arm64/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", "license": "MIT", - "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" } }, - "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "dev": true, + "node_modules/@utexo/rgb-lib-linux-arm64": { + "version": "0.3.0-beta.8", + "resolved": "https://registry.npmjs.org/@utexo/rgb-lib-linux-arm64/-/rgb-lib-linux-arm64-0.3.0-beta.8.tgz", + "integrity": "sha512-g/oDjqbZqqhl59TxdKOwyl0vwNCswioXh/spJ+WZCiWP7xi4aH0A4i3uRqFkHQ/QGuRMnLz50LGMreheZNhgUw==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "big.js": "^6.2.2", + "node-addon-api": "^8.3.0", + "node-gyp": "^11.0.0" } }, - "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "dev": true, + "node_modules/@utexo/rgb-lib-linux-arm64/node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" + "optional": true, + "engines": { + "node": "*" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, + "node_modules/@utexo/rgb-lib-linux-arm64/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/@utexo/rgb-lib-linux-x64": { + "version": "0.3.0-beta.8", + "resolved": "https://registry.npmjs.org/@utexo/rgb-lib-linux-x64/-/rgb-lib-linux-x64-0.3.0-beta.8.tgz", + "integrity": "sha512-9v0F/bcwd3dHyPVloTE1ayJbkc+zSTlyWTK2hakEM62ReoxJmHUaRhAuP5p+1vzP07y+N4zAm5tO9lYfj7BNMA==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "dependencies": { + "big.js": "^6.2.2", + "node-addon-api": "^8.3.0", + "node-gyp": "^11.0.0" + } + }, + "node_modules/@utexo/rgb-lib-linux-x64/node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } + }, + "node_modules/@utexo/rgb-lib-linux-x64/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/@utexo/rgb-sdk": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@utexo/rgb-sdk/-/rgb-sdk-1.0.4.tgz", + "integrity": "sha512-QA+tx7qWHHY0EE2XYB/fgqHGEwwiZH7mtBqzbZIEEME0qujeZ8XHRbdg8NK4SJyJ0DAfe7tFGH57uPSN6QfTEw==", + "license": "MIT", + "dependencies": { + "@bitcoindevkit/bdk-wallet-node": "^0.2.0", + "@bitcoindevkit/bdk-wallet-web": "^0.2.0", + "@bitcoinerlab/secp256k1": "^1.2.0", + "@noble/hashes": "^2.0.1", + "@utexo/rgb-lib": "0.3.0-beta.8", + "axios": "^1.8.4", + "bare-node-runtime": "^1.1.4", + "bip32": "^5.0.0-rc.0", + "bip39": "^3.1.0", + "bitcoinjs-lib": "^6.1.7", + "form-data": "^4.0.0", + "ripemd160": "^2.0.3" + }, + "optionalDependencies": { + "@utexo/rgb-lib-darwin-arm64": "0.3.0-beta.8", + "@utexo/rgb-lib-linux-arm64": "0.3.0-beta.8", + "@utexo/rgb-lib-linux-x64": "0.3.0-beta.8" + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@utexo/rgb-sdk/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/@utexo/rgb-sdk/node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/@utexo/rgb-sdk/node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/bip32": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-5.0.0.tgz", + "integrity": "sha512-h043yQ9n3iU4WZ8KLRpEECMl3j1yx2DQ1kcPlzWg8VZC0PtukbDiyLDKbe6Jm79mL6Tfg+WFuZMYxnzVyr/Hyw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "@scure/base": "^1.1.1", + "uint8array-tools": "^0.0.8", + "valibot": "^0.37.0", + "wif": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/bip32/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/@utexo/rgb-sdk/node_modules/bitcoinjs-lib": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.7.tgz", + "integrity": "sha512-tlf/r2DGMbF7ky1MgUqXHzypYHakkEnm0SZP23CJKIqNY/5uNAnMbFhMJdhjrL/7anfb/U8+AlpdjPWjPnAalg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/bitcoinjs-lib/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/@utexo/rgb-sdk/node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/bs58check/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/@utexo/rgb-sdk/node_modules/uint8array-tools": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz", + "integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/valibot": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.37.0.tgz", + "integrity": "sha512-FQz52I8RXgFgOHym3XHYSREbNtkgSjF9prvMFH1nBsRyfL6SfCzoT1GuSDTlbsuPubM7/6Kbw0ZMQb8A+V+VsQ==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/wif": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/wif/-/wif-5.0.0.tgz", + "integrity": "sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==", + "license": "MIT", + "dependencies": { + "bs58check": "^4.0.0" + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/wif/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/@utexo/rgb-sdk/node_modules/wif/node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/@utexo/rgb-sdk/node_modules/wif/node_modules/bs58check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-4.0.0.tgz", + "integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^6.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { @@ -7633,6 +8188,16 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/abort-controller-x": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.3.tgz", @@ -7674,7 +8239,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7763,7 +8327,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8220,7 +8783,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/available-typed-arrays": { @@ -8248,6 +8810,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -8630,9 +9203,120 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/bare-abort-controller": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bare-abort-controller/-/bare-abort-controller-1.0.0.tgz", + "integrity": "sha512-RSUPsS16TC0EfqfjZUlQwLAtFCUUl7NA0CYaE4/Sl9ExFx3q0WIogBHmW5Zn16NRZUHDJqEffmtnvIyw6LFUjA==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/bare-addon-resolve": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.9.7.tgz", + "integrity": "sha512-cIo93Y4Maw8ZNrfq5qlvRBqCHUyeSzMyFBqJRNi91SOspeWiFq3ixiq0ExYWmtOBauIkCLod2Mng73xr7fmkXA==", + "license": "Apache-2.0", + "dependencies": { + "bare-module-resolve": "^1.10.0", + "bare-semver": "^1.0.0" + }, + "peerDependencies": { + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-ansi-escapes": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", + "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-stream": "^2.6.5" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-assert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", + "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-inspect": "^3.1.2" + } + }, + "node_modules/bare-async-hooks": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/bare-async-hooks/-/bare-async-hooks-0.0.0.tgz", + "integrity": "sha512-xNfGwUobaomCGMGAqohAekS3uMCj+4tvI4AoOaJnO7NfpN+dvFdkC5xkeQtmZzs2vxf2TR5J6i5FDd1ImCZERw==", + "license": "Apache-2.0" + }, + "node_modules/bare-buffer": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.4.2.tgz", + "integrity": "sha512-e3s+hh+7KsEPA9Gg1q0e6vkvMaO+vyARkDrU/LPfojg4Q2G5wj+fjr8YDFELibSokECMhJmzhJMRwPRKVqPh+g==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.20.0" + } + }, + "node_modules/bare-bundle": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", + "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-buffer": "*", + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-channel": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bare-channel/-/bare-channel-5.2.2.tgz", + "integrity": "sha512-lM5nPZKV4mWB0djziPr+FaLeoaMmNeUjlbtRZ84ad0FbZEWjlrEIj3ShufHx5xXMXgot0GZk+iLtubCYZVoSNQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.0.0", + "bare-stream": "^2.7.0", + "bare-structured-clone": "^1.4.0" + }, + "engines": { + "bare": ">=1.7.0" + } + }, + "node_modules/bare-console": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/bare-console/-/bare-console-6.1.0.tgz", + "integrity": "sha512-BziGTEIyVh0zn78842mmpOLGTEFE7/S4A5Tb0d2iIu7bryVdoiF0przraYMetJckHxm0P5ITTZrYfRuYRr9n/Q==", + "license": "Apache-2.0", + "dependencies": { + "bare-hrtime": "^2.0.0", + "bare-logger": "^2.0.0", + "bare-system-logger": "^1.0.2" + } + }, "node_modules/bare-crypto": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz", @@ -8650,6 +9334,31 @@ } } }, + "node_modules/bare-debug-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", + "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-dgram": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bare-dgram/-/bare-dgram-1.0.1.tgz", + "integrity": "sha512-EdsyRErrkWgN8fENdrDdXFEE9HAuJ/m6ehXz13fVj9JhdCaLWIA+L8o5aYNRLt66x08RlyG2vbrRAZoxGfcdlg==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.0", + "udx-native": "^1.11.2" + } + }, + "node_modules/bare-diagnostics-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bare-diagnostics-channel/-/bare-diagnostics-channel-1.1.0.tgz", + "integrity": "sha512-Reu+EQo+eLpB+a5p8UykFEdXndFaRaSgKV38uAMh/qhE2eTeJcdwAwo74hKYyeN8GD3DIFqC9ZlM4bnDc03CIg==", + "license": "Apache-2.0" + }, "node_modules/bare-dns": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", @@ -8659,6 +9368,29 @@ "bare": ">=1.7.0" } }, + "node_modules/bare-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.0.tgz", + "integrity": "sha512-9T5CSCaytaIWZpFWx9LQLJ6/z/m2Slnan9tQBKmOvoq/UtPBbOKT/B2fo29Xhi4X1FFtNx8DFdtrFgqm2yse/Q==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-env": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", + "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, "node_modules/bare-events": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", @@ -8707,22 +9439,199 @@ "bare-stream": "^2.6.5" } }, + "node_modules/bare-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.1.tgz", + "integrity": "sha512-1oS+LZrWK6tnYnvNSHDGljc2MPunRxwhpFriuCgzNF+oklrnwmBKD91tS0yt+jpl2j3UgcSDzBIMiVTvLs9A8w==", + "license": "Apache-2.0", + "dependencies": { + "bare-inspect": "^3.0.0" + } + }, + "node_modules/bare-fs": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.3.tgz", + "integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-hrtime": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", + "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", + "license": "Apache-2.0" + }, "node_modules/bare-http-parser": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz", "integrity": "sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==", "license": "Apache-2.0" }, - "node_modules/bare-http1": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.2.tgz", - "integrity": "sha512-XL1aeSSjKNIIjyo5czdWZb7C1fVWiL7Y0CPLLgKy6fWMOXZksLY84QjRmvKTAfRN2beNQuIexccCWknI8sStNg==", + "node_modules/bare-http1": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.2.tgz", + "integrity": "sha512-XL1aeSSjKNIIjyo5czdWZb7C1fVWiL7Y0CPLLgKy6fWMOXZksLY84QjRmvKTAfRN2beNQuIexccCWknI8sStNg==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.6.0", + "bare-http-parser": "^1.0.0", + "bare-stream": "^2.3.0", + "bare-tcp": "^2.2.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-https": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", + "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", + "license": "Apache-2.0", + "dependencies": { + "bare-http1": "^4.0.0", + "bare-tcp": "^2.2.0", + "bare-tls": "^2.0.0" + } + }, + "node_modules/bare-inspect": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", + "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", + "license": "Apache-2.0", + "dependencies": { + "bare-ansi-escapes": "^2.1.0", + "bare-type": "^1.0.0" + }, + "engines": { + "bare": ">=1.18.0" + } + }, + "node_modules/bare-inspector": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-5.0.0.tgz", + "integrity": "sha512-blObRFPBK6yn0/wZbrRgkGcOuLUQUYVvkKbsEp5p3bWJB+uNrAh+qUqOijQHS6mZjGvyGCE86VERp3EiUR2TyA==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.1.0", + "bare-http1": "^4.0.0", + "bare-stream": "^2.0.0", + "bare-url": "^2.0.0", + "bare-ws": "^2.0.0" + }, + "engines": { + "bare": ">=1.2.0" + }, + "peerDependencies": { + "bare-tcp": "*" + }, + "peerDependenciesMeta": { + "bare-tcp": { + "optional": true + } + } + }, + "node_modules/bare-logger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bare-logger/-/bare-logger-2.0.0.tgz", + "integrity": "sha512-Oc/jYOFVXnNtIp+BLttGBnOmJU0ved1/R2uUZZNW6VXXP0f6N5v5+tc2sJan7p21VMDPvKCHV+CyUO3QL9lkaA==", + "license": "Apache-2.0", + "dependencies": { + "bare-format": "^1.0.0" + } + }, + "node_modules/bare-module": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", + "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", + "license": "Apache-2.0", + "dependencies": { + "bare-bundle": "^1.3.0", + "bare-module-lexer": "^1.0.0", + "bare-module-resolve": "^1.8.0", + "bare-path": "^3.0.0", + "bare-url": "^2.0.1" + }, + "engines": { + "bare": ">=1.23.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-module-lexer": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", + "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", + "license": "Apache-2.0", + "dependencies": { + "require-addon": "^1.0.2" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-module-resolve": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", + "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", + "license": "Apache-2.0", + "dependencies": { + "bare-semver": "^1.0.0" + }, + "peerDependencies": { + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-module-traverse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bare-module-traverse/-/bare-module-traverse-2.0.1.tgz", + "integrity": "sha512-1au+Og5p97T9b6Y7xmHZ7KtpW8vEYtz2jC2whmm+YJp46EaHfk26j91MmQhufdkR/8sdK1Q5p+P9A/Y5GrJg7Q==", "license": "Apache-2.0", "dependencies": { - "bare-events": "^2.6.0", - "bare-http-parser": "^1.0.0", - "bare-stream": "^2.3.0", - "bare-tcp": "^2.2.0" + "bare-addon-resolve": "^1.5.0", + "bare-module-lexer": "^1.4.0", + "bare-module-resolve": "^1.7.0" }, "peerDependencies": { "bare-buffer": "*", @@ -8737,17 +9646,6 @@ } } }, - "node_modules/bare-https": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz", - "integrity": "sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==", - "license": "Apache-2.0", - "dependencies": { - "bare-http1": "^4.0.0", - "bare-tcp": "^2.2.0", - "bare-tls": "^2.0.0" - } - }, "node_modules/bare-net": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz", @@ -8760,6 +9658,75 @@ "bare-tcp": "^2.0.0" } }, + "node_modules/bare-node-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bare-node-runtime/-/bare-node-runtime-1.1.4.tgz", + "integrity": "sha512-Scer2k81VcuTsPmyEX/PCiFqvLZvVNvrmnHCgU/l5/7BM+m9W1wy+oaLkWnU1r33UTHL+Cmxoe0l5HtOgybZOQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-abort-controller": "^1.0.0", + "bare-assert": "^1.1.0", + "bare-async-hooks": "^0.0.0", + "bare-buffer": "^3.3.1", + "bare-console": "^6.0.1", + "bare-crypto": "^1.11.2", + "bare-dgram": "^1.0.1", + "bare-diagnostics-channel": "^1.1.0", + "bare-dns": "^2.1.4", + "bare-events": "^2.7.0", + "bare-fetch": "^2.5.0", + "bare-fs": "^4.2.3", + "bare-http1": "^4.0.4", + "bare-https": "^2.0.0", + "bare-inspector": "^5.0.0", + "bare-module": "^6.1.2", + "bare-net": "^2.0.2", + "bare-os": "^3.6.2", + "bare-path": "^3.0.0", + "bare-performance": "^1.1.0", + "bare-process": "^4.2.1", + "bare-punycode": "^0.0.0", + "bare-querystring": "^1.0.0", + "bare-readline": "^1.1.0", + "bare-repl": "^6.0.1", + "bare-stream": "^2.7.0", + "bare-string-decoder": "^1.0.0", + "bare-subprocess": "^5.1.1", + "bare-timers": "^3.0.3", + "bare-tls": "^2.1.3", + "bare-tty": "^5.0.3", + "bare-url": "^2.2.2", + "bare-utils": "^1.5.1", + "bare-v8": "^1.0.1", + "bare-vm": "^1.0.0", + "bare-worker": "^4.0.0", + "bare-zlib": "^1.3.1" + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-performance": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bare-performance/-/bare-performance-1.3.0.tgz", + "integrity": "sha512-ITlHSQwsnJSPjpagTtPo043LRkFgcrl9SgPsVUBXZvTeGWuNLUfMRwmwAS+35i8qnIEinySoLxwhVoJNZAIF2g==", + "license": "Apache-2.0" + }, "node_modules/bare-pipe": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.2.tgz", @@ -8773,6 +9740,90 @@ "bare": ">=1.16.0" } }, + "node_modules/bare-process": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.2.2.tgz", + "integrity": "sha512-ikzEw+HGLB+2lS/WLVEsqi8BXBwzleG3n7DYI0v6/YNklvNabOIGlLd1dof+7Jw5ob4jsBRPtyflweVahY7rFA==", + "license": "Apache-2.0", + "dependencies": { + "bare-env": "^3.0.0", + "bare-events": "^2.3.1", + "bare-hrtime": "^2.0.0", + "bare-os": "^3.5.0", + "bare-pipe": "^4.0.0", + "bare-signals": "^4.0.0", + "bare-tty": "^5.0.0" + } + }, + "node_modules/bare-punycode": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/bare-punycode/-/bare-punycode-0.0.0.tgz", + "integrity": "sha512-PC2Y6mGLytZPJCB9M7CvO5Zb6uWYVUZ+5t3L6vePFuFM6tdC6SsQ+sIsuf0Sa6LHBoWURX7I9yMMbPXMv7TIdQ==", + "license": "Apache-2.0", + "dependencies": { + "punycode": "^2.3.1" + } + }, + "node_modules/bare-querystring": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bare-querystring/-/bare-querystring-1.0.0.tgz", + "integrity": "sha512-Kbqdjd4f1MxMtutXzraEKIXqXRfq9uWLwzUT5bLUh9TCiPznY9l6vHc4BUPNIa13DyH1VAlPy3iOxHzhqV2quQ==", + "license": "Apache-2.0" + }, + "node_modules/bare-readline": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bare-readline/-/bare-readline-1.2.1.tgz", + "integrity": "sha512-znH0bIYuGByDqoEvQ5wLK6651RaeN9tDJpvaV7V+/rGrHbIo0FBp0dvuo+TWs08d1VMvmfaGk6O7w/dyi30O8Q==", + "license": "Apache-2.0", + "dependencies": { + "bare-ansi-escapes": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-realm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bare-realm/-/bare-realm-2.0.0.tgz", + "integrity": "sha512-b01iOBrE/PpMsbIrQkqa9dQ3k8R6AKB7lRfbHXiKCzsQ/5vFYEtgLCdx5su+i9xK5yQtWIV3w41gQhksPCOXuQ==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.5.0" + } + }, + "node_modules/bare-repl": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/bare-repl/-/bare-repl-6.0.1.tgz", + "integrity": "sha512-dVPUM1UJX2nWTDY3omvrgLeC7IJy3hClxblAWf+dkegz8fh/F9tMs+sB5KCpeBSZ4qvAQv92C+XDtlePqE5sdQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-inspect": "^3.0.0", + "bare-module": "^6.0.1", + "bare-os": "^3.0.1", + "bare-path": "^3.0.0", + "bare-pipe": "^4.0.0", + "bare-readline": "^1.0.0", + "bare-stream": "^2.0.0", + "bare-tty": "^5.0.0" + } + }, + "node_modules/bare-semver": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.2.tgz", + "integrity": "sha512-ESVaN2nzWhcI5tf3Zzcq9aqCZ676VWzqw07eEZ0qxAcEOAFYBa0pWq8sK34OQeHLY3JsfKXZS9mDyzyxGjeLzA==", + "license": "Apache-2.0" + }, + "node_modules/bare-signals": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", + "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.3", + "bare-os": "^3.3.1" + }, + "engines": { + "bare": ">=1.7.0" + } + }, "node_modules/bare-stream": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", @@ -8794,6 +9845,81 @@ } } }, + "node_modules/bare-string-decoder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bare-string-decoder/-/bare-string-decoder-1.0.0.tgz", + "integrity": "sha512-FjFvfHo88U7borNQSj9ijP2JTb1asqY6K28OZrix4dF9iZf5D2wi1+99CgLlHT3HtYqdwxRhDAiKu8rVstt5rQ==", + "license": "Apache-2.0", + "dependencies": { + "text-decoder": "^1.2.3" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-structured-clone": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/bare-structured-clone/-/bare-structured-clone-1.5.2.tgz", + "integrity": "sha512-V5yevE00wUcmPGgk/O+bsEmuMRGRkwMW1Fncbl8cjH/Eu0+7g0oZSbN7oVGi19c8df9dn25NYymC0v03VVN70w==", + "license": "Apache-2.0", + "dependencies": { + "bare-type": "^1.1.0", + "compact-encoding": "^2.15.0" + }, + "engines": { + "bare": ">=1.2.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-url": { + "optional": true + } + } + }, + "node_modules/bare-subprocess": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.2.tgz", + "integrity": "sha512-L6oXgQ1aWs25RtG5Ky0bDD06p3RAcVVrDDMWb1DfXpHtyEWxamcyIWUbSykxMTWrpLlmSj6ytbb6yoKehGFfmw==", + "license": "Apache-2.0", + "dependencies": { + "bare-env": "^3.0.0", + "bare-events": "^2.5.4", + "bare-os": "^3.0.1", + "bare-pipe": "^4.0.0", + "bare-url": "^2.2.2" + }, + "engines": { + "bare": ">=1.7.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-system-logger": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bare-system-logger/-/bare-system-logger-1.0.2.tgz", + "integrity": "sha512-J9IL6aDQzIMYqaQkS8gSmw8VOJtaOVRZ6s5Jnqe/0c6JbSL0Cao+Dyei+weTV7JZoQS7ObMjwmaKSsNIwmWubQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-logger": "^2.0.0" + } + }, "node_modules/bare-tcp": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.2.tgz", @@ -8808,6 +9934,34 @@ "bare": ">=1.16.0" } }, + "node_modules/bare-thread": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bare-thread/-/bare-thread-1.1.6.tgz", + "integrity": "sha512-t6+mun2bpC6UlxyjWxn0bQYp8hM25XXRo6/UuoXx1mvYFZ6dHpp9nRyI0WWQk+n2Z5zb/g9hiUv6U+g4N8MQ3w==", + "license": "Apache-2.0", + "dependencies": { + "bare-bundle": "^1.9.0", + "bare-module-resolve": "^1.11.2", + "bare-module-traverse": "^2.0.0" + } + }, + "node_modules/bare-timers": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/bare-timers/-/bare-timers-3.2.0.tgz", + "integrity": "sha512-pBeQu+mUJzT5en5kxA6C6lJuu/9pcIcGhlqbVXXn61CETzRs9vepOMS3hXl82vFlsg3/ggtG36Q+7OZ4IP2MPw==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.7.0" + }, + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, "node_modules/bare-tls": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz", @@ -8821,6 +9975,103 @@ "bare": ">=1.7.0" } }, + "node_modules/bare-tty": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.0.3.tgz", + "integrity": "sha512-jW24RBWRZOMHuSEWC9mh8wUKO5WUNl0UWUTNPn3yZ8qkOqwa8vaV2dR3a+BvblONPk7V35wuvK9P8508+judAg==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.2.0", + "bare-signals": "^4.0.0", + "bare-stream": "^2.0.0" + }, + "engines": { + "bare": ">=1.16.0" + } + }, + "node_modules/bare-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", + "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.2.0" + } + }, + "node_modules/bare-url": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz", + "integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/bare-utils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.5.1.tgz", + "integrity": "sha512-mxCkFvmDU3mlD/mb+pT64kKXOsx2KMsWLQbngN1LB+NOXfhfnRnyvpy3VZc6m7gzQxe57Bsi+aTCBqA4/S3elQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-debug-log": "^2.0.0", + "bare-encoding": "^1.0.0", + "bare-format": "^1.0.0", + "bare-inspect": "^3.0.0", + "bare-type": "^1.0.6" + } + }, + "node_modules/bare-v8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bare-v8/-/bare-v8-1.0.1.tgz", + "integrity": "sha512-/cR5ZvFWQRdtTZ4tx0j7TKvTWce8UnnLqm88fwHtJmfM7HODIBVjQGDT7KkDLeD2d/eHP2pzB71Y8/QyiMMKrQ==", + "license": "Apache-2.0" + }, + "node_modules/bare-vm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bare-vm/-/bare-vm-1.0.1.tgz", + "integrity": "sha512-yLnbRvKt3AhRTmtfTIrYfdTHqGEfIJc+Fgb2DcHejE0HJ+p5adGxxPMvd3893Z7iXVYnalxukNARn4oJSZELHQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-realm": "^2.0.0" + } + }, + "node_modules/bare-worker": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/bare-worker/-/bare-worker-4.1.6.tgz", + "integrity": "sha512-yvsektF7xNqlAyjJV+YYeBx+sGZ8Fhbo5Cjc/lpIdkM8Gd/e3MlaxTFg8NknkyvJ4oDJXqvp38Q+ZOn5Gyr+nQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-channel": "^5.1.5", + "bare-events": "^2.2.1", + "bare-module": "^6.0.1", + "bare-thread": "^1.1.3" + } + }, + "node_modules/bare-ws": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.0.4.tgz", + "integrity": "sha512-SQMXzBYna9dRj57Dz1/ag+VWHCRXbfCjMHgyfM2F2lhkVLzMjnVSZP72aVeFWPFqe494Rd70Kzhe2JElGwFlJQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-crypto": "^1.2.0", + "bare-events": "^2.3.1", + "bare-http1": "^4.0.0", + "bare-https": "^2.0.0", + "bare-stream": "^2.1.2" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-url": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-url": { + "optional": true + } + } + }, "node_modules/bare-zlib": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/bare-zlib/-/bare-zlib-1.3.1.tgz", @@ -9473,7 +10724,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -9647,27 +10897,119 @@ "node": ">=6.0.0" } }, - "node_modules/c32check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/c32check/-/c32check-2.0.0.tgz", - "integrity": "sha512-rpwfAcS/CMqo0oCqDf3r9eeLgScRE3l/xHDCXhM3UyrfvIn7PrLq63uHh7yYbv8NzaZn5MVsVhIRpQ+5GZ5HyA==", - "license": "MIT", + "node_modules/c32check": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/c32check/-/c32check-2.0.0.tgz", + "integrity": "sha512-rpwfAcS/CMqo0oCqDf3r9eeLgScRE3l/xHDCXhM3UyrfvIn7PrLq63uHh7yYbv8NzaZn5MVsVhIRpQ+5GZ5HyA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.2", + "base-x": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", + "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/cacache/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==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "optional": true + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "optional": true, "dependencies": { - "@noble/hashes": "^1.1.2", - "base-x": "^4.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, + "node_modules/cacache/node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "license": "MIT", + "optional": true, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/call-bind": { @@ -9847,6 +11189,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -10050,7 +11402,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -10076,6 +11427,29 @@ "dev": true, "license": "MIT" }, + "node_modules/compact-encoding": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.18.0.tgz", + "integrity": "sha512-goACAOlhMI2xo5jGOMUDfOLnGdRE1jGfyZ+zie8N5114nHrbPIqf6GLUtzbLof6DSyrERlYRm3EcBplte5LcQw==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.3.0" + } + }, + "node_modules/compact-encoding/node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -10493,7 +11867,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -10963,7 +12337,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -11572,6 +12945,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT", + "optional": true + }, "node_modules/ecpair": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-3.0.0.tgz", @@ -11689,7 +13069,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/emojis-list": { @@ -11712,6 +13092,16 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/engine.io-client": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", @@ -11786,7 +13176,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -11805,6 +13195,13 @@ "node": ">=4" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -11963,7 +13360,6 @@ "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==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12117,7 +13513,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -12174,7 +13569,6 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -13062,6 +14456,13 @@ "node": ">=12.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -13300,7 +14701,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -13547,7 +14947,6 @@ "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "dev": true, "funding": [ { "type": "individual", @@ -13579,11 +14978,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -13616,6 +15044,19 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -14003,7 +15444,7 @@ "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, + "devOptional": true, "license": "ISC" }, "node_modules/graphemer": { @@ -14018,7 +15459,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -14403,6 +15843,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "optional": true + }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -14554,7 +16001,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -14687,7 +16134,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -14756,6 +16203,16 @@ "node": ">=10.13.0" } }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", @@ -15505,7 +16962,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/isobject": { @@ -15626,13 +17083,28 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -17611,6 +19083,39 @@ "dev": true, "license": "ISC" }, + "node_modules/make-fetch-happen": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", + "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -17785,7 +19290,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -17795,7 +19299,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -17859,6 +19362,159 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", + "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/module-definition": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.1.tgz", @@ -18129,6 +19785,31 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-gyp": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", + "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -18140,6 +19821,45 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.2.tgz", + "integrity": "sha512-mIcis6w+JiQf3P7t7mg/35GKB4T1FQsBOtMIvuKw4YErj5RjtbhcTd5/I30fmkmGMwvI0WlzSNN+27K0QCMkAw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -18167,6 +19887,22 @@ "node": ">=18" } }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -18651,6 +20387,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0", + "optional": true + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -18810,7 +20553,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -18822,6 +20565,30 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "optional": true + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -19138,7 +20905,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -19306,7 +21072,6 @@ "integrity": "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -19372,6 +21137,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -19387,6 +21162,30 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -19468,6 +21267,12 @@ "node": ">= 0.10" } }, + "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/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -19499,7 +21304,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -19705,7 +21509,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -19715,7 +21518,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -19736,7 +21538,6 @@ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -20089,6 +21890,18 @@ "strip-ansi": "^6.0.1" } }, + "node_modules/require-addon": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", + "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", + "license": "Apache-2.0", + "dependencies": { + "bare-addon-resolve": "^1.3.0" + }, + "engines": { + "bare": ">=1.10.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -20489,7 +22302,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/sass": { @@ -20498,7 +22311,6 @@ "integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -20940,7 +22752,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -20953,7 +22765,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -21171,6 +22983,17 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/socket.io-client": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", @@ -21221,6 +23044,46 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -21395,6 +23258,19 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -21584,6 +23460,29 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -21740,6 +23639,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -21924,6 +23837,33 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tar": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/terser": { "version": "5.46.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", @@ -22176,7 +24116,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -22193,7 +24133,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -22211,9 +24151,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, + "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -22611,8 +24550,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsutils": { "version": "3.21.0", @@ -22643,7 +24581,6 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -22713,7 +24650,6 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -22824,7 +24760,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -22884,6 +24819,35 @@ "node": "*" } }, + "node_modules/udx-native": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.19.2.tgz", + "integrity": "sha512-RNYh+UhfryCsF5hE2ZOuIqcZ+qdipXK3UsarwxWJwsUQZFE3ybwz0mPjwb5ev1PMBcjFahWiepS/q0wwL51c2g==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.5.0", + "bare-events": "^2.2.0", + "require-addon": "^1.1.0", + "streamx": "^2.22.0" + }, + "engines": { + "bare": ">=1.17.4" + } + }, + "node_modules/udx-native/node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -23001,6 +24965,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unique-filename": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", + "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/unique-slug": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", + "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -23224,7 +25214,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -23341,7 +25330,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -23531,7 +25519,6 @@ "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -23581,7 +25568,6 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -23692,7 +25678,6 @@ "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -23817,7 +25802,6 @@ "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -23920,7 +25904,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -24094,6 +26078,41 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -24135,7 +26154,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/ext/package.json b/ext/package.json index 89257f8c8..4c4b74b14 100755 --- a/ext/package.json +++ b/ext/package.json @@ -35,6 +35,7 @@ "@stacks/blockchain-api-client": "8.13.6", "@stacks/transactions": "7.3.1", "@stacks/wallet-sdk": "7.2.0", + "@utexo/rgb-sdk": "1.0.4", "assert": "2.1.0", "bignumber.js": "9.3.1", "bip21": "3.0.0", diff --git a/ext/src/modules/rgb-adapter.ts b/ext/src/modules/rgb-adapter.ts new file mode 100644 index 000000000..0d77e23d1 --- /dev/null +++ b/ext/src/modules/rgb-adapter.ts @@ -0,0 +1,228 @@ +import type { BtcBalance, GeneratedKeys, InvoiceReceiveData, SendResult, WalletManager } from '@utexo/rgb-sdk'; + +import type { IRGBAdapter, RGBConnection, RGBNetwork } from '@shared/class/wallets/rgb-wallet'; +import type { + CreateBackupParams, + CreateUtxosParams, + BackupResult, + DecodeRgbInvoiceResponseCustom, + InvoiceRequestCustom, + ListAssetsResponseCustom, + RgbTransferCustom, + SendAssetParams, + SendBtcParams, + TransactionCustom, + UnspentCustom, +} from '@shared/class/wallets/rgb-types'; + +type RGBSDK = typeof import('@utexo/rgb-sdk'); + +class RGBAdapter implements IRGBAdapter { + private sdk: RGBSDK | undefined; + private wallet: WalletManager | undefined; + private cc: RGBConnection | undefined; + private sdkLock: Promise = Promise.resolve(); + private _dataDir: string = 'rgb-data'; + + /** + * Wraps a method to ensure SDK initialization and locking. + * Handles sync→async conversion for Node.js SDK methods. + */ + private withLockAndWallet(fn: (wallet: WalletManager, ...args: Args) => Promise | T): (connection: RGBConnection, ...args: Args) => Promise { + return async (connection: RGBConnection, ...args: Args): Promise => { + let releaseLock: () => void = () => {}; + const lockPromise = new Promise((resolve) => (releaseLock = resolve)); + await this.sdkLock; + this.sdkLock = lockPromise; + + try { + const wallet = await this.getWallet(connection); + const result = fn(wallet, ...args); + return result instanceof Promise ? await result : result; + } finally { + releaseLock(); + } + }; + } + + /** + * Gets or creates a WalletManager for the given connection. + * Reuses existing wallet if connection params match. + */ + private async getWallet(connection: RGBConnection): Promise { + // Initialize SDK if needed + if (!this.sdk) { + this.sdk = await import('@utexo/rgb-sdk'); + } + + // Reuse existing wallet if same connection + if (this.wallet && this.cc && connection.mnemonic === this.cc.mnemonic && connection.network === this.cc.network) { + return this.wallet; + } + + // Derive keys and create new wallet + const keys = await this.sdk.deriveKeysFromMnemonic(connection.network, connection.mnemonic); + this.wallet = new this.sdk.WalletManager({ + xpubVan: keys.accountXpubVanilla, + xpubCol: keys.accountXpubColored, + masterFingerprint: keys.masterFingerprint, + mnemonic: keys.mnemonic, + network: connection.network, + dataDir: connection.dataDir, + transportEndpoint: connection.transportEndpoint, + indexerUrl: connection.indexerUrl, + }); + this.cc = connection; + return this.wallet; + } + + // ============================================ + // Private method implementations + // ============================================ + + private _registerWallet(wallet: WalletManager): { address: string; btcBalance: BtcBalance } { + return wallet.registerWallet(); + } + + private _refreshWallet(wallet: WalletManager): void { + wallet.refreshWallet(); + } + + private _getBtcBalance(wallet: WalletManager): BtcBalance { + return wallet.getBtcBalance(); + } + + private _getAddress(wallet: WalletManager): string { + return wallet.getAddress(); + } + + private _listUnspents(wallet: WalletManager): UnspentCustom[] { + return wallet.listUnspents() as unknown as UnspentCustom[]; + } + + private _listAssets(wallet: WalletManager): ListAssetsResponseCustom { + return wallet.listAssets() as unknown as ListAssetsResponseCustom; + } + + private _sendBtcBegin(wallet: WalletManager, params: SendBtcParams): string { + return wallet.sendBtcBegin(params); + } + + private _sendBtcEnd(wallet: WalletManager, params: { signedPsbt: string }): string { + return wallet.sendBtcEnd(params); + } + + private _sendBegin(wallet: WalletManager, params: SendAssetParams): string { + return wallet.sendBegin(params); + } + + private _sendEnd(wallet: WalletManager, params: { signedPsbt: string }): SendResult { + return wallet.sendEnd(params); + } + + private async _createUtxos(wallet: WalletManager, params: CreateUtxosParams): Promise { + return await wallet.createUtxos(params); + } + + private _blindReceive(wallet: WalletManager, params: InvoiceRequestCustom): InvoiceReceiveData { + return wallet.blindReceive(params as Parameters[0]); + } + + private _decodeRGBInvoice(wallet: WalletManager, params: { invoice: string }): DecodeRgbInvoiceResponseCustom { + return wallet.decodeRGBInvoice(params) as unknown as DecodeRgbInvoiceResponseCustom; + } + + private _listTransactions(wallet: WalletManager): TransactionCustom[] { + return wallet.listTransactions() as unknown as TransactionCustom[]; + } + + private _listTransfers(wallet: WalletManager, assetId: string): RgbTransferCustom[] { + return wallet.listTransfers(assetId) as unknown as RgbTransferCustom[]; + } + + private async _signPsbt(wallet: WalletManager, psbt: string): Promise { + return await wallet.signPsbt(psbt); + } + + private _createBackup(wallet: WalletManager, params: CreateBackupParams): BackupResult { + return wallet.createBackup(params); + } + + // ============================================ + // Public API + // ============================================ + + get api() { + const registerWallet = this.withLockAndWallet(this._registerWallet.bind(this)); + const refreshWallet = this.withLockAndWallet(this._refreshWallet.bind(this)); + const getBtcBalance = this.withLockAndWallet(this._getBtcBalance.bind(this)); + const getAddress = this.withLockAndWallet(this._getAddress.bind(this)); + const listUnspents = this.withLockAndWallet(this._listUnspents.bind(this)); + const listAssets = this.withLockAndWallet(this._listAssets.bind(this)); + const sendBtcBegin = this.withLockAndWallet(this._sendBtcBegin.bind(this)); + const sendBtcEnd = this.withLockAndWallet(this._sendBtcEnd.bind(this)); + const sendBegin = this.withLockAndWallet(this._sendBegin.bind(this)); + const sendEnd = this.withLockAndWallet(this._sendEnd.bind(this)); + const createUtxos = this.withLockAndWallet(this._createUtxos.bind(this)); + const blindReceive = this.withLockAndWallet(this._blindReceive.bind(this)); + const decodeRGBInvoice = this.withLockAndWallet(this._decodeRGBInvoice.bind(this)); + const listTransactions = this.withLockAndWallet(this._listTransactions.bind(this)); + const listTransfers = this.withLockAndWallet(this._listTransfers.bind(this)); + const signPsbt = this.withLockAndWallet(this._signPsbt.bind(this)); + const createBackup = this.withLockAndWallet(this._createBackup.bind(this)); + + return { + registerWallet, + refreshWallet, + getBtcBalance, + getAddress, + listUnspents, + listAssets, + sendBtcBegin, + sendBtcEnd, + sendBegin, + sendEnd, + createUtxos, + blindReceive, + decodeRGBInvoice, + listTransactions, + listTransfers, + signPsbt, + createBackup, + }; + } + + async deriveKeysFromMnemonic(network: RGBNetwork, mnemonic: string): Promise { + if (!this.sdk) { + this.sdk = await import('@utexo/rgb-sdk'); + } + return await this.sdk.deriveKeysFromMnemonic(network, mnemonic); + } + + getDataDir(): string { + return this._dataDir; + } + + // File operations for backup management + async fileExists(path: string): Promise { + try { + const fs = await import('fs/promises'); + await fs.access(path); + return true; + } catch { + return false; + } + } + + async deleteFile(path: string): Promise { + const fs = await import('fs/promises'); + await fs.unlink(path); + } + + async renameFile(from: string, to: string): Promise { + const fs = await import('fs/promises'); + await fs.rename(from, to); + } +} + +globalThis.rgbAdapter = new RGBAdapter(); diff --git a/ext/src/typings/globals.d.ts b/ext/src/typings/globals.d.ts index 304df4ab4..b7bc218d1 100644 --- a/ext/src/typings/globals.d.ts +++ b/ext/src/typings/globals.d.ts @@ -1,10 +1,12 @@ import { IBreezAdapter } from '@shared/class/wallets/breez-wallet'; +import { IRGBAdapter } from '@shared/class/wallets/rgb-wallet'; import { ISparkAdapter } from '@shared/class/wallets/spark-wallet'; declare function alert(message: string): void; declare global { var breezAdapter: IBreezAdapter; + var rgbAdapter: IRGBAdapter; var sparkAdapter: ISparkAdapter; var handleError: ((error: unknown, context?: string) => void | Promise) | undefined; } diff --git a/ext/vitest.config.mts b/ext/vitest.config.mts index c992d7ac8..fcd97b72b 100644 --- a/ext/vitest.config.mts +++ b/ext/vitest.config.mts @@ -8,6 +8,7 @@ export default defineConfig({ '../shared/tests/setup-vi.js', '../ext/src/modules/spark-adapter.ts', '../ext/src/modules/breeze-adapter.ts', + '../ext/src/modules/rgb-adapter.ts', '../ext/src/modules/error-handler.ts', ], testTimeout: 120_000, diff --git a/mobile/app/(tabs)/_layout.tsx b/mobile/app/(tabs)/_layout.tsx new file mode 100644 index 000000000..7bc80aeef --- /dev/null +++ b/mobile/app/(tabs)/_layout.tsx @@ -0,0 +1,91 @@ +import { Icon, Label, NativeTabs } from 'expo-router/unstable-native-tabs'; +import { Tabs } from 'expo-router'; +import { Image } from 'expo-image'; +import { Platform, StyleSheet } from 'react-native'; + +import CustomTabBarBackground from '@/components/ui/CustomTabBarBackground'; + +// Use custom tab bar only on iOS 18–25 for a reliable background. iOS 26+ and Android use native tabs. +const iosVersion = Platform.OS === 'ios' ? (typeof Platform.Version === 'string' ? parseInt(String(Platform.Version), 10) : Number(Platform.Version)) : 0; +const useCustomTabBar = Platform.OS === 'ios' && iosVersion >= 18 && iosVersion < 26; + +export default function TabsLayout() { + if (!useCustomTabBar) { + return ( + + + + + + + + + + + + + + + ); + } + + return ( + , + }} + > + + , + tabBarButtonTestID: 'Tab-home', + tabBarAccessibilityLabel: 'Tab-home', + }} + /> + , + tabBarButtonTestID: 'Tab-swaps', + tabBarAccessibilityLabel: 'Tab-swaps', + }} + /> + , + tabBarButtonTestID: 'Tab-explorer', + tabBarAccessibilityLabel: 'Tab-explorer', + }} + /> + + ); +} + +const styles = StyleSheet.create({ + tabBar: { + position: 'absolute', + borderTopWidth: 0, + elevation: 0, + }, + tabBarLabel: { + fontSize: 12, + fontWeight: '500', + }, +}); diff --git a/mobile/app/(tabs)/explorer.tsx b/mobile/app/(tabs)/explorer.tsx new file mode 100644 index 000000000..13009fd51 --- /dev/null +++ b/mobile/app/(tabs)/explorer.tsx @@ -0,0 +1,5 @@ +import DAppBrowserComponent from '../DAppBrowser'; + +export default function ExplorerTab() { + return ; +} diff --git a/mobile/app/(tabs)/home.tsx b/mobile/app/(tabs)/home.tsx new file mode 100644 index 000000000..6201239f9 --- /dev/null +++ b/mobile/app/(tabs)/home.tsx @@ -0,0 +1,5 @@ +import HomeComponent from '../Home'; + +export default function HomeTab() { + return ; +} diff --git a/mobile/app/(tabs)/index.tsx b/mobile/app/(tabs)/index.tsx new file mode 100644 index 000000000..cf78a4fab --- /dev/null +++ b/mobile/app/(tabs)/index.tsx @@ -0,0 +1,5 @@ +import { Redirect } from 'expo-router'; + +export default function TabsIndex() { + return ; +} diff --git a/mobile/app/(tabs)/swaps/_layout.tsx b/mobile/app/(tabs)/swaps/_layout.tsx new file mode 100644 index 000000000..21ea40c8e --- /dev/null +++ b/mobile/app/(tabs)/swaps/_layout.tsx @@ -0,0 +1,17 @@ +import { Stack } from 'expo-router'; +import React from 'react'; + +export default function SwapsTabLayout() { + return ( + + + + ); +} diff --git a/mobile/app/(tabs)/swaps/confirm.tsx b/mobile/app/(tabs)/swaps/confirm.tsx new file mode 100644 index 000000000..bbb3fc648 --- /dev/null +++ b/mobile/app/(tabs)/swaps/confirm.tsx @@ -0,0 +1 @@ +export { default } from '../../transfer/confirm'; diff --git a/mobile/app/(tabs)/swaps/index.tsx b/mobile/app/(tabs)/swaps/index.tsx new file mode 100644 index 000000000..ff170f121 --- /dev/null +++ b/mobile/app/(tabs)/swaps/index.tsx @@ -0,0 +1 @@ +export { default } from '../../transfer/index'; diff --git a/mobile/app/(tabs)/swaps/select-asset.tsx b/mobile/app/(tabs)/swaps/select-asset.tsx new file mode 100644 index 000000000..d0d087366 --- /dev/null +++ b/mobile/app/(tabs)/swaps/select-asset.tsx @@ -0,0 +1 @@ +export { default } from '../../transfer/select-asset'; diff --git a/mobile/app/(tabs)/swaps/success.tsx b/mobile/app/(tabs)/swaps/success.tsx new file mode 100644 index 000000000..0e0b6fc76 --- /dev/null +++ b/mobile/app/(tabs)/swaps/success.tsx @@ -0,0 +1 @@ +export { default } from '../../transfer/success'; diff --git a/mobile/app/Home.tsx b/mobile/app/Home.tsx index 1765c2f4a..f8aa8e3e8 100644 --- a/mobile/app/Home.tsx +++ b/mobile/app/Home.tsx @@ -35,11 +35,24 @@ import { USDT_TOKENS } from '@shared/models/token-list'; import { sleep } from '@shared/modules/sleep'; import { capitalizeFirstLetter } from '@shared/modules/string-utils'; import { CommonTransaction } from '@shared/types/common-transaction'; -import { NETWORK_ARK, NETWORK_LIGHTNING, NETWORK_LIGHTNING_TESTNET, NETWORK_LIQUID, NETWORK_LIQUID_TESTNET, NETWORK_ROOTSTOCK, NETWORK_SPARK, NETWORK_USDT, Networks } from '@shared/types/networks'; +import { + NETWORK_ARK, + NETWORK_LIGHTNING, + NETWORK_LIGHTNING_TESTNET, + NETWORK_LIQUID, + NETWORK_LIQUID_TESTNET, + NETWORK_RGB, + NETWORK_RGB_TESTNET, + NETWORK_ROOTSTOCK, + NETWORK_SPARK, + NETWORK_USDT, + Networks, +} from '@shared/types/networks'; import { SO_LIQUID_USDT, SO_ROOTSTOCK_USDT, SwapPlatform } from '@shared/types/swap'; import { CachedTokenInfo } from '@shared/types/token-info'; import { ReceiveTokenProps } from './Receive'; import { SendTokenEvmProps } from './SendTokenEvm'; +import { SendRgbTokenParams } from './SendRgbToken'; import { SwapParams } from './Swap'; import { SendParams } from './send'; @@ -316,6 +329,23 @@ export default function Home() { { children: , onClick: () => {} }, ]; + // RGB receive actions + const handleReceiveBitcoinOnRgb = () => { + router.push('/Receive'); + }; + + const handleReceiveTokenOnRgb = () => { + router.push({ pathname: '/ReceiveRgbToken', params: { network } }); + }; + + const rgbReceiveActions = [ + { children: , onClick: handleReceiveBitcoinOnRgb }, + { children: , onClick: handleReceiveTokenOnRgb }, + { children: , onClick: () => {} }, + ]; + + const isRgbNetwork = network === NETWORK_RGB || network === NETWORK_RGB_TESTNET; + const usdtSwapActions = useMemo(() => { const actions = []; if (getSwapPairs(SO_LIQUID_USDT, SwapPlatform.MOBILE).length > 0) { @@ -485,6 +515,13 @@ export default function Home() { Receive + ) : isRgbNetwork ? ( + + + + Receive + + ) : ( diff --git a/mobile/app/ReceiveRgbToken.tsx b/mobile/app/ReceiveRgbToken.tsx new file mode 100644 index 000000000..6931ab5fb --- /dev/null +++ b/mobile/app/ReceiveRgbToken.tsx @@ -0,0 +1,437 @@ +import { Ionicons } from '@expo/vector-icons'; +import assert from 'assert'; +import * as Clipboard from 'expo-clipboard'; +import * as Haptics from 'expo-haptics'; +import { Stack, useLocalSearchParams } from 'expo-router'; +import React, { useCallback, useContext, useRef, useState } from 'react'; +import { ActivityIndicator, Animated, KeyboardAvoidingView, Platform, ScrollView, Share, StyleSheet, TextInput, View } from 'react-native'; +import QRCode from 'react-native-qrcode-svg'; + +import ScreenHeader from '@/components/navigation/ScreenHeader'; +import RadialGradientScreen from '@/components/RadialGradientScreen'; +import { ThemedText } from '@/components/ThemedText'; +import { BackgroundExecutor } from '@/src/modules/background-executor'; +import { RGBWallet } from '@shared/class/wallets/rgb-wallet'; +import { AccountNumberContext } from '@shared/hooks/AccountNumberContext'; +import { capitalizeFirstLetter } from '@shared/modules/string-utils'; +import { NETWORK_RGB, NETWORK_RGB_TESTNET, Networks } from '@shared/types/networks'; +import Pressable from '../components/Pressable'; + +export type ReceiveRgbTokenProps = { + network: Networks; +}; + +enum ReceiveStep { + EnterAmount = 'enter_amount', + ShowInvoice = 'show_invoice', +} + +export default function ReceiveRgbTokenScreen() { + const params = useLocalSearchParams(); + const network = (params.network ?? NETWORK_RGB_TESTNET) as typeof NETWORK_RGB | typeof NETWORK_RGB_TESTNET; + const { accountNumber } = useContext(AccountNumberContext); + const [step, setStep] = useState(ReceiveStep.EnterAmount); + const [amount, setAmount] = useState(''); + const [invoice, setInvoice] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isCopied, setIsCopied] = useState(false); + const [isSharing, setIsSharing] = useState(false); + const [error, setError] = useState(null); + const scaleAnim = useRef(new Animated.Value(1)).current; + const opacityAnim = useRef(new Animated.Value(1)).current; + const pressScaleAnim = useRef(new Animated.Value(1)).current; + + const handleGenerateInvoice = useCallback(async () => { + const amountNumber = parseInt(amount, 10); + if (!amount || isNaN(amountNumber) || amountNumber <= 0) { + setError('Please enter a valid amount'); + return; + } + + setIsLoading(true); + setError(null); + try { + const wallet = await BackgroundExecutor.lazyInitWallet(network, accountNumber); + assert(wallet instanceof RGBWallet, 'Not an RGB wallet'); + const receiveInvoice = await wallet.getWitnessReceiveInvoice(amountNumber); + setInvoice(receiveInvoice); + setStep(ReceiveStep.ShowInvoice); + } catch (err) { + console.error('Error getting RGB receive invoice:', err); + setError('Failed to generate receive invoice'); + } finally { + setIsLoading(false); + } + }, [network, accountNumber, amount]); + + const handleAmountChange = (text: string) => { + // Only allow positive integers + const normalized = text.replace(/[^0-9]/g, ''); + setAmount(normalized); + setError(null); + }; + + const handleShare = async () => { + if (!invoice || isSharing) return; + setIsSharing(true); + try { + await Share.share({ + message: `My RGB invoice: ${invoice}`, + }); + } finally { + setIsSharing(false); + } + }; + + const handleCopyInvoice = async () => { + if (invoice) { + await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); + + Animated.timing(opacityAnim, { + toValue: 0, + duration: 150, + useNativeDriver: true, + }).start(() => { + setIsCopied(true); + + Animated.timing(opacityAnim, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }).start(); + }); + + Animated.sequence([ + Animated.timing(scaleAnim, { + toValue: 0.95, + duration: 100, + useNativeDriver: true, + }), + Animated.timing(scaleAnim, { + toValue: 1, + duration: 100, + useNativeDriver: true, + }), + ]).start(); + + await Clipboard.setStringAsync(invoice); + setTimeout(() => { + Animated.timing(opacityAnim, { + toValue: 0, + duration: 150, + useNativeDriver: true, + }).start(() => { + setIsCopied(false); + + Animated.timing(opacityAnim, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }).start(); + }); + }, 2000); + } + }; + + const handlePressIn = () => { + Animated.timing(pressScaleAnim, { + toValue: 0.98, + duration: 100, + useNativeDriver: true, + }).start(); + }; + + const handlePressOut = () => { + Animated.timing(pressScaleAnim, { + toValue: 1, + duration: 100, + useNativeDriver: true, + }).start(); + }; + + // Amount input step + if (step === ReceiveStep.EnterAmount) { + return ( + + + + + + + + Enter Amount + Specify the token amount you want to receive + + + + {error && ( + + + {error} + + )} + + + {isLoading ? : Generate Invoice} + + + + + + ); + } + + // Show invoice step + return ( + + + + + + + + + {!isLoading && invoice && !error ? ( + + + + + + + + Amount: {amount} + {isCopied ? ( + + + Copied ✓ + + + ) : ( + + {invoice} + + )} + + + + ) : ( + <> + + {isLoading ? ( + + + Generating invoice... + + ) : error ? ( + + + {error} + + ) : ( + + No invoice available + + )} + + + )} + + + + + Share... + + + + + + ); +} + +const styles = StyleSheet.create({ + keyboardAvoidingView: { + flex: 1, + }, + scrollContent: { + flexGrow: 1, + paddingHorizontal: 16, + }, + contentContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'space-between', + }, + amountInputContainer: { + flex: 1, + paddingTop: 40, + }, + label: { + fontSize: 24, + fontWeight: '600', + color: 'white', + textAlign: 'center', + marginBottom: 8, + }, + sublabel: { + fontSize: 14, + color: 'rgba(255, 255, 255, 0.6)', + textAlign: 'center', + marginBottom: 40, + }, + amountInput: { + backgroundColor: 'rgba(0, 0, 0, 0.3)', + borderRadius: 16, + padding: 20, + fontSize: 32, + fontWeight: '600', + color: 'white', + textAlign: 'center', + textAlignVertical: 'center', + marginBottom: 24, + width: '100%', + }, + errorContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 8, + marginBottom: 16, + }, + generateButton: { + backgroundColor: '#000000', + borderRadius: 16, + padding: 18, + alignItems: 'center', + justifyContent: 'center', + }, + generateButtonDisabled: { + opacity: 0.5, + }, + generateButtonText: { + color: 'white', + fontSize: 16, + fontWeight: '600', + }, + qrSection: { + alignItems: 'center', + marginTop: 60, + marginBottom: 40, + }, + qrAndAddressContainer: { + alignItems: 'center', + }, + qrContainer: { + padding: 24, + backgroundColor: 'white', + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + alignItems: 'center', + justifyContent: 'center', + }, + qrPlaceholder: { + width: 320, + height: 320, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#f0f0f0', + borderRadius: 20, + }, + loadingText: { + marginTop: 10, + color: '#666', + }, + errorText: { + color: '#FF6B6B', + marginLeft: 4, + textAlign: 'center', + }, + addressContainer: { + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderBottomRightRadius: 16, + borderBottomLeftRadius: 16, + padding: 16, + width: 368, + minHeight: 100, + justifyContent: 'center', + }, + amountBadge: { + fontSize: 14, + fontWeight: '600', + color: 'rgba(255, 255, 255, 0.8)', + marginBottom: 8, + backgroundColor: 'rgba(0, 0, 0, 0.2)', + paddingHorizontal: 12, + paddingVertical: 4, + borderRadius: 8, + }, + addressDisplay: { + textAlign: 'center', + lineHeight: 24, + color: 'white', + fontSize: 14, + }, + addressTextContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'center', + alignItems: 'center', + }, + actionButtons: { + width: '100%', + gap: 8, + marginBottom: 20, + }, + shareButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.15)', + paddingVertical: 18, + borderRadius: 16, + gap: 12, + }, + shareButtonText: { + color: 'white', + fontSize: 16, + fontWeight: '600', + }, +}); diff --git a/mobile/app/SendRgbToken.tsx b/mobile/app/SendRgbToken.tsx new file mode 100644 index 000000000..224cb6e5b --- /dev/null +++ b/mobile/app/SendRgbToken.tsx @@ -0,0 +1,713 @@ +import { Ionicons } from '@expo/vector-icons'; +import assert from 'assert'; +import BigNumber from 'bignumber.js'; +import { Stack, router, useLocalSearchParams } from 'expo-router'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { ActivityIndicator, Alert, KeyboardAvoidingView, Platform, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'; +import Animated, { Extrapolation, interpolate, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; +import Rive, { RiveRef } from 'rive-react-native'; + +import LongPressButton from '@/components/LongPressButton'; +import ScreenHeader from '@/components/navigation/ScreenHeader'; +import RadialGradientScreen from '@/components/RadialGradientScreen'; +import { ThemedText } from '@/components/ThemedText'; +import { ScanQrContext } from '@/src/hooks/ScanQrContext'; +import { BackgroundExecutor } from '@/src/modules/background-executor'; +import { TFeeEstimate } from '@shared/blue_modules/BlueElectrum'; +import { walletCanHaveTokens } from '@shared/class/wallets/interface-can-have-tokens'; +import { RGBWallet } from '@shared/class/wallets/rgb-wallet'; +import { AccountNumberContext } from '@shared/hooks/AccountNumberContext'; +import { NetworkContext } from '@shared/hooks/NetworkContext'; +import { useTokenBalance } from '@shared/hooks/useTokenBalance'; +import { capitalizeFirstLetter, formatBalance } from '@shared/modules/string-utils'; +import { CachedTokenInfo } from '@shared/types/token-info'; +import Pressable from '../components/Pressable'; + +export type SendRgbTokenParams = { + tokenId: string; + tokenSymbol: string; + tokenName: string; + tokenDecimals: string; +}; + +// Enum for the different steps in the send token flow +export enum SendRgbTokenStep { + Init = 'init', + Loading = 'loading', + Signing = 'signing', + Signed = 'signed', + Broadcasting = 'broadcasting', + Success = 'success', +} + +export default function SendRgbTokenScreen() { + const params = useLocalSearchParams(); + const { network } = useContext(NetworkContext); + const { accountNumber } = useContext(AccountNumberContext); + const { scanQr } = useContext(ScanQrContext); + + // State management + const [step, setStep] = useState(SendRgbTokenStep.Init); + const [invoice, setInvoice] = useState(''); + const [amountToSend, setAmountToSend] = useState(''); + const [error, setError] = useState(''); + const [token, setToken] = useState(); + + // Fee state + const [feeEstimates, setFeeEstimates] = useState(); + const [selectedFeeRate, setSelectedFeeRate] = useState(); + const [customFeeRate, setCustomFeeRate] = useState(); + const [isFeeSelectorExpanded, setIsFeeSelectorExpanded] = useState(false); + const [isLoadingFees, setIsLoadingFees] = useState(true); + + // Animation + const riveRef = useRef(null); + const expandAnimation = useSharedValue(0); + const chevronRotation = useSharedValue(0); + + const tokenPublicKey = params.tokenId || ''; + const { balance } = useTokenBalance(network, accountNumber, tokenPublicKey, BackgroundExecutor); + + // Computed fee rate + const feeRate = selectedFeeRate ?? customFeeRate ?? feeEstimates?.medium ?? 1; + + const feeName = feeEstimates ? (feeRate === feeEstimates.fast ? 'Fast' : feeRate === feeEstimates.medium ? 'Medium' : feeRate === feeEstimates.slow ? 'Slow' : 'Custom') : 'Network Fee'; + + // Colors for gradient background + const textColor = 'rgba(255, 255, 255, 0.9)'; + const borderColor = 'rgba(255, 255, 255, 0.3)'; + const primaryColor = 'rgba(255, 255, 255, 0.8)'; + const errorColor = 'rgba(255, 100, 100, 0.9)'; + const successColor = 'rgba(75, 181, 67, 1)'; + + // Load token info + useEffect(() => { + const loadToken = async () => { + try { + const wallet = await BackgroundExecutor.lazyInitWallet(network as any, accountNumber); + assert(walletCanHaveTokens(wallet), 'Not a wallet that can have tokens'); + + const tokenBalances = wallet.getTokenBalances(); + for (const t of tokenBalances) { + if (t.id === tokenPublicKey) { + setToken(t); + return; + } + } + setError('Token not found'); + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + } + }; + + loadToken(); + }, [accountNumber, network, tokenPublicKey]); + + // Load fee estimates + useEffect(() => { + const loadFees = async () => { + setIsLoadingFees(true); + try { + const wallet = (await BackgroundExecutor.lazyInitWallet(network as any, accountNumber)) as RGBWallet; + const fees = await wallet.getFeeEstimates(); + setFeeEstimates(fees); + } catch (err) { + console.error('Failed to load fee estimates:', err); + } finally { + setIsLoadingFees(false); + } + }; + + loadFees(); + }, [network, accountNumber]); + + // Fee selector animation + useEffect(() => { + const duration = 100; + if (isFeeSelectorExpanded) { + expandAnimation.value = withTiming(1, { duration }); + chevronRotation.value = withTiming(1, { duration }); + } else { + expandAnimation.value = withTiming(0, { duration }); + chevronRotation.value = withTiming(0, { duration }); + } + }, [isFeeSelectorExpanded, expandAnimation, chevronRotation]); + + const animatedFeeOptionsStyle = useAnimatedStyle(() => { + const height = interpolate(expandAnimation.value, [0, 1], [0, feeEstimates ? 192 : 0], Extrapolation.CLAMP); + const opacity = interpolate(expandAnimation.value, [0, 0.1, 1], [0, 0, 1], Extrapolation.CLAMP); + return { height, opacity }; + }); + + const animatedChevronStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${chevronRotation.value}deg` }] })); + + // Validate invoice format + const validateInvoice = (inv: string): boolean => { + return RGBWallet.isRgbInvoice(inv); + }; + + // Send the token + const actuallySend = async () => { + try { + assert(token, 'internal error: token not loaded'); + assert(invoice, 'invoice is required'); + setStep(SendRgbTokenStep.Broadcasting); + await new Promise((resolve) => setTimeout(resolve, 200)); + + const wallet = (await BackgroundExecutor.lazyInitWallet(network as any, accountNumber)) as RGBWallet; + assert(wallet instanceof RGBWallet, 'Not an RGB wallet'); + + const satValueToSend = new BigNumber(amountToSend).multipliedBy(new BigNumber(10).pow(token.decimals)).toFixed(0); + + const transactionId = await wallet.transferToken(token.id, BigInt(satValueToSend), invoice, String(feeRate)); + + if (transactionId) { + setStep(SendRgbTokenStep.Success); + } else { + setError('Error: transaction failed (unknown error)'); + setStep(SendRgbTokenStep.Init); + } + } catch (error: any) { + setError(error.message); + setStep(SendRgbTokenStep.Init); + } + }; + + // Prepare and sign transaction + const prepareTransaction = async () => { + setStep(SendRgbTokenStep.Loading); + await new Promise((resolve) => setTimeout(resolve, 200)); + setError(''); + + try { + assert(balance, 'internal error: balance not loaded'); + assert(token, 'internal error: token not loaded'); + assert(invoice, 'invoice is required'); + assert(validateInvoice(invoice), 'Invalid RGB invoice format. Must start with rgb:'); + + const amt = parseFloat(amountToSend); + assert(!isNaN(amt), 'Invalid amount'); + assert(amt > 0, 'Amount should be > 0'); + + const satValueToSendBN = new BigNumber(amt); + const satValueToSend = satValueToSendBN.multipliedBy(new BigNumber(10).pow(token.decimals)).toString(10); + assert(new BigNumber(balance).gte(satValueToSend), 'Not enough balance'); + + setStep(SendRgbTokenStep.Signed); + } catch (error: any) { + setError(error.message); + setStep(SendRgbTokenStep.Init); + } + }; + + const handleScanQR = useCallback(async () => { + try { + const scannedInvoice = await scanQr(); + if (scannedInvoice) { + setInvoice(scannedInvoice); + } + } catch (error) { + console.error('QR scan error:', error); + Alert.alert('Error', 'Failed to scan QR code'); + } + }, [scanQr]); + + const handleMaxAmount = useCallback(() => { + if (balance && token) { + const formattedBalance = formatBalance(balance, token.decimals, 8); + setAmountToSend(formattedBalance); + } + }, [balance, token]); + + const handleFeeSelection = (rate: number) => { + setSelectedFeeRate(rate); + setCustomFeeRate(undefined); + setIsFeeSelectorExpanded(false); + }; + + const toggleFeeSelector = () => { + setIsFeeSelectorExpanded(!isFeeSelectorExpanded); + }; + + const resetToHome = () => { + router.replace('/Home'); + }; + + // Validate required parameters + if (!params.tokenId || !params.tokenSymbol) { + Alert.alert('Error', 'Missing token information'); + router.back(); + return null; + } + + const isInputDisabled = step !== SendRgbTokenStep.Init; + + return ( + + + + + on {capitalizeFirstLetter(network)} + + + + + {/* Success Screen */} + {step === SendRgbTokenStep.Success ? ( + + + console.log('Rive animation error:', error)} /> + + Transaction Sent! + Your token transfer was successful. + + Back to Wallet + + + ) : ( + <> + {/* Invoice Input */} + + RGB Invoice + + + + + + + + + {/* Amount Input */} + + + Amount + + MAX + + + + + {`Available: ${token?.symbol || ''} ${balance ? formatBalance(balance, token?.decimals ?? 2, token?.decimals ?? 2) : '0'}`} + + + + {/* Fee Selector */} + {!isLoadingFees && feeEstimates && ( + + + + {isFeeSelectorExpanded ? ( + <> + Network Fee + + + + + ) : ( + <> + + Network Fee + + {feeName} - {feeRate} sats/vB + + + + + + + )} + + + + handleFeeSelection(feeEstimates.fast)}> + + Fast + {feeEstimates.fast} sats/vB + + + + handleFeeSelection(feeEstimates.medium)}> + + Medium + {feeEstimates.medium} sats/vB + + + + handleFeeSelection(feeEstimates.slow)}> + + Slow + {feeEstimates.slow} sats/vB + + + + + + )} + + {isLoadingFees && ( + + + Loading fees... + + )} + + {/* Error Display */} + {error && ( + + {error} + + )} + + {/* Loading States */} + {step === SendRgbTokenStep.Loading && ( + + + Validating... + + )} + + {step === SendRgbTokenStep.Broadcasting && ( + + + Sending... + + )} + + {/* Action Buttons */} + + {step === SendRgbTokenStep.Init && ( + + + Review + + )} + + {step === SendRgbTokenStep.Signed && ( + + {/* Transaction Summary */} + + + Amount + + {amountToSend} {token?.symbol} + + + + Network Fee + {feeRate} sats/vB + + + To Invoice + + {invoice} + + + + + + setStep(SendRgbTokenStep.Init)} activeOpacity={0.7}> + Cancel + + + )} + + + )} + + + + ); +} + +const styles = StyleSheet.create({ + section: { + marginBottom: 24, + }, + label: { + fontSize: 16, + fontWeight: '600', + marginBottom: 8, + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + input: { + flex: 1, + height: 50, + borderWidth: 1, + borderRadius: 8, + paddingHorizontal: 12, + fontSize: 16, + }, + scanButton: { + width: 50, + height: 50, + backgroundColor: 'rgba(255, 255, 255, 0.1)', + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.2)', + borderRadius: 16, + alignItems: 'center', + justifyContent: 'center', + marginLeft: 12, + }, + amountHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 8, + }, + maxButton: { + fontSize: 14, + fontWeight: '600', + }, + balanceText: { + fontSize: 14, + fontWeight: '500', + }, + errorContainer: { + padding: 12, + borderRadius: 8, + backgroundColor: 'rgba(255, 100, 100, 0.1)', + borderWidth: 1, + borderColor: 'rgba(255, 100, 100, 0.3)', + }, + errorText: { + fontSize: 14, + fontWeight: '500', + }, + loadingContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + padding: 16, + }, + loadingText: { + marginLeft: 8, + fontSize: 16, + fontWeight: '500', + }, + buttonContainer: { + marginTop: 24, + }, + sendIcon: { + marginRight: 0, + }, + sendButton: { + backgroundColor: '#000000', + borderRadius: 16, + padding: 16, + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'center', + gap: 8, + }, + sendButtonText: { + color: 'rgba(255, 255, 255, 0.9)', + }, + confirmButton: { + backgroundColor: '#000000', + borderRadius: 16, + padding: 16, + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'center', + width: '100%', + }, + confirmButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#FFFFFF', + }, + cancelButton: { + paddingVertical: 12, + alignItems: 'center', + }, + cancelButtonText: { + fontSize: 16, + fontWeight: '500', + textDecorationLine: 'underline', + }, + successContainer: { + alignItems: 'center', + paddingVertical: 32, + }, + riveContainer: { + width: '100%', + height: 200, + alignItems: 'center', + justifyContent: 'center', + marginBottom: 20, + }, + riveAnimation: { + width: '180%', + height: '180%', + }, + successTitle: { + fontSize: 20, + fontWeight: '600', + marginBottom: 8, + }, + successMessage: { + fontSize: 16, + textAlign: 'center', + marginBottom: 24, + maxWidth: 280, + }, + sendAnotherButton: { + paddingHorizontal: 28, + paddingVertical: 12, + borderRadius: 8, + }, + sendAnotherButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#FFFFFF', + }, + networkBadge: { + alignSelf: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.1)', + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.2)', + borderRadius: 20, + paddingVertical: 6, + paddingHorizontal: 12, + marginBottom: 30, + }, + networkText: { + color: 'rgba(255, 255, 255, 0.9)', + textAlign: 'center', + }, + keyboardAvoidingView: { + flex: 1, + }, + scrollView: { + flex: 1, + padding: 16, + }, + preparedContainer: { + width: '100%', + }, + summaryContainer: { + backgroundColor: 'rgba(255, 255, 255, 0.1)', + borderRadius: 12, + padding: 16, + marginBottom: 16, + }, + summaryRow: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 8, + }, + summaryLabel: { + color: 'rgba(255, 255, 255, 0.6)', + fontSize: 14, + }, + summaryValue: { + color: 'rgba(255, 255, 255, 0.9)', + fontSize: 14, + fontWeight: '600', + }, + summaryValueSmall: { + color: 'rgba(255, 255, 255, 0.9)', + fontSize: 12, + fontWeight: '500', + maxWidth: 200, + }, + // Fee selector styles + feeSelectorContainer: { + marginBottom: 0, + }, + feeSelectorHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.15)', + paddingHorizontal: 17, + borderRadius: 16, + height: 64, + }, + feeSelectorExpandedHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.15)', + paddingHorizontal: 17, + borderRadius: 16, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + height: 64, + }, + feeSelectorTitle: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 18, + fontWeight: '400', + }, + feeSelectorCollapsedContent: { + flex: 1, + }, + feeSelectorLabel: { + color: 'rgba(255, 255, 255, 0.5)', + fontSize: 14, + }, + feeSelectorSelected: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 16, + }, + feeOptionsContainer: { + backgroundColor: 'rgba(255, 255, 255, 0.1)', + borderBottomLeftRadius: 16, + borderBottomRightRadius: 16, + overflow: 'hidden', + }, + feeOption: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 16, + backgroundColor: 'rgba(255, 255, 255, 0.1)', + height: 64, + }, + selectedFeeOption: { + backgroundColor: 'rgba(255, 255, 255, 0.15)', + }, + feeOptionContent: { + flex: 1, + }, + feeOptionName: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 16, + fontWeight: '500', + marginBottom: 2, + }, + feeOptionRate: { + color: 'rgba(255, 255, 255, 0.5)', + fontSize: 13, + }, +}); diff --git a/mobile/app/TransactionDetails.tsx b/mobile/app/TransactionDetails.tsx index 877ef07e6..a06b56423 100644 --- a/mobile/app/TransactionDetails.tsx +++ b/mobile/app/TransactionDetails.tsx @@ -15,10 +15,24 @@ import { useExchangeRate } from '@shared/hooks/useExchangeRate'; import { getDecimalsByNetwork, getIsEVM, getTickerByNetwork } from '@shared/models/network-getters'; import { getTokenInfo, getTokenIconColor } from '@shared/models/token-list'; import { capitalizeFirstLetter, formatBalance, formatFiatBalance } from '@shared/modules/string-utils'; -import { CommonTransaction } from '@shared/types/common-transaction'; +import { CommonTransaction, CommonTokenTransfer } from '@shared/types/common-transaction'; import { NETWORK_ARK, NETWORK_ARK_MUTINYNET, NETWORK_BITCOIN, NETWORK_LIGHTNING, NETWORK_LIGHTNING_TESTNET, NETWORK_SPARK } from '@shared/types/networks'; import * as BlueElectrum from '@shared/blue_modules/BlueElectrum'; +// Helper to get token info - uses embedded info if available, falls back to static list +function getTokenInfoFromTransfer(transfer: CommonTokenTransfer) { + if (transfer.name !== undefined && transfer.symbol !== undefined && transfer.decimals !== undefined) { + return { + id: transfer.tokenId, + name: transfer.name, + symbol: transfer.symbol, + decimals: transfer.decimals, + chainId: 0, + }; + } + return getTokenInfo(transfer.tokenId); +} + export default function TransactionDetails() { const { transaction: jsonTransaction, layerNetwork } = useLocalSearchParams(); const transaction: CommonTransaction = JSON.parse(jsonTransaction as string); @@ -270,7 +284,7 @@ export default function TransactionDetails() { const singleTokenInfo = useMemo(() => { if (isZeroAmountWithTokens && transaction.tokenTransfers?.length === 1) { - return getTokenInfo(transaction.tokenTransfers[0].tokenId); + return getTokenInfoFromTransfer(transaction.tokenTransfers[0]); } return null; }, [isZeroAmountWithTokens, transaction.tokenTransfers]); @@ -493,7 +507,7 @@ export default function TransactionDetails() { return ( {transaction.tokenTransfers.map((transfer, index) => { - const tokenInfo = getTokenInfo(transfer.tokenId); + const tokenInfo = getTokenInfoFromTransfer(transfer); const iconColor = getTokenIconColor(tokenInfo.name); const formattedAmount = transfer.amount ? formatBalance(transfer.amount.toString(), tokenInfo.decimals) : '0'; const isNegative = !transfer.amount && transaction.direction === 'send'; diff --git a/mobile/app/TransferDetails.tsx b/mobile/app/TransferDetails.tsx new file mode 100644 index 000000000..632914974 --- /dev/null +++ b/mobile/app/TransferDetails.tsx @@ -0,0 +1,529 @@ +import { AntDesign, MaterialIcons } from '@expo/vector-icons'; +import * as Clipboard from 'expo-clipboard'; +import { useLocalSearchParams, useRouter } from 'expo-router'; +import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { Linking, Platform, StyleSheet, View } from 'react-native'; +import Animated, { useAnimatedStyle, useSharedValue, withRepeat, withSequence, withTiming } from 'react-native-reanimated'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import Timeline from 'react-native-timeline-flatlist'; + +import DetachedSheet from '@/components/DetachedSheet'; +import Pressable from '@/components/Pressable'; +import { ThemedText } from '@/components/ThemedText'; +import { LayerzStorage } from '@/src/class/layerz-storage'; +import { AccountNumberContext } from '@shared/hooks/AccountNumberContext'; +import { useTransferService } from '@shared/hooks/useTransferService'; +import { getAssetInfo } from '@shared/models/asset-info'; +import { EXECUTION_CLAIM, getStatusLabel, isActiveStatus, isTerminalStatus, TransferExecution } from '@shared/types/transfer'; + +const POLL_INTERVAL = 10_000; + +export default function TransferDetails() { + const { execution: jsonExecution } = useLocalSearchParams(); + const [execution, setExecution] = useState(() => JSON.parse(jsonExecution as string)); + const sendAssetInfo = getAssetInfo(execution.sendAsset); + const receiveAssetInfo = getAssetInfo(execution.receiveAsset); + const sameTicker = sendAssetInfo.ticker === receiveAssetInfo.ticker; + const sendLabel = sameTicker ? sendAssetInfo.name : sendAssetInfo.ticker; + const receiveLabel = sameTicker ? receiveAssetInfo.name : receiveAssetInfo.ticker; + const router = useRouter(); + const { accountNumber } = useContext(AccountNumberContext); + const transferService = useTransferService(LayerzStorage); + const pollTimer = useRef | null>(null); + + const [isTimelineExpanded, setIsTimelineExpanded] = useState(false); + + const descriptionOpacity = useSharedValue(0); + const timestampOpacity = useSharedValue(0); + const descriptionMaxHeight = useSharedValue(0); + const pendingFlashOpacity = useSharedValue(1); + + const descriptionAnimatedStyle = useAnimatedStyle(() => ({ + opacity: descriptionOpacity.value, + maxHeight: descriptionMaxHeight.value, + overflow: 'hidden', + })); + + const timestampAnimatedStyle = useAnimatedStyle(() => ({ + opacity: timestampOpacity.value, + })); + + const pendingFlashAnimatedStyle = useAnimatedStyle(() => ({ + opacity: pendingFlashOpacity.value, + })); + + const isActive = isActiveStatus(execution.status); + + const isClaimable = execution.status === 'claimable'; + const trackingUrl = transferService.getTrackingUrl(execution); + + // Flashing animation for active step + useEffect(() => { + if (isActive) { + pendingFlashOpacity.value = withRepeat(withSequence(withTiming(0.4, { duration: 800 }), withTiming(1, { duration: 800 })), -1, false); + } else { + pendingFlashOpacity.value = withTiming(1, { duration: 300 }); + } + }, [isActive, pendingFlashOpacity]); + + // Sync expand/collapse animations + useEffect(() => { + if (isTimelineExpanded) { + descriptionOpacity.value = withTiming(1, { duration: 300 }); + timestampOpacity.value = withTiming(1, { duration: 300 }); + descriptionMaxHeight.value = withTiming(100, { duration: 300 }); + } else { + descriptionOpacity.value = withTiming(0, { duration: 300 }); + timestampOpacity.value = withTiming(0, { duration: 300 }); + descriptionMaxHeight.value = withTiming(0, { duration: 300 }); + } + }, [isTimelineExpanded, descriptionOpacity, timestampOpacity, descriptionMaxHeight]); + + // Poll for status updates on non-terminal transfers + useEffect(() => { + const skipPolling = isTerminalStatus(execution.status) || (execution.status === 'claimable' && !(execution.type === EXECUTION_CLAIM && execution.autoClaim)); + if (skipPolling) return; + + const poll = async () => { + try { + if (transferService.refreshTransferStatus) { + const updated = await transferService.refreshTransferStatus(execution.id, accountNumber); + setExecution(updated); + } + } catch { + // Silently ignore — will retry on next interval + } + }; + + pollTimer.current = setInterval(poll, POLL_INTERVAL); + return () => { + if (pollTimer.current) clearInterval(pollTimer.current); + }; + }, [execution.status, execution.id]); // eslint-disable-line react-hooks/exhaustive-deps + + const toggleTimeline = () => { + setIsTimelineExpanded(!isTimelineExpanded); + }; + + const handleSheetClose = () => { + router.back(); + }; + + const handleCopy = async (text?: string) => { + if (!text) return; + await Clipboard.setStringAsync(text); + }; + + const formattedDate = useMemo(() => { + const d = new Date(execution.createdAt * 1000); + const dateStr = d.toLocaleDateString('en-US', { month: 'long', day: '2-digit', year: 'numeric' }); + const timeStr = d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); + return `${dateStr} - ${timeStr.toLowerCase()}`; + }, [execution.createdAt]); + + // Timeline data — driven by service-defined steps + const timelineData = useMemo(() => { + const steps = transferService.getTimelineSteps(execution); + + const white = '#FFFFFF'; + const gray = 'rgba(255, 255, 255, 0.3)'; + const red = '#FF3B30'; + + const formatTime = (ts: number) => { + const d = new Date(ts * 1000); + return d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); + }; + + return steps.map((step) => { + const isReached = step.status === 'completed' || step.status === 'active'; + const isError = step.status === 'error'; + const circleColor = isError ? red : isReached ? white : gray; + const icon = isError ? : ; + + return { + time: step.timestamp ? formatTime(step.timestamp) : '', + title: step.title, + description: step.description, + completed: isReached || isError, + isActiveStep: step.status === 'active', + lineColor: step.status === 'completed' ? white : isError ? red : gray, + circleColor, + icon, + }; + }); + }, [execution, transferService]); + + const detailRows = useMemo(() => { + const rows: { label: string; value: string; copyable?: boolean }[] = []; + + if (execution.serviceName) { + const displayName = execution.serviceName === 'NativeDeposit' ? 'Native' : execution.serviceName; + rows.push({ label: 'Provider', value: displayName }); + } + + rows.push({ label: 'Status', value: getStatusLabel(execution.status, execution) }); + + if (execution.providerId) { + rows.push({ label: 'Transfer ID', value: execution.providerId, copyable: true }); + } + + if (execution.depositAddress) { + rows.push({ label: 'Deposit Address', value: execution.depositAddress, copyable: true }); + } + if (execution.settleAddress) { + rows.push({ label: 'Settle Address', value: execution.settleAddress, copyable: true }); + } + + if (execution.depositTxid) { + rows.push({ label: 'Deposit Txid', value: execution.depositTxid, copyable: true }); + } + if (execution.type === EXECUTION_CLAIM && execution.claimTxid) { + rows.push({ label: 'Claim Txid', value: execution.claimTxid, copyable: true }); + } + + return rows; + }, [execution]); + + return ( + + + + {/* Header */} + + + + + + Transfer + {formattedDate} + + + + {/* Amounts */} + + + {execution.sendAmount} + {sendLabel} + + + + {execution.receiveAmount} {receiveLabel} + + + + {/* Timeline */} + + + null} + renderDetail={(rowData: any, rowDataIndex?: number) => { + const isCompleted = rowData?.completed !== false; + const isActiveStep = rowData?.isActiveStep === true; + const isLastItem = rowDataIndex === timelineData.length - 1; + + return ( + + + {isActiveStep ? ( + {rowData?.title} + ) : ( + {rowData?.title} + )} + + {rowData?.time ? ( + + {rowData.time} + + ) : null} + + + + {rowData?.description} + + + ); + }} + /> + + + + {/* Claim button for NativeDeposit claimable transfers */} + {execution.type === EXECUTION_CLAIM && isClaimable && ( + { + router.push({ pathname: '/SwapXArkClaim', params: { swapJson: execution.claimSwapJson } }); + }} + > + {execution.autoClaim && !execution.autoClaimError ? 'Claiming...' : 'Claim'} + + )} + + {/* View Online */} + {trackingUrl && ( + Linking.openURL(trackingUrl)}> + + View Online + + )} + + {/* Details */} + + {detailRows.map((row) => ( + + {row.label} + + {row.copyable && ( + handleCopy(row.value)}> + + + )} + + + {row.value} + + + + + ))} + + + + + ); +} + +const styles = StyleSheet.create({ + safeArea: { + flex: 1, + backgroundColor: 'transparent', + }, + container: { + flexGrow: 1, + marginHorizontal: 16, + paddingBottom: 16, + }, + topHeader: { + flexDirection: 'row', + alignItems: 'center', + }, + iconCircle: { + width: 36, + height: 36, + backgroundColor: 'rgba(255, 255, 255, 0.2)', + borderRadius: 100, + alignItems: 'center', + justifyContent: 'center', + }, + swapIcon: { + transform: [{ rotate: '-50deg' }], + }, + typeTextWrap: { + marginLeft: 12, + }, + typeText: { + fontSize: 16, + color: 'rgba(255, 255, 255, 0.8)', + }, + subText: { + fontSize: 14, + color: 'rgba(255, 255, 255, 0.4)', + marginTop: -2, + }, + amountsBlock: { + marginVertical: 48, + alignItems: 'center', + }, + amountPrimary: { + color: 'rgba(255, 255, 255, 0.9)', + textAlign: 'center', + }, + amountTicker: { + fontSize: 16, + color: 'rgba(255, 255, 255, 0.9)', + }, + arrowText: { + fontSize: 16, + color: 'rgba(255, 255, 255, 0.4)', + marginVertical: 4, + }, + receiveText: { + fontSize: 16, + color: 'rgba(255, 255, 255, 0.6)', + }, + // Timeline styles — match TransactionDetails exactly + timelineContainer: { + paddingHorizontal: 16, + paddingVertical: 16, + backgroundColor: 'rgba(255, 255, 255, 0.10)', + borderRadius: 20, + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.10)', + overflow: 'hidden', + ...Platform.select({ + ios: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 12, + }, + android: {}, + }), + }, + timelineInnerContainer: { + minHeight: 104, + }, + timelineTime: { + textAlign: 'right', + color: 'rgba(255, 255, 255, 0.6)', + fontSize: 12, + marginLeft: 'auto', + }, + timelineTitle: { + fontSize: 16, + fontWeight: '600', + color: 'rgba(255, 255, 255)', + flex: 1, + margin: 0, + padding: 0, + }, + timelineTitleRow: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + margin: 0, + padding: 0, + width: '100%', + }, + timelineDescription: { + fontSize: 14, + color: 'rgba(255, 255, 255, 0.7)', + margin: 0, + padding: 0, + }, + timelineRowContainer: { + alignItems: 'flex-start', + margin: 0, + padding: 0, + }, + timelineEventContainer: { + flex: 1, + height: 'auto', + alignItems: 'flex-start', + justifyContent: 'flex-start', + margin: 0, + padding: 0, + }, + timelineDetailContainer: { + flex: 1, + marginHorizontal: 0, + marginTop: -12, + marginBottom: 6, + paddingTop: 0, + paddingBottom: 6, + alignItems: 'flex-start', + justifyContent: 'flex-start', + }, + timelineDetailContainerLast: { + marginBottom: 0, + paddingBottom: 0, + }, + timelineIconStyle: { + alignItems: 'center', + justifyContent: 'center', + }, + timelineListView: { + margin: 0, + padding: 0, + flexGrow: 1, + }, + claimButton: { + marginTop: 20, + paddingVertical: 14, + borderRadius: 12, + backgroundColor: 'rgba(33, 150, 243, 0.2)', + borderWidth: 1, + borderColor: 'rgba(33, 150, 243, 0.5)', + alignItems: 'center', + }, + claimButtonDisabled: { + opacity: 0.5, + }, + claimButtonText: { + fontSize: 16, + fontWeight: '600', + color: '#2196F3', + }, + trackingButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 6, + marginTop: 20, + paddingVertical: 14, + borderRadius: 12, + backgroundColor: 'rgba(255, 255, 255, 0.10)', + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.15)', + }, + trackingButtonText: { + fontSize: 16, + fontWeight: '500', + color: 'rgba(255, 255, 255, 0.8)', + }, + // Detail rows — match TransactionDetails + detailsList: { + marginTop: 28, + gap: 8, + }, + detailRow: { + flexDirection: 'row', + alignItems: 'center', + width: '100%', + justifyContent: 'space-between', + }, + detailLabel: { + fontSize: 16, + color: 'rgba(255, 255, 255, 0.8)', + flexShrink: 0, + marginRight: 16, + }, + detailValueWrap: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + flex: 1, + flexShrink: 1, + flexBasis: 0, + minWidth: 0, + justifyContent: 'flex-end', + }, + detailValueContainer: { + flexShrink: 1, + minWidth: 0, + maxWidth: '100%', + alignItems: 'flex-end', + }, + detailValue: { + fontSize: 16, + color: 'rgba(255, 255, 255, 0.5)', + textAlign: 'right', + }, +}); diff --git a/mobile/app/YieldList.tsx b/mobile/app/YieldList.tsx new file mode 100644 index 000000000..9f58ec0c8 --- /dev/null +++ b/mobile/app/YieldList.tsx @@ -0,0 +1,186 @@ +import React, { useCallback, useContext, useMemo, useState } from 'react'; +import { StyleSheet, TouchableOpacity, View } from 'react-native'; +import { Image } from 'expo-image'; +import RadialGradientScreen from '@/components/RadialGradientScreen'; +import ScreenHeader from '@/components/navigation/ScreenHeader'; +import SectionContainer from '@/components/SectionContainer'; +import { ThemedText } from '@/components/ThemedText'; +import YieldRow from '@/components/YieldRow'; +import { LayerzStorage } from '@/src/class/layerz-storage'; +import { BackgroundExecutor } from '@/src/modules/background-executor'; +import { getNetworkImageAsset } from '@/utils/networkAssets'; +import { AccountNumberContext } from '@shared/hooks/AccountNumberContext'; +import { NetworkContext } from '@shared/hooks/NetworkContext'; +import { useYieldDiscovery, YieldBearingCachedTokenInfo, YIELD_TOKEN_DEFINITIONS_BY_NETWORK } from '@shared/hooks/useYieldDiscovery'; +import { getTokenInfo } from '@shared/models/token-list'; +import { AssetId } from '@shared/types/asset'; +import { NETWORK_BOTANIX, NETWORK_CITREA, NETWORK_SPARK, Networks } from '@shared/types/networks'; +import { router } from 'expo-router'; + +const USDB_YIELD_TOKEN_ID = 'btkn1xgrvjwey5ngcagvap2dzzvsy4uk8ua9x69k82dwvt5e7ef9drm9qztux87'; +/** Transfer screen: BTC-Spark → Spark USDB (Flashnet) */ +const TRANSFER_TO_USDB: { sendAsset: AssetId; receiveAsset: AssetId } = { + sendAsset: 'native:spark', + receiveAsset: 'token:spark:usdb', +}; + +type YieldWithNetwork = YieldBearingCachedTokenInfo & { network: Networks }; + +const availableYields = (Object.entries(YIELD_TOKEN_DEFINITIONS_BY_NETWORK) as [Networks, { tokenId: string; apr: string; url: string }[]][]).flatMap(([network, definitions]) => + definitions.map((def) => ({ ...def, network, tokenInfo: getTokenInfo(def.tokenId) })) +); + +export default function YieldListScreen() { + const { network, setNetwork } = useContext(NetworkContext); + const { accountNumber } = useContext(AccountNumberContext); + + const { yieldList: botanixYield } = useYieldDiscovery(NETWORK_BOTANIX, accountNumber, BackgroundExecutor, LayerzStorage); + const { yieldList: citreaYield } = useYieldDiscovery(NETWORK_CITREA, accountNumber, BackgroundExecutor, LayerzStorage); + const { yieldList: sparkYield } = useYieldDiscovery(NETWORK_SPARK, accountNumber, BackgroundExecutor, LayerzStorage); + + const allYields = useMemo( + () => [ + ...botanixYield.map((y): YieldWithNetwork => ({ ...y, network: NETWORK_BOTANIX })), + ...citreaYield.map((y): YieldWithNetwork => ({ ...y, network: NETWORK_CITREA })), + ...sparkYield.map((y): YieldWithNetwork => ({ ...y, network: NETWORK_SPARK })), + ], + [botanixYield, citreaYield, sparkYield] + ); + + const [visibleAllocatedIds, setVisibleAllocatedIds] = useState>(new Set()); + + const handleYieldVisible = useCallback((id: string) => { + setVisibleAllocatedIds((prev) => { + if (prev.has(id)) return prev; + const next = new Set(prev); + next.add(id); + return next; + }); + }, []); + + const filteredAvailableYields = useMemo(() => availableYields.filter((def) => !visibleAllocatedIds.has(def.tokenId.toLowerCase())), [visibleAllocatedIds]); + + // Extract all possible yielding tokens into one flat array + const allYieldTokens = Object.values(YIELD_TOKEN_DEFINITIONS_BY_NETWORK).flatMap((defs) => defs ?? []); + + const handleYieldPress = async (_token: YieldBearingCachedTokenInfo) => { + switch (_token.id) { + case '0xF4586028FFdA7Eca636864F80f8a3f2589E33795': + // botanix yield + setNetwork(NETWORK_BOTANIX); + await new Promise((res) => setTimeout(res, 100)); // propagate network change + router.push({ pathname: '/DAppBrowser', params: { url: 'https://yield.botanixlabs.com' } }); + break; + case USDB_YIELD_TOKEN_ID: + router.push({ + pathname: '/transfer', + params: { sendAsset: TRANSFER_TO_USDB.sendAsset, receiveAsset: TRANSFER_TO_USDB.receiveAsset }, + }); + break; + } + }; + + return ( + + + + + {allYields.map((yieldToken) => ( + handleYieldVisible(yieldToken.id.toLowerCase())} + /> + ))} + {filteredAvailableYields.length === allYieldTokens.length && Nothing allocated yet} + + + {filteredAvailableYields.length > 0 && ( + + {filteredAvailableYields.map((def) => { + const networkIcon = getNetworkImageAsset(def.network); + return ( + handleYieldPress({ ...def.tokenInfo, balance: undefined, yield: { tokenId: def.tokenId, apr: def.apr, url: def.url } })} + > + {networkIcon && } + + {def.tokenInfo.name} + + APR: + {def.apr} + + + + ); + })} + + )} + + + ); +} + +const styles = StyleSheet.create({ + noAllocatedYields: { + paddingLeft: 16, + }, + list: { + paddingTop: 16, + paddingHorizontal: 16, + gap: 16, + }, + sectionRows: { + gap: 16, + }, + availableRow: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 12, + }, + availableIcon: { + width: 38, + height: 38, + borderRadius: 19, + overflow: 'hidden', + alignItems: 'center', + justifyContent: 'center', + marginRight: 8, + }, + availableIconImage: { + width: '100%', + height: '100%', + borderRadius: 19, + }, + availableInfo: { + flex: 1, + }, + availableName: { + fontSize: 16, + fontWeight: '500', + color: '#ffffff', + }, + availableAprRow: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 2, + }, + aprPrefix: { + fontSize: 13, + fontWeight: '500', + color: 'rgba(255, 255, 255, 0.3)', + marginRight: 4, + }, + aprValue: { + fontSize: 13, + fontWeight: '600', + color: '#00ff6e', + }, +}); diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx index d17f5e854..c0be0fb60 100644 --- a/mobile/app/_layout.tsx +++ b/mobile/app/_layout.tsx @@ -10,6 +10,7 @@ import { SWRConfig } from 'swr'; import '../src/modules/breeze-adapter'; // needed to be imported before we can use BreezWallet import '../src/modules/spark-adapter'; // needed to be imported before we can use SparkWallet +import '../src/modules/rgb-adapter'; // needed to be imported before we can use RGBWallet import { ErrorBoundary } from '@/components/ErrorBoundary'; import { useColorScheme } from '@/hooks/useColorScheme'; diff --git a/mobile/app/modals/transfer-confirm.tsx b/mobile/app/modals/transfer-confirm.tsx new file mode 100644 index 000000000..ee1a5082c --- /dev/null +++ b/mobile/app/modals/transfer-confirm.tsx @@ -0,0 +1 @@ +export { default } from '../transfer/confirm'; diff --git a/mobile/app/modals/transfer-select-asset.tsx b/mobile/app/modals/transfer-select-asset.tsx new file mode 100644 index 000000000..37add1ec6 --- /dev/null +++ b/mobile/app/modals/transfer-select-asset.tsx @@ -0,0 +1 @@ +export { default } from '../transfer/select-asset'; diff --git a/mobile/app/modals/transfer-success.tsx b/mobile/app/modals/transfer-success.tsx new file mode 100644 index 000000000..36333e3ba --- /dev/null +++ b/mobile/app/modals/transfer-success.tsx @@ -0,0 +1 @@ +export { default } from '../transfer/success'; diff --git a/mobile/app/send/_layout.tsx b/mobile/app/send/_layout.tsx index fed0e5253..84a273216 100644 --- a/mobile/app/send/_layout.tsx +++ b/mobile/app/send/_layout.tsx @@ -11,6 +11,7 @@ import { HDSegwitBech32Wallet } from '@shared/class/wallets/hd-segwit-bech32-wal import { AccountNumberContext } from '@shared/hooks/AccountNumberContext'; import { NetworkContext } from '@shared/hooks/NetworkContext'; import { GetBtcSendDataResponse } from '@shared/types/IBackgroundCaller'; +import type { RgbDecodedInvoice } from '@shared/class/wallets/rgb-wallet'; import { NETWORK_ARK, NETWORK_BITCOIN, NETWORK_LIGHTNING, NETWORK_LIGHTNING_TESTNET, NETWORK_LIQUID, NETWORK_LIQUID_TESTNET, NETWORK_SPARK, Networks } from '@shared/types/networks'; // Bitcoin-specific data types @@ -48,6 +49,16 @@ export interface LightningNetworkData { setLnurlPayServicePayload: React.Dispatch>; } +// RGB-specific data types +export interface RgbPreparedTx { + signedPsbt: string; + feeRate: number; + amount: number; + // Token-specific fields (optional, only for token sends) + tokenId?: string; + invoice?: string; +} + // Denomination type export type Denomination = 'Native' | 'Fiat'; @@ -70,6 +81,10 @@ export interface SendFlowContextData { // Lightning-specific data lightning: LightningNetworkData | undefined; + // RGB-specific data + rgbDecodedInvoice: RgbDecodedInvoice | undefined; + rgbPreparedTx: RgbPreparedTx | undefined; + // Generic created transaction createdTransaction: CreatedTransaction | undefined; @@ -82,6 +97,8 @@ export interface SendFlowContextData { setMemo: (memo: string) => void; setCreatedTransaction: (transaction: CreatedTransaction | undefined) => void; setLiquidPrepareResult: (result: PrepareSendResponse | undefined) => void; + setRgbDecodedInvoice: (invoice: RgbDecodedInvoice | undefined) => void; + setRgbPreparedTx: (tx: RgbPreparedTx | undefined) => void; reset: () => void; } @@ -126,6 +143,10 @@ function SendFlowProvider({ children, initialNetwork }: SendFlowProviderProps) { // Liquid-specific state const [liquidPrepareResult, setLiquidPrepareResult] = useState(undefined); + // RGB-specific state + const [rgbDecodedInvoice, setRgbDecodedInvoice] = useState(undefined); + const [rgbPreparedTx, setRgbPreparedTx] = useState(undefined); + // Generic created transaction (for all networks) const [createdTransaction, setCreatedTransaction] = useState(undefined); @@ -182,6 +203,8 @@ function SendFlowProvider({ children, initialNetwork }: SendFlowProviderProps) { setMemo(''); setCreatedTransaction(undefined); setLiquidPrepareResult(undefined); + setRgbDecodedInvoice(undefined); + setRgbPreparedTx(undefined); }; // Construct Bitcoin-specific data object @@ -225,6 +248,8 @@ function SendFlowProvider({ children, initialNetwork }: SendFlowProviderProps) { bitcoin: bitcoinData, lightning: lightningData, liquidPrepareResult, + rgbDecodedInvoice, + rgbPreparedTx, createdTransaction, setNetwork, setAddress, @@ -234,6 +259,8 @@ function SendFlowProvider({ children, initialNetwork }: SendFlowProviderProps) { setMemo, setCreatedTransaction, setLiquidPrepareResult, + setRgbDecodedInvoice, + setRgbPreparedTx, reset, }} > @@ -257,10 +284,12 @@ export default function SendLayout() { > + + diff --git a/mobile/app/send/index.tsx b/mobile/app/send/index.tsx index bb5e9d60b..8aec46db1 100644 --- a/mobile/app/send/index.tsx +++ b/mobile/app/send/index.tsx @@ -2,7 +2,7 @@ import { Redirect, useLocalSearchParams } from 'expo-router'; import React, { useContext, useLayoutEffect } from 'react'; import { NetworkContext } from '@shared/hooks/NetworkContext'; -import { NETWORK_LIGHTNING, NETWORK_LIGHTNING_TESTNET } from '@shared/types/networks'; +import { NETWORK_LIGHTNING, NETWORK_LIGHTNING_TESTNET, NETWORK_RGB, NETWORK_RGB_TESTNET } from '@shared/types/networks'; import { useSendFlow } from './_layout'; export type SendParams = { @@ -38,6 +38,11 @@ const SendIndex: React.FC = () => { return ; } + // Route to RGB address screen for RGB networks + if (network === NETWORK_RGB || network === NETWORK_RGB_TESTNET) { + return ; + } + return ; }; diff --git a/mobile/app/send/send-address-rgb.tsx b/mobile/app/send/send-address-rgb.tsx new file mode 100644 index 000000000..30f59fd4e --- /dev/null +++ b/mobile/app/send/send-address-rgb.tsx @@ -0,0 +1,304 @@ +import { Ionicons } from '@expo/vector-icons'; +import assert from 'assert'; +import { Stack, useRouter } from 'expo-router'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { ActivityIndicator, KeyboardAvoidingView, Platform, StyleSheet, TextInput, View } from 'react-native'; + +import RadialGradientScreen from '@/components/RadialGradientScreen'; +import ScreenSendHeader from '@/components/navigation/ScreenSendHeader'; +import { ThemedText } from '@/components/ThemedText'; +import TokensView from '@/components/TokensView'; +import { overlayBackgroundDeeper } from '@shared/constants/Colors'; +import { ScanQrContext } from '@/src/hooks/ScanQrContext'; +import { BackgroundExecutor } from '@/src/modules/background-executor'; +import { RGBWallet, type RgbDecodedInvoice } from '@shared/class/wallets/rgb-wallet'; +import { AccountNumberContext } from '@shared/hooks/AccountNumberContext'; +import { getTickerByNetwork } from '@shared/models/network-getters'; +import { NETWORK_RGB, NETWORK_RGB_TESTNET } from '@shared/types/networks'; +import { CachedTokenInfo } from '@shared/types/token-info'; +import Pressable from '../../components/Pressable'; +import { useSendFlow } from './_layout'; + +const SendAddressRgb: React.FC = () => { + const { scanQr } = useContext(ScanQrContext); + const router = useRouter(); + const { network: networkType, address: contextAddress, setAddress: setContextAddress, token, setToken, setAmount, setDenomination, setMemo, setRgbDecodedInvoice } = useSendFlow(); + const network = networkType as typeof NETWORK_RGB | typeof NETWORK_RGB_TESTNET; + const { accountNumber } = useContext(AccountNumberContext); + + const [localAddress, setLocalAddress] = useState(contextAddress); + const [errorMessage, setErrorMessage] = useState(''); + const [isValidating, setIsValidating] = useState(false); + const [decodedInvoice, setDecodedInvoice] = useState(); + const inputRef = useRef(null); + + const handleScanQR = async () => { + const scanned = await scanQr(); + if (scanned) { + setLocalAddress(scanned.trim()); + } + }; + + const validateInvoice = useCallback( + async (invoice: string) => { + setIsValidating(true); + setErrorMessage(''); + setDecodedInvoice(undefined); + + try { + const wallet = await BackgroundExecutor.lazyInitWallet(network, accountNumber); + assert(wallet instanceof RGBWallet, 'Not an RGB wallet'); + const decoded = await wallet.decodeRgbInvoice(invoice); + + // If invoice specifies an assetId, check if user has this token + if (decoded.assetId) { + const tokenBalances = wallet.getTokenBalances(); + const matchingToken = tokenBalances.find((t) => t.id === decoded.assetId); + if (!matchingToken) { + setErrorMessage("You don't have this token"); + return; + } + setToken(decoded.assetId); + } + + setDecodedInvoice(decoded); + setRgbDecodedInvoice(decoded); + } catch (e: any) { + setErrorMessage(e.message || 'Invalid RGB invoice'); + setRgbDecodedInvoice(undefined); + } finally { + setIsValidating(false); + } + }, + [network, accountNumber, setToken, setRgbDecodedInvoice] + ); + + // Validate on input change (debounced for RGB invoices) + useEffect(() => { + const trimmed = localAddress.trim(); + if (!trimmed) { + setErrorMessage(''); + setDecodedInvoice(undefined); + setRgbDecodedInvoice(undefined); + return; + } + + if (RGBWallet.isRgbInvoice(trimmed)) { + const timeoutId = setTimeout(() => { + validateInvoice(trimmed); + }, 300); + return () => clearTimeout(timeoutId); + } else if (RGBWallet.isTaprootAddress(trimmed)) { + setErrorMessage(''); + setDecodedInvoice(undefined); + setRgbDecodedInvoice(undefined); + setToken(undefined); + } else { + setErrorMessage('Enter an RGB invoice (rgb:...) or taproot address'); + setDecodedInvoice(undefined); + setRgbDecodedInvoice(undefined); + } + }, [localAddress, validateInvoice, setRgbDecodedInvoice, setToken]); + + const handleContinue = () => { + const trimmed = localAddress.trim(); + if (!trimmed || isValidating || errorMessage) return; + + const isInvoice = RGBWallet.isRgbInvoice(trimmed); + + if (isInvoice && !decodedInvoice) { + setErrorMessage('Please wait for invoice validation'); + return; + } + + // RGB invoices require a token (can't send BTC to an invoice) + if (isInvoice && !token) { + setErrorMessage('Please select a token'); + return; + } + + setContextAddress(trimmed); + router.push('/send/send-amount-rgb'); + }; + + const handleInputWrapperPress = () => { + inputRef.current?.focus(); + }; + + const handleTokenPress = (clickedToken: CachedTokenInfo) => { + setToken(token === clickedToken.id ? undefined : clickedToken.id); + setAmount(''); + setDenomination('Native'); + setMemo(''); + }; + + const trimmed = localAddress.trim(); + const isInvoice = RGBWallet.isRgbInvoice(trimmed); + // Allow token selection only for RGB invoices that don't specify an asset + const canSelectToken = isInvoice && decodedInvoice && !decodedInvoice.assetId; + // Taproot: ready when valid address; Invoice: ready when decoded + token selected + const canContinue = !!trimmed && !isValidating && !errorMessage && (!isInvoice || (!!decodedInvoice && !!token)); + + // Build info message for valid decoded invoices + let invoiceInfoText: string | null = null; + if (decodedInvoice && !errorMessage && !isValidating) { + const parts: string[] = []; + if (decodedInvoice.assetId) parts.push('Token detected'); + if (decodedInvoice.assignment?.amount) parts.push(`Amount: ${decodedInvoice.assignment.amount}`); + invoiceInfoText = parts.length > 0 ? parts.join(' · ') : 'Valid RGB invoice'; + } + + return ( + + + + + + + + + + To + + + + + + + + {isValidating && ( + + + Validating invoice... + + )} + + {errorMessage && !isValidating && ( + + + {errorMessage} + + )} + + {invoiceInfoText && ( + + + {invoiceInfoText} + + )} + + + + + + Next + + + + + ); +}; + +const styles = StyleSheet.create({ + keyboardAvoidingView: { + flex: 1, + }, + container: { + flex: 1, + paddingHorizontal: 16, + justifyContent: 'space-between', + }, + inputSection: { + marginBottom: 30, + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: overlayBackgroundDeeper, + borderRadius: 20, + height: 64, + paddingLeft: 24, + paddingRight: 12, + gap: 12, + }, + inputWrapper: { + flex: 1, + justifyContent: 'center', + }, + inputLabel: { + color: 'rgba(255, 255, 255, 0.5)', + fontSize: 14, + fontWeight: '400', + marginBottom: 4, + }, + input: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 16, + padding: 0, + margin: 0, + }, + scanButton: { + width: 40, + height: 40, + alignItems: 'center', + justifyContent: 'center', + }, + validatingContainer: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 8, + gap: 6, + }, + validatingText: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 14, + }, + errorContainer: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 8, + gap: 6, + }, + errorText: { + color: 'white', + fontSize: 14, + }, + infoContainer: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 8, + gap: 6, + }, + infoText: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 14, + }, + continueButton: { + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.3)', + paddingVertical: 16, + borderRadius: 16, + marginTop: 'auto', + marginBottom: 24, + }, + continueButtonText: { + color: 'rgba(255, 255, 255, 0.9)', + fontSize: 16, + fontWeight: '600', + }, + disabledButton: { + opacity: 0.5, + }, +}); + +export default SendAddressRgb; diff --git a/mobile/app/send/send-address.tsx b/mobile/app/send/send-address.tsx index 155c787a1..2b0946f33 100644 --- a/mobile/app/send/send-address.tsx +++ b/mobile/app/send/send-address.tsx @@ -1,7 +1,7 @@ import { Ionicons } from '@expo/vector-icons'; import * as bip21 from 'bip21'; import { Stack, useRouter } from 'expo-router'; -import React, { useContext, useRef, useState } from 'react'; +import React, { useContext, useMemo, useRef, useState } from 'react'; import { KeyboardAvoidingView, Platform, StyleSheet, TextInput, View } from 'react-native'; import Pressable from '../../components/Pressable'; @@ -11,9 +11,10 @@ import { ThemedText } from '@/components/ThemedText'; import TokensView from '@/components/TokensView'; import { overlayBackgroundDeeper } from '@shared/constants/Colors'; import { ScanQrContext } from '@/src/hooks/ScanQrContext'; +import { RGBWallet } from '@shared/class/wallets/rgb-wallet'; import { getIsAccountBased, getIsEVM, getTickerByNetwork } from '@shared/models/network-getters'; import { validateAddress } from '@shared/modules/wallet-utils'; -import { NETWORK_BITCOIN, NETWORK_LIQUID, NETWORK_LIQUID_TESTNET } from '@shared/types/networks'; +import { NETWORK_BITCOIN, NETWORK_LIQUID, NETWORK_LIQUID_TESTNET, NETWORK_RGB, NETWORK_RGB_TESTNET } from '@shared/types/networks'; import { CachedTokenInfo } from '@shared/types/token-info'; import { useSendFlow } from './_layout'; @@ -26,6 +27,16 @@ const SendAddress: React.FC = () => { const [errorMessage, setErrorMessage] = useState(''); const inputRef = useRef(null); + const isRgbNetwork = network === NETWORK_RGB || network === NETWORK_RGB_TESTNET; + + // For RGB networks, detect if address is an RGB invoice or taproot address + const addressType = useMemo(() => { + if (!isRgbNetwork || !localAddress.trim()) return null; + if (RGBWallet.isRgbInvoice(localAddress)) return 'invoice'; + if (RGBWallet.isTaprootAddress(localAddress)) return 'taproot'; + return null; + }, [isRgbNetwork, localAddress]); + const handleScanQR = async () => { const scanned = await scanQr(); if (scanned) { @@ -49,6 +60,18 @@ const SendAddress: React.FC = () => { return; } + // For RGB networks: validate address type matches token selection + if (isRgbNetwork) { + if (token && addressType !== 'invoice') { + setErrorMessage('Token sends require an RGB invoice (rgb:...)'); + return; + } + if (!token && addressType !== 'taproot') { + setErrorMessage('BTC sends require a taproot address (bc1p/tb1p)'); + return; + } + } + setErrorMessage(''); try { @@ -58,12 +81,15 @@ const SendAddress: React.FC = () => { setContextAddress(localAddress); if (getIsEVM(network)) { router.push('/send/send-amount-evm'); + } else if (network === NETWORK_RGB || network === NETWORK_RGB_TESTNET) { + // RGB check must come before getIsAccountBased since RGB is also account-based + router.push('/send/send-amount-rgb'); + } else if (network === NETWORK_LIQUID || network === NETWORK_LIQUID_TESTNET) { + router.push('/send/send-amount-liquid'); } else if (getIsAccountBased(network)) { router.push('/send/send-amount-acc'); } else if (network === NETWORK_BITCOIN) { router.push('/send/send-amount-btc'); - } else if (network === NETWORK_LIQUID || network === NETWORK_LIQUID_TESTNET) { - router.push('/send/send-amount-liquid'); } else { throw new Error('Invalid network'); } @@ -97,7 +123,7 @@ const SendAddress: React.FC = () => { { + const router = useRouter(); + const { + network: networkType, + address, + amount: contextAmount, + setAmount: setContextAmount, + setCreatedTransaction, + denomination, + setDenomination, + setRgbPreparedTx, + rgbDecodedInvoice, + token, + } = useSendFlow(); + const network = networkType as typeof NETWORK_RGB | typeof NETWORK_RGB_TESTNET; + const { accountNumber } = useContext(AccountNumberContext); + const { balance: btcBalance } = useBalance(network, accountNumber, BackgroundExecutor); + const { balance: tokenBalance } = useTokenBalance(network, accountNumber, token || '', BackgroundExecutor); + const { exchangeRate } = useCachedExchangeRate(network, 'USD'); + + // Token info state - fetched from wallet + const [tokenInfo, setTokenInfo] = useState(null); + + const [localAmount, setLocalAmount] = useState(contextAmount); + const [selectedFeeRate, setSelectedFeeRate] = useState(); + const [selectedFeeIndex, setSelectedFeeIndex] = useState(); + const [isFeeSelectorExpanded, setIsFeeSelectorExpanded] = useState(false); + const [customFeeRate, setCustomFeeRate] = useState(); + const [isCreatingTransaction, setIsCreatingTransaction] = useState(false); + const [transactionError, setTransactionError] = useState(null); + const [validationError, setValidationError] = useState(null); + + // Fee estimates + const [feeEstimates, setFeeEstimates] = useState(); + const [isLoadingFees, setIsLoadingFees] = useState(true); + const [feeLoadingError, setFeeLoadingError] = useState(); + + // Determine if this is a token send or BTC send + const isTokenSend = !!token; + + // Whether the amount is locked (set by decoded invoice) + const isAmountLocked = !!rgbDecodedInvoice?.assignment?.amount; + + // Get the appropriate balance and decimals based on send type + const balance = isTokenSend ? tokenBalance : btcBalance; + const decimals = isTokenSend && tokenInfo ? tokenInfo.decimals : getDecimalsByNetwork(network); + const ticker = isTokenSend && tokenInfo ? tokenInfo.symbol : getTickerByNetwork(network); + + const formattedBalance = formatBalance(balance || '0', decimals); + + // Load token info when token is selected + useEffect(() => { + if (!token) { + setTokenInfo(null); + return; + } + + const loadTokenInfo = async () => { + try { + const wallet = await BackgroundExecutor.lazyInitWallet(network, accountNumber); + assert(wallet instanceof RGBWallet, 'Not an RGB wallet'); + const tokenBalances = wallet.getTokenBalances(); + const info = tokenBalances.find((t) => t.id === token); + if (info) { + setTokenInfo(info); + } + } catch (e) { + console.error('Failed to load token info:', e); + } + }; + loadTokenInfo(); + }, [token, network, accountNumber]); + + // Pre-fill amount from decoded invoice (convert base units to display units) + useEffect(() => { + if (!isAmountLocked) return; + const d = isTokenSend && tokenInfo ? tokenInfo.decimals : getDecimalsByNetwork(network); + const displayAmount = new BigNumber(rgbDecodedInvoice!.assignment!.amount).dividedBy(new BigNumber(10).pow(d)).toString(); + setLocalAmount(displayAmount); + }, [isAmountLocked, rgbDecodedInvoice, tokenInfo, isTokenSend, network]); + + // Load fee estimates + useEffect(() => { + const loadFees = async () => { + setIsLoadingFees(true); + setFeeLoadingError(undefined); + try { + const wallet = await BackgroundExecutor.lazyInitWallet(network, accountNumber); + assert(wallet instanceof RGBWallet, 'Not an RGB wallet'); + const fees = await wallet.getFeeEstimates(); + setFeeEstimates(fees); + } catch (e) { + const errorMessage = e instanceof Error ? e.message : 'Failed to load network fees'; + setFeeLoadingError(errorMessage); + } finally { + setIsLoadingFees(false); + } + }; + loadFees(); + }, [network, accountNumber]); + + const handleDenominationSwitch = () => { + if (exchangeRate) { + setDenomination(denomination === 'Native' ? 'Fiat' : 'Native'); + } + }; + + const buttonDisabled = useMemo(() => { + return isLoadingFees || (feeLoadingError && !customFeeRate) || !localAmount || isCreatingTransaction; + }, [isLoadingFees, feeLoadingError, customFeeRate, localAmount, isCreatingTransaction]); + + const [feeRate, feeIndex] = useMemo(() => { + if (selectedFeeRate !== undefined) return [selectedFeeRate, selectedFeeIndex ?? FeeIndex.Medium]; + if (customFeeRate !== undefined) return [customFeeRate, FeeIndex.Slow]; + if (feeEstimates) return [feeEstimates.medium, FeeIndex.Medium]; + return [1, FeeIndex.Slow]; + }, [selectedFeeRate, customFeeRate, feeEstimates, selectedFeeIndex]); + + const feeName = useMemo(() => { + switch (feeIndex) { + case FeeIndex.Fast: + return 'Fast'; + case FeeIndex.Medium: + return 'Medium'; + case FeeIndex.Slow: + return 'Slow'; + default: + return 'Network Fee'; + } + }, [feeIndex]); + + const handleAmountChange = (text: string) => { + const normalized = text.replace(',', '.'); + if (normalized === '' || /^\d*\.?\d*$/.test(normalized)) { + setLocalAmount(normalized); + setValidationError(null); + } + }; + + const handleCustomFeeChange = (text: string) => { + const normalized = text.replace(',', '.'); + if (normalized === '' || /^\d*\.?\d*$/.test(normalized)) { + const numValue = normalized === '' ? undefined : Number(normalized); + setCustomFeeRate(numValue); + setSelectedFeeRate(undefined); + } + }; + + const handleMaxPress = () => { + if (balance) { + // For RGB, we need to account for fees, but we'll just use the full balance for now + // The SDK will handle fee calculation + const maxAmount = formatBalance(balance, decimals); + setLocalAmount(maxAmount); + } + }; + + const handleFeeSelection = (rate: number, index: FeeIndex) => { + setSelectedFeeRate(rate); + setSelectedFeeIndex(index); + setIsFeeSelectorExpanded(false); + }; + + const toggleFeeSelector = () => { + setIsFeeSelectorExpanded(!isFeeSelectorExpanded); + }; + + const validateAmount = () => { + if (!localAmount || !balance) return { isValid: false, error: 'Please enter an amount' }; + if (localAmount.includes('.') && localAmount.split('.')[1]?.length > decimals) { + return { isValid: false, error: `Maximum ${decimals} decimal place${decimals !== 1 ? 's' : ''} allowed` }; + } + + const amt = parseFloat(localAmount); + if (isNaN(amt) || amt <= 0) return { isValid: false, error: 'Amount must be greater than 0' }; + + const satValueBN = new BigNumber(amt); + const satValue = satValueBN.multipliedBy(new BigNumber(10).pow(decimals)).toString(10); + if (!new BigNumber(balance).gte(satValue)) { + return { isValid: false, error: 'Insufficient balance' }; + } + return { isValid: true, error: null }; + }; + + const validateFee = () => { + if (feeLoadingError && customFeeRate === undefined) { + return { isValid: false, error: 'Please enter a custom fee rate' }; + } + if (customFeeRate !== undefined && (isNaN(customFeeRate) || customFeeRate <= 0)) { + return { isValid: false, error: 'Please enter a valid fee rate' }; + } + return { isValid: true, error: null }; + }; + + const handleContinue = async () => { + const amountValidation = validateAmount(); + const feeValidation = validateFee(); + + if (!amountValidation.isValid) { + setValidationError(amountValidation.error); + return; + } + + if (!feeValidation.isValid) { + setValidationError(feeValidation.error); + return; + } + + setValidationError(null); + setTransactionError(null); + setIsCreatingTransaction(true); + + await sleep(100); + + try { + const amt = parseFloat(localAmount); + const amountValueBN = new BigNumber(amt); + const amountValue = amountValueBN.multipliedBy(new BigNumber(10).pow(decimals)); + + // Get wallet + const wallet = await BackgroundExecutor.lazyInitWallet(network as any, accountNumber); + assert(wallet instanceof RGBWallet, 'Not an RGB wallet'); + + let signedData: string; + let amountInBaseUnits: number; + + if (isTokenSend && token) { + // Token send - validate invoice first using decodeRgbInvoice + if (!RGBWallet.isRgbInvoice(address)) { + throw new Error('Token sends require a valid RGB invoice (rgb:...)'); + } + + // Validate the invoice by decoding it + try { + await wallet.decodeRgbInvoice(address); + } catch (invoiceError: any) { + throw new Error(`Invalid RGB invoice: ${invoiceError.message || 'Failed to decode invoice'}`); + } + + amountInBaseUnits = amountValue.toNumber(); + + const signedPsbt = await wallet.sendTokenPrepare(token, BigInt(amountValue.toFixed(0)), address, feeRate); + signedData = signedPsbt; + + // Store signed PSBT and token info in context for confirm screen to broadcast + setRgbPreparedTx({ + signedPsbt, + feeRate, + amount: amountInBaseUnits, + tokenId: token, + invoice: address, + }); + + setCreatedTransaction({ + txhex: signedPsbt, + actualFee: feeRate * 200, // Approximate fee for RGB token tx + feeRate: feeRate, + }); + } else { + // BTC send - validate taproot address + if (!RGBWallet.isTaprootAddress(address)) { + throw new Error('BTC sends require a taproot address (bc1p/tb1p)'); + } + + amountInBaseUnits = amountValue.toNumber(); + + // Create and sign PSBT (but don't broadcast) + const signedPsbt = await wallet.sendBtcPrepare(address, amountInBaseUnits, feeRate); + signedData = signedPsbt; + + // Store signed PSBT in context + setRgbPreparedTx({ + signedPsbt, + feeRate, + amount: amountInBaseUnits, + }); + + // Also set created transaction for the confirm screen + setCreatedTransaction({ + txhex: signedPsbt, + actualFee: feeRate * 150, // Approximate fee (150 vB typical for taproot tx) + feeRate: feeRate, + }); + } + + setContextAmount(localAmount); + router.push('/send/send-confirm'); + } catch (error: any) { + console.error('Failed to create transaction:', error); + setTransactionError(error.message || 'Failed to create transaction'); + } finally { + setIsCreatingTransaction(false); + } + }; + + // Animation for fee selector + const expandAnimation = useSharedValue(0); + const chevronRotation = useSharedValue(0); + + useEffect(() => { + const duration = 100; + if (isFeeSelectorExpanded) { + expandAnimation.value = withTiming(1, { duration }); + chevronRotation.value = withTiming(1, { duration }); + } else { + expandAnimation.value = withTiming(0, { duration }); + chevronRotation.value = withTiming(0, { duration }); + } + }, [isFeeSelectorExpanded, expandAnimation, chevronRotation]); + + const animatedFeeOptionsStyle = useAnimatedStyle(() => { + const height = interpolate(expandAnimation.value, [0, 1], [0, feeEstimates ? 192 : 0], Extrapolation.CLAMP); + const opacity = interpolate(expandAnimation.value, [0, 0.1, 1], [0, 0, 1], Extrapolation.CLAMP); + return { height, opacity }; + }); + + const animatedChevronStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${chevronRotation.value}deg` }] })); + + return ( + + + + + + + + + + {validationError && ( + + + {validationError} + + )} + + {transactionError && ( + + + {transactionError} + + )} + + + {isLoadingFees && ( + + + Loading network fees... + + )} + + {feeLoadingError && ( + + + + Network fees unavailable + + {feeLoadingError || 'Unable to load network fees. Please enter a custom fee rate manually.'} + + Custom Fee Rate (sats/vB) + + + + )} + + {!isLoadingFees && !feeLoadingError && feeEstimates && ( + + + {isFeeSelectorExpanded ? ( + <> + Network Fee + + + + + ) : ( + <> + + Network Fee + + {feeName} - {feeRate} sats/vB + + + + + + + )} + + + + {FeeOptions.map((option) => ( + handleFeeSelection(feeEstimates[option.index], option.index)} + > + + {option.name} + {feeEstimates[option.key]} sats/vB + + + ))} + + + )} + + + {isCreatingTransaction ? ( + <> + + Signing... + + ) : ( + Next + )} + + + + + ); +}; + +const styles = StyleSheet.create({ + keyboardAvoidingView: { + flex: 1, + }, + container: { + flex: 1, + paddingHorizontal: 16, + justifyContent: 'space-between', + }, + inputSection: { + marginBottom: 16, + }, + errorContainer: { + flexDirection: 'row', + alignItems: 'center', + marginTop: 8, + gap: 6, + }, + errorText: { + color: 'white', + fontSize: 14, + }, + loadingContainer: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginVertical: 20, + gap: 12, + }, + loadingText: { + color: 'rgba(255, 255, 255, 0.8)', + }, + feeSelectorContainer: { + marginBottom: 30, + }, + feeSelectorHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.15)', + paddingHorizontal: 17, + borderRadius: 16, + height: 64, + }, + feeSelectorExpandedHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.15)', + paddingHorizontal: 17, + borderRadius: 16, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + height: 64, + }, + feeSelectorTitle: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 18, + fontWeight: '400', + }, + feeSelectorCollapsedContent: { + flex: 1, + }, + feeSelectorLabel: { + color: 'rgba(255, 255, 255, 0.5)', + fontSize: 14, + }, + feeSelectorSelected: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 16, + }, + feeOptionsContainer: { + backgroundColor: overlayBackgroundDeeper, + borderBottomLeftRadius: 16, + borderBottomRightRadius: 16, + overflow: 'hidden', + }, + feeOption: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 16, + backgroundColor: overlayBackgroundDeeper, + height: 64, + }, + selectedFeeOption: { + backgroundColor: 'rgba(255, 255, 255, 0.15)', + }, + feeOptionContent: { + flex: 1, + }, + feeOptionName: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 16, + fontWeight: '500', + marginBottom: 2, + }, + feeOptionRate: { + color: 'rgba(255, 255, 255, 0.5)', + fontSize: 13, + }, + continueButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.3)', + paddingVertical: 16, + borderRadius: 16, + gap: 8, + marginTop: 'auto', + }, + continueButtonText: { + color: 'rgba(255, 255, 255, 0.9)', + fontSize: 16, + fontWeight: '600', + }, + disabledButton: { + opacity: 0.5, + }, + feeErrorContainer: { + backgroundColor: 'rgba(255, 149, 0, 0.1)', + borderRadius: 16, + padding: 16, + marginBottom: 30, + borderWidth: 1, + borderColor: 'rgba(255, 149, 0, 0.3)', + }, + feeErrorHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 8, + gap: 8, + }, + feeErrorTitle: { + color: '#FF9500', + fontSize: 16, + fontWeight: '600', + }, + feeErrorText: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 14, + marginBottom: 16, + }, + customFeeInputContainer: { + gap: 12, + }, + customFeeLabel: { + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 14, + fontWeight: '500', + }, + customFeeInput: { + backgroundColor: overlayBackgroundDeeper, + borderRadius: 12, + paddingHorizontal: 16, + paddingVertical: 12, + borderWidth: 1, + borderColor: 'rgba(255, 255, 255, 0.2)', + color: 'rgba(255, 255, 255, 0.8)', + fontSize: 16, + }, +}); + +export default SendAmountRgb; diff --git a/mobile/app/send/send-confirm.tsx b/mobile/app/send/send-confirm.tsx index 585851215..c0bfffa5e 100644 --- a/mobile/app/send/send-confirm.tsx +++ b/mobile/app/send/send-confirm.tsx @@ -18,6 +18,7 @@ import * as BlueElectrum from '@shared/blue_modules/BlueElectrum'; import { EvmWallet } from '@shared/class/evm-wallet'; import { ArkWallet } from '@shared/class/wallets/ark-wallet'; import { BreezWallet } from '@shared/class/wallets/breez-wallet'; +import { RGBWallet } from '@shared/class/wallets/rgb-wallet'; import { SparkWallet } from '@shared/class/wallets/spark-wallet'; import { StacksWallet } from '@shared/class/wallets/stacks-wallet'; import { AccountNumberContext } from '@shared/hooks/AccountNumberContext'; @@ -25,13 +26,24 @@ import { NetworkContext } from '@shared/hooks/NetworkContext'; import { useCachedExchangeRate } from '@shared/hooks/useCachedExchangeRate'; import { getDecimalsByNetwork, getIsAccountBased, getIsEVM, getTickerByNetwork } from '@shared/models/network-getters'; import { formatBalance } from '@shared/modules/string-utils'; -import { NETWORK_ARK, NETWORK_ARK_MUTINYNET, NETWORK_BITCOIN, NETWORK_LIQUID, NETWORK_LIQUID_TESTNET, NETWORK_SPARK, NETWORK_STACKS, NETWORK_USDT } from '@shared/types/networks'; +import { + NETWORK_ARK, + NETWORK_ARK_MUTINYNET, + NETWORK_BITCOIN, + NETWORK_LIQUID, + NETWORK_LIQUID_TESTNET, + NETWORK_RGB, + NETWORK_RGB_TESTNET, + NETWORK_SPARK, + NETWORK_STACKS, + NETWORK_USDT, +} from '@shared/types/networks'; import { useSendFlow } from './_layout'; const SendConfirm: React.FC = ({ ticker, token }) => { const router = useRouter(); const { network: contextNetwork } = useContext(NetworkContext); - const { network, address, amount, createdTransaction, memo, liquidPrepareResult } = useSendFlow(); + const { network, address, amount, createdTransaction, memo, liquidPrepareResult, rgbPreparedTx, token: selectedTokenId } = useSendFlow(); const { accountNumber } = useContext(AccountNumberContext); const { exchangeRate } = useCachedExchangeRate(network, 'USD'); @@ -95,7 +107,8 @@ const SendConfirm: React.FC = ({ ticker, token }) => { // Redirect back in case no transaction is available const isLiquid = network === NETWORK_LIQUID || network === NETWORK_LIQUID_TESTNET; - if (!getIsAccountBased(network) && !createdTransaction && !liquidPrepareResult) { + const isRgb = network === NETWORK_RGB || network === NETWORK_RGB_TESTNET; + if (!getIsAccountBased(network) && !createdTransaction && !liquidPrepareResult && !rgbPreparedTx) { Alert.alert('No transaction available'); return ; } @@ -105,9 +118,10 @@ const SendConfirm: React.FC = ({ ticker, token }) => { const networkDecimals = getDecimalsByNetwork(network); const nativeTicker = getTickerByNetwork(network); - // For Liquid, get fee from prepare result + // For Liquid, get fee from prepare result; for RGB, estimate from fee rate const liquidFee = liquidPrepareResult?.feesSat ?? 0; - const feeToUse = isLiquid ? liquidFee : actualFee; + const rgbFee = rgbPreparedTx ? rgbPreparedTx.feeRate * 150 : 0; // Approximate vBytes for taproot tx + const feeToUse = isLiquid ? liquidFee : isRgb ? rgbFee : actualFee; const feeInNative = formatBalance(String(feeToUse), networkDecimals, 8); const feeInNativeUnits = BigNumber(feeToUse).dividedBy(new BigNumber(10).pow(networkDecimals)); @@ -184,6 +198,17 @@ const SendConfirm: React.FC = ({ ticker, token }) => { if (!result) { throw new Error('Transaction failed'); } + } else if (network === NETWORK_RGB || network === NETWORK_RGB_TESTNET) { + assert(rgbPreparedTx, 'RGB prepared transaction is required'); + + const wallet = await BackgroundExecutor.lazyInitWallet(network, accountNumber); + assert(wallet instanceof RGBWallet, 'Internal error: incorrect wallet instance'); + + if (rgbPreparedTx.tokenId) { + await wallet.sendTokenBroadcast(rgbPreparedTx.signedPsbt); + } else { + await wallet.sendBtcBroadcast(rgbPreparedTx.signedPsbt); + } } else { throw new Error('Unsupported network for broadcasting'); } diff --git a/mobile/app/transfer/_layout.tsx b/mobile/app/transfer/_layout.tsx new file mode 100644 index 000000000..88b75e348 --- /dev/null +++ b/mobile/app/transfer/_layout.tsx @@ -0,0 +1,17 @@ +import { Stack } from 'expo-router'; +import React from 'react'; + +export default function TransferLayout() { + return ( + + + + ); +} diff --git a/mobile/app/transfer/confirm.tsx b/mobile/app/transfer/confirm.tsx new file mode 100644 index 000000000..100506378 --- /dev/null +++ b/mobile/app/transfer/confirm.tsx @@ -0,0 +1,599 @@ +import { Ionicons } from '@expo/vector-icons'; +import BigNumber from 'bignumber.js'; +import { useRouter } from 'expo-router'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { ActivityIndicator, Pressable as RNPressable, ScrollView, StyleSheet, useWindowDimensions, View } from 'react-native'; +import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'; +import Animated, { interpolate, runOnJS, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; + +import Button from '@/components/Button'; +import { ThemedText } from '@/components/ThemedText'; +import TransferAssetIcon from '@/components/transfer/TransferAssetIcon'; +import { BackgroundExecutor, getOnchainDepositAddress } from '@/src/modules/background-executor'; +import { EvmWallet } from '@shared/class/evm-wallet'; +import { InterfaceSendQuotable, walletCanSendQuote } from '@shared/class/wallets/interface-send-quotable'; +import { AccountNumberContext } from '@shared/hooks/AccountNumberContext'; +import { useAssetExchangeRate } from '@shared/hooks/useAssetExchangeRate'; +import { AllNetworkInfos } from '@shared/models/all-network-infos'; +import { getAssetInfo } from '@shared/models/asset-info'; +import { sleep } from '@shared/modules/sleep'; +import { TSupportedLazyInitWalletNetworks } from '@shared/modules/wallet-utils'; +import type { AssetId } from '@shared/types/asset'; +import type { SendQuote } from '@shared/types/send-quote'; +import { EXECUTION_CLAIM, type TransferExecution } from '@shared/types/transfer'; +import { NETWORK_SPARK } from '@shared/types/networks'; +import { useTransferFlow } from '@/src/transfer/TransferFlowContext'; + +const DISMISS_THRESHOLD = 150; +const CLAIM_OPTIONS_HEIGHT = 40 * 2; // 2 option rows + +export default function TransferConfirm() { + const router = useRouter(); + const { height: screenHeight } = useWindowDimensions(); + const { sendAsset, receiveAsset, quote, setQuote, setCommitted, transferService } = useTransferFlow(); + const { accountNumber } = useContext(AccountNumberContext); + const { exchangeRate: sendRate } = useAssetExchangeRate(sendAsset); + const { exchangeRate: receiveRate } = useAssetExchangeRate(receiveAsset); + + const [isPreparing, setIsPreparing] = useState(true); + const [isConfirming, setIsConfirming] = useState(false); + const [sendQuote, setSendQuote] = useState(); + const [error, setError] = useState(''); + const [claimMode, setClaimMode] = useState<'auto' | 'manual'>('auto'); + const [claimExpanded, setClaimExpanded] = useState(false); + const claimAnim = useSharedValue(0); + const [expirySeconds, setExpirySeconds] = useState(() => (quote ? Math.max(0, quote.expiresAt - Math.floor(Date.now() / 1000)) : 0)); + const expiryInterval = useRef | null>(null); + const quotableWalletRef = useRef(undefined); + const executionRef = useRef(undefined); + const isFakeProvider = quote?.serviceName === 'Fake'; + const isNativeDeposit = quote?.serviceName === 'Native'; + const isSparkDeposit = isNativeDeposit && receiveAsset ? getAssetInfo(receiveAsset).network === NETWORK_SPARK : false; + + useEffect(() => { + if (!quote) return; + expiryInterval.current = setInterval(() => { + const remaining = Math.max(0, quote.expiresAt - Math.floor(Date.now() / 1000)); + setExpirySeconds(remaining); + if (remaining === 0 && expiryInterval.current) clearInterval(expiryInterval.current); + }, 1000); + return () => { + if (expiryInterval.current) clearInterval(expiryInterval.current); + }; + }, [quote]); + + const translateY = useSharedValue(screenHeight); + + useEffect(() => { + translateY.value = withTiming(0, { duration: 300 }); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Clear quote on unmount so going back to index doesn't reuse a consumed quote + useEffect(() => { + return () => { + setQuote(undefined); + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const getQuotableWallet = async (assetId: AssetId): Promise => { + const assetInfo = getAssetInfo(assetId); + const networkInfo = AllNetworkInfos[assetInfo.network]; + + if (networkInfo.isEVM) { + const e = new EvmWallet(); + e.network = assetInfo.network; + return e; + } + + const wallet = await BackgroundExecutor.lazyInitWallet(assetInfo.network as TSupportedLazyInitWalletNetworks, accountNumber); + return walletCanSendQuote(wallet) ? wallet : undefined; + }; + + // On mount: create shift + get send quote so everything is ready for one-tap confirm + useEffect(() => { + let cancelled = false; + + const prepare = async () => { + if (!quote || !sendAsset || !receiveAsset) return; + + const sendAssetInfo = getAssetInfo(sendAsset); + const receiveAssetInfo = getAssetInfo(receiveAsset); + + try { + const settleAddress = isFakeProvider + ? 'fake-address' + : isNativeDeposit + ? await getOnchainDepositAddress(receiveAssetInfo.network, accountNumber) + : await BackgroundExecutor.getAddress(receiveAssetInfo.network, accountNumber); + const fromAddress = isFakeProvider ? 'fake-address' : await BackgroundExecutor.getAddress(sendAssetInfo.network, accountNumber); + const execution = await transferService.executeTransfer(quote, accountNumber, settleAddress, fromAddress); + if (cancelled) return; + executionRef.current = execution; + + // Fake provider or no deposit address: skip send quote + if (isFakeProvider || !execution.depositAddress) { + setIsPreparing(false); + return; + } + + const wallet = await getQuotableWallet(sendAsset); + if (cancelled) return; + if (!wallet) { + setError('Automated sending not supported for this network yet'); + setIsPreparing(false); + return; + } + quotableWalletRef.current = wallet; + const amountSmallest = new BigNumber(quote.sendAmount).multipliedBy(new BigNumber(10).pow(sendAssetInfo.decimals)).toFixed(0); + + const sq = await wallet.getSendQuote({ + toAddress: execution.depositAddress, + amount: amountSmallest, + fromAddress, + tokenId: sendAssetInfo.tokenId, + }); + if (cancelled) return; + setSendQuote(sq); + } catch (e: any) { + if (!cancelled) setError(e.message || 'Failed to prepare transfer'); + } finally { + if (!cancelled) setIsPreparing(false); + } + }; + + prepare(); + return () => { + cancelled = true; + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Single confirm: commit + broadcast + const handleConfirm = async () => { + setIsConfirming(true); + setError(''); + await sleep(10); + + try { + if (isExpired) throw new Error('Quote has expired. Please go back and get a new quote.'); + const execution = executionRef.current; + if (!execution) throw new Error('Transfer not ready. Please go back and try again.'); + + if (execution.type === EXECUTION_CLAIM) { + execution.autoClaim = !isSparkDeposit || claimMode === 'auto'; + } + + // Fake provider: commit and go straight to success + if (isFakeProvider) { + await transferService.commitTransfer(execution); + setCommitted(true); + router.replace('/modals/transfer-success'); + return; + } + + if (!sendQuote) throw new Error('Send quote not available. Please go back and try again.'); + + await transferService.commitTransfer(execution); + + const wallet = quotableWalletRef.current; + if (!wallet) throw new Error('Wallet does not support send quote'); + const mnemonic = await BackgroundExecutor.getMasterSeed(); + const txid = await wallet.executeSendQuote(sendQuote, mnemonic, accountNumber); + + // Update with deposit txid after successful send + execution.depositTxid = txid; + await transferService.commitTransfer(execution); + + setCommitted(true); + router.replace('/modals/transfer-success'); + } catch (e: any) { + // If send was already broadcast, update transfer with txid + const execution = executionRef.current; + if (execution?.depositTxid) { + await transferService.commitTransfer(execution); + } + setError(e.message || 'Failed to send funds'); + } finally { + setIsConfirming(false); + } + }; + + const handleDismiss = useCallback(() => { + router.back(); + }, [router]); + + const animateDismiss = useCallback(() => { + translateY.value = withTiming(screenHeight, { duration: 250 }, () => { + runOnJS(handleDismiss)(); + }); + }, [translateY, screenHeight, handleDismiss]); + + const panGesture = Gesture.Pan() + .onUpdate((event) => { + if (event.translationY > 0) { + translateY.value = event.translationY; + } + }) + .onEnd((event) => { + if (event.translationY > DISMISS_THRESHOLD || event.velocityY > 1000) { + runOnJS(animateDismiss)(); + } else { + translateY.value = withTiming(0, { duration: 200 }); + } + }); + + const cardAnimatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + })); + + const overlayAnimatedStyle = useAnimatedStyle(() => ({ + opacity: 1 - translateY.value / (screenHeight * 0.75), + })); + + const toggleClaim = () => { + const next = !claimExpanded; + setClaimExpanded(next); + claimAnim.value = withTiming(next ? 1 : 0, { duration: 250 }); + }; + + const selectClaim = (mode: 'auto' | 'manual') => { + setClaimMode(mode); + setClaimExpanded(false); + claimAnim.value = withTiming(0, { duration: 250 }); + }; + + const claimOptionsStyle = useAnimatedStyle(() => ({ + height: interpolate(claimAnim.value, [0, 1], [0, CLAIM_OPTIONS_HEIGHT]), + opacity: interpolate(claimAnim.value, [0, 0.5, 1], [0, 0, 1]), + overflow: 'hidden' as const, + })); + + const formatTime = (seconds: number) => { + if (seconds >= 3600) return `~${Math.round(seconds / 3600)}h`; + if (seconds >= 60) return `~${Math.round(seconds / 60)}m`; + return `${seconds}s`; + }; + + const formatExpiry = (seconds: number) => { + const m = Math.floor(seconds / 60); + const s = seconds % 60; + return `${m}:${s.toString().padStart(2, '0')}`; + }; + + const sendAmount = quote?.sendAmount ?? ''; + const receiveAmount = quote?.receiveAmount ?? ''; + const sendFiat = sendRate && sendAmount ? `$${new BigNumber(sendAmount).multipliedBy(sendRate).toFixed(2)}` : ''; + const receiveFiat = receiveRate && receiveAmount ? `$${new BigNumber(receiveAmount).multipliedBy(receiveRate).toFixed(2)}` : ''; + + const isExpired = !isNativeDeposit && expirySeconds <= 0; + const isReady = !isPreparing && !error && !isExpired; + + if (!sendAsset || !receiveAsset || !quote) { + router.back(); + return null; + } + + const sendAssetInfo = getAssetInfo(sendAsset); + const receiveAssetInfo = getAssetInfo(receiveAsset); + const feeDisplay = sendQuote + ? `${new BigNumber(sendQuote.fee).dividedBy(new BigNumber(10).pow(AllNetworkInfos[sendAssetInfo.network].decimals)).toFixed()} ${sendQuote.feeTicker}` + : quote.feeTicker + ? `${quote.fee} ${quote.feeTicker}` + : quote.fee; + + return ( + + + + + + + + + + + {isPreparing ? ( + + + Preparing transfer... + + ) : ( + <> + + + {/* Send Amount Block */} + + + + {sendAmount} {sendAssetInfo.ticker} + + {sendFiat || '$0.00'} + + + + + {/* Arrow Divider */} + + + + + + + {/* Receive Amount Block */} + + + + {receiveAmount} {receiveAssetInfo.ticker} + + {receiveFiat || '$0.00'} + + + + + {/* Details */} + + + Rate + + {quote.rate} + + + + + + Fees + + {feeDisplay} + + + + + + Est. time + {formatTime(quote.estimatedTime)} + + + {!isNativeDeposit && ( + + Quote expires + {expirySeconds > 0 ? formatExpiry(expirySeconds) : 'expired'} + + )} + + {quote.serviceName && ( + + Provider + {quote.serviceName} + + )} + + {isSparkDeposit && ( + + + Claim + + {claimMode} + + + + + selectClaim('auto')} testID="ConfirmClaimAuto"> + auto + {claimMode === 'auto' && } + + selectClaim('manual')} testID="ConfirmClaimManual"> + manual + {claimMode === 'manual' && } + + + + )} + + + {/* Error */} + {error ? ( + + {error} + + ) : null} + + + + {/* Confirm Button */} + + {isConfirming ? ( + + + + ) : ( +