@usebutr/bitcoin 1.0.1 → 1.1.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,222 @@
1
+ # @usebutr/bitcoin
2
+
3
+ ## 1.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`bc5b58c`](https://github.com/pedroapfilho/usebutr/commit/bc5b58cd87601be38dbb77a14e16eaaa36e8f012), [`bc5b58c`](https://github.com/pedroapfilho/usebutr/commit/bc5b58cd87601be38dbb77a14e16eaaa36e8f012)]:
8
+ - @usebutr/wallet-standard-shared@1.0.0
9
+ - @usebutr/core@2.0.1
10
+
11
+ ## 1.1.1
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [302ae4a]
16
+ - @usebutr/wallet-standard-shared@0.5.1
17
+
18
+ ## 1.1.0
19
+
20
+ ### Minor Changes
21
+
22
+ - 9b1caa2: Correctness pass across the storage, hydration and connector layers. Several of
23
+ these change observable behaviour; the ones worth knowing about before upgrading:
24
+
25
+ **Breaking**
26
+
27
+ - `SuiWallet.signTransaction` now returns `{ bytes, signature }` instead of
28
+ `Uint8Array`. Sui's `executeTransactionBlock` needs both, and each connector
29
+ previously returned a different half, so the old value could not be broadcast
30
+ and a consumer could not tell which half they held. SVM and Bitcoin keep their
31
+ single-`Uint8Array` shape.
32
+ - `@usebutr/svm`'s `sendTx` returns the signature base58-encoded, matching the
33
+ WalletConnect namespace, the Ledger app, Solana explorers and
34
+ `getSignatureStatuses`. It previously returned base64.
35
+ - `@usebutr/sui`'s feature input types drop the `string` arm from
36
+ `transaction`; wallets only ever accepted the `toJSON()` form. Strings and BCS
37
+ bytes are now wrapped for you, so `sendTx` accepts strictly more than before.
38
+
39
+ **Behaviour fixes**
40
+
41
+ - `WalletStorage.setPool` and `removePoolEntry` could self-deadlock on a corrupt
42
+ pool payload, leaving `connectWallet` unresolved and every later pool write
43
+ jammed for the page's life. Both are fixed, and `readWalletSnapshot` now shares
44
+ one codec with `WalletStorage` so the server and client decodes cannot drift.
45
+ - A seeded (SSR) entry whose silent reconnect failed used to stay in the pool
46
+ backed by a placeholder connector forever, so `useSigner`/`useBalance`
47
+ reported an error and `useConnectionStatus` reported `"reconnecting"`
48
+ permanently. Those entries are now dropped.
49
+ - `useSigner` and `useBalance` stay `idle` for a wallet that is still
50
+ reconnecting instead of surfacing a placeholder rejection as `status: "error"`.
51
+ - `useConnectionStatus` lets a live `"connecting"`/`"error"` take precedence over
52
+ the derived `"reconnecting"`, so an in-flight attempt and a connection error
53
+ are no longer hidden. New `useIsReconnecting(connectorId?)` answers the
54
+ per-wallet question directly.
55
+ - A background restore no longer writes the connect-attempt status or steals
56
+ `activeConnectorId`, and a superseded `CONNECT_FAILED` no longer overwrites the
57
+ current attempt.
58
+ - An externally-disconnected connector is now torn down, not just dropped from
59
+ the pool, so a cached adapter is not reused while still holding a session.
60
+ - EVM: a malformed provider response is treated as absent rather than `""`, which
61
+ previously produced chain `eip155:0`, zero-length signatures, empty transaction
62
+ hashes and a confident `0 ETH`. `switchChain` gained the same-chain
63
+ short-circuit `sendTxToChain` already had.
64
+ - WalletConnect: accounts carry their own chain instead of all being stamped with
65
+ the active one, so a multi-chain session no longer returns a wrong-chain
66
+ address. One pairing now covers every configured namespace, the pairing URI
67
+ survives a reconnect, and concurrent connects share one pairing.
68
+ - Bitcoin: the sats-connect (Xverse) adapter honours `silent`, so a reload no
69
+ longer triggers an unsolicited approval prompt, and exposes the payment address
70
+ as its single account instead of also presenting the ordinals address as
71
+ spendable.
72
+ - Polkadot: the injected adapter actually delivers events to subscribers,
73
+ `switchChain` notifies like its Wallet Standard sibling, and chain resolution
74
+ prefers the mainnets rather than whatever the wallet listed first.
75
+ - Ledger: the device session is committed atomically, so a locked device no
76
+ longer leaves an open transport that reports itself connected, and concurrent
77
+ connects open one transport.
78
+ - SVM: `sendTxToChain` submits to the chain you asked for instead of the
79
+ adapter's current one, and rejects a chain the wallet does not advertise.
80
+ - `@usebutr/svm`'s `switchChain` capability now counts only `solana:` chains, so
81
+ a multi-namespace wallet no longer advertises a method that always throws.
82
+
83
+ **Testing**
84
+
85
+ - `createFakePersistence` is now the real `WalletStorage` over memory drivers
86
+ rather than a parallel implementation, so it inherits upsert semantics,
87
+ validation and JSON round-tripping. Two divergences are fixed as a result:
88
+ `setPool` upserts rather than replaces, and `clearAll` leaves the
89
+ user-disconnected flag alone.
90
+ - `createFakeConnectedWallet` rejects being given both an `adapter` and explicit
91
+ `addresses`/`accounts`, a combination whose halves could disagree.
92
+
93
+ ### Patch Changes
94
+
95
+ - Updated dependencies [9b1caa2]
96
+ - @usebutr/wallet-standard-shared@0.5.0
97
+ - @usebutr/core@2.0.0
98
+
99
+ ## 1.0.1
100
+
101
+ ### Patch Changes
102
+
103
+ - Updated dependencies [8ecaf89]
104
+ - @usebutr/core@1.1.0
105
+ - @usebutr/wallet-standard-shared@0.4.1
106
+
107
+ ## 1.0.0
108
+
109
+ ### Major Changes
110
+
111
+ - f0a5116: **Breaking:** the `platform` field is gone from the `PlatformDiscoverer` type in `@usebutr/core`, and from the `evmDiscoverer`, `svmDiscoverer`, `suiDiscoverer`, `bitcoinDiscoverer` and `polkadotDiscoverer` objects that implement it. Nothing read it: the aggregator keys discoverers by `ChainPlatform` in its own registry, so the field only restated the key.
112
+
113
+ Migration: read the platform from the `KNOWN_DISCOVERERS` key in `@usebutr/wallets` (`Object.entries(KNOWN_DISCOVERERS)`), or from `adapter.chainPlatform` on a discovered adapter. Custom `PlatformDiscoverer` implementations must drop the `platform` property; keeping it is now an excess-property error.
114
+
115
+ - f0a5116: **Breaking:** `slugify` is no longer exported from `@usebutr/svm`, `@usebutr/sui` or `@usebutr/bitcoin`. Each package exported a one-argument wrapper that only bound a platform prefix onto the canonical two-argument helper, so three names shadowed one implementation.
116
+
117
+ Migration: import `slugify` from `@usebutr/wallet-standard-shared` and pass the platform prefix as the first argument. `slugify(name)` from `@usebutr/svm` becomes `slugify("svm", name)`; the `@usebutr/sui` prefix is `"sui"` and the `@usebutr/bitcoin` prefix is `"btc"`. Adapter ids are byte-for-byte the same.
118
+
119
+ ### Patch Changes
120
+
121
+ - 7887cf0: Resolve capabilities through `buildWalletCapabilities` from `@usebutr/wallet-standard-shared` instead of a per-package copy of the same object literal. Resolver names, input types and returned flags are unchanged. The injected Polkadot profile now derives its chain count from `POLKADOT_CHAINS_LIST` rather than asserting `switchChain: true` directly; the result is the same.
122
+ - 7887cf0: Move each package's `discover*Adapters` function next to the adapter builder it wraps and drop the single-function `wallet-standard.ts` module. The functions are still exported from the package root under the same names; only an internal file boundary went away.
123
+ - Updated dependencies [7887cf0]
124
+ - Updated dependencies [7887cf0]
125
+ - Updated dependencies [f0a5116]
126
+ - Updated dependencies [f0a5116]
127
+ - Updated dependencies [7887cf0]
128
+ - @usebutr/core@1.0.0
129
+ - @usebutr/wallet-standard-shared@0.4.0
130
+
131
+ ## 0.2.7
132
+
133
+ ### Patch Changes
134
+
135
+ - efe4550: Share the session plumbing behind the Wallet Standard and WalletConnect CAIP adapters. `@usebutr/wallet-standard-shared` now exports `createWalletStandardCore`, which the Bitcoin, Polkadot, Sui and SVM adapters build on; the WalletConnect Bitcoin, Sui and SVM namespaces share an equivalent CAIP core. Every package's public API is unchanged. The one visible difference is a dev-console warning: a failed SVM disconnect now logs `[butr] SVM Wallet Standard disconnect threw:` instead of `[butr] Wallet Standard disconnect threw:`.
136
+ - Updated dependencies [efe4550]
137
+ - Updated dependencies [4467a5e]
138
+ - @usebutr/wallet-standard-shared@0.3.0
139
+ - @usebutr/core@0.5.0
140
+
141
+ ## 0.2.6
142
+
143
+ ### Patch Changes
144
+
145
+ - 99eaef0: Restore guards that the type-aware lint pass narrowed away.
146
+
147
+ - `@usebutr/bitcoin`: the sats-connect `getAccounts` reader dropped its optional
148
+ chain on the RPC payload. The declared shape is an assertion over an untyped
149
+ bridge, so a wallet answering without a `result` threw a `TypeError` instead
150
+ of reporting no accounts.
151
+ - `@usebutr/walletconnect`: the Sui and Solana signing paths treated an
152
+ empty-string `transaction` / `transactionBytes` / `bytes` field as a real
153
+ value and decoded it to zero bytes. They now fall through to the signature
154
+ path (Sui `signPersonalMessage`) or the original message, matching the
155
+ previous truthiness checks.
156
+ - `@usebutr/ledger`: the unknown-platform rejection lost the platform name from
157
+ its message, which is the only detail that made the error actionable.
158
+
159
+ - Updated dependencies [c1309ee]
160
+ - @usebutr/core@0.4.2
161
+ - @usebutr/wallet-standard-shared@0.2.6
162
+
163
+ ## 0.2.5
164
+
165
+ ### Patch Changes
166
+
167
+ - 937dfae: Bump runtime dependency floors (`@wallet-standard/app` 1.1.1, `@ledgerhq/*` latest minors, `@walletconnect/universal-provider` 2.23.10) and modernize public type declarations from method signatures to property function types (oxlint `method-signature-style`). Type-level only — no runtime behavior change.
168
+ - Updated dependencies [937dfae]
169
+ - @usebutr/core@0.4.1
170
+ - @usebutr/wallet-standard-shared@0.2.5
171
+
172
+ ## 0.2.4
173
+
174
+ ### Patch Changes
175
+
176
+ - a46eecd: Ship unminified ESM so downstream bundlers (Vite/esbuild dep pre-bundling) process the package correctly; fixes a ReferenceError in consumer dev servers. The consuming app minifies once at its own build.
177
+ - Updated dependencies [b5322ae]
178
+ - Updated dependencies [d5f32c7]
179
+ - Updated dependencies [a46eecd]
180
+ - Updated dependencies [3f26776]
181
+ - @usebutr/core@0.4.0
182
+ - @usebutr/wallet-standard-shared@0.2.4
183
+
184
+ ## 0.2.3
185
+
186
+ ### Patch Changes
187
+
188
+ - Updated dependencies [886ee1d]
189
+ - @usebutr/core@0.3.0
190
+ - @usebutr/wallet-standard-shared@0.2.3
191
+
192
+ ## 0.2.2
193
+
194
+ ### Patch Changes
195
+
196
+ - Updated dependencies [db5d7e9]
197
+ - @usebutr/core@0.2.2
198
+ - @usebutr/wallet-standard-shared@0.2.2
199
+
200
+ ## 0.2.1
201
+
202
+ ### Patch Changes
203
+
204
+ - f846e77: Wallet-announced icons are trimmed of surrounding whitespace on ingestion. Some wallets ship data-URI icons with a leading newline, which strict consumers reject — Next.js's `<Image>` throws because `src` must not start with a control character. `@usebutr/core` exports a `sanitizeIcon` helper; the EIP-6963 and Wallet Standard adapters apply it, and an all-whitespace icon now resolves to `undefined` rather than a blank string.
205
+ - Updated dependencies [f846e77]
206
+ - @usebutr/core@0.2.1
207
+ - @usebutr/wallet-standard-shared@0.2.1
208
+
209
+ ## 0.2.0
210
+
211
+ ### Minor Changes
212
+
213
+ - b77a477: Shared Wallet Standard protocol types (`WalletStandardWallet`, `WalletStandardWalletAccount`, `WalletStandardAppModule`, `WalletsApp`, `StandardConnectFeature`, `StandardDisconnectFeature`, `StandardEventsFeature`, `StandardEventsListener`) are imported from `@usebutr/wallet-standard-shared` directly — they are no longer re-exported from `@usebutr/svm`, `@usebutr/sui`, or `@usebutr/bitcoin`. Platform-specific feature shapes still ship from their own packages.
214
+
215
+ `@usebutr/svm` adapter ids are now platform-prefixed (`wallet-standard:svm-<slug>`), consistent with the `sui-` and `btc-` prefixes. `slugify` in `@usebutr/wallet-standard-shared` requires a non-empty platform prefix.
216
+
217
+ ### Patch Changes
218
+
219
+ - Updated dependencies [b77a477]
220
+ - Updated dependencies [b77a477]
221
+ - @usebutr/core@0.2.0
222
+ - @usebutr/wallet-standard-shared@0.2.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pedro Filho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @usebutr/bitcoin
2
+
3
+ Wallet Standard + injected (sats-connect / Unisat / OKX) wallet discovery and adapters for butr (Bitcoin).
4
+
5
+ Part of [butr](https://www.usebutr.com), a multi-chain wallet discovery and
6
+ connection-state library. Your application owns the picker UI and chain client.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install @usebutr/react @usebutr/core @usebutr/bitcoin zustand @wallet-standard/app
12
+ ```
13
+
14
+ React 18+ is a peer dependency of the provider. Discovery does not render a connect modal; add your picker as the provider child. `@wallet-standard/app` is required for Wallet Standard discovery.
15
+
16
+ ## Usage
17
+
18
+ ```tsx
19
+ import type { ReactNode } from "react";
20
+ import { createWalletSource } from "@usebutr/core";
21
+ import { discoverBitcoinAdapters } from "@usebutr/bitcoin";
22
+ import { WalletManagerProvider } from "@usebutr/react";
23
+
24
+ const discovery = createWalletSource(discoverBitcoinAdapters);
25
+ export const WalletProvider = ({ children }: { children: ReactNode }) => (
26
+ <WalletManagerProvider discovery={discovery}>{children}</WalletManagerProvider>
27
+ );
28
+ ```
29
+
30
+ ## Documentation
31
+
32
+ - [Package reference](https://docs.usebutr.com/api/bitcoin)
33
+ - [Getting started](https://docs.usebutr.com/getting-started/quickstart)
34
+ - [Examples and source](https://github.com/pedroapfilho/usebutr)
35
+ - [Report an issue](https://github.com/pedroapfilho/usebutr/issues)
36
+
37
+ ## License
38
+
39
+ [MIT](./LICENSE), copyright 2026 Pedro Filho.
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ type BitcoinSignMessageOutput = {
11
11
  };
12
12
  type BitcoinSignMessageFeature = {
13
13
  signMessage: (input: BitcoinSignMessageInput) => Promise<BitcoinSignMessageOutput>;
14
+ version?: string;
14
15
  };
15
16
  type BitcoinSignPsbtInput = {
16
17
  account: WalletStandardWalletAccount;
@@ -29,6 +30,7 @@ type BitcoinSignPsbtOutput = {
29
30
  };
30
31
  type BitcoinSignPsbtFeature = {
31
32
  signPsbt: (input: BitcoinSignPsbtInput) => Promise<BitcoinSignPsbtOutput>;
33
+ version?: string;
32
34
  };
33
35
  type BitcoinSendTransferInput = {
34
36
  account: WalletStandardWalletAccount;
@@ -42,73 +44,28 @@ type BitcoinSendTransferOutput = {
42
44
  };
43
45
  type BitcoinSendTransferFeature = {
44
46
  sendTransfer: (input: BitcoinSendTransferInput) => Promise<BitcoinSendTransferOutput>;
47
+ version?: string;
45
48
  };
46
49
  //#endregion
47
50
  //#region src/wallet-standard-adapter.d.ts
48
51
  /**
49
- * Adapt a Bitcoin Wallet Standard `Wallet` into a butr `WalletAdapter`.
50
- *
51
- * Returns `null` if the wallet doesn't advertise any `bip122:` chain or
52
- * doesn't expose `standard:connect`. Same posture as the SVM/Sui
53
- * adapters: gate features on what each wallet advertises, return
54
- * placeholders for RPC reads, forward `subscribe` through
55
- * `standard:events`.
56
- *
57
- * **Known limitations**
58
- *
59
- * - `getBalance` returns `{ value: 0n, symbol: "BTC" }`. Bitcoin
60
- * balance lookups require an Esplora/Electrum/RPC client outside
61
- * butr's scope.
62
- * - `getTransactionReceipt` returns `"Pending"` for the same reason.
63
- * - `sendTx` expects the consumer to pass `{ amount: bigint, recipient: string }`
64
- * via butr's `unknown` tx parameter; the simplest mental model that
65
- * maps onto `bitcoin:sendTransfer`. PSBT-shaped flows go through
66
- * `signTransaction`.
67
- * - `signTransaction` expects a `Uint8Array` PSBT (raw bytes); the
68
- * same shape `bitcoin:signPsbt` accepts. Consumers building with
69
- * `bitcoinjs-lib` get bytes via `psbt.toBuffer()`.
70
- * - `switchChain` is local-state-only; the capability flag is `false`
71
- * when only one chain is advertised. Wallets that ship a real switch
72
- * primitive (none today, portably) would need their own adapter.
52
+ * `sendTx` takes `{ amount, recipient }` for `bitcoin:sendTransfer` and
53
+ * `signTransaction` takes raw PSBT bytes for `bitcoin:signPsbt`. Balance
54
+ * and receipt reads would need an Esplora/Electrum client butr doesn't ship.
73
55
  */
74
56
  declare const buildBitcoinAdapter: (wallet: WalletStandardWallet,
75
57
  /** Optional. Called with a function that pushes a synthetic
76
58
  * `disconnected` event to all current subscribers. The discovery
77
59
  * layer invokes it on Wallet Standard `unregister`. */
78
60
  registerDisconnector?: (emit: () => void) => void) => WalletAdapter | null;
79
- /**
80
- * Subscribe to Bitcoin Wallet Standard announcements.
81
- *
82
- * Phantom (Bitcoin), Magic Eden, OKX Wallet (Bitcoin), and Leather
83
- * advertise `bitcoin:*` features over the same `getWallets()` bus that
84
- * SVM and Sui consume. `buildBitcoinAdapter` returns `null` for wallets
85
- * that don't advertise any `bip122:` chain, so multi-chain wallets
86
- * (Phantom: EVM + SVM + Sui + Bitcoin) produce one adapter per platform.
87
- *
88
- * Requires the **optional peer dependency** `@wallet-standard/app`.
89
- *
90
- * The discovery loop (dynamic import, dedupe, register / unregister
91
- * bridge) lives in `@usebutr/wallet-standard-shared`.
92
- */
61
+ /** Requires the optional `@wallet-standard/app` peer dep. */
93
62
  declare const discoverBitcoinAdapters: (onAdapter: (adapter: WalletAdapter) => void) => (() => void);
94
63
  //#endregion
95
64
  //#region src/chains.d.ts
96
65
  /**
97
- * CAIP-2 Bitcoin chain references use the first 8 bytes (big-endian)
98
- * of each network's genesis block hash. These string ids are what
99
- * Bitcoin Wallet Standard wallets (Phantom, Magic Eden, OKX) advertise
100
- * in `wallet.chains`; the adapter's `switchChain` rejects anything else.
101
- *
102
- * - Mainnet: `bip122:000000000019d6689c085ae165831e93`
103
- * - Testnet: `bip122:000000000933ea01ad0ee984209779ba`
104
- * - Signet: `bip122:00000008819873e925422c1ff0f99f7c`
105
- *
106
- * Bitcoin has no per-call "switch chain" RPC across the major wallets,
107
- * so butr's adapter treats chain as a wallet-side configuration;
108
- * `switchChain` updates local state to influence subsequent calls (same
109
- * posture as SVM). Bitcoin's `switchChain` capability is `false` by
110
- * default; consumers building on top can opt in if their wallet exposes
111
- * a switch primitive.
66
+ * CAIP-2 Bitcoin references are the first 8 bytes of each network's
67
+ * genesis block hash, which is what wallets advertise in `wallet.chains`.
68
+ * No major wallet exposes a switch-chain RPC, so chain stays local state.
112
69
  */
113
70
  declare const BITCOIN_CHAINS: {
114
71
  readonly mainnet: {
@@ -143,30 +100,23 @@ type BitcoinCapabilityInput = {
143
100
  };
144
101
  };
145
102
  /**
146
- * Bitcoin feature → butr capability mapping. The flags that hold for every
147
- * namespace are documented on `buildWalletCapabilities`.
148
- *
149
- * - `sendTransaction = bitcoin:sendTransfer present`. The wallet handles
150
- * UTXO selection, fee estimation, signing, and broadcast.
151
- * - `signTransaction = bitcoin:signPsbt present`. Sign-only PSBT path
152
- * for consumers that build their own UTXO-shaped transactions with
153
- * `bitcoinjs-lib` and broadcast through their own provider.
154
- * - `signMessage = bitcoin:signMessage present`.
155
- * - `signIn = false`. No Sign-In-With-Bitcoin standard yet (BIP-322 is
156
- * message-signing oriented, not auth).
157
- * - `subscribe = events present`. Most Bitcoin wallets don't emit
158
- * change events portably; capabilities admits the truth.
103
+ * `signIn` is false: there is no Sign-In-With-Bitcoin standard, and BIP-322
104
+ * is message-signing oriented rather than auth. `sendTransfer` leaves UTXO
105
+ * selection, fees and broadcast to the wallet; `signPsbt` is sign-only.
159
106
  */
160
107
  declare const resolveBitcoinCapabilities: (input: BitcoinCapabilityInput) => WalletCapabilities;
161
108
  //#endregion
162
109
  //#region src/injected/sats-connect.d.ts
163
110
  /** sats-connect (Xverse) shape; a JSON-RPC-ish `request(method, params)`. */
111
+ type RpcValue = boolean | number | string | null | ReadonlyArray<RpcValue> | {
112
+ readonly [key: string]: RpcValue | undefined;
113
+ };
164
114
  type SatsConnectProvider = {
165
- request: (method: string, params?: Record<string, unknown>) => Promise<{
115
+ request: (method: string, params?: Readonly<Record<string, RpcValue>>) => Promise<{
166
116
  error?: {
167
117
  message: string;
168
118
  };
169
- result?: unknown;
119
+ result?: RpcValue;
170
120
  }>;
171
121
  };
172
122
  //#endregion
@@ -177,24 +127,24 @@ type SatsConnectProvider = {
177
127
  type UnisatProvider = {
178
128
  getAccounts: () => Promise<ReadonlyArray<string>>;
179
129
  getNetwork?: () => Promise<"livenet" | "mainnet" | "testnet" | "signet">;
180
- on?: (event: "accountsChanged" | "networkChanged", listener: (...args: Array<unknown>) => void) => void;
130
+ on?: (event: "accountsChanged" | "networkChanged", listener: (...args: Array<UnisatEventValue>) => void) => void;
181
131
  pushPsbt?: (psbtHex: string) => Promise<string>;
182
- removeListener?: (event: "accountsChanged" | "networkChanged", listener: (...args: Array<unknown>) => void) => void;
132
+ removeListener?: (event: "accountsChanged" | "networkChanged", listener: (...args: Array<UnisatEventValue>) => void) => void;
183
133
  requestAccounts: () => Promise<ReadonlyArray<string>>;
184
134
  sendBitcoin?: (recipient: string, amount: number) => Promise<string>;
185
135
  signMessage: (message: string, type?: "ecdsa" | "bip322-simple") => Promise<string>;
186
- signPsbt: (psbtHex: string, options?: Record<string, unknown>) => Promise<string>;
136
+ signPsbt: (psbtHex: string, options?: UnisatSignPsbtOptions) => Promise<string>;
137
+ };
138
+ type UnisatEventItem = number | string | null;
139
+ type UnisatEventValue = ReadonlyArray<UnisatEventItem> | string | undefined;
140
+ type UnisatSignPsbtOptions = {
141
+ autoFinalized?: boolean;
187
142
  };
188
143
  //#endregion
189
144
  //#region src/injected/icon.d.ts
190
145
  /**
191
- * Generic Bitcoin wallet icon, same purse SVG style as the EVM
192
- * package, recoloured. Inline so the picker doesn't flash.
193
- *
194
- * Lives in its own file because both injected adapters (UniSat-shape
195
- * and sats-connect) need the same fallback when the wallet doesn't
196
- * advertise its own icon, and there's no other "shared" thing they
197
- * both want; a one-line module beats a grab-bag.
146
+ * Inlined as a data URI so the picker doesn't flash, and shared by both
147
+ * injected adapters as the fallback when a wallet advertises no icon.
198
148
  */
199
149
  declare const GENERIC_BITCOIN_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2Y3OTMxYSIgc3Ryb2tlPSJub25lIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjx0ZXh0IHg9IjEyIiB5PSIxNyIgZm9udC1mYW1pbHk9InNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+QjwvdGV4dD48L3N2Zz4=";
200
150
  //#endregion
@@ -209,6 +159,18 @@ type InjectedHost = {
209
159
  BitcoinProvider?: SatsConnectProvider;
210
160
  };
211
161
  };
162
+ declare global {
163
+ interface Window {
164
+ btc?: UnisatProvider;
165
+ okxwallet?: {
166
+ bitcoin?: UnisatProvider;
167
+ };
168
+ unisat?: UnisatProvider;
169
+ XverseProviders?: {
170
+ BitcoinProvider?: SatsConnectProvider;
171
+ };
172
+ }
173
+ }
212
174
  type InjectedBitcoinDiscoveryOptions = {
213
175
  /** Suppresses emission when Wallet Standard discovery has already
214
176
  * registered a Bitcoin adapter for the same browser session. */
@@ -220,64 +182,29 @@ type InjectedBitcoinDiscoveryOptions = {
220
182
  target?: InjectedHost | null;
221
183
  };
222
184
  /**
223
- * Last-resort discovery path for Bitcoin wallets that DON'T announce via
224
- * Wallet Standard. Probes, in order: `window.unisat`,
225
- * `window.okxwallet.bitcoin`, `window.XverseProviders.BitcoinProvider`,
226
- * `window.btc`.
227
- *
228
- * Wallet Standard wallets (Phantom, Magic Eden, Leather, modern OKX
229
- * builds) are already handled by `discoverBitcoinAdapters`. This
230
- * fallback waits `settleMs` (default 150ms), consults
231
- * `hasAnyWalletStandardAdapter` if provided, and emits adapters for
232
- * any injected providers that weren't already covered.
233
- *
234
- * Each emitted adapter has a stable id prefix (`injected:bitcoin:*`)
235
- * so consumers can distinguish them in the picker UI.
185
+ * Last-resort path for wallets that don't announce via Wallet Standard:
186
+ * `window.unisat`, OKX, Xverse, `window.btc`. The `settleMs` wait lets
187
+ * Wallet Standard announce first, else a wallet on both channels lists twice.
236
188
  */
237
189
  declare const discoverInjectedBitcoinAdapter: (onAdapter: (adapter: WalletAdapter) => void, options?: InjectedBitcoinDiscoveryOptions) => (() => void);
238
190
  //#endregion
239
191
  //#region src/discoverer.d.ts
240
192
  /**
241
- * Bitcoin's `PlatformDiscoverer`: Wallet Standard primary (Phantom,
242
- * Magic Eden, Leather, OKX); legacy fallback covers sats-connect
243
- * (Xverse), `window.unisat`, OKX legacy, and `window.btc`. The
244
- * fallback defers to Wallet Standard via `hasAnyPrimaryAdapter`.
193
+ * Wallet Standard is primary; the legacy fallback covers sats-connect
194
+ * (Xverse), `window.unisat`, OKX legacy and `window.btc`, and defers to
195
+ * the primary channel via `hasAnyPrimaryAdapter`.
245
196
  */
246
197
  declare const bitcoinDiscoverer: PlatformDiscoverer;
247
198
  //#endregion
248
199
  //#region src/signer-augmentation.d.ts
249
200
  /**
250
- * Augment `@usebutr/core`'s `SignerForPlatform` registry with the Bitcoin
251
- * entry. Bitcoin's signer surface is fragmented: `getSigner()` may
252
- * return one of three shapes depending on which adapter discovered the
253
- * wallet:
254
- *
255
- * - `WalletStandardWallet`: for Phantom, Magic Eden, Leather, OKX
256
- * (modern Wallet Standard path).
257
- * - A UniSat-shaped provider; for Unisat, OKX legacy, `window.btc`
258
- * (injected path).
259
- * - A sats-connect `BitcoinProvider`; for Xverse (sats-connect path).
260
- *
261
- * The registry surfaces the union honestly so consumers know they need
262
- * to runtime-narrow.
201
+ * Bitcoin's signer surface is fragmented: `getSigner()` returns a
202
+ * `WalletStandardWallet`, a UniSat-shaped provider, or a sats-connect
203
+ * `BitcoinProvider` depending on which adapter discovered the wallet.
263
204
  */
264
- type UnisatLike = {
265
- getAccounts: () => Promise<ReadonlyArray<string>>;
266
- requestAccounts: () => Promise<ReadonlyArray<string>>;
267
- signMessage: (message: string, type?: string) => Promise<string>;
268
- signPsbt: (psbtHex: string, options?: Record<string, unknown>) => Promise<string>;
269
- };
270
- type SatsConnectLike = {
271
- request: (method: string, params?: Record<string, unknown>) => Promise<{
272
- error?: {
273
- message: string;
274
- };
275
- result?: unknown;
276
- }>;
277
- };
278
205
  declare module "@usebutr/core" {
279
206
  interface SignerForPlatform {
280
- bitcoin: SatsConnectLike | UnisatLike | WalletStandardWallet;
207
+ bitcoin: SatsConnectProvider | UnisatProvider | WalletStandardWallet;
281
208
  }
282
209
  }
283
210
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/wallet-standard-types.ts","../src/wallet-standard-adapter.ts","../src/chains.ts","../src/capabilities.ts","../src/injected/sats-connect.ts","../src/injected/unisat.ts","../src/injected/icon.ts","../src/injected/index.ts","../src/discoverer.ts","../src/signer-augmentation.ts"],"mappings":";;;KAgBK;EACH,SAAS;EACT,SAAS;;KAGN;EACH,WAAW;EACX,eAAe;;KAGZ;EACH,cAAc,OAAO,4BAA4B,QAAQ;;KAGtD;EACH,SAAS;;EAET;;EAEA,MAAM;;;EAGN,aAAa;;KAGV;;;EAGH,YAAY;;KAGT;EACH,WAAW,OAAO,yBAAyB,QAAQ;;KAGhD;EACH,SAAS;EACT;EACA;EACA;;KAGG;;EAEH;;KAGG;EACH,eAAe,OAAO,6BAA6B,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cClBvD,sBACJ,QAAQ,sBAIR;;;;AAAA,wBAAwB,8BACvB;;;;;;;;;;;;;;;cAiIG,0BAA2B,YAAY,SAAS;;;;;;;;;;;;;;;;;;;;cClKhD;;;;;;;;;;;;;;;;;;;;cAqBA,qBAAqB,cAAc;;;KCrCpC;EACH;EACA;IACE;IACA;IACA;IACA;;;;;;;;;;;;;;;;;;cAmBE,6BAA8B,OAAO,2BAAyB;;;;KCnB/D;EACH,UACE,gBACA,SAAS,4BACN;IAAU;MAAU;;IAAmB;;;;;;;;KCFzC;EACH,mBAAmB,QAAQ;EAC3B,mBAAmB;EACnB,MACE,6CACA,cAAc,MAAM;EAEtB,YAAY,oBAAoB;EAChC,kBACE,6CACA,cAAc,MAAM;EAEtB,uBAAuB,QAAQ;EAC/B,eAAe,mBAAmB,mBAAmB;EACrD,cAAc,iBAAiB,qCAAqC;EACpE,WAAW,iBAAiB,UAAU,4BAA4B;;;;;;;;;;;;;cCjB9D;;;KCAD;EACH,MAAM;EACN;IAAc,UAAU;;EACxB,SAAS;EACT;IAAoB,kBAAkB;;;KAGnC;;;EAGH;;;EAGA;;EAEA,SAAS;;;;;;;;;;;;;;;;;cA+DL,iCACJ,YAAY,SAAS,wBACrB,UAAS;;;;;;;;;cC9EL,mBAAmB;;;;;;;;;;;;;;;;;;KCMpB;EACH,mBAAmB,QAAQ;EAC3B,uBAAuB,QAAQ;EAC/B,cAAc,iBAAiB,kBAAkB;EACjD,WAAW,iBAAiB,UAAU,4BAA4B;;KAG/D;EACH,UACE,gBACA,SAAS,4BACN;IAAU;MAAU;;IAAmB;;;;YAKlC;IACR,SAAS,kBAAkB,aAAa"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/wallet-standard-types.ts","../src/wallet-standard-adapter.ts","../src/chains.ts","../src/capabilities.ts","../src/injected/sats-connect.ts","../src/injected/unisat.ts","../src/injected/icon.ts","../src/injected/index.ts","../src/discoverer.ts","../src/signer-augmentation.ts"],"mappings":";;;KAQK;EACH,SAAS;EACT,SAAS;;KAGN;EACH,WAAW;EACX,eAAe;;KAGZ;EACH,cAAc,OAAO,4BAA4B,QAAQ;EACzD;;KAGG;EACH,SAAS;;EAET;;EAEA,MAAM;;;EAGN,aAAa;;KAGV;;;EAGH,YAAY;;KAGT;EACH,WAAW,OAAO,yBAAyB,QAAQ;EACnD;;KAGG;EACH,SAAS;EACT;EACA;EACA;;KAGG;;EAEH;;KAGG;EACH,eAAe,OAAO,6BAA6B,QAAQ;EAC3D;;;;;;;;;cCnBI,sBAAmB,QACf,sBAIe;;;;AAAA,wBAAC,8BACvB;;cAuIG,0BAAuB,YAAgB,SAAS;;;;;;;;cC9KhD;;aAEF;aACA;aACA;aACA;;;aAGA;aACA;aACA;aACA;;;aAGA;aACA;aACA;aACA;;;cAIE,qBAAqB,cAAc;;;KCzBpC;EACH;EACA;IACE;IACA;IACA;IACA;;;;;;;;cASE,6BAA0B,OAAW,2BAAyB;;;;KCF/D,8CAKD,cAAc;YACF,cAAc;;KAEzB;EACH,UACE,gBACA,SAAS,SAAS,eAAe,eAC9B;IAAU;MAAU;;IAAmB,SAAS;;;;;;;;KCjBlD;EACH,mBAAmB,QAAQ;EAC3B,mBAAmB;EACnB,MACE,6CACA,cAAc,MAAM,MAAM;EAE5B,YAAY,oBAAoB;EAChC,kBACE,6CACA,cAAc,MAAM,MAAM;EAE5B,uBAAuB,QAAQ;EAC/B,eAAe,mBAAmB,mBAAmB;EACrD,cAAc,iBAAiB,qCAAqC;EACpE,WAAW,iBAAiB,UAAU,0BAA0B;;KAG7D;KACA,mBAAmB,cAAc;KAEjC;EACH;;;;;;;;cC7BI;;;KCKD;EACH,MAAM;EACN;IAAc,UAAU;;EACxB,SAAS;EACT;IAAoB,kBAAkB;;;QAGhC;YAEI;IACR,MAAM;IACN;MAAc,UAAU;;IACxB,SAAS;IACT;MAAoB,kBAAkB;;;;KAIrC;;;EAGH;;;EAGA;;EAEA,SAAS;;;;;;;cAiDL,iCAA8B,YACtB,SAAS,wBAAsB,UAClC;;;;;;;;cC3EL,mBAAmB;;;;;;;;;YCEb;IACR,SAAS,sBAAsB,iBAAiB"}
package/dist/index.js CHANGED
@@ -1,21 +1,12 @@
1
1
  import { buildAccount, buildWalletCapabilities, createWalletStandardCore, discoverWalletStandard, getFeature, slugify } from "@usebutr/wallet-standard-shared";
2
2
  import { base64ToBytes, bytesToBase64, bytesToHex, hexToBytes } from "@usebutr/core";
3
+ import { z } from "zod";
3
4
 
4
5
  //#region src/capabilities.ts
5
6
  /**
6
- * Bitcoin feature → butr capability mapping. The flags that hold for every
7
- * namespace are documented on `buildWalletCapabilities`.
8
- *
9
- * - `sendTransaction = bitcoin:sendTransfer present`. The wallet handles
10
- * UTXO selection, fee estimation, signing, and broadcast.
11
- * - `signTransaction = bitcoin:signPsbt present`. Sign-only PSBT path
12
- * for consumers that build their own UTXO-shaped transactions with
13
- * `bitcoinjs-lib` and broadcast through their own provider.
14
- * - `signMessage = bitcoin:signMessage present`.
15
- * - `signIn = false`. No Sign-In-With-Bitcoin standard yet (BIP-322 is
16
- * message-signing oriented, not auth).
17
- * - `subscribe = events present`. Most Bitcoin wallets don't emit
18
- * change events portably; capabilities admits the truth.
7
+ * `signIn` is false: there is no Sign-In-With-Bitcoin standard, and BIP-322
8
+ * is message-signing oriented rather than auth. `sendTransfer` leaves UTXO
9
+ * selection, fees and broadcast to the wallet; `signPsbt` is sign-only.
19
10
  */
20
11
  const resolveBitcoinCapabilities = (input) => buildWalletCapabilities({
21
12
  chainCount: input.chainCount,
@@ -31,31 +22,13 @@ const resolveBitcoinCapabilities = (input) => buildWalletCapabilities({
31
22
  const BITCOIN_PREFIX = "bip122:";
32
23
  const BITCOIN_DECIMALS = 8;
33
24
  const BITCOIN_MAINNET_ID = "bip122:000000000019d6689c085ae165831e93";
25
+ const isBitcoinSignMessageFeature = (feature) => "signMessage" in feature && typeof feature.signMessage === "function";
26
+ const isBitcoinSignPsbtFeature = (feature) => "signPsbt" in feature && typeof feature.signPsbt === "function";
27
+ const isBitcoinSendTransferFeature = (feature) => "sendTransfer" in feature && typeof feature.sendTransfer === "function";
34
28
  /**
35
- * Adapt a Bitcoin Wallet Standard `Wallet` into a butr `WalletAdapter`.
36
- *
37
- * Returns `null` if the wallet doesn't advertise any `bip122:` chain or
38
- * doesn't expose `standard:connect`. Same posture as the SVM/Sui
39
- * adapters: gate features on what each wallet advertises, return
40
- * placeholders for RPC reads, forward `subscribe` through
41
- * `standard:events`.
42
- *
43
- * **Known limitations**
44
- *
45
- * - `getBalance` returns `{ value: 0n, symbol: "BTC" }`. Bitcoin
46
- * balance lookups require an Esplora/Electrum/RPC client outside
47
- * butr's scope.
48
- * - `getTransactionReceipt` returns `"Pending"` for the same reason.
49
- * - `sendTx` expects the consumer to pass `{ amount: bigint, recipient: string }`
50
- * via butr's `unknown` tx parameter; the simplest mental model that
51
- * maps onto `bitcoin:sendTransfer`. PSBT-shaped flows go through
52
- * `signTransaction`.
53
- * - `signTransaction` expects a `Uint8Array` PSBT (raw bytes); the
54
- * same shape `bitcoin:signPsbt` accepts. Consumers building with
55
- * `bitcoinjs-lib` get bytes via `psbt.toBuffer()`.
56
- * - `switchChain` is local-state-only; the capability flag is `false`
57
- * when only one chain is advertised. Wallets that ship a real switch
58
- * primitive (none today, portably) would need their own adapter.
29
+ * `sendTx` takes `{ amount, recipient }` for `bitcoin:sendTransfer` and
30
+ * `signTransaction` takes raw PSBT bytes for `bitcoin:signPsbt`. Balance
31
+ * and receipt reads would need an Esplora/Electrum client butr doesn't ship.
59
32
  */
60
33
  const buildBitcoinAdapter = (wallet, registerDisconnector) => {
61
34
  const core = createWalletStandardCore({
@@ -70,21 +43,28 @@ const buildBitcoinAdapter = (wallet, registerDisconnector) => {
70
43
  wallet
71
44
  });
72
45
  if (core === null) return null;
73
- const signMessage = getFeature(wallet, "bitcoin:signMessage");
74
- const signPsbt = getFeature(wallet, "bitcoin:signPsbt");
75
- const sendTransfer = getFeature(wallet, "bitcoin:sendTransfer");
76
- const sendTransferTx = async (tx, account) => {
46
+ const signMessage = getFeature(wallet, "bitcoin:signMessage", isBitcoinSignMessageFeature);
47
+ const signPsbt = getFeature(wallet, "bitcoin:signPsbt", isBitcoinSignPsbtFeature);
48
+ const sendTransfer = getFeature(wallet, "bitcoin:sendTransfer", isBitcoinSendTransferFeature);
49
+ /** Resolve a caller-supplied target into a chain the wallet advertises,
50
+ * accepting either a full CAIP-2 id or a bare genesis-hash reference. */
51
+ const resolveTargetChain = (targetChainId) => {
52
+ const candidate = targetChainId.startsWith(BITCOIN_PREFIX) ? targetChainId : `${BITCOIN_PREFIX}${targetChainId}`;
53
+ if (!wallet.chains.includes(candidate)) throw new Error(`Wallet ${wallet.name} does not advertise chain "${candidate}". Available: ${wallet.chains.join(", ")}`);
54
+ return candidate;
55
+ };
56
+ const sendTransferTx = async (tx, account, chain) => {
77
57
  if (sendTransfer === void 0) throw new Error(`Wallet ${wallet.name} does not advertise bitcoin:sendTransfer`);
78
58
  if (typeof tx !== "object" || tx === null || !("amount" in tx) || typeof tx.amount !== "bigint" || !("recipient" in tx) || typeof tx.recipient !== "string") throw new TypeError("Bitcoin sendTx expects { amount: bigint, recipient: string }: pass the recipient address and an amount in satoshis");
79
59
  const { amount, recipient } = tx;
80
60
  return (await sendTransfer.sendTransfer({
81
61
  account: core.resolveAccount(account),
82
62
  amount,
83
- chain: core.currentChainId(),
63
+ chain: chain ?? core.currentChainId(),
84
64
  recipient
85
65
  })).txid;
86
66
  };
87
- return {
67
+ const adapter = {
88
68
  ...core,
89
69
  capabilities: resolveBitcoinCapabilities({
90
70
  chainCount: core.chainCount,
@@ -107,9 +87,10 @@ const buildBitcoinAdapter = (wallet, registerDisconnector) => {
107
87
  await core.connect();
108
88
  },
109
89
  sendTx: (tx, account) => sendTransferTx(tx, account),
110
- sendTxToChain: (tx, _targetChainId, account, cb) => {
111
- cb?.();
112
- return sendTransferTx(tx, account);
90
+ async sendTxToChain(tx, targetChainId, account, cb) {
91
+ const target = resolveTargetChain(targetChainId);
92
+ if (target !== core.currentChainId()) cb?.();
93
+ return await sendTransferTx(tx, account, target);
113
94
  },
114
95
  async signMessage(msg, account) {
115
96
  if (signMessage === void 0) throw new Error(`Wallet ${wallet.name} does not advertise bitcoin:signMessage`);
@@ -121,52 +102,28 @@ const buildBitcoinAdapter = (wallet, registerDisconnector) => {
121
102
  signature: output.signature,
122
103
  signedMessage: output.signedMessage
123
104
  };
124
- },
125
- ...signPsbt === void 0 ? {} : { async signTransaction(tx, account) {
126
- const wsAccount = core.resolveAccount(account);
127
- if (!(tx instanceof Uint8Array)) throw new TypeError("Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())");
128
- return (await signPsbt.signPsbt({
129
- account: wsAccount,
130
- chain: core.currentChainId(),
131
- psbt: tx
132
- })).signedPsbt;
133
- } }
105
+ }
134
106
  };
107
+ if (signPsbt !== void 0) adapter.signTransaction = async (tx, account) => {
108
+ const wsAccount = core.resolveAccount(account);
109
+ if (!(tx instanceof Uint8Array)) throw new TypeError("Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())");
110
+ return (await signPsbt.signPsbt({
111
+ account: wsAccount,
112
+ chain: core.currentChainId(),
113
+ psbt: tx
114
+ })).signedPsbt;
115
+ };
116
+ return adapter;
135
117
  };
136
- /**
137
- * Subscribe to Bitcoin Wallet Standard announcements.
138
- *
139
- * Phantom (Bitcoin), Magic Eden, OKX Wallet (Bitcoin), and Leather
140
- * advertise `bitcoin:*` features over the same `getWallets()` bus that
141
- * SVM and Sui consume. `buildBitcoinAdapter` returns `null` for wallets
142
- * that don't advertise any `bip122:` chain, so multi-chain wallets
143
- * (Phantom: EVM + SVM + Sui + Bitcoin) produce one adapter per platform.
144
- *
145
- * Requires the **optional peer dependency** `@wallet-standard/app`.
146
- *
147
- * The discovery loop (dynamic import, dedupe, register / unregister
148
- * bridge) lives in `@usebutr/wallet-standard-shared`.
149
- */
118
+ /** Requires the optional `@wallet-standard/app` peer dep. */
150
119
  const discoverBitcoinAdapters = (onAdapter) => discoverWalletStandard(onAdapter, (wallet, registerDisconnector) => buildBitcoinAdapter(wallet, registerDisconnector));
151
120
 
152
121
  //#endregion
153
122
  //#region src/chains.ts
154
123
  /**
155
- * CAIP-2 Bitcoin chain references use the first 8 bytes (big-endian)
156
- * of each network's genesis block hash. These string ids are what
157
- * Bitcoin Wallet Standard wallets (Phantom, Magic Eden, OKX) advertise
158
- * in `wallet.chains`; the adapter's `switchChain` rejects anything else.
159
- *
160
- * - Mainnet: `bip122:000000000019d6689c085ae165831e93`
161
- * - Testnet: `bip122:000000000933ea01ad0ee984209779ba`
162
- * - Signet: `bip122:00000008819873e925422c1ff0f99f7c`
163
- *
164
- * Bitcoin has no per-call "switch chain" RPC across the major wallets,
165
- * so butr's adapter treats chain as a wallet-side configuration;
166
- * `switchChain` updates local state to influence subsequent calls (same
167
- * posture as SVM). Bitcoin's `switchChain` capability is `false` by
168
- * default; consumers building on top can opt in if their wallet exposes
169
- * a switch primitive.
124
+ * CAIP-2 Bitcoin references are the first 8 bytes of each network's
125
+ * genesis block hash, which is what wallets advertise in `wallet.chains`.
126
+ * No major wallet exposes a switch-chain RPC, so chain stays local state.
170
127
  */
171
128
  const BITCOIN_CHAINS = {
172
129
  mainnet: {
@@ -193,13 +150,8 @@ const BITCOIN_CHAINS_LIST = Object.values(BITCOIN_CHAINS);
193
150
  //#endregion
194
151
  //#region src/injected/icon.ts
195
152
  /**
196
- * Generic Bitcoin wallet icon, same purse SVG style as the EVM
197
- * package, recoloured. Inline so the picker doesn't flash.
198
- *
199
- * Lives in its own file because both injected adapters (UniSat-shape
200
- * and sats-connect) need the same fallback when the wallet doesn't
201
- * advertise its own icon, and there's no other "shared" thing they
202
- * both want; a one-line module beats a grab-bag.
153
+ * Inlined as a data URI so the picker doesn't flash, and shared by both
154
+ * injected adapters as the fallback when a wallet advertises no icon.
203
155
  */
204
156
  const GENERIC_BITCOIN_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2Y3OTMxYSIgc3Ryb2tlPSJub25lIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjx0ZXh0IHg9IjEyIiB5PSIxNyIgZm9udC1mYW1pbHk9InNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+QjwvdGV4dD48L3N2Zz4=";
205
157
 
@@ -217,37 +169,65 @@ const CAPS_SATS_CONNECT = {
217
169
  switchAccount: false,
218
170
  switchChain: false
219
171
  };
172
+ const ACCOUNT_PURPOSES = ["payment", "ordinals"];
173
+ const CONNECT_MESSAGE = "Connect to butr";
174
+ const satsAddressSchema = z.object({
175
+ address: z.string(),
176
+ publicKey: z.string().optional(),
177
+ purpose: z.string().optional()
178
+ });
179
+ const accountResultSchema = z.object({ addresses: z.array(satsAddressSchema).optional() }).nullable();
180
+ const sendTransferResultSchema = z.object({ txid: z.string() });
181
+ const signMessageResultSchema = z.object({
182
+ messageHash: z.string().optional(),
183
+ signature: z.string()
184
+ });
185
+ const signPsbtResultSchema = z.object({ psbt: z.string() });
186
+ const pickPaymentAddress = (addresses) => addresses.find((a) => a.purpose === "payment") ?? addresses[0];
220
187
  /**
221
- * Wrap an Xverse-style `BitcoinProvider` (sats-connect) into a butr
222
- * `WalletAdapter`. sats-connect routes everything through a single
223
- * `request(method, params)` RPC; we shim each butr capability onto the
224
- * corresponding sats-connect method.
225
- *
226
- * Tested against Xverse's `getAccounts`, `signMessage`, `signPsbt`,
227
- * `sendTransfer` methods.
188
+ * Only the payment address is exposed as an Account: sats-connect's
189
+ * `sendTransfer` takes no sender and always debits it. The taproot ordinals
190
+ * address stays reachable through `getSigner()` and `signMessage`'s account.
228
191
  */
229
192
  const buildSatsConnectAdapter = (id, name, provider) => {
230
193
  let chain = BITCOIN_CHAINS.mainnet;
231
- let cachedAccounts = [];
232
- const callRequest = async (method, params) => {
194
+ let session = { status: "disconnected" };
195
+ const callRequest = async (method, schema, params) => {
233
196
  const response = await provider.request(method, params);
234
197
  if (response.error) throw new Error(`[butr/bitcoin] sats-connect ${method} failed: ${response.error.message}`);
235
- return response.result;
198
+ return schema.parse(response.result);
199
+ };
200
+ const startSession = async (method, params) => {
201
+ const addresses = (await callRequest(method, accountResultSchema, params))?.addresses ?? [];
202
+ const payment = pickPaymentAddress(addresses);
203
+ if (payment === void 0) throw new Error(`Wallet ${name} returned no addresses from ${method}`);
204
+ session = {
205
+ addresses,
206
+ payment: payment.address,
207
+ status: "connected"
208
+ };
236
209
  };
237
- const loadAccounts = async () => {
238
- const result = await callRequest("getAccounts", {
239
- message: "Connect to butr",
240
- purposes: ["payment", "ordinals"]
241
- });
242
- if (result?.addresses === void 0) return [];
243
- const addresses = result.addresses.map((a) => a.address);
244
- cachedAccounts = addresses;
245
- return addresses;
210
+ const requestSession = () => startSession("getAccounts", {
211
+ message: CONNECT_MESSAGE,
212
+ purposes: ACCOUNT_PURPOSES
213
+ });
214
+ const requireSession = () => {
215
+ if (session.status !== "connected") throw new Error("No connected account");
216
+ return session;
246
217
  };
247
- const sendTransferTx = async (tx) => {
218
+ const resolveAddress = (account) => {
219
+ const active = requireSession();
220
+ if (account === void 0) return active.payment;
221
+ const match = active.addresses.find((a) => a.address === account.walletAddress);
222
+ if (match === void 0) throw new Error(`Account ${account.walletAddress} is not exposed by ${name}`);
223
+ return match.address;
224
+ };
225
+ const sendTransferTx = async (tx, account) => {
226
+ const sender = resolveAddress(account);
227
+ if (sender !== requireSession().payment) throw new Error(`Wallet ${name} always debits its payment address; ${sender} cannot be used as the sender.`);
248
228
  if (typeof tx !== "object" || tx === null || !("recipient" in tx) || typeof tx.recipient !== "string" || !("amount" in tx) || typeof tx.amount !== "bigint") throw new TypeError("Bitcoin sendTx expects { amount: bigint, recipient: string }: amount in satoshis");
249
229
  const { amount, recipient } = tx;
250
- return (await callRequest("sendTransfer", { recipients: [{
230
+ return (await callRequest("sendTransfer", sendTransferResultSchema, { recipients: [{
251
231
  address: recipient,
252
232
  amount: amount.toString()
253
233
  }] })).txid;
@@ -255,22 +235,19 @@ const buildSatsConnectAdapter = (id, name, provider) => {
255
235
  return {
256
236
  capabilities: CAPS_SATS_CONNECT,
257
237
  chainPlatform: "bitcoin",
258
- async connect() {
259
- await loadAccounts();
238
+ async connect(opts) {
239
+ if (opts?.silent === true) {
240
+ await startSession("wallet_getAccount", { addresses: ACCOUNT_PURPOSES });
241
+ return;
242
+ }
243
+ await requestSession();
260
244
  },
261
245
  disconnect: () => {
262
- cachedAccounts = [];
246
+ session = { status: "disconnected" };
263
247
  return Promise.resolve();
264
248
  },
265
- async getAccount() {
266
- const first = cachedAccounts[0];
267
- if (first !== void 0) return buildAccount(first, chain);
268
- const firstFresh = (await loadAccounts())[0];
269
- return firstFresh === void 0 ? null : buildAccount(firstFresh, chain);
270
- },
271
- async getAccounts() {
272
- return (cachedAccounts.length > 0 ? cachedAccounts : await loadAccounts()).map((a) => buildAccount(a, chain));
273
- },
249
+ getAccount: () => Promise.resolve(session.status === "connected" ? buildAccount(session.payment, chain) : null),
250
+ getAccounts: () => Promise.resolve(session.status === "connected" ? [buildAccount(session.payment, chain)] : []),
274
251
  getBalance: () => Promise.resolve({
275
252
  decimals: 8,
276
253
  formatted: "0",
@@ -282,18 +259,15 @@ const buildSatsConnectAdapter = (id, name, provider) => {
282
259
  icon: GENERIC_BITCOIN_ICON,
283
260
  id,
284
261
  name,
285
- async requestAccounts() {
286
- await loadAccounts();
287
- },
288
- sendTx: (tx) => sendTransferTx(tx),
289
- sendTxToChain: (tx, _targetChainId, _account, cb) => {
262
+ requestAccounts: () => requestSession(),
263
+ sendTx: (tx, account) => sendTransferTx(tx, account),
264
+ sendTxToChain: (tx, _targetChainId, account, cb) => {
290
265
  cb?.();
291
- return sendTransferTx(tx);
266
+ return sendTransferTx(tx, account);
292
267
  },
293
- async signMessage(msg) {
294
- const address = cachedAccounts[0];
295
- if (address === void 0) throw new Error("No connected account");
296
- const result = await callRequest("signMessage", {
268
+ async signMessage(msg, account) {
269
+ const address = resolveAddress(account);
270
+ const result = await callRequest("signMessage", signMessageResultSchema, {
297
271
  address,
298
272
  message: new TextDecoder().decode(msg)
299
273
  });
@@ -304,7 +278,7 @@ const buildSatsConnectAdapter = (id, name, provider) => {
304
278
  },
305
279
  async signTransaction(tx) {
306
280
  if (!(tx instanceof Uint8Array)) throw new TypeError("Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())");
307
- const result = await callRequest("signPsbt", { psbt: bytesToBase64(tx) });
281
+ const result = await callRequest("signPsbt", signPsbtResultSchema, { psbt: bytesToBase64(tx) });
308
282
  return base64ToBytes(result.psbt);
309
283
  },
310
284
  switchChain: (target) => {
@@ -329,23 +303,19 @@ const CAPS_UNISAT = {
329
303
  switchAccount: false,
330
304
  switchChain: false
331
305
  };
306
+ const toStringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
332
307
  /**
333
- * Wrap a UniSat-style provider (`window.unisat`, `window.okxwallet.bitcoin`,
334
- * `window.btc`) into a butr `WalletAdapter`.
335
- *
336
- * The UniSat shape is consistent enough that one adapter handles every
337
- * derivative. Differences (network names, `sendBitcoin` presence) are
338
- * gated by feature detection per call.
308
+ * The UniSat shape is consistent enough across `window.unisat`,
309
+ * `window.okxwallet.bitcoin` and `window.btc` that one adapter covers all
310
+ * three; the differences (network names, `sendBitcoin`) are probed per call.
339
311
  */
340
- const toStringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
341
312
  const buildUnisatAdapter = (id, name, provider) => {
342
313
  let chain = BITCOIN_CHAINS.mainnet;
343
- const listenersSet = /* @__PURE__ */ new Set();
344
- const sendBitcoinTx = (tx) => {
314
+ const sendBitcoinTx = async (tx) => {
345
315
  if (typeof provider.sendBitcoin !== "function") throw new TypeError(`Wallet ${name} does not expose sendBitcoin`);
346
316
  if (typeof tx !== "object" || tx === null || !("recipient" in tx) || typeof tx.recipient !== "string" || !("amount" in tx) || typeof tx.amount !== "bigint") throw new TypeError("Bitcoin sendTx expects { amount: bigint, recipient: string }: amount in satoshis");
347
317
  const { amount, recipient } = tx;
348
- return provider.sendBitcoin(recipient, Number(amount));
318
+ return await provider.sendBitcoin(recipient, Number(amount));
349
319
  };
350
320
  const refreshChain = async () => {
351
321
  if (typeof provider.getNetwork !== "function") return;
@@ -415,7 +385,6 @@ const buildUnisatAdapter = (id, name, provider) => {
415
385
  return hexToBytes(signedHex);
416
386
  },
417
387
  subscribe(listener) {
418
- listenersSet.add(listener);
419
388
  const onAccountsChanged = (...args) => {
420
389
  const accounts = toStringArray(args[0]);
421
390
  if (accounts.length === 0) {
@@ -439,14 +408,12 @@ const buildUnisatAdapter = (id, name, provider) => {
439
408
  return () => {
440
409
  provider.removeListener?.("accountsChanged", onAccountsChanged);
441
410
  provider.removeListener?.("networkChanged", onNetworkChanged);
442
- listenersSet.delete(listener);
443
411
  };
444
412
  },
445
- switchChain: (target) => {
413
+ async switchChain(target) {
446
414
  if (target.namespace !== "bip122") throw new Error(`Bitcoin adapter received non-Bitcoin chain "${target.id}". Pass a chain with namespace "bip122".`);
447
415
  chain = target;
448
- refreshChain();
449
- return Promise.resolve();
416
+ await refreshChain();
450
417
  }
451
418
  };
452
419
  };
@@ -483,19 +450,9 @@ const probeProviders = (host) => {
483
450
  return adapters;
484
451
  };
485
452
  /**
486
- * Last-resort discovery path for Bitcoin wallets that DON'T announce via
487
- * Wallet Standard. Probes, in order: `window.unisat`,
488
- * `window.okxwallet.bitcoin`, `window.XverseProviders.BitcoinProvider`,
489
- * `window.btc`.
490
- *
491
- * Wallet Standard wallets (Phantom, Magic Eden, Leather, modern OKX
492
- * builds) are already handled by `discoverBitcoinAdapters`. This
493
- * fallback waits `settleMs` (default 150ms), consults
494
- * `hasAnyWalletStandardAdapter` if provided, and emits adapters for
495
- * any injected providers that weren't already covered.
496
- *
497
- * Each emitted adapter has a stable id prefix (`injected:bitcoin:*`)
498
- * so consumers can distinguish them in the picker UI.
453
+ * Last-resort path for wallets that don't announce via Wallet Standard:
454
+ * `window.unisat`, OKX, Xverse, `window.btc`. The `settleMs` wait lets
455
+ * Wallet Standard announce first, else a wallet on both channels lists twice.
499
456
  */
500
457
  const discoverInjectedBitcoinAdapter = (onAdapter, options = {}) => {
501
458
  const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
@@ -517,10 +474,9 @@ const discoverInjectedBitcoinAdapter = (onAdapter, options = {}) => {
517
474
  //#endregion
518
475
  //#region src/discoverer.ts
519
476
  /**
520
- * Bitcoin's `PlatformDiscoverer`: Wallet Standard primary (Phantom,
521
- * Magic Eden, Leather, OKX); legacy fallback covers sats-connect
522
- * (Xverse), `window.unisat`, OKX legacy, and `window.btc`. The
523
- * fallback defers to Wallet Standard via `hasAnyPrimaryAdapter`.
477
+ * Wallet Standard is primary; the legacy fallback covers sats-connect
478
+ * (Xverse), `window.unisat`, OKX legacy and `window.btc`, and defers to
479
+ * the primary channel via `hasAnyPrimaryAdapter`.
524
480
  */
525
481
  const bitcoinDiscoverer = {
526
482
  fallback: { subscribe: (onAdapter, opts) => discoverInjectedBitcoinAdapter(onAdapter, { hasAnyWalletStandardAdapter: opts.hasAnyPrimaryAdapter }) },
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/capabilities.ts","../src/wallet-standard-adapter.ts","../src/chains.ts","../src/injected/icon.ts","../src/injected/sats-connect.ts","../src/injected/unisat.ts","../src/injected/index.ts","../src/discoverer.ts"],"sourcesContent":["import type { WalletCapabilities } from \"@usebutr/core\";\nimport { buildWalletCapabilities } from \"@usebutr/wallet-standard-shared\";\n\ntype BitcoinCapabilityInput = {\n chainCount: number;\n features: {\n events: boolean;\n sendTransfer: boolean;\n signMessage: boolean;\n signPsbt: boolean;\n };\n};\n\n/**\n * Bitcoin feature → butr capability mapping. The flags that hold for every\n * namespace are documented on `buildWalletCapabilities`.\n *\n * - `sendTransaction = bitcoin:sendTransfer present`. The wallet handles\n * UTXO selection, fee estimation, signing, and broadcast.\n * - `signTransaction = bitcoin:signPsbt present`. Sign-only PSBT path\n * for consumers that build their own UTXO-shaped transactions with\n * `bitcoinjs-lib` and broadcast through their own provider.\n * - `signMessage = bitcoin:signMessage present`.\n * - `signIn = false`. No Sign-In-With-Bitcoin standard yet (BIP-322 is\n * message-signing oriented, not auth).\n * - `subscribe = events present`. Most Bitcoin wallets don't emit\n * change events portably; capabilities admits the truth.\n */\nconst resolveBitcoinCapabilities = (input: BitcoinCapabilityInput): WalletCapabilities =>\n buildWalletCapabilities({\n chainCount: input.chainCount,\n events: input.features.events,\n sendTransaction: input.features.sendTransfer,\n signIn: false,\n signMessage: input.features.signMessage,\n signTransaction: input.features.signPsbt,\n });\n\nexport type { BitcoinCapabilityInput };\nexport { resolveBitcoinCapabilities };\n","import type { WalletAdapter } from \"@usebutr/core\";\nimport {\n createWalletStandardCore,\n discoverWalletStandard,\n getFeature,\n slugify,\n} from \"@usebutr/wallet-standard-shared\";\nimport type { WalletStandardWallet } from \"@usebutr/wallet-standard-shared\";\n\nimport { resolveBitcoinCapabilities } from \"./capabilities\";\nimport type {\n BitcoinSendTransferFeature,\n BitcoinSignMessageFeature,\n BitcoinSignPsbtFeature,\n} from \"./wallet-standard-types\";\n\nconst BITCOIN_PREFIX = \"bip122:\";\nconst BITCOIN_DECIMALS = 8;\nconst BITCOIN_MAINNET_ID = \"bip122:000000000019d6689c085ae165831e93\";\n\n/**\n * Adapt a Bitcoin Wallet Standard `Wallet` into a butr `WalletAdapter`.\n *\n * Returns `null` if the wallet doesn't advertise any `bip122:` chain or\n * doesn't expose `standard:connect`. Same posture as the SVM/Sui\n * adapters: gate features on what each wallet advertises, return\n * placeholders for RPC reads, forward `subscribe` through\n * `standard:events`.\n *\n * **Known limitations**\n *\n * - `getBalance` returns `{ value: 0n, symbol: \"BTC\" }`. Bitcoin\n * balance lookups require an Esplora/Electrum/RPC client outside\n * butr's scope.\n * - `getTransactionReceipt` returns `\"Pending\"` for the same reason.\n * - `sendTx` expects the consumer to pass `{ amount: bigint, recipient: string }`\n * via butr's `unknown` tx parameter; the simplest mental model that\n * maps onto `bitcoin:sendTransfer`. PSBT-shaped flows go through\n * `signTransaction`.\n * - `signTransaction` expects a `Uint8Array` PSBT (raw bytes); the\n * same shape `bitcoin:signPsbt` accepts. Consumers building with\n * `bitcoinjs-lib` get bytes via `psbt.toBuffer()`.\n * - `switchChain` is local-state-only; the capability flag is `false`\n * when only one chain is advertised. Wallets that ship a real switch\n * primitive (none today, portably) would need their own adapter.\n */\nconst buildBitcoinAdapter = (\n wallet: WalletStandardWallet,\n /** Optional. Called with a function that pushes a synthetic\n * `disconnected` event to all current subscribers. The discovery\n * layer invokes it on Wallet Standard `unregister`. */\n registerDisconnector?: (emit: () => void) => void,\n): WalletAdapter | null => {\n const core = createWalletStandardCore({\n chainPrefix: BITCOIN_PREFIX,\n id: slugify(\"btc\", wallet.name),\n label: \"Bitcoin\",\n namespace: \"bip122\",\n platform: \"Bitcoin\",\n preferredChainIds: [BITCOIN_MAINNET_ID],\n registerDisconnector,\n trackChainChanges: true,\n wallet,\n });\n if (core === null) {\n return null;\n }\n\n const signMessage = getFeature<BitcoinSignMessageFeature>(wallet, \"bitcoin:signMessage\");\n const signPsbt = getFeature<BitcoinSignPsbtFeature>(wallet, \"bitcoin:signPsbt\");\n const sendTransfer = getFeature<BitcoinSendTransferFeature>(wallet, \"bitcoin:sendTransfer\");\n\n const sendTransferTx = async (\n tx: unknown,\n account?: { walletAddress: string },\n ): Promise<string> => {\n if (sendTransfer === undefined) {\n throw new Error(`Wallet ${wallet.name} does not advertise bitcoin:sendTransfer`);\n }\n if (\n typeof tx !== \"object\" ||\n tx === null ||\n !(\"amount\" in tx) ||\n typeof tx.amount !== \"bigint\" ||\n !(\"recipient\" in tx) ||\n typeof tx.recipient !== \"string\"\n ) {\n throw new TypeError(\n \"Bitcoin sendTx expects { amount: bigint, recipient: string }: pass the recipient address and an amount in satoshis\",\n );\n }\n const { amount, recipient } = tx;\n const output = await sendTransfer.sendTransfer({\n account: core.resolveAccount(account),\n amount,\n chain: core.currentChainId(),\n recipient,\n });\n return output.txid;\n };\n\n return {\n ...core,\n capabilities: resolveBitcoinCapabilities({\n chainCount: core.chainCount,\n features: {\n events: core.hasEvents,\n sendTransfer: Boolean(sendTransfer),\n signMessage: Boolean(signMessage),\n signPsbt: Boolean(signPsbt),\n },\n }),\n chainPlatform: \"bitcoin\",\n\n getBalance: () =>\n Promise.resolve({\n decimals: BITCOIN_DECIMALS,\n formatted: \"0\",\n symbol: \"BTC\",\n value: 0n,\n }),\n\n getTransactionReceipt: () => Promise.resolve({ status: \"Pending\" as const }),\n\n async requestAccounts() {\n await core.connect();\n },\n\n sendTx: (tx, account) => sendTransferTx(tx, account),\n\n sendTxToChain: (tx, _targetChainId, account, cb) => {\n cb?.();\n return sendTransferTx(tx, account);\n },\n\n async signMessage(msg, account) {\n if (signMessage === undefined) {\n throw new Error(`Wallet ${wallet.name} does not advertise bitcoin:signMessage`);\n }\n const output = await signMessage.signMessage({\n account: core.resolveAccount(account),\n message: msg,\n });\n return { signature: output.signature, signedMessage: output.signedMessage };\n },\n\n ...(signPsbt === undefined\n ? {}\n : {\n async signTransaction(tx, account) {\n const wsAccount = core.resolveAccount(account);\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\n \"Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())\",\n );\n }\n const output = await signPsbt.signPsbt({\n account: wsAccount,\n chain: core.currentChainId(),\n psbt: tx,\n });\n return output.signedPsbt;\n },\n }),\n };\n};\n\n/**\n * Subscribe to Bitcoin Wallet Standard announcements.\n *\n * Phantom (Bitcoin), Magic Eden, OKX Wallet (Bitcoin), and Leather\n * advertise `bitcoin:*` features over the same `getWallets()` bus that\n * SVM and Sui consume. `buildBitcoinAdapter` returns `null` for wallets\n * that don't advertise any `bip122:` chain, so multi-chain wallets\n * (Phantom: EVM + SVM + Sui + Bitcoin) produce one adapter per platform.\n *\n * Requires the **optional peer dependency** `@wallet-standard/app`.\n *\n * The discovery loop (dynamic import, dedupe, register / unregister\n * bridge) lives in `@usebutr/wallet-standard-shared`.\n */\nconst discoverBitcoinAdapters = (onAdapter: (adapter: WalletAdapter) => void): (() => void) =>\n discoverWalletStandard(onAdapter, (wallet, registerDisconnector) =>\n buildBitcoinAdapter(wallet, registerDisconnector),\n );\n\nexport { buildBitcoinAdapter, discoverBitcoinAdapters };\n","import type { ChainBase } from \"@usebutr/core\";\n\n/**\n * CAIP-2 Bitcoin chain references use the first 8 bytes (big-endian)\n * of each network's genesis block hash. These string ids are what\n * Bitcoin Wallet Standard wallets (Phantom, Magic Eden, OKX) advertise\n * in `wallet.chains`; the adapter's `switchChain` rejects anything else.\n *\n * - Mainnet: `bip122:000000000019d6689c085ae165831e93`\n * - Testnet: `bip122:000000000933ea01ad0ee984209779ba`\n * - Signet: `bip122:00000008819873e925422c1ff0f99f7c`\n *\n * Bitcoin has no per-call \"switch chain\" RPC across the major wallets,\n * so butr's adapter treats chain as a wallet-side configuration;\n * `switchChain` updates local state to influence subsequent calls (same\n * posture as SVM). Bitcoin's `switchChain` capability is `false` by\n * default; consumers building on top can opt in if their wallet exposes\n * a switch primitive.\n */\nconst BITCOIN_CHAINS = {\n mainnet: {\n id: \"bip122:000000000019d6689c085ae165831e93\",\n name: \"Bitcoin\",\n namespace: \"bip122\",\n reference: \"000000000019d6689c085ae165831e93\",\n },\n signet: {\n id: \"bip122:00000008819873e925422c1ff0f99f7c\",\n name: \"Bitcoin Signet\",\n namespace: \"bip122\",\n reference: \"00000008819873e925422c1ff0f99f7c\",\n },\n testnet: {\n id: \"bip122:000000000933ea01ad0ee984209779ba\",\n name: \"Bitcoin Testnet\",\n namespace: \"bip122\",\n reference: \"000000000933ea01ad0ee984209779ba\",\n },\n} as const satisfies Record<string, ChainBase>;\n\nconst BITCOIN_CHAINS_LIST: ReadonlyArray<ChainBase> = Object.values(BITCOIN_CHAINS);\n\nexport { BITCOIN_CHAINS, BITCOIN_CHAINS_LIST };\n","/**\n * Generic Bitcoin wallet icon, same purse SVG style as the EVM\n * package, recoloured. Inline so the picker doesn't flash.\n *\n * Lives in its own file because both injected adapters (UniSat-shape\n * and sats-connect) need the same fallback when the wallet doesn't\n * advertise its own icon, and there's no other \"shared\" thing they\n * both want; a one-line module beats a grab-bag.\n */\nconst GENERIC_BITCOIN_ICON =\n \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2Y3OTMxYSIgc3Ryb2tlPSJub25lIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjx0ZXh0IHg9IjEyIiB5PSIxNyIgZm9udC1mYW1pbHk9InNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+QjwvdGV4dD48L3N2Zz4=\";\n\nexport { GENERIC_BITCOIN_ICON };\n","import type { ChainBase, WalletAdapter, WalletCapabilities } from \"@usebutr/core\";\nimport { base64ToBytes, bytesToBase64 } from \"@usebutr/core\";\nimport { buildAccount } from \"@usebutr/wallet-standard-shared\";\n\nimport { BITCOIN_CHAINS } from \"../chains\";\n\nimport { GENERIC_BITCOIN_ICON } from \"./icon\";\n\n/** sats-connect (Xverse) shape; a JSON-RPC-ish `request(method, params)`. */\ntype SatsConnectProvider = {\n request: (\n method: string,\n params?: Record<string, unknown>,\n ) => Promise<{ error?: { message: string }; result?: unknown }>;\n};\n\nconst CAPS_SATS_CONNECT: WalletCapabilities = {\n getBalance: false,\n getTransactionReceipt: false,\n requestAccounts: true,\n sendTransaction: true,\n signIn: false,\n signMessage: true,\n signTransaction: true,\n subscribe: false,\n switchAccount: false,\n switchChain: false,\n};\n\n/**\n * Wrap an Xverse-style `BitcoinProvider` (sats-connect) into a butr\n * `WalletAdapter`. sats-connect routes everything through a single\n * `request(method, params)` RPC; we shim each butr capability onto the\n * corresponding sats-connect method.\n *\n * Tested against Xverse's `getAccounts`, `signMessage`, `signPsbt`,\n * `sendTransfer` methods.\n */\nconst buildSatsConnectAdapter = (\n id: string,\n name: string,\n provider: SatsConnectProvider,\n): WalletAdapter => {\n let chain: ChainBase = BITCOIN_CHAINS.mainnet;\n let cachedAccounts: ReadonlyArray<string> = [];\n\n // oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- caller-supplied result shape for an untyped RPC bridge\n const callRequest = async <T>(method: string, params?: Record<string, unknown>): Promise<T> => {\n const response = await provider.request(method, params);\n if (response.error) {\n throw new Error(`[butr/bitcoin] sats-connect ${method} failed: ${response.error.message}`);\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- untyped sats-connect RPC result; caller declares the shape\n return response.result as T;\n };\n\n const loadAccounts = async (): Promise<ReadonlyArray<string>> => {\n // `callRequest` only asserts the caller's shape onto an untyped RPC\n // payload, so a wallet that answers without a `result` lands here as\n // null/undefined and has to be read through an optional chain.\n const result = await callRequest<{\n addresses?: ReadonlyArray<{ address: string; purpose?: string }>;\n } | null>(\"getAccounts\", {\n message: \"Connect to butr\",\n purposes: [\"payment\", \"ordinals\"],\n });\n if (result?.addresses === undefined) {\n return [];\n }\n const addresses = result.addresses.map((a) => a.address);\n cachedAccounts = addresses;\n return addresses;\n };\n\n const sendTransferTx = async (tx: unknown): Promise<string> => {\n if (\n typeof tx !== \"object\" ||\n tx === null ||\n !(\"recipient\" in tx) ||\n typeof tx.recipient !== \"string\" ||\n !(\"amount\" in tx) ||\n typeof tx.amount !== \"bigint\"\n ) {\n throw new TypeError(\n \"Bitcoin sendTx expects { amount: bigint, recipient: string }: amount in satoshis\",\n );\n }\n const { amount, recipient } = tx;\n const result = await callRequest<{ txid: string }>(\"sendTransfer\", {\n recipients: [{ address: recipient, amount: amount.toString() }],\n });\n return result.txid;\n };\n\n return {\n capabilities: CAPS_SATS_CONNECT,\n chainPlatform: \"bitcoin\",\n\n async connect() {\n await loadAccounts();\n },\n\n disconnect: () => {\n cachedAccounts = [];\n return Promise.resolve();\n },\n\n async getAccount() {\n const first = cachedAccounts[0];\n if (first !== undefined) {\n return buildAccount(first, chain);\n }\n const accounts = await loadAccounts();\n const firstFresh = accounts[0];\n return firstFresh === undefined ? null : buildAccount(firstFresh, chain);\n },\n\n async getAccounts() {\n const accounts = cachedAccounts.length > 0 ? cachedAccounts : await loadAccounts();\n return accounts.map((a) => buildAccount(a, chain));\n },\n\n getBalance: () =>\n Promise.resolve({\n decimals: 8,\n formatted: \"0\",\n symbol: \"BTC\",\n value: 0n,\n }),\n\n getSigner: () => Promise.resolve(provider),\n\n getTransactionReceipt: () => Promise.resolve({ status: \"Pending\" as const }),\n\n icon: GENERIC_BITCOIN_ICON,\n id,\n name,\n\n async requestAccounts() {\n await loadAccounts();\n },\n\n sendTx: (tx) => sendTransferTx(tx),\n\n sendTxToChain: (tx, _targetChainId, _account, cb) => {\n cb?.();\n return sendTransferTx(tx);\n },\n\n async signMessage(msg) {\n const address = cachedAccounts[0];\n if (address === undefined) {\n throw new Error(\"No connected account\");\n }\n const result = await callRequest<{ messageHash?: string; signature: string }>(\"signMessage\", {\n address,\n message: new TextDecoder().decode(msg),\n });\n return { signature: base64ToBytes(result.signature), signedMessage: msg };\n },\n\n async signTransaction(tx) {\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\n \"Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())\",\n );\n }\n // sats-connect's signPsbt takes a base64 PSBT; encode our bytes\n // and decode the response back to bytes.\n const result = await callRequest<{ psbt: string }>(\"signPsbt\", {\n psbt: bytesToBase64(tx),\n });\n return base64ToBytes(result.psbt);\n },\n\n switchChain: (target) => {\n if (target.namespace !== \"bip122\") {\n throw new Error(\n `Bitcoin adapter received non-Bitcoin chain \"${target.id}\". Pass a chain with namespace \"bip122\".`,\n );\n }\n chain = target;\n return Promise.resolve();\n },\n };\n};\n\nexport type { SatsConnectProvider };\nexport { buildSatsConnectAdapter };\n","import type { ChainBase, ConnectorEvent, WalletAdapter, WalletCapabilities } from \"@usebutr/core\";\nimport { base64ToBytes, bytesToHex, hexToBytes } from \"@usebutr/core\";\nimport { buildAccount } from \"@usebutr/wallet-standard-shared\";\n\nimport { BITCOIN_CHAINS } from \"../chains\";\n\nimport { GENERIC_BITCOIN_ICON } from \"./icon\";\n\n/** UniSat-style provider: a single object on `window.unisat` with the\n * same four methods every UniSat-derivative wallet exposes (UniSat\n * itself, OKX Wallet's Bitcoin path at `window.okxwallet.bitcoin`). */\ntype UnisatProvider = {\n getAccounts: () => Promise<ReadonlyArray<string>>;\n getNetwork?: () => Promise<\"livenet\" | \"mainnet\" | \"testnet\" | \"signet\">;\n on?: (\n event: \"accountsChanged\" | \"networkChanged\",\n listener: (...args: Array<unknown>) => void,\n ) => void;\n pushPsbt?: (psbtHex: string) => Promise<string>;\n removeListener?: (\n event: \"accountsChanged\" | \"networkChanged\",\n listener: (...args: Array<unknown>) => void,\n ) => void;\n requestAccounts: () => Promise<ReadonlyArray<string>>;\n sendBitcoin?: (recipient: string, amount: number) => Promise<string>;\n signMessage: (message: string, type?: \"ecdsa\" | \"bip322-simple\") => Promise<string>;\n signPsbt: (psbtHex: string, options?: Record<string, unknown>) => Promise<string>;\n};\n\nconst CAPS_UNISAT: WalletCapabilities = {\n getBalance: false,\n getTransactionReceipt: false,\n requestAccounts: true,\n sendTransaction: true,\n signIn: false,\n signMessage: true,\n signTransaction: true,\n subscribe: true,\n switchAccount: false,\n switchChain: false,\n};\n\n/**\n * Wrap a UniSat-style provider (`window.unisat`, `window.okxwallet.bitcoin`,\n * `window.btc`) into a butr `WalletAdapter`.\n *\n * The UniSat shape is consistent enough that one adapter handles every\n * derivative. Differences (network names, `sendBitcoin` presence) are\n * gated by feature detection per call.\n */\nconst toStringArray = (value: unknown): Array<string> =>\n Array.isArray(value) ? value.filter((item): item is string => typeof item === \"string\") : [];\n\nconst buildUnisatAdapter = (id: string, name: string, provider: UnisatProvider): WalletAdapter => {\n let chain: ChainBase = BITCOIN_CHAINS.mainnet;\n const listenersSet = new Set<(event: ConnectorEvent) => void>();\n\n const sendBitcoinTx = (tx: unknown): Promise<string> => {\n if (typeof provider.sendBitcoin !== \"function\") {\n throw new TypeError(`Wallet ${name} does not expose sendBitcoin`);\n }\n if (\n typeof tx !== \"object\" ||\n tx === null ||\n !(\"recipient\" in tx) ||\n typeof tx.recipient !== \"string\" ||\n !(\"amount\" in tx) ||\n typeof tx.amount !== \"bigint\"\n ) {\n throw new TypeError(\n \"Bitcoin sendTx expects { amount: bigint, recipient: string }: amount in satoshis\",\n );\n }\n const { amount, recipient } = tx;\n return provider.sendBitcoin(recipient, Number(amount));\n };\n\n const refreshChain = async () => {\n if (typeof provider.getNetwork !== \"function\") {\n return;\n }\n try {\n const network = await provider.getNetwork();\n if (network === \"testnet\") {\n chain = BITCOIN_CHAINS.testnet;\n } else if (network === \"signet\") {\n chain = BITCOIN_CHAINS.signet;\n } else {\n chain = BITCOIN_CHAINS.mainnet;\n }\n } catch {\n void 0;\n }\n };\n\n return {\n capabilities: CAPS_UNISAT,\n chainPlatform: \"bitcoin\",\n\n async connect(opts) {\n if (opts?.silent === true) {\n const accounts = await provider.getAccounts();\n if (accounts.length === 0) {\n throw new Error(\"No authorized accounts for silent reconnect\");\n }\n await refreshChain();\n return;\n }\n await provider.requestAccounts();\n await refreshChain();\n },\n\n disconnect: () =>\n // No portable disconnect across UniSat-shaped wallets. butr's\n // reducer marks the wallet disconnected on its side regardless.\n Promise.resolve(),\n\n async getAccount() {\n const accounts = await provider.getAccounts();\n const first = accounts[0];\n if (first === undefined) {\n return null;\n }\n await refreshChain();\n return buildAccount(first, chain);\n },\n\n async getAccounts() {\n const accounts = await provider.getAccounts();\n if (accounts.length === 0) {\n return [];\n }\n await refreshChain();\n return accounts.map((a) => buildAccount(a, chain));\n },\n\n getBalance: () =>\n Promise.resolve({\n decimals: 8,\n formatted: \"0\",\n symbol: \"BTC\",\n value: 0n,\n }),\n\n getSigner: () => Promise.resolve(provider),\n\n getTransactionReceipt: () => Promise.resolve({ status: \"Pending\" as const }),\n\n icon: GENERIC_BITCOIN_ICON,\n id,\n name,\n\n async requestAccounts() {\n await provider.requestAccounts();\n await refreshChain();\n },\n\n sendTx: (tx) => sendBitcoinTx(tx),\n\n sendTxToChain: (tx, _targetChainIdDecimal, _account, cb) => {\n cb?.();\n return sendBitcoinTx(tx);\n },\n\n async signMessage(msg) {\n // UniSat takes a UTF-8 message and returns a base64 signature.\n // We return the raw signature bytes (decoded from base64) and the\n // bytes the wallet signed (pass-through of the input). UniSat\n // doesn't expose a \"pre-image\" so the input itself is the signed\n // message from butr's perspective.\n const text = new TextDecoder().decode(msg);\n const signatureB64 = await provider.signMessage(text);\n return { signature: base64ToBytes(signatureB64), signedMessage: msg };\n },\n\n async signTransaction(tx) {\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\n \"Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())\",\n );\n }\n const signedHex = await provider.signPsbt(bytesToHex(tx));\n return hexToBytes(signedHex);\n },\n\n subscribe(listener) {\n listenersSet.add(listener);\n const onAccountsChanged = (...args: ReadonlyArray<unknown>) => {\n const accounts = toStringArray(args[0]);\n if (accounts.length === 0) {\n listener({ type: \"disconnected\" });\n return;\n }\n const built = accounts.map((a) => buildAccount(a, chain));\n const first = built[0];\n if (first === undefined) {\n return;\n }\n listener({ account: first, accounts: built, type: \"accountChanged\" });\n };\n const onNetworkChanged = () => {\n void refreshChain();\n };\n provider.on?.(\"accountsChanged\", onAccountsChanged);\n provider.on?.(\"networkChanged\", onNetworkChanged);\n return () => {\n provider.removeListener?.(\"accountsChanged\", onAccountsChanged);\n provider.removeListener?.(\"networkChanged\", onNetworkChanged);\n listenersSet.delete(listener);\n };\n },\n\n switchChain: (target) => {\n if (target.namespace !== \"bip122\") {\n throw new Error(\n `Bitcoin adapter received non-Bitcoin chain \"${target.id}\". Pass a chain with namespace \"bip122\".`,\n );\n }\n // UniSat-shaped wallets pick their network via the user's\n // extension UI; we update local state so consumers see it, but\n // the wallet itself doesn't switch.\n chain = target;\n void refreshChain();\n return Promise.resolve();\n },\n };\n};\n\nexport type { UnisatProvider };\nexport { buildUnisatAdapter };\n","import type { WalletAdapter } from \"@usebutr/core\";\n\nimport type { SatsConnectProvider } from \"./sats-connect\";\nimport { buildSatsConnectAdapter } from \"./sats-connect\";\nimport type { UnisatProvider } from \"./unisat\";\nimport { buildUnisatAdapter } from \"./unisat\";\n\nconst DEFAULT_SETTLE_MS = 150;\n\ntype InjectedHost = {\n btc?: UnisatProvider;\n okxwallet?: { bitcoin?: UnisatProvider };\n unisat?: UnisatProvider;\n XverseProviders?: { BitcoinProvider?: SatsConnectProvider };\n};\n\ntype InjectedBitcoinDiscoveryOptions = {\n /** Suppresses emission when Wallet Standard discovery has already\n * registered a Bitcoin adapter for the same browser session. */\n hasAnyWalletStandardAdapter?: () => boolean;\n /** Settle deadline in ms before the fallback fires. Same default and\n * intent as the EVM injected fallback. */\n settleMs?: number;\n /** Override the global host (tests, iframes). */\n target?: InjectedHost | null;\n};\n\nconst readHost = (target: InjectedBitcoinDiscoveryOptions[\"target\"]): InjectedHost | null => {\n if (target !== undefined) {\n return target;\n }\n if (typeof window === \"undefined\") {\n return null;\n }\n // `window` carries the injected wallet globals (`unisat`, `okxwallet`,\n // etc.) as an untyped host object; `InjectedHost` names the four keys\n // butr probes, all optional and read defensively in `probeProviders`.\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- untyped injected-wallet window globals\n return window as unknown as InjectedHost;\n};\n\nconst probeProviders = (host: InjectedHost): Array<WalletAdapter> => {\n const adapters: Array<WalletAdapter> = [];\n const seen = new Set<UnisatProvider | SatsConnectProvider>();\n\n if (host.unisat && !seen.has(host.unisat)) {\n seen.add(host.unisat);\n adapters.push(buildUnisatAdapter(\"injected:bitcoin:unisat\", \"Unisat\", host.unisat));\n }\n\n const okxBitcoin = host.okxwallet?.bitcoin;\n if (okxBitcoin && !seen.has(okxBitcoin)) {\n seen.add(okxBitcoin);\n adapters.push(buildUnisatAdapter(\"injected:bitcoin:okx\", \"OKX Wallet (Bitcoin)\", okxBitcoin));\n }\n\n const xverse = host.XverseProviders?.BitcoinProvider;\n if (xverse && !seen.has(xverse)) {\n seen.add(xverse);\n adapters.push(buildSatsConnectAdapter(\"injected:bitcoin:xverse\", \"Xverse\", xverse));\n }\n\n if (host.btc && !seen.has(host.btc)) {\n seen.add(host.btc);\n adapters.push(\n buildUnisatAdapter(\"injected:bitcoin:legacy\", \"Browser Bitcoin wallet\", host.btc),\n );\n }\n\n return adapters;\n};\n\n/**\n * Last-resort discovery path for Bitcoin wallets that DON'T announce via\n * Wallet Standard. Probes, in order: `window.unisat`,\n * `window.okxwallet.bitcoin`, `window.XverseProviders.BitcoinProvider`,\n * `window.btc`.\n *\n * Wallet Standard wallets (Phantom, Magic Eden, Leather, modern OKX\n * builds) are already handled by `discoverBitcoinAdapters`. This\n * fallback waits `settleMs` (default 150ms), consults\n * `hasAnyWalletStandardAdapter` if provided, and emits adapters for\n * any injected providers that weren't already covered.\n *\n * Each emitted adapter has a stable id prefix (`injected:bitcoin:*`)\n * so consumers can distinguish them in the picker UI.\n */\nconst discoverInjectedBitcoinAdapter = (\n onAdapter: (adapter: WalletAdapter) => void,\n options: InjectedBitcoinDiscoveryOptions = {},\n): (() => void) => {\n const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;\n let cancelled = false;\n const timer = setTimeout(() => {\n if (cancelled) {\n return;\n }\n if (options.hasAnyWalletStandardAdapter?.() === true) {\n return;\n }\n const host = readHost(options.target);\n if (host === null) {\n return;\n }\n const adapters = probeProviders(host);\n for (const adapter of adapters) {\n onAdapter(adapter);\n }\n }, settleMs);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n };\n};\n\nexport type { InjectedBitcoinDiscoveryOptions };\nexport { GENERIC_BITCOIN_ICON } from \"./icon\";\nexport { discoverInjectedBitcoinAdapter };\n","import type { PlatformDiscoverer } from \"@usebutr/core\";\n\nimport { discoverInjectedBitcoinAdapter } from \"./injected\";\nimport { discoverBitcoinAdapters } from \"./wallet-standard-adapter\";\n\n/**\n * Bitcoin's `PlatformDiscoverer`: Wallet Standard primary (Phantom,\n * Magic Eden, Leather, OKX); legacy fallback covers sats-connect\n * (Xverse), `window.unisat`, OKX legacy, and `window.btc`. The\n * fallback defers to Wallet Standard via `hasAnyPrimaryAdapter`.\n */\nconst bitcoinDiscoverer: PlatformDiscoverer = {\n fallback: {\n subscribe: (onAdapter, opts) =>\n discoverInjectedBitcoinAdapter(onAdapter, {\n hasAnyWalletStandardAdapter: opts.hasAnyPrimaryAdapter,\n }),\n },\n subscribe: discoverBitcoinAdapters,\n};\n\nexport { bitcoinDiscoverer };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/capabilities.ts","../src/wallet-standard-adapter.ts","../src/chains.ts","../src/injected/icon.ts","../src/injected/sats-connect.ts","../src/injected/unisat.ts","../src/injected/index.ts","../src/discoverer.ts"],"sourcesContent":["import type { WalletCapabilities } from \"@usebutr/core\";\nimport { buildWalletCapabilities } from \"@usebutr/wallet-standard-shared\";\n\ntype BitcoinCapabilityInput = {\n chainCount: number;\n features: {\n events: boolean;\n sendTransfer: boolean;\n signMessage: boolean;\n signPsbt: boolean;\n };\n};\n\n/**\n * `signIn` is false: there is no Sign-In-With-Bitcoin standard, and BIP-322\n * is message-signing oriented rather than auth. `sendTransfer` leaves UTXO\n * selection, fees and broadcast to the wallet; `signPsbt` is sign-only.\n */\nconst resolveBitcoinCapabilities = (input: BitcoinCapabilityInput): WalletCapabilities =>\n buildWalletCapabilities({\n chainCount: input.chainCount,\n events: input.features.events,\n sendTransaction: input.features.sendTransfer,\n signIn: false,\n signMessage: input.features.signMessage,\n signTransaction: input.features.signPsbt,\n });\n\nexport type { BitcoinCapabilityInput };\nexport { resolveBitcoinCapabilities };\n","import type { TransactionInput, WalletAdapter } from \"@usebutr/core\";\nimport {\n createWalletStandardCore,\n discoverWalletStandard,\n getFeature,\n slugify,\n} from \"@usebutr/wallet-standard-shared\";\nimport type { WalletStandardFeature, WalletStandardWallet } from \"@usebutr/wallet-standard-shared\";\n\nimport { resolveBitcoinCapabilities } from \"./capabilities\";\nimport type {\n BitcoinSendTransferFeature,\n BitcoinSignMessageFeature,\n BitcoinSignPsbtFeature,\n} from \"./wallet-standard-types\";\n\nconst BITCOIN_PREFIX = \"bip122:\";\nconst BITCOIN_DECIMALS = 8;\nconst BITCOIN_MAINNET_ID = \"bip122:000000000019d6689c085ae165831e93\";\n\nconst isBitcoinSignMessageFeature = (\n feature: WalletStandardFeature,\n): feature is WalletStandardFeature & BitcoinSignMessageFeature =>\n \"signMessage\" in feature && typeof feature.signMessage === \"function\";\n\nconst isBitcoinSignPsbtFeature = (\n feature: WalletStandardFeature,\n): feature is WalletStandardFeature & BitcoinSignPsbtFeature =>\n \"signPsbt\" in feature && typeof feature.signPsbt === \"function\";\n\nconst isBitcoinSendTransferFeature = (\n feature: WalletStandardFeature,\n): feature is WalletStandardFeature & BitcoinSendTransferFeature =>\n \"sendTransfer\" in feature && typeof feature.sendTransfer === \"function\";\n\n/**\n * `sendTx` takes `{ amount, recipient }` for `bitcoin:sendTransfer` and\n * `signTransaction` takes raw PSBT bytes for `bitcoin:signPsbt`. Balance\n * and receipt reads would need an Esplora/Electrum client butr doesn't ship.\n */\nconst buildBitcoinAdapter = (\n wallet: WalletStandardWallet,\n /** Optional. Called with a function that pushes a synthetic\n * `disconnected` event to all current subscribers. The discovery\n * layer invokes it on Wallet Standard `unregister`. */\n registerDisconnector?: (emit: () => void) => void,\n): WalletAdapter | null => {\n const core = createWalletStandardCore({\n chainPrefix: BITCOIN_PREFIX,\n id: slugify(\"btc\", wallet.name),\n label: \"Bitcoin\",\n namespace: \"bip122\",\n platform: \"Bitcoin\",\n preferredChainIds: [BITCOIN_MAINNET_ID],\n registerDisconnector,\n trackChainChanges: true,\n wallet,\n });\n if (core === null) {\n return null;\n }\n\n const signMessage = getFeature(wallet, \"bitcoin:signMessage\", isBitcoinSignMessageFeature);\n const signPsbt = getFeature(wallet, \"bitcoin:signPsbt\", isBitcoinSignPsbtFeature);\n const sendTransfer = getFeature(wallet, \"bitcoin:sendTransfer\", isBitcoinSendTransferFeature);\n\n /** Resolve a caller-supplied target into a chain the wallet advertises,\n * accepting either a full CAIP-2 id or a bare genesis-hash reference. */\n const resolveTargetChain = (targetChainId: string): string => {\n const candidate = targetChainId.startsWith(BITCOIN_PREFIX)\n ? targetChainId\n : `${BITCOIN_PREFIX}${targetChainId}`;\n if (!wallet.chains.includes(candidate)) {\n throw new Error(\n `Wallet ${wallet.name} does not advertise chain \"${candidate}\". Available: ${wallet.chains.join(\", \")}`,\n );\n }\n return candidate;\n };\n\n const sendTransferTx = async (\n tx: TransactionInput,\n account?: { walletAddress: string },\n chain?: string,\n ): Promise<string> => {\n if (sendTransfer === undefined) {\n throw new Error(`Wallet ${wallet.name} does not advertise bitcoin:sendTransfer`);\n }\n if (\n typeof tx !== \"object\" ||\n tx === null ||\n !(\"amount\" in tx) ||\n typeof tx.amount !== \"bigint\" ||\n !(\"recipient\" in tx) ||\n typeof tx.recipient !== \"string\"\n ) {\n throw new TypeError(\n \"Bitcoin sendTx expects { amount: bigint, recipient: string }: pass the recipient address and an amount in satoshis\",\n );\n }\n const { amount, recipient } = tx;\n const output = await sendTransfer.sendTransfer({\n account: core.resolveAccount(account),\n amount,\n chain: chain ?? core.currentChainId(),\n recipient,\n });\n return output.txid;\n };\n\n const adapter: WalletAdapter = {\n ...core,\n capabilities: resolveBitcoinCapabilities({\n chainCount: core.chainCount,\n features: {\n events: core.hasEvents,\n sendTransfer: Boolean(sendTransfer),\n signMessage: Boolean(signMessage),\n signPsbt: Boolean(signPsbt),\n },\n }),\n chainPlatform: \"bitcoin\",\n\n getBalance: () =>\n Promise.resolve({\n decimals: BITCOIN_DECIMALS,\n formatted: \"0\",\n symbol: \"BTC\",\n value: 0n,\n }),\n\n getTransactionReceipt: () => Promise.resolve({ status: \"Pending\" as const }),\n\n async requestAccounts() {\n await core.connect();\n },\n\n sendTx: (tx, account) => sendTransferTx(tx, account),\n\n async sendTxToChain(tx, targetChainId, account, cb) {\n const target = resolveTargetChain(targetChainId);\n if (target !== core.currentChainId()) {\n cb?.();\n }\n const txid = await sendTransferTx(tx, account, target);\n return txid;\n },\n\n async signMessage(msg, account) {\n if (signMessage === undefined) {\n throw new Error(`Wallet ${wallet.name} does not advertise bitcoin:signMessage`);\n }\n const output = await signMessage.signMessage({\n account: core.resolveAccount(account),\n message: msg,\n });\n return { signature: output.signature, signedMessage: output.signedMessage };\n },\n };\n\n if (signPsbt !== undefined) {\n adapter.signTransaction = async (tx, account) => {\n const wsAccount = core.resolveAccount(account);\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\n \"Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())\",\n );\n }\n const output = await signPsbt.signPsbt({\n account: wsAccount,\n chain: core.currentChainId(),\n psbt: tx,\n });\n return output.signedPsbt;\n };\n }\n\n return adapter;\n};\n\n/** Requires the optional `@wallet-standard/app` peer dep. */\nconst discoverBitcoinAdapters = (onAdapter: (adapter: WalletAdapter) => void): (() => void) =>\n discoverWalletStandard(onAdapter, (wallet, registerDisconnector) =>\n buildBitcoinAdapter(wallet, registerDisconnector),\n );\n\nexport { buildBitcoinAdapter, discoverBitcoinAdapters };\n","import type { ChainBase } from \"@usebutr/core\";\n\n/**\n * CAIP-2 Bitcoin references are the first 8 bytes of each network's\n * genesis block hash, which is what wallets advertise in `wallet.chains`.\n * No major wallet exposes a switch-chain RPC, so chain stays local state.\n */\nconst BITCOIN_CHAINS = {\n mainnet: {\n id: \"bip122:000000000019d6689c085ae165831e93\",\n name: \"Bitcoin\",\n namespace: \"bip122\",\n reference: \"000000000019d6689c085ae165831e93\",\n },\n signet: {\n id: \"bip122:00000008819873e925422c1ff0f99f7c\",\n name: \"Bitcoin Signet\",\n namespace: \"bip122\",\n reference: \"00000008819873e925422c1ff0f99f7c\",\n },\n testnet: {\n id: \"bip122:000000000933ea01ad0ee984209779ba\",\n name: \"Bitcoin Testnet\",\n namespace: \"bip122\",\n reference: \"000000000933ea01ad0ee984209779ba\",\n },\n} as const satisfies Record<string, ChainBase>;\n\nconst BITCOIN_CHAINS_LIST: ReadonlyArray<ChainBase> = Object.values(BITCOIN_CHAINS);\n\nexport { BITCOIN_CHAINS, BITCOIN_CHAINS_LIST };\n","/**\n * Inlined as a data URI so the picker doesn't flash, and shared by both\n * injected adapters as the fallback when a wallet advertises no icon.\n */\nconst GENERIC_BITCOIN_ICON =\n \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2Y3OTMxYSIgc3Ryb2tlPSJub25lIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIvPjx0ZXh0IHg9IjEyIiB5PSIxNyIgZm9udC1mYW1pbHk9InNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+QjwvdGV4dD48L3N2Zz4=\";\n\nexport { GENERIC_BITCOIN_ICON };\n","import type {\n Account,\n ChainBase,\n TransactionInput,\n WalletAdapter,\n WalletCapabilities,\n} from \"@usebutr/core\";\nimport { base64ToBytes, bytesToBase64 } from \"@usebutr/core\";\nimport { buildAccount } from \"@usebutr/wallet-standard-shared\";\nimport { z } from \"zod\";\n\nimport { BITCOIN_CHAINS } from \"../chains\";\n\nimport { GENERIC_BITCOIN_ICON } from \"./icon\";\n\n/** sats-connect (Xverse) shape; a JSON-RPC-ish `request(method, params)`. */\ntype RpcValue =\n | boolean\n | number\n | string\n | null\n | ReadonlyArray<RpcValue>\n | { readonly [key: string]: RpcValue | undefined };\n\ntype SatsConnectProvider = {\n request: (\n method: string,\n params?: Readonly<Record<string, RpcValue>>,\n ) => Promise<{ error?: { message: string }; result?: RpcValue }>;\n};\n\n/** One address entry of a sats-connect account result. `purpose` tags the\n * role: `\"payment\"` is the spendable address, `\"ordinals\"` is a taproot\n * address that `sendTransfer` never debits. */\ntype SatsAddress = {\n address: string;\n publicKey?: string;\n purpose?: string;\n};\n\ntype ConnectedSession = {\n addresses: ReadonlyArray<SatsAddress>;\n payment: string;\n status: \"connected\";\n};\n\ntype Session = ConnectedSession | { status: \"disconnected\" };\n\nconst CAPS_SATS_CONNECT: WalletCapabilities = {\n getBalance: false,\n getTransactionReceipt: false,\n requestAccounts: true,\n sendTransaction: true,\n signIn: false,\n signMessage: true,\n signTransaction: true,\n subscribe: false,\n switchAccount: false,\n switchChain: false,\n};\n\nconst ACCOUNT_PURPOSES = [\"payment\", \"ordinals\"];\nconst CONNECT_MESSAGE = \"Connect to butr\";\n\nconst satsAddressSchema = z.object({\n address: z.string(),\n publicKey: z.string().optional(),\n purpose: z.string().optional(),\n});\n\nconst accountResultSchema = z\n .object({ addresses: z.array(satsAddressSchema).optional() })\n .nullable();\nconst sendTransferResultSchema = z.object({ txid: z.string() });\nconst signMessageResultSchema = z.object({\n messageHash: z.string().optional(),\n signature: z.string(),\n});\nconst signPsbtResultSchema = z.object({ psbt: z.string() });\n\nconst pickPaymentAddress = (addresses: ReadonlyArray<SatsAddress>): SatsAddress | undefined =>\n addresses.find((a) => a.purpose === \"payment\") ?? addresses[0];\n\n/**\n * Only the payment address is exposed as an Account: sats-connect's\n * `sendTransfer` takes no sender and always debits it. The taproot ordinals\n * address stays reachable through `getSigner()` and `signMessage`'s account.\n */\nconst buildSatsConnectAdapter = (\n id: string,\n name: string,\n provider: SatsConnectProvider,\n): WalletAdapter => {\n let chain: ChainBase = BITCOIN_CHAINS.mainnet;\n let session: Session = { status: \"disconnected\" };\n\n const callRequest = async <Schema extends z.ZodType>(\n method: string,\n schema: Schema,\n params?: Readonly<Record<string, RpcValue>>,\n ): Promise<z.output<Schema>> => {\n const response = await provider.request(method, params);\n if (response.error) {\n throw new Error(`[butr/bitcoin] sats-connect ${method} failed: ${response.error.message}`);\n }\n return schema.parse(response.result);\n };\n\n const startSession = async (\n method: string,\n params: Readonly<Record<string, RpcValue>>,\n ): Promise<void> => {\n const result = await callRequest(method, accountResultSchema, params);\n const addresses = result?.addresses ?? [];\n const payment = pickPaymentAddress(addresses);\n if (payment === undefined) {\n throw new Error(`Wallet ${name} returned no addresses from ${method}`);\n }\n session = { addresses, payment: payment.address, status: \"connected\" };\n };\n\n const requestSession = () =>\n startSession(\"getAccounts\", { message: CONNECT_MESSAGE, purposes: ACCOUNT_PURPOSES });\n\n const requireSession = (): ConnectedSession => {\n if (session.status !== \"connected\") {\n throw new Error(\"No connected account\");\n }\n return session;\n };\n\n const resolveAddress = (account?: Account): string => {\n const active = requireSession();\n if (account === undefined) {\n return active.payment;\n }\n const match = active.addresses.find((a) => a.address === account.walletAddress);\n if (match === undefined) {\n throw new Error(`Account ${account.walletAddress} is not exposed by ${name}`);\n }\n return match.address;\n };\n\n const sendTransferTx = async (tx: TransactionInput, account?: Account): Promise<string> => {\n const sender = resolveAddress(account);\n if (sender !== requireSession().payment) {\n throw new Error(\n `Wallet ${name} always debits its payment address; ${sender} cannot be used as the sender.`,\n );\n }\n if (\n typeof tx !== \"object\" ||\n tx === null ||\n !(\"recipient\" in tx) ||\n typeof tx.recipient !== \"string\" ||\n !(\"amount\" in tx) ||\n typeof tx.amount !== \"bigint\"\n ) {\n throw new TypeError(\n \"Bitcoin sendTx expects { amount: bigint, recipient: string }: amount in satoshis\",\n );\n }\n const { amount, recipient } = tx;\n const result = await callRequest(\"sendTransfer\", sendTransferResultSchema, {\n recipients: [{ address: recipient, amount: amount.toString() }],\n });\n return result.txid;\n };\n\n return {\n capabilities: CAPS_SATS_CONNECT,\n chainPlatform: \"bitcoin\",\n\n async connect(opts) {\n if (opts?.silent === true) {\n // `wallet_getAccount` reads already-granted permissions without\n // showing wallet UI; `getAccounts` IS Xverse's approval prompt.\n // Builds predating `wallet_getAccount` reject here, which butr's\n // hydration treats as a clean restore failure.\n await startSession(\"wallet_getAccount\", { addresses: ACCOUNT_PURPOSES });\n return;\n }\n await requestSession();\n },\n\n disconnect: () => {\n session = { status: \"disconnected\" };\n return Promise.resolve();\n },\n\n getAccount: () =>\n Promise.resolve(session.status === \"connected\" ? buildAccount(session.payment, chain) : null),\n\n getAccounts: () =>\n Promise.resolve(session.status === \"connected\" ? [buildAccount(session.payment, chain)] : []),\n\n getBalance: () =>\n Promise.resolve({\n decimals: 8,\n formatted: \"0\",\n symbol: \"BTC\",\n value: 0n,\n }),\n\n getSigner: () => Promise.resolve(provider),\n\n getTransactionReceipt: () => Promise.resolve({ status: \"Pending\" as const }),\n\n icon: GENERIC_BITCOIN_ICON,\n id,\n name,\n\n requestAccounts: () => requestSession(),\n\n sendTx: (tx, account) => sendTransferTx(tx, account),\n\n sendTxToChain: (tx, _targetChainId, account, cb) => {\n cb?.();\n return sendTransferTx(tx, account);\n },\n\n async signMessage(msg, account) {\n const address = resolveAddress(account);\n const result = await callRequest(\"signMessage\", signMessageResultSchema, {\n address,\n message: new TextDecoder().decode(msg),\n });\n return { signature: base64ToBytes(result.signature), signedMessage: msg };\n },\n\n async signTransaction(tx) {\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\n \"Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())\",\n );\n }\n const result = await callRequest(\"signPsbt\", signPsbtResultSchema, {\n psbt: bytesToBase64(tx),\n });\n return base64ToBytes(result.psbt);\n },\n\n switchChain: (target) => {\n if (target.namespace !== \"bip122\") {\n throw new Error(\n `Bitcoin adapter received non-Bitcoin chain \"${target.id}\". Pass a chain with namespace \"bip122\".`,\n );\n }\n chain = target;\n return Promise.resolve();\n },\n };\n};\n\nexport type { RpcValue, SatsConnectProvider };\nexport { buildSatsConnectAdapter };\n","import type { ChainBase, TransactionInput, WalletAdapter, WalletCapabilities } from \"@usebutr/core\";\nimport { base64ToBytes, bytesToHex, hexToBytes } from \"@usebutr/core\";\nimport { buildAccount } from \"@usebutr/wallet-standard-shared\";\n\nimport { BITCOIN_CHAINS } from \"../chains\";\n\nimport { GENERIC_BITCOIN_ICON } from \"./icon\";\n\n/** UniSat-style provider: a single object on `window.unisat` with the\n * same four methods every UniSat-derivative wallet exposes (UniSat\n * itself, OKX Wallet's Bitcoin path at `window.okxwallet.bitcoin`). */\ntype UnisatProvider = {\n getAccounts: () => Promise<ReadonlyArray<string>>;\n getNetwork?: () => Promise<\"livenet\" | \"mainnet\" | \"testnet\" | \"signet\">;\n on?: (\n event: \"accountsChanged\" | \"networkChanged\",\n listener: (...args: Array<UnisatEventValue>) => void,\n ) => void;\n pushPsbt?: (psbtHex: string) => Promise<string>;\n removeListener?: (\n event: \"accountsChanged\" | \"networkChanged\",\n listener: (...args: Array<UnisatEventValue>) => void,\n ) => void;\n requestAccounts: () => Promise<ReadonlyArray<string>>;\n sendBitcoin?: (recipient: string, amount: number) => Promise<string>;\n signMessage: (message: string, type?: \"ecdsa\" | \"bip322-simple\") => Promise<string>;\n signPsbt: (psbtHex: string, options?: UnisatSignPsbtOptions) => Promise<string>;\n};\n\ntype UnisatEventItem = number | string | null;\ntype UnisatEventValue = ReadonlyArray<UnisatEventItem> | string | undefined;\n\ntype UnisatSignPsbtOptions = {\n autoFinalized?: boolean;\n};\n\nconst CAPS_UNISAT: WalletCapabilities = {\n getBalance: false,\n getTransactionReceipt: false,\n requestAccounts: true,\n sendTransaction: true,\n signIn: false,\n signMessage: true,\n signTransaction: true,\n subscribe: true,\n switchAccount: false,\n switchChain: false,\n};\n\nconst toStringArray = (value: UnisatEventValue | undefined): Array<string> =>\n Array.isArray(value) ? value.filter((item): item is string => typeof item === \"string\") : [];\n\n/**\n * The UniSat shape is consistent enough across `window.unisat`,\n * `window.okxwallet.bitcoin` and `window.btc` that one adapter covers all\n * three; the differences (network names, `sendBitcoin`) are probed per call.\n */\nconst buildUnisatAdapter = (id: string, name: string, provider: UnisatProvider): WalletAdapter => {\n let chain: ChainBase = BITCOIN_CHAINS.mainnet;\n\n const sendBitcoinTx = async (tx: TransactionInput): Promise<string> => {\n if (typeof provider.sendBitcoin !== \"function\") {\n throw new TypeError(`Wallet ${name} does not expose sendBitcoin`);\n }\n if (\n typeof tx !== \"object\" ||\n tx === null ||\n !(\"recipient\" in tx) ||\n typeof tx.recipient !== \"string\" ||\n !(\"amount\" in tx) ||\n typeof tx.amount !== \"bigint\"\n ) {\n throw new TypeError(\n \"Bitcoin sendTx expects { amount: bigint, recipient: string }: amount in satoshis\",\n );\n }\n const { amount, recipient } = tx;\n const txid = await provider.sendBitcoin(recipient, Number(amount));\n return txid;\n };\n\n const refreshChain = async () => {\n if (typeof provider.getNetwork !== \"function\") {\n return;\n }\n try {\n const network = await provider.getNetwork();\n if (network === \"testnet\") {\n chain = BITCOIN_CHAINS.testnet;\n } else if (network === \"signet\") {\n chain = BITCOIN_CHAINS.signet;\n } else {\n chain = BITCOIN_CHAINS.mainnet;\n }\n } catch {\n void 0;\n }\n };\n\n return {\n capabilities: CAPS_UNISAT,\n chainPlatform: \"bitcoin\",\n\n async connect(opts) {\n if (opts?.silent === true) {\n const accounts = await provider.getAccounts();\n if (accounts.length === 0) {\n throw new Error(\"No authorized accounts for silent reconnect\");\n }\n await refreshChain();\n return;\n }\n await provider.requestAccounts();\n await refreshChain();\n },\n\n disconnect: () => Promise.resolve(),\n\n async getAccount() {\n const accounts = await provider.getAccounts();\n const first = accounts[0];\n if (first === undefined) {\n return null;\n }\n await refreshChain();\n return buildAccount(first, chain);\n },\n\n async getAccounts() {\n const accounts = await provider.getAccounts();\n if (accounts.length === 0) {\n return [];\n }\n await refreshChain();\n return accounts.map((a) => buildAccount(a, chain));\n },\n\n getBalance: () =>\n Promise.resolve({\n decimals: 8,\n formatted: \"0\",\n symbol: \"BTC\",\n value: 0n,\n }),\n\n getSigner: () => Promise.resolve(provider),\n\n getTransactionReceipt: () => Promise.resolve({ status: \"Pending\" as const }),\n\n icon: GENERIC_BITCOIN_ICON,\n id,\n name,\n\n async requestAccounts() {\n await provider.requestAccounts();\n await refreshChain();\n },\n\n sendTx: (tx) => sendBitcoinTx(tx),\n\n sendTxToChain: (tx, _targetChainIdDecimal, _account, cb) => {\n cb?.();\n return sendBitcoinTx(tx);\n },\n\n async signMessage(msg) {\n const text = new TextDecoder().decode(msg);\n const signatureB64 = await provider.signMessage(text);\n return { signature: base64ToBytes(signatureB64), signedMessage: msg };\n },\n\n async signTransaction(tx) {\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\n \"Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())\",\n );\n }\n const signedHex = await provider.signPsbt(bytesToHex(tx));\n return hexToBytes(signedHex);\n },\n\n subscribe(listener) {\n const onAccountsChanged = (...args: ReadonlyArray<UnisatEventValue>) => {\n const accounts = toStringArray(args[0]);\n if (accounts.length === 0) {\n listener({ type: \"disconnected\" });\n return;\n }\n const built = accounts.map((a) => buildAccount(a, chain));\n const first = built[0];\n if (first === undefined) {\n return;\n }\n listener({ account: first, accounts: built, type: \"accountChanged\" });\n };\n const onNetworkChanged = () => {\n void refreshChain();\n };\n provider.on?.(\"accountsChanged\", onAccountsChanged);\n provider.on?.(\"networkChanged\", onNetworkChanged);\n return () => {\n provider.removeListener?.(\"accountsChanged\", onAccountsChanged);\n provider.removeListener?.(\"networkChanged\", onNetworkChanged);\n };\n },\n\n // `capabilities.switchChain` is false: UniSat exposes no way to ask the\n // wallet to change network, so the wallet's own setting is authoritative\n // and every account read re-reads it. This validates the request and then\n // reports what the wallet actually says, rather than assigning the target\n // locally and having the next read silently contradict it.\n async switchChain(target) {\n if (target.namespace !== \"bip122\") {\n throw new Error(\n `Bitcoin adapter received non-Bitcoin chain \"${target.id}\". Pass a chain with namespace \"bip122\".`,\n );\n }\n chain = target;\n await refreshChain();\n },\n };\n};\n\nexport type { UnisatProvider };\nexport { buildUnisatAdapter };\n","import type { WalletAdapter } from \"@usebutr/core\";\n\nimport type { SatsConnectProvider } from \"./sats-connect\";\nimport { buildSatsConnectAdapter } from \"./sats-connect\";\nimport type { UnisatProvider } from \"./unisat\";\nimport { buildUnisatAdapter } from \"./unisat\";\n\nconst DEFAULT_SETTLE_MS = 150;\n\ntype InjectedHost = {\n btc?: UnisatProvider;\n okxwallet?: { bitcoin?: UnisatProvider };\n unisat?: UnisatProvider;\n XverseProviders?: { BitcoinProvider?: SatsConnectProvider };\n};\n\ndeclare global {\n // oxlint-disable-next-line typescript/consistent-type-definitions -- DOM globals require interface merging.\n interface Window {\n btc?: UnisatProvider;\n okxwallet?: { bitcoin?: UnisatProvider };\n unisat?: UnisatProvider;\n XverseProviders?: { BitcoinProvider?: SatsConnectProvider };\n }\n}\n\ntype InjectedBitcoinDiscoveryOptions = {\n /** Suppresses emission when Wallet Standard discovery has already\n * registered a Bitcoin adapter for the same browser session. */\n hasAnyWalletStandardAdapter?: () => boolean;\n /** Settle deadline in ms before the fallback fires. Same default and\n * intent as the EVM injected fallback. */\n settleMs?: number;\n /** Override the global host (tests, iframes). */\n target?: InjectedHost | null;\n};\n\nconst readHost = (target: InjectedBitcoinDiscoveryOptions[\"target\"]): InjectedHost | null => {\n if (target !== undefined) {\n return target;\n }\n if (typeof window === \"undefined\") {\n return null;\n }\n return window;\n};\n\nconst probeProviders = (host: InjectedHost): Array<WalletAdapter> => {\n const adapters: Array<WalletAdapter> = [];\n const seen = new Set<UnisatProvider | SatsConnectProvider>();\n\n if (host.unisat && !seen.has(host.unisat)) {\n seen.add(host.unisat);\n adapters.push(buildUnisatAdapter(\"injected:bitcoin:unisat\", \"Unisat\", host.unisat));\n }\n\n const okxBitcoin = host.okxwallet?.bitcoin;\n if (okxBitcoin && !seen.has(okxBitcoin)) {\n seen.add(okxBitcoin);\n adapters.push(buildUnisatAdapter(\"injected:bitcoin:okx\", \"OKX Wallet (Bitcoin)\", okxBitcoin));\n }\n\n const xverse = host.XverseProviders?.BitcoinProvider;\n if (xverse && !seen.has(xverse)) {\n seen.add(xverse);\n adapters.push(buildSatsConnectAdapter(\"injected:bitcoin:xverse\", \"Xverse\", xverse));\n }\n\n if (host.btc && !seen.has(host.btc)) {\n seen.add(host.btc);\n adapters.push(\n buildUnisatAdapter(\"injected:bitcoin:legacy\", \"Browser Bitcoin wallet\", host.btc),\n );\n }\n\n return adapters;\n};\n\n/**\n * Last-resort path for wallets that don't announce via Wallet Standard:\n * `window.unisat`, OKX, Xverse, `window.btc`. The `settleMs` wait lets\n * Wallet Standard announce first, else a wallet on both channels lists twice.\n */\nconst discoverInjectedBitcoinAdapter = (\n onAdapter: (adapter: WalletAdapter) => void,\n options: InjectedBitcoinDiscoveryOptions = {},\n): (() => void) => {\n const settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;\n let cancelled = false;\n const timer = setTimeout(() => {\n if (cancelled) {\n return;\n }\n if (options.hasAnyWalletStandardAdapter?.() === true) {\n return;\n }\n const host = readHost(options.target);\n if (host === null) {\n return;\n }\n const adapters = probeProviders(host);\n for (const adapter of adapters) {\n onAdapter(adapter);\n }\n }, settleMs);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n };\n};\n\nexport type { InjectedBitcoinDiscoveryOptions };\nexport { GENERIC_BITCOIN_ICON } from \"./icon\";\nexport { discoverInjectedBitcoinAdapter };\n","import type { PlatformDiscoverer } from \"@usebutr/core\";\n\nimport { discoverInjectedBitcoinAdapter } from \"./injected\";\nimport { discoverBitcoinAdapters } from \"./wallet-standard-adapter\";\n\n/**\n * Wallet Standard is primary; the legacy fallback covers sats-connect\n * (Xverse), `window.unisat`, OKX legacy and `window.btc`, and defers to\n * the primary channel via `hasAnyPrimaryAdapter`.\n */\nconst bitcoinDiscoverer: PlatformDiscoverer = {\n fallback: {\n subscribe: (onAdapter, opts) =>\n discoverInjectedBitcoinAdapter(onAdapter, {\n hasAnyWalletStandardAdapter: opts.hasAnyPrimaryAdapter,\n }),\n },\n subscribe: discoverBitcoinAdapters,\n};\n\nexport { bitcoinDiscoverer };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,23 @@
1
1
  {
2
2
  "name": "@usebutr/bitcoin",
3
- "version": "1.0.1",
3
+ "version": "1.1.2",
4
4
  "description": "Wallet Standard + injected (sats-connect / Unisat / OKX) wallet discovery and adapters for butr (Bitcoin).",
5
+ "keywords": [
6
+ "bitcoin",
7
+ "butr",
8
+ "dapp",
9
+ "psbt",
10
+ "react",
11
+ "typescript",
12
+ "unisat",
13
+ "wallet",
14
+ "web3",
15
+ "xverse"
16
+ ],
17
+ "homepage": "https://docs.usebutr.com/api/bitcoin",
18
+ "bugs": {
19
+ "url": "https://github.com/pedroapfilho/usebutr/issues"
20
+ },
5
21
  "license": "MIT",
6
22
  "author": "Pedro Filho <pedro@filho.me>",
7
23
  "repository": {
@@ -10,7 +26,8 @@
10
26
  "directory": "packages/bitcoin"
11
27
  },
12
28
  "files": [
13
- "dist"
29
+ "dist",
30
+ "CHANGELOG.md"
14
31
  ],
15
32
  "type": "module",
16
33
  "sideEffects": false,
@@ -25,12 +42,13 @@
25
42
  "access": "public"
26
43
  },
27
44
  "dependencies": {
28
- "@usebutr/core": "1.1.0",
29
- "@usebutr/wallet-standard-shared": "0.4.1"
45
+ "zod": "^4.4.3",
46
+ "@usebutr/core": "2.0.1",
47
+ "@usebutr/wallet-standard-shared": "1.0.0"
30
48
  },
31
49
  "devDependencies": {
32
50
  "@wallet-standard/app": "^1.1.1",
33
- "typescript": "^6.0.3",
51
+ "typescript": "^7.0.2",
34
52
  "vitest": "^4.1.10",
35
53
  "@repo/config-vitest": "0.0.0",
36
54
  "@repo/typescript-config": "0.0.0"
@@ -47,7 +65,6 @@
47
65
  "build": "tsdown",
48
66
  "clean": "rm -rf dist",
49
67
  "dev": "tsdown --watch",
50
- "lint": "oxlint src",
51
68
  "test": "vitest run --passWithNoTests",
52
69
  "test:coverage": "vitest run --coverage --passWithNoTests",
53
70
  "typecheck": "tsc -p tsconfig.json --noEmit"