remove evm + add xrp
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
# Koins
|
# Koins
|
||||||
|
|
||||||
Multi-chain desktop wallet for **Ethereum**, **BSC**, **Polygon**, and **Monero**. Built with [Bun](https://bun.sh) + [Electrobun](https://electrobun.dev) + Svelte.
|
Desktop wallet for **Monero** and **XRP**. Built with [Bun](https://bun.sh) + [Electrobun](https://electrobun.dev) + Svelte.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Multi-coin mode**: ETH, BSC, Polygon wallet with seed phrase vault stored in system keychain
|
|
||||||
- **Monero mode**: Self-contained Monero wallet — auto-downloads `monero-wallet-rpc` binary on first launch, no manual binary install
|
- **Monero mode**: Self-contained Monero wallet — auto-downloads `monero-wallet-rpc` binary on first launch, no manual binary install
|
||||||
|
- **XRP mode**: XRP Ledger wallet — generate or import XRP seeds, send payments with optional destination tags
|
||||||
- **Touch ID unlock**: Biometric auth via Swift helper, no node-gyp or FFI dylib
|
- **Touch ID unlock**: Biometric auth via Swift helper, no node-gyp or FFI dylib
|
||||||
- **Password fallback**: PBKDF2-salted password stored in keychain, used when biometric is unavailable
|
- **Password fallback**: PBKDF2-salted password stored in keychain, used when biometric is unavailable
|
||||||
- **QR codes**: Address QR with chain logo overlay
|
- **QR codes**: Address QR with chain logo overlay
|
||||||
- **Transaction history**: Cached in local SQLite via Drizzle ORM
|
- **Transaction history**: Live XRP ledger transactions and Monero wallet transactions
|
||||||
- **Settings**: Reset app clears all DB tables and keychain entries
|
- **Settings**: Reset app clears all keychain entries
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -35,41 +35,38 @@ apps/desktop/
|
|||||||
│ │ │ ├── monero/ # Monero RPC management
|
│ │ │ ├── monero/ # Monero RPC management
|
||||||
│ │ │ │ ├── binary.ts # Download/extract monero-wallet-rpc
|
│ │ │ │ ├── binary.ts # Download/extract monero-wallet-rpc
|
||||||
│ │ │ │ └── wallet.ts # Child process + RPC client
|
│ │ │ │ └── wallet.ts # Child process + RPC client
|
||||||
│ │ │ ├── alchemy/ # Alchemy API calls
|
│ │ │ ├── xrp/ # XRP Ledger client (xrpl)
|
||||||
│ │ │ ├── viem/ # viem chain calls
|
|
||||||
│ │ │ ├── tokens/ # Token metadata (DB-first cache)
|
|
||||||
│ │ │ ├── transactions/ # Tx history + details (DB-first cache)
|
|
||||||
│ │ │ ├── db/ # Drizzle ORM + SQLite schema
|
|
||||||
│ │ │ └── biometric.ts # Biometric auth (Swift subprocess)
|
│ │ │ └── biometric.ts # Biometric auth (Swift subprocess)
|
||||||
|
│ │ ├── rpc/
|
||||||
|
│ │ │ ├── monero/ # Monero RPC handlers
|
||||||
|
│ │ │ └── xrp/ # XRP RPC handlers
|
||||||
│ │ └── native/
|
│ │ └── native/
|
||||||
│ │ └── biometric-helper.swift # Touch ID prompt binary
|
│ │ └── biometric-helper.swift # Touch ID prompt binary
|
||||||
│ ├── lib/
|
│ ├── lib/
|
||||||
│ │ ├── states/
|
│ │ ├── states/
|
||||||
│ │ │ └── wallet.svelte.ts # Wallet state (singleton)
|
│ │ │ ├── monero-wallet.svelte.ts # Monero wallet state (singleton)
|
||||||
│ │ ├── components/
|
│ │ │ └── xrp-wallet.svelte.ts # XRP wallet state (singleton)
|
||||||
│ │ │ └── EvmDashboard.svelte # Shared EVM dashboard
|
|
||||||
│ │ └── electrobun.ts # RPC type definitions
|
│ │ └── electrobun.ts # RPC type definitions
|
||||||
│ └── routes/
|
│ └── routes/
|
||||||
│ ├── index.svelte # Login/welcome page
|
│ ├── index.svelte # Login/welcome page
|
||||||
│ ├── multicoin/ # ETH / BSC / Polygon routes
|
│ ├── monero/ # Monero routes
|
||||||
│ ├── monero/ # Monero dashboard
|
│ ├── xrp/ # XRP routes
|
||||||
│ ├── settings.svelte # App settings
|
│ └── settings.svelte # App settings
|
||||||
│ └── tx/ # Transaction details
|
|
||||||
└── package.json
|
└── package.json
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Decisions
|
## Key Decisions
|
||||||
|
|
||||||
- **Monero via RPC, not WASM**: `monero-ts`'s WASM wallet (`MoneroWalletFull`) doesn't work in Bun because the C++ HTTP client fails at runtime. Instead, `monero-wallet-rpc` runs as a child process.
|
- **Monero via RPC, not WASM**: `monero-ts`'s WASM wallet (`MoneroWalletFull`) doesn't work in Bun because the C++ HTTP client fails at runtime. Instead, `monero-wallet-rpc` runs as a child process.
|
||||||
- **SQLite cache**: All chain data (tx history, receipts, token metadata) cached in SQLite via Drizzle, not localStorage.
|
- **XRP via xrpl**: Uses the official `xrpl` library over the public `wss://s1.ripple.com` WebSocket server. XRP seeds are stored in the system keychain.
|
||||||
|
- **Keychain storage**: Wallet seeds and metadata are stored in the macOS keychain via `Bun.secrets`, not a local database.
|
||||||
- **Biometric auth**: Uses a Swift binary spawned via `Bun.spawnSync()` (not `dlopen` — Bun crashes on ObjC runtime; not `node-mac-auth` — fragile node-gyp chain).
|
- **Biometric auth**: Uses a Swift binary spawned via `Bun.spawnSync()` (not `dlopen` — Bun crashes on ObjC runtime; not `node-mac-auth` — fragile node-gyp chain).
|
||||||
- **Separate account types**: EVM and Monero are separate account types with their own seed formats and auth flows.
|
- **Separate account types**: Monero and XRP are separate account types with their own seed formats and auth flows.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- [Viem](https://viem.sh) — EVM chain RPC calls + wallet operations
|
- [xrpl](https://github.com/XRPLF/xrpl.js) — XRP Ledger client
|
||||||
- [monero-ts](https://github.com/woodser/monero-ts) — Monero RPC client
|
- [monero-ts](https://github.com/woodser/monero-ts) — Monero RPC client
|
||||||
- [Drizzle ORM](https://orm.drizzle.team) — SQLite schema + queries
|
|
||||||
- [Svelte 5](https://svelte.dev) — UI framework
|
- [Svelte 5](https://svelte.dev) — UI framework
|
||||||
- [Electrobun](https://electrobun.dev) — Desktop app shell
|
- [Electrobun](https://electrobun.dev) — Desktop app shell
|
||||||
- [sv-router](https://github.com/TZGyn/sv-router) — File-based routing
|
- [sv-router](https://github.com/TZGyn/sv-router) — File-based routing
|
||||||
@@ -80,5 +77,4 @@ apps/desktop/
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run dev # Start development server
|
bun run dev # Start development server
|
||||||
bun run db:push # Push Drizzle schema to SQLite
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { defineConfig } from 'drizzle-kit'
|
|
||||||
import envPaths from 'env-paths'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { mkdirSync } from 'fs'
|
|
||||||
import { getENV } from './src/bun/lib/get-env'
|
|
||||||
|
|
||||||
const env = getENV()
|
|
||||||
|
|
||||||
const { data } = envPaths(env.appName, { suffix: '' })
|
|
||||||
mkdirSync(data, { recursive: true })
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
out: './drizzle',
|
|
||||||
schema: './src/bun/lib/db/schema.ts',
|
|
||||||
dialect: 'sqlite',
|
|
||||||
dbCredentials: {
|
|
||||||
url: join(data, env.dbFilename),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
CREATE TABLE `token_metadata` (
|
|
||||||
`name` text,
|
|
||||||
`symbol` text NOT NULL,
|
|
||||||
`decimals` integer,
|
|
||||||
`logo` text,
|
|
||||||
`contractAddress` text NOT NULL,
|
|
||||||
`chainId` text NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `contract_unique` ON `token_metadata` (`chainId`,`contractAddress`);
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "7",
|
|
||||||
"dialect": "sqlite",
|
|
||||||
"id": "2845d968-33cd-48b6-8944-03e0e46a4a90",
|
|
||||||
"prevIds": [
|
|
||||||
"00000000-0000-0000-0000-000000000000"
|
|
||||||
],
|
|
||||||
"ddl": [
|
|
||||||
{
|
|
||||||
"name": "token_metadata",
|
|
||||||
"entityType": "tables"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "name",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "symbol",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "decimals",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "logo",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "contractAddress",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "chainId",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"value": "chainId",
|
|
||||||
"isExpression": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "contractAddress",
|
|
||||||
"isExpression": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": true,
|
|
||||||
"where": null,
|
|
||||||
"origin": "manual",
|
|
||||||
"name": "contract_unique",
|
|
||||||
"entityType": "indexes",
|
|
||||||
"table": "token_metadata"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"renames": []
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
CREATE TABLE `transaction_receipts` (
|
|
||||||
`transactionHash` text NOT NULL,
|
|
||||||
`chainId` text NOT NULL,
|
|
||||||
`blockHash` text NOT NULL,
|
|
||||||
`blockNumber` text NOT NULL,
|
|
||||||
`from` text NOT NULL,
|
|
||||||
`to` text,
|
|
||||||
`cumulativeGasUsed` text NOT NULL,
|
|
||||||
`gasUsed` text NOT NULL,
|
|
||||||
`status` text NOT NULL,
|
|
||||||
`effectiveGasPrice` text,
|
|
||||||
`contractAddress` text,
|
|
||||||
`type` text NOT NULL,
|
|
||||||
`transactionIndex` integer NOT NULL,
|
|
||||||
`logsBloom` text
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE `transactions` (
|
|
||||||
`hash` text NOT NULL,
|
|
||||||
`chainId` text NOT NULL,
|
|
||||||
`from` text NOT NULL,
|
|
||||||
`to` text,
|
|
||||||
`value` text NOT NULL,
|
|
||||||
`blockNumber` text,
|
|
||||||
`blockHash` text,
|
|
||||||
`gas` text NOT NULL,
|
|
||||||
`gasPrice` text,
|
|
||||||
`maxFeePerGas` text,
|
|
||||||
`maxPriorityFeePerGas` text,
|
|
||||||
`input` text NOT NULL,
|
|
||||||
`nonce` integer NOT NULL,
|
|
||||||
`transactionIndex` integer,
|
|
||||||
`type` text,
|
|
||||||
`v` text,
|
|
||||||
`r` text,
|
|
||||||
`s` text,
|
|
||||||
`yParity` integer
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `receipt_unique` ON `transaction_receipts` (`chainId`,`transactionHash`);--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX `tx_unique` ON `transactions` (`chainId`,`hash`);
|
|
||||||
@@ -1,467 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "7",
|
|
||||||
"dialect": "sqlite",
|
|
||||||
"id": "3271a353-c528-4d23-a2ff-9be22f763b9d",
|
|
||||||
"prevIds": [
|
|
||||||
"2845d968-33cd-48b6-8944-03e0e46a4a90"
|
|
||||||
],
|
|
||||||
"ddl": [
|
|
||||||
{
|
|
||||||
"name": "token_metadata",
|
|
||||||
"entityType": "tables"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "transaction_receipts",
|
|
||||||
"entityType": "tables"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "transactions",
|
|
||||||
"entityType": "tables"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "name",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "symbol",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "decimals",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "logo",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "contractAddress",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "chainId",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "transactionHash",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "chainId",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "blockHash",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "blockNumber",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "from",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "to",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "cumulativeGasUsed",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "gasUsed",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "status",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "effectiveGasPrice",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "contractAddress",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "type",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "transactionIndex",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "logsBloom",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "hash",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "chainId",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "from",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "to",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "value",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "blockNumber",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "blockHash",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "gas",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "gasPrice",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "maxFeePerGas",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "maxPriorityFeePerGas",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "input",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"notNull": true,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "nonce",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "transactionIndex",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "type",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "v",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "r",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "s",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "integer",
|
|
||||||
"notNull": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"default": null,
|
|
||||||
"generated": null,
|
|
||||||
"name": "yParity",
|
|
||||||
"entityType": "columns",
|
|
||||||
"table": "transactions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"value": "chainId",
|
|
||||||
"isExpression": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "contractAddress",
|
|
||||||
"isExpression": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": true,
|
|
||||||
"where": null,
|
|
||||||
"origin": "manual",
|
|
||||||
"name": "contract_unique",
|
|
||||||
"entityType": "indexes",
|
|
||||||
"table": "token_metadata"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"value": "chainId",
|
|
||||||
"isExpression": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "transactionHash",
|
|
||||||
"isExpression": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": true,
|
|
||||||
"where": null,
|
|
||||||
"origin": "manual",
|
|
||||||
"name": "receipt_unique",
|
|
||||||
"entityType": "indexes",
|
|
||||||
"table": "transaction_receipts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"value": "chainId",
|
|
||||||
"isExpression": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "hash",
|
|
||||||
"isExpression": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"isUnique": true,
|
|
||||||
"where": null,
|
|
||||||
"origin": "manual",
|
|
||||||
"name": "tx_unique",
|
|
||||||
"entityType": "indexes",
|
|
||||||
"table": "transactions"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"renames": []
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ export default {
|
|||||||
'dist/index.html': 'views/mainview/index.html',
|
'dist/index.html': 'views/mainview/index.html',
|
||||||
'dist/assets': 'views/mainview/assets',
|
'dist/assets': 'views/mainview/assets',
|
||||||
'dist/icons': 'views/mainview/icons',
|
'dist/icons': 'views/mainview/icons',
|
||||||
'drizzle': 'drizzle',
|
|
||||||
'src/bun/native/biometric-helper': 'native/biometric-helper',
|
'src/bun/native/biometric-helper': 'native/biometric-helper',
|
||||||
},
|
},
|
||||||
// Ignore Vite output in watch mode — HMR handles view rebuilds separately
|
// Ignore Vite output in watch mode — HMR handles view rebuilds separately
|
||||||
|
|||||||
@@ -11,23 +11,19 @@
|
|||||||
"build:canary": "bun run build:native && vite build && electrobun build --env=canary",
|
"build:canary": "bun run build:native && vite build && electrobun build --env=canary",
|
||||||
"build:stable": "bun run build:native && vite build && electrobun build --env=stable",
|
"build:stable": "bun run build:native && vite build && electrobun build --env=stable",
|
||||||
"build:win": "vite build && electrobun build --env=stable",
|
"build:win": "vite build && electrobun build --env=stable",
|
||||||
"db:push": "bun x --bun drizzle-kit push",
|
|
||||||
"db:generate": "drizzle-kit generate",
|
|
||||||
"build:native": "swiftc -O -o src/bun/native/biometric-helper src/bun/native/biometric-helper.swift"
|
"build:native": "swiftc -O -o src/bun/native/biometric-helper src/bun/native/biometric-helper.swift"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alchemy/common": "^5.0.0",
|
|
||||||
"@alchemy/wallet-apis": "^5.0.0",
|
|
||||||
"@koins/utils": "workspace:*",
|
"@koins/utils": "workspace:*",
|
||||||
"d3-scale": "^4.0.2",
|
"d3-scale": "^4.0.2",
|
||||||
"d3-shape": "^3.2.0",
|
"d3-shape": "^3.2.0",
|
||||||
"drizzle-orm": "^1.0.0-beta.22",
|
|
||||||
"electrobun": "^1.18.1",
|
"electrobun": "^1.18.1",
|
||||||
|
"env-paths": "^4.0.0",
|
||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"monero-ts": "^0.11.8",
|
"monero-ts": "^0.11.8",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"sv-router": "latest",
|
"sv-router": "latest",
|
||||||
"viem": "^2.48.11"
|
"xrpl": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fontsource-variable/geist-mono": "^5.2.7",
|
"@fontsource-variable/geist-mono": "^5.2.7",
|
||||||
@@ -45,7 +41,6 @@
|
|||||||
"bun-types": "^1.3.14",
|
"bun-types": "^1.3.14",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"drizzle-kit": "^1.0.0-beta.22",
|
|
||||||
"embla-carousel-svelte": "^8.6.0",
|
"embla-carousel-svelte": "^8.6.0",
|
||||||
"formsnap": "^2.0.1",
|
"formsnap": "^2.0.1",
|
||||||
"layerchart": "2.0.0-next.48",
|
"layerchart": "2.0.0-next.48",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
@@ -6,36 +6,21 @@ import {
|
|||||||
Updater,
|
Updater,
|
||||||
Utils,
|
Utils,
|
||||||
} from 'electrobun/bun'
|
} from 'electrobun/bun'
|
||||||
import { migrate } from 'drizzle-orm/bun-sqlite/migrator'
|
|
||||||
import { db } from './lib/db'
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import { getENV } from './lib/get-env'
|
import { getENV } from './lib/get-env'
|
||||||
import { createEvmHandlers } from './rpc/evm'
|
|
||||||
import { createMoneroHandlers } from './rpc/monero'
|
import { createMoneroHandlers } from './rpc/monero'
|
||||||
|
import { createXrpHandlers } from './rpc/xrp'
|
||||||
import type { MoneroWalletState } from './lib/monero'
|
import type { MoneroWalletState } from './lib/monero'
|
||||||
import { canPromptTouchID, promptTouchID } from './lib/biometric'
|
import { canPromptTouchID, promptTouchID } from './lib/biometric'
|
||||||
import { sqlite } from './lib/sqlite'
|
import { getXrpWalletList } from './lib/xrp'
|
||||||
import { evmWallets } from './lib/db/schema'
|
|
||||||
import type { RPC } from '../lib/rpc-schema'
|
import type { RPC } from '../lib/rpc-schema'
|
||||||
import { log, logError } from './lib/logger'
|
import { log } from './lib/logger'
|
||||||
|
|
||||||
const env = getENV()
|
const env = getENV()
|
||||||
|
|
||||||
log(`env: ${JSON.stringify(env)}`)
|
log(`env: ${JSON.stringify(env)}`)
|
||||||
|
|
||||||
if (env.env === 'stable') {
|
|
||||||
try {
|
|
||||||
log('running migrations...')
|
|
||||||
migrate(db, {
|
|
||||||
migrationsFolder: join(PATHS.RESOURCES_FOLDER, 'app', 'drizzle'),
|
|
||||||
})
|
|
||||||
log('migrations complete')
|
|
||||||
} catch (e) {
|
|
||||||
logError('migration failed', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEV_SERVER_PORT = 5173
|
const DEV_SERVER_PORT = 5173
|
||||||
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`
|
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`
|
||||||
|
|
||||||
@@ -92,10 +77,6 @@ const moneroState = {
|
|||||||
manager: null as MoneroWalletState | null,
|
manager: null as MoneroWalletState | null,
|
||||||
downloading: false,
|
downloading: false,
|
||||||
}
|
}
|
||||||
const syncState = {
|
|
||||||
target: null as { address: string; chainid: string } | null,
|
|
||||||
timer: null as ReturnType<typeof setInterval> | null,
|
|
||||||
}
|
|
||||||
|
|
||||||
const rpc = BrowserView.defineRPC<RPC>({
|
const rpc = BrowserView.defineRPC<RPC>({
|
||||||
maxRequestTime: 120000,
|
maxRequestTime: 120000,
|
||||||
@@ -108,11 +89,11 @@ const rpc = BrowserView.defineRPC<RPC>({
|
|||||||
rpc.setRequestHandler({
|
rpc.setRequestHandler({
|
||||||
resetApp: async () => {
|
resetApp: async () => {
|
||||||
try {
|
try {
|
||||||
const wallets = db.select().from(evmWallets).all()
|
const wallets = await getXrpWalletList()
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
Bun.secrets.delete({
|
Bun.secrets.delete({
|
||||||
service: 'koins',
|
service: 'koins',
|
||||||
name: 'alchemy_key',
|
name: 'xrp_wallets',
|
||||||
}),
|
}),
|
||||||
...wallets.flatMap((w) => [
|
...wallets.flatMap((w) => [
|
||||||
Bun.secrets.delete({
|
Bun.secrets.delete({
|
||||||
@@ -121,15 +102,10 @@ rpc.setRequestHandler({
|
|||||||
}),
|
}),
|
||||||
Bun.secrets.delete({
|
Bun.secrets.delete({
|
||||||
service: 'koins',
|
service: 'koins',
|
||||||
name: `evm_auth_${w.id}`,
|
name: `xrp_auth_${w.id}`,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
sqlite.run('DELETE FROM token_metadata')
|
|
||||||
sqlite.run('DELETE FROM transactions')
|
|
||||||
sqlite.run('DELETE FROM transaction_receipts')
|
|
||||||
sqlite.run('DELETE FROM tx_history')
|
|
||||||
sqlite.run('DELETE FROM evm_wallets')
|
|
||||||
console.log('[rpc] resetApp complete')
|
console.log('[rpc] resetApp complete')
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -174,8 +150,8 @@ rpc.setRequestHandler({
|
|||||||
logToFile: async ({ message }) => {
|
logToFile: async ({ message }) => {
|
||||||
log(message)
|
log(message)
|
||||||
},
|
},
|
||||||
...createEvmHandlers(rpc, syncState),
|
|
||||||
...createMoneroHandlers(moneroState),
|
...createMoneroHandlers(moneroState),
|
||||||
|
...createXrpHandlers(),
|
||||||
})
|
})
|
||||||
|
|
||||||
log('creating BrowserWindow...')
|
log('creating BrowserWindow...')
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import { tryCatch } from '@koins/utils'
|
|
||||||
|
|
||||||
const TIMEOUT = 15_000
|
|
||||||
|
|
||||||
export const post = async <T>(
|
|
||||||
url: string,
|
|
||||||
body: any,
|
|
||||||
): Promise<{ jsonrpc: '2.0'; id: number; result: T }> => {
|
|
||||||
const [response, error] = await tryCatch(
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
signal: AbortSignal.timeout(TIMEOUT),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.log(error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
const [data, dataError] = await tryCatch(response.json())
|
|
||||||
|
|
||||||
if (dataError) {
|
|
||||||
console.log(dataError)
|
|
||||||
throw dataError
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { post } from './call'
|
|
||||||
import { getChainID, getURL } from './network'
|
|
||||||
import { inspect } from 'util'
|
|
||||||
|
|
||||||
export const getTokensBalances = async (
|
|
||||||
key: string,
|
|
||||||
chainid: string,
|
|
||||||
address: string,
|
|
||||||
) => {
|
|
||||||
const chainId = getChainID(chainid)
|
|
||||||
if (!chainId) return
|
|
||||||
|
|
||||||
const url = getURL({
|
|
||||||
chainId,
|
|
||||||
key,
|
|
||||||
})
|
|
||||||
|
|
||||||
const balRes = await post<{
|
|
||||||
address: `0x${string}`
|
|
||||||
tokenBalances: {
|
|
||||||
contractAddress: `0x${string}`
|
|
||||||
tokenBalance: string
|
|
||||||
}[]
|
|
||||||
}>(url, {
|
|
||||||
jsonrpc: '2.0',
|
|
||||||
id: 0,
|
|
||||||
method: 'alchemy_getTokenBalances',
|
|
||||||
params: [address, 'erc20'],
|
|
||||||
})
|
|
||||||
|
|
||||||
return balRes
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { post } from './call'
|
|
||||||
import { getChainID, getURL } from './network'
|
|
||||||
|
|
||||||
export const getTokenMetadata = async (
|
|
||||||
key: string,
|
|
||||||
chainid: string,
|
|
||||||
tokenAddress: string,
|
|
||||||
) => {
|
|
||||||
const chainId = getChainID(chainid)
|
|
||||||
if (!chainId) return
|
|
||||||
|
|
||||||
const url = getURL({ chainId, key })
|
|
||||||
|
|
||||||
return await post<{
|
|
||||||
name: string | null
|
|
||||||
symbol: string | null
|
|
||||||
decimals: number | null
|
|
||||||
logo: string | null
|
|
||||||
}>(url, {
|
|
||||||
jsonrpc: '2.0',
|
|
||||||
id: 0,
|
|
||||||
method: 'alchemy_getTokenMetadata',
|
|
||||||
params: [tokenAddress],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import { post } from './call'
|
|
||||||
import { getChainID, getURL } from './network'
|
|
||||||
import type { Transaction } from './types'
|
|
||||||
import { numberToHex } from 'viem'
|
|
||||||
|
|
||||||
type PageResult = {
|
|
||||||
transfers: Transaction[]
|
|
||||||
pageKey?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchPage(
|
|
||||||
url: string,
|
|
||||||
fromAddr: string | undefined,
|
|
||||||
toAddr: string | undefined,
|
|
||||||
fromBlock: string | undefined,
|
|
||||||
chainId: string,
|
|
||||||
): Promise<{ result?: PageResult }> {
|
|
||||||
const category = ['external', 'erc20']
|
|
||||||
if (chainId !== '56') {
|
|
||||||
category.push('internal')
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
jsonrpc: '2.0',
|
|
||||||
id: 0,
|
|
||||||
method: 'alchemy_getAssetTransfers',
|
|
||||||
params: [
|
|
||||||
{
|
|
||||||
...(fromAddr ? { fromAddress: fromAddr } : {}),
|
|
||||||
...(toAddr ? { toAddress: toAddr } : {}),
|
|
||||||
...(fromBlock ? { fromBlock } : {}),
|
|
||||||
category,
|
|
||||||
withMetadata: true,
|
|
||||||
maxCount: numberToHex(1000),
|
|
||||||
order: 'asc' as const,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
return post<PageResult>(url, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getTransactionHistory = async (
|
|
||||||
key: string,
|
|
||||||
chainid: string,
|
|
||||||
address: string,
|
|
||||||
opts: { maxCount: number; fromBlock?: string },
|
|
||||||
) => {
|
|
||||||
const chainId = getChainID(chainid)
|
|
||||||
if (!chainId) return
|
|
||||||
|
|
||||||
const url = getURL({ chainId, key })
|
|
||||||
const seen = new Set<string>()
|
|
||||||
const all: Transaction[] = []
|
|
||||||
|
|
||||||
for (const direction of ['out', 'in'] as const) {
|
|
||||||
const fromAddr = direction === 'out' ? address : undefined
|
|
||||||
const toAddr = direction === 'in' ? address : undefined
|
|
||||||
let cursorBlock = opts.fromBlock
|
|
||||||
|
|
||||||
for (let page = 0; page < 100; page++) {
|
|
||||||
const res = await fetchPage(url, fromAddr, toAddr, cursorBlock, chainId)
|
|
||||||
const transfers = res.result?.transfers ?? []
|
|
||||||
if (transfers.length === 0) break
|
|
||||||
|
|
||||||
let pageMaxBlock = 0
|
|
||||||
let firstTimestamp = ''
|
|
||||||
let lastTimestamp = ''
|
|
||||||
for (const t of transfers) {
|
|
||||||
if (!t.hash) continue
|
|
||||||
const dedupKey = `${t.hash}_${t.category}_${t.rawContract?.address ?? ''}`
|
|
||||||
if (seen.has(dedupKey)) continue
|
|
||||||
seen.add(dedupKey)
|
|
||||||
all.push(t)
|
|
||||||
|
|
||||||
if (t.blockNum) {
|
|
||||||
const blockNum = parseInt(t.blockNum, 16)
|
|
||||||
if (blockNum > pageMaxBlock) {
|
|
||||||
pageMaxBlock = blockNum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (t.metadata?.blockTimestamp) {
|
|
||||||
if (!firstTimestamp) firstTimestamp = t.metadata.blockTimestamp
|
|
||||||
lastTimestamp = t.metadata.blockTimestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[alchemy] ${direction} page ${page + 1}: ${transfers.length} transfers, ` +
|
|
||||||
`block ${pageMaxBlock}, ` +
|
|
||||||
`${firstTimestamp ? new Date(firstTimestamp).toISOString() : '?'} → ${lastTimestamp ? new Date(lastTimestamp).toISOString() : '?'}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (pageMaxBlock === 0) break
|
|
||||||
cursorBlock = numberToHex(pageMaxBlock + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[alchemy] total: ${all.length} transfers for ${chainid}/${address.slice(0, 10)}... ` +
|
|
||||||
`from ${opts.fromBlock ?? 'genesis'}`,
|
|
||||||
)
|
|
||||||
return all
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './get-token-balances'
|
|
||||||
export * from './get-token-metadata'
|
|
||||||
export * from './types'
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
export const ALCHEMY_NETWORKS = {
|
|
||||||
'1': 'eth-mainnet',
|
|
||||||
'137': 'polygon-mainnet',
|
|
||||||
'56': 'bnb-mainnet',
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export type ChainID = keyof typeof ALCHEMY_NETWORKS
|
|
||||||
|
|
||||||
export const CHAIN_ID = Object.keys(
|
|
||||||
ALCHEMY_NETWORKS,
|
|
||||||
) as Array<ChainID>
|
|
||||||
|
|
||||||
export const getChainID = (id: string) => {
|
|
||||||
if (CHAIN_ID.includes(id as ChainID)) {
|
|
||||||
return id as ChainID
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getURL = ({
|
|
||||||
key,
|
|
||||||
chainId,
|
|
||||||
}: {
|
|
||||||
chainId: ChainID
|
|
||||||
key: string
|
|
||||||
}) => {
|
|
||||||
const network = ALCHEMY_NETWORKS[chainId]
|
|
||||||
const url = `https://${network}.g.alchemy.com/v2/${key}`
|
|
||||||
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
export type Token = {}
|
|
||||||
|
|
||||||
export type TokenBalance = {}
|
|
||||||
|
|
||||||
export type Transaction = {
|
|
||||||
category?:
|
|
||||||
| 'external'
|
|
||||||
| 'internal'
|
|
||||||
| 'token'
|
|
||||||
| 'erc20'
|
|
||||||
| 'erc721'
|
|
||||||
| 'erc1155'
|
|
||||||
| 'specialnft'
|
|
||||||
blockNum?: string
|
|
||||||
from?: string
|
|
||||||
to?: string
|
|
||||||
value?: number
|
|
||||||
erc721TokenId?: string
|
|
||||||
erc1155Metadata?: {
|
|
||||||
tokenId: string
|
|
||||||
value: string
|
|
||||||
}[]
|
|
||||||
tokenId?: string
|
|
||||||
asset?: string
|
|
||||||
uniqueId?: string
|
|
||||||
hash?: string
|
|
||||||
rawContract?: {
|
|
||||||
value?: string
|
|
||||||
address?: string
|
|
||||||
decimal?: string
|
|
||||||
}
|
|
||||||
metadata?: {
|
|
||||||
blockTimestamp?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { drizzle } from 'drizzle-orm/bun-sqlite'
|
|
||||||
import { sqlite } from '../sqlite'
|
|
||||||
import { relations } from './relations'
|
|
||||||
|
|
||||||
export const db = drizzle({ client: sqlite, relations })
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { defineRelations } from 'drizzle-orm'
|
|
||||||
import * as schema from './schema'
|
|
||||||
|
|
||||||
export const relations = defineRelations(schema, () => ({}))
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import { int, sqliteTable, text, uniqueIndex, index } from 'drizzle-orm/sqlite-core'
|
|
||||||
|
|
||||||
export const tokenMetadata = sqliteTable(
|
|
||||||
'token_metadata',
|
|
||||||
{
|
|
||||||
name: text(),
|
|
||||||
symbol: text().notNull(),
|
|
||||||
decimals: int(),
|
|
||||||
logo: text(),
|
|
||||||
contractAddress: text().notNull(),
|
|
||||||
chainId: text().notNull(),
|
|
||||||
},
|
|
||||||
(table) => ({
|
|
||||||
contractUnique: uniqueIndex('contract_unique').on(
|
|
||||||
table.chainId,
|
|
||||||
table.contractAddress,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const transactions = sqliteTable(
|
|
||||||
'transactions',
|
|
||||||
{
|
|
||||||
hash: text().notNull(),
|
|
||||||
chainId: text().notNull(),
|
|
||||||
from: text().notNull(),
|
|
||||||
to: text(),
|
|
||||||
value: text().notNull(),
|
|
||||||
blockNumber: text(),
|
|
||||||
blockHash: text(),
|
|
||||||
gas: text().notNull(),
|
|
||||||
gasPrice: text(),
|
|
||||||
maxFeePerGas: text(),
|
|
||||||
maxPriorityFeePerGas: text(),
|
|
||||||
input: text().notNull(),
|
|
||||||
nonce: int().notNull(),
|
|
||||||
transactionIndex: int(),
|
|
||||||
type: text(),
|
|
||||||
v: text(),
|
|
||||||
r: text(),
|
|
||||||
s: text(),
|
|
||||||
yParity: int(),
|
|
||||||
},
|
|
||||||
(table) => ({
|
|
||||||
txUnique: uniqueIndex('tx_unique').on(table.chainId, table.hash),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const transactionReceipts = sqliteTable(
|
|
||||||
'transaction_receipts',
|
|
||||||
{
|
|
||||||
transactionHash: text().notNull(),
|
|
||||||
chainId: text().notNull(),
|
|
||||||
blockHash: text().notNull(),
|
|
||||||
blockNumber: text().notNull(),
|
|
||||||
from: text().notNull(),
|
|
||||||
to: text(),
|
|
||||||
cumulativeGasUsed: text().notNull(),
|
|
||||||
gasUsed: text().notNull(),
|
|
||||||
status: text().notNull(),
|
|
||||||
effectiveGasPrice: text(),
|
|
||||||
contractAddress: text(),
|
|
||||||
type: text().notNull(),
|
|
||||||
transactionIndex: int().notNull(),
|
|
||||||
logsBloom: text(),
|
|
||||||
},
|
|
||||||
(table) => ({
|
|
||||||
receiptUnique: uniqueIndex('receipt_unique').on(
|
|
||||||
table.chainId,
|
|
||||||
table.transactionHash,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const evmWallets = sqliteTable('evm_wallets', {
|
|
||||||
id: text('id').primaryKey(),
|
|
||||||
name: text('name').notNull(),
|
|
||||||
passwordHash: text('password_hash'),
|
|
||||||
vaultKey: text('vault_key').notNull(),
|
|
||||||
createdAt: text('created_at').notNull(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const txSyncStatus = sqliteTable('tx_sync_status', {
|
|
||||||
chainId: text('chain_id').notNull(),
|
|
||||||
address: text('address').notNull(),
|
|
||||||
lastSyncBlock: text('last_sync_block').notNull(),
|
|
||||||
syncedAt: text('synced_at').notNull(),
|
|
||||||
}, (table) => ({
|
|
||||||
syncUnique: uniqueIndex('sync_unique').on(table.chainId, table.address),
|
|
||||||
}))
|
|
||||||
|
|
||||||
export const txHistory = sqliteTable(
|
|
||||||
'tx_history',
|
|
||||||
{
|
|
||||||
chainId: text().notNull(),
|
|
||||||
hash: text().notNull(),
|
|
||||||
blockNum: text(),
|
|
||||||
from: text(),
|
|
||||||
to: text(),
|
|
||||||
value: text(),
|
|
||||||
asset: text(),
|
|
||||||
category: text(),
|
|
||||||
rawContractAddress: text(),
|
|
||||||
rawContractDecimal: text(),
|
|
||||||
metadataBlockTimestamp: text(),
|
|
||||||
raw: text().notNull(),
|
|
||||||
syncedAt: text().notNull(),
|
|
||||||
},
|
|
||||||
(table) => ({
|
|
||||||
historyUnique: uniqueIndex('tx_history_unique').on(table.chainId, table.hash, table.category, table.rawContractAddress),
|
|
||||||
blockIdx: index('tx_history_block_idx').on(table.chainId, table.blockNum),
|
|
||||||
fromIdx: index('tx_history_from_idx').on(table.from),
|
|
||||||
toIdx: index('tx_history_to_idx').on(table.to),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Database } from 'bun:sqlite'
|
|
||||||
import envPaths from 'env-paths'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { mkdirSync } from 'fs'
|
|
||||||
import { getENV } from '../get-env'
|
|
||||||
|
|
||||||
const env = getENV()
|
|
||||||
|
|
||||||
const { data } = envPaths(env.appName, { suffix: '' })
|
|
||||||
mkdirSync(data, { recursive: true })
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
export const sqlite = new Database(join(data, env.dbFilename), {
|
|
||||||
create: true,
|
|
||||||
})
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { syncTransactionHistory } from './sync-tx-history'
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { getClient, getTransactionDetails } from '../viem'
|
|
||||||
import { db } from '../db'
|
|
||||||
import { transactions, transactionReceipts } from '../db/schema'
|
|
||||||
import { getChainID } from '../alchemy/network'
|
|
||||||
|
|
||||||
export const syncTxDetails = async (
|
|
||||||
chainId: string,
|
|
||||||
apiKey: string,
|
|
||||||
hash: string,
|
|
||||||
) => {
|
|
||||||
const client = getClient(chainId, apiKey)
|
|
||||||
|
|
||||||
const id = getChainID(chainId)
|
|
||||||
if (!id) return null
|
|
||||||
|
|
||||||
const raw = await getTransactionDetails(client, hash)
|
|
||||||
if (!raw) return null
|
|
||||||
|
|
||||||
const hashLower = hash.toLowerCase()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db
|
|
||||||
.insert(transactions)
|
|
||||||
.values({
|
|
||||||
hash: hashLower,
|
|
||||||
chainId: id,
|
|
||||||
from: raw.tx.from.toLowerCase(),
|
|
||||||
to: raw.tx.to?.toLowerCase() ?? null,
|
|
||||||
value: raw.tx.value.toString(),
|
|
||||||
blockNumber: raw.tx.blockNumber?.toString() ?? null,
|
|
||||||
blockHash: raw.tx.blockHash?.toLowerCase() ?? null,
|
|
||||||
gas: raw.tx.gas.toString(),
|
|
||||||
gasPrice: raw.tx.gasPrice?.toString() ?? null,
|
|
||||||
maxFeePerGas: raw.tx.maxFeePerGas?.toString() ?? null,
|
|
||||||
maxPriorityFeePerGas:
|
|
||||||
raw.tx.maxPriorityFeePerGas?.toString() ?? null,
|
|
||||||
input: raw.tx.input,
|
|
||||||
nonce: raw.tx.nonce,
|
|
||||||
transactionIndex: raw.tx.transactionIndex ?? null,
|
|
||||||
type: raw.tx.type ?? null,
|
|
||||||
v: raw.tx.v?.toString() ?? null,
|
|
||||||
r: raw.tx.r?.toLowerCase() ?? null,
|
|
||||||
s: raw.tx.s?.toLowerCase() ?? null,
|
|
||||||
yParity: raw.tx.yParity ?? null,
|
|
||||||
})
|
|
||||||
.onConflictDoNothing()
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to cache transaction', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db
|
|
||||||
.insert(transactionReceipts)
|
|
||||||
.values({
|
|
||||||
transactionHash: hashLower,
|
|
||||||
chainId: id,
|
|
||||||
blockHash: raw.receipt.blockHash.toLowerCase(),
|
|
||||||
blockNumber: raw.receipt.blockNumber.toString(),
|
|
||||||
from: raw.receipt.from.toLowerCase(),
|
|
||||||
to: raw.receipt.to?.toLowerCase() ?? null,
|
|
||||||
cumulativeGasUsed: raw.receipt.cumulativeGasUsed.toString(),
|
|
||||||
gasUsed: raw.receipt.gasUsed.toString(),
|
|
||||||
status: raw.receipt.status,
|
|
||||||
effectiveGasPrice:
|
|
||||||
raw.receipt.effectiveGasPrice?.toString() ?? null,
|
|
||||||
contractAddress:
|
|
||||||
raw.receipt.contractAddress?.toLowerCase() ?? null,
|
|
||||||
type: raw.receipt.type,
|
|
||||||
transactionIndex: raw.receipt.transactionIndex,
|
|
||||||
logsBloom: raw.receipt.logsBloom?.toLowerCase() ?? null,
|
|
||||||
})
|
|
||||||
.onConflictDoNothing()
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to cache transaction receipt', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
import { eq, and } from 'drizzle-orm'
|
|
||||||
import { numberToHex } from 'viem'
|
|
||||||
import { db } from '../db'
|
|
||||||
import { txHistory, txSyncStatus } from '../db/schema'
|
|
||||||
import { getTransactionHistory } from '../alchemy/get-transaction-history'
|
|
||||||
import { syncTxDetails } from './sync-tx-details'
|
|
||||||
|
|
||||||
const PAGE_SIZE = 76
|
|
||||||
|
|
||||||
export const syncTransactionHistory = async (
|
|
||||||
key: string,
|
|
||||||
chainid: string,
|
|
||||||
address: string,
|
|
||||||
): Promise<number> => {
|
|
||||||
const addrLower = address.toLowerCase()
|
|
||||||
|
|
||||||
const existing = db
|
|
||||||
.select()
|
|
||||||
.from(txSyncStatus)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(txSyncStatus.chainId, chainid),
|
|
||||||
eq(txSyncStatus.address, addrLower),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.get()
|
|
||||||
|
|
||||||
const fromBlock = existing?.lastSyncBlock
|
|
||||||
? numberToHex(Number(existing.lastSyncBlock) + 1)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const transfers = await getTransactionHistory(
|
|
||||||
key,
|
|
||||||
chainid,
|
|
||||||
address,
|
|
||||||
{
|
|
||||||
maxCount: 1000,
|
|
||||||
fromBlock,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!transfers || transfers.length === 0) return 0
|
|
||||||
|
|
||||||
const prepared: Array<typeof txHistory.$inferInsert> = []
|
|
||||||
let latestBlock = Number(existing?.lastSyncBlock ?? '0')
|
|
||||||
|
|
||||||
for (const t of transfers) {
|
|
||||||
if (!t.hash) continue
|
|
||||||
if (t.blockNum) {
|
|
||||||
const blockNum = parseInt(t.blockNum, 16)
|
|
||||||
if (blockNum > latestBlock) latestBlock = blockNum
|
|
||||||
}
|
|
||||||
|
|
||||||
prepared.push({
|
|
||||||
chainId: chainid,
|
|
||||||
hash: t.hash,
|
|
||||||
blockNum: t.blockNum ? String(parseInt(t.blockNum, 16)) : null,
|
|
||||||
from: t.from?.toLowerCase() ?? null,
|
|
||||||
to: t.to?.toLowerCase() ?? null,
|
|
||||||
value: t.value?.toString() ?? null,
|
|
||||||
asset: t.asset ?? null,
|
|
||||||
category: t.category ?? null,
|
|
||||||
rawContractAddress:
|
|
||||||
t.rawContract?.address?.toLowerCase() ?? null,
|
|
||||||
rawContractDecimal: t.rawContract?.decimal ?? null,
|
|
||||||
metadataBlockTimestamp: t.metadata?.blockTimestamp ?? null,
|
|
||||||
raw: JSON.stringify(t),
|
|
||||||
syncedAt: new Date().toISOString(),
|
|
||||||
})
|
|
||||||
|
|
||||||
await syncTxDetails(chainid, key, t.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
flush(prepared)
|
|
||||||
db.insert(txSyncStatus)
|
|
||||||
.values({
|
|
||||||
chainId: chainid,
|
|
||||||
address: addrLower,
|
|
||||||
lastSyncBlock: String(latestBlock),
|
|
||||||
syncedAt: new Date().toISOString(),
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: [txSyncStatus.chainId, txSyncStatus.address],
|
|
||||||
set: {
|
|
||||||
lastSyncBlock: String(latestBlock),
|
|
||||||
syncedAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.run()
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`[sync] ${chainid}/${addrLower}: synced ${prepared.length} transfers up to block ${latestBlock}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
return prepared.length
|
|
||||||
}
|
|
||||||
|
|
||||||
function flush(prepared: Array<typeof txHistory.$inferInsert>) {
|
|
||||||
while (prepared.length > 0) {
|
|
||||||
const batch = prepared.splice(0, PAGE_SIZE)
|
|
||||||
const cols = [
|
|
||||||
'chainId',
|
|
||||||
'hash',
|
|
||||||
'blockNum',
|
|
||||||
'"from"',
|
|
||||||
'"to"',
|
|
||||||
'value',
|
|
||||||
'asset',
|
|
||||||
'category',
|
|
||||||
'rawContractAddress',
|
|
||||||
'rawContractDecimal',
|
|
||||||
'metadataBlockTimestamp',
|
|
||||||
'raw',
|
|
||||||
'syncedAt',
|
|
||||||
]
|
|
||||||
const colKeys = [
|
|
||||||
'chainId',
|
|
||||||
'hash',
|
|
||||||
'blockNum',
|
|
||||||
'from',
|
|
||||||
'to',
|
|
||||||
'value',
|
|
||||||
'asset',
|
|
||||||
'category',
|
|
||||||
'rawContractAddress',
|
|
||||||
'rawContractDecimal',
|
|
||||||
'metadataBlockTimestamp',
|
|
||||||
'raw',
|
|
||||||
'syncedAt',
|
|
||||||
]
|
|
||||||
const placeholders = batch
|
|
||||||
.map(() => `(${colKeys.map(() => '?').join(',')})`)
|
|
||||||
.join(',')
|
|
||||||
const flat = batch.flatMap((r) =>
|
|
||||||
colKeys.map((c) => (r as any)[c] ?? null),
|
|
||||||
)
|
|
||||||
const sql = `INSERT OR IGNORE INTO tx_history (${cols.join(',')}) VALUES ${placeholders}`
|
|
||||||
|
|
||||||
const dbClient = db.$client
|
|
||||||
dbClient.exec('BEGIN')
|
|
||||||
try {
|
|
||||||
dbClient.run(sql, ...flat)
|
|
||||||
dbClient.exec('COMMIT')
|
|
||||||
} catch (e) {
|
|
||||||
dbClient.exec('ROLLBACK')
|
|
||||||
console.error('[sync] flush error:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { eq, and } from 'drizzle-orm'
|
|
||||||
import { db } from '../db'
|
|
||||||
import { tokenMetadata } from '../db/schema'
|
|
||||||
import { getChainID } from '../alchemy/network'
|
|
||||||
import { getTokenMetadata as getTokenMetadataFromAlchemy } from '../alchemy/get-token-metadata'
|
|
||||||
|
|
||||||
export const getTokenMetadata = async (
|
|
||||||
key: string,
|
|
||||||
chainid: string,
|
|
||||||
tokenAddress: string,
|
|
||||||
) => {
|
|
||||||
const chainId = getChainID(chainid)
|
|
||||||
if (!chainId) return
|
|
||||||
|
|
||||||
const cached = db
|
|
||||||
.select()
|
|
||||||
.from(tokenMetadata)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(tokenMetadata.chainId, chainId),
|
|
||||||
eq(tokenMetadata.contractAddress, tokenAddress.toLowerCase()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.get()
|
|
||||||
|
|
||||||
if (cached) {
|
|
||||||
return {
|
|
||||||
jsonrpc: '2.0' as const,
|
|
||||||
id: 0,
|
|
||||||
result: {
|
|
||||||
name: cached.name ?? null,
|
|
||||||
symbol: cached.symbol,
|
|
||||||
decimals: cached.decimals ?? null,
|
|
||||||
logo: cached.logo ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await getTokenMetadataFromAlchemy(
|
|
||||||
key,
|
|
||||||
chainid,
|
|
||||||
tokenAddress,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (result?.result?.symbol) {
|
|
||||||
try {
|
|
||||||
await db
|
|
||||||
.insert(tokenMetadata)
|
|
||||||
.values({
|
|
||||||
name: result.result.name,
|
|
||||||
symbol: result.result.symbol,
|
|
||||||
decimals: result.result.decimals,
|
|
||||||
logo: result.result.logo,
|
|
||||||
contractAddress: tokenAddress.toLowerCase(),
|
|
||||||
chainId,
|
|
||||||
})
|
|
||||||
.onConflictDoNothing()
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Failed to cache token metadata', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { getTokenMetadata } from './get-metadata'
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { eq, and } from 'drizzle-orm'
|
|
||||||
import { formatEther, formatGwei, type PublicClient } from 'viem'
|
|
||||||
import { db } from '../db'
|
|
||||||
import { transactions, transactionReceipts } from '../db/schema'
|
|
||||||
import { getChainID } from '../alchemy/network'
|
|
||||||
|
|
||||||
export const getTransactionDetails = async (
|
|
||||||
hash: string,
|
|
||||||
chainId: string,
|
|
||||||
) => {
|
|
||||||
if (!hash.startsWith('0x')) return null
|
|
||||||
|
|
||||||
const id = getChainID(chainId)
|
|
||||||
if (!id) return null
|
|
||||||
|
|
||||||
const hashLower = hash.toLowerCase()
|
|
||||||
|
|
||||||
const cachedTx = db
|
|
||||||
.select()
|
|
||||||
.from(transactions)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(transactions.hash, hashLower),
|
|
||||||
eq(transactions.chainId, id),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.get()
|
|
||||||
|
|
||||||
const cachedReceipt = db
|
|
||||||
.select()
|
|
||||||
.from(transactionReceipts)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(transactionReceipts.transactionHash, hashLower),
|
|
||||||
eq(transactionReceipts.chainId, id),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.get()
|
|
||||||
|
|
||||||
if (cachedTx && cachedReceipt) {
|
|
||||||
const value = BigInt(cachedTx.value)
|
|
||||||
const gasUsed = BigInt(cachedReceipt.gasUsed)
|
|
||||||
const gasPrice = cachedTx.gasPrice
|
|
||||||
? BigInt(cachedTx.gasPrice)
|
|
||||||
: BigInt(0)
|
|
||||||
|
|
||||||
return {
|
|
||||||
hash: cachedTx.hash,
|
|
||||||
from: cachedTx.from,
|
|
||||||
to: cachedTx.to,
|
|
||||||
value: formatEther(value),
|
|
||||||
blockNumber: cachedReceipt.blockNumber,
|
|
||||||
fee: (
|
|
||||||
parseFloat(formatGwei(gasUsed)) *
|
|
||||||
parseFloat(formatGwei(gasPrice))
|
|
||||||
).toString(),
|
|
||||||
gasPrice: formatGwei(gasPrice),
|
|
||||||
status: cachedReceipt.status as 'success' | 'reverted',
|
|
||||||
type: cachedTx.type ?? 'legacy',
|
|
||||||
nonce: cachedTx.nonce,
|
|
||||||
input: cachedTx.input,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { eq, and, or, desc } from 'drizzle-orm'
|
|
||||||
import { db } from '../db'
|
|
||||||
import { txHistory } from '../db/schema'
|
|
||||||
import type { Transaction } from '../alchemy/types'
|
|
||||||
|
|
||||||
export const getTransactionHistory = async (
|
|
||||||
key: string,
|
|
||||||
chainid: string,
|
|
||||||
address: string,
|
|
||||||
opts: { maxCount: number },
|
|
||||||
) => {
|
|
||||||
return getCachedTransactionHistory(chainid, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCachedTransactionHistory = (
|
|
||||||
chainid: string,
|
|
||||||
address: string,
|
|
||||||
) => {
|
|
||||||
const addrLower = address.toLowerCase()
|
|
||||||
const rows = db
|
|
||||||
.select()
|
|
||||||
.from(txHistory)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(txHistory.chainId, chainid),
|
|
||||||
or(
|
|
||||||
eq(txHistory.from, addrLower),
|
|
||||||
eq(txHistory.to, addrLower),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.orderBy(desc(txHistory.blockNum))
|
|
||||||
.all()
|
|
||||||
|
|
||||||
return rows.map(toTransfer)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toTransfer(row: typeof txHistory.$inferSelect): Transaction {
|
|
||||||
if (row.raw) {
|
|
||||||
return JSON.parse(row.raw) as Transaction
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
hash: row.hash,
|
|
||||||
blockNum: row.blockNum
|
|
||||||
? `0x${parseInt(row.blockNum).toString(16)}`
|
|
||||||
: undefined,
|
|
||||||
from: row.from ?? undefined,
|
|
||||||
to: row.to ?? undefined,
|
|
||||||
value: row.value ? Number(row.value) : undefined,
|
|
||||||
asset: row.asset ?? undefined,
|
|
||||||
category: row.category as Transaction['category'],
|
|
||||||
rawContract:
|
|
||||||
row.rawContractAddress || row.rawContractDecimal
|
|
||||||
? {
|
|
||||||
address: row.rawContractAddress ?? undefined,
|
|
||||||
decimal: row.rawContractDecimal ?? undefined,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
metadata: row.metadataBlockTimestamp
|
|
||||||
? { blockTimestamp: row.metadataBlockTimestamp }
|
|
||||||
: undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export {
|
|
||||||
getTransactionHistory,
|
|
||||||
getCachedTransactionHistory,
|
|
||||||
} from './get-history'
|
|
||||||
export { getTransactionDetails } from './get-details'
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { isHex, type PublicClient } from 'viem'
|
|
||||||
|
|
||||||
export const getTransactionDetails = async (
|
|
||||||
client: PublicClient,
|
|
||||||
hash: string,
|
|
||||||
) => {
|
|
||||||
if (!isHex(hash)) return null
|
|
||||||
|
|
||||||
const [tx, receipt] = await Promise.all([
|
|
||||||
client.getTransaction({ hash }),
|
|
||||||
client.getTransactionReceipt({ hash }),
|
|
||||||
])
|
|
||||||
|
|
||||||
return {
|
|
||||||
tx: {
|
|
||||||
hash: tx.hash,
|
|
||||||
from: tx.from,
|
|
||||||
to: tx.to ?? null,
|
|
||||||
value: tx.value,
|
|
||||||
blockNumber: tx.blockNumber ?? null,
|
|
||||||
gasPrice: tx.gasPrice ?? null,
|
|
||||||
gas: tx.gas,
|
|
||||||
input: tx.input,
|
|
||||||
nonce: tx.nonce,
|
|
||||||
type: tx.type ?? 'legacy',
|
|
||||||
blockHash: tx.blockHash ?? null,
|
|
||||||
maxFeePerGas: tx.maxFeePerGas ?? null,
|
|
||||||
maxPriorityFeePerGas: tx.maxPriorityFeePerGas ?? null,
|
|
||||||
transactionIndex: tx.transactionIndex ?? null,
|
|
||||||
v: tx.v ?? null,
|
|
||||||
r: tx.r?.toString() ?? null,
|
|
||||||
s: tx.s?.toString() ?? null,
|
|
||||||
yParity: tx.yParity ?? null,
|
|
||||||
},
|
|
||||||
receipt: {
|
|
||||||
blockHash: receipt.blockHash,
|
|
||||||
blockNumber: receipt.blockNumber,
|
|
||||||
from: receipt.from,
|
|
||||||
to: receipt.to ?? null,
|
|
||||||
cumulativeGasUsed: receipt.cumulativeGasUsed,
|
|
||||||
gasUsed: receipt.gasUsed,
|
|
||||||
status: receipt.status,
|
|
||||||
effectiveGasPrice: receipt.effectiveGasPrice ?? null,
|
|
||||||
contractAddress: receipt.contractAddress ?? null,
|
|
||||||
type: receipt.type,
|
|
||||||
transactionIndex: receipt.transactionIndex,
|
|
||||||
logsBloom: receipt.logsBloom?.toString() ?? null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { bsc, mainnet, polygon, type Chain } from 'viem/chains'
|
|
||||||
import type { ChainID } from '../alchemy/network'
|
|
||||||
|
|
||||||
const chains: Record<ChainID, Chain> = {
|
|
||||||
'1': mainnet,
|
|
||||||
'56': bsc,
|
|
||||||
'137': polygon,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getChain = (id: ChainID) => {
|
|
||||||
return chains[id]
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { createPublicClient, http } from 'viem'
|
|
||||||
import { getChainID, getURL } from '../alchemy/network'
|
|
||||||
import { getChain } from './getChain'
|
|
||||||
|
|
||||||
export const getClient = (chainId: string, apiKey: string) => {
|
|
||||||
const id = getChainID(chainId)
|
|
||||||
|
|
||||||
if (!id) {
|
|
||||||
throw new Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = getURL({ chainId: id, key: apiKey })
|
|
||||||
const publicClient = createPublicClient({
|
|
||||||
chain: getChain(id),
|
|
||||||
transport: http(url),
|
|
||||||
})
|
|
||||||
|
|
||||||
return publicClient
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './getClient'
|
|
||||||
export { getTransactionDetails } from './get-transaction-details'
|
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import {
|
||||||
|
Client,
|
||||||
|
Wallet,
|
||||||
|
xrpToDrops,
|
||||||
|
dropsToXrp,
|
||||||
|
isValidClassicAddress,
|
||||||
|
} from 'xrpl'
|
||||||
|
import type {
|
||||||
|
XrpBalance,
|
||||||
|
XrpTxDetails,
|
||||||
|
XrpTxEntry,
|
||||||
|
XrpWalletInfo,
|
||||||
|
} from '../../../lib/rpc-schema'
|
||||||
|
|
||||||
|
const SERVER = 'wss://s1.ripple.com'
|
||||||
|
const RIPPLE_EPOCH = 946684800
|
||||||
|
const WALLET_LIST_KEY = 'xrp_wallets'
|
||||||
|
const SERVICE = 'koins'
|
||||||
|
|
||||||
|
let client: Client | null = null
|
||||||
|
|
||||||
|
export async function getClient(): Promise<Client> {
|
||||||
|
if (client?.isConnected()) return client
|
||||||
|
client = new Client(SERVER)
|
||||||
|
await client.connect()
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isValidAddress(address: string): boolean {
|
||||||
|
return isValidClassicAddress(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateWallet(): { address: string; seed: string } {
|
||||||
|
const wallet = Wallet.generate()
|
||||||
|
return { address: wallet.classicAddress, seed: wallet.seed! }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function walletFromSecret(secret: string): { address: string } {
|
||||||
|
const s = secret.trim()
|
||||||
|
try {
|
||||||
|
return { address: Wallet.fromSeed(s).classicAddress }
|
||||||
|
} catch {
|
||||||
|
return { address: Wallet.fromMnemonic(s).classicAddress }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function walletForSigning(secret: string): Wallet {
|
||||||
|
const s = secret.trim()
|
||||||
|
try {
|
||||||
|
return Wallet.fromSeed(s)
|
||||||
|
} catch {
|
||||||
|
return Wallet.fromMnemonic(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getXrpWalletList(): Promise<XrpWalletInfo[]> {
|
||||||
|
const raw = await Bun.secrets.get({ service: SERVICE, name: WALLET_LIST_KEY })
|
||||||
|
if (!raw) return []
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveXrpWalletList(
|
||||||
|
wallets: XrpWalletInfo[],
|
||||||
|
): Promise<void> {
|
||||||
|
await Bun.secrets.set({
|
||||||
|
service: SERVICE,
|
||||||
|
name: WALLET_LIST_KEY,
|
||||||
|
value: JSON.stringify(wallets),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBalance(address: string): Promise<XrpBalance> {
|
||||||
|
const c = await getClient()
|
||||||
|
try {
|
||||||
|
const balance = await c.getXrpBalance(address)
|
||||||
|
return { address, balance: String(balance), funded: true }
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error && e.message.includes('Account not found')) {
|
||||||
|
return { address, balance: '0', funded: false }
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dropsAmount(value: unknown): string {
|
||||||
|
if (typeof value !== 'string') return '0'
|
||||||
|
return String(dropsToXrp(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTransactions(address: string): Promise<XrpTxEntry[]> {
|
||||||
|
const c = await getClient()
|
||||||
|
const res = await c.request({
|
||||||
|
command: 'account_tx',
|
||||||
|
account: address,
|
||||||
|
ledger_index_min: -1,
|
||||||
|
ledger_index_max: -1,
|
||||||
|
limit: 25,
|
||||||
|
})
|
||||||
|
const entries: XrpTxEntry[] = []
|
||||||
|
for (const entry of res.result.transactions as any[]) {
|
||||||
|
const tx = entry.tx_json ?? entry.tx
|
||||||
|
if (!tx || tx.TransactionType !== 'Payment') continue
|
||||||
|
const meta = entry.meta ?? entry.metaData ?? {}
|
||||||
|
if (meta.TransactionResult && meta.TransactionResult !== 'tesSUCCESS')
|
||||||
|
continue
|
||||||
|
const delivered = meta.delivered_amount ?? meta.DeliveredAmount ?? tx.Amount
|
||||||
|
entries.push({
|
||||||
|
hash: entry.hash ?? tx.hash,
|
||||||
|
amount: dropsAmount(delivered),
|
||||||
|
timestamp: tx.date ? String(tx.date + RIPPLE_EPOCH) : '0',
|
||||||
|
direction: tx.Destination === address ? 'in' : 'out',
|
||||||
|
from: tx.Account,
|
||||||
|
to: tx.Destination,
|
||||||
|
destinationTag: tx.DestinationTag,
|
||||||
|
fee: dropsAmount(tx.Fee),
|
||||||
|
confirmed: entry.validated === true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTxDetails(
|
||||||
|
hash: string,
|
||||||
|
): Promise<XrpTxDetails | null> {
|
||||||
|
const c = await getClient()
|
||||||
|
try {
|
||||||
|
const res = await c.request({ command: 'tx', transaction: hash })
|
||||||
|
const d = res.result as any
|
||||||
|
const tx = d.tx_json ?? d
|
||||||
|
if (tx.TransactionType !== 'Payment') return null
|
||||||
|
const meta = d.meta ?? {}
|
||||||
|
const delivered = meta.delivered_amount ?? meta.DeliveredAmount ?? tx.Amount
|
||||||
|
return {
|
||||||
|
hash: d.hash ?? hash,
|
||||||
|
from: tx.Account,
|
||||||
|
to: tx.Destination,
|
||||||
|
amount: dropsAmount(delivered),
|
||||||
|
fee: dropsAmount(tx.Fee),
|
||||||
|
timestamp: tx.date ? String(tx.date + RIPPLE_EPOCH) : '0',
|
||||||
|
ledgerIndex: d.ledger_index ?? 0,
|
||||||
|
destinationTag: tx.DestinationTag,
|
||||||
|
confirmed: d.validated === true,
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendPayment(
|
||||||
|
secret: string,
|
||||||
|
to: string,
|
||||||
|
amount: string,
|
||||||
|
destinationTag?: number,
|
||||||
|
): Promise<{ hash: string; fee: string }> {
|
||||||
|
if (!isValidClassicAddress(to)) throw new Error('Invalid XRP address')
|
||||||
|
const wallet = walletForSigning(secret)
|
||||||
|
const c = await getClient()
|
||||||
|
const prepared = await c.autofill({
|
||||||
|
TransactionType: 'Payment',
|
||||||
|
Account: wallet.classicAddress,
|
||||||
|
Amount: xrpToDrops(amount),
|
||||||
|
Destination: to,
|
||||||
|
...(destinationTag !== undefined ? { DestinationTag: destinationTag } : {}),
|
||||||
|
})
|
||||||
|
const signed = wallet.sign(prepared)
|
||||||
|
const result = await c.submit(signed.tx_blob)
|
||||||
|
const engineResult = (result.result as any).engine_result
|
||||||
|
if (engineResult !== 'tesSUCCESS') {
|
||||||
|
const msg =
|
||||||
|
(result.result as any).engine_result_message ?? 'Transaction failed'
|
||||||
|
throw new Error(`${engineResult}: ${msg}`)
|
||||||
|
}
|
||||||
|
return { hash: signed.hash, fee: String(dropsToXrp((prepared as any).Fee ?? '0')) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFee(): Promise<{ fee: string } | null> {
|
||||||
|
try {
|
||||||
|
const c = await getClient()
|
||||||
|
const res = await c.request({ command: 'fee' })
|
||||||
|
return { fee: String(dropsToXrp(res.result.drops.open_ledger_fee)) }
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,641 +0,0 @@
|
|||||||
import { getTokenMetadata } from '../../lib/tokens'
|
|
||||||
import { getTokensBalances } from '../../lib/alchemy'
|
|
||||||
import { tryCatch } from '@koins/utils'
|
|
||||||
import {
|
|
||||||
getTransactionHistory,
|
|
||||||
getTransactionDetails,
|
|
||||||
getCachedTransactionHistory,
|
|
||||||
} from '../../lib/transactions'
|
|
||||||
import { syncTransactionHistory } from '../../lib/sync'
|
|
||||||
import { eq, and } from 'drizzle-orm'
|
|
||||||
import { db } from '../../lib/db'
|
|
||||||
import { getClient } from '../../lib/viem'
|
|
||||||
import {
|
|
||||||
formatGwei,
|
|
||||||
parseEther,
|
|
||||||
parseUnits,
|
|
||||||
encodeFunctionData,
|
|
||||||
createWalletClient,
|
|
||||||
} from 'viem'
|
|
||||||
import { mnemonicToAccount } from 'viem/accounts'
|
|
||||||
import { getChain } from '../../lib/viem/getChain'
|
|
||||||
import { getChainID } from '../../lib/alchemy/network'
|
|
||||||
import { alchemyTransport } from '@alchemy/common'
|
|
||||||
import { sqlite } from '../../lib/sqlite'
|
|
||||||
import { txHistory, evmWallets } from '../../lib/db/schema'
|
|
||||||
import type {
|
|
||||||
TokenBalanceResult,
|
|
||||||
TokenPriceEntry,
|
|
||||||
TxEntry,
|
|
||||||
} from '../../../lib/rpc-schema'
|
|
||||||
|
|
||||||
export function createEvmHandlers(
|
|
||||||
rpc: {
|
|
||||||
send: {
|
|
||||||
transfersUpdate: (p: {
|
|
||||||
count: number
|
|
||||||
chainid: string
|
|
||||||
address: string
|
|
||||||
}) => void
|
|
||||||
}
|
|
||||||
},
|
|
||||||
syncState: {
|
|
||||||
target: { address: string; chainid: string } | null
|
|
||||||
timer: ReturnType<typeof setInterval> | null
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const ALCHEMY_NETWORKS: Record<string, string> = {
|
|
||||||
'1': 'eth-mainnet',
|
|
||||||
'137': 'polygon-mainnet',
|
|
||||||
'56': 'bnb-mainnet',
|
|
||||||
}
|
|
||||||
|
|
||||||
const NATIVE_SYMBOLS: Record<string, string> = {
|
|
||||||
'1': 'ETH',
|
|
||||||
'137': 'POL',
|
|
||||||
'56': 'BNB',
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchAlchemyTransfers(
|
|
||||||
key: string,
|
|
||||||
chainid: string,
|
|
||||||
address: string,
|
|
||||||
) {
|
|
||||||
const transactions = await getTransactionHistory(
|
|
||||||
key,
|
|
||||||
chainid,
|
|
||||||
address,
|
|
||||||
{ maxCount: 10 },
|
|
||||||
)
|
|
||||||
if (!transactions) return []
|
|
||||||
return transactions
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatUnits(balance: string, decimals: number): string {
|
|
||||||
const bal = BigInt(balance)
|
|
||||||
if (bal === 0n) return '0'
|
|
||||||
const divisor = 10n ** BigInt(decimals)
|
|
||||||
const intPart = bal / divisor
|
|
||||||
const fracPart = (bal % divisor)
|
|
||||||
.toString()
|
|
||||||
.padStart(decimals, '0')
|
|
||||||
.replace(/0+$/, '')
|
|
||||||
return fracPart ? `${intPart}.${fracPart}` : `${intPart}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchAlchemyTokenBalances(
|
|
||||||
key: string,
|
|
||||||
chainid: string,
|
|
||||||
address: string,
|
|
||||||
): Promise<TokenBalanceResult[]> {
|
|
||||||
const [balRes, balResError] = await tryCatch(
|
|
||||||
getTokensBalances(key, chainid, address),
|
|
||||||
)
|
|
||||||
if (balResError) {
|
|
||||||
console.log('Get Tokens Balance Error', balResError)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
if (!balRes) return []
|
|
||||||
const tokens = balRes.result?.tokenBalances ?? []
|
|
||||||
const nonZero = tokens.filter(
|
|
||||||
(t: any) => t.tokenBalance !== '0x0',
|
|
||||||
)
|
|
||||||
const metas = await Promise.allSettled(
|
|
||||||
nonZero.map((t: any) =>
|
|
||||||
getTokenMetadata(key, chainid, t.contractAddress),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (!metas) return []
|
|
||||||
return nonZero
|
|
||||||
.map((t: any, i: number) => {
|
|
||||||
const meta =
|
|
||||||
metas[i].status === 'fulfilled' && metas[i].value
|
|
||||||
? metas[i].value.result
|
|
||||||
: null
|
|
||||||
return {
|
|
||||||
symbol: meta?.symbol ?? 'Unknown',
|
|
||||||
decimals: meta?.decimals ?? 0,
|
|
||||||
balance: formatUnits(t.tokenBalance, meta?.decimals ?? 18),
|
|
||||||
contractAddress: t.contractAddress,
|
|
||||||
logo: meta?.logo ?? undefined,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter((t) => t.logo && t.decimals !== 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapTransfer(t: any): TxEntry {
|
|
||||||
const isExternal = t.category === 'external'
|
|
||||||
return {
|
|
||||||
hash: t.hash,
|
|
||||||
timeStamp: String(
|
|
||||||
Math.floor(
|
|
||||||
new Date(t.metadata?.blockTimestamp).getTime() / 1000,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
from: t.from,
|
|
||||||
to: t.to,
|
|
||||||
value: String(t.value),
|
|
||||||
...(isExternal
|
|
||||||
? {}
|
|
||||||
: {
|
|
||||||
tokenSymbol: t.asset,
|
|
||||||
tokenDecimal: t.rawContract?.decimal
|
|
||||||
? String(parseInt(t.rawContract.decimal, 16))
|
|
||||||
: undefined,
|
|
||||||
contractAddress: t.rawContract?.address ?? undefined,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pairTransfers(
|
|
||||||
transfers: any[],
|
|
||||||
address: string,
|
|
||||||
key?: string,
|
|
||||||
chainid?: string,
|
|
||||||
): Promise<TxEntry[]> {
|
|
||||||
const addrLower = address.toLowerCase()
|
|
||||||
const byHash = new Map<string, any[]>()
|
|
||||||
for (const t of transfers) {
|
|
||||||
if (!t.hash) continue
|
|
||||||
const h = t.hash
|
|
||||||
if (!byHash.has(h)) byHash.set(h, [])
|
|
||||||
byHash.get(h)!.push(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
const combined: TxEntry[] = []
|
|
||||||
|
|
||||||
for (const [, group] of byHash) {
|
|
||||||
const native = group.filter(
|
|
||||||
(t: any) =>
|
|
||||||
t.category === 'external' || t.category === 'internal',
|
|
||||||
)
|
|
||||||
const tokens = group.filter((t: any) => t.category === 'erc20')
|
|
||||||
|
|
||||||
const extOut = native.filter(
|
|
||||||
(t: any) => t.from?.toLowerCase() === addrLower,
|
|
||||||
)
|
|
||||||
const extIn = native.filter(
|
|
||||||
(t: any) => t.to?.toLowerCase() === addrLower,
|
|
||||||
)
|
|
||||||
const tokIn = tokens.filter(
|
|
||||||
(t: any) => t.to?.toLowerCase() === addrLower,
|
|
||||||
)
|
|
||||||
const tokOut = tokens.filter(
|
|
||||||
(t: any) => t.from?.toLowerCase() === addrLower,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (extOut.length > 0 && tokIn.length > 0) {
|
|
||||||
combined.push({
|
|
||||||
...mapTransfer(extOut[0]),
|
|
||||||
tokenSymbol: tokIn[0].asset,
|
|
||||||
tokenDecimal: tokIn[0].rawContract?.decimal
|
|
||||||
? String(parseInt(tokIn[0].rawContract.decimal, 16))
|
|
||||||
: undefined,
|
|
||||||
contractAddress: tokIn[0].rawContract?.address ?? undefined,
|
|
||||||
pairedValue: String(tokIn[0].value),
|
|
||||||
pairedSymbol: tokIn[0].asset,
|
|
||||||
pairedDecimals: tokIn[0].rawContract?.decimal
|
|
||||||
? String(parseInt(tokIn[0].rawContract.decimal, 16))
|
|
||||||
: undefined,
|
|
||||||
pairedContractAddress:
|
|
||||||
tokIn[0].rawContract?.address ?? undefined,
|
|
||||||
})
|
|
||||||
} else if (extOut.length > 0) {
|
|
||||||
combined.push(mapTransfer(extOut[0]))
|
|
||||||
} else if (extIn.length > 0) {
|
|
||||||
combined.push(mapTransfer(extIn[0]))
|
|
||||||
} else if (tokOut.length > 0 && tokIn.length > 0) {
|
|
||||||
combined.push({
|
|
||||||
...mapTransfer(tokOut[0]),
|
|
||||||
pairedValue: String(tokIn[0].value),
|
|
||||||
pairedSymbol: tokIn[0].asset,
|
|
||||||
pairedDecimals: tokIn[0].rawContract?.decimal
|
|
||||||
? String(parseInt(tokIn[0].rawContract.decimal, 16))
|
|
||||||
: undefined,
|
|
||||||
pairedContractAddress:
|
|
||||||
tokIn[0].rawContract?.address ?? undefined,
|
|
||||||
})
|
|
||||||
} else if (tokOut.length > 0 && extIn.length > 0) {
|
|
||||||
combined.push({
|
|
||||||
...mapTransfer(tokOut[0]),
|
|
||||||
pairedValue: String(extIn[0].value),
|
|
||||||
pairedSymbol: chainid ? NATIVE_SYMBOLS[chainid] : undefined,
|
|
||||||
})
|
|
||||||
} else if (tokOut.length > 0) {
|
|
||||||
combined.push(mapTransfer(tokOut[0]))
|
|
||||||
} else if (tokIn.length > 0) {
|
|
||||||
combined.push(mapTransfer(tokIn[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key && chainid) {
|
|
||||||
const addrSet = new Set<string>()
|
|
||||||
for (const entry of combined) {
|
|
||||||
if (entry.contractAddress)
|
|
||||||
addrSet.add(entry.contractAddress.toLowerCase())
|
|
||||||
if (entry.pairedContractAddress)
|
|
||||||
addrSet.add(entry.pairedContractAddress.toLowerCase())
|
|
||||||
}
|
|
||||||
const logoMap = new Map<string, string>()
|
|
||||||
await Promise.all(
|
|
||||||
Array.from(addrSet).map(async (addr) => {
|
|
||||||
const meta = await getTokenMetadata(key, chainid, addr)
|
|
||||||
if (meta?.result?.logo) logoMap.set(addr, meta.result.logo)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
for (const entry of combined) {
|
|
||||||
if (entry.contractAddress)
|
|
||||||
entry.logo = logoMap.get(
|
|
||||||
entry.contractAddress.toLowerCase(),
|
|
||||||
)
|
|
||||||
if (entry.pairedContractAddress)
|
|
||||||
entry.pairedLogo = logoMap.get(
|
|
||||||
entry.pairedContractAddress.toLowerCase(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
combined.sort((a, b) => Number(b.timeStamp) - Number(a.timeStamp))
|
|
||||||
return combined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fetchTxHistory: async ({
|
|
||||||
address,
|
|
||||||
chainid,
|
|
||||||
}: {
|
|
||||||
address: string
|
|
||||||
chainid: string
|
|
||||||
}) => {
|
|
||||||
const key = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
})
|
|
||||||
if (!key) return []
|
|
||||||
const [all, allError] = await tryCatch(
|
|
||||||
fetchAlchemyTransfers(key, chainid, address),
|
|
||||||
)
|
|
||||||
if (allError) {
|
|
||||||
console.log(allError)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return pairTransfers(all, address, key, chainid)
|
|
||||||
},
|
|
||||||
fetchCachedTxHistory: async ({
|
|
||||||
address,
|
|
||||||
chainid,
|
|
||||||
}: {
|
|
||||||
address: string
|
|
||||||
chainid: string
|
|
||||||
}) => {
|
|
||||||
const transfers = await getCachedTransactionHistory(
|
|
||||||
chainid,
|
|
||||||
address,
|
|
||||||
)
|
|
||||||
return pairTransfers(transfers, address)
|
|
||||||
},
|
|
||||||
syncTxHistory: async ({
|
|
||||||
address,
|
|
||||||
chainid,
|
|
||||||
}: {
|
|
||||||
address: string
|
|
||||||
chainid: string
|
|
||||||
}) => {
|
|
||||||
const key = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
})
|
|
||||||
if (!key) return
|
|
||||||
try {
|
|
||||||
const count = await syncTransactionHistory(
|
|
||||||
key,
|
|
||||||
chainid,
|
|
||||||
address,
|
|
||||||
)
|
|
||||||
rpc.send.transfersUpdate({ count, chainid, address })
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[sync] error:', e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setAutoSync: async (
|
|
||||||
target: { address: string; chainid: string } | null,
|
|
||||||
) => {
|
|
||||||
if (syncState.timer) clearInterval(syncState.timer)
|
|
||||||
syncState.timer = null
|
|
||||||
syncState.target = null
|
|
||||||
if (!target) return
|
|
||||||
syncState.target = target
|
|
||||||
syncState.timer = setInterval(async () => {
|
|
||||||
if (!syncState.target) return
|
|
||||||
const key = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
})
|
|
||||||
if (!key) return
|
|
||||||
try {
|
|
||||||
const count = await syncTransactionHistory(
|
|
||||||
key,
|
|
||||||
syncState.target.chainid,
|
|
||||||
syncState.target.address,
|
|
||||||
)
|
|
||||||
rpc.send.transfersUpdate({
|
|
||||||
count,
|
|
||||||
chainid: syncState.target.chainid,
|
|
||||||
address: syncState.target.address,
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[auto-sync] error:', e)
|
|
||||||
}
|
|
||||||
}, 30000)
|
|
||||||
},
|
|
||||||
flushTxCache: async () => {
|
|
||||||
sqlite.run('DELETE FROM tx_history')
|
|
||||||
sqlite.run('DELETE FROM tx_sync_status')
|
|
||||||
console.log('[rpc] flushTxCache complete')
|
|
||||||
},
|
|
||||||
fetchTokenBalances: async ({
|
|
||||||
address,
|
|
||||||
chainid,
|
|
||||||
}: {
|
|
||||||
address: string
|
|
||||||
chainid: string
|
|
||||||
}) => {
|
|
||||||
const key = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
})
|
|
||||||
if (!key) return []
|
|
||||||
try {
|
|
||||||
return await fetchAlchemyTokenBalances(key, chainid, address)
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchTokenPrices: async ({
|
|
||||||
symbols,
|
|
||||||
addresses,
|
|
||||||
}: {
|
|
||||||
symbols?: string[]
|
|
||||||
addresses?: { network: string; address: string }[]
|
|
||||||
}) => {
|
|
||||||
const key = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
})
|
|
||||||
if (!key) return []
|
|
||||||
try {
|
|
||||||
const results: TokenPriceEntry[] = []
|
|
||||||
if (symbols && symbols.length > 0) {
|
|
||||||
const url = `https://api.g.alchemy.com/prices/v1/${key}/tokens/by-symbol?${symbols.map((s) => `symbols=${encodeURIComponent(s)}`).join('&')}`
|
|
||||||
const res = await fetch(url, {
|
|
||||||
signal: AbortSignal.timeout(10000),
|
|
||||||
})
|
|
||||||
if (res.ok) {
|
|
||||||
const body = await res.json()
|
|
||||||
for (const d of body.data ?? []) {
|
|
||||||
for (const p of d.prices ?? []) {
|
|
||||||
results.push({
|
|
||||||
symbol: d.symbol,
|
|
||||||
currency: p.currency,
|
|
||||||
value: p.value,
|
|
||||||
lastUpdatedAt: p.lastUpdatedAt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (addresses && addresses.length > 0) {
|
|
||||||
const url = `https://api.g.alchemy.com/prices/v1/${key}/tokens/by-address`
|
|
||||||
const res = await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ addresses }),
|
|
||||||
signal: AbortSignal.timeout(10000),
|
|
||||||
})
|
|
||||||
if (res.ok) {
|
|
||||||
const body = await res.json()
|
|
||||||
for (const d of body.data ?? []) {
|
|
||||||
for (const p of d.prices ?? []) {
|
|
||||||
results.push({
|
|
||||||
symbol: d.symbol,
|
|
||||||
currency: p.currency,
|
|
||||||
value: p.value,
|
|
||||||
lastUpdatedAt: p.lastUpdatedAt,
|
|
||||||
network: d.network,
|
|
||||||
address: d.address,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('[prices] results:', results.length)
|
|
||||||
return results
|
|
||||||
} catch (error) {
|
|
||||||
console.log('[prices] error:', error)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchGasPrice: async ({ chainid }: { chainid: string }) => {
|
|
||||||
const key = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
})
|
|
||||||
if (!key) {
|
|
||||||
console.log('[gas] no key')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const client = getClient(chainid, key)
|
|
||||||
const gasPrice = await client.getGasPrice()
|
|
||||||
console.log(
|
|
||||||
'[gas] price for chain',
|
|
||||||
chainid,
|
|
||||||
':',
|
|
||||||
gasPrice.toString(),
|
|
||||||
)
|
|
||||||
return formatGwei(gasPrice)
|
|
||||||
} catch (error) {
|
|
||||||
console.log('[gas] error:', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchTransactionDetails: async ({
|
|
||||||
hash,
|
|
||||||
chainid,
|
|
||||||
address,
|
|
||||||
}: {
|
|
||||||
hash: string
|
|
||||||
chainid: string
|
|
||||||
address?: string
|
|
||||||
}) => {
|
|
||||||
const key = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
})
|
|
||||||
if (!key) return null
|
|
||||||
try {
|
|
||||||
const details = await getTransactionDetails(hash, chainid)
|
|
||||||
if (!details) return null
|
|
||||||
if (address) {
|
|
||||||
const transfers = db
|
|
||||||
.select()
|
|
||||||
.from(txHistory)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(txHistory.chainId, chainid),
|
|
||||||
eq(txHistory.hash, hash.toLowerCase()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.all()
|
|
||||||
if (transfers.length > 0) {
|
|
||||||
const paired = await pairTransfers(transfers, address)
|
|
||||||
if (paired.length > 0 && paired[0].pairedValue) {
|
|
||||||
details.pairedValue = paired[0].pairedValue
|
|
||||||
details.pairedSymbol = paired[0].pairedSymbol
|
|
||||||
details.pairedDecimals = paired[0].pairedDecimals
|
|
||||||
details.pairedContractAddress =
|
|
||||||
paired[0].pairedContractAddress
|
|
||||||
details.pairedLogo = paired[0].pairedLogo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return details
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
evmCreateWallet: async ({
|
|
||||||
name,
|
|
||||||
phrase,
|
|
||||||
passwordHash,
|
|
||||||
}: {
|
|
||||||
name: string
|
|
||||||
phrase: string
|
|
||||||
passwordHash?: string
|
|
||||||
}) => {
|
|
||||||
console.log('[rpc] evmCreateWallet:', name)
|
|
||||||
const id = crypto.randomUUID()
|
|
||||||
const vaultKey = `evm_seed_${id}`
|
|
||||||
await Bun.secrets.set({
|
|
||||||
service: 'koins',
|
|
||||||
name: vaultKey,
|
|
||||||
value: phrase,
|
|
||||||
})
|
|
||||||
await db.insert(evmWallets).values({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
passwordHash: passwordHash ?? null,
|
|
||||||
vaultKey,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
})
|
|
||||||
console.log('[rpc] evmCreateWallet complete:', id)
|
|
||||||
return { id, name, createdAt: new Date().toISOString() }
|
|
||||||
},
|
|
||||||
evmListWallets: async () => {
|
|
||||||
console.log('[rpc] evmListWallets')
|
|
||||||
const wallets = db.select().from(evmWallets).all()
|
|
||||||
return wallets.map((w: any) => ({
|
|
||||||
id: w.id,
|
|
||||||
name: w.name,
|
|
||||||
hasPassword: !!w.passwordHash,
|
|
||||||
vaultKey: w.vaultKey,
|
|
||||||
createdAt: w.createdAt,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
evmGetSeed: async ({ vaultKey }: { vaultKey: string }) => {
|
|
||||||
console.log('[rpc] evmGetSeed:', vaultKey)
|
|
||||||
const seed = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: vaultKey,
|
|
||||||
})
|
|
||||||
if (!seed) throw new Error('Seed not found in keychain')
|
|
||||||
return seed
|
|
||||||
},
|
|
||||||
evmDeleteWallet: async ({ id }: { id: string }) => {
|
|
||||||
console.log('[rpc] evmDeleteWallet:', id)
|
|
||||||
const wallet = db
|
|
||||||
.select()
|
|
||||||
.from(evmWallets)
|
|
||||||
.where(eq(evmWallets.id, id))
|
|
||||||
.get()
|
|
||||||
if (!wallet) throw new Error('Wallet not found')
|
|
||||||
await Promise.all([
|
|
||||||
Bun.secrets.delete({
|
|
||||||
service: 'koins',
|
|
||||||
name: wallet.vaultKey,
|
|
||||||
}),
|
|
||||||
Bun.secrets.delete({
|
|
||||||
service: 'koins',
|
|
||||||
name: `evm_auth_${id}`,
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
db.delete(evmWallets).where(eq(evmWallets.id, id)).run()
|
|
||||||
console.log('[rpc] evmDeleteWallet complete')
|
|
||||||
},
|
|
||||||
evmSendTransfer: async ({
|
|
||||||
seed,
|
|
||||||
to,
|
|
||||||
amount,
|
|
||||||
chainid,
|
|
||||||
contractAddress,
|
|
||||||
tokenDecimals,
|
|
||||||
}: {
|
|
||||||
seed: string
|
|
||||||
to: string
|
|
||||||
amount: string
|
|
||||||
chainid: string
|
|
||||||
contractAddress?: string
|
|
||||||
tokenDecimals?: number
|
|
||||||
}) => {
|
|
||||||
const key = await Bun.secrets.get({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
})
|
|
||||||
if (!key) throw new Error('API key not set')
|
|
||||||
const _amount = String(amount)
|
|
||||||
const account = mnemonicToAccount(seed)
|
|
||||||
const id = getChainID(chainid)
|
|
||||||
if (!id) throw new Error('Unsupported chain')
|
|
||||||
const chain = getChain(id)
|
|
||||||
const walletClient = createWalletClient({
|
|
||||||
account,
|
|
||||||
chain,
|
|
||||||
transport: alchemyTransport({ apiKey: key }),
|
|
||||||
})
|
|
||||||
if (contractAddress && tokenDecimals !== undefined) {
|
|
||||||
const erc20Abi = [
|
|
||||||
{
|
|
||||||
name: 'transfer',
|
|
||||||
type: 'function',
|
|
||||||
inputs: [
|
|
||||||
{ name: 'to', type: 'address' },
|
|
||||||
{ name: 'amount', type: 'uint256' },
|
|
||||||
],
|
|
||||||
outputs: [{ name: '', type: 'bool' }],
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
const data = encodeFunctionData({
|
|
||||||
abi: erc20Abi,
|
|
||||||
functionName: 'transfer',
|
|
||||||
args: [
|
|
||||||
to as `0x${string}`,
|
|
||||||
parseUnits(_amount, tokenDecimals),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
const hash = await walletClient.sendTransaction({
|
|
||||||
to: contractAddress as `0x${string}`,
|
|
||||||
data,
|
|
||||||
value: 0n,
|
|
||||||
})
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
const hash = await walletClient.sendTransaction({
|
|
||||||
to: to as `0x${string}`,
|
|
||||||
value: parseEther(_amount),
|
|
||||||
})
|
|
||||||
return hash
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import {
|
||||||
|
generateWallet,
|
||||||
|
walletFromSecret,
|
||||||
|
getXrpWalletList,
|
||||||
|
saveXrpWalletList,
|
||||||
|
getBalance,
|
||||||
|
getTransactions,
|
||||||
|
getTxDetails,
|
||||||
|
sendPayment,
|
||||||
|
getFee,
|
||||||
|
} from '../../lib/xrp'
|
||||||
|
import type { XrpWalletInfo } from '../../../lib/rpc-schema'
|
||||||
|
|
||||||
|
const SERVICE = 'koins'
|
||||||
|
|
||||||
|
export function createXrpHandlers() {
|
||||||
|
return {
|
||||||
|
xrpListWallets: async () => {
|
||||||
|
console.log('[rpc] xrpListWallets')
|
||||||
|
return getXrpWalletList()
|
||||||
|
},
|
||||||
|
xrpCreateWallet: async ({
|
||||||
|
name,
|
||||||
|
hasPassword,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
hasPassword?: boolean
|
||||||
|
}) => {
|
||||||
|
console.log('[rpc] xrpCreateWallet:', name)
|
||||||
|
const { address, seed } = generateWallet()
|
||||||
|
const id = crypto.randomUUID()
|
||||||
|
const vaultKey = `xrp_seed_${id}`
|
||||||
|
await Bun.secrets.set({ service: SERVICE, name: vaultKey, value: seed })
|
||||||
|
const wallets = await getXrpWalletList()
|
||||||
|
const createdAt = new Date().toISOString()
|
||||||
|
const info: XrpWalletInfo = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
address,
|
||||||
|
hasPassword: hasPassword ?? false,
|
||||||
|
vaultKey,
|
||||||
|
createdAt,
|
||||||
|
}
|
||||||
|
await saveXrpWalletList([...wallets, info])
|
||||||
|
console.log('[rpc] xrpCreateWallet complete:', id)
|
||||||
|
return { id, address, seed, createdAt }
|
||||||
|
},
|
||||||
|
xrpImportWallet: async ({
|
||||||
|
name,
|
||||||
|
secret,
|
||||||
|
hasPassword,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
secret: string
|
||||||
|
hasPassword?: boolean
|
||||||
|
}) => {
|
||||||
|
console.log('[rpc] xrpImportWallet:', name)
|
||||||
|
const { address } = walletFromSecret(secret)
|
||||||
|
const id = crypto.randomUUID()
|
||||||
|
const vaultKey = `xrp_seed_${id}`
|
||||||
|
await Bun.secrets.set({
|
||||||
|
service: SERVICE,
|
||||||
|
name: vaultKey,
|
||||||
|
value: secret.trim(),
|
||||||
|
})
|
||||||
|
const wallets = await getXrpWalletList()
|
||||||
|
const createdAt = new Date().toISOString()
|
||||||
|
const info: XrpWalletInfo = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
address,
|
||||||
|
hasPassword: hasPassword ?? false,
|
||||||
|
vaultKey,
|
||||||
|
createdAt,
|
||||||
|
}
|
||||||
|
await saveXrpWalletList([...wallets, info])
|
||||||
|
console.log('[rpc] xrpImportWallet complete:', id)
|
||||||
|
return { id, address, createdAt }
|
||||||
|
},
|
||||||
|
xrpDeleteWallet: async ({ id }: { id: string }) => {
|
||||||
|
console.log('[rpc] xrpDeleteWallet:', id)
|
||||||
|
const wallets = await getXrpWalletList()
|
||||||
|
const wallet = wallets.find((w) => w.id === id)
|
||||||
|
if (!wallet) throw new Error('Wallet not found')
|
||||||
|
await Promise.all([
|
||||||
|
Bun.secrets.delete({ service: SERVICE, name: wallet.vaultKey }),
|
||||||
|
Bun.secrets.delete({ service: SERVICE, name: `xrp_auth_${id}` }),
|
||||||
|
])
|
||||||
|
await saveXrpWalletList(wallets.filter((w) => w.id !== id))
|
||||||
|
console.log('[rpc] xrpDeleteWallet complete')
|
||||||
|
},
|
||||||
|
xrpGetSeed: async ({ vaultKey }: { vaultKey: string }) => {
|
||||||
|
console.log('[rpc] xrpGetSeed:', vaultKey)
|
||||||
|
const seed = await Bun.secrets.get({ service: SERVICE, name: vaultKey })
|
||||||
|
if (!seed) throw new Error('Seed not found in keychain')
|
||||||
|
return seed
|
||||||
|
},
|
||||||
|
xrpGetBalance: async ({ address }: { address: string }) => {
|
||||||
|
console.log('[rpc] xrpGetBalance:', address)
|
||||||
|
return getBalance(address)
|
||||||
|
},
|
||||||
|
xrpGetTransactions: async ({ address }: { address: string }) => {
|
||||||
|
console.log('[rpc] xrpGetTransactions:', address)
|
||||||
|
try {
|
||||||
|
return await getTransactions(address)
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error && e.message.includes('Account not found')) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xrpGetTxDetails: async ({ hash }: { hash: string }) => {
|
||||||
|
console.log('[rpc] xrpGetTxDetails:', hash)
|
||||||
|
return getTxDetails(hash)
|
||||||
|
},
|
||||||
|
xrpSend: async ({
|
||||||
|
secret,
|
||||||
|
to,
|
||||||
|
amount,
|
||||||
|
destinationTag,
|
||||||
|
}: {
|
||||||
|
secret: string
|
||||||
|
to: string
|
||||||
|
amount: string
|
||||||
|
destinationTag?: number
|
||||||
|
}) => {
|
||||||
|
console.log('[rpc] xrpSend:', { to, amount, destinationTag })
|
||||||
|
const result = await sendPayment(secret, to, amount, destinationTag)
|
||||||
|
console.log('[rpc] xrpSend complete:', result.hash)
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
xrpGetFee: async () => {
|
||||||
|
console.log('[rpc] xrpGetFee')
|
||||||
|
return getFee()
|
||||||
|
},
|
||||||
|
fetchXrpPrice: async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
'https://api.coingecko.com/api/v3/simple/price?ids=ripple&vs_currencies=usd',
|
||||||
|
{ signal: AbortSignal.timeout(10000) },
|
||||||
|
)
|
||||||
|
if (!res.ok) return null
|
||||||
|
const data = await res.json()
|
||||||
|
if (!data?.ripple?.usd) return null
|
||||||
|
return { usd: String(data.ripple.usd) }
|
||||||
|
} catch (error) {
|
||||||
|
console.log('[xrp][price] error:', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,799 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { electrobun, type TxEntry } from '$lib/electrobun.js'
|
|
||||||
import {
|
|
||||||
evmWallet as wallet,
|
|
||||||
type NetworkId,
|
|
||||||
} from '$lib/states/evm-wallet.svelte.js'
|
|
||||||
import { Button } from '$lib/components/ui/button/index.js'
|
|
||||||
import { Textarea } from '$lib/components/ui/textarea/index.js'
|
|
||||||
import { generateMnemonic } from 'viem/accounts'
|
|
||||||
import { english } from 'viem/accounts'
|
|
||||||
import { navigate } from 'sv-router/generated'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '$lib/components/ui/card/index.js'
|
|
||||||
import ArrowDown from '@lucide/svelte/icons/arrow-down'
|
|
||||||
import ArrowUp from '@lucide/svelte/icons/arrow-up'
|
|
||||||
import ArrowLeftRight from '@lucide/svelte/icons/arrow-left-right'
|
|
||||||
import QrCodeIcon from '@lucide/svelte/icons/qr-code'
|
|
||||||
import SettingsIcon from '@lucide/svelte/icons/settings'
|
|
||||||
import Fingerprint from '@lucide/svelte/icons/fingerprint'
|
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2'
|
|
||||||
import Plus from '@lucide/svelte/icons/plus'
|
|
||||||
import WalletIcon from '@lucide/svelte/icons/wallet'
|
|
||||||
import LockKeyhole from '@lucide/svelte/icons/lock-keyhole'
|
|
||||||
import ExternalLink from '@lucide/svelte/icons/external-link'
|
|
||||||
import * as Dialog from '$lib/components/ui/dialog/index.js'
|
|
||||||
import QRCode from 'qrcode'
|
|
||||||
import Loader from './loader.svelte'
|
|
||||||
import { LineChart } from 'layerchart'
|
|
||||||
import { scaleUtc } from 'd3-scale'
|
|
||||||
import { curveLinear } from 'd3-shape'
|
|
||||||
import * as Chart from '$lib/components/ui/chart/index.js'
|
|
||||||
import { formatEther, parseEther } from 'viem'
|
|
||||||
import { frontendLog, frontendLogError } from '$lib/electrobun.js'
|
|
||||||
|
|
||||||
let { networkId }: { networkId: NetworkId } = $props()
|
|
||||||
|
|
||||||
const CDN =
|
|
||||||
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains'
|
|
||||||
|
|
||||||
const chainDir: Record<string, string> = {
|
|
||||||
eth: 'ethereum',
|
|
||||||
bsc: 'smartchain',
|
|
||||||
polygon: 'polygon',
|
|
||||||
}
|
|
||||||
|
|
||||||
function nativeIcon(id: string) {
|
|
||||||
return `${CDN}/${chainDir[id]}/info/logo.png`
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenIcon(id: string, address?: string) {
|
|
||||||
if (!address) return ''
|
|
||||||
return `${CDN}/${chainDir[id]}/assets/${address}/logo.png`
|
|
||||||
}
|
|
||||||
|
|
||||||
function txAction(tx: TxEntry, address: string): string {
|
|
||||||
if (tx.pairedValue) return 'Swap'
|
|
||||||
const dir =
|
|
||||||
tx.from.toLowerCase() === address.toLowerCase()
|
|
||||||
? 'Sent'
|
|
||||||
: 'Received'
|
|
||||||
if (tx.tokenSymbol) return `${dir} ${tx.tokenSymbol}`
|
|
||||||
return dir
|
|
||||||
}
|
|
||||||
|
|
||||||
const w = wallet
|
|
||||||
let inputSeed = $state('')
|
|
||||||
let inputPassword = $state('')
|
|
||||||
let inputUnlockPassword = $state('')
|
|
||||||
let inputWalletName = $state('')
|
|
||||||
let apiKeyInput = $state('')
|
|
||||||
let qrDataUrl = $state('')
|
|
||||||
let qrDialogOpen = $state(false)
|
|
||||||
let showCreateForm = $state(false)
|
|
||||||
$effect(() => {
|
|
||||||
if (w.address) {
|
|
||||||
QRCode.toDataURL(w.address, { width: 256, margin: 1 }).then(
|
|
||||||
(url) => (qrDataUrl = url),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function navTarget(id: NetworkId): string {
|
|
||||||
if (id === 'eth') return '/multicoin'
|
|
||||||
return `/multicoin/${id}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const CHAIN_NETWORKS: Record<string, string> = {
|
|
||||||
'1': 'eth-mainnet',
|
|
||||||
'137': 'polygon-mainnet',
|
|
||||||
'56': 'bnb-mainnet',
|
|
||||||
}
|
|
||||||
|
|
||||||
let initStarted = false
|
|
||||||
let nativePrice = $state<{
|
|
||||||
value: string
|
|
||||||
currency: string
|
|
||||||
} | null>(null)
|
|
||||||
let tokenPrices = $state<
|
|
||||||
Record<string, { value: string; currency: string }>
|
|
||||||
>({})
|
|
||||||
let gasPrice = $state<string | null>(null)
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
const syms: string[] = w.symbol ? [w.symbol] : []
|
|
||||||
const addrs: { network: string; address: string }[] = []
|
|
||||||
const network = CHAIN_NETWORKS[w.chainid]
|
|
||||||
if (network) {
|
|
||||||
for (const t of w.tokenBalances) {
|
|
||||||
if (t.contractAddress) {
|
|
||||||
addrs.push({ network, address: t.contractAddress })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (syms.length > 0 || addrs.length > 0) {
|
|
||||||
electrobun.rpc?.request
|
|
||||||
.fetchTokenPrices({
|
|
||||||
symbols: syms.length > 0 ? syms : undefined,
|
|
||||||
addresses: addrs.length > 0 ? addrs : undefined,
|
|
||||||
})
|
|
||||||
.then((prices) => {
|
|
||||||
const usd = prices.find(
|
|
||||||
(p) =>
|
|
||||||
p.currency === 'usd' &&
|
|
||||||
p.symbol === w.symbol &&
|
|
||||||
!p.address,
|
|
||||||
)
|
|
||||||
nativePrice = usd
|
|
||||||
? { value: usd.value, currency: usd.currency }
|
|
||||||
: null
|
|
||||||
const map: Record<
|
|
||||||
string,
|
|
||||||
{ value: string; currency: string }
|
|
||||||
> = {}
|
|
||||||
for (const p of prices) {
|
|
||||||
if (p.currency === 'usd' && p.address) {
|
|
||||||
map[p.address.toLowerCase()] = {
|
|
||||||
value: p.value,
|
|
||||||
currency: p.currency,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tokenPrices = map
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (w.chainid) {
|
|
||||||
electrobun.rpc?.request
|
|
||||||
.fetchGasPrice({ chainid: w.chainid })
|
|
||||||
.then((gp) => (gasPrice = gp))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (!initStarted) {
|
|
||||||
initStarted = true
|
|
||||||
w.init().then(async () => {
|
|
||||||
if (w.accountType === 'multi') {
|
|
||||||
await w.switchNetwork(networkId)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function isNative(tx: TxEntry): boolean {
|
|
||||||
return !tx.contractAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
const balanceHistory = $derived.by(() => {
|
|
||||||
const native = w.transactions.filter(isNative)
|
|
||||||
const sorted = [...native].reverse()
|
|
||||||
if (sorted.length === 0) return []
|
|
||||||
let cum = 0n
|
|
||||||
const addr = w.address.toLowerCase()
|
|
||||||
const points: { date: Date; balance: number }[] = []
|
|
||||||
for (const tx of sorted) {
|
|
||||||
console.log(tx.value)
|
|
||||||
const val = parseEther(tx.value)
|
|
||||||
if (tx.from.toLowerCase() === addr) cum -= val
|
|
||||||
else cum += val
|
|
||||||
points.push({
|
|
||||||
date: new Date(Number(tx.timeStamp) * 1000),
|
|
||||||
balance: Number(formatEther(cum)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const cur = Number(w.balance)
|
|
||||||
const offset = cur - Number(formatEther(cum))
|
|
||||||
for (const p of points) p.balance += offset
|
|
||||||
return points
|
|
||||||
})
|
|
||||||
|
|
||||||
$inspect(balanceHistory)
|
|
||||||
|
|
||||||
let selectedRange = $state<'1D' | '1W' | '1M' | '1Y' | 'ALL'>('ALL')
|
|
||||||
|
|
||||||
const rangeCutoff = $derived(
|
|
||||||
selectedRange === 'ALL'
|
|
||||||
? 0
|
|
||||||
: Date.now() -
|
|
||||||
(
|
|
||||||
{
|
|
||||||
'1D': 86400000,
|
|
||||||
'1W': 604800000,
|
|
||||||
'1M': 2592000000,
|
|
||||||
'1Y': 31536000000,
|
|
||||||
} as const
|
|
||||||
)[selectedRange],
|
|
||||||
)
|
|
||||||
|
|
||||||
const chartData = $derived(
|
|
||||||
selectedRange === 'ALL'
|
|
||||||
? balanceHistory
|
|
||||||
: balanceHistory.filter((p) => p.date.getTime() >= rangeCutoff),
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if !w.ready}
|
|
||||||
<div
|
|
||||||
class="mx-auto mt-16 max-w-md text-center text-muted-foreground text-sm">
|
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
{:else if w.wallets.length === 0 || showCreateForm}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>
|
|
||||||
{#if showCreateForm}
|
|
||||||
{@const cur = w.currentWallet}
|
|
||||||
{cur ? `Add Another Wallet` : `Import / Create Wallet`}
|
|
||||||
{:else}
|
|
||||||
Import / Create Wallet
|
|
||||||
{/if}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Name your wallet and enter your seed phrase to save it
|
|
||||||
securely, or generate a new wallet
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Wallet name (e.g. Main, Savings)"
|
|
||||||
class="flex h-8 w-full rounded-md border border-input bg-transparent px-2 py-1 font-mono text-xs"
|
|
||||||
bind:value={inputWalletName} />
|
|
||||||
<Textarea
|
|
||||||
placeholder="Enter your 12 or 24 word seed phrase"
|
|
||||||
bind:value={inputSeed} />
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onclick={() => {
|
|
||||||
inputSeed = generateMnemonic(english)
|
|
||||||
}}
|
|
||||||
class="w-full mt-1">
|
|
||||||
Generate Random Seed
|
|
||||||
</Button>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="Set a password (optional)"
|
|
||||||
class="flex h-8 w-full rounded-md border border-input bg-transparent px-2 py-1 font-mono text-xs"
|
|
||||||
bind:value={inputPassword} />
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<Button
|
|
||||||
onclick={() =>
|
|
||||||
w.createWallet(
|
|
||||||
inputWalletName,
|
|
||||||
inputSeed,
|
|
||||||
inputPassword || undefined,
|
|
||||||
)}
|
|
||||||
disabled={w.loading ||
|
|
||||||
!inputSeed.trim() ||
|
|
||||||
!inputWalletName.trim()}>
|
|
||||||
{w.loading ? 'Saving...' : 'Save Wallet'}
|
|
||||||
</Button>
|
|
||||||
{#if showCreateForm}
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onclick={() => (showCreateForm = false)}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if w.error}
|
|
||||||
<p class="mt-3 text-red-500">{w.error}</p>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
{:else if !w.currentWalletId}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Wallets</CardTitle>
|
|
||||||
<CardDescription>Select a wallet to use</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
{#each w.wallets as wal}
|
|
||||||
<button
|
|
||||||
onclick={async () => {
|
|
||||||
if (wal.hasPassword) {
|
|
||||||
await w.selectWallet(wal.id)
|
|
||||||
} else {
|
|
||||||
await w.selectAndUnlockWallet(wal.id)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
class="flex w-full cursor-pointer items-center gap-3 rounded-md border border-input p-3 text-left hover:bg-muted transition-colors">
|
|
||||||
<WalletIcon
|
|
||||||
size={20}
|
|
||||||
class="shrink-0 text-muted-foreground" />
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<p class="font-medium text-sm">{wal.name}</p>
|
|
||||||
<p class="text-xs text-muted-foreground">
|
|
||||||
{new Date(wal.createdAt).toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-1">
|
|
||||||
{#if wal.hasPassword}
|
|
||||||
<LockKeyhole
|
|
||||||
size={14}
|
|
||||||
class="text-muted-foreground" />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onclick={() => (showCreateForm = true)}
|
|
||||||
class="mt-2">
|
|
||||||
<Plus size={16} />
|
|
||||||
Add Wallet
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
{:else if w.isLocked}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>
|
|
||||||
{w.currentWallet?.name ?? 'Wallet'} Locked
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>Unlock to access your wallet</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
{#if w.biometricAvailable}
|
|
||||||
<Button
|
|
||||||
onclick={() => w.unlockWithBiometrics()}
|
|
||||||
disabled={w.loading}>
|
|
||||||
<Fingerprint size={16} />
|
|
||||||
{w.loading ? 'Unlocking...' : 'Unlock with Touch ID'}
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
{#if w.currentPasswordHash}
|
|
||||||
<div class="flex gap-2 items-end">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="Enter password"
|
|
||||||
class="flex h-8 w-full rounded-md border border-input bg-transparent px-2 py-1 font-mono text-xs"
|
|
||||||
bind:value={inputUnlockPassword} />
|
|
||||||
<Button
|
|
||||||
onclick={async () => {
|
|
||||||
const ok = await w.unlockWithPassword(
|
|
||||||
inputUnlockPassword,
|
|
||||||
)
|
|
||||||
if (!ok) w.error = 'Wrong password'
|
|
||||||
inputUnlockPassword = ''
|
|
||||||
}}
|
|
||||||
disabled={w.loading || !inputUnlockPassword}
|
|
||||||
size="sm">
|
|
||||||
{w.loading ? '...' : 'Unlock'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<Button variant="outline" onclick={() => w.clearSelection()}>
|
|
||||||
Pick another wallet
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{#if w.error}
|
|
||||||
<p class="mt-3 text-red-500">{w.error}</p>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
{:else}
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="flex gap-1 justify-center" role="group">
|
|
||||||
{#each w.networks as net}
|
|
||||||
<button
|
|
||||||
class="cursor-pointer rounded-md px-2.5 py-1 text-xs font-medium transition-colors {w.network ===
|
|
||||||
net.id
|
|
||||||
? 'bg-primary text-primary-foreground'
|
|
||||||
: 'bg-muted text-muted-foreground hover:bg-muted/80'}"
|
|
||||||
onclick={() => navigate(navTarget(net.id))}>
|
|
||||||
<img
|
|
||||||
src={nativeIcon(net.id)}
|
|
||||||
alt=""
|
|
||||||
class="-ml-0.5 mr-1 inline-block size-3.5 rounded-full align-middle"
|
|
||||||
onerror={(e) =>
|
|
||||||
((e.target as HTMLElement).style.display = 'none')} />
|
|
||||||
{net.name}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<CardTitle>
|
|
||||||
{w.currentWallet?.name ?? 'Wallet'}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>Your wallet is unlocked</CardDescription>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{#if w.address}
|
|
||||||
<div class="mb-4 space-y-1">
|
|
||||||
<p class="font-medium text-xs">Address</p>
|
|
||||||
<div class="flex items-start gap-2">
|
|
||||||
<p class="font-mono text-xs break-all flex-1">
|
|
||||||
{w.address}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onclick={() =>
|
|
||||||
electrobun.rpc?.request.openExternal({
|
|
||||||
url: w.explorerAddressUrl,
|
|
||||||
})}
|
|
||||||
class="shrink-0 cursor-pointer rounded-md border p-1.5 text-muted-foreground hover:bg-muted transition-colors">
|
|
||||||
<ExternalLink size={16} />
|
|
||||||
</button>
|
|
||||||
{#if qrDataUrl}
|
|
||||||
<Dialog.Root bind:open={qrDialogOpen}>
|
|
||||||
<Dialog.Trigger>
|
|
||||||
<button
|
|
||||||
class="shrink-0 cursor-pointer rounded-md border p-1.5 text-muted-foreground hover:bg-muted transition-colors">
|
|
||||||
<QrCodeIcon size={16} />
|
|
||||||
</button>
|
|
||||||
</Dialog.Trigger>
|
|
||||||
<Dialog.Content>
|
|
||||||
<div
|
|
||||||
class="flex flex-col items-center gap-3 py-4">
|
|
||||||
<div class="relative">
|
|
||||||
<img
|
|
||||||
src={qrDataUrl}
|
|
||||||
alt="QR code"
|
|
||||||
class="size-52 rounded-md" />
|
|
||||||
<img
|
|
||||||
src={nativeIcon(w.network)}
|
|
||||||
alt=""
|
|
||||||
class="absolute left-1/2 top-1/2 size-7 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white p-0.5" />
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="font-mono text-xs break-all text-center max-w-64">
|
|
||||||
{w.address}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog.Root>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<p class="mt-2 font-medium text-xs">
|
|
||||||
{w.symbol} Balance
|
|
||||||
</p>
|
|
||||||
<p class="flex items-center gap-1.5 font-mono text-lg">
|
|
||||||
<img
|
|
||||||
src={nativeIcon(w.network)}
|
|
||||||
alt=""
|
|
||||||
class="size-4 rounded-full"
|
|
||||||
onerror={(e) =>
|
|
||||||
((e.target as HTMLElement).style.display =
|
|
||||||
'none')} />
|
|
||||||
{w.balance}
|
|
||||||
{w.symbol}
|
|
||||||
</p>
|
|
||||||
{#if nativePrice}
|
|
||||||
{@const balVal =
|
|
||||||
Number(w.balance) * Number(nativePrice.value)}
|
|
||||||
<p class="font-mono text-xs text-muted-foreground">
|
|
||||||
${balVal.toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
})} @ ${Number(nativePrice.value).toLocaleString(
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
},
|
|
||||||
)}/{w.symbol}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
{#if gasPrice}
|
|
||||||
{@const gasNative = (Number(gasPrice) * 21000) / 1e9}
|
|
||||||
<p class="font-mono text-xs text-muted-foreground mt-1">
|
|
||||||
Gas: {Number(gasPrice).toFixed(1)} Gwei ({gasNative.toLocaleString(
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
minimumFractionDigits: 6,
|
|
||||||
maximumFractionDigits: 6,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
{w.symbol})
|
|
||||||
{#if nativePrice}
|
|
||||||
${(
|
|
||||||
gasNative * Number(nativePrice.value)
|
|
||||||
).toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 4,
|
|
||||||
maximumFractionDigits: 4,
|
|
||||||
})}
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 space-y-1">
|
|
||||||
<p class="font-medium text-xs">Tokens</p>
|
|
||||||
{#each w.tokenBalances as t}
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-1.5 font-mono text-sm">
|
|
||||||
<img
|
|
||||||
src={t.logo}
|
|
||||||
alt=""
|
|
||||||
class="size-4 rounded-full"
|
|
||||||
onerror={(e) =>
|
|
||||||
((e.target as HTMLElement).style.display =
|
|
||||||
'none')} />
|
|
||||||
{Number(t.balance) < 0.0001 && Number(t.balance) > 0
|
|
||||||
? '<0.0001'
|
|
||||||
: Number(t.balance).toFixed(4)}
|
|
||||||
{t.symbol}
|
|
||||||
</div>
|
|
||||||
{#if t.contractAddress && tokenPrices[t.contractAddress.toLowerCase()]}
|
|
||||||
{@const val =
|
|
||||||
tokenPrices[t.contractAddress.toLowerCase()]}
|
|
||||||
{@const balVal =
|
|
||||||
Number(t.balance) * Number(val.value)}
|
|
||||||
<p
|
|
||||||
class="font-mono text-xs text-muted-foreground -mt-0.5 ml-6">
|
|
||||||
${balVal.toLocaleString(undefined, {
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
})} @ ${Number(val.value).toLocaleString(
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
},
|
|
||||||
)}/{t.symbol}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if w.loadingBalance && w.address}
|
|
||||||
<div class="flex justify-center py-4">
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if w.error}
|
|
||||||
<p class="mb-3 text-red-500">{w.error}</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="flex gap-2 flex-wrap">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onclick={() => navigate('/multicoin/send')}>
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onclick={() => navigate('/settings')}>
|
|
||||||
<SettingsIcon size={16} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onclick={async () => {
|
|
||||||
await w.logout()
|
|
||||||
navigate('/')
|
|
||||||
}}>
|
|
||||||
Logout
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{#if balanceHistory.length > 1}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<CardTitle>Balance</CardTitle>
|
|
||||||
<div class="flex gap-1">
|
|
||||||
{#each ['1D', '1W', '1M', '1Y', 'ALL'] as const as range}
|
|
||||||
<button
|
|
||||||
onclick={() => (selectedRange = range)}
|
|
||||||
class="rounded px-1.5 py-0.5 text-xs font-medium transition-colors {selectedRange ===
|
|
||||||
range
|
|
||||||
? 'bg-primary text-primary-foreground'
|
|
||||||
: 'text-muted-foreground hover:text-foreground'}">
|
|
||||||
{range}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{#if chartData.length > 1}
|
|
||||||
<Chart.Container
|
|
||||||
config={{
|
|
||||||
balance: { label: w.symbol, color: 'var(--chart-1)' },
|
|
||||||
}}>
|
|
||||||
<LineChart
|
|
||||||
data={chartData}
|
|
||||||
x="date"
|
|
||||||
xScale={scaleUtc()}
|
|
||||||
axis="x"
|
|
||||||
points
|
|
||||||
series={[
|
|
||||||
{
|
|
||||||
key: 'balance',
|
|
||||||
label: w.symbol,
|
|
||||||
color: 'var(--chart-1)',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
yDomain={[
|
|
||||||
chartData.reduce((a, b) =>
|
|
||||||
a.balance < b.balance ? a : b,
|
|
||||||
).balance,
|
|
||||||
chartData.reduce((a, b) =>
|
|
||||||
a.balance > b.balance ? a : b,
|
|
||||||
).balance,
|
|
||||||
]}
|
|
||||||
props={{
|
|
||||||
spline: {
|
|
||||||
curve: curveLinear,
|
|
||||||
motion: 'tween',
|
|
||||||
strokeWidth: 2,
|
|
||||||
},
|
|
||||||
yAxis: {},
|
|
||||||
xAxis: {
|
|
||||||
format: (v: Date) =>
|
|
||||||
v.toLocaleDateString('en-US', {
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
highlight: { points: { r: 4 } },
|
|
||||||
}}>
|
|
||||||
{#snippet tooltip()}
|
|
||||||
<Chart.Tooltip
|
|
||||||
labelFormatter={(value: Date) => {
|
|
||||||
return value.toLocaleDateString()
|
|
||||||
}}
|
|
||||||
valueFormatter={(value: Number) => {
|
|
||||||
return Number(value).toFixed(6)
|
|
||||||
}} />
|
|
||||||
{/snippet}
|
|
||||||
</LineChart>
|
|
||||||
</Chart.Container>
|
|
||||||
{:else}
|
|
||||||
<p class="text-center text-sm text-muted-foreground">
|
|
||||||
No transactions in this time range.
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Transactions</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{#if w.loadingTransactions}
|
|
||||||
<div class="flex justify-center py-4">
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
{:else if !w.apiKey}
|
|
||||||
<div class="flex gap-2 items-center">
|
|
||||||
<input
|
|
||||||
class="flex h-8 w-full rounded-md border border-input bg-transparent px-2 py-1 font-mono text-xs"
|
|
||||||
placeholder="Alchemy API key"
|
|
||||||
bind:value={apiKeyInput} />
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
onclick={() => w.saveApiKey(apiKeyInput)}
|
|
||||||
disabled={!apiKeyInput.trim()}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{:else if w.transactions.length === 0}
|
|
||||||
<p class="text-muted-foreground text-xs">
|
|
||||||
No transactions found
|
|
||||||
</p>
|
|
||||||
{:else}
|
|
||||||
<div class="max-h-96 space-y-1 overflow-y-auto">
|
|
||||||
{#each w.transactions.filter((tx) => tx.tokenDecimal !== '0') as tx}
|
|
||||||
<button
|
|
||||||
onclick={() =>
|
|
||||||
navigate('/tx/:chainid/:hash', {
|
|
||||||
params: {
|
|
||||||
chainid: w.chainid,
|
|
||||||
hash: tx.hash,
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
class="flex w-full cursor-pointer items-start gap-2 rounded-md bg-muted px-3 py-2 text-xs text-left hover:bg-muted/80 transition-colors">
|
|
||||||
<div
|
|
||||||
class="shrink-0 mt-0.5 {txAction(tx, w.address) ===
|
|
||||||
'Swap'
|
|
||||||
? 'text-yellow-500'
|
|
||||||
: tx.from.toLowerCase() ===
|
|
||||||
w.address.toLowerCase()
|
|
||||||
? 'text-muted-foreground'
|
|
||||||
: 'text-green-500'}">
|
|
||||||
{#if txAction(tx, w.address) === 'Swap'}
|
|
||||||
<ArrowLeftRight size={16} />
|
|
||||||
{:else if tx.from.toLowerCase() === w.address.toLowerCase()}
|
|
||||||
<ArrowUp size={16} />
|
|
||||||
{:else}
|
|
||||||
<ArrowDown size={16} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="min-w-0 flex-1 space-y-0.5">
|
|
||||||
<p class="font-medium">
|
|
||||||
{txAction(tx, w.address)}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
class="flex items-center gap-1 text-muted-foreground font-mono">
|
|
||||||
{#if tx.pairedValue}
|
|
||||||
<img
|
|
||||||
src={nativeIcon(w.network)}
|
|
||||||
alt=""
|
|
||||||
class="size-3.5 rounded-full"
|
|
||||||
onerror={(e) =>
|
|
||||||
((e.target as HTMLElement).style.display =
|
|
||||||
'none')} />
|
|
||||||
{Number(tx.value).toFixed(4)}
|
|
||||||
{w.symbol}
|
|
||||||
<span class="mx-0.5">→</span>
|
|
||||||
<img
|
|
||||||
src={tx.pairedLogo ||
|
|
||||||
tokenIcon(w.network, tx.contractAddress)}
|
|
||||||
alt=""
|
|
||||||
class="size-3.5 rounded-full"
|
|
||||||
onerror={(e) =>
|
|
||||||
((e.target as HTMLElement).style.display =
|
|
||||||
'none')} />
|
|
||||||
{Number(tx.pairedValue).toFixed(4)}
|
|
||||||
{tx.pairedSymbol ?? w.symbol}
|
|
||||||
{:else if tx.tokenSymbol}
|
|
||||||
<img
|
|
||||||
src={tx.logo ||
|
|
||||||
tokenIcon(w.network, tx.contractAddress)}
|
|
||||||
alt=""
|
|
||||||
class="size-3.5 rounded-full"
|
|
||||||
onerror={(e) =>
|
|
||||||
((e.target as HTMLElement).style.display =
|
|
||||||
'none')} />
|
|
||||||
{Number(tx.value).toFixed(4)}
|
|
||||||
{tx.tokenSymbol}
|
|
||||||
{:else}
|
|
||||||
<img
|
|
||||||
src={nativeIcon(w.network)}
|
|
||||||
alt=""
|
|
||||||
class="size-3.5 rounded-full"
|
|
||||||
onerror={(e) =>
|
|
||||||
((e.target as HTMLElement).style.display =
|
|
||||||
'none')} />
|
|
||||||
{Number(tx.value).toFixed(4)}
|
|
||||||
{w.symbol}
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
{new Date(
|
|
||||||
Number(tx.timeStamp) * 1000,
|
|
||||||
).toLocaleDateString('en-US', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
class="text-muted-foreground/50 truncate font-mono">
|
|
||||||
{tx.hash.slice(0, 10)}...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
@@ -9,10 +9,11 @@ export type {
|
|||||||
MoneroAccountEntry,
|
MoneroAccountEntry,
|
||||||
MoneroSendResult,
|
MoneroSendResult,
|
||||||
MoneroTransferDetails,
|
MoneroTransferDetails,
|
||||||
TxEntry,
|
XrpWalletInfo,
|
||||||
TransactionDetails,
|
XrpBalance,
|
||||||
TokenBalanceResult,
|
XrpTxEntry,
|
||||||
EvmWalletInfo,
|
XrpTxDetails,
|
||||||
|
XrpSendResult,
|
||||||
} from './rpc-schema'
|
} from './rpc-schema'
|
||||||
|
|
||||||
const rpc = Electroview.defineRPC<RPC>({
|
const rpc = Electroview.defineRPC<RPC>({
|
||||||
|
|||||||
@@ -79,67 +79,50 @@ export type MoneroFeeEstimate = {
|
|||||||
estimatedFee: string
|
estimatedFee: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TxEntry = {
|
export type XrpWalletInfo = {
|
||||||
hash: string
|
|
||||||
timeStamp: string
|
|
||||||
from: string
|
|
||||||
to: string
|
|
||||||
value: string
|
|
||||||
tokenSymbol?: string
|
|
||||||
tokenDecimal?: string
|
|
||||||
contractAddress?: string
|
|
||||||
logo?: string
|
|
||||||
pairedValue?: string
|
|
||||||
pairedSymbol?: string
|
|
||||||
pairedDecimals?: string
|
|
||||||
pairedContractAddress?: string
|
|
||||||
pairedLogo?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TransactionDetails = {
|
|
||||||
hash: string
|
|
||||||
from: string
|
|
||||||
to: string | null
|
|
||||||
value: string
|
|
||||||
blockNumber: string | null
|
|
||||||
fee: string
|
|
||||||
gasPrice: string
|
|
||||||
status: 'success' | 'reverted'
|
|
||||||
type: string
|
|
||||||
nonce: number
|
|
||||||
input: string
|
|
||||||
pairedValue?: string
|
|
||||||
pairedSymbol?: string
|
|
||||||
pairedDecimals?: string
|
|
||||||
pairedContractAddress?: string
|
|
||||||
pairedLogo?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TokenBalanceResult = {
|
|
||||||
symbol: string
|
|
||||||
decimals: number
|
|
||||||
balance: string
|
|
||||||
contractAddress: string
|
|
||||||
logo?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TokenPriceEntry = {
|
|
||||||
symbol: string
|
|
||||||
currency: string
|
|
||||||
value: string
|
|
||||||
lastUpdatedAt: string
|
|
||||||
network?: string
|
|
||||||
address?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EvmWalletInfo = {
|
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
address: string
|
||||||
hasPassword: boolean
|
hasPassword: boolean
|
||||||
vaultKey: string
|
vaultKey: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type XrpBalance = {
|
||||||
|
address: string
|
||||||
|
balance: string
|
||||||
|
funded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type XrpTxEntry = {
|
||||||
|
hash: string
|
||||||
|
amount: string
|
||||||
|
timestamp: string
|
||||||
|
direction: 'in' | 'out'
|
||||||
|
from: string
|
||||||
|
to: string
|
||||||
|
destinationTag?: number
|
||||||
|
fee: string
|
||||||
|
confirmed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type XrpTxDetails = {
|
||||||
|
hash: string
|
||||||
|
from: string
|
||||||
|
to: string
|
||||||
|
amount: string
|
||||||
|
fee: string
|
||||||
|
timestamp: string
|
||||||
|
ledgerIndex: number
|
||||||
|
destinationTag?: number
|
||||||
|
confirmed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type XrpSendResult = {
|
||||||
|
hash: string
|
||||||
|
fee: string
|
||||||
|
}
|
||||||
|
|
||||||
export type GeneralRpcRequests = {
|
export type GeneralRpcRequests = {
|
||||||
resetApp: { params: {}; response: boolean }
|
resetApp: { params: {}; response: boolean }
|
||||||
biometricCanAuth: { params: {}; response: boolean }
|
biometricCanAuth: { params: {}; response: boolean }
|
||||||
@@ -151,23 +134,6 @@ export type GeneralRpcRequests = {
|
|||||||
logToFile: { params: { message: string }; response: void }
|
logToFile: { params: { message: string }; response: void }
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EvmRpcRequests = {
|
|
||||||
fetchTxHistory: { params: { address: string; chainid: string }; response: TxEntry[] }
|
|
||||||
fetchCachedTxHistory: { params: { address: string; chainid: string }; response: TxEntry[] }
|
|
||||||
syncTxHistory: { params: { address: string; chainid: string }; response: void }
|
|
||||||
flushTxCache: { params: {}; response: void }
|
|
||||||
setAutoSync: { params: { address: string; chainid: string } | null; response: void }
|
|
||||||
fetchTokenBalances: { params: { address: string; chainid: string }; response: TokenBalanceResult[] }
|
|
||||||
fetchTransactionDetails: { params: { hash: string; chainid: string; address?: string }; response: TransactionDetails | null }
|
|
||||||
fetchTokenPrices: { params: { symbols?: string[]; addresses?: { network: string; address: string }[] }; response: TokenPriceEntry[] }
|
|
||||||
fetchGasPrice: { params: { chainid: string }; response: string | null }
|
|
||||||
evmCreateWallet: { params: { name: string; phrase: string; passwordHash?: string }; response: { id: string; name: string; createdAt: string } }
|
|
||||||
evmListWallets: { params: {}; response: EvmWalletInfo[] }
|
|
||||||
evmGetSeed: { params: { vaultKey: string }; response: string }
|
|
||||||
evmDeleteWallet: { params: { id: string }; response: void }
|
|
||||||
evmSendTransfer: { params: { seed: string; to: string; amount: string; chainid: string; contractAddress?: string; tokenDecimals?: number }; response: string }
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MoneroRpcRequests = {
|
export type MoneroRpcRequests = {
|
||||||
moneroBinaryStatus: { params: {}; response: MoneroBinaryStatus }
|
moneroBinaryStatus: { params: {}; response: MoneroBinaryStatus }
|
||||||
moneroDownloadBinary: { params: {}; response: void }
|
moneroDownloadBinary: { params: {}; response: void }
|
||||||
@@ -190,15 +156,27 @@ export type MoneroRpcRequests = {
|
|||||||
fetchMoneroPrice: { params: {}; response: { usd: string } | null }
|
fetchMoneroPrice: { params: {}; response: { usd: string } | null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type XrpRpcRequests = {
|
||||||
|
xrpListWallets: { params: {}; response: XrpWalletInfo[] }
|
||||||
|
xrpCreateWallet: { params: { name: string; hasPassword?: boolean }; response: { id: string; address: string; seed: string; createdAt: string } }
|
||||||
|
xrpImportWallet: { params: { name: string; secret: string; hasPassword?: boolean }; response: { id: string; address: string; createdAt: string } }
|
||||||
|
xrpDeleteWallet: { params: { id: string }; response: void }
|
||||||
|
xrpGetSeed: { params: { vaultKey: string }; response: string }
|
||||||
|
xrpGetBalance: { params: { address: string }; response: XrpBalance }
|
||||||
|
xrpGetTransactions: { params: { address: string }; response: XrpTxEntry[] }
|
||||||
|
xrpGetTxDetails: { params: { hash: string }; response: XrpTxDetails | null }
|
||||||
|
xrpSend: { params: { secret: string; to: string; amount: string; destinationTag?: number }; response: XrpSendResult }
|
||||||
|
xrpGetFee: { params: {}; response: { fee: string } | null }
|
||||||
|
fetchXrpPrice: { params: {}; response: { usd: string } | null }
|
||||||
|
}
|
||||||
|
|
||||||
export type RPC = {
|
export type RPC = {
|
||||||
bun: {
|
bun: {
|
||||||
requests: GeneralRpcRequests & EvmRpcRequests & MoneroRpcRequests
|
requests: GeneralRpcRequests & MoneroRpcRequests & XrpRpcRequests
|
||||||
messages: {}
|
messages: {}
|
||||||
}
|
}
|
||||||
webview: {
|
webview: {
|
||||||
requests: {}
|
requests: {}
|
||||||
messages: {
|
messages: {}
|
||||||
transfersUpdate: { count: number; chainid: string; address: string }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+103
-242
@@ -1,73 +1,37 @@
|
|||||||
import { mnemonicToAccount } from 'viem/accounts'
|
|
||||||
import { createPublicClient, http, formatEther } from 'viem'
|
|
||||||
import {
|
import {
|
||||||
electrobun,
|
electrobun,
|
||||||
type TxEntry,
|
type XrpTxEntry,
|
||||||
type EvmWalletInfo,
|
type XrpWalletInfo,
|
||||||
} from '$lib/electrobun'
|
} from '$lib/electrobun'
|
||||||
import { tryCatch } from '@koins/utils'
|
import { tryCatch } from '@koins/utils'
|
||||||
import { moneroWallet } from './monero-wallet.svelte.js'
|
|
||||||
|
|
||||||
export const networks = [
|
export const xrpNetwork = {
|
||||||
{
|
id: 'xrp',
|
||||||
id: 'bsc',
|
name: 'XRP Ledger',
|
||||||
name: 'BNB Smart Chain',
|
symbol: 'XRP',
|
||||||
rpc: 'https://bsc-dataseed.binance.org',
|
explorerUrl: 'https://xrpscan.com/tx/',
|
||||||
symbol: 'BNB',
|
explorerAddressUrl: 'https://xrpscan.com/account/',
|
||||||
chainid: '56',
|
} as const
|
||||||
explorerUrl: 'https://bscscan.com/tx/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'polygon',
|
|
||||||
name: 'Polygon',
|
|
||||||
rpc: 'https://polygon-bor.publicnode.com',
|
|
||||||
symbol: 'POL',
|
|
||||||
chainid: '137',
|
|
||||||
explorerUrl: 'https://polygonscan.com/tx/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'eth',
|
|
||||||
name: 'Ethereum',
|
|
||||||
rpc: 'https://ethereum-rpc.publicnode.com',
|
|
||||||
symbol: 'ETH',
|
|
||||||
chainid: '1',
|
|
||||||
explorerUrl: 'https://etherscan.io/tx/',
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
|
|
||||||
export type NetworkId = (typeof networks)[number]['id']
|
export const XrpWallet = () => {
|
||||||
|
let accountType = $state<'xrp' | null>(null)
|
||||||
export type TokenBalance = {
|
|
||||||
symbol: string
|
|
||||||
decimals: number
|
|
||||||
balance: string
|
|
||||||
contractAddress: string
|
|
||||||
logo?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AccountType = 'multi' | 'monero'
|
|
||||||
|
|
||||||
export const EvmWallet = () => {
|
|
||||||
let accountType = $state<AccountType | null>(null)
|
|
||||||
let ready = $state(false)
|
let ready = $state(false)
|
||||||
let biometricAvailable = $state(false)
|
let biometricAvailable = $state(false)
|
||||||
let seed = $state('')
|
let seed = $state('')
|
||||||
let address = $state('')
|
let address = $state('')
|
||||||
let balance = $state('0')
|
let balance = $state('0')
|
||||||
let tokenBalances = $state<TokenBalance[]>([])
|
let funded = $state(true)
|
||||||
let network = $state<NetworkId>('eth')
|
let price = $state<string | null>(null)
|
||||||
let apiKey = $state('')
|
let fee = $state<string | null>(null)
|
||||||
let transactions = $state<TxEntry[]>([])
|
let transactions = $state<XrpTxEntry[]>([])
|
||||||
let loadingBalance = $state(false)
|
let loadingBalance = $state(false)
|
||||||
let loadingTransactions = $state(false)
|
let loadingTransactions = $state(false)
|
||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
let error = $state('')
|
let error = $state('')
|
||||||
let wallets = $state<EvmWalletInfo[]>([])
|
let wallets = $state<XrpWalletInfo[]>([])
|
||||||
let currentWalletId = $state<string | null>(null)
|
let currentWalletId = $state<string | null>(null)
|
||||||
let currentPasswordHash = $state<string | null>(null)
|
let currentPasswordHash = $state<string | null>(null)
|
||||||
|
|
||||||
const net = () => networks.find((n) => n.id === network)!
|
|
||||||
|
|
||||||
async function hashPassword(
|
async function hashPassword(
|
||||||
password: string,
|
password: string,
|
||||||
salt?: string,
|
salt?: string,
|
||||||
@@ -111,25 +75,8 @@ export const EvmWallet = () => {
|
|||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
if (!electrobun.rpc) return
|
if (!electrobun.rpc) return
|
||||||
|
|
||||||
electrobun.rpc.addMessageListener(
|
|
||||||
'transfersUpdate',
|
|
||||||
(payload: {
|
|
||||||
chainid: string
|
|
||||||
address: string
|
|
||||||
count: number
|
|
||||||
}) => {
|
|
||||||
if (
|
|
||||||
payload.address === address &&
|
|
||||||
payload.chainid === net().chainid
|
|
||||||
) {
|
|
||||||
fetchTxHistory()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const [walletList] = await tryCatch(
|
const [walletList] = await tryCatch(
|
||||||
electrobun.rpc.request.evmListWallets({}),
|
electrobun.rpc.request.xrpListWallets({}),
|
||||||
)
|
)
|
||||||
if (walletList) {
|
if (walletList) {
|
||||||
wallets = walletList
|
wallets = walletList
|
||||||
@@ -137,28 +84,13 @@ export const EvmWallet = () => {
|
|||||||
currentWalletId = walletList[0].id
|
currentWalletId = walletList[0].id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [key] = await tryCatch(
|
|
||||||
electrobun.rpc.request.getSecret({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if (key) apiKey = key
|
|
||||||
|
|
||||||
// await moneroWallet.checkStatus()
|
|
||||||
// if (moneroWallet.installed && !moneroWallet.running && !moneroWallet.downloading) {
|
|
||||||
// await moneroWallet.start()
|
|
||||||
// await moneroWallet.checkStatus()
|
|
||||||
// }
|
|
||||||
|
|
||||||
await checkBiometric()
|
await checkBiometric()
|
||||||
ready = true
|
ready = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
accountType = 'multi'
|
accountType = 'xrp'
|
||||||
if (seed) await switchNetwork(network)
|
if (seed) await refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
@@ -170,27 +102,25 @@ export const EvmWallet = () => {
|
|||||||
seed = ''
|
seed = ''
|
||||||
address = ''
|
address = ''
|
||||||
balance = '0'
|
balance = '0'
|
||||||
tokenBalances = []
|
funded = true
|
||||||
transactions = []
|
transactions = []
|
||||||
error = ''
|
error = ''
|
||||||
currentPasswordHash = null
|
currentPasswordHash = null
|
||||||
electrobun.rpc?.request.setAutoSync(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unlockWallet = async (walletId: string) => {
|
const loadSeedForWallet = async (walletId: string) => {
|
||||||
if (!electrobun.rpc) return false
|
if (!electrobun.rpc) return false
|
||||||
const wallet = wallets.find((w) => w.id === walletId)
|
const wallet = wallets.find((w) => w.id === walletId)
|
||||||
if (!wallet) return false
|
if (!wallet) return false
|
||||||
currentWalletId = walletId
|
|
||||||
const [vaultSeed] = await tryCatch(
|
const [vaultSeed] = await tryCatch(
|
||||||
electrobun.rpc.request.evmGetSeed({
|
electrobun.rpc.request.xrpGetSeed({
|
||||||
vaultKey: wallet.vaultKey,
|
vaultKey: wallet.vaultKey,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
if (!vaultSeed) return false
|
if (!vaultSeed) return false
|
||||||
seed = vaultSeed
|
seed = vaultSeed
|
||||||
|
address = wallet.address
|
||||||
await refresh()
|
await refresh()
|
||||||
await setAutoSync()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,8 +133,7 @@ export const EvmWallet = () => {
|
|||||||
)
|
)
|
||||||
if (!authed) return false
|
if (!authed) return false
|
||||||
if (!currentWalletId) return false
|
if (!currentWalletId) return false
|
||||||
await loadSeedForWallet(currentWalletId)
|
return loadSeedForWallet(currentWalletId)
|
||||||
return !!seed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unlockWithPassword = async (password: string) => {
|
const unlockWithPassword = async (password: string) => {
|
||||||
@@ -219,22 +148,6 @@ export const EvmWallet = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadSeedForWallet = async (walletId: string) => {
|
|
||||||
if (!electrobun.rpc) return false
|
|
||||||
const wallet = wallets.find((w) => w.id === walletId)
|
|
||||||
if (!wallet) return false
|
|
||||||
const [vaultSeed] = await tryCatch(
|
|
||||||
electrobun.rpc.request.evmGetSeed({
|
|
||||||
vaultKey: wallet.vaultKey,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if (!vaultSeed) return false
|
|
||||||
seed = vaultSeed
|
|
||||||
await refresh()
|
|
||||||
await setAutoSync()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectWallet = async (walletId: string) => {
|
const selectWallet = async (walletId: string) => {
|
||||||
currentWalletId = walletId
|
currentWalletId = walletId
|
||||||
clearWallet()
|
clearWallet()
|
||||||
@@ -244,7 +157,7 @@ export const EvmWallet = () => {
|
|||||||
const [ph] = await tryCatch(
|
const [ph] = await tryCatch(
|
||||||
electrobun.rpc.request.getSecret({
|
electrobun.rpc.request.getSecret({
|
||||||
service: 'koins',
|
service: 'koins',
|
||||||
name: `evm_auth_${walletId}`,
|
name: `xrp_auth_${walletId}`,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
if (ph) {
|
if (ph) {
|
||||||
@@ -260,61 +173,89 @@ export const EvmWallet = () => {
|
|||||||
if (!currentPasswordHash && biometricAvailable) {
|
if (!currentPasswordHash && biometricAvailable) {
|
||||||
return unlockWithBiometrics()
|
return unlockWithBiometrics()
|
||||||
}
|
}
|
||||||
|
if (!currentPasswordHash) {
|
||||||
|
return loadSeedForWallet(walletId)
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const createWallet = async (
|
const saveWallet = async (
|
||||||
|
kind: 'create' | 'import',
|
||||||
name: string,
|
name: string,
|
||||||
phrase: string,
|
secret: string | undefined,
|
||||||
password?: string,
|
password?: string,
|
||||||
) => {
|
) => {
|
||||||
loading = true
|
loading = true
|
||||||
error = ''
|
error = ''
|
||||||
try {
|
try {
|
||||||
mnemonicToAccount(phrase.trim())
|
if (!electrobun.rpc) throw new Error('RPC not available')
|
||||||
let passwordHash: string | undefined
|
let passwordHash: string | undefined
|
||||||
if (password) {
|
if (password) {
|
||||||
const ph = await hashPassword(password)
|
const ph = await hashPassword(password)
|
||||||
passwordHash = JSON.stringify(ph)
|
passwordHash = JSON.stringify(ph)
|
||||||
}
|
}
|
||||||
const [result, resultError] = await tryCatch(
|
const result: {
|
||||||
electrobun.rpc!.request.evmCreateWallet({
|
id: string
|
||||||
name,
|
address: string
|
||||||
phrase: phrase.trim(),
|
createdAt: string
|
||||||
passwordHash,
|
seed?: string
|
||||||
}),
|
} =
|
||||||
)
|
kind === 'create'
|
||||||
if (resultError) throw resultError
|
? await electrobun.rpc.request.xrpCreateWallet({
|
||||||
|
name,
|
||||||
|
hasPassword: !!passwordHash,
|
||||||
|
})
|
||||||
|
: await electrobun.rpc.request.xrpImportWallet({
|
||||||
|
name,
|
||||||
|
secret: secret!,
|
||||||
|
hasPassword: !!passwordHash,
|
||||||
|
})
|
||||||
|
|
||||||
if (passwordHash) {
|
if (passwordHash) {
|
||||||
await electrobun.rpc?.request.setSecret({
|
await electrobun.rpc.request.setSecret({
|
||||||
service: 'koins',
|
service: 'koins',
|
||||||
name: `evm_auth_${result.id}`,
|
name: `xrp_auth_${result.id}`,
|
||||||
value: passwordHash,
|
value: passwordHash,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const newWallet: EvmWalletInfo = {
|
const newWallet: XrpWalletInfo = {
|
||||||
id: result.id,
|
id: result.id,
|
||||||
name,
|
name,
|
||||||
|
address: result.address,
|
||||||
hasPassword: !!passwordHash,
|
hasPassword: !!passwordHash,
|
||||||
vaultKey: `evm_seed_${result.id}`,
|
vaultKey: `xrp_seed_${result.id}`,
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
}
|
}
|
||||||
wallets = [...wallets, newWallet]
|
wallets = [...wallets, newWallet]
|
||||||
currentWalletId = result.id
|
currentWalletId = result.id
|
||||||
if (passwordHash) currentPasswordHash = passwordHash
|
if (passwordHash) currentPasswordHash = passwordHash
|
||||||
seed = phrase.trim()
|
seed = result.seed ?? secret!
|
||||||
|
address = result.address
|
||||||
await refresh()
|
await refresh()
|
||||||
|
return result.seed
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = e instanceof Error ? e.message : 'Invalid seed phrase'
|
error = e instanceof Error ? e.message : 'Failed to save wallet'
|
||||||
|
throw e
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createWallet = async (name: string, password?: string) => {
|
||||||
|
return saveWallet('create', name, undefined, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
const importWallet = async (
|
||||||
|
name: string,
|
||||||
|
secret: string,
|
||||||
|
password?: string,
|
||||||
|
) => {
|
||||||
|
return saveWallet('import', name, secret, password)
|
||||||
|
}
|
||||||
|
|
||||||
const deleteWallet = async (walletId: string) => {
|
const deleteWallet = async (walletId: string) => {
|
||||||
await electrobun.rpc?.request.evmDeleteWallet({ id: walletId })
|
await electrobun.rpc?.request.xrpDeleteWallet({ id: walletId })
|
||||||
wallets = wallets.filter((w) => w.id !== walletId)
|
wallets = wallets.filter((w) => w.id !== walletId)
|
||||||
if (currentWalletId === walletId) {
|
if (currentWalletId === walletId) {
|
||||||
clearWallet()
|
clearWallet()
|
||||||
@@ -327,103 +268,49 @@ export const EvmWallet = () => {
|
|||||||
currentWalletId = null
|
currentWalletId = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const switchNetwork = async (id: NetworkId) => {
|
|
||||||
network = id
|
|
||||||
if (seed) await refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
if (!seed || !electrobun.rpc) return
|
if (!address || !electrobun.rpc) return
|
||||||
|
const rpc = electrobun.rpc
|
||||||
loadingBalance = true
|
loadingBalance = true
|
||||||
loadingTransactions = true
|
loadingTransactions = true
|
||||||
balance = '0'
|
|
||||||
error = ''
|
error = ''
|
||||||
const account = mnemonicToAccount(seed)
|
|
||||||
address = account.address
|
|
||||||
const client = createPublicClient({ transport: http(net().rpc) })
|
|
||||||
|
|
||||||
syncTxHistory()
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const [nativeBal] = await tryCatch(
|
const [bal] = await tryCatch(
|
||||||
client.getBalance({ address: account.address }),
|
rpc.request.xrpGetBalance({ address }),
|
||||||
)
|
)
|
||||||
if (nativeBal !== undefined) balance = formatEther(nativeBal)
|
if (bal) {
|
||||||
const [bals] = await tryCatch(
|
balance = bal.balance
|
||||||
electrobun.rpc.request.fetchTokenBalances({
|
funded = bal.funded
|
||||||
address,
|
}
|
||||||
chainid: net().chainid,
|
const [p] = await tryCatch(rpc.request.fetchXrpPrice({}))
|
||||||
}),
|
if (p) price = p.usd
|
||||||
)
|
const [f] = await tryCatch(rpc.request.xrpGetFee({}))
|
||||||
if (bals) tokenBalances = bals
|
if (f) fee = f.fee
|
||||||
loadingBalance = false
|
loadingBalance = false
|
||||||
})()
|
})()
|
||||||
;(async () => {
|
;(async () => {
|
||||||
await fetchTxHistory()
|
const [txs] = await tryCatch(
|
||||||
|
rpc.request.xrpGetTransactions({ address }),
|
||||||
|
)
|
||||||
|
if (txs) transactions = txs
|
||||||
loadingTransactions = false
|
loadingTransactions = false
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncTxHistory = () => {
|
|
||||||
if (!address || !apiKey || !electrobun.rpc) return
|
|
||||||
electrobun.rpc.request.syncTxHistory({
|
|
||||||
address,
|
|
||||||
chainid: net().chainid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const setAutoSync = async () => {
|
|
||||||
if (!address || !electrobun.rpc) return
|
|
||||||
electrobun.rpc.request.setAutoSync({
|
|
||||||
address,
|
|
||||||
chainid: net().chainid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const flushTxCache = async () => {
|
|
||||||
await electrobun.rpc?.request.flushTxCache({})
|
|
||||||
await fetchTxHistory()
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveApiKey = async (key: string) => {
|
|
||||||
await electrobun.rpc?.request.setSecret({
|
|
||||||
service: 'koins',
|
|
||||||
name: 'alchemy_key',
|
|
||||||
value: key,
|
|
||||||
})
|
|
||||||
apiKey = key
|
|
||||||
syncTxHistory()
|
|
||||||
if (address) await fetchTxHistory()
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchTxHistory = async () => {
|
|
||||||
if (!address) {
|
|
||||||
transactions = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const txs = await electrobun.rpc?.request.fetchCachedTxHistory({
|
|
||||||
address,
|
|
||||||
chainid: net().chainid,
|
|
||||||
})
|
|
||||||
transactions = txs ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
const send = async (
|
const send = async (
|
||||||
to: string,
|
to: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
contractAddress?: string,
|
destinationTag?: number,
|
||||||
tokenDecimals?: number,
|
|
||||||
) => {
|
) => {
|
||||||
if (!electrobun.rpc || !seed) throw new Error('Wallet not unlocked')
|
if (!electrobun.rpc || !seed) throw new Error('Wallet not unlocked')
|
||||||
const hash = await electrobun.rpc.request.evmSendTransfer({
|
const result = await electrobun.rpc.request.xrpSend({
|
||||||
seed,
|
secret: seed,
|
||||||
to,
|
to,
|
||||||
amount,
|
amount,
|
||||||
chainid: net().chainid,
|
destinationTag,
|
||||||
contractAddress,
|
|
||||||
tokenDecimals,
|
|
||||||
})
|
})
|
||||||
await refresh()
|
await refresh()
|
||||||
return hash
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const lock = () => {
|
const lock = () => {
|
||||||
@@ -432,7 +319,6 @@ export const EvmWallet = () => {
|
|||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
clearWallet()
|
clearWallet()
|
||||||
apiKey = ''
|
|
||||||
wallets = []
|
wallets = []
|
||||||
currentWalletId = null
|
currentWalletId = null
|
||||||
}
|
}
|
||||||
@@ -464,34 +350,14 @@ export const EvmWallet = () => {
|
|||||||
get balance() {
|
get balance() {
|
||||||
return balance
|
return balance
|
||||||
},
|
},
|
||||||
get tokenBalances() {
|
get funded() {
|
||||||
return tokenBalances
|
return funded
|
||||||
},
|
},
|
||||||
get network() {
|
get price() {
|
||||||
return network
|
return price
|
||||||
},
|
},
|
||||||
get chainid() {
|
get fee() {
|
||||||
return net().chainid
|
return fee
|
||||||
},
|
|
||||||
get networkName() {
|
|
||||||
return net().name
|
|
||||||
},
|
|
||||||
get symbol() {
|
|
||||||
return net().symbol
|
|
||||||
},
|
|
||||||
get explorerUrl() {
|
|
||||||
return net().explorerUrl
|
|
||||||
},
|
|
||||||
get explorerAddressUrl() {
|
|
||||||
return address
|
|
||||||
? `${net().explorerUrl.replace('/tx/', '/address/')}${address}`
|
|
||||||
: ''
|
|
||||||
},
|
|
||||||
get networks() {
|
|
||||||
return networks
|
|
||||||
},
|
|
||||||
get apiKey() {
|
|
||||||
return apiKey
|
|
||||||
},
|
},
|
||||||
get transactions() {
|
get transactions() {
|
||||||
return transactions
|
return transactions
|
||||||
@@ -526,8 +392,8 @@ export const EvmWallet = () => {
|
|||||||
get currentPasswordHash() {
|
get currentPasswordHash() {
|
||||||
return currentPasswordHash
|
return currentPasswordHash
|
||||||
},
|
},
|
||||||
set apiKey(v: string) {
|
get explorerAddressUrl() {
|
||||||
apiKey = v
|
return address ? `${xrpNetwork.explorerAddressUrl}${address}` : ''
|
||||||
},
|
},
|
||||||
set error(v: string) {
|
set error(v: string) {
|
||||||
error = v
|
error = v
|
||||||
@@ -537,9 +403,7 @@ export const EvmWallet = () => {
|
|||||||
logout,
|
logout,
|
||||||
refresh,
|
refresh,
|
||||||
createWallet,
|
createWallet,
|
||||||
switchNetwork,
|
importWallet,
|
||||||
saveApiKey,
|
|
||||||
fetchTxHistory,
|
|
||||||
lock,
|
lock,
|
||||||
reset,
|
reset,
|
||||||
resetApp,
|
resetApp,
|
||||||
@@ -548,14 +412,11 @@ export const EvmWallet = () => {
|
|||||||
hashPassword,
|
hashPassword,
|
||||||
selectWallet,
|
selectWallet,
|
||||||
selectAndUnlockWallet,
|
selectAndUnlockWallet,
|
||||||
unlockWallet,
|
loadSeedForWallet,
|
||||||
deleteWallet,
|
deleteWallet,
|
||||||
clearSelection,
|
clearSelection,
|
||||||
send,
|
send,
|
||||||
syncTxHistory,
|
|
||||||
setAutoSync,
|
|
||||||
flushTxCache,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const evmWallet = EvmWallet()
|
export const xrpWallet = XrpWallet()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { evmWallet as wallet } from '$lib/states/evm-wallet.svelte.js'
|
|
||||||
import { moneroWallet } from '$lib/states/monero-wallet.svelte.js'
|
import { moneroWallet } from '$lib/states/monero-wallet.svelte.js'
|
||||||
|
import { xrpWallet } from '$lib/states/xrp-wallet.svelte.js'
|
||||||
import { Button } from '$lib/components/ui/button/index.js'
|
import { Button } from '$lib/components/ui/button/index.js'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -13,14 +13,20 @@
|
|||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import { frontendLog, frontendLogError } from '$lib/electrobun.js'
|
import { frontendLog, frontendLogError } from '$lib/electrobun.js'
|
||||||
|
|
||||||
const w = wallet
|
|
||||||
|
|
||||||
let feError = $state<string | null>(null)
|
let feError = $state<string | null>(null)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
frontendLog('index.svelte mounted')
|
frontendLog('index.svelte mounted')
|
||||||
w.init().catch((e) => frontendLogError('w.init() failed', e))
|
|
||||||
moneroWallet.init().catch((e) => frontendLogError('moneroWallet.init() failed', e))
|
moneroWallet.init().catch((e) => frontendLogError('moneroWallet.init() failed', e))
|
||||||
|
xrpWallet.init().catch((e) => frontendLogError('xrpWallet.init() failed', e))
|
||||||
|
})
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (moneroWallet.accountType === 'monero') {
|
||||||
|
navigate('/monero')
|
||||||
|
} else if (xrpWallet.accountType === 'xrp') {
|
||||||
|
navigate('/xrp')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -56,61 +62,42 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="mx-auto mt-24 max-w-sm">
|
<div class="mx-auto mt-24 max-w-sm">
|
||||||
{#if !w.ready}
|
<Card>
|
||||||
<p class="text-center text-muted-foreground text-sm">
|
<CardHeader class="text-center">
|
||||||
Loading...
|
<CardTitle>Welcome</CardTitle>
|
||||||
</p>
|
<CardDescription>
|
||||||
{:else if w.accountType}
|
Choose an account type to get started
|
||||||
{#if w.accountType === 'multi'}
|
</CardDescription>
|
||||||
{navigate('/multicoin')}
|
</CardHeader>
|
||||||
{:else}
|
<CardContent>
|
||||||
{navigate('/monero')}
|
<div class="flex flex-col gap-3">
|
||||||
{/if}
|
<Button
|
||||||
{:else}
|
onclick={async () => {
|
||||||
<Card>
|
await moneroWallet.login()
|
||||||
<CardHeader class="text-center">
|
navigate('/monero')
|
||||||
<CardTitle>Welcome</CardTitle>
|
}}
|
||||||
<CardDescription>
|
variant="outline"
|
||||||
Choose an account type to get started
|
class="w-full">
|
||||||
</CardDescription>
|
<img
|
||||||
</CardHeader>
|
src="/icons/monero.png"
|
||||||
<CardContent>
|
alt=""
|
||||||
<div class="flex flex-col gap-3">
|
class="-ml-1 mr-1.5 inline-block size-5 rounded-full align-middle" />
|
||||||
<Button
|
Monero
|
||||||
onclick={async () => {
|
</Button>
|
||||||
await w.login()
|
<Button
|
||||||
navigate('/multicoin')
|
onclick={async () => {
|
||||||
}}
|
await xrpWallet.login()
|
||||||
class="w-full">
|
navigate('/xrp')
|
||||||
<span class="-ml-1 mr-2 flex items-center">
|
}}
|
||||||
<img
|
variant="outline"
|
||||||
src="/icons/ethereum.png"
|
class="w-full">
|
||||||
alt=""
|
<img
|
||||||
class="relative z-30 size-5 rounded-full ring-2 ring-accent" />
|
src="/icons/xrp.png"
|
||||||
<img
|
alt=""
|
||||||
src="/icons/binance.png"
|
class="-ml-1 mr-1.5 inline-block size-5 rounded-full align-middle" />
|
||||||
alt=""
|
XRP
|
||||||
class="-ml-1.5 relative z-20 size-5 rounded-full ring-2 ring-accent" />
|
</Button>
|
||||||
<img
|
</div>
|
||||||
src="/icons/polygon.png"
|
</CardContent>
|
||||||
alt=""
|
</Card>
|
||||||
class="-ml-1.5 relative z-10 size-5 rounded-full ring-2 ring-accent" />
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onclick={async () => {
|
|
||||||
await moneroWallet.login()
|
|
||||||
navigate('/monero')
|
|
||||||
}}
|
|
||||||
variant="outline"
|
|
||||||
class="w-full">
|
|
||||||
<img
|
|
||||||
src="/icons/monero.png"
|
|
||||||
alt=""
|
|
||||||
class="-ml-1 mr-1.5 inline-block size-5 rounded-full align-middle" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import EvmDashboard from '$lib/components/EvmDashboard.svelte'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mx-auto mt-16 max-w-md">
|
|
||||||
<EvmDashboard networkId="bsc" />
|
|
||||||
</div>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import EvmDashboard from '$lib/components/EvmDashboard.svelte'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mx-auto mt-16 max-w-md">
|
|
||||||
<EvmDashboard networkId="eth" />
|
|
||||||
</div>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import EvmDashboard from '$lib/components/EvmDashboard.svelte'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mx-auto mt-16 max-w-md">
|
|
||||||
<EvmDashboard networkId="polygon" />
|
|
||||||
</div>
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { evmWallet as wallet } from '$lib/states/evm-wallet.svelte.js'
|
|
||||||
import { electrobun } from '$lib/electrobun.js'
|
|
||||||
import { Button } from '$lib/components/ui/button/index.js'
|
|
||||||
import { Input } from '$lib/components/ui/input/index.js'
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
} from '$lib/components/ui/select/index.js'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
CardDescription,
|
|
||||||
} from '$lib/components/ui/card/index.js'
|
|
||||||
import { navigate } from 'sv-router/generated'
|
|
||||||
import { parseEther, formatEther, parseGwei } from 'viem'
|
|
||||||
|
|
||||||
const w = wallet
|
|
||||||
|
|
||||||
type TokenOption = {
|
|
||||||
symbol: string
|
|
||||||
balance: string
|
|
||||||
contractAddress: string
|
|
||||||
decimals: number
|
|
||||||
logo?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokens = $derived<TokenOption[]>([
|
|
||||||
{
|
|
||||||
symbol: w.symbol,
|
|
||||||
balance: w.balance,
|
|
||||||
contractAddress: '',
|
|
||||||
decimals: 18,
|
|
||||||
},
|
|
||||||
...w.tokenBalances.map((t) => ({
|
|
||||||
symbol: t.symbol,
|
|
||||||
balance: t.balance,
|
|
||||||
contractAddress: t.contractAddress,
|
|
||||||
decimals: t.decimals,
|
|
||||||
logo: t.logo,
|
|
||||||
})),
|
|
||||||
])
|
|
||||||
|
|
||||||
let selectedSymbol = $state(w.symbol)
|
|
||||||
let recipient = $state('')
|
|
||||||
let amount = $state('')
|
|
||||||
let sending = $state(false)
|
|
||||||
let sentResult = $state<{ txHash: string } | null>(null)
|
|
||||||
let sendError = $state('')
|
|
||||||
let sendAll = $state(false)
|
|
||||||
|
|
||||||
const selectedToken = $derived(
|
|
||||||
tokens.find((t) => t.symbol === selectedSymbol) ?? tokens[0],
|
|
||||||
)
|
|
||||||
|
|
||||||
const exceedsBalance = $derived(
|
|
||||||
amount !== '' &&
|
|
||||||
selectedToken.balance !== '0' &&
|
|
||||||
parseFloat(amount) > parseFloat(selectedToken.balance),
|
|
||||||
)
|
|
||||||
|
|
||||||
const canSend = $derived(
|
|
||||||
recipient.length === 42 &&
|
|
||||||
recipient.startsWith('0x') &&
|
|
||||||
!sending &&
|
|
||||||
(sendAll || (amount !== '' && parseFloat(amount) > 0)),
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleSend = async () => {
|
|
||||||
sending = true
|
|
||||||
sendError = ''
|
|
||||||
sentResult = null
|
|
||||||
try {
|
|
||||||
const isNative = !selectedToken.contractAddress
|
|
||||||
let finalAmount = String(amount)
|
|
||||||
if (sendAll && isNative) {
|
|
||||||
const gasPriceGwei = await electrobun.rpc?.request.fetchGasPrice({ chainid: w.chainid })
|
|
||||||
if (!gasPriceGwei) throw new Error('Could not fetch gas price')
|
|
||||||
const gasWei = parseGwei(gasPriceGwei) * 21000n
|
|
||||||
const balanceWei = parseEther(selectedToken.balance)
|
|
||||||
const sendWei = balanceWei - gasWei
|
|
||||||
if (sendWei <= 0n) throw new Error('Balance too low to cover gas')
|
|
||||||
finalAmount = formatEther(sendWei)
|
|
||||||
} else if (sendAll) {
|
|
||||||
finalAmount = String(selectedToken.balance)
|
|
||||||
}
|
|
||||||
const txHash = await w.send(
|
|
||||||
recipient,
|
|
||||||
finalAmount,
|
|
||||||
selectedToken.contractAddress || undefined,
|
|
||||||
selectedToken.decimals,
|
|
||||||
)
|
|
||||||
sentResult = { txHash }
|
|
||||||
recipient = ''
|
|
||||||
amount = ''
|
|
||||||
sendAll = false
|
|
||||||
} catch (e) {
|
|
||||||
sendError = e instanceof Error ? e.message : 'Send failed'
|
|
||||||
} finally {
|
|
||||||
sending = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mx-auto mt-16 max-w-md">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onclick={() => navigate(w.network === 'eth' ? '/multicoin' : `/multicoin/${w.network}`)}
|
|
||||||
class="mb-4">
|
|
||||||
← Back
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Send</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Balance: {selectedToken.balance} {selectedToken.symbol}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<form
|
|
||||||
onsubmit={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
handleSend()
|
|
||||||
}}
|
|
||||||
class="space-y-4">
|
|
||||||
<div class="space-y-1.5">
|
|
||||||
<label class="text-xs font-medium">Asset</label>
|
|
||||||
<Select type="single" bind:value={selectedSymbol}>
|
|
||||||
<SelectTrigger>
|
|
||||||
{selectedToken.symbol}
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{#each tokens as t}
|
|
||||||
<SelectItem value={t.symbol}>
|
|
||||||
{t.symbol} ({t.balance})
|
|
||||||
</SelectItem>
|
|
||||||
{/each}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
|
||||||
<label class="text-xs font-medium">Recipient Address</label>
|
|
||||||
<Input
|
|
||||||
placeholder="0x..."
|
|
||||||
bind:value={recipient} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-1.5">
|
|
||||||
<label class="text-xs font-medium">Amount ({selectedToken.symbol})</label>
|
|
||||||
<div class="flex gap-2 items-start">
|
|
||||||
<div class="flex-1">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
step="0.000000000000000001"
|
|
||||||
min="0"
|
|
||||||
placeholder="0.0"
|
|
||||||
bind:value={amount} />
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onclick={() => { amount = selectedToken.balance }}
|
|
||||||
class="mt-0 shrink-0">
|
|
||||||
All
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{#if exceedsBalance}
|
|
||||||
<p class="text-xs text-red-500">
|
|
||||||
Amount exceeds balance
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !selectedToken.contractAddress}
|
|
||||||
<label class="flex items-center gap-2 cursor-pointer">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
bind:checked={sendAll}
|
|
||||||
class="h-3.5 w-3.5" />
|
|
||||||
<span class="text-xs text-muted-foreground">
|
|
||||||
Send all (deducts estimated gas fee)
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Button type="submit" disabled={!canSend} class="w-full">
|
|
||||||
{sending ? 'Sending...' : 'Send'}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{#if sendError}
|
|
||||||
<p class="mt-3 text-xs text-red-500">{sendError}</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if sentResult}
|
|
||||||
<div
|
|
||||||
class="mt-4 space-y-2 rounded-md border border-green-500/30 bg-green-500/5 p-3">
|
|
||||||
<p class="text-xs font-medium text-green-600">
|
|
||||||
Transaction Sent
|
|
||||||
</p>
|
|
||||||
<p class="font-mono text-xs break-all">
|
|
||||||
<a
|
|
||||||
href="{w.explorerUrl}{sentResult.txHash}"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="underline hover:text-green-500">
|
|
||||||
{sentResult.txHash}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { evmWallet as wallet } from '$lib/states/evm-wallet.svelte.js'
|
import { xrpWallet } from '$lib/states/xrp-wallet.svelte.js'
|
||||||
import { electrobun } from '$lib/electrobun.js'
|
|
||||||
import { Button } from '$lib/components/ui/button/index.js'
|
import { Button } from '$lib/components/ui/button/index.js'
|
||||||
import { Input } from '$lib/components/ui/input/index.js'
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -12,19 +10,13 @@
|
|||||||
} from '$lib/components/ui/card/index.js'
|
} from '$lib/components/ui/card/index.js'
|
||||||
import { navigate } from 'sv-router/generated'
|
import { navigate } from 'sv-router/generated'
|
||||||
import ArrowLeft from '@lucide/svelte/icons/arrow-left'
|
import ArrowLeft from '@lucide/svelte/icons/arrow-left'
|
||||||
import Eye from '@lucide/svelte/icons/eye'
|
|
||||||
import Fingerprint from '@lucide/svelte/icons/fingerprint'
|
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2'
|
import Trash2 from '@lucide/svelte/icons/trash-2'
|
||||||
import WalletIcon from '@lucide/svelte/icons/wallet'
|
import WalletIcon from '@lucide/svelte/icons/wallet'
|
||||||
|
|
||||||
const w = wallet
|
const w = xrpWallet
|
||||||
let confirm = $state(false)
|
let confirm = $state(false)
|
||||||
let resetting = $state(false)
|
let resetting = $state(false)
|
||||||
let deletingWalletId = $state<string | null>(null)
|
let deletingWalletId = $state<string | null>(null)
|
||||||
let flushing = $state(false)
|
|
||||||
let seedRevealed = $state(false)
|
|
||||||
let seedPassword = $state('')
|
|
||||||
let seedError = $state('')
|
|
||||||
|
|
||||||
async function handleReset() {
|
async function handleReset() {
|
||||||
resetting = true
|
resetting = true
|
||||||
@@ -37,20 +29,22 @@
|
|||||||
async function handleDeleteWallet(id: string) {
|
async function handleDeleteWallet(id: string) {
|
||||||
await w.deleteWallet(id)
|
await w.deleteWallet(id)
|
||||||
deletingWalletId = null
|
deletingWalletId = null
|
||||||
if (w.wallets.length === 0) navigate('/multicoin')
|
if (w.wallets.length === 0) navigate('/xrp')
|
||||||
}
|
|
||||||
|
|
||||||
async function handleFlushCache() {
|
|
||||||
flushing = true
|
|
||||||
await w.flushTxCache()
|
|
||||||
flushing = false
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto mt-16 max-w-md">
|
<div class="mx-auto mt-16 max-w-md">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Button variant="outline" size="sm" onclick={() => navigate(w.network === 'eth' ? '/multicoin' : `/multicoin/${w.network}`)}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onclick={() =>
|
||||||
|
w.currentWalletId
|
||||||
|
? navigate('/xrp/wallet/:id', {
|
||||||
|
params: { id: w.currentWalletId },
|
||||||
|
})
|
||||||
|
: navigate('/xrp')}>
|
||||||
<ArrowLeft size={16} />
|
<ArrowLeft size={16} />
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
@@ -59,7 +53,7 @@
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Wallets</CardTitle>
|
<CardTitle>Wallets</CardTitle>
|
||||||
<CardDescription>Manage your EVM wallets</CardDescription>
|
<CardDescription>Manage your XRP wallets</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@@ -68,8 +62,8 @@
|
|||||||
<WalletIcon size={18} class="shrink-0 text-muted-foreground" />
|
<WalletIcon size={18} class="shrink-0 text-muted-foreground" />
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<p class="font-medium text-sm">{wal.name}</p>
|
<p class="font-medium text-sm">{wal.name}</p>
|
||||||
<p class="text-xs text-muted-foreground">
|
<p class="text-xs text-muted-foreground truncate font-mono">
|
||||||
Created {new Date(wal.createdAt).toLocaleDateString()}
|
{wal.address}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{#if deletingWalletId === wal.id}
|
{#if deletingWalletId === wal.id}
|
||||||
@@ -92,83 +86,17 @@
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
{/if}
|
{/if}
|
||||||
{#if w.seed}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Seed Phrase</CardTitle>
|
|
||||||
<CardDescription>Reveal your wallet recovery phrase</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{#if seedRevealed}
|
|
||||||
<div class="rounded-md border border-destructive/50 bg-destructive/5 p-3">
|
|
||||||
<p class="text-xs text-destructive font-medium mb-2">
|
|
||||||
Never share your seed phrase. Anyone with it can access your funds.
|
|
||||||
</p>
|
|
||||||
<p class="font-mono text-xs break-all">{w.seed}</p>
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" class="mt-3" onclick={() => { seedRevealed = false; seedPassword = ''; seedError = '' }}>
|
|
||||||
<Eye size={14} /> Hide
|
|
||||||
</Button>
|
|
||||||
{:else}
|
|
||||||
<div class="space-y-2">
|
|
||||||
{#if w.currentPasswordHash}
|
|
||||||
<div class="flex gap-2 items-end">
|
|
||||||
<Input type="password" placeholder="Enter password" bind:value={seedPassword} />
|
|
||||||
<Button onclick={async () => {
|
|
||||||
seedError = ''
|
|
||||||
const { salt, hash } = JSON.parse(w.currentPasswordHash!)
|
|
||||||
const { hash: check } = await w.hashPassword(seedPassword, salt)
|
|
||||||
if (check === hash) {
|
|
||||||
seedRevealed = true
|
|
||||||
} else {
|
|
||||||
seedError = 'Incorrect password'
|
|
||||||
}
|
|
||||||
seedPassword = ''
|
|
||||||
}}>Reveal</Button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if w.biometricAvailable}
|
|
||||||
<Button variant="outline" class="w-full" onclick={async () => {
|
|
||||||
seedError = ''
|
|
||||||
const ok = await electrobun.rpc?.request.biometricAuth({ reason: 'Reveal seed phrase' })
|
|
||||||
if (ok) seedRevealed = true
|
|
||||||
}}>
|
|
||||||
<Fingerprint size={14} /> Reveal with Touch ID
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
{#if !w.currentPasswordHash && !w.biometricAvailable}
|
|
||||||
<Button onclick={() => (seedRevealed = true)}>Reveal Seed Phrase</Button>
|
|
||||||
{/if}
|
|
||||||
{#if seedError}
|
|
||||||
<p class="text-xs text-destructive">{seedError}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
{/if}
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Settings</CardTitle>
|
<CardTitle>Settings</CardTitle>
|
||||||
<CardDescription>App preferences and data management</CardDescription>
|
<CardDescription>App preferences and data management</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="space-y-4">
|
<CardContent class="space-y-4">
|
||||||
<div>
|
|
||||||
<p class="font-medium text-xs">Transaction Cache</p>
|
|
||||||
<p class="text-muted-foreground text-xs mt-1">
|
|
||||||
Clears all cached transaction history and sync status. Data will be re-fetched from Alchemy on next refresh.
|
|
||||||
</p>
|
|
||||||
<div class="mt-3">
|
|
||||||
<Button variant="outline" onclick={handleFlushCache} disabled={flushing}>
|
|
||||||
{flushing ? 'Flushing...' : 'Flush Transaction Cache'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<p class="font-medium text-xs">Reset App</p>
|
<p class="font-medium text-xs">Reset App</p>
|
||||||
<p class="text-muted-foreground text-xs mt-1">
|
<p class="text-muted-foreground text-xs mt-1">
|
||||||
Removes your seed, API key, and all cached data from the keychain
|
Removes all XRP wallets and seeds from the keychain. You
|
||||||
and database. You can then set up the wallet fresh.
|
can then set up the wallet fresh.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
{#if confirm}
|
{#if confirm}
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
electrobun,
|
|
||||||
type TransactionDetails,
|
|
||||||
} from '$lib/electrobun.js'
|
|
||||||
import { evmWallet } from '$lib/states/evm-wallet.svelte.js'
|
|
||||||
import { networks } from '$lib/states/evm-wallet.svelte.js'
|
|
||||||
import { navigate, route } from 'sv-router/generated'
|
|
||||||
import { Button } from '$lib/components/ui/button/index.js'
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from '$lib/components/ui/card/index.js'
|
|
||||||
|
|
||||||
let details = $state<TransactionDetails | null>(null)
|
|
||||||
let loading = $state(true)
|
|
||||||
let error = $state('')
|
|
||||||
|
|
||||||
const chainid = $derived(route.params.chainid)
|
|
||||||
const hash = $derived(route.params.hash)
|
|
||||||
|
|
||||||
const network = $derived(
|
|
||||||
networks.find((n) => n.chainid === chainid),
|
|
||||||
)
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (!hash || !chainid) return
|
|
||||||
loading = true
|
|
||||||
error = ''
|
|
||||||
electrobun.rpc?.request
|
|
||||||
.fetchTransactionDetails({
|
|
||||||
hash,
|
|
||||||
chainid,
|
|
||||||
address: evmWallet.address || undefined,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
details = res
|
|
||||||
loading = false
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
error =
|
|
||||||
e instanceof Error ? e.message : 'Failed to fetch details'
|
|
||||||
loading = false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function explorerUrl() {
|
|
||||||
if (!network) return ''
|
|
||||||
return network.explorerUrl + hash
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="mx-auto mt-16 max-w-md">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onclick={() => navigate('/multicoin')}
|
|
||||||
class="mb-4">
|
|
||||||
← Back
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<CardTitle>Transaction Details</CardTitle>
|
|
||||||
{#if network}
|
|
||||||
<span
|
|
||||||
class="rounded-md bg-muted px-2 py-0.5 text-xs font-medium">
|
|
||||||
{network.name}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{#if loading}
|
|
||||||
<p class="text-muted-foreground text-xs">Loading...</p>
|
|
||||||
{:else if error}
|
|
||||||
<p class="text-red-500 text-xs">{error}</p>
|
|
||||||
{:else if details}
|
|
||||||
<div class="space-y-2 font-mono text-xs">
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">Hash</p>
|
|
||||||
<p class="break-all">{details.hash}</p>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">Status</p>
|
|
||||||
<p
|
|
||||||
class={details.status === 'success'
|
|
||||||
? 'text-green-500'
|
|
||||||
: 'text-red-500'}>
|
|
||||||
{details.status}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">Block</p>
|
|
||||||
<p>{details.blockNumber ?? 'N/A'}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">From</p>
|
|
||||||
<p class="break-all">{details.from}</p>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">To</p>
|
|
||||||
<p class="break-all">{details.to ?? 'N/A'}</p>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">
|
|
||||||
Value ({network?.symbol ?? 'ETH'})
|
|
||||||
</p>
|
|
||||||
<p>{details.value}</p>
|
|
||||||
</div>
|
|
||||||
{#if details.pairedValue}
|
|
||||||
<div
|
|
||||||
class="rounded-md border border-yellow-500/30 bg-yellow-500/5 p-2">
|
|
||||||
<p class="mb-0.5 flex items-center gap-1 text-xs font-medium text-yellow-600">
|
|
||||||
Swap
|
|
||||||
</p>
|
|
||||||
<p class="font-mono text-xs">
|
|
||||||
{Number(details.value).toFixed(4)}
|
|
||||||
{network?.symbol ?? 'ETH'}
|
|
||||||
<span class="mx-1">→</span>
|
|
||||||
{Number(details.pairedValue).toFixed(4)}
|
|
||||||
{details.pairedSymbol ?? network?.symbol ?? 'ETH'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">
|
|
||||||
Tx Fee ({network?.symbol ?? 'ETH'})
|
|
||||||
</p>
|
|
||||||
<p>{details.fee}</p>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">
|
|
||||||
Gas Price (Gwei)
|
|
||||||
</p>
|
|
||||||
<p>{details.gasPrice}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">Nonce</p>
|
|
||||||
<p>{details.nonce}</p>
|
|
||||||
</div>
|
|
||||||
<div class="rounded-md bg-muted p-2">
|
|
||||||
<p class="text-muted-foreground mb-0.5">Type</p>
|
|
||||||
<p>{details.type}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
class="w-full"
|
|
||||||
onclick={() =>
|
|
||||||
electrobun.rpc?.request.openExternal({
|
|
||||||
url: explorerUrl(),
|
|
||||||
})}>
|
|
||||||
View on Explorer
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<p class="text-muted-foreground text-xs">
|
|
||||||
No details available
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { xrpWallet } from '$lib/states/xrp-wallet.svelte.js'
|
||||||
|
import { Button } from '$lib/components/ui/button/index.js'
|
||||||
|
import { Input } from '$lib/components/ui/input/index.js'
|
||||||
|
import { Textarea } from '$lib/components/ui/textarea/index.js'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '$lib/components/ui/card/index.js'
|
||||||
|
import Fingerprint from '@lucide/svelte/icons/fingerprint'
|
||||||
|
import LockKeyhole from '@lucide/svelte/icons/lock-keyhole'
|
||||||
|
import Plus from '@lucide/svelte/icons/plus'
|
||||||
|
import WalletIcon from '@lucide/svelte/icons/wallet'
|
||||||
|
import Loader from '$lib/components/loader.svelte'
|
||||||
|
import { navigate } from 'sv-router/generated'
|
||||||
|
|
||||||
|
const w = xrpWallet
|
||||||
|
|
||||||
|
let inputWalletName = $state('')
|
||||||
|
let inputSeed = $state('')
|
||||||
|
let inputPassword = $state('')
|
||||||
|
let inputUnlockPassword = $state('')
|
||||||
|
let showCreateForm = $state(false)
|
||||||
|
let newSeed = $state<string | null>(null)
|
||||||
|
|
||||||
|
let initStarted = false
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!initStarted) {
|
||||||
|
initStarted = true
|
||||||
|
w.init().then(async () => {
|
||||||
|
if (w.accountType === 'xrp') return
|
||||||
|
await w.login()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (w.accountType === 'xrp' && w.currentWalletId && w.seed && !newSeed) {
|
||||||
|
navigate('/xrp/wallet/:id', {
|
||||||
|
params: { id: w.currentWalletId },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleCreate = async () => {
|
||||||
|
const seed = await w.createWallet(
|
||||||
|
inputWalletName,
|
||||||
|
inputPassword || undefined,
|
||||||
|
)
|
||||||
|
newSeed = seed ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImport = async () => {
|
||||||
|
await w.importWallet(
|
||||||
|
inputWalletName,
|
||||||
|
inputSeed.trim(),
|
||||||
|
inputPassword || undefined,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto mt-16 max-w-md">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
{#if !w.ready}
|
||||||
|
<p class="text-center text-muted-foreground text-sm mt-8">
|
||||||
|
Loading...
|
||||||
|
</p>
|
||||||
|
{:else if !w.accountType}
|
||||||
|
<p class="text-center text-muted-foreground text-sm mt-8">
|
||||||
|
<a href="/" class="underline">Go to welcome page</a>
|
||||||
|
to get started
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="text-xs text-muted-foreground underline mb-1 inline-block">
|
||||||
|
← Back to home
|
||||||
|
</a>
|
||||||
|
{#if newSeed}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Wallet Created</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Save this seed phrase — it is the only way to recover
|
||||||
|
your wallet
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div class="rounded-md border border-destructive/50 bg-destructive/5 p-3 mb-3">
|
||||||
|
<p class="text-xs text-destructive font-medium mb-2">
|
||||||
|
Never share your seed. Anyone with it can access your
|
||||||
|
funds.
|
||||||
|
</p>
|
||||||
|
<p class="font-mono text-xs break-all">{newSeed}</p>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-muted-foreground mb-3">
|
||||||
|
Your XRP address becomes active after it receives at
|
||||||
|
least 10 XRP (the base reserve).
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
class="w-full"
|
||||||
|
onclick={() => {
|
||||||
|
newSeed = null
|
||||||
|
inputWalletName = ''
|
||||||
|
inputPassword = ''
|
||||||
|
}}>
|
||||||
|
I saved my seed
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{:else if w.wallets.length === 0 || showCreateForm}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
{showCreateForm ? 'Add Another Wallet' : 'Create / Import Wallet'}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Generate a new XRP wallet or import an existing seed
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<Input
|
||||||
|
placeholder="Wallet name (e.g. Main, Savings)"
|
||||||
|
bind:value={inputWalletName} />
|
||||||
|
<Button
|
||||||
|
onclick={handleCreate}
|
||||||
|
disabled={w.loading || !inputWalletName.trim()}>
|
||||||
|
{w.loading ? 'Creating...' : 'Generate New Wallet'}
|
||||||
|
</Button>
|
||||||
|
<hr class="border-muted" />
|
||||||
|
<p class="text-xs text-muted-foreground">
|
||||||
|
Or import an existing wallet:
|
||||||
|
</p>
|
||||||
|
<Textarea
|
||||||
|
placeholder="XRP seed (s...) or 12/24-word phrase"
|
||||||
|
bind:value={inputSeed} />
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="Set a password (optional)"
|
||||||
|
bind:value={inputPassword} />
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onclick={handleImport}
|
||||||
|
disabled={w.loading ||
|
||||||
|
!inputSeed.trim() ||
|
||||||
|
!inputWalletName.trim()}>
|
||||||
|
{w.loading ? 'Importing...' : 'Import Wallet'}
|
||||||
|
</Button>
|
||||||
|
{#if showCreateForm}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onclick={() => (showCreateForm = false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if w.error}
|
||||||
|
<p class="mt-3 text-xs text-red-500">{w.error}</p>
|
||||||
|
{/if}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{:else if !w.currentWalletId}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Wallets</CardTitle>
|
||||||
|
<CardDescription>Select a wallet to use</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{#each w.wallets as wal}
|
||||||
|
<button
|
||||||
|
onclick={async () => {
|
||||||
|
if (wal.hasPassword) {
|
||||||
|
await w.selectWallet(wal.id)
|
||||||
|
} else {
|
||||||
|
await w.selectAndUnlockWallet(wal.id)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
class="flex w-full cursor-pointer items-center gap-3 rounded-md border border-input p-3 text-left hover:bg-muted transition-colors">
|
||||||
|
<WalletIcon
|
||||||
|
size={20}
|
||||||
|
class="shrink-0 text-muted-foreground" />
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="font-medium text-sm">{wal.name}</p>
|
||||||
|
<p class="text-xs text-muted-foreground truncate font-mono">
|
||||||
|
{wal.address}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
{#if wal.hasPassword}
|
||||||
|
<LockKeyhole
|
||||||
|
size={14}
|
||||||
|
class="text-muted-foreground" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onclick={() => (showCreateForm = true)}
|
||||||
|
class="mt-2">
|
||||||
|
<Plus size={16} />
|
||||||
|
Add Wallet
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{:else if w.isLocked}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
{w.currentWallet?.name ?? 'Wallet'} Locked
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Unlock to access your wallet
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
{#if w.biometricAvailable}
|
||||||
|
<Button
|
||||||
|
onclick={() => w.unlockWithBiometrics()}
|
||||||
|
disabled={w.loading}>
|
||||||
|
<Fingerprint size={16} />
|
||||||
|
{w.loading ? 'Unlocking...' : 'Unlock with Touch ID'}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
{#if w.currentPasswordHash}
|
||||||
|
<div class="flex gap-2 items-end">
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
placeholder="Enter password"
|
||||||
|
bind:value={inputUnlockPassword} />
|
||||||
|
<Button
|
||||||
|
onclick={async () => {
|
||||||
|
const ok = await w.unlockWithPassword(
|
||||||
|
inputUnlockPassword,
|
||||||
|
)
|
||||||
|
if (!ok) w.error = 'Wrong password'
|
||||||
|
inputUnlockPassword = ''
|
||||||
|
}}
|
||||||
|
disabled={w.loading || !inputUnlockPassword}
|
||||||
|
size="sm">
|
||||||
|
{#if w.loading}
|
||||||
|
<Loader />
|
||||||
|
{:else}
|
||||||
|
Unlock
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onclick={() => w.clearSelection()}>
|
||||||
|
Pick another wallet
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{#if w.error}
|
||||||
|
<p class="mt-3 text-xs text-red-500">{w.error}</p>
|
||||||
|
{/if}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { xrpWallet } from '$lib/states/xrp-wallet.svelte.js'
|
||||||
|
import { Button } from '$lib/components/ui/button/index.js'
|
||||||
|
import { Input } from '$lib/components/ui/input/index.js'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
} from '$lib/components/ui/card/index.js'
|
||||||
|
import { navigate } from 'sv-router/generated'
|
||||||
|
|
||||||
|
const w = xrpWallet
|
||||||
|
|
||||||
|
let recipient = $state('')
|
||||||
|
let destinationTag = $state('')
|
||||||
|
let amountXrp = $state('')
|
||||||
|
let sending = $state(false)
|
||||||
|
let sentResult = $state<{ hash: string; fee: string } | null>(null)
|
||||||
|
let sendError = $state('')
|
||||||
|
|
||||||
|
const isValidAddress = $derived(/^r[1-9A-HJ-NP-Za-km-z]{24,34}$/.test(recipient))
|
||||||
|
|
||||||
|
const amount = $derived(parseFloat(amountXrp))
|
||||||
|
const amountValid = $derived(!isNaN(amount) && amount > 0)
|
||||||
|
const exceedsBalance = $derived(amountValid && amount > parseFloat(w.balance))
|
||||||
|
const tagValid = $derived(
|
||||||
|
destinationTag === '' ||
|
||||||
|
(/^\d+$/.test(destinationTag) && Number(destinationTag) <= 4294967295),
|
||||||
|
)
|
||||||
|
const canSend = $derived(
|
||||||
|
isValidAddress &&
|
||||||
|
amountValid &&
|
||||||
|
!exceedsBalance &&
|
||||||
|
tagValid &&
|
||||||
|
!sending,
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSend = async () => {
|
||||||
|
sending = true
|
||||||
|
sendError = ''
|
||||||
|
sentResult = null
|
||||||
|
try {
|
||||||
|
const result = await w.send(
|
||||||
|
recipient,
|
||||||
|
amount.toString(),
|
||||||
|
destinationTag === '' ? undefined : Number(destinationTag),
|
||||||
|
)
|
||||||
|
sentResult = result
|
||||||
|
recipient = ''
|
||||||
|
destinationTag = ''
|
||||||
|
amountXrp = ''
|
||||||
|
} catch (e) {
|
||||||
|
sendError = e instanceof Error ? e.message : 'Send failed'
|
||||||
|
} finally {
|
||||||
|
sending = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto mt-16 max-w-md">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onclick={() =>
|
||||||
|
navigate('/xrp/wallet/:id', {
|
||||||
|
params: { id: w.currentWalletId ?? '' },
|
||||||
|
})}
|
||||||
|
class="mb-4">
|
||||||
|
← Back
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Send XRP</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Balance: {w.balance} XRP
|
||||||
|
{#if w.fee}
|
||||||
|
(fee: ~{w.fee} XRP)
|
||||||
|
{/if}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form
|
||||||
|
onsubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
handleSend()
|
||||||
|
}}
|
||||||
|
class="space-y-4">
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<label class="text-xs font-medium">Recipient Address</label>
|
||||||
|
<Input
|
||||||
|
placeholder="XRP address starting with r..."
|
||||||
|
bind:value={recipient} />
|
||||||
|
{#if recipient && !isValidAddress}
|
||||||
|
<p class="text-xs text-red-500">Invalid XRP address</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<label class="text-xs font-medium">
|
||||||
|
Destination Tag (optional)
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
placeholder="Required by some exchanges"
|
||||||
|
bind:value={destinationTag} />
|
||||||
|
{#if !tagValid}
|
||||||
|
<p class="text-xs text-red-500">
|
||||||
|
Destination tag must be a number between 0 and
|
||||||
|
4294967295
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-1.5">
|
||||||
|
<label class="text-xs font-medium">Amount (XRP)</label>
|
||||||
|
<div class="relative">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
step="0.000001"
|
||||||
|
min="0"
|
||||||
|
placeholder="0.0"
|
||||||
|
bind:value={amountXrp} />
|
||||||
|
<span
|
||||||
|
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
|
||||||
|
XRP
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{#if exceedsBalance}
|
||||||
|
<p class="text-xs text-red-500">
|
||||||
|
Amount exceeds balance
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button type="submit" disabled={!canSend} class="w-full">
|
||||||
|
{sending ? 'Sending...' : 'Send'}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#if sendError}
|
||||||
|
<p class="mt-3 text-xs text-red-500">{sendError}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if sentResult}
|
||||||
|
<div
|
||||||
|
class="mt-4 space-y-2 rounded-md border border-green-500/30 bg-green-500/5 p-3">
|
||||||
|
<p class="text-xs font-medium text-green-600">
|
||||||
|
Transaction Sent
|
||||||
|
</p>
|
||||||
|
<p class="font-mono text-xs break-all">
|
||||||
|
Hash: {sentResult.hash}
|
||||||
|
</p>
|
||||||
|
<p class="font-mono text-xs">
|
||||||
|
Fee: {sentResult.fee} XRP
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { electrobun, type XrpTxDetails } from '$lib/electrobun.js'
|
||||||
|
import { xrpWallet, xrpNetwork } from '$lib/states/xrp-wallet.svelte.js'
|
||||||
|
import { navigate, route } from 'sv-router/generated'
|
||||||
|
import { Button } from '$lib/components/ui/button/index.js'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '$lib/components/ui/card/index.js'
|
||||||
|
import ArrowDown from '@lucide/svelte/icons/arrow-down'
|
||||||
|
import ArrowUp from '@lucide/svelte/icons/arrow-up'
|
||||||
|
|
||||||
|
let details = $state<XrpTxDetails | null>(null)
|
||||||
|
let loading = $state(true)
|
||||||
|
let error = $state('')
|
||||||
|
|
||||||
|
const hash = $derived(route.params.hash)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!hash) return
|
||||||
|
loading = true
|
||||||
|
error = ''
|
||||||
|
electrobun.rpc?.request
|
||||||
|
.xrpGetTxDetails({ hash })
|
||||||
|
.then((res) => {
|
||||||
|
details = res
|
||||||
|
loading = false
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
error = e instanceof Error ? e.message : 'Failed to fetch details'
|
||||||
|
loading = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto mt-16 max-w-md">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onclick={() =>
|
||||||
|
navigate('/xrp/wallet/:id', {
|
||||||
|
params: { id: xrpWallet.currentWalletId ?? '' },
|
||||||
|
})}
|
||||||
|
class="mb-4">
|
||||||
|
← Back
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Transaction Details</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{#if loading}
|
||||||
|
<p class="text-muted-foreground text-xs">Loading...</p>
|
||||||
|
{:else if error}
|
||||||
|
<p class="text-red-500 text-xs">{error}</p>
|
||||||
|
{:else if details}
|
||||||
|
<div class="space-y-2 font-mono text-xs">
|
||||||
|
<div class="flex items-center gap-2 rounded-md bg-muted p-2">
|
||||||
|
<div class="shrink-0 {details.to === xrpWallet.address ? 'text-green-500' : 'text-muted-foreground'}">
|
||||||
|
{#if details.to === xrpWallet.address}
|
||||||
|
<ArrowDown size={16} />
|
||||||
|
{:else}
|
||||||
|
<ArrowUp size={16} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<p class="font-medium">
|
||||||
|
{details.to === xrpWallet.address ? 'Received' : 'Sent'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">Transaction Hash</p>
|
||||||
|
<p class="break-all">{details.hash}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">From</p>
|
||||||
|
<p class="break-all">{details.from}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">To</p>
|
||||||
|
<p class="break-all">{details.to}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">Amount</p>
|
||||||
|
<p>{details.amount} XRP</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">Fee</p>
|
||||||
|
<p>{details.fee} XRP</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">Ledger</p>
|
||||||
|
<p>{details.ledgerIndex.toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">Validated</p>
|
||||||
|
<p>{details.confirmed ? 'Yes' : 'No'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if details.destinationTag !== undefined}
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">Destination Tag</p>
|
||||||
|
<p>{details.destinationTag}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if details.timestamp && details.timestamp !== '0'}
|
||||||
|
<div class="rounded-md bg-muted p-2">
|
||||||
|
<p class="text-muted-foreground mb-0.5">Date</p>
|
||||||
|
<p>{new Date(Number(details.timestamp) * 1000).toLocaleString()}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="w-full"
|
||||||
|
onclick={() =>
|
||||||
|
electrobun.rpc?.request.openExternal({
|
||||||
|
url: `${xrpNetwork.explorerUrl}${details?.hash}`,
|
||||||
|
})}>
|
||||||
|
View on Explorer
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="text-muted-foreground text-xs">No details available</p>
|
||||||
|
{/if}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { xrpWallet, xrpNetwork } from '$lib/states/xrp-wallet.svelte.js'
|
||||||
|
import { electrobun } from '$lib/electrobun.js'
|
||||||
|
import { Button } from '$lib/components/ui/button/index.js'
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '$lib/components/ui/card/index.js'
|
||||||
|
import ArrowDown from '@lucide/svelte/icons/arrow-down'
|
||||||
|
import ArrowUp from '@lucide/svelte/icons/arrow-up'
|
||||||
|
import SettingsIcon from '@lucide/svelte/icons/settings'
|
||||||
|
import QrCodeIcon from '@lucide/svelte/icons/qr-code'
|
||||||
|
import ExternalLink from '@lucide/svelte/icons/external-link'
|
||||||
|
import Loader from '$lib/components/loader.svelte'
|
||||||
|
import QrCode from '$lib/components/qr.svelte'
|
||||||
|
import * as Dialog from '$lib/components/ui/dialog/index.js'
|
||||||
|
import { CopyButton } from '$lib/components/ui/copy-button'
|
||||||
|
import { navigate, route } from 'sv-router/generated'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
|
const w = xrpWallet
|
||||||
|
|
||||||
|
const walletId = $derived(route.params.id)
|
||||||
|
|
||||||
|
let qrDialogOpen = $state(false)
|
||||||
|
let autoRefreshTimer: ReturnType<typeof setInterval> | undefined =
|
||||||
|
$state()
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (w.seed && !autoRefreshTimer) {
|
||||||
|
autoRefreshTimer = setInterval(() => {
|
||||||
|
w.refresh()
|
||||||
|
}, 30000)
|
||||||
|
} else if (!w.seed && autoRefreshTimer) {
|
||||||
|
clearInterval(autoRefreshTimer)
|
||||||
|
autoRefreshTimer = undefined
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (autoRefreshTimer) {
|
||||||
|
clearInterval(autoRefreshTimer)
|
||||||
|
autoRefreshTimer = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!w.seed || w.currentWalletId !== walletId) {
|
||||||
|
navigate('/xrp')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.refresh()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto mt-16 max-w-md">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<CardTitle>
|
||||||
|
{w.currentWallet?.name ?? 'XRP Wallet'}
|
||||||
|
</CardTitle>
|
||||||
|
<img src="/icons/xrp.png" alt="" class="size-6 rounded-full" />
|
||||||
|
</div>
|
||||||
|
<CardDescription>XRP Ledger</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div class="mb-4 space-y-1">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="min-w-0 flex-1 space-y-1">
|
||||||
|
<p class="font-medium text-xs">Address</p>
|
||||||
|
<p class="font-mono text-xs break-all">{w.address}</p>
|
||||||
|
<div class="flex gap-1.5 mt-1">
|
||||||
|
<Dialog.Root bind:open={qrDialogOpen}>
|
||||||
|
<Dialog.Trigger>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon-sm"
|
||||||
|
class="text-muted-foreground"
|
||||||
|
aria-label="Show QR code">
|
||||||
|
<QrCodeIcon size={16} />
|
||||||
|
</Button>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Content>
|
||||||
|
<div
|
||||||
|
class="flex flex-col items-center gap-3 py-4">
|
||||||
|
<QrCode text={w.address} size={256} />
|
||||||
|
<p
|
||||||
|
class="font-mono text-xs break-all text-center max-w-64">
|
||||||
|
{w.address}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
<CopyButton
|
||||||
|
text={w.address}
|
||||||
|
variant="outline"
|
||||||
|
size="icon-sm"
|
||||||
|
class="text-muted-foreground">
|
||||||
|
</CopyButton>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon-sm"
|
||||||
|
class="text-muted-foreground"
|
||||||
|
aria-label="View on explorer"
|
||||||
|
onclick={() =>
|
||||||
|
electrobun.rpc?.request.openExternal({
|
||||||
|
url: w.explorerAddressUrl,
|
||||||
|
})}>
|
||||||
|
<ExternalLink size={16} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 font-medium text-xs">Balance</p>
|
||||||
|
{#if w.loadingBalance}
|
||||||
|
<div class="py-1"><Loader /></div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex items-baseline gap-2">
|
||||||
|
<p class="font-mono text-lg">{w.balance} XRP</p>
|
||||||
|
{#if w.price}
|
||||||
|
<p class="font-mono text-sm text-muted-foreground">
|
||||||
|
≈ ${(
|
||||||
|
parseFloat(w.balance) * parseFloat(w.price)
|
||||||
|
).toLocaleString(undefined, {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if !w.funded}
|
||||||
|
<p class="text-xs text-muted-foreground">
|
||||||
|
Account not activated — it needs at least 10 XRP to
|
||||||
|
meet the base reserve.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{#if w.fee}
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">
|
||||||
|
Network fee: ~{w.fee} XRP
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onclick={() => navigate('/xrp/send')}>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onclick={() => navigate('/settings')}>
|
||||||
|
<SettingsIcon size={16} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onclick={async () => {
|
||||||
|
await w.logout()
|
||||||
|
navigate('/')
|
||||||
|
}}>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Transactions</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{#if w.loadingTransactions}
|
||||||
|
<div class="flex justify-center py-4">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
{:else if w.transactions.length === 0}
|
||||||
|
<p class="text-muted-foreground text-xs">
|
||||||
|
No transactions found
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<div class="max-h-96 space-y-1 overflow-y-auto">
|
||||||
|
{#each w.transactions as tx}
|
||||||
|
<button
|
||||||
|
onclick={() =>
|
||||||
|
navigate('/xrp/tx/:hash', {
|
||||||
|
params: { hash: tx.hash },
|
||||||
|
})}
|
||||||
|
class="flex w-full items-start gap-2 rounded-md bg-muted px-3 py-2 text-xs text-left cursor-pointer hover:bg-muted/80 transition-colors">
|
||||||
|
<div
|
||||||
|
class="shrink-0 mt-0.5 {tx.direction === 'in'
|
||||||
|
? 'text-green-500'
|
||||||
|
: 'text-muted-foreground'}">
|
||||||
|
{#if tx.direction === 'in'}
|
||||||
|
<ArrowDown size={16} />
|
||||||
|
{:else}
|
||||||
|
<ArrowUp size={16} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1 space-y-0.5">
|
||||||
|
<p class="font-medium">
|
||||||
|
{tx.direction === 'in' ? 'Received' : 'Sent'}
|
||||||
|
{#if !tx.confirmed}
|
||||||
|
<span class="text-muted-foreground">
|
||||||
|
(unconfirmed)
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
<p class="font-mono text-muted-foreground">
|
||||||
|
{tx.amount} XRP
|
||||||
|
</p>
|
||||||
|
{#if tx.timestamp && tx.timestamp !== '0'}
|
||||||
|
<p class="text-muted-foreground">
|
||||||
|
{new Date(
|
||||||
|
Number(tx.timestamp) * 1000,
|
||||||
|
).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
<p
|
||||||
|
class="text-muted-foreground/50 truncate font-mono">
|
||||||
|
{tx.hash.slice(0, 12)}...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -13,18 +13,16 @@
|
|||||||
"apps/desktop": {
|
"apps/desktop": {
|
||||||
"name": "koins",
|
"name": "koins",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alchemy/common": "^5.0.0",
|
|
||||||
"@alchemy/wallet-apis": "^5.0.0",
|
|
||||||
"@koins/utils": "workspace:*",
|
"@koins/utils": "workspace:*",
|
||||||
"d3-scale": "^4.0.2",
|
"d3-scale": "^4.0.2",
|
||||||
"d3-shape": "^3.2.0",
|
"d3-shape": "^3.2.0",
|
||||||
"drizzle-orm": "^1.0.0-beta.22",
|
|
||||||
"electrobun": "^1.18.1",
|
"electrobun": "^1.18.1",
|
||||||
|
"env-paths": "^4.0.0",
|
||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"monero-ts": "^0.11.8",
|
"monero-ts": "^0.11.8",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"sv-router": "latest",
|
"sv-router": "latest",
|
||||||
"viem": "^2.48.11",
|
"xrpl": "^5.0.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fontsource-variable/geist-mono": "^5.2.7",
|
"@fontsource-variable/geist-mono": "^5.2.7",
|
||||||
@@ -42,7 +40,6 @@
|
|||||||
"bun-types": "^1.3.14",
|
"bun-types": "^1.3.14",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
"drizzle-kit": "^1.0.0-beta.22",
|
|
||||||
"embla-carousel-svelte": "^8.6.0",
|
"embla-carousel-svelte": "^8.6.0",
|
||||||
"formsnap": "^2.0.1",
|
"formsnap": "^2.0.1",
|
||||||
"layerchart": "2.0.0-next.48",
|
"layerchart": "2.0.0-next.48",
|
||||||
@@ -75,14 +72,6 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="],
|
|
||||||
|
|
||||||
"@alchemy/common": ["@alchemy/common@5.0.2", "", { "dependencies": { "zod": "^3.23.0" }, "peerDependencies": { "viem": "^2.45.0" } }, "sha512-ORFjRhAJ07BUjhNqjgx2b4khWu0sNNEnnTXj3DT7UKFxZvK79cpyoE/6E0D17OWiz40y+uk6eRtkWog5bNWVpg=="],
|
|
||||||
|
|
||||||
"@alchemy/wallet-api-types": ["@alchemy/wallet-api-types@0.2.0-alpha.3", "", { "dependencies": { "@alchemy/common": "5.0.0-beta.32", "deep-equal": "^2.2.3", "ox": "^0.6.12", "viem": "^2.45.0", "zod": "^4.4.1" }, "peerDependencies": { "typescript": "^5.8.2" } }, "sha512-VaNj8gzGfPveo8i6l3aX4YovQezu4oDuAqyAy2k1LqsLf18pn2+iw3cV/WiFTwMrdQXLtnDYzS9D6NdkgEokzw=="],
|
|
||||||
|
|
||||||
"@alchemy/wallet-apis": ["@alchemy/wallet-apis@5.0.2", "", { "dependencies": { "@alchemy/common": "5.0.2", "@alchemy/wallet-api-types": "0.2.0-alpha.3", "deep-equal": "^2.2.3", "ox": "^0.11.1", "zod": "^4.4.1" }, "peerDependencies": { "@solana/kit": ">=6.0.0", "@solana/web3.js": "^1.95.0", "viem": "^2.45.0" }, "optionalPeers": ["@solana/kit", "@solana/web3.js"] }, "sha512-HmwOAvpe248LOQaJ3Der2owT82l0OwxBfVbwiXd7WXlJ+k04L1ooFKo5AjiWXMaoJQzpIkh37n66SUmyVVd4PA=="],
|
|
||||||
|
|
||||||
"@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="],
|
"@ark/schema": ["@ark/schema@0.56.0", "", { "dependencies": { "@ark/util": "0.56.0" } }, "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA=="],
|
||||||
|
|
||||||
"@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="],
|
"@ark/util": ["@ark/util@0.56.0", "", {}, "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="],
|
||||||
@@ -105,65 +94,63 @@
|
|||||||
|
|
||||||
"@dagrejs/graphlib": ["@dagrejs/graphlib@3.0.4", "", {}, "sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg=="],
|
"@dagrejs/graphlib": ["@dagrejs/graphlib@3.0.4", "", {}, "sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg=="],
|
||||||
|
|
||||||
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.11.0", "", {}, "sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg=="],
|
|
||||||
|
|
||||||
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||||
|
|
||||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
|
||||||
|
|
||||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
|
||||||
|
|
||||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
|
||||||
|
|
||||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
|
||||||
|
|
||||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
|
||||||
|
|
||||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
|
||||||
|
|
||||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
|
||||||
|
|
||||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
|
||||||
|
|
||||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
|
||||||
|
|
||||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
|
||||||
|
|
||||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
|
||||||
|
|
||||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
|
||||||
|
|
||||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
|
||||||
|
|
||||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
|
||||||
|
|
||||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
|
||||||
|
|
||||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
|
||||||
|
|
||||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
|
||||||
|
|
||||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
|
||||||
|
|
||||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
|
||||||
|
|
||||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
|
||||||
|
|
||||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
|
||||||
|
|
||||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
|
||||||
|
|
||||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
|
||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
|
||||||
|
|
||||||
"@exodus/schemasafe": ["@exodus/schemasafe@1.3.0", "", {}, "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="],
|
"@exodus/schemasafe": ["@exodus/schemasafe@1.3.0", "", {}, "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw=="],
|
||||||
|
|
||||||
@@ -191,8 +178,6 @@
|
|||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
"@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],
|
|
||||||
|
|
||||||
"@koins/utils": ["@koins/utils@workspace:packages/utils"],
|
"@koins/utils": ["@koins/utils@workspace:packages/utils"],
|
||||||
|
|
||||||
"@layerstack/svelte-actions": ["@layerstack/svelte-actions@1.0.1-next.18", "", { "dependencies": { "@floating-ui/dom": "^1.7.0", "@layerstack/utils": "2.0.0-next.18", "d3-scale": "^4.0.2" } }, "sha512-gxPzCnJ1c9LTfWtRqLUzefCx+k59ZpxDUQ2XB+LokveZQPe7IDSOwHaBOEMlaGoGrtwc3Ft8dSZq+2WT2o9u/g=="],
|
"@layerstack/svelte-actions": ["@layerstack/svelte-actions@1.0.1-next.18", "", { "dependencies": { "@floating-ui/dom": "^1.7.0", "@layerstack/utils": "2.0.0-next.18", "d3-scale": "^4.0.2" } }, "sha512-gxPzCnJ1c9LTfWtRqLUzefCx+k59ZpxDUQ2XB+LokveZQPe7IDSOwHaBOEMlaGoGrtwc3Ft8dSZq+2WT2o9u/g=="],
|
||||||
@@ -209,11 +194,9 @@
|
|||||||
|
|
||||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
|
||||||
|
|
||||||
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
|
"@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="],
|
||||||
|
|
||||||
"@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
|
"@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
|
||||||
|
|
||||||
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
|
||||||
|
|
||||||
"@oxc-project/types": ["@oxc-project/types@0.95.0", "", {}, "sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ=="],
|
"@oxc-project/types": ["@oxc-project/types@0.95.0", "", {}, "sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ=="],
|
||||||
|
|
||||||
@@ -257,11 +240,11 @@
|
|||||||
|
|
||||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.45", "", {}, "sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA=="],
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.45", "", {}, "sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA=="],
|
||||||
|
|
||||||
"@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
|
"@scure/base": ["@scure/base@2.2.0", "", {}, "sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg=="],
|
||||||
|
|
||||||
"@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="],
|
"@scure/bip32": ["@scure/bip32@2.2.0", "", { "dependencies": { "@noble/curves": "2.2.0", "@noble/hashes": "2.2.0", "@scure/base": "2.2.0" } }, "sha512-zFr7t2F+a9+5tB7QbarF2HQNYrgjCNaoLAupZdKkrFMYMozJf5zqH2WJCQibMzm1qQ0QogrxVGO3qXfQDYMaQg=="],
|
||||||
|
|
||||||
"@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="],
|
"@scure/bip39": ["@scure/bip39@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0", "@scure/base": "2.2.0" } }, "sha512-T/Bj/YvYMNkIPq6EENO6/rcs2e7qTNuyoUXf0KBFDmp0ZDu0H2X4Lq6yC3i0c8PcWkov5EbW+yQZZbdMmk154A=="],
|
||||||
|
|
||||||
"@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="],
|
"@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="],
|
||||||
|
|
||||||
@@ -369,7 +352,9 @@
|
|||||||
|
|
||||||
"@vinejs/vine": ["@vinejs/vine@3.0.1", "", { "dependencies": { "@poppinss/macroable": "^1.0.4", "@types/validator": "^13.12.2", "@vinejs/compiler": "^3.0.0", "camelcase": "^8.0.0", "dayjs": "^1.11.13", "dlv": "^1.1.3", "normalize-url": "^8.0.1", "validator": "^13.12.0" } }, "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ=="],
|
"@vinejs/vine": ["@vinejs/vine@3.0.1", "", { "dependencies": { "@poppinss/macroable": "^1.0.4", "@types/validator": "^13.12.2", "@vinejs/compiler": "^3.0.0", "camelcase": "^8.0.0", "dayjs": "^1.11.13", "dlv": "^1.1.3", "normalize-url": "^8.0.1", "validator": "^13.12.0" } }, "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ=="],
|
||||||
|
|
||||||
"abitype": ["abitype@1.2.3", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg=="],
|
"@xrplf/isomorphic": ["@xrplf/isomorphic@1.0.2", "", { "dependencies": { "@noble/hashes": "^2.0.1", "eventemitter3": "5.0.1", "ws": "^8.20.0" } }, "sha512-ncZUdMXr6VlSXtdoiDi0jTH+gBrgGxwVeEidhoegII3PmyErbQsyj6e+j7acmR4LW/lvBkPkzb9QzRfJH0n3rA=="],
|
||||||
|
|
||||||
|
"@xrplf/secret-numbers": ["@xrplf/secret-numbers@3.0.0", "", { "dependencies": { "@xrplf/isomorphic": "^1.0.2", "ripple-keypairs": "^3.0.0" } }, "sha512-qpGhAZXv5noMDjCtfzq5NK0y5rrdwTVjKhhPcAYSE+a/gogBOgqdpCKyieprVVPCnmVmJnGeRoZKBAqpCGegsA=="],
|
||||||
|
|
||||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||||
|
|
||||||
@@ -389,8 +374,6 @@
|
|||||||
|
|
||||||
"arktype": ["arktype@2.2.0", "", { "dependencies": { "@ark/schema": "0.56.0", "@ark/util": "0.56.0", "arkregex": "0.0.5" } }, "sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ=="],
|
"arktype": ["arktype@2.2.0", "", { "dependencies": { "@ark/schema": "0.56.0", "@ark/util": "0.56.0", "arkregex": "0.0.5" } }, "sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ=="],
|
||||||
|
|
||||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
|
||||||
|
|
||||||
"ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="],
|
"ast-kit": ["ast-kit@2.2.0", "", { "dependencies": { "@babel/parser": "^7.28.5", "pathe": "^2.0.3" } }, "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw=="],
|
||||||
|
|
||||||
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
|
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
|
||||||
@@ -399,14 +382,14 @@
|
|||||||
|
|
||||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||||
|
|
||||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
|
||||||
|
|
||||||
"axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="],
|
"axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="],
|
||||||
|
|
||||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
"basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
|
"basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
|
||||||
|
|
||||||
|
"bignumber.js": ["bignumber.js@10.0.2", "", {}, "sha512-E8Wp9O06QA6lneJ4aRUXKYf/1GIomqUEmUMwtIOMtDxf1U52ffJY+y7JBk/8wRafA8qOIqLnXQGqonYXZdBnFQ=="],
|
||||||
|
|
||||||
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
||||||
|
|
||||||
"bits-ui": ["bits-ui@2.18.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA=="],
|
"bits-ui": ["bits-ui@2.18.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA=="],
|
||||||
@@ -415,12 +398,8 @@
|
|||||||
|
|
||||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
"call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="],
|
|
||||||
|
|
||||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||||
|
|
||||||
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
|
||||||
|
|
||||||
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
|
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
|
||||||
|
|
||||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
@@ -513,14 +492,8 @@
|
|||||||
|
|
||||||
"decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
|
"decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
|
||||||
|
|
||||||
"deep-equal": ["deep-equal@2.2.3", "", { "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "isarray": "^2.0.5", "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", "which-typed-array": "^1.1.13" } }, "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA=="],
|
|
||||||
|
|
||||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||||
|
|
||||||
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
|
|
||||||
|
|
||||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
|
||||||
|
|
||||||
"defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="],
|
"defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="],
|
||||||
|
|
||||||
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
|
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
|
||||||
@@ -541,10 +514,6 @@
|
|||||||
|
|
||||||
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
|
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
|
||||||
|
|
||||||
"drizzle-kit": ["drizzle-kit@1.0.0-beta.22", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-9HTZuQRljQKTgCx4UhiGn8KYYfHGk4+B/bRR1714W67kz0qgJvdrG527i8rQD8uUyET9UTGR1u8syySJD4znGw=="],
|
|
||||||
|
|
||||||
"drizzle-orm": ["drizzle-orm@1.0.0-beta.22", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/mssql", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mssql", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-F+DZyVIvH0oVKa/w08Cle1xfoH+pc+htIXHG/frnMLG72aby9NYYr9oc+9XvghnoO4umxFItduz0OMmQJMnenw=="],
|
|
||||||
|
|
||||||
"dts-resolver": ["dts-resolver@2.1.3", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw=="],
|
"dts-resolver": ["dts-resolver@2.1.3", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw=="],
|
||||||
|
|
||||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||||
@@ -571,13 +540,11 @@
|
|||||||
|
|
||||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||||
|
|
||||||
"es-get-iterator": ["es-get-iterator@1.1.3", "", { "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.3", "has-symbols": "^1.0.3", "is-arguments": "^1.1.1", "is-map": "^2.0.2", "is-set": "^2.0.2", "is-string": "^1.0.7", "isarray": "^2.0.5", "stop-iteration-iterator": "^1.0.0" } }, "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw=="],
|
|
||||||
|
|
||||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||||
|
|
||||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
"esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
|
||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
@@ -607,8 +574,6 @@
|
|||||||
|
|
||||||
"follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
|
"follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
|
||||||
|
|
||||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
|
||||||
|
|
||||||
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
||||||
|
|
||||||
"formsnap": ["formsnap@2.0.1", "", { "dependencies": { "svelte-toolbelt": "^0.5.0" }, "peerDependencies": { "svelte": "^5.0.0", "sveltekit-superforms": "^2.19.0" } }, "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ=="],
|
"formsnap": ["formsnap@2.0.1", "", { "dependencies": { "svelte-toolbelt": "^0.5.0" }, "peerDependencies": { "svelte": "^5.0.0", "sveltekit-superforms": "^2.19.0" } }, "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ=="],
|
||||||
@@ -617,8 +582,6 @@
|
|||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||||
|
|
||||||
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
|
|
||||||
|
|
||||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||||
@@ -633,12 +596,8 @@
|
|||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
|
||||||
|
|
||||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||||
|
|
||||||
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
|
|
||||||
|
|
||||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||||
|
|
||||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||||
@@ -655,64 +614,26 @@
|
|||||||
|
|
||||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||||
|
|
||||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
|
||||||
|
|
||||||
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
|
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
|
||||||
|
|
||||||
"ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
|
"ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
|
||||||
|
|
||||||
"is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="],
|
|
||||||
|
|
||||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
|
||||||
|
|
||||||
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
|
|
||||||
|
|
||||||
"is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="],
|
|
||||||
|
|
||||||
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
|
|
||||||
|
|
||||||
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
|
|
||||||
|
|
||||||
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||||
|
|
||||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
|
|
||||||
|
|
||||||
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
|
||||||
|
|
||||||
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
||||||
|
|
||||||
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
|
|
||||||
|
|
||||||
"is-safe-filename": ["is-safe-filename@0.1.1", "", {}, "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g=="],
|
"is-safe-filename": ["is-safe-filename@0.1.1", "", {}, "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g=="],
|
||||||
|
|
||||||
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
|
|
||||||
|
|
||||||
"is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
|
|
||||||
|
|
||||||
"is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
|
|
||||||
|
|
||||||
"is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
|
|
||||||
|
|
||||||
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
|
|
||||||
|
|
||||||
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
|
|
||||||
|
|
||||||
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||||
|
|
||||||
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
|
||||||
|
|
||||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
"isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="],
|
|
||||||
|
|
||||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||||
|
|
||||||
"joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="],
|
"joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="],
|
||||||
|
|
||||||
"jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="],
|
|
||||||
|
|
||||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
||||||
@@ -797,18 +718,8 @@
|
|||||||
|
|
||||||
"normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="],
|
"normalize-url": ["normalize-url@8.1.1", "", {}, "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ=="],
|
||||||
|
|
||||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
|
||||||
|
|
||||||
"object-is": ["object-is@1.1.6", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" } }, "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q=="],
|
|
||||||
|
|
||||||
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
|
|
||||||
|
|
||||||
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
|
|
||||||
|
|
||||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||||
|
|
||||||
"ox": ["ox@0.11.3", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-1bWYGk/xZel3xro3l8WGg6eq4YEKlaqvyMtVhfMFpbJzK2F6rj4EDRtqDCWVEJMkzcmEi9uW2QxsqELokOlarw=="],
|
|
||||||
|
|
||||||
"p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
"p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||||
|
|
||||||
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||||
@@ -835,8 +746,6 @@
|
|||||||
|
|
||||||
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||||
|
|
||||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="],
|
"postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="],
|
||||||
|
|
||||||
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
"prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="],
|
||||||
@@ -867,8 +776,6 @@
|
|||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
|
||||||
|
|
||||||
"remixicon-svelte": ["remixicon-svelte@0.0.5", "", { "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-a7tnkBeA6P/ItRc2Q/jOgF+Xoh6Fj7hKrr/E6ZUCmOM4XBFi+mBDR95hqdRHWVf2s8sK85ycGXg+AAFZv87s+Q=="],
|
"remixicon-svelte": ["remixicon-svelte@0.0.5", "", { "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-a7tnkBeA6P/ItRc2Q/jOgF+Xoh6Fj7hKrr/E6ZUCmOM4XBFi+mBDR95hqdRHWVf2s8sK85ycGXg+AAFZv87s+Q=="],
|
||||||
|
|
||||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||||
@@ -877,6 +784,12 @@
|
|||||||
|
|
||||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||||
|
|
||||||
|
"ripple-address-codec": ["ripple-address-codec@5.0.1", "", { "dependencies": { "@scure/base": "^2.0.0", "@xrplf/isomorphic": "^1.0.2" } }, "sha512-JQHLKuVJV8lv9Qobmn4aUM2Dpv9WRRLKnNWfM8tN02fAbUtG8mUPsu9q9UYX8P76G4qzytEc5ZKMp/3JggNYmw=="],
|
||||||
|
|
||||||
|
"ripple-binary-codec": ["ripple-binary-codec@2.8.0", "", { "dependencies": { "@xrplf/isomorphic": "^1.0.2", "bignumber.js": "^10.0.2", "ripple-address-codec": "^5.0.1" } }, "sha512-+NKnOi3hdzjm5dDpoZLUEaYon1jahPlSGnp3YrDoNMSR09ICEqgupN5wpEkPuqJvV75PF/g+W1QUwIXVzbEe7w=="],
|
||||||
|
|
||||||
|
"ripple-keypairs": ["ripple-keypairs@3.0.0", "", { "dependencies": { "@noble/curves": "^2.0.1", "@xrplf/isomorphic": "^1.0.2", "ripple-address-codec": "^5.0.1" } }, "sha512-lE69pD0E8hFNCqZoVXRyY45Yi8Ku+Qw7Rf1qRwPj4nOi34vp9NAuwzfiJH1IwXGWNCfEkwVfctG99CPTEoUf+g=="],
|
||||||
|
|
||||||
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
|
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
|
||||||
|
|
||||||
"rolldown": ["rolldown@1.0.0-beta.45", "", { "dependencies": { "@oxc-project/types": "=0.95.0", "@rolldown/pluginutils": "1.0.0-beta.45" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.45", "@rolldown/binding-darwin-arm64": "1.0.0-beta.45", "@rolldown/binding-darwin-x64": "1.0.0-beta.45", "@rolldown/binding-freebsd-x64": "1.0.0-beta.45", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.45", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.45", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.45", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.45", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.45", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.45", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.45", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.45", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.45", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.45" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ=="],
|
"rolldown": ["rolldown@1.0.0-beta.45", "", { "dependencies": { "@oxc-project/types": "=0.95.0", "@rolldown/pluginutils": "1.0.0-beta.45" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.45", "@rolldown/binding-darwin-arm64": "1.0.0-beta.45", "@rolldown/binding-darwin-x64": "1.0.0-beta.45", "@rolldown/binding-freebsd-x64": "1.0.0-beta.45", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.45", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.45", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.45", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.45", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.45", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.45", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.45", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.45", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.45", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.45" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-iMmuD72XXLf26Tqrv1cryNYLX6NNPLhZ3AmNkSf8+xda0H+yijjGJ+wVT9UdBUHOpKzq9RjKtQKRCWoEKQQBZQ=="],
|
||||||
@@ -893,8 +806,6 @@
|
|||||||
|
|
||||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||||
|
|
||||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
"semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
|
"semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
|
||||||
@@ -905,10 +816,6 @@
|
|||||||
|
|
||||||
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
|
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
|
||||||
|
|
||||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
|
||||||
|
|
||||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
|
||||||
|
|
||||||
"shadcn-svelte": ["shadcn-svelte@1.2.7", "", { "dependencies": { "commander": "^14.0.0", "node-fetch-native": "^1.6.4", "postcss": "^8.5.5", "tailwind-merge": "^3.0.0" }, "peerDependencies": { "svelte": "^5.0.0" }, "bin": { "shadcn-svelte": "dist/index.mjs" } }, "sha512-mWuQk4H4gtV+J2wJQ7nEPKNnB/v86AALFryZU0SSM7ChHmJJMZ1kH+qIuxYKrXm9vOOOcSWHRsWzPDB71DnjYA=="],
|
"shadcn-svelte": ["shadcn-svelte@1.2.7", "", { "dependencies": { "commander": "^14.0.0", "node-fetch-native": "^1.6.4", "postcss": "^8.5.5", "tailwind-merge": "^3.0.0" }, "peerDependencies": { "svelte": "^5.0.0" }, "bin": { "shadcn-svelte": "dist/index.mjs" } }, "sha512-mWuQk4H4gtV+J2wJQ7nEPKNnB/v86AALFryZU0SSM7ChHmJJMZ1kH+qIuxYKrXm9vOOOcSWHRsWzPDB71DnjYA=="],
|
||||||
|
|
||||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
@@ -917,14 +824,6 @@
|
|||||||
|
|
||||||
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
||||||
|
|
||||||
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
|
||||||
|
|
||||||
"side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
|
|
||||||
|
|
||||||
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
|
||||||
|
|
||||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
|
||||||
|
|
||||||
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
||||||
|
|
||||||
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
||||||
@@ -937,8 +836,6 @@
|
|||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
|
||||||
|
|
||||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
@@ -949,7 +846,7 @@
|
|||||||
|
|
||||||
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||||
|
|
||||||
"sv-router": ["sv-router@0.16.3", "", { "dependencies": { "esm-env": "^1.2.2" }, "peerDependencies": { "svelte": "^5" }, "bin": { "sv-router": "./src/cli/index.js" } }, "sha512-nne8keA2GKZRdRE8r7HFx7wcR2ruqVPf/RZ9De5webNgaDbfKHEeh5qpAe0Hg0cczXnZh/qcgRsSNDphbVd0Gw=="],
|
"sv-router": ["sv-router@0.18.0", "", { "dependencies": { "esm-env": "^1.2.2" }, "peerDependencies": { "svelte": "^5" }, "bin": { "sv-router": "./src/cli/index.js" } }, "sha512-gLky7FBGj8P6MAna1wxYniHJxMrXgaeTNnkZ7WH387ddRyUohfDtvEgWQFfB4oLL2XxWlw8Lfmk03TtBdK/JTA=="],
|
||||||
|
|
||||||
"svelte": ["svelte@5.55.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw=="],
|
"svelte": ["svelte@5.55.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw=="],
|
||||||
|
|
||||||
@@ -1025,8 +922,6 @@
|
|||||||
|
|
||||||
"vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="],
|
"vaul-svelte": ["vaul-svelte@1.0.0-next.7", "", { "dependencies": { "runed": "^0.23.2", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-7zN7Bi3dFQixvvbUJY9uGDe7Ws/dGZeBQR2pXdXmzQiakjrxBvWo0QrmsX3HK+VH+SZOltz378cmgmCS9f9rSg=="],
|
||||||
|
|
||||||
"viem": ["viem@2.48.11", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", "ox": "0.14.20", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-+WZ5E0dBS6GtKb+1wEk5DeYRRRW42+pFnXCo67Ydodf42sBwO+hu3wnQy66lc4MKmHz+llPVdbyehYr9oTE2iw=="],
|
|
||||||
|
|
||||||
"vite": ["vite@8.0.11", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.14", "rolldown": "1.0.0-rc.18", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow=="],
|
"vite": ["vite@8.0.11", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.14", "rolldown": "1.0.0-rc.18", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow=="],
|
||||||
|
|
||||||
"vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="],
|
"vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="],
|
||||||
@@ -1035,17 +930,13 @@
|
|||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
|
||||||
|
|
||||||
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
|
||||||
|
|
||||||
"which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
|
"which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
|
||||||
|
|
||||||
"which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="],
|
|
||||||
|
|
||||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||||
|
|
||||||
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
"ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
|
||||||
|
|
||||||
|
"xrpl": ["xrpl@5.0.0", "", { "dependencies": { "@scure/bip32": "^2.0.1", "@scure/bip39": "^2.0.1", "@xrplf/isomorphic": "^1.0.2", "@xrplf/secret-numbers": "^3.0.0", "bignumber.js": "^10.0.2", "eventemitter3": "^5.0.1", "fast-json-stable-stringify": "^2.1.0", "ripple-address-codec": "^5.0.1", "ripple-binary-codec": "^2.8.0", "ripple-keypairs": "^3.0.0" } }, "sha512-YqaTFJUnhOu0mI4bsuHbKGj6w9ATcH8EIgw+gOLnh1rrlLTo5oImLQzhKJixCAPqqWOKnsY7J3jsN+l+zeEWgA=="],
|
||||||
|
|
||||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||||
|
|
||||||
@@ -1061,12 +952,6 @@
|
|||||||
|
|
||||||
"zod-v3-to-json-schema": ["zod-v3-to-json-schema@4.0.0", "", { "peerDependencies": { "zod": "^3.25 || ^4.0.14" } }, "sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w=="],
|
"zod-v3-to-json-schema": ["zod-v3-to-json-schema@4.0.0", "", { "peerDependencies": { "zod": "^3.25 || ^4.0.14" } }, "sha512-KixLrhX/uPmRFnDgsZrzrk4x5SSJA+PmaE5adbfID9+3KPJcdxqRobaHU397EfWBqfQircrjKqvEqZ/mW5QH6w=="],
|
||||||
|
|
||||||
"@alchemy/common/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
|
||||||
|
|
||||||
"@alchemy/wallet-api-types/@alchemy/common": ["@alchemy/common@5.0.0-beta.32", "", { "dependencies": { "zod": "^3.23.0" }, "peerDependencies": { "viem": "^2.45.0" } }, "sha512-gVUrMhnhdD8UUipd70XUbmxm3PX3OhHK1TSi3ODBGOH3Jd9SoH0+mFagSn62pwi5XFPCOg3UGGXj9bmLKFlGhA=="],
|
|
||||||
|
|
||||||
"@alchemy/wallet-api-types/ox": ["ox@0.6.12", "", { "dependencies": { "@adraffy/ens-normalize": "^1.10.1", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0", "@scure/bip32": "^1.5.0", "@scure/bip39": "^1.4.0", "abitype": "^1.0.6", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-78hziRGLj0qTDa0UW4+ynv9tW2Cp1vmCfGokL8D7kiSDh6Y0LAfHL+HaDN4l2a9jcrOG3fexTDtLNtDNkEwLtg=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
|
||||||
@@ -1119,18 +1004,12 @@
|
|||||||
|
|
||||||
"svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
|
"svelte-sonner/runed": ["runed@0.28.0", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ=="],
|
||||||
|
|
||||||
"tsx/esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
|
|
||||||
|
|
||||||
"vaul-svelte/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
"vaul-svelte/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
|
||||||
|
|
||||||
"vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
"vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
||||||
|
|
||||||
"viem/ox": ["ox@0.14.20", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.2.3", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-rby38C3nDn8eQkf29Zgw4hkCZJ64Qqi0zRPWL8ENUQ7JVuoITqrVtwWQgM/He19SCMUEc7hS/Sjw0jIOSLJhOw=="],
|
|
||||||
|
|
||||||
"vite/rolldown": ["rolldown@1.0.0-rc.18", "", { "dependencies": { "@oxc-project/types": "=0.128.0", "@rolldown/pluginutils": "1.0.0-rc.18" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.18", "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", "@rolldown/binding-darwin-x64": "1.0.0-rc.18", "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg=="],
|
"vite/rolldown": ["rolldown@1.0.0-rc.18", "", { "dependencies": { "@oxc-project/types": "=0.128.0", "@rolldown/pluginutils": "1.0.0-rc.18" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.18", "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", "@rolldown/binding-darwin-x64": "1.0.0-rc.18", "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg=="],
|
||||||
|
|
||||||
"@alchemy/wallet-api-types/@alchemy/common/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
|
||||||
|
|
||||||
"@types/bun/bun-types/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
|
"@types/bun/bun-types/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
|
||||||
|
|
||||||
"axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
"axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
|
||||||
@@ -1153,58 +1032,6 @@
|
|||||||
|
|
||||||
"qrcode/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
"qrcode/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
|
|
||||||
|
|
||||||
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
|
|
||||||
|
|
||||||
"vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.128.0", "", {}, "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ=="],
|
"vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.128.0", "", {}, "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ=="],
|
||||||
|
|
||||||
"vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.18", "", { "os": "android", "cpu": "arm64" }, "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ=="],
|
"vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.18", "", { "os": "android", "cpu": "arm64" }, "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ=="],
|
||||||
|
|||||||
Reference in New Issue
Block a user