hd-wallet-ui 2.0.20 → 2.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +101 -123
  3. package/data/common-passwords-sdn-v1.source.json +12 -0
  4. package/data/common-passwords-sdn-v1.txt +10000 -0
  5. package/dist/browser/sdn-wallet-callback.js +1 -0
  6. package/dist/browser/sdn-wallet-public-client.js +1 -0
  7. package/dist/browser/wallet-callback.html +6 -0
  8. package/dist/client/asset-review.d.ts +8 -0
  9. package/dist/client/asset-review.js +1570 -0
  10. package/dist/client/callback.d.ts +13 -0
  11. package/dist/client/callback.js +29 -0
  12. package/dist/client/index.d.ts +11 -0
  13. package/dist/client/index.js +1379 -0
  14. package/dist/client/sdn.d.ts +8 -0
  15. package/dist/client/sdn.js +1542 -0
  16. package/dist/client/style.css +32 -0
  17. package/dist/client/types.d.ts +278 -0
  18. package/dist/compat/index.d.ts +26 -0
  19. package/dist/compat/index.js +62 -0
  20. package/dist/wallet-origin/index.d.ts +21 -0
  21. package/dist/wallet-origin/index.js +4607 -0
  22. package/dist/wallet-origin-host/assets/wallet-origin.3d16729bf9aaca4517cc699bec526c7209be184cdbb0ba0dbf4063d36a091e1d.js +12463 -0
  23. package/dist/wallet-origin-host/assets/wallet-origin.e22071a3643a57055d29f2e6951c1a2f15e77c1b58b0f81bb7f2afed5bcfb0b4.wasm +0 -0
  24. package/dist/wallet-origin-host/assets/wallet-origin.f3273f7ea70bb494cda1fc02264b936f2e9b010dc07df15eed05fa3f7d1356de.css +1 -0
  25. package/dist/wallet-origin-host/index.html +10 -0
  26. package/dist/wallet-origin-host/integrity.json +1 -0
  27. package/package.json +46 -18
  28. package/src/address-derivation.js +0 -514
  29. package/src/app.js +0 -6008
  30. package/src/blockchain-trust.js +0 -752
  31. package/src/clipboard.js +0 -44
  32. package/src/constants.js +0 -66
  33. package/src/hash.js +0 -9
  34. package/src/lib.js +0 -66
  35. package/src/template.js +0 -626
  36. package/src/trust-ui.js +0 -745
  37. package/src/wallet-storage.js +0 -773
  38. package/styles/main.css +0 -6220
  39. package/styles/widget.css +0 -6212
@@ -0,0 +1 @@
1
+ :root{--sdn-wallet-origin-style-ready: "2.0.26";color-scheme:dark;font-family:system-ui,sans-serif;background:#07101f;color:#f5f7ff}*{box-sizing:border-box}body{min-height:100vh;margin:0;display:grid;place-items:center;background:radial-gradient(circle at top,#14294d,#07101f 62%)}[data-wallet-origin-root]{width:min(40rem,calc(100vw - 2rem));padding:1.5rem;border:1px solid #36527d;border-radius:1rem;background:#0d1a2ee8;box-shadow:0 1.5rem 4rem #0008}form,section{display:grid;gap:.9rem}label{display:grid;gap:.35rem}input,textarea,button{font:inherit}input,textarea{width:100%;padding:.75rem;border:1px solid #58739d;border-radius:.55rem;background:#081426;color:inherit}button{padding:.7rem 1rem;border:0;border-radius:.55rem;background:#6ca5ff;color:#06101f;font-weight:700;cursor:pointer}button[disabled]{opacity:.55;cursor:default}button:focus-visible,input:focus-visible,textarea:focus-visible{outline:.2rem solid #b9d5ff;outline-offset:.15rem}.wallet-error{color:#ffd4d4}.wallet-confirmation,.wallet-confirmation-row{min-width:0;max-width:100%;overflow-wrap:anywhere}@media (max-width: 320px){[data-wallet-origin-root]{width:100%;padding:1rem;border-radius:0}body{display:block}}@media (prefers-reduced-motion: reduce){*,*:before,*:after{scroll-behavior:auto!important;transition:none!important;animation:none!important}}
@@ -0,0 +1,10 @@
1
+ <!doctype html>
2
+ <html lang="en"><head><meta charset="utf-8">
3
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; img-src 'self' blob:; connect-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'; worker-src 'none'; manifest-src 'self'">
4
+ <meta name="referrer" content="no-referrer">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>SDN Wallet</title>
7
+ <link rel="stylesheet" href="/assets/wallet-origin.f3273f7ea70bb494cda1fc02264b936f2e9b010dc07df15eed05fa3f7d1356de.css" integrity="sha384-b+LH0CYqNk9YJOdDbL16KJJA8vtBLHRlXKr3jaNrHXhiiCo5WAv2Ee701M5w+Ijt" crossorigin="anonymous">
8
+ </head><body><main data-wallet-origin-root aria-live="polite"></main>
9
+ <script type="module" src="/assets/wallet-origin.3d16729bf9aaca4517cc699bec526c7209be184cdbb0ba0dbf4063d36a091e1d.js" integrity="sha384-FYCLfOYfFtVEj1tp7JTQc78CHjRUGou2aTkgSPbHQhekToHe2XXgL6zujqM3HVok" crossorigin="anonymous"></script>
10
+ </body></html>
@@ -0,0 +1 @@
1
+ {"files":{"assets/wallet-origin.f3273f7ea70bb494cda1fc02264b936f2e9b010dc07df15eed05fa3f7d1356de.css":{"bytes":1332,"sha256":"f3273f7ea70bb494cda1fc02264b936f2e9b010dc07df15eed05fa3f7d1356de","sha384":"sha384-b+LH0CYqNk9YJOdDbL16KJJA8vtBLHRlXKr3jaNrHXhiiCo5WAv2Ee701M5w+Ijt"},"assets/wallet-origin.3d16729bf9aaca4517cc699bec526c7209be184cdbb0ba0dbf4063d36a091e1d.js":{"bytes":441483,"sha256":"3d16729bf9aaca4517cc699bec526c7209be184cdbb0ba0dbf4063d36a091e1d","sha384":"sha384-FYCLfOYfFtVEj1tp7JTQc78CHjRUGou2aTkgSPbHQhekToHe2XXgL6zujqM3HVok"},"assets/wallet-origin.e22071a3643a57055d29f2e6951c1a2f15e77c1b58b0f81bb7f2afed5bcfb0b4.wasm":{"bytes":3839340,"sha256":"e22071a3643a57055d29f2e6951c1a2f15e77c1b58b0f81bb7f2afed5bcfb0b4","sha384":"sha384-ynfCFG+8LkI0uPZDU8wJwhoxHNjiK/FVAih+xb27pBFDyMYWj0TReucoEmXaqn8c"},"index.html":{"bytes":1059,"sha256":"0824c377eb3753254cafc45c2f7bcd89ee74aa2a97caeba514c92b48760ffb9a","sha384":"sha384-IYVOvtc8OI+VKA2KpUqgqrAgWAeljoFX7IJ+AdxS5ywR5D6bLd2MM4Bgohk0ZMvN"}},"schemaVersion":1}
package/package.json CHANGED
@@ -1,22 +1,47 @@
1
1
  {
2
2
  "name": "hd-wallet-ui",
3
- "version": "2.0.20",
3
+ "version": "2.0.26",
4
4
  "description": "HD Wallet modal UI — login, keys, identity, trust map, and security bond. Attach to any button in your app.",
5
5
  "type": "module",
6
- "main": "src/app.js",
7
- "module": "src/app.js",
6
+ "main": "./dist/compat/index.js",
7
+ "module": "./dist/compat/index.js",
8
+ "types": "./dist/compat/index.d.ts",
8
9
  "exports": {
9
- ".": "./src/app.js",
10
- "./lib": "./src/lib.js",
11
- "./styles": "./styles/widget.css",
12
- "./styles/demo": "./styles/main.css"
10
+ ".": {
11
+ "types": "./dist/compat/index.d.ts",
12
+ "import": "./dist/compat/index.js"
13
+ },
14
+ "./client": {
15
+ "types": "./dist/client/index.d.ts",
16
+ "import": "./dist/client/index.js"
17
+ },
18
+ "./client/asset-review": {
19
+ "types": "./dist/client/asset-review.d.ts",
20
+ "import": "./dist/client/asset-review.js"
21
+ },
22
+ "./client/callback": {
23
+ "types": "./dist/client/callback.d.ts",
24
+ "import": "./dist/client/callback.js"
25
+ },
26
+ "./client/sdn": {
27
+ "types": "./dist/client/sdn.d.ts",
28
+ "import": "./dist/client/sdn.js"
29
+ },
30
+ "./styles": "./dist/client/style.css",
31
+ "./wallet-origin": {
32
+ "types": "./dist/wallet-origin/index.d.ts",
33
+ "import": "./dist/wallet-origin/index.js"
34
+ }
13
35
  },
14
36
  "files": [
15
- "src/",
16
- "styles/"
37
+ "dist/",
38
+ "README.md",
39
+ "LICENSE",
40
+ "data/common-passwords-sdn-v1.txt",
41
+ "data/common-passwords-sdn-v1.source.json"
17
42
  ],
18
43
  "sideEffects": [
19
- "**/*.css"
44
+ "./dist/client/style.css"
20
45
  ],
21
46
  "scripts": {
22
47
  "build:wasm": "emcmake cmake -B ../build-wasm -S .. -DCMAKE_BUILD_TYPE=Release -DHD_WALLET_BUILD_WASM=ON && cmake --build ../build-wasm --target hd_wallet_wasm_npm -j8",
@@ -26,26 +51,28 @@
26
51
  "build": "npm run build:wasm && vite build",
27
52
  "build:docs": "npm run build:wasm && vite build --outDir ../docs --emptyOutDir",
28
53
  "preview": "vite preview",
29
- "prepack": "npm run build:widget-css",
30
- "test": "vitest run",
31
- "test:watch": "vitest"
54
+ "build:release": "node scripts/build-release.mjs",
55
+ "test": "vitest run --no-file-parallelism",
56
+ "test:watch": "vitest",
57
+ "test:browser": "playwright test --config test/browser/playwright.config.mjs"
32
58
  },
33
59
  "dependencies": {
60
+ "hd-wallet-wasm": "2.0.26"
61
+ },
62
+ "devDependencies": {
34
63
  "@noble/curves": "^1.9.7",
35
64
  "@noble/hashes": "^1.7.2",
36
65
  "@peculiar/x509": "^1.14.3",
66
+ "@playwright/test": "1.61.1",
37
67
  "@scure/base": "^1.2.4",
38
68
  "@scure/bip32": "^2.0.1",
39
69
  "bip39": "^3.1.0",
40
70
  "buffer": "^6.0.3",
41
71
  "flatbuffers": "^25.9.23",
42
72
  "flatc-wasm": "^26.1.32",
43
- "hd-wallet-wasm": "^2.0.20",
44
73
  "qrcode": "^1.5.3",
45
74
  "spacedatastandards.org": "^1.93.3",
46
- "vcard-cryptoperson": "^1.1.11"
47
- },
48
- "devDependencies": {
75
+ "vcard-cryptoperson": "^1.1.11",
49
76
  "vite": "^5.0.0",
50
77
  "vitest": "^4.0.18"
51
78
  },
@@ -70,5 +97,6 @@
70
97
  "bugs": {
71
98
  "url": "https://github.com/DigitalArsenal/hd-wallet-wasm/issues"
72
99
  },
73
- "homepage": "https://digitalarsenal.github.io/hd-wallet-wasm/"
100
+ "homepage": "https://digitalarsenal.github.io/hd-wallet-wasm/",
101
+ "gitHead": "428d8d54a2beac98d65ae33678b3adb972e35fd4"
74
102
  }
@@ -1,514 +0,0 @@
1
- /**
2
- * Address Derivation Module
3
- *
4
- * Functions for generating blockchain addresses from public keys:
5
- * BTC, ETH, SOL, SUI, Monad, Cardano
6
- */
7
-
8
- import { secp256k1 } from '@noble/curves/secp256k1';
9
- import { blake2b } from '@noble/hashes/blake2b';
10
- import { keccak_256 } from '@noble/hashes/sha3';
11
- import { ripemd160 } from '@noble/hashes/ripemd160';
12
- import { sha256 as sha256Noble } from '@noble/hashes/sha256';
13
- import { base58check, base58 } from '@scure/base';
14
-
15
- import { coinTypeToConfig } from './constants.js';
16
-
17
- // =============================================================================
18
- // API Proxy (dev mode CORS workaround)
19
- // =============================================================================
20
-
21
- const isDev = import.meta.env?.DEV ?? false;
22
-
23
- const proxyMap = {
24
- 'https://blockchain.info': '/api/blockchain',
25
- 'https://blockstream.info': '/api/blockstream',
26
- 'https://cloudflare-eth.com': '/api/eth',
27
- 'https://api.mainnet-beta.solana.com': '/api/solana/official',
28
- 'https://solana-rpc.publicnode.com': '/api/solana/publicnode',
29
- 'https://mainnet.helius-rpc.com': '/api/solana/helius',
30
- /* Commented out — BTC/ETH/SOL only for now
31
- 'https://fullnode.mainnet.sui.io:443': '/api/sui',
32
- 'https://testnet-rpc.monad.xyz': '/api/monad',
33
- 'https://api.koios.rest': '/api/koios',
34
- 'https://s1.ripple.com:51234': '/api/xrp',
35
- */
36
- 'https://api.coinbase.com': '/api/coinbase',
37
- 'https://api.hiro.so': '/api/hiro',
38
- };
39
-
40
- export function apiUrl(url) {
41
- if (!isDev) return url;
42
- for (const [origin, proxy] of Object.entries(proxyMap)) {
43
- if (url.startsWith(origin)) {
44
- return url.replace(origin, proxy);
45
- }
46
- }
47
- return url;
48
- }
49
-
50
- // =============================================================================
51
- // Utility Helpers
52
- // =============================================================================
53
-
54
- /**
55
- * Convert a Uint8Array to a compact hex string (no spaces)
56
- * @param {Uint8Array} bytes
57
- * @returns {string}
58
- */
59
- export function toHexCompact(bytes) {
60
- return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
61
- }
62
-
63
- /**
64
- * Alias for toHexCompact
65
- */
66
- export function toHex(bytes) {
67
- return toHexCompact(bytes);
68
- }
69
-
70
- /**
71
- * Convert hex string to Uint8Array
72
- * @param {string} hex
73
- * @returns {Uint8Array}
74
- */
75
- export function hexToBytes(hex) {
76
- const bytes = new Uint8Array(hex.length / 2);
77
- for (let i = 0; i < hex.length; i += 2) {
78
- bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
79
- }
80
- return bytes;
81
- }
82
-
83
- /**
84
- * Ensure a value is a Uint8Array.
85
- * Handles localStorage deserialization which produces plain objects.
86
- * @param {*} value
87
- * @returns {Uint8Array}
88
- */
89
- export function ensureUint8Array(value) {
90
- if (value instanceof Uint8Array) {
91
- return value;
92
- }
93
- if (typeof value === 'object' && value !== null) {
94
- return new Uint8Array(Object.values(value));
95
- }
96
- return new Uint8Array(value);
97
- }
98
-
99
- // =============================================================================
100
- // Base58Check encoder for Bitcoin
101
- // =============================================================================
102
-
103
- const base58checkBtc = base58check(sha256Noble);
104
-
105
- // =============================================================================
106
- // Address Generation Functions
107
- // =============================================================================
108
-
109
- /**
110
- * Generate a Bitcoin P2PKH address from a compressed secp256k1 public key
111
- * Uses @scure/base for proper Base58Check encoding
112
- * @param {Uint8Array} publicKey - Compressed secp256k1 public key (33 bytes)
113
- * @returns {string} Bitcoin address starting with '1'
114
- */
115
- export function generateBtcAddress(publicKey) {
116
- const hash160 = ripemd160(sha256Noble(publicKey));
117
- return base58checkBtc.encode(new Uint8Array([0x00, ...hash160]));
118
- }
119
-
120
- /**
121
- * Generate an Ethereum address from a secp256k1 public key
122
- * Uses @noble/hashes keccak_256 for proper Ethereum address derivation
123
- * @param {Uint8Array} publicKey - Compressed secp256k1 public key (33 bytes)
124
- * @returns {string} Ethereum address with 0x prefix
125
- */
126
- export function generateEthAddress(publicKey) {
127
- const point = secp256k1.ProjectivePoint.fromHex(publicKey);
128
- const uncompressed = point.toRawBytes(false); // 65 bytes: 04 || x || y
129
- const hash = keccak_256(uncompressed.slice(1));
130
- return '0x' + toHexCompact(hash.slice(-20));
131
- }
132
-
133
- /**
134
- * Generate a Solana address from an Ed25519 public key
135
- * Uses @scure/base for proper Base58 encoding
136
- * @param {Uint8Array} publicKey - Ed25519 public key (32 bytes)
137
- * @returns {string} Solana address
138
- */
139
- export function generateSolAddress(publicKey) {
140
- return base58.encode(publicKey);
141
- }
142
-
143
- /**
144
- * Generate an XRP address from a compressed secp256k1 public key
145
- * Uses SHA-256 → RIPEMD-160 hash, then Base58Check with version byte 0x00
146
- * @param {Uint8Array} publicKey - Compressed secp256k1 public key (33 bytes)
147
- * @returns {string} XRP address starting with 'r'
148
- */
149
- export function generateXrpAddress(publicKey) {
150
- const hash160 = ripemd160(sha256Noble(publicKey));
151
- // XRP uses the same Base58Check as BTC but the alphabet produces 'r' prefix
152
- return base58checkBtc.encode(new Uint8Array([0x00, ...hash160]));
153
- }
154
-
155
- /**
156
- * Derive an Ethereum/Monad-compatible address from a secp256k1 public key.
157
- * Handles compressed (33 bytes), uncompressed (65 bytes), and raw (64 bytes) formats.
158
- * @param {Uint8Array} publicKey
159
- * @returns {string|null} Ethereum address with 0x prefix, or null on failure
160
- */
161
- export function deriveEthAddress(publicKey) {
162
- try {
163
- if (publicKey.length === 33) {
164
- const point = secp256k1.ProjectivePoint.fromHex(publicKey);
165
- const uncompressed = point.toRawBytes(false).slice(1);
166
- const hash = keccak_256(uncompressed);
167
- return '0x' + toHex(hash.slice(-20));
168
- }
169
- if (publicKey.length === 65) {
170
- const hash = keccak_256(publicKey.slice(1));
171
- return '0x' + toHex(hash.slice(-20));
172
- }
173
- if (publicKey.length === 64) {
174
- const hash = keccak_256(publicKey);
175
- return '0x' + toHex(hash.slice(-20));
176
- }
177
- return null;
178
- } catch (e) {
179
- return null;
180
- }
181
- }
182
-
183
- /**
184
- * Derive a SUI address from a public key using BLAKE2b
185
- * @param {Uint8Array} publicKey - Public key bytes
186
- * @param {string} scheme - Key scheme: 'ed25519', 'secp256k1', or 'secp256r1'
187
- * @returns {string} SUI address with 0x prefix
188
- */
189
- export function deriveSuiAddress(publicKey, scheme = 'ed25519') {
190
- const schemeFlags = {
191
- 'ed25519': 0x00,
192
- 'secp256k1': 0x01,
193
- 'secp256r1': 0x02,
194
- };
195
- const flag = schemeFlags[scheme] ?? 0x00;
196
-
197
- const data = new Uint8Array(1 + publicKey.length);
198
- data[0] = flag;
199
- data.set(publicKey, 1);
200
-
201
- const hash = blake2b(data, { dkLen: 32 });
202
- return '0x' + Array.from(hash).map(b => b.toString(16).padStart(2, '0')).join('');
203
- }
204
-
205
- /**
206
- * Derive a Monad address from a secp256k1 public key (same as Ethereum derivation)
207
- * @param {Uint8Array} publicKey - secp256k1 public key (33 or 65 bytes)
208
- * @returns {string} Monad address with 0x prefix
209
- */
210
- export function deriveMonadAddress(publicKey) {
211
- let uncompressedPubKey;
212
- if (publicKey.length === 33) {
213
- const point = secp256k1.ProjectivePoint.fromHex(publicKey);
214
- uncompressedPubKey = point.toRawBytes(false);
215
- } else if (publicKey.length === 65) {
216
- uncompressedPubKey = publicKey;
217
- } else {
218
- throw new Error('Invalid public key length for Monad address derivation');
219
- }
220
-
221
- const hash = keccak_256(uncompressedPubKey.slice(1));
222
- const address = hash.slice(-20);
223
- return '0x' + Array.from(address).map(b => b.toString(16).padStart(2, '0')).join('');
224
- }
225
-
226
- /**
227
- * Derive a Cardano enterprise address from an Ed25519 public key
228
- * Uses Bech32 encoding with "addr" prefix for mainnet
229
- * @param {Uint8Array} publicKey - Ed25519 public key (32 bytes)
230
- * @returns {string} Cardano address in Bech32 format
231
- */
232
- export function deriveCardanoAddress(publicKey) {
233
- const keyHash = blake2b(publicKey, { dkLen: 28 }); // 224-bit hash
234
- const addressBytes = new Uint8Array(29);
235
- addressBytes[0] = 0x61; // Enterprise address, mainnet
236
- addressBytes.set(keyHash, 1);
237
- return bech32Encode('addr', addressBytes);
238
- }
239
-
240
- // =============================================================================
241
- // Bech32 Encoding (for Cardano)
242
- // =============================================================================
243
-
244
- function bech32Encode(prefix, data) {
245
- const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
246
-
247
- const data5bit = convertBits(data, 8, 5, true);
248
- const checksumData = expandHrp(prefix).concat(data5bit).concat([0, 0, 0, 0, 0, 0]);
249
- const polymod = bech32Polymod(checksumData) ^ 1;
250
- const checksum = [];
251
- for (let i = 0; i < 6; i++) {
252
- checksum.push((polymod >> (5 * (5 - i))) & 31);
253
- }
254
-
255
- let result = prefix + '1';
256
- for (const d of data5bit.concat(checksum)) {
257
- result += CHARSET[d];
258
- }
259
- return result;
260
- }
261
-
262
- function convertBits(data, fromBits, toBits, pad) {
263
- let acc = 0;
264
- let bits = 0;
265
- const ret = [];
266
- const maxv = (1 << toBits) - 1;
267
- for (const value of data) {
268
- acc = (acc << fromBits) | value;
269
- bits += fromBits;
270
- while (bits >= toBits) {
271
- bits -= toBits;
272
- ret.push((acc >> bits) & maxv);
273
- }
274
- }
275
- if (pad) {
276
- if (bits > 0) {
277
- ret.push((acc << (toBits - bits)) & maxv);
278
- }
279
- }
280
- return ret;
281
- }
282
-
283
- function expandHrp(hrp) {
284
- const ret = [];
285
- for (const c of hrp) {
286
- ret.push(c.charCodeAt(0) >> 5);
287
- }
288
- ret.push(0);
289
- for (const c of hrp) {
290
- ret.push(c.charCodeAt(0) & 31);
291
- }
292
- return ret;
293
- }
294
-
295
- function bech32Polymod(values) {
296
- const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
297
- let chk = 1;
298
- for (const v of values) {
299
- const b = chk >> 25;
300
- chk = ((chk & 0x1ffffff) << 5) ^ v;
301
- for (let i = 0; i < 5; i++) {
302
- if ((b >> i) & 1) {
303
- chk ^= GEN[i];
304
- }
305
- }
306
- }
307
- return chk;
308
- }
309
-
310
- // =============================================================================
311
- // Composite Address Generation
312
- // =============================================================================
313
-
314
- /**
315
- * Generate BTC, ETH, and SOL addresses from a wallet key set
316
- * @param {{ secp256k1: { publicKey: Uint8Array }, ed25519: { publicKey: Uint8Array } }} wallet
317
- * @returns {{ btc: string, eth: string, sol: string }}
318
- */
319
- export function generateAddresses(wallet) {
320
- return {
321
- btc: generateBtcAddress(wallet.secp256k1.publicKey),
322
- eth: generateEthAddress(wallet.secp256k1.publicKey),
323
- sol: generateSolAddress(wallet.ed25519.publicKey),
324
- // xrp: generateXrpAddress(wallet.secp256k1.publicKey), // Commented out — BTC/ETH/SOL only for now
325
- };
326
- }
327
-
328
- /**
329
- * Generate address from public key based on coin type for HD derivation
330
- * @param {Uint8Array} publicKey - The derived public key
331
- * @param {number} coinType - BIP44 coin type
332
- * @returns {string} The generated address
333
- */
334
- export function generateAddressForCoin(publicKey, coinType) {
335
- const config = coinTypeToConfig[coinType];
336
- if (!config) {
337
- return toHexCompact(publicKey);
338
- }
339
-
340
- switch (coinType) {
341
- case 0: // Bitcoin
342
- case 2: // Litecoin
343
- case 3: // Dogecoin
344
- case 145: // Bitcoin Cash
345
- return generateBtcAddress(publicKey);
346
-
347
- case 60: // Ethereum
348
- return generateEthAddress(publicKey);
349
-
350
- case 501: // Solana - uses ed25519, but we generate from secp256k1 for demo
351
- return base58.encode(publicKey.slice(0, 32));
352
-
353
- case 144: // XRP
354
- return generateXrpAddress(publicKey);
355
-
356
- case 118: // Cosmos
357
- case 330: // Algorand
358
- case 354: // Polkadot
359
- case 1815: { // Cardano
360
- const hash = sha256Noble(publicKey);
361
- return toHexCompact(hash.slice(0, 20));
362
- }
363
-
364
- default: {
365
- const defaultHash = sha256Noble(publicKey);
366
- return toHexCompact(defaultHash.slice(0, 20));
367
- }
368
- }
369
- }
370
-
371
- /**
372
- * Truncate an address for display
373
- * @param {string} address
374
- * @returns {string}
375
- */
376
- export function truncateAddress(address) {
377
- if (address.length <= 16) return address;
378
- return address.slice(0, 8) + '...' + address.slice(-6);
379
- }
380
-
381
- // =============================================================================
382
- // Balance Fetching
383
- // =============================================================================
384
-
385
- /**
386
- * Fetch Bitcoin balance
387
- * @param {string} address
388
- * @returns {Promise<{balance: string, error?: string}>}
389
- */
390
- export async function fetchBtcBalance(address) {
391
- let lastError = 'No available endpoint';
392
-
393
- // Primary endpoint: blockchain.info (fast/simple satoshi response)
394
- try {
395
- const response = await fetch(apiUrl(`https://blockchain.info/q/addressbalance/${address}?cors=true`));
396
- if (response.ok) {
397
- const satoshis = await response.text();
398
- const satoshisInt = parseInt(satoshis, 10);
399
- if (Number.isFinite(satoshisInt)) {
400
- return { balance: (satoshisInt / 1e8).toFixed(8) };
401
- }
402
- lastError = 'Invalid BTC balance response from blockchain.info';
403
- } else {
404
- lastError = `blockchain.info HTTP ${response.status}`;
405
- }
406
- } catch (e) {
407
- lastError = `blockchain.info ${e.message || 'request failed'}`;
408
- }
409
-
410
- // Fallback endpoint: blockstream.info (chain_stats + mempool_stats)
411
- try {
412
- const response = await fetch(apiUrl(`https://blockstream.info/api/address/${address}`));
413
- if (response.ok) {
414
- const data = await response.json();
415
- const chainFunded = BigInt(data?.chain_stats?.funded_txo_sum ?? 0);
416
- const chainSpent = BigInt(data?.chain_stats?.spent_txo_sum ?? 0);
417
- const mempoolFunded = BigInt(data?.mempool_stats?.funded_txo_sum ?? 0);
418
- const mempoolSpent = BigInt(data?.mempool_stats?.spent_txo_sum ?? 0);
419
- const satoshis = chainFunded - chainSpent + mempoolFunded - mempoolSpent;
420
- return { balance: (Number(satoshis) / 1e8).toFixed(8) };
421
- }
422
- lastError = `${lastError}; blockstream.info HTTP ${response.status}`;
423
- } catch (e) {
424
- lastError = `${lastError}; blockstream.info ${e.message || 'request failed'}`;
425
- }
426
-
427
- console.debug('BTC balance fetch unavailable:', lastError);
428
- return { balance: '--', error: lastError };
429
- }
430
-
431
- /**
432
- * Fetch Ethereum balance
433
- * @param {string} address
434
- * @returns {Promise<{balance: string, error?: string}>}
435
- */
436
- export async function fetchEthBalance(address) {
437
- try {
438
- const response = await fetch(apiUrl('https://cloudflare-eth.com'), {
439
- method: 'POST',
440
- headers: { 'Content-Type': 'application/json' },
441
- body: JSON.stringify({
442
- jsonrpc: '2.0',
443
- id: 1,
444
- method: 'eth_getBalance',
445
- params: [address, 'latest']
446
- })
447
- });
448
- if (!response.ok) {
449
- return { balance: '--', error: `HTTP ${response.status}` };
450
- }
451
- const data = await response.json();
452
- if (data.error) {
453
- return { balance: '--', error: data.error.message || 'ETH RPC error' };
454
- }
455
- const balanceWei = BigInt(data.result || '0x0');
456
- const balanceEth = Number(balanceWei) / 1e18;
457
- return { balance: balanceEth.toFixed(6) };
458
- } catch (e) {
459
- console.debug('ETH balance fetch unavailable:', e.message);
460
- return { balance: '--', error: e.message };
461
- }
462
- }
463
-
464
- /**
465
- * Fetch Solana balance
466
- * @param {string} address
467
- * @returns {Promise<{balance: string, error?: string}>}
468
- */
469
- export async function fetchSolBalance(address) {
470
- const endpoints = [
471
- 'https://solana-rpc.publicnode.com',
472
- 'https://mainnet.helius-rpc.com/?api-key=1d8740dc-e5f4-421c-b823-e1bad1889eda',
473
- 'https://api.mainnet-beta.solana.com',
474
- ];
475
- let lastError = 'No available endpoint';
476
-
477
- for (const endpoint of endpoints) {
478
- try {
479
- const response = await fetch(apiUrl(endpoint), {
480
- method: 'POST',
481
- headers: { 'Content-Type': 'application/json' },
482
- body: JSON.stringify({
483
- jsonrpc: '2.0',
484
- id: 1,
485
- method: 'getBalance',
486
- params: [address]
487
- })
488
- });
489
- if (!response.ok) {
490
- lastError = `HTTP ${response.status}`;
491
- continue;
492
- }
493
- const data = await response.json();
494
- if (data.error) {
495
- lastError = data.error.message || 'SOL RPC error';
496
- continue;
497
- }
498
- const lamports = data.result?.value || 0;
499
- const sol = lamports / 1e9;
500
- return { balance: sol.toFixed(6) };
501
- } catch (e) {
502
- lastError = e?.message || 'SOL RPC fetch error';
503
- continue;
504
- }
505
- }
506
- console.debug('SOL balance fetch unavailable: all endpoints failed');
507
- return { balance: '--', error: lastError };
508
- }
509
-
510
- // Commented out — BTC/ETH/SOL only for now
511
- // export async function fetchSuiBalance(address) { ... }
512
- // export async function fetchMonadBalance(address) { ... }
513
- // export async function fetchAdaBalance(address) { ... }
514
- // export async function fetchXrpBalance(address) { ... }