From 0d3acef182d735656a2f0e49b3983ba7cc1d105b Mon Sep 17 00:00:00 2001 From: TZGyn Date: Sat, 1 Aug 2026 00:58:22 +0800 Subject: [PATCH] remove evm + add xrp --- README.md | 38 +- apps/desktop/drizzle.config.ts | 19 - .../migration.sql | 10 - .../snapshot.json | 93 -- .../migration.sql | 41 - .../snapshot.json | 467 ---------- apps/desktop/electrobun.config.ts | 1 - apps/desktop/package.json | 9 +- apps/desktop/public/icons/binance.png | Bin 17163 -> 0 bytes apps/desktop/public/icons/ethereum.png | Bin 2984 -> 0 bytes apps/desktop/public/icons/polygon.png | Bin 10248 -> 0 bytes apps/desktop/public/icons/xrp.png | Bin 0 -> 7004 bytes apps/desktop/src/bun/index.ts | 38 +- apps/desktop/src/bun/lib/alchemy/call.ts | 31 - .../src/bun/lib/alchemy/get-token-balances.ts | 32 - .../src/bun/lib/alchemy/get-token-metadata.ts | 25 - .../lib/alchemy/get-transaction-history.ts | 104 --- apps/desktop/src/bun/lib/alchemy/index.ts | 3 - apps/desktop/src/bun/lib/alchemy/network.ts | 31 - apps/desktop/src/bun/lib/alchemy/types.ts | 35 - apps/desktop/src/bun/lib/db/index.ts | 5 - apps/desktop/src/bun/lib/db/relations.ts | 4 - apps/desktop/src/bun/lib/db/schema.ts | 115 --- apps/desktop/src/bun/lib/sqlite/index.ts | 16 - apps/desktop/src/bun/lib/sync/index.ts | 1 - .../src/bun/lib/sync/sync-tx-details.ts | 76 -- .../src/bun/lib/sync/sync-tx-history.ts | 149 ---- .../src/bun/lib/tokens/get-metadata.ts | 64 -- apps/desktop/src/bun/lib/tokens/index.ts | 1 - .../src/bun/lib/transactions/get-details.ts | 66 -- .../src/bun/lib/transactions/get-history.ts | 63 -- .../desktop/src/bun/lib/transactions/index.ts | 5 - .../bun/lib/viem/get-transaction-details.ts | 50 -- apps/desktop/src/bun/lib/viem/getChain.ts | 12 - apps/desktop/src/bun/lib/viem/getClient.ts | 19 - apps/desktop/src/bun/lib/viem/index.ts | 2 - apps/desktop/src/bun/lib/xrp/index.ts | 188 +++++ apps/desktop/src/bun/rpc/evm/index.ts | 641 -------------- apps/desktop/src/bun/rpc/xrp/index.ts | 153 ++++ .../src/lib/components/EvmDashboard.svelte | 799 ------------------ apps/desktop/src/lib/electrobun.ts | 9 +- apps/desktop/src/lib/rpc-schema.ts | 128 ++- ...-wallet.svelte.ts => xrp-wallet.svelte.ts} | 345 +++----- apps/desktop/src/routes/index.svelte | 109 ++- .../src/routes/multicoin/bsc/index.svelte | 7 - .../desktop/src/routes/multicoin/index.svelte | 7 - .../src/routes/multicoin/polygon/index.svelte | 7 - apps/desktop/src/routes/multicoin/send.svelte | 220 ----- apps/desktop/src/routes/settings.svelte | 106 +-- .../src/routes/tx/[chainid]/[hash].svelte | 175 ---- apps/desktop/src/routes/xrp/index.svelte | 273 ++++++ apps/desktop/src/routes/xrp/send.svelte | 165 ++++ apps/desktop/src/routes/xrp/tx/[hash].svelte | 142 ++++ .../desktop/src/routes/xrp/wallet/[id].svelte | 235 ++++++ bun.lock | 271 ++---- 55 files changed, 1457 insertions(+), 4148 deletions(-) delete mode 100644 apps/desktop/drizzle.config.ts delete mode 100644 apps/desktop/drizzle/20260514040815_regular_sunspot/migration.sql delete mode 100644 apps/desktop/drizzle/20260514040815_regular_sunspot/snapshot.json delete mode 100644 apps/desktop/drizzle/20260516080547_short_firebird/migration.sql delete mode 100644 apps/desktop/drizzle/20260516080547_short_firebird/snapshot.json delete mode 100644 apps/desktop/public/icons/binance.png delete mode 100644 apps/desktop/public/icons/ethereum.png delete mode 100644 apps/desktop/public/icons/polygon.png create mode 100644 apps/desktop/public/icons/xrp.png delete mode 100644 apps/desktop/src/bun/lib/alchemy/call.ts delete mode 100644 apps/desktop/src/bun/lib/alchemy/get-token-balances.ts delete mode 100644 apps/desktop/src/bun/lib/alchemy/get-token-metadata.ts delete mode 100644 apps/desktop/src/bun/lib/alchemy/get-transaction-history.ts delete mode 100644 apps/desktop/src/bun/lib/alchemy/index.ts delete mode 100644 apps/desktop/src/bun/lib/alchemy/network.ts delete mode 100644 apps/desktop/src/bun/lib/alchemy/types.ts delete mode 100644 apps/desktop/src/bun/lib/db/index.ts delete mode 100644 apps/desktop/src/bun/lib/db/relations.ts delete mode 100644 apps/desktop/src/bun/lib/db/schema.ts delete mode 100644 apps/desktop/src/bun/lib/sqlite/index.ts delete mode 100644 apps/desktop/src/bun/lib/sync/index.ts delete mode 100644 apps/desktop/src/bun/lib/sync/sync-tx-details.ts delete mode 100644 apps/desktop/src/bun/lib/sync/sync-tx-history.ts delete mode 100644 apps/desktop/src/bun/lib/tokens/get-metadata.ts delete mode 100644 apps/desktop/src/bun/lib/tokens/index.ts delete mode 100644 apps/desktop/src/bun/lib/transactions/get-details.ts delete mode 100644 apps/desktop/src/bun/lib/transactions/get-history.ts delete mode 100644 apps/desktop/src/bun/lib/transactions/index.ts delete mode 100644 apps/desktop/src/bun/lib/viem/get-transaction-details.ts delete mode 100644 apps/desktop/src/bun/lib/viem/getChain.ts delete mode 100644 apps/desktop/src/bun/lib/viem/getClient.ts delete mode 100644 apps/desktop/src/bun/lib/viem/index.ts create mode 100644 apps/desktop/src/bun/lib/xrp/index.ts delete mode 100644 apps/desktop/src/bun/rpc/evm/index.ts create mode 100644 apps/desktop/src/bun/rpc/xrp/index.ts delete mode 100644 apps/desktop/src/lib/components/EvmDashboard.svelte rename apps/desktop/src/lib/states/{evm-wallet.svelte.ts => xrp-wallet.svelte.ts} (53%) delete mode 100644 apps/desktop/src/routes/multicoin/bsc/index.svelte delete mode 100644 apps/desktop/src/routes/multicoin/index.svelte delete mode 100644 apps/desktop/src/routes/multicoin/polygon/index.svelte delete mode 100644 apps/desktop/src/routes/multicoin/send.svelte delete mode 100644 apps/desktop/src/routes/tx/[chainid]/[hash].svelte create mode 100644 apps/desktop/src/routes/xrp/index.svelte create mode 100644 apps/desktop/src/routes/xrp/send.svelte create mode 100644 apps/desktop/src/routes/xrp/tx/[hash].svelte create mode 100644 apps/desktop/src/routes/xrp/wallet/[id].svelte diff --git a/README.md b/README.md index d116244..1f5a165 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # 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 -- **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 +- **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 - **Password fallback**: PBKDF2-salted password stored in keychain, used when biometric is unavailable - **QR codes**: Address QR with chain logo overlay -- **Transaction history**: Cached in local SQLite via Drizzle ORM -- **Settings**: Reset app clears all DB tables and keychain entries +- **Transaction history**: Live XRP ledger transactions and Monero wallet transactions +- **Settings**: Reset app clears all keychain entries ## Prerequisites @@ -35,41 +35,38 @@ apps/desktop/ │ │ │ ├── monero/ # Monero RPC management │ │ │ │ ├── binary.ts # Download/extract monero-wallet-rpc │ │ │ │ └── wallet.ts # Child process + RPC client -│ │ │ ├── alchemy/ # Alchemy API calls -│ │ │ ├── viem/ # viem chain calls -│ │ │ ├── tokens/ # Token metadata (DB-first cache) -│ │ │ ├── transactions/ # Tx history + details (DB-first cache) -│ │ │ ├── db/ # Drizzle ORM + SQLite schema +│ │ │ ├── xrp/ # XRP Ledger client (xrpl) │ │ │ └── biometric.ts # Biometric auth (Swift subprocess) +│ │ ├── rpc/ +│ │ │ ├── monero/ # Monero RPC handlers +│ │ │ └── xrp/ # XRP RPC handlers │ │ └── native/ │ │ └── biometric-helper.swift # Touch ID prompt binary │ ├── lib/ │ │ ├── states/ -│ │ │ └── wallet.svelte.ts # Wallet state (singleton) -│ │ ├── components/ -│ │ │ └── EvmDashboard.svelte # Shared EVM dashboard +│ │ │ ├── monero-wallet.svelte.ts # Monero wallet state (singleton) +│ │ │ └── xrp-wallet.svelte.ts # XRP wallet state (singleton) │ │ └── electrobun.ts # RPC type definitions │ └── routes/ │ ├── index.svelte # Login/welcome page -│ ├── multicoin/ # ETH / BSC / Polygon routes -│ ├── monero/ # Monero dashboard -│ ├── settings.svelte # App settings -│ └── tx/ # Transaction details +│ ├── monero/ # Monero routes +│ ├── xrp/ # XRP routes +│ └── settings.svelte # App settings └── package.json ``` ## 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. -- **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). -- **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 -- [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 -- [Drizzle ORM](https://orm.drizzle.team) — SQLite schema + queries - [Svelte 5](https://svelte.dev) — UI framework - [Electrobun](https://electrobun.dev) — Desktop app shell - [sv-router](https://github.com/TZGyn/sv-router) — File-based routing @@ -80,5 +77,4 @@ apps/desktop/ ```bash bun run dev # Start development server -bun run db:push # Push Drizzle schema to SQLite ``` diff --git a/apps/desktop/drizzle.config.ts b/apps/desktop/drizzle.config.ts deleted file mode 100644 index 870e0ea..0000000 --- a/apps/desktop/drizzle.config.ts +++ /dev/null @@ -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), - }, -}) diff --git a/apps/desktop/drizzle/20260514040815_regular_sunspot/migration.sql b/apps/desktop/drizzle/20260514040815_regular_sunspot/migration.sql deleted file mode 100644 index bd62f8a..0000000 --- a/apps/desktop/drizzle/20260514040815_regular_sunspot/migration.sql +++ /dev/null @@ -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`); \ No newline at end of file diff --git a/apps/desktop/drizzle/20260514040815_regular_sunspot/snapshot.json b/apps/desktop/drizzle/20260514040815_regular_sunspot/snapshot.json deleted file mode 100644 index 5d6e17b..0000000 --- a/apps/desktop/drizzle/20260514040815_regular_sunspot/snapshot.json +++ /dev/null @@ -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": [] -} \ No newline at end of file diff --git a/apps/desktop/drizzle/20260516080547_short_firebird/migration.sql b/apps/desktop/drizzle/20260516080547_short_firebird/migration.sql deleted file mode 100644 index d55b752..0000000 --- a/apps/desktop/drizzle/20260516080547_short_firebird/migration.sql +++ /dev/null @@ -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`); \ No newline at end of file diff --git a/apps/desktop/drizzle/20260516080547_short_firebird/snapshot.json b/apps/desktop/drizzle/20260516080547_short_firebird/snapshot.json deleted file mode 100644 index 32b2cfc..0000000 --- a/apps/desktop/drizzle/20260516080547_short_firebird/snapshot.json +++ /dev/null @@ -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": [] -} \ No newline at end of file diff --git a/apps/desktop/electrobun.config.ts b/apps/desktop/electrobun.config.ts index f958f7f..065c4cd 100644 --- a/apps/desktop/electrobun.config.ts +++ b/apps/desktop/electrobun.config.ts @@ -12,7 +12,6 @@ export default { 'dist/index.html': 'views/mainview/index.html', 'dist/assets': 'views/mainview/assets', 'dist/icons': 'views/mainview/icons', - 'drizzle': 'drizzle', 'src/bun/native/biometric-helper': 'native/biometric-helper', }, // Ignore Vite output in watch mode — HMR handles view rebuilds separately diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 2afe4d2..b759a14 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -11,23 +11,19 @@ "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: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" }, "dependencies": { - "@alchemy/common": "^5.0.0", - "@alchemy/wallet-apis": "^5.0.0", "@koins/utils": "workspace:*", "d3-scale": "^4.0.2", "d3-shape": "^3.2.0", - "drizzle-orm": "^1.0.0-beta.22", "electrobun": "^1.18.1", + "env-paths": "^4.0.0", "mode-watcher": "^1.1.0", "monero-ts": "^0.11.8", "qrcode": "^1.5.4", "sv-router": "latest", - "viem": "^2.48.11" + "xrpl": "^5.0.0" }, "devDependencies": { "@fontsource-variable/geist-mono": "^5.2.7", @@ -45,7 +41,6 @@ "bun-types": "^1.3.14", "clsx": "^2.1.1", "concurrently": "^9.2.1", - "drizzle-kit": "^1.0.0-beta.22", "embla-carousel-svelte": "^8.6.0", "formsnap": "^2.0.1", "layerchart": "2.0.0-next.48", diff --git a/apps/desktop/public/icons/binance.png b/apps/desktop/public/icons/binance.png deleted file mode 100644 index 27688c0af1e64c74bf84e6df27136597cc245070..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17163 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 Ef(=$VfdBvi diff --git a/apps/desktop/public/icons/ethereum.png b/apps/desktop/public/icons/ethereum.png deleted file mode 100644 index bf957d38c766d5e73df222d2f55eff5c8d00fac0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2984 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 Ca`wOg diff --git a/apps/desktop/public/icons/polygon.png b/apps/desktop/public/icons/polygon.png deleted file mode 100644 index 6d3c0e9b20fc8d348214c7eed81881c331a8c56b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10248 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 diff --git a/apps/desktop/public/icons/xrp.png b/apps/desktop/public/icons/xrp.png new file mode 100644 index 0000000000000000000000000000000000000000..89ca2727e9a9b8bbd81c4462656236182226b721 GIT binary patch literal 7004 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 literal 0 HcmV?d00001 diff --git a/apps/desktop/src/bun/index.ts b/apps/desktop/src/bun/index.ts index a3cc018..752e8be 100644 --- a/apps/desktop/src/bun/index.ts +++ b/apps/desktop/src/bun/index.ts @@ -6,36 +6,21 @@ import { Updater, Utils, } from 'electrobun/bun' -import { migrate } from 'drizzle-orm/bun-sqlite/migrator' -import { db } from './lib/db' import { join } from 'path' import { existsSync } from 'fs' import { getENV } from './lib/get-env' -import { createEvmHandlers } from './rpc/evm' import { createMoneroHandlers } from './rpc/monero' +import { createXrpHandlers } from './rpc/xrp' import type { MoneroWalletState } from './lib/monero' import { canPromptTouchID, promptTouchID } from './lib/biometric' -import { sqlite } from './lib/sqlite' -import { evmWallets } from './lib/db/schema' +import { getXrpWalletList } from './lib/xrp' import type { RPC } from '../lib/rpc-schema' -import { log, logError } from './lib/logger' +import { log } from './lib/logger' const env = getENV() 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_URL = `http://localhost:${DEV_SERVER_PORT}` @@ -92,10 +77,6 @@ const moneroState = { manager: null as MoneroWalletState | null, downloading: false, } -const syncState = { - target: null as { address: string; chainid: string } | null, - timer: null as ReturnType | null, -} const rpc = BrowserView.defineRPC({ maxRequestTime: 120000, @@ -108,11 +89,11 @@ const rpc = BrowserView.defineRPC({ rpc.setRequestHandler({ resetApp: async () => { try { - const wallets = db.select().from(evmWallets).all() + const wallets = await getXrpWalletList() await Promise.all([ Bun.secrets.delete({ service: 'koins', - name: 'alchemy_key', + name: 'xrp_wallets', }), ...wallets.flatMap((w) => [ Bun.secrets.delete({ @@ -121,15 +102,10 @@ rpc.setRequestHandler({ }), Bun.secrets.delete({ 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') return true } catch (e) { @@ -174,8 +150,8 @@ rpc.setRequestHandler({ logToFile: async ({ message }) => { log(message) }, - ...createEvmHandlers(rpc, syncState), ...createMoneroHandlers(moneroState), + ...createXrpHandlers(), }) log('creating BrowserWindow...') diff --git a/apps/desktop/src/bun/lib/alchemy/call.ts b/apps/desktop/src/bun/lib/alchemy/call.ts deleted file mode 100644 index 2751ad3..0000000 --- a/apps/desktop/src/bun/lib/alchemy/call.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { tryCatch } from '@koins/utils' - -const TIMEOUT = 15_000 - -export const post = async ( - 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 -} diff --git a/apps/desktop/src/bun/lib/alchemy/get-token-balances.ts b/apps/desktop/src/bun/lib/alchemy/get-token-balances.ts deleted file mode 100644 index 6e33f46..0000000 --- a/apps/desktop/src/bun/lib/alchemy/get-token-balances.ts +++ /dev/null @@ -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 -} diff --git a/apps/desktop/src/bun/lib/alchemy/get-token-metadata.ts b/apps/desktop/src/bun/lib/alchemy/get-token-metadata.ts deleted file mode 100644 index 1f04f18..0000000 --- a/apps/desktop/src/bun/lib/alchemy/get-token-metadata.ts +++ /dev/null @@ -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], - }) -} diff --git a/apps/desktop/src/bun/lib/alchemy/get-transaction-history.ts b/apps/desktop/src/bun/lib/alchemy/get-transaction-history.ts deleted file mode 100644 index 1696e77..0000000 --- a/apps/desktop/src/bun/lib/alchemy/get-transaction-history.ts +++ /dev/null @@ -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(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() - 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 -} diff --git a/apps/desktop/src/bun/lib/alchemy/index.ts b/apps/desktop/src/bun/lib/alchemy/index.ts deleted file mode 100644 index d8c5a17..0000000 --- a/apps/desktop/src/bun/lib/alchemy/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './get-token-balances' -export * from './get-token-metadata' -export * from './types' diff --git a/apps/desktop/src/bun/lib/alchemy/network.ts b/apps/desktop/src/bun/lib/alchemy/network.ts deleted file mode 100644 index f2ac99e..0000000 --- a/apps/desktop/src/bun/lib/alchemy/network.ts +++ /dev/null @@ -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 - -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 -} diff --git a/apps/desktop/src/bun/lib/alchemy/types.ts b/apps/desktop/src/bun/lib/alchemy/types.ts deleted file mode 100644 index 9ff9ec8..0000000 --- a/apps/desktop/src/bun/lib/alchemy/types.ts +++ /dev/null @@ -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 - } -} diff --git a/apps/desktop/src/bun/lib/db/index.ts b/apps/desktop/src/bun/lib/db/index.ts deleted file mode 100644 index d595d89..0000000 --- a/apps/desktop/src/bun/lib/db/index.ts +++ /dev/null @@ -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 }) diff --git a/apps/desktop/src/bun/lib/db/relations.ts b/apps/desktop/src/bun/lib/db/relations.ts deleted file mode 100644 index ae8b602..0000000 --- a/apps/desktop/src/bun/lib/db/relations.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { defineRelations } from 'drizzle-orm' -import * as schema from './schema' - -export const relations = defineRelations(schema, () => ({})) diff --git a/apps/desktop/src/bun/lib/db/schema.ts b/apps/desktop/src/bun/lib/db/schema.ts deleted file mode 100644 index ee2210a..0000000 --- a/apps/desktop/src/bun/lib/db/schema.ts +++ /dev/null @@ -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), - }), -) diff --git a/apps/desktop/src/bun/lib/sqlite/index.ts b/apps/desktop/src/bun/lib/sqlite/index.ts deleted file mode 100644 index e1b3510..0000000 --- a/apps/desktop/src/bun/lib/sqlite/index.ts +++ /dev/null @@ -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, -}) diff --git a/apps/desktop/src/bun/lib/sync/index.ts b/apps/desktop/src/bun/lib/sync/index.ts deleted file mode 100644 index b06499a..0000000 --- a/apps/desktop/src/bun/lib/sync/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { syncTransactionHistory } from './sync-tx-history' \ No newline at end of file diff --git a/apps/desktop/src/bun/lib/sync/sync-tx-details.ts b/apps/desktop/src/bun/lib/sync/sync-tx-details.ts deleted file mode 100644 index 600c787..0000000 --- a/apps/desktop/src/bun/lib/sync/sync-tx-details.ts +++ /dev/null @@ -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) - } -} diff --git a/apps/desktop/src/bun/lib/sync/sync-tx-history.ts b/apps/desktop/src/bun/lib/sync/sync-tx-history.ts deleted file mode 100644 index 93db22d..0000000 --- a/apps/desktop/src/bun/lib/sync/sync-tx-history.ts +++ /dev/null @@ -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 => { - 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 = [] - 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) { - 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) - } - } -} diff --git a/apps/desktop/src/bun/lib/tokens/get-metadata.ts b/apps/desktop/src/bun/lib/tokens/get-metadata.ts deleted file mode 100644 index cd7f310..0000000 --- a/apps/desktop/src/bun/lib/tokens/get-metadata.ts +++ /dev/null @@ -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 -} diff --git a/apps/desktop/src/bun/lib/tokens/index.ts b/apps/desktop/src/bun/lib/tokens/index.ts deleted file mode 100644 index 778f742..0000000 --- a/apps/desktop/src/bun/lib/tokens/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { getTokenMetadata } from './get-metadata' diff --git a/apps/desktop/src/bun/lib/transactions/get-details.ts b/apps/desktop/src/bun/lib/transactions/get-details.ts deleted file mode 100644 index 29df5c3..0000000 --- a/apps/desktop/src/bun/lib/transactions/get-details.ts +++ /dev/null @@ -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 -} diff --git a/apps/desktop/src/bun/lib/transactions/get-history.ts b/apps/desktop/src/bun/lib/transactions/get-history.ts deleted file mode 100644 index a7a3b57..0000000 --- a/apps/desktop/src/bun/lib/transactions/get-history.ts +++ /dev/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, - } -} diff --git a/apps/desktop/src/bun/lib/transactions/index.ts b/apps/desktop/src/bun/lib/transactions/index.ts deleted file mode 100644 index 422254f..0000000 --- a/apps/desktop/src/bun/lib/transactions/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { - getTransactionHistory, - getCachedTransactionHistory, -} from './get-history' -export { getTransactionDetails } from './get-details' diff --git a/apps/desktop/src/bun/lib/viem/get-transaction-details.ts b/apps/desktop/src/bun/lib/viem/get-transaction-details.ts deleted file mode 100644 index 78812f6..0000000 --- a/apps/desktop/src/bun/lib/viem/get-transaction-details.ts +++ /dev/null @@ -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, - }, - } -} diff --git a/apps/desktop/src/bun/lib/viem/getChain.ts b/apps/desktop/src/bun/lib/viem/getChain.ts deleted file mode 100644 index 4b44314..0000000 --- a/apps/desktop/src/bun/lib/viem/getChain.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { bsc, mainnet, polygon, type Chain } from 'viem/chains' -import type { ChainID } from '../alchemy/network' - -const chains: Record = { - '1': mainnet, - '56': bsc, - '137': polygon, -} - -export const getChain = (id: ChainID) => { - return chains[id] -} diff --git a/apps/desktop/src/bun/lib/viem/getClient.ts b/apps/desktop/src/bun/lib/viem/getClient.ts deleted file mode 100644 index 81bf22b..0000000 --- a/apps/desktop/src/bun/lib/viem/getClient.ts +++ /dev/null @@ -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 -} diff --git a/apps/desktop/src/bun/lib/viem/index.ts b/apps/desktop/src/bun/lib/viem/index.ts deleted file mode 100644 index 202fb9a..0000000 --- a/apps/desktop/src/bun/lib/viem/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './getClient' -export { getTransactionDetails } from './get-transaction-details' diff --git a/apps/desktop/src/bun/lib/xrp/index.ts b/apps/desktop/src/bun/lib/xrp/index.ts new file mode 100644 index 0000000..59e3ee1 --- /dev/null +++ b/apps/desktop/src/bun/lib/xrp/index.ts @@ -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 { + 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 { + 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 { + await Bun.secrets.set({ + service: SERVICE, + name: WALLET_LIST_KEY, + value: JSON.stringify(wallets), + }) +} + +export async function getBalance(address: string): Promise { + 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 { + 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 { + 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 + } +} diff --git a/apps/desktop/src/bun/rpc/evm/index.ts b/apps/desktop/src/bun/rpc/evm/index.ts deleted file mode 100644 index ebbad09..0000000 --- a/apps/desktop/src/bun/rpc/evm/index.ts +++ /dev/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 | null - }, -) { - const ALCHEMY_NETWORKS: Record = { - '1': 'eth-mainnet', - '137': 'polygon-mainnet', - '56': 'bnb-mainnet', - } - - const NATIVE_SYMBOLS: Record = { - '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 { - 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 { - const addrLower = address.toLowerCase() - const byHash = new Map() - 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() - 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() - 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 - }, - } -} diff --git a/apps/desktop/src/bun/rpc/xrp/index.ts b/apps/desktop/src/bun/rpc/xrp/index.ts new file mode 100644 index 0000000..4dcbcb4 --- /dev/null +++ b/apps/desktop/src/bun/rpc/xrp/index.ts @@ -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 + } + }, + } +} diff --git a/apps/desktop/src/lib/components/EvmDashboard.svelte b/apps/desktop/src/lib/components/EvmDashboard.svelte deleted file mode 100644 index 32afe3c..0000000 --- a/apps/desktop/src/lib/components/EvmDashboard.svelte +++ /dev/null @@ -1,799 +0,0 @@ - - -{#if !w.ready} -
- Loading... -
-{:else if w.wallets.length === 0 || showCreateForm} - - - - {#if showCreateForm} - {@const cur = w.currentWallet} - {cur ? `Add Another Wallet` : `Import / Create Wallet`} - {:else} - Import / Create Wallet - {/if} - - - Name your wallet and enter your seed phrase to save it - securely, or generate a new wallet - - - -
- -