@meshconnect/uwc-bitcoin 0.2.2 → 0.2.4-snapshot.aa56682

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.
@@ -1,11 +1,17 @@
1
- import { describe, it, expect } from 'vitest'
2
- import type { Network } from '@meshconnect/uwc-types'
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import type {
3
+ BitcoinAccount,
4
+ BitcoinSignMessageParams,
5
+ Network,
6
+ SignatureType
7
+ } from '@meshconnect/uwc-types'
3
8
  import { WalletConnectorError } from '@meshconnect/uwc-types'
4
9
  import {
5
10
  BitcoinNamespace,
6
11
  assertBitcoinNetwork,
7
12
  BITCOIN_NAMESPACE
8
13
  } from './bitcoin-namespace'
14
+ import type { BitcoinTransportAdapter } from './adapter'
9
15
 
10
16
  const bitcoinNetwork: Network = {
11
17
  internalId: 0,
@@ -27,6 +33,23 @@ const ethereumNetwork: Network = {
27
33
  networkType: 'evm'
28
34
  }
29
35
 
36
+ const account: BitcoinAccount = {
37
+ address: 'bc1qexampleaddress',
38
+ publicKey: '02aabbcc',
39
+ purpose: 'payment'
40
+ }
41
+
42
+ const signature: SignatureType = { type: 'standard', signature: 'deadbeef' }
43
+
44
+ /** Full fake adapter so the namespace's delegation can be asserted. */
45
+ const fakeAdapter = (): BitcoinTransportAdapter => ({
46
+ getAccounts: vi.fn().mockResolvedValue([account]),
47
+ signMessage: vi.fn().mockResolvedValue(signature),
48
+ signPsbt: vi.fn().mockResolvedValue('c2lnbmVk'),
49
+ sendTransfer: vi.fn().mockResolvedValue('txid-abc'),
50
+ disconnect: vi.fn().mockResolvedValue(undefined)
51
+ })
52
+
30
53
  describe('assertBitcoinNetwork', () => {
31
54
  it('passes for a bip122 network', () => {
32
55
  expect(() => assertBitcoinNetwork(bitcoinNetwork, 'test')).not.toThrow()
@@ -48,18 +71,81 @@ describe('assertBitcoinNetwork', () => {
48
71
  describe('BitcoinNamespace', () => {
49
72
  it('serves the bip122 namespace', () => {
50
73
  expect(BITCOIN_NAMESPACE).toBe('bip122')
51
- expect(new BitcoinNamespace().namespace).toBe(BITCOIN_NAMESPACE)
74
+ expect(new BitcoinNamespace(fakeAdapter()).namespace).toBe(
75
+ BITCOIN_NAMESPACE
76
+ )
52
77
  })
53
78
 
54
79
  it('assertSupportedNetwork accepts a bip122 network', () => {
55
80
  expect(() =>
56
- new BitcoinNamespace().assertSupportedNetwork(bitcoinNetwork)
81
+ new BitcoinNamespace(fakeAdapter()).assertSupportedNetwork(bitcoinNetwork)
57
82
  ).not.toThrow()
58
83
  })
59
84
 
60
85
  it('assertSupportedNetwork rejects a non-bip122 network', () => {
61
86
  expect(() =>
62
- new BitcoinNamespace().assertSupportedNetwork(ethereumNetwork)
87
+ new BitcoinNamespace(fakeAdapter()).assertSupportedNetwork(
88
+ ethereumNetwork
89
+ )
63
90
  ).toThrow(WalletConnectorError)
64
91
  })
92
+
93
+ it('delegates getAccounts to the adapter', async () => {
94
+ const adapter = fakeAdapter()
95
+ const result = await new BitcoinNamespace(adapter).getAccounts()
96
+ expect(adapter.getAccounts).toHaveBeenCalledOnce()
97
+ expect(result).toEqual([account])
98
+ })
99
+
100
+ it('delegates signMessage to the adapter with the same params', async () => {
101
+ const adapter = fakeAdapter()
102
+ const params: BitcoinSignMessageParams = { account, message: 'verify me' }
103
+ const result = await new BitcoinNamespace(adapter).signMessage(params)
104
+ expect(adapter.signMessage).toHaveBeenCalledWith(params)
105
+ expect(result).toBe(signature)
106
+ })
107
+
108
+ it('delegates disconnect and tolerates an adapter without one', async () => {
109
+ const adapter = fakeAdapter()
110
+ await new BitcoinNamespace(adapter).disconnect()
111
+ expect(adapter.disconnect).toHaveBeenCalledOnce()
112
+
113
+ const noDisconnect: BitcoinTransportAdapter = {
114
+ getAccounts: adapter.getAccounts,
115
+ signMessage: adapter.signMessage
116
+ }
117
+ await expect(
118
+ new BitcoinNamespace(noDisconnect).disconnect()
119
+ ).resolves.toBeUndefined()
120
+ })
121
+
122
+ it('delegates signPsbt to the adapter', async () => {
123
+ const adapter = fakeAdapter()
124
+ const request = { from: 'bc1qexampleaddress', psbt: 'cHNidA==' }
125
+ const result = await new BitcoinNamespace(adapter).signPsbt(request)
126
+ expect(adapter.signPsbt).toHaveBeenCalledWith(request)
127
+ expect(result).toBe('c2lnbmVk')
128
+ })
129
+
130
+ it('delegates sendTransfer to the adapter', async () => {
131
+ const adapter = fakeAdapter()
132
+ const request = { from: 'bc1qexampleaddress', to: 'bc1qdest', amount: 1000 }
133
+ const result = await new BitcoinNamespace(adapter).sendTransfer(request)
134
+ expect(adapter.sendTransfer).toHaveBeenCalledWith(request)
135
+ expect(result).toBe('txid-abc')
136
+ })
137
+
138
+ it('rejects when the adapter does not support signPsbt / sendTransfer', async () => {
139
+ const signOnly: BitcoinTransportAdapter = {
140
+ getAccounts: vi.fn(),
141
+ signMessage: vi.fn()
142
+ }
143
+ const ns = new BitcoinNamespace(signOnly)
144
+ await expect(
145
+ ns.signPsbt({ from: 'bc1q', psbt: 'x' })
146
+ ).rejects.toBeInstanceOf(WalletConnectorError)
147
+ await expect(
148
+ ns.sendTransfer({ from: 'bc1q', to: 'bc1q2', amount: 1 })
149
+ ).rejects.toBeInstanceOf(WalletConnectorError)
150
+ })
65
151
  })
@@ -1,5 +1,15 @@
1
- import type { Namespace, Network } from '@meshconnect/uwc-types'
1
+ import type {
2
+ BitcoinAccount,
3
+ BitcoinPsbtSignRequest,
4
+ BitcoinSendTransferRequest,
5
+ BitcoinSignMessageParams,
6
+ Namespace,
7
+ Network,
8
+ SignatureType,
9
+ TransactionResult
10
+ } from '@meshconnect/uwc-types'
2
11
  import { WalletConnectorError } from '@meshconnect/uwc-types'
12
+ import type { BitcoinTransportAdapter } from './adapter'
3
13
 
4
14
  /** CAIP-2 namespace this module serves. */
5
15
  export const BITCOIN_NAMESPACE: Namespace = 'bip122'
@@ -25,15 +35,14 @@ export function assertBitcoinNetwork(network: Network, method: string): void {
25
35
  * (`injected-connector`, `wallet-connect-connector`) own wallet discovery,
26
36
  * pairing, and the provider/session lifecycle. This module owns only
27
37
  * Bitcoin-specific operations performed against a transport-supplied
28
- * provider/session — enumerating bip122 accounts, signing messages,
29
- * building/signing/finalizing PSBTs, and broadcasting. Both transports
30
- * dispatch their `bip122` branch into the matching adapter here.
38
+ * provider/session, delegating each to the {@link BitcoinTransportAdapter} it
39
+ * was constructed with — the transport builds that adapter bound to the live
40
+ * session.
31
41
  *
32
- * Those operations, and the provider/session types they take, land on the
33
- * `BitcoinTransportAdapter` port in Phase 2 — deferred here so the scaffold
34
- * doesn't pre-commit signatures before those types exist.
35
- *
36
- * Phase 1 (scaffold): namespace identity + the shared network guard only.
42
+ * Phase 2 exposes the grounded verify-flow operations (account enumeration and
43
+ * message signing). PSBT signing (Path B) plus finalize/broadcast, and the
44
+ * high-level `sendTransfer` (Path A), land in Phase 3 alongside their concrete
45
+ * adapters.
37
46
  *
38
47
  * Phased build plan:
39
48
  * https://app.notion.com/p/3a5f862e950a8187b3d3f0e255d13f45
@@ -42,8 +51,53 @@ export class BitcoinNamespace {
42
51
  /** CAIP-2 namespace this module serves. */
43
52
  readonly namespace = BITCOIN_NAMESPACE
44
53
 
54
+ constructor(private readonly adapter: BitcoinTransportAdapter) {}
55
+
45
56
  /** Throws unless `network` is a bip122 network. */
46
57
  assertSupportedNetwork(network: Network): void {
47
58
  assertBitcoinNetwork(network, 'BitcoinNamespace.assertSupportedNetwork')
48
59
  }
60
+
61
+ /** Enumerate the bip122 accounts on the connected session. */
62
+ getAccounts(): Promise<BitcoinAccount[]> {
63
+ return this.adapter.getAccounts()
64
+ }
65
+
66
+ /** Sign a message with the given account. */
67
+ signMessage(params: BitcoinSignMessageParams): Promise<SignatureType> {
68
+ return this.adapter.signMessage(params)
69
+ }
70
+
71
+ /**
72
+ * Sign a PSBT (Path B). Returns the wallet-signed PSBT (base64); finalizing +
73
+ * broadcasting is a separate step. Rejects if the active adapter's wallet
74
+ * does not support PSBT signing.
75
+ */
76
+ async signPsbt(request: BitcoinPsbtSignRequest): Promise<string> {
77
+ if (!this.adapter.signPsbt) throw this.unsupported('signPsbt')
78
+ return this.adapter.signPsbt(request)
79
+ }
80
+
81
+ /**
82
+ * High-level transfer (Path A): the wallet signs and broadcasts, returning a
83
+ * txid. Rejects if the active adapter's transport does not offer it.
84
+ */
85
+ async sendTransfer(
86
+ request: BitcoinSendTransferRequest
87
+ ): Promise<TransactionResult> {
88
+ if (!this.adapter.sendTransfer) throw this.unsupported('sendTransfer')
89
+ return this.adapter.sendTransfer(request)
90
+ }
91
+
92
+ /** Release any session resources the adapter holds. */
93
+ async disconnect(): Promise<void> {
94
+ await this.adapter.disconnect?.()
95
+ }
96
+
97
+ private unsupported(method: string): WalletConnectorError {
98
+ return new WalletConnectorError({
99
+ type: 'unknown',
100
+ message: `BitcoinNamespace.${method}: the active adapter does not support it`
101
+ })
102
+ }
49
103
  }
@@ -0,0 +1,29 @@
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
+ }
package/src/index.ts CHANGED
@@ -3,3 +3,11 @@ export {
3
3
  assertBitcoinNetwork,
4
4
  BITCOIN_NAMESPACE
5
5
  } from './bitcoin-namespace'
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 { WalletConnectBitcoinAdapter } from './adapters/walletconnect'
13
+ export type { BitcoinWalletConnectRequest } from './adapters/walletconnect'