@meshconnect/uwc-bitcoin 0.2.3 → 0.2.4-snapshot.c3e9146
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/dist/adapter.d.ts +20 -7
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapters/okx.d.ts +50 -0
- package/dist/adapters/okx.d.ts.map +1 -0
- package/dist/adapters/okx.js +70 -0
- package/dist/adapters/okx.js.map +1 -0
- package/dist/adapters/wallet-standard.d.ts +88 -0
- package/dist/adapters/wallet-standard.d.ts.map +1 -0
- package/dist/adapters/wallet-standard.js +118 -0
- package/dist/adapters/wallet-standard.js.map +1 -0
- package/dist/bitcoin-namespace.d.ts +20 -3
- package/dist/bitcoin-namespace.d.ts.map +1 -1
- package/dist/bitcoin-namespace.js +39 -2
- package/dist/bitcoin-namespace.js.map +1 -1
- package/dist/encoding.d.ts +12 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +46 -0
- package/dist/encoding.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/adapter.ts +25 -7
- package/src/adapters/okx.test.ts +140 -0
- package/src/adapters/okx.ts +116 -0
- package/src/adapters/wallet-standard.test.ts +148 -0
- package/src/adapters/wallet-standard.ts +194 -0
- package/src/bitcoin-namespace.test.ts +92 -1
- package/src/bitcoin-namespace.ts +46 -3
- package/src/encoding.ts +47 -0
- package/src/index.ts +8 -0
|
@@ -33,6 +33,28 @@ const ethereumNetwork: Network = {
|
|
|
33
33
|
networkType: 'evm'
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/** Same bip122 namespace as Bitcoin, but a different chain id. */
|
|
37
|
+
const dogeNetwork: Network = {
|
|
38
|
+
internalId: 3,
|
|
39
|
+
id: 'bip122:1a91e3dace36e2be3bf030a65679fe82',
|
|
40
|
+
namespace: 'bip122',
|
|
41
|
+
name: 'Dogecoin',
|
|
42
|
+
logoUrl: '',
|
|
43
|
+
nativeCurrency: { name: 'Dogecoin', symbol: 'DOGE', decimals: 8 },
|
|
44
|
+
networkType: 'dogecoin'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Same bip122 namespace as Bitcoin, but a different chain id. */
|
|
48
|
+
const litecoinNetwork: Network = {
|
|
49
|
+
internalId: 2,
|
|
50
|
+
id: 'bip122:12a765e31ffd4059bada1e25190f6e98',
|
|
51
|
+
namespace: 'bip122',
|
|
52
|
+
name: 'Litecoin',
|
|
53
|
+
logoUrl: '',
|
|
54
|
+
nativeCurrency: { name: 'Litecoin', symbol: 'LTC', decimals: 8 },
|
|
55
|
+
networkType: 'litecoin'
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
const account: BitcoinAccount = {
|
|
37
59
|
address: 'bc1qexampleaddress',
|
|
38
60
|
publicKey: '02aabbcc',
|
|
@@ -41,10 +63,12 @@ const account: BitcoinAccount = {
|
|
|
41
63
|
|
|
42
64
|
const signature: SignatureType = { type: 'standard', signature: 'deadbeef' }
|
|
43
65
|
|
|
44
|
-
/**
|
|
66
|
+
/** Full fake adapter so the namespace's delegation can be asserted. */
|
|
45
67
|
const fakeAdapter = (): BitcoinTransportAdapter => ({
|
|
46
68
|
getAccounts: vi.fn().mockResolvedValue([account]),
|
|
47
69
|
signMessage: vi.fn().mockResolvedValue(signature),
|
|
70
|
+
signPsbt: vi.fn().mockResolvedValue('c2lnbmVk'),
|
|
71
|
+
sendTransfer: vi.fn().mockResolvedValue('txid-abc'),
|
|
48
72
|
disconnect: vi.fn().mockResolvedValue(undefined)
|
|
49
73
|
})
|
|
50
74
|
|
|
@@ -64,6 +88,32 @@ describe('assertBitcoinNetwork', () => {
|
|
|
64
88
|
ethereumNetwork.id
|
|
65
89
|
)
|
|
66
90
|
})
|
|
91
|
+
|
|
92
|
+
it('throws a distinct error for a bip122 network that is not Bitcoin mainnet (Dogecoin)', () => {
|
|
93
|
+
expect(() => assertBitcoinNetwork(dogeNetwork, 'test')).toThrow(
|
|
94
|
+
WalletConnectorError
|
|
95
|
+
)
|
|
96
|
+
expect(() => assertBitcoinNetwork(dogeNetwork, 'test')).toThrow(
|
|
97
|
+
dogeNetwork.id
|
|
98
|
+
)
|
|
99
|
+
// Distinct from the namespace-mismatch message — callers must be able to
|
|
100
|
+
// tell "wrong namespace" apart from "right namespace, wrong chain".
|
|
101
|
+
expect(() => assertBitcoinNetwork(dogeNetwork, 'test')).toThrow(
|
|
102
|
+
/unsupported bip122 chain/
|
|
103
|
+
)
|
|
104
|
+
expect(() => assertBitcoinNetwork(ethereumNetwork, 'test')).not.toThrow(
|
|
105
|
+
/unsupported bip122 chain/
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('throws a distinct error for a bip122 network that is not Bitcoin mainnet (Litecoin)', () => {
|
|
110
|
+
expect(() => assertBitcoinNetwork(litecoinNetwork, 'test')).toThrow(
|
|
111
|
+
WalletConnectorError
|
|
112
|
+
)
|
|
113
|
+
expect(() => assertBitcoinNetwork(litecoinNetwork, 'test')).toThrow(
|
|
114
|
+
/unsupported bip122 chain/
|
|
115
|
+
)
|
|
116
|
+
})
|
|
67
117
|
})
|
|
68
118
|
|
|
69
119
|
describe('BitcoinNamespace', () => {
|
|
@@ -88,6 +138,17 @@ describe('BitcoinNamespace', () => {
|
|
|
88
138
|
).toThrow(WalletConnectorError)
|
|
89
139
|
})
|
|
90
140
|
|
|
141
|
+
it('assertSupportedNetwork rejects other bip122 chains (Dogecoin, Litecoin)', () => {
|
|
142
|
+
expect(() =>
|
|
143
|
+
new BitcoinNamespace(fakeAdapter()).assertSupportedNetwork(dogeNetwork)
|
|
144
|
+
).toThrow(WalletConnectorError)
|
|
145
|
+
expect(() =>
|
|
146
|
+
new BitcoinNamespace(fakeAdapter()).assertSupportedNetwork(
|
|
147
|
+
litecoinNetwork
|
|
148
|
+
)
|
|
149
|
+
).toThrow(WalletConnectorError)
|
|
150
|
+
})
|
|
151
|
+
|
|
91
152
|
it('delegates getAccounts to the adapter', async () => {
|
|
92
153
|
const adapter = fakeAdapter()
|
|
93
154
|
const result = await new BitcoinNamespace(adapter).getAccounts()
|
|
@@ -116,4 +177,34 @@ describe('BitcoinNamespace', () => {
|
|
|
116
177
|
new BitcoinNamespace(noDisconnect).disconnect()
|
|
117
178
|
).resolves.toBeUndefined()
|
|
118
179
|
})
|
|
180
|
+
|
|
181
|
+
it('delegates signPsbt to the adapter', async () => {
|
|
182
|
+
const adapter = fakeAdapter()
|
|
183
|
+
const request = { from: 'bc1qexampleaddress', psbt: 'cHNidA==' }
|
|
184
|
+
const result = await new BitcoinNamespace(adapter).signPsbt(request)
|
|
185
|
+
expect(adapter.signPsbt).toHaveBeenCalledWith(request)
|
|
186
|
+
expect(result).toBe('c2lnbmVk')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('delegates sendTransfer to the adapter', async () => {
|
|
190
|
+
const adapter = fakeAdapter()
|
|
191
|
+
const request = { from: 'bc1qexampleaddress', to: 'bc1qdest', amount: 1000 }
|
|
192
|
+
const result = await new BitcoinNamespace(adapter).sendTransfer(request)
|
|
193
|
+
expect(adapter.sendTransfer).toHaveBeenCalledWith(request)
|
|
194
|
+
expect(result).toBe('txid-abc')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('rejects when the adapter does not support signPsbt / sendTransfer', async () => {
|
|
198
|
+
const signOnly: BitcoinTransportAdapter = {
|
|
199
|
+
getAccounts: vi.fn(),
|
|
200
|
+
signMessage: vi.fn()
|
|
201
|
+
}
|
|
202
|
+
const ns = new BitcoinNamespace(signOnly)
|
|
203
|
+
await expect(
|
|
204
|
+
ns.signPsbt({ from: 'bc1q', psbt: 'x' })
|
|
205
|
+
).rejects.toBeInstanceOf(WalletConnectorError)
|
|
206
|
+
await expect(
|
|
207
|
+
ns.sendTransfer({ from: 'bc1q', to: 'bc1q2', amount: 1 })
|
|
208
|
+
).rejects.toBeInstanceOf(WalletConnectorError)
|
|
209
|
+
})
|
|
119
210
|
})
|
package/src/bitcoin-namespace.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BitcoinAccount,
|
|
3
|
+
BitcoinPsbtSignRequest,
|
|
4
|
+
BitcoinSendTransferRequest,
|
|
3
5
|
BitcoinSignMessageParams,
|
|
4
6
|
Namespace,
|
|
5
7
|
Network,
|
|
6
|
-
SignatureType
|
|
8
|
+
SignatureType,
|
|
9
|
+
TransactionResult
|
|
7
10
|
} from '@meshconnect/uwc-types'
|
|
11
|
+
import { bitcoinNetwork } from '@meshconnect/uwc-constants'
|
|
8
12
|
import { WalletConnectorError } from '@meshconnect/uwc-types'
|
|
9
13
|
import type { BitcoinTransportAdapter } from './adapter'
|
|
10
14
|
|
|
@@ -12,8 +16,13 @@ import type { BitcoinTransportAdapter } from './adapter'
|
|
|
12
16
|
export const BITCOIN_NAMESPACE: Namespace = 'bip122'
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
|
-
* Assert `network` is
|
|
16
|
-
*
|
|
19
|
+
* Assert `network` is the bip122 network this module actually supports
|
|
20
|
+
* (Bitcoin mainnet), else throw. Other bip122 chains (Dogecoin, Litecoin,
|
|
21
|
+
* etc.) share the namespace but differ in chain id — accepting them here
|
|
22
|
+
* would let a Bitcoin-only wallet silently return a Bitcoin address stamped
|
|
23
|
+
* with the wrong network id, so they must be rejected explicitly rather than
|
|
24
|
+
* only checking the namespace. Shared by the namespace module and its
|
|
25
|
+
* transport adapters so the boundary check lives in one place.
|
|
17
26
|
*/
|
|
18
27
|
export function assertBitcoinNetwork(network: Network, method: string): void {
|
|
19
28
|
if (network.namespace !== BITCOIN_NAMESPACE) {
|
|
@@ -22,6 +31,12 @@ export function assertBitcoinNetwork(network: Network, method: string): void {
|
|
|
22
31
|
message: `${method}: expected a bip122 network but got "${network.id}"`
|
|
23
32
|
})
|
|
24
33
|
}
|
|
34
|
+
if (network.id !== bitcoinNetwork.id) {
|
|
35
|
+
throw new WalletConnectorError({
|
|
36
|
+
type: 'unknown',
|
|
37
|
+
message: `${method}: unsupported bip122 chain "${network.id}" — only Bitcoin mainnet ("${bitcoinNetwork.id}") is supported`
|
|
38
|
+
})
|
|
39
|
+
}
|
|
25
40
|
}
|
|
26
41
|
|
|
27
42
|
/**
|
|
@@ -65,8 +80,36 @@ export class BitcoinNamespace {
|
|
|
65
80
|
return this.adapter.signMessage(params)
|
|
66
81
|
}
|
|
67
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Sign a PSBT (Path B). Returns the wallet-signed PSBT (base64); finalizing +
|
|
85
|
+
* broadcasting is a separate step. Rejects if the active adapter's wallet
|
|
86
|
+
* does not support PSBT signing.
|
|
87
|
+
*/
|
|
88
|
+
async signPsbt(request: BitcoinPsbtSignRequest): Promise<string> {
|
|
89
|
+
if (!this.adapter.signPsbt) throw this.unsupported('signPsbt')
|
|
90
|
+
return this.adapter.signPsbt(request)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* High-level transfer (Path A): the wallet signs and broadcasts, returning a
|
|
95
|
+
* txid. Rejects if the active adapter's transport does not offer it.
|
|
96
|
+
*/
|
|
97
|
+
async sendTransfer(
|
|
98
|
+
request: BitcoinSendTransferRequest
|
|
99
|
+
): Promise<TransactionResult> {
|
|
100
|
+
if (!this.adapter.sendTransfer) throw this.unsupported('sendTransfer')
|
|
101
|
+
return this.adapter.sendTransfer(request)
|
|
102
|
+
}
|
|
103
|
+
|
|
68
104
|
/** Release any session resources the adapter holds. */
|
|
69
105
|
async disconnect(): Promise<void> {
|
|
70
106
|
await this.adapter.disconnect?.()
|
|
71
107
|
}
|
|
108
|
+
|
|
109
|
+
private unsupported(method: string): WalletConnectorError {
|
|
110
|
+
return new WalletConnectorError({
|
|
111
|
+
type: 'unknown',
|
|
112
|
+
message: `BitcoinNamespace.${method}: the active adapter does not support it`
|
|
113
|
+
})
|
|
114
|
+
}
|
|
72
115
|
}
|
package/src/encoding.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal encoding helpers for the boundary between our transport-agnostic
|
|
3
|
+
* domain shapes (hex/base64 strings, JSON-safe) and the Wallet Standard
|
|
4
|
+
* `bitcoin:*` interface (raw `Uint8Array`). Uses only Web-standard APIs
|
|
5
|
+
* (`TextEncoder`, `atob`/`btoa`) so `uwc-bitcoin` stays dependency-free.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export function utf8ToBytes(text: string): Uint8Array {
|
|
9
|
+
return new TextEncoder().encode(text)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
13
|
+
let hex = ''
|
|
14
|
+
for (const byte of bytes) hex += byte.toString(16).padStart(2, '0')
|
|
15
|
+
return hex
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function bytesToBase64(bytes: Uint8Array): string {
|
|
19
|
+
let binary = ''
|
|
20
|
+
for (const byte of bytes) binary += String.fromCharCode(byte)
|
|
21
|
+
return btoa(binary)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function base64ToBytes(base64: string): Uint8Array {
|
|
25
|
+
const binary = atob(base64)
|
|
26
|
+
const bytes = new Uint8Array(binary.length)
|
|
27
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
|
|
28
|
+
return bytes
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function hexToBytes(hex: string): Uint8Array {
|
|
32
|
+
const clean = /^0x/i.test(hex) ? hex.slice(2) : hex
|
|
33
|
+
if (clean.length % 2 !== 0) {
|
|
34
|
+
throw new Error('hexToBytes: odd-length hex string')
|
|
35
|
+
}
|
|
36
|
+
// Validate before parsing: `parseInt` on a non-hex pair yields NaN, which a
|
|
37
|
+
// Uint8Array silently coerces to 0 — so bad input would corrupt data (a PSBT
|
|
38
|
+
// or public key) instead of failing fast. Reject it.
|
|
39
|
+
if (!/^[0-9a-fA-F]*$/.test(clean)) {
|
|
40
|
+
throw new Error('hexToBytes: non-hex characters')
|
|
41
|
+
}
|
|
42
|
+
const bytes = new Uint8Array(clean.length / 2)
|
|
43
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
44
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16)
|
|
45
|
+
}
|
|
46
|
+
return bytes
|
|
47
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,3 +4,11 @@ export {
|
|
|
4
4
|
BITCOIN_NAMESPACE
|
|
5
5
|
} from './bitcoin-namespace'
|
|
6
6
|
export type { BitcoinTransportAdapter } from './adapter'
|
|
7
|
+
export { WalletStandardBitcoinAdapter } from './adapters/wallet-standard'
|
|
8
|
+
export type {
|
|
9
|
+
WalletStandardBitcoinWallet,
|
|
10
|
+
WalletStandardBitcoinAccount
|
|
11
|
+
} from './adapters/wallet-standard'
|
|
12
|
+
export { OkxBitcoinAdapter } from './adapters/okx'
|
|
13
|
+
export type { OkxBitcoinProvider } from './adapters/okx'
|
|
14
|
+
export { bytesToHex, hexToBytes } from './encoding'
|