hd-wallet-ui 2.0.21 → 2.0.27
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.
- package/LICENSE +190 -0
- package/README.md +101 -123
- package/data/common-passwords-sdn-v1.source.json +12 -0
- package/data/common-passwords-sdn-v1.txt +10000 -0
- package/dist/browser/sdn-wallet-callback.js +1 -0
- package/dist/browser/sdn-wallet-public-client.js +1 -0
- package/dist/browser/wallet-callback.html +6 -0
- package/dist/client/asset-review.d.ts +8 -0
- package/dist/client/asset-review.js +1570 -0
- package/dist/client/callback.d.ts +13 -0
- package/dist/client/callback.js +29 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.js +1379 -0
- package/dist/client/sdn.d.ts +8 -0
- package/dist/client/sdn.js +1542 -0
- package/dist/client/style.css +32 -0
- package/dist/client/types.d.ts +278 -0
- package/dist/compat/index.d.ts +26 -0
- package/dist/compat/index.js +62 -0
- package/dist/wallet-origin/index.d.ts +21 -0
- package/dist/wallet-origin/index.js +4607 -0
- package/dist/wallet-origin-host/assets/wallet-origin.43cf84e0eba17742309357ec6bc2c544736e155ffd5d76d5024408e998500a57.wasm +0 -0
- package/dist/wallet-origin-host/assets/wallet-origin.4c9455f1a2618948e203f720485add9d8ebf77b5542b41f933594afcfbb34361.css +1 -0
- package/dist/wallet-origin-host/assets/wallet-origin.aedc355452e665e16c0f72a6597602873378dbc39d4cbf6104621c07579347e0.js +12463 -0
- package/dist/wallet-origin-host/index.html +10 -0
- package/dist/wallet-origin-host/integrity.json +1 -0
- package/package.json +46 -18
- package/src/address-derivation.js +0 -514
- package/src/app.js +0 -6008
- package/src/blockchain-trust.js +0 -752
- package/src/clipboard.js +0 -44
- package/src/constants.js +0 -66
- package/src/hash.js +0 -9
- package/src/lib.js +0 -66
- package/src/template.js +0 -626
- package/src/trust-ui.js +0 -745
- package/src/wallet-storage.js +0 -773
- package/styles/main.css +0 -6220
- package/styles/widget.css +0 -6212
|
@@ -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.4c9455f1a2618948e203f720485add9d8ebf77b5542b41f933594afcfbb34361.css" integrity="sha384-L+K+DIhK8MC4/Af3DTEJJlQQBhEQOiNWv/YJBATKJHjszSXwaqhv/jQvHo75USIY" crossorigin="anonymous">
|
|
8
|
+
</head><body><main data-wallet-origin-root aria-live="polite"></main>
|
|
9
|
+
<script type="module" src="/assets/wallet-origin.aedc355452e665e16c0f72a6597602873378dbc39d4cbf6104621c07579347e0.js" integrity="sha384-CDQGDeL5LqNDgqGKI9CycVpeLUU/MyBKtI+ojSlbs/Cb3TeYCOjXb02SQ/yOyFaW" crossorigin="anonymous"></script>
|
|
10
|
+
</body></html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"files":{"assets/wallet-origin.4c9455f1a2618948e203f720485add9d8ebf77b5542b41f933594afcfbb34361.css":{"bytes":1332,"sha256":"4c9455f1a2618948e203f720485add9d8ebf77b5542b41f933594afcfbb34361","sha384":"sha384-L+K+DIhK8MC4/Af3DTEJJlQQBhEQOiNWv/YJBATKJHjszSXwaqhv/jQvHo75USIY"},"assets/wallet-origin.aedc355452e665e16c0f72a6597602873378dbc39d4cbf6104621c07579347e0.js":{"bytes":441483,"sha256":"aedc355452e665e16c0f72a6597602873378dbc39d4cbf6104621c07579347e0","sha384":"sha384-CDQGDeL5LqNDgqGKI9CycVpeLUU/MyBKtI+ojSlbs/Cb3TeYCOjXb02SQ/yOyFaW"},"assets/wallet-origin.43cf84e0eba17742309357ec6bc2c544736e155ffd5d76d5024408e998500a57.wasm":{"bytes":3839340,"sha256":"43cf84e0eba17742309357ec6bc2c544736e155ffd5d76d5024408e998500a57","sha384":"sha384-UHNVeOMtOSXvZ0pJ9OMvKk79WVCBDU1oYAEYotatrh6in95Yc07JyNst65xGPowo"},"index.html":{"bytes":1059,"sha256":"1e49dd7bb029dab94992c38874855af7ec3fa7c64b66f45248ace955de4b9c2d","sha384":"sha384-cTGuvGtkteZlbl+1cvGZkiNRpO1geyGiw0k16jCVkZnHclv0U9VThy6pgZx4hvWK"}},"schemaVersion":1}
|
package/package.json
CHANGED
|
@@ -1,22 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hd-wallet-ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.27",
|
|
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": "
|
|
7
|
-
"module": "
|
|
6
|
+
"main": "./dist/compat/index.js",
|
|
7
|
+
"module": "./dist/compat/index.js",
|
|
8
|
+
"types": "./dist/compat/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
"
|
|
16
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.27"
|
|
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.21",
|
|
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": "ea29356700b750627f005df03bc5ac9037a9418f"
|
|
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) { ... }
|