@usebutr/bitcoin 1.1.2 → 2.0.0

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 CHANGED
@@ -1,5 +1,38 @@
1
1
  # @usebutr/bitcoin
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#203](https://github.com/pedroapfilho/usebutr/pull/203) [`1263f86`](https://github.com/pedroapfilho/usebutr/commit/1263f8666afd43b4f7dbf8df1763709c3edf09d9) Thanks [@pedroapfilho](https://github.com/pedroapfilho)! - Adapters now say what they can do by having the method: `capabilities` is gone, and an optional method (`sendTx`, `signMessage`, `getBalance`, `switchChain`, …) exists only when it works for that wallet. No adapter returns a placeholder balance or a receipt that stays pending anymore.
8
+
9
+ - `sendTx(tx, { account, chain })` replaces `sendTx` and `sendTxToChain`, and `signMessage`, `signTransaction` and `getBalance` take options objects. `chain` is a `ChainBase`. An adapter either routes the call to that chain, switches the wallet to it, or rejects; none ignores it.
10
+ - An `account` the wallet does not expose now rejects instead of signing with the first account. `chain.name` is the chain's name, never the wallet's.
11
+ - `getAccounts()` resolves the active account first. `getAccount` and `switchAccount` are removed.
12
+ - `getSigner()` resolves a tagged `WalletSigner` (`eip1193`, `wallet-standard`, `walletconnect`, `ledger-*`, …), so it can be narrowed with `switch (signer.kind)` instead of cast.
13
+ - `createWalletManager(config, { initialState })` owns discovery, hydration and persistence, and works without React. `config.sources` replaces the `discovery`, `connectors` and `createConnector` props. `fromAdapters` registers WalletConnect, Ledger and hand-built adapters, which now also appear in `useDiscoveredWallets()`.
14
+ - `WalletManagerProvider` takes `config` and `initialState`. `useSigner(wallet)` and `useBalance(wallet, { token, account })` take the wallet entry itself, and id-taking hooks read the active wallet only when the id is omitted (`null` reads none). The hooks are `useWalletManager`, `useWalletState`, `useConnect`, `useWallet`, `useSelectedWallet` (now typed per platform), `useAccounts`, `useConnectionStatus`, `useIsHydrated`, `useIsReconnecting`, `useDiscoveredWallets`, `useConnectedWallets`, the `…ByPlatform` groupings, `useBalance` and `useSigner`.
15
+ - `manager.connect()` and `useConnect().connectAsync()` resolve the `ConnectedWallet` and reject with a `ConnectionError`, now an `Error` subclass with a `kind` and a new `WalletNotFound` kind. `useConnect().connect()` never rejects: the failure lands in its `error`.
16
+ - `WalletPersistence` is `load()` / `save(state)`, and `createWalletStorage` replaces `new WalletStorage`. Storage keys are unchanged.
17
+ - Chain registries (`EVM_CHAINS`, `SVM_CHAINS`, …, `CHAINS_BY_PLATFORM`) move to `@usebutr/core`, and `BITCOIN_CHAINS` gains `testnet4`. `@usebutr/wallets` loads every platform's signer kinds, so an app that imports only it can narrow `signer.kind`.
18
+ - `@usebutr/react` requires React 19, which its `use()` call already needed.
19
+ - WalletConnect requests on Solana, Sui and Bitcoin now carry their chain, so they no longer fall through to the first namespace in a multi-namespace session. Solana takes `SVM_CHAINS` chains and maps them to the genesis-hash ids WalletConnect sessions use.
20
+ - Ledger's Solana `signTransaction` returns the full signed transaction, Sui signs the intent message, and Bitcoin `signMessage` returns a BIP-137 signature.
21
+ - `@usebutr/testing`'s fake adapter resolves a `signer` you pass instead of registering a test-only signer kind.
22
+ - `setAccount` selects an exposed account without changing any account's chain. Unknown accounts are ignored; chain changes come from the adapter.
23
+ - WalletConnect EVM events use the namespace in `session_event`, including empty account lists that end the connection. Local chain switches still update the manager.
24
+ - Failed persistence saves wait for every key write to finish before reporting failure, so delayed writes cannot overwrite a later disconnect.
25
+ - `@usebutr/svm/transaction` provides the shared legacy/v0 signing codec used by Ledger and WalletConnect, with consistent layout and signature validation.
26
+
27
+ See the migration guide at https://docs.usebutr.com/migration.
28
+
29
+ ### Patch Changes
30
+
31
+ - [#199](https://github.com/pedroapfilho/usebutr/pull/199) [`5d1f234`](https://github.com/pedroapfilho/usebutr/commit/5d1f23403263f7a1f8a48be061727972fe749b5f) Thanks [@pedroapfilho](https://github.com/pedroapfilho)! - Raise the zod dependency floor to 4.6.1.
32
+ - Updated dependencies [[`1263f86`](https://github.com/pedroapfilho/usebutr/commit/1263f8666afd43b4f7dbf8df1763709c3edf09d9), [`5d1f234`](https://github.com/pedroapfilho/usebutr/commit/5d1f23403263f7a1f8a48be061727972fe749b5f)]:
33
+ - @usebutr/core@3.0.0
34
+ - @usebutr/wallet-standard-shared@2.0.0
35
+
3
36
  ## 1.1.2
4
37
 
5
38
  ### Patch Changes
package/README.md CHANGED
@@ -11,22 +11,74 @@ connection-state library. Your application owns the picker UI and chain client.
11
11
  npm install @usebutr/react @usebutr/core @usebutr/bitcoin zustand @wallet-standard/app
12
12
  ```
13
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.
14
+ React 19 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
15
 
16
16
  ## Usage
17
17
 
18
18
  ```tsx
19
19
  import type { ReactNode } from "react";
20
- import { createWalletSource } from "@usebutr/core";
20
+ import type { WalletManagerConfig } from "@usebutr/core";
21
21
  import { discoverBitcoinAdapters } from "@usebutr/bitcoin";
22
22
  import { WalletManagerProvider } from "@usebutr/react";
23
23
 
24
- const discovery = createWalletSource(discoverBitcoinAdapters);
24
+ const config: WalletManagerConfig = { sources: [discoverBitcoinAdapters] };
25
+
25
26
  export const WalletProvider = ({ children }: { children: ReactNode }) => (
26
- <WalletManagerProvider discovery={discovery}>{children}</WalletManagerProvider>
27
+ <WalletManagerProvider config={config}>{children}</WalletManagerProvider>
27
28
  );
28
29
  ```
29
30
 
31
+ `discoverInjectedBitcoinAdapter` covers wallets that never announce through
32
+ Wallet Standard (`window.unisat`, `window.okxwallet.bitcoin`, Xverse's
33
+ sats-connect provider, `window.btc`). `bitcoinDiscoverer` pairs both channels
34
+ so the injected one stays quiet once Wallet Standard found a Bitcoin wallet;
35
+ `autoDiscovery(["bitcoin"])` from `@usebutr/wallets` wires it for you.
36
+
37
+ ## Transactions
38
+
39
+ A method exists only when the wallet can back it, so check before calling:
40
+
41
+ ```ts
42
+ import { BITCOIN_CHAINS } from "@usebutr/core";
43
+
44
+ if (wallet.connector.sendTx) {
45
+ const txid = await wallet.connector.sendTx(
46
+ { amount: 10_000n, recipient: "tb1q…" }, // satoshis
47
+ { account: wallet.account, chain: BITCOIN_CHAINS.testnet },
48
+ );
49
+ }
50
+ ```
51
+
52
+ - `sendTx(transfer, { account?, chain? })` asks the wallet to build, sign and
53
+ broadcast a transfer and resolves the txid.
54
+ - `signTransaction(psbt, { account?, chain? })` signs PSBT bytes
55
+ (`psbt.toBuffer()`) without broadcasting them.
56
+ - `signMessage(message, { account? })` signs a UTF-8 message.
57
+
58
+ Wallet Standard wallets route `chain` per call and gain `switchChain` when they
59
+ advertise more than one Bitcoin network. Injected wallets have one network for
60
+ the whole wallet: they switch it before acting (Unisat through
61
+ `switchNetwork`, Xverse through `wallet_changeNetwork`), or reject with a
62
+ `ChainMismatch` `ConnectionError` when the provider cannot switch (OKX,
63
+ `window.btc`). Unisat-style wallets sign with their active account only, and
64
+ Xverse sends from its payment address only; any other `account` rejects.
65
+
66
+ Balances and receipts need an Esplora or Electrum client, which butr does not
67
+ ship, so these adapters define neither.
68
+
69
+ ## Signers
70
+
71
+ `getSigner()` resolves the object the adapter drives, tagged by transport:
72
+
73
+ ```ts
74
+ const signer = await wallet.connector.getSigner();
75
+ switch (signer.kind) {
76
+ case "wallet-standard": // signer.wallet
77
+ case "unisat": // signer.provider, the UniSat-style provider
78
+ case "sats-connect": // signer.provider, Xverse's BitcoinProvider
79
+ }
80
+ ```
81
+
30
82
  ## Documentation
31
83
 
32
84
  - [Package reference](https://docs.usebutr.com/api/bitcoin)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { BitcoinAdapter, PlatformDiscoverer, WalletAdapter } from "@usebutr/core";
1
2
  import { WalletStandardWallet, WalletStandardWalletAccount } from "@usebutr/wallet-standard-shared";
2
- import { ChainBase, PlatformDiscoverer, WalletAdapter, WalletCapabilities } from "@usebutr/core";
3
3
  //#region src/wallet-standard-types.d.ts
4
4
  type BitcoinSignMessageInput = {
5
5
  account: WalletStandardWalletAccount;
@@ -49,63 +49,18 @@ type BitcoinSendTransferFeature = {
49
49
  //#endregion
50
50
  //#region src/wallet-standard-adapter.d.ts
51
51
  /**
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.
52
+ * `bitcoin:sendTransfer` backs `sendTx` and `bitcoin:signPsbt` backs
53
+ * `signTransaction`, both routing `options.chain` per call. Balance and
54
+ * receipt reads would need an Esplora/Electrum client butr doesn't ship.
55
55
  */
56
56
  declare const buildBitcoinAdapter: (wallet: WalletStandardWallet,
57
57
  /** Optional. Called with a function that pushes a synthetic
58
58
  * `disconnected` event to all current subscribers. The discovery
59
59
  * layer invokes it on Wallet Standard `unregister`. */
60
- registerDisconnector?: (emit: () => void) => void) => WalletAdapter | null;
60
+ registerDisconnector?: (emit: () => void) => void) => BitcoinAdapter | null;
61
61
  /** Requires the optional `@wallet-standard/app` peer dep. */
62
62
  declare const discoverBitcoinAdapters: (onAdapter: (adapter: WalletAdapter) => void) => (() => void);
63
63
  //#endregion
64
- //#region src/chains.d.ts
65
- /**
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.
69
- */
70
- declare const BITCOIN_CHAINS: {
71
- readonly mainnet: {
72
- readonly id: "bip122:000000000019d6689c085ae165831e93";
73
- readonly name: "Bitcoin";
74
- readonly namespace: "bip122";
75
- readonly reference: "000000000019d6689c085ae165831e93";
76
- };
77
- readonly signet: {
78
- readonly id: "bip122:00000008819873e925422c1ff0f99f7c";
79
- readonly name: "Bitcoin Signet";
80
- readonly namespace: "bip122";
81
- readonly reference: "00000008819873e925422c1ff0f99f7c";
82
- };
83
- readonly testnet: {
84
- readonly id: "bip122:000000000933ea01ad0ee984209779ba";
85
- readonly name: "Bitcoin Testnet";
86
- readonly namespace: "bip122";
87
- readonly reference: "000000000933ea01ad0ee984209779ba";
88
- };
89
- };
90
- declare const BITCOIN_CHAINS_LIST: ReadonlyArray<ChainBase>;
91
- //#endregion
92
- //#region src/capabilities.d.ts
93
- type BitcoinCapabilityInput = {
94
- chainCount: number;
95
- features: {
96
- events: boolean;
97
- sendTransfer: boolean;
98
- signMessage: boolean;
99
- signPsbt: boolean;
100
- };
101
- };
102
- /**
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.
106
- */
107
- declare const resolveBitcoinCapabilities: (input: BitcoinCapabilityInput) => WalletCapabilities;
108
- //#endregion
109
64
  //#region src/injected/sats-connect.d.ts
110
65
  /** sats-connect (Xverse) shape; a JSON-RPC-ish `request(method, params)`. */
111
66
  type RpcValue = boolean | number | string | null | ReadonlyArray<RpcValue> | {
@@ -121,12 +76,15 @@ type SatsConnectProvider = {
121
76
  };
122
77
  //#endregion
123
78
  //#region src/injected/unisat.d.ts
79
+ type UnisatNetwork = "livenet" | "mainnet" | "signet" | "testnet";
80
+ /** The only networks UniSat's `switchNetwork` accepts. */
81
+ type SwitchableNetwork = "livenet" | "testnet";
124
82
  /** UniSat-style provider: a single object on `window.unisat` with the
125
83
  * same four methods every UniSat-derivative wallet exposes (UniSat
126
84
  * itself, OKX Wallet's Bitcoin path at `window.okxwallet.bitcoin`). */
127
85
  type UnisatProvider = {
128
86
  getAccounts: () => Promise<ReadonlyArray<string>>;
129
- getNetwork?: () => Promise<"livenet" | "mainnet" | "testnet" | "signet">;
87
+ getNetwork?: () => Promise<UnisatNetwork>;
130
88
  on?: (event: "accountsChanged" | "networkChanged", listener: (...args: Array<UnisatEventValue>) => void) => void;
131
89
  pushPsbt?: (psbtHex: string) => Promise<string>;
132
90
  removeListener?: (event: "accountsChanged" | "networkChanged", listener: (...args: Array<UnisatEventValue>) => void) => void;
@@ -134,6 +92,8 @@ type UnisatProvider = {
134
92
  sendBitcoin?: (recipient: string, amount: number) => Promise<string>;
135
93
  signMessage: (message: string, type?: "ecdsa" | "bip322-simple") => Promise<string>;
136
94
  signPsbt: (psbtHex: string, options?: UnisatSignPsbtOptions) => Promise<string>;
95
+ /** UniSat has it; OKX's `window.okxwallet.bitcoin` is pinned to mainnet. */
96
+ switchNetwork?: (network: SwitchableNetwork) => Promise<void>;
137
97
  };
138
98
  type UnisatEventItem = number | string | null;
139
99
  type UnisatEventValue = ReadonlyArray<UnisatEventItem> | string | undefined;
@@ -198,15 +158,20 @@ declare const bitcoinDiscoverer: PlatformDiscoverer;
198
158
  //#endregion
199
159
  //#region src/signer-augmentation.d.ts
200
160
  /**
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.
161
+ * The injected adapters hand back the raw provider they drive. Wallet
162
+ * Standard adapters register `"wallet-standard"` through
163
+ * `@usebutr/wallet-standard-shared`.
204
164
  */
205
165
  declare module "@usebutr/core" {
206
- interface SignerForPlatform {
207
- bitcoin: SatsConnectProvider | UnisatProvider | WalletStandardWallet;
166
+ interface WalletSignerRegistry {
167
+ "sats-connect": {
168
+ provider: SatsConnectProvider;
169
+ };
170
+ unisat: {
171
+ provider: UnisatProvider;
172
+ };
208
173
  }
209
174
  }
210
175
  //#endregion
211
- export { BITCOIN_CHAINS, BITCOIN_CHAINS_LIST, type BitcoinCapabilityInput, type BitcoinSendTransferFeature, type BitcoinSendTransferInput, type BitcoinSendTransferOutput, type BitcoinSignMessageFeature, type BitcoinSignMessageInput, type BitcoinSignMessageOutput, type BitcoinSignPsbtFeature, type BitcoinSignPsbtInput, type BitcoinSignPsbtOutput, GENERIC_BITCOIN_ICON, type InjectedBitcoinDiscoveryOptions, bitcoinDiscoverer, buildBitcoinAdapter, discoverBitcoinAdapters, discoverInjectedBitcoinAdapter, resolveBitcoinCapabilities };
176
+ export { type BitcoinSendTransferFeature, type BitcoinSendTransferInput, type BitcoinSendTransferOutput, type BitcoinSignMessageFeature, type BitcoinSignMessageInput, type BitcoinSignMessageOutput, type BitcoinSignPsbtFeature, type BitcoinSignPsbtInput, type BitcoinSignPsbtOutput, GENERIC_BITCOIN_ICON, type InjectedBitcoinDiscoveryOptions, bitcoinDiscoverer, buildBitcoinAdapter, discoverBitcoinAdapters, discoverInjectedBitcoinAdapter };
212
177
  //# sourceMappingURL=index.d.ts.map
@@ -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":";;;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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/wallet-standard-types.ts","../src/wallet-standard-adapter.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;;;;;;;;;cChCI,sBAAmB,QACf,sBAIe;;;;AAAA,wBAAC,8BACvB;;cAgEG,0BAAuB,YAAgB,SAAS;;;;KC3EjD,8CAKD,cAAc;YACF,cAAc;;KAEzB;EACH,UACE,gBACA,SAAS,SAAS,eAAe,eAC9B;IAAU;MAAU;;IAAmB,SAAS;;;;;KCZlD;;KAGA;;;;KAKA;EACH,mBAAmB,QAAQ;EAC3B,mBAAmB,QAAQ;EAC3B,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;;EAEhE,iBAAiB,SAAS,sBAAsB;;KAG7C;KACA,mBAAmB,cAAc;KAEjC;EACH;;;;;;;;cClDI;;;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;;;;;;;;;YCAb;IACR;MAAkB,UAAU;;IAC5B;MAAU,UAAU"}
package/dist/index.js CHANGED
@@ -1,151 +1,69 @@
1
- import { buildAccount, buildWalletCapabilities, createWalletStandardCore, discoverWalletStandard, getFeature, slugify } from "@usebutr/wallet-standard-shared";
2
- import { base64ToBytes, bytesToBase64, bytesToHex, hexToBytes } from "@usebutr/core";
1
+ import { BITCOIN_CHAINS, BITCOIN_CHAINS_LIST, ConnectionError, base64ToBytes, buildAccount, bytesToBase64, bytesToHex, hexToBytes, logWarn, resolveChain } from "@usebutr/core";
2
+ import { createWalletStandardCore, discoverWalletStandard, getFeature, slugify } from "@usebutr/wallet-standard-shared";
3
3
  import { z } from "zod";
4
4
 
5
- //#region src/capabilities.ts
6
- /**
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.
10
- */
11
- const resolveBitcoinCapabilities = (input) => buildWalletCapabilities({
12
- chainCount: input.chainCount,
13
- events: input.features.events,
14
- sendTransaction: input.features.sendTransfer,
15
- signIn: false,
16
- signMessage: input.features.signMessage,
17
- signTransaction: input.features.signPsbt
18
- });
19
-
20
- //#endregion
21
5
  //#region src/wallet-standard-adapter.ts
22
- const BITCOIN_PREFIX = "bip122:";
23
- const BITCOIN_DECIMALS = 8;
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";
28
6
  /**
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.
7
+ * `bitcoin:sendTransfer` backs `sendTx` and `bitcoin:signPsbt` backs
8
+ * `signTransaction`, both routing `options.chain` per call. Balance and
9
+ * receipt reads would need an Esplora/Electrum client butr doesn't ship.
32
10
  */
33
11
  const buildBitcoinAdapter = (wallet, registerDisconnector) => {
34
12
  const core = createWalletStandardCore({
35
- chainPrefix: BITCOIN_PREFIX,
13
+ chains: BITCOIN_CHAINS_LIST,
36
14
  id: slugify("btc", wallet.name),
37
15
  label: "Bitcoin",
38
16
  namespace: "bip122",
39
- platform: "Bitcoin",
40
- preferredChainIds: [BITCOIN_MAINNET_ID],
17
+ preferredChainIds: [BITCOIN_CHAINS.mainnet.id],
41
18
  registerDisconnector,
42
19
  trackChainChanges: true,
43
20
  wallet
44
21
  });
45
22
  if (core === null) return null;
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) => {
57
- if (sendTransfer === void 0) throw new Error(`Wallet ${wallet.name} does not advertise bitcoin:sendTransfer`);
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");
59
- const { amount, recipient } = tx;
60
- return (await sendTransfer.sendTransfer({
61
- account: core.resolveAccount(account),
62
- amount,
63
- chain: chain ?? core.currentChainId(),
64
- recipient
65
- })).txid;
66
- };
67
- const adapter = {
68
- ...core,
69
- capabilities: resolveBitcoinCapabilities({
70
- chainCount: core.chainCount,
71
- features: {
72
- events: core.hasEvents,
73
- sendTransfer: Boolean(sendTransfer),
74
- signMessage: Boolean(signMessage),
75
- signPsbt: Boolean(signPsbt)
76
- }
77
- }),
23
+ const sendTransfer = getFeature(wallet, "bitcoin:sendTransfer", "sendTransfer");
24
+ const signMessage = getFeature(wallet, "bitcoin:signMessage", "signMessage");
25
+ const signPsbt = getFeature(wallet, "bitcoin:signPsbt", "signPsbt");
26
+ return {
27
+ ...core.base,
78
28
  chainPlatform: "bitcoin",
79
- getBalance: () => Promise.resolve({
80
- decimals: BITCOIN_DECIMALS,
81
- formatted: "0",
82
- symbol: "BTC",
83
- value: 0n
84
- }),
85
- getTransactionReceipt: () => Promise.resolve({ status: "Pending" }),
86
- async requestAccounts() {
87
- await core.connect();
88
- },
89
- sendTx: (tx, account) => 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);
94
- },
95
- async signMessage(msg, account) {
96
- if (signMessage === void 0) throw new Error(`Wallet ${wallet.name} does not advertise bitcoin:signMessage`);
29
+ ...sendTransfer !== void 0 && { async sendTx({ amount, recipient }, options) {
30
+ return (await sendTransfer.sendTransfer({
31
+ account: core.resolveAccount(options?.account),
32
+ amount,
33
+ chain: core.resolveChainId(options?.chain),
34
+ recipient
35
+ })).txid;
36
+ } },
37
+ ...signMessage !== void 0 && { async signMessage(message, options) {
97
38
  const output = await signMessage.signMessage({
98
- account: core.resolveAccount(account),
99
- message: msg
39
+ account: core.resolveAccount(options?.account),
40
+ message
100
41
  });
101
42
  return {
102
43
  signature: output.signature,
103
44
  signedMessage: output.signedMessage
104
45
  };
105
- }
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;
46
+ } },
47
+ ...signPsbt !== void 0 && { async signTransaction(psbt, options) {
48
+ return (await signPsbt.signPsbt({
49
+ account: core.resolveAccount(options?.account),
50
+ chain: core.resolveChainId(options?.chain),
51
+ psbt
52
+ })).signedPsbt;
53
+ } }
115
54
  };
116
- return adapter;
117
55
  };
118
56
  /** Requires the optional `@wallet-standard/app` peer dep. */
119
- const discoverBitcoinAdapters = (onAdapter) => discoverWalletStandard(onAdapter, (wallet, registerDisconnector) => buildBitcoinAdapter(wallet, registerDisconnector));
57
+ const discoverBitcoinAdapters = (onAdapter) => discoverWalletStandard(onAdapter, buildBitcoinAdapter);
120
58
 
121
59
  //#endregion
122
- //#region src/chains.ts
123
- /**
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.
127
- */
128
- const BITCOIN_CHAINS = {
129
- mainnet: {
130
- id: "bip122:000000000019d6689c085ae165831e93",
131
- name: "Bitcoin",
132
- namespace: "bip122",
133
- reference: "000000000019d6689c085ae165831e93"
134
- },
135
- signet: {
136
- id: "bip122:00000008819873e925422c1ff0f99f7c",
137
- name: "Bitcoin Signet",
138
- namespace: "bip122",
139
- reference: "00000008819873e925422c1ff0f99f7c"
140
- },
141
- testnet: {
142
- id: "bip122:000000000933ea01ad0ee984209779ba",
143
- name: "Bitcoin Testnet",
144
- namespace: "bip122",
145
- reference: "000000000933ea01ad0ee984209779ba"
146
- }
60
+ //#region src/injected/chain.ts
61
+ const assertBitcoinChain = (chain) => {
62
+ if (chain.namespace !== "bip122") throw new Error(`Bitcoin adapter received non-Bitcoin chain "${chain.id}". Pass a chain with namespace "bip122".`);
147
63
  };
148
- const BITCOIN_CHAINS_LIST = Object.values(BITCOIN_CHAINS);
64
+ /** An injected wallet has one network for every call, so a target it cannot
65
+ * move to is refused rather than sent on the network it happens to be on. */
66
+ const chainMismatch = (wallet, current, target) => new ConnectionError("ChainMismatch", `Wallet ${wallet} is on ${current.name} and cannot switch to ${target.name}`);
149
67
 
150
68
  //#endregion
151
69
  //#region src/injected/icon.ts
@@ -157,48 +75,56 @@ const GENERIC_BITCOIN_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDov
157
75
 
158
76
  //#endregion
159
77
  //#region src/injected/sats-connect.ts
160
- const CAPS_SATS_CONNECT = {
161
- getBalance: false,
162
- getTransactionReceipt: false,
163
- requestAccounts: true,
164
- sendTransaction: true,
165
- signIn: false,
166
- signMessage: true,
167
- signTransaction: true,
168
- subscribe: false,
169
- switchAccount: false,
170
- switchChain: false
171
- };
172
78
  const ACCOUNT_PURPOSES = ["payment", "ordinals"];
173
79
  const CONNECT_MESSAGE = "Connect to butr";
80
+ const networkNameSchema = z.enum([
81
+ "Mainnet",
82
+ "Regtest",
83
+ "Signet",
84
+ "Testnet",
85
+ "Testnet4"
86
+ ]);
87
+ /** sats-connect names networks, not chains. Regtest sits outside butr's
88
+ * registry, so `resolveChain` names it by its CAIP-2 id. */
89
+ const NETWORK_CHAIN_IDS = {
90
+ Mainnet: BITCOIN_CHAINS.mainnet.id,
91
+ Regtest: "bip122:0f9188f13cb7b2c71f2a335e3a4fc328",
92
+ Signet: BITCOIN_CHAINS.signet.id,
93
+ Testnet: BITCOIN_CHAINS.testnet.id,
94
+ Testnet4: BITCOIN_CHAINS.testnet4.id
95
+ };
174
96
  const satsAddressSchema = z.object({
175
97
  address: z.string(),
176
98
  publicKey: z.string().optional(),
177
99
  purpose: z.string().optional()
178
100
  });
179
- const accountResultSchema = z.object({ addresses: z.array(satsAddressSchema).optional() }).nullable();
101
+ const accountResultSchema = z.union([z.array(satsAddressSchema), z.object({ addresses: z.array(satsAddressSchema) }).transform((result) => result.addresses)]);
102
+ const networkResultSchema = z.object({ bitcoin: z.object({ name: networkNameSchema }) });
180
103
  const sendTransferResultSchema = z.object({ txid: z.string() });
181
104
  const signMessageResultSchema = z.object({
182
105
  messageHash: z.string().optional(),
183
106
  signature: z.string()
184
107
  });
185
108
  const signPsbtResultSchema = z.object({ psbt: z.string() });
109
+ const changeNetworkResultSchema = z.unknown();
186
110
  const pickPaymentAddress = (addresses) => addresses.find((a) => a.purpose === "payment") ?? addresses[0];
111
+ const networkFor = (chain) => Object.entries(NETWORK_CHAIN_IDS).find(([, chainId]) => chainId === chain.id)?.[0];
187
112
  /**
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.
113
+ * Only the payment address is an Account (`sendTransfer` always debits it); `signMessage` also
114
+ * takes the ordinals address. The network is wallet-wide, so a call targeting another chain
115
+ * moves it with `wallet_changeNetwork` first.
191
116
  */
192
117
  const buildSatsConnectAdapter = (id, name, provider) => {
193
118
  let chain = BITCOIN_CHAINS.mainnet;
194
119
  let session = { status: "disconnected" };
120
+ const listeners = /* @__PURE__ */ new Set();
195
121
  const callRequest = async (method, schema, params) => {
196
122
  const response = await provider.request(method, params);
197
123
  if (response.error) throw new Error(`[butr/bitcoin] sats-connect ${method} failed: ${response.error.message}`);
198
124
  return schema.parse(response.result);
199
125
  };
200
126
  const startSession = async (method, params) => {
201
- const addresses = (await callRequest(method, accountResultSchema, params))?.addresses ?? [];
127
+ const addresses = await callRequest(method, accountResultSchema, params);
202
128
  const payment = pickPaymentAddress(addresses);
203
129
  if (payment === void 0) throw new Error(`Wallet ${name} returned no addresses from ${method}`);
204
130
  session = {
@@ -207,10 +133,12 @@ const buildSatsConnectAdapter = (id, name, provider) => {
207
133
  status: "connected"
208
134
  };
209
135
  };
210
- const requestSession = () => startSession("getAccounts", {
211
- message: CONNECT_MESSAGE,
212
- purposes: ACCOUNT_PURPOSES
213
- });
136
+ const readSession = () => startSession("wallet_getAccount", { addresses: ACCOUNT_PURPOSES });
137
+ const readChain = async () => {
138
+ const result = await callRequest("wallet_getNetwork", networkResultSchema);
139
+ return resolveChain(NETWORK_CHAIN_IDS[result.bitcoin.name], BITCOIN_CHAINS_LIST);
140
+ };
141
+ const exposedAccounts = () => session.status === "connected" ? [buildAccount(session.payment, chain)] : [];
214
142
  const requireSession = () => {
215
143
  if (session.status !== "connected") throw new Error("No connected account");
216
144
  return session;
@@ -222,199 +150,199 @@ const buildSatsConnectAdapter = (id, name, provider) => {
222
150
  if (match === void 0) throw new Error(`Account ${account.walletAddress} is not exposed by ${name}`);
223
151
  return match.address;
224
152
  };
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.`);
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");
229
- const { amount, recipient } = tx;
230
- return (await callRequest("sendTransfer", sendTransferResultSchema, { recipients: [{
231
- address: recipient,
232
- amount: amount.toString()
233
- }] })).txid;
153
+ const moveTo = async (target) => {
154
+ assertBitcoinChain(target);
155
+ const current = await readChain();
156
+ if (current.id !== target.id) {
157
+ const network = networkFor(target);
158
+ if (network === void 0) throw chainMismatch(name, current, target);
159
+ await callRequest("wallet_changeNetwork", changeNetworkResultSchema, { name: network });
160
+ }
161
+ if (chain.id === target.id) return;
162
+ await readSession();
163
+ chain = resolveChain(target.id, BITCOIN_CHAINS_LIST);
164
+ const accounts = exposedAccounts();
165
+ for (const listener of listeners) listener({
166
+ accounts,
167
+ type: "accountsChanged"
168
+ });
169
+ };
170
+ const prepare = async (options) => {
171
+ if (options?.chain !== void 0) await moveTo(options.chain);
172
+ return resolveAddress(options?.account);
234
173
  };
235
174
  return {
236
- capabilities: CAPS_SATS_CONNECT,
237
175
  chainPlatform: "bitcoin",
238
- async connect(opts) {
239
- if (opts?.silent === true) {
240
- await startSession("wallet_getAccount", { addresses: ACCOUNT_PURPOSES });
241
- return;
242
- }
243
- await requestSession();
176
+ async connect(options) {
177
+ await (options?.silent === true ? readSession() : startSession("getAccounts", {
178
+ message: CONNECT_MESSAGE,
179
+ purposes: ACCOUNT_PURPOSES
180
+ }));
181
+ try {
182
+ chain = await readChain();
183
+ } catch {}
244
184
  },
245
185
  disconnect: () => {
246
186
  session = { status: "disconnected" };
247
187
  return Promise.resolve();
248
188
  },
249
- getAccount: () => Promise.resolve(session.status === "connected" ? buildAccount(session.payment, chain) : null),
250
- getAccounts: () => Promise.resolve(session.status === "connected" ? [buildAccount(session.payment, chain)] : []),
251
- getBalance: () => Promise.resolve({
252
- decimals: 8,
253
- formatted: "0",
254
- symbol: "BTC",
255
- value: 0n
189
+ getAccounts: () => Promise.resolve(exposedAccounts()),
190
+ getSigner: () => Promise.resolve({
191
+ kind: "sats-connect",
192
+ provider
256
193
  }),
257
- getSigner: () => Promise.resolve(provider),
258
- getTransactionReceipt: () => Promise.resolve({ status: "Pending" }),
259
194
  icon: GENERIC_BITCOIN_ICON,
260
195
  id,
261
196
  name,
262
- requestAccounts: () => requestSession(),
263
- sendTx: (tx, account) => sendTransferTx(tx, account),
264
- sendTxToChain: (tx, _targetChainId, account, cb) => {
265
- cb?.();
266
- return sendTransferTx(tx, account);
197
+ async sendTx({ amount, recipient }, options) {
198
+ const sender = await prepare(options);
199
+ if (sender !== requireSession().payment) throw new Error(`Wallet ${name} always debits its payment address; ${sender} cannot be used as the sender.`);
200
+ return (await callRequest("sendTransfer", sendTransferResultSchema, { recipients: [{
201
+ address: recipient,
202
+ amount: Number(amount)
203
+ }] })).txid;
267
204
  },
268
- async signMessage(msg, account) {
269
- const address = resolveAddress(account);
205
+ async signMessage(message, options) {
206
+ const address = resolveAddress(options?.account);
270
207
  const result = await callRequest("signMessage", signMessageResultSchema, {
271
208
  address,
272
- message: new TextDecoder().decode(msg)
209
+ message: new TextDecoder().decode(message)
273
210
  });
274
211
  return {
275
212
  signature: base64ToBytes(result.signature),
276
- signedMessage: msg
213
+ signedMessage: message
277
214
  };
278
215
  },
279
- async signTransaction(tx) {
280
- if (!(tx instanceof Uint8Array)) throw new TypeError("Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())");
281
- const result = await callRequest("signPsbt", signPsbtResultSchema, { psbt: bytesToBase64(tx) });
216
+ async signTransaction(psbt, options) {
217
+ await prepare(options);
218
+ const result = await callRequest("signPsbt", signPsbtResultSchema, { psbt: bytesToBase64(psbt) });
282
219
  return base64ToBytes(result.psbt);
283
220
  },
284
- switchChain: (target) => {
285
- if (target.namespace !== "bip122") throw new Error(`Bitcoin adapter received non-Bitcoin chain "${target.id}". Pass a chain with namespace "bip122".`);
286
- chain = target;
287
- return Promise.resolve();
288
- }
221
+ subscribe: (listener) => {
222
+ listeners.add(listener);
223
+ return () => {
224
+ listeners.delete(listener);
225
+ };
226
+ },
227
+ switchChain: moveTo
289
228
  };
290
229
  };
291
230
 
292
231
  //#endregion
293
232
  //#region src/injected/unisat.ts
294
- const CAPS_UNISAT = {
295
- getBalance: false,
296
- getTransactionReceipt: false,
297
- requestAccounts: true,
298
- sendTransaction: true,
299
- signIn: false,
300
- signMessage: true,
301
- signTransaction: true,
302
- subscribe: true,
303
- switchAccount: false,
304
- switchChain: false
305
- };
233
+ const NETWORK_CHAINS = /* @__PURE__ */ new Map([
234
+ ["livenet", BITCOIN_CHAINS.mainnet],
235
+ ["mainnet", BITCOIN_CHAINS.mainnet],
236
+ ["signet", BITCOIN_CHAINS.signet],
237
+ ["testnet", BITCOIN_CHAINS.testnet]
238
+ ]);
239
+ const SWITCH_NETWORKS = /* @__PURE__ */ new Map([[BITCOIN_CHAINS.mainnet.id, "livenet"], [BITCOIN_CHAINS.testnet.id, "testnet"]]);
306
240
  const toStringArray = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
307
241
  /**
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.
242
+ * One adapter covers `window.unisat`, `window.okxwallet.bitcoin` and `window.btc`.
243
+ * The network is wallet-wide, so a call targeting another chain switches it
244
+ * first, or rejects when the provider cannot switch.
311
245
  */
312
246
  const buildUnisatAdapter = (id, name, provider) => {
247
+ const getNetwork = provider.getNetwork?.bind(provider);
248
+ const on = provider.on?.bind(provider);
249
+ const sendBitcoin = provider.sendBitcoin?.bind(provider);
250
+ const switchNetwork = provider.switchNetwork?.bind(provider);
313
251
  let chain = BITCOIN_CHAINS.mainnet;
314
- const sendBitcoinTx = async (tx) => {
315
- if (typeof provider.sendBitcoin !== "function") throw new TypeError(`Wallet ${name} does not expose sendBitcoin`);
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");
317
- const { amount, recipient } = tx;
318
- return await provider.sendBitcoin(recipient, Number(amount));
252
+ const readChain = async () => {
253
+ if (getNetwork === void 0) return chain;
254
+ const network = await getNetwork();
255
+ const match = NETWORK_CHAINS.get(network);
256
+ if (match === void 0) throw new Error(`Wallet ${name} reported an unknown network "${network}"`);
257
+ return match;
319
258
  };
320
- const refreshChain = async () => {
321
- if (typeof provider.getNetwork !== "function") return;
259
+ const readAccounts = async () => {
260
+ const addresses = await provider.getAccounts();
261
+ if (addresses.length === 0) return [];
322
262
  try {
323
- const network = await provider.getNetwork();
324
- if (network === "testnet") chain = BITCOIN_CHAINS.testnet;
325
- else if (network === "signet") chain = BITCOIN_CHAINS.signet;
326
- else chain = BITCOIN_CHAINS.mainnet;
263
+ chain = await readChain();
327
264
  } catch {}
265
+ return addresses.map((address) => buildAccount(address, chain));
266
+ };
267
+ const assertActive = async (account) => {
268
+ if (account === void 0) return;
269
+ const [active] = await provider.getAccounts();
270
+ if (account.walletAddress !== active) throw new Error(`Wallet ${name} signs only with its active account, and ${account.walletAddress} is not it`);
271
+ };
272
+ const moveTo = async (target) => {
273
+ assertBitcoinChain(target);
274
+ chain = await readChain();
275
+ if (chain.id === target.id) return;
276
+ const network = SWITCH_NETWORKS.get(target.id);
277
+ if (switchNetwork === void 0 || network === void 0) throw chainMismatch(name, chain, target);
278
+ await switchNetwork(network);
279
+ chain = resolveChain(target.id, BITCOIN_CHAINS_LIST);
280
+ };
281
+ const prepare = async (options) => {
282
+ if (options?.chain !== void 0) await moveTo(options.chain);
283
+ await assertActive(options?.account);
328
284
  };
329
285
  return {
330
- capabilities: CAPS_UNISAT,
331
286
  chainPlatform: "bitcoin",
332
- async connect(opts) {
333
- if (opts?.silent === true) {
334
- if ((await provider.getAccounts()).length === 0) throw new Error("No authorized accounts for silent reconnect");
335
- await refreshChain();
287
+ async connect(options) {
288
+ if (options?.silent !== true) {
289
+ await provider.requestAccounts();
336
290
  return;
337
291
  }
338
- await provider.requestAccounts();
339
- await refreshChain();
340
- },
341
- disconnect: () => Promise.resolve(),
342
- async getAccount() {
343
- const first = (await provider.getAccounts())[0];
344
- if (first === void 0) return null;
345
- await refreshChain();
346
- return buildAccount(first, chain);
292
+ if ((await provider.getAccounts()).length === 0) throw new Error("No authorized accounts for silent reconnect");
347
293
  },
348
- async getAccounts() {
349
- const accounts = await provider.getAccounts();
350
- if (accounts.length === 0) return [];
351
- await refreshChain();
352
- return accounts.map((a) => buildAccount(a, chain));
353
- },
354
- getBalance: () => Promise.resolve({
355
- decimals: 8,
356
- formatted: "0",
357
- symbol: "BTC",
358
- value: 0n
294
+ getAccounts: readAccounts,
295
+ getSigner: () => Promise.resolve({
296
+ kind: "unisat",
297
+ provider
359
298
  }),
360
- getSigner: () => Promise.resolve(provider),
361
- getTransactionReceipt: () => Promise.resolve({ status: "Pending" }),
362
299
  icon: GENERIC_BITCOIN_ICON,
363
300
  id,
364
301
  name,
365
- async requestAccounts() {
366
- await provider.requestAccounts();
367
- await refreshChain();
368
- },
369
- sendTx: (tx) => sendBitcoinTx(tx),
370
- sendTxToChain: (tx, _targetChainIdDecimal, _account, cb) => {
371
- cb?.();
372
- return sendBitcoinTx(tx);
373
- },
374
- async signMessage(msg) {
375
- const text = new TextDecoder().decode(msg);
376
- const signatureB64 = await provider.signMessage(text);
302
+ async signMessage(message, options) {
303
+ await assertActive(options?.account);
304
+ const signature = await provider.signMessage(new TextDecoder().decode(message));
377
305
  return {
378
- signature: base64ToBytes(signatureB64),
379
- signedMessage: msg
306
+ signature: base64ToBytes(signature),
307
+ signedMessage: message
380
308
  };
381
309
  },
382
- async signTransaction(tx) {
383
- if (!(tx instanceof Uint8Array)) throw new TypeError("Bitcoin signTransaction expects a PSBT as Uint8Array (e.g. psbt.toBuffer())");
384
- const signedHex = await provider.signPsbt(bytesToHex(tx));
385
- return hexToBytes(signedHex);
310
+ async signTransaction(psbt, options) {
311
+ await prepare(options);
312
+ return hexToBytes(await provider.signPsbt(bytesToHex(psbt)));
386
313
  },
387
- subscribe(listener) {
314
+ ...sendBitcoin !== void 0 && { async sendTx({ amount, recipient }, options) {
315
+ await prepare(options);
316
+ return sendBitcoin(recipient, Number(amount));
317
+ } },
318
+ ...on !== void 0 && { subscribe: (listener) => {
319
+ const emit = (accounts) => {
320
+ listener(accounts.length === 0 ? { type: "disconnected" } : {
321
+ accounts,
322
+ type: "accountsChanged"
323
+ });
324
+ };
388
325
  const onAccountsChanged = (...args) => {
389
- const accounts = toStringArray(args[0]);
390
- if (accounts.length === 0) {
391
- listener({ type: "disconnected" });
392
- return;
326
+ emit(toStringArray(args[0]).map((address) => buildAccount(address, chain)));
327
+ };
328
+ const reemitAccounts = async () => {
329
+ try {
330
+ emit(await readAccounts());
331
+ } catch (error) {
332
+ logWarn(`[butr] ${name} accounts could not be re-read after a network change:`, error);
393
333
  }
394
- const built = accounts.map((a) => buildAccount(a, chain));
395
- const first = built[0];
396
- if (first === void 0) return;
397
- listener({
398
- account: first,
399
- accounts: built,
400
- type: "accountChanged"
401
- });
402
334
  };
403
335
  const onNetworkChanged = () => {
404
- refreshChain();
336
+ reemitAccounts();
405
337
  };
406
- provider.on?.("accountsChanged", onAccountsChanged);
407
- provider.on?.("networkChanged", onNetworkChanged);
338
+ on("accountsChanged", onAccountsChanged);
339
+ on("networkChanged", onNetworkChanged);
408
340
  return () => {
409
341
  provider.removeListener?.("accountsChanged", onAccountsChanged);
410
342
  provider.removeListener?.("networkChanged", onNetworkChanged);
411
343
  };
412
- },
413
- async switchChain(target) {
414
- if (target.namespace !== "bip122") throw new Error(`Bitcoin adapter received non-Bitcoin chain "${target.id}". Pass a chain with namespace "bip122".`);
415
- chain = target;
416
- await refreshChain();
417
- }
344
+ } },
345
+ ...switchNetwork !== void 0 && { switchChain: moveTo }
418
346
  };
419
347
  };
420
348
 
@@ -484,5 +412,5 @@ const bitcoinDiscoverer = {
484
412
  };
485
413
 
486
414
  //#endregion
487
- export { BITCOIN_CHAINS, BITCOIN_CHAINS_LIST, GENERIC_BITCOIN_ICON, bitcoinDiscoverer, buildBitcoinAdapter, discoverBitcoinAdapters, discoverInjectedBitcoinAdapter, resolveBitcoinCapabilities };
415
+ export { GENERIC_BITCOIN_ICON, bitcoinDiscoverer, buildBitcoinAdapter, discoverBitcoinAdapters, discoverInjectedBitcoinAdapter };
488
416
  //# sourceMappingURL=index.js.map
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 * `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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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/wallet-standard-adapter.ts","../src/injected/chain.ts","../src/injected/icon.ts","../src/injected/sats-connect.ts","../src/injected/unisat.ts","../src/injected/index.ts","../src/discoverer.ts"],"sourcesContent":["import type {\n AccountOptions,\n BitcoinAdapter,\n BitcoinTransfer,\n TransactionOptions,\n WalletAdapter,\n} from \"@usebutr/core\";\nimport { BITCOIN_CHAINS, BITCOIN_CHAINS_LIST } 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 type {\n BitcoinSendTransferFeature,\n BitcoinSignMessageFeature,\n BitcoinSignPsbtFeature,\n} from \"./wallet-standard-types\";\n\n/**\n * `bitcoin:sendTransfer` backs `sendTx` and `bitcoin:signPsbt` backs\n * `signTransaction`, both routing `options.chain` per call. Balance and\n * 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): BitcoinAdapter | null => {\n const core = createWalletStandardCore({\n chains: BITCOIN_CHAINS_LIST,\n id: slugify(\"btc\", wallet.name),\n label: \"Bitcoin\",\n namespace: \"bip122\",\n preferredChainIds: [BITCOIN_CHAINS.mainnet.id],\n registerDisconnector,\n trackChainChanges: true,\n wallet,\n });\n if (core === null) {\n return null;\n }\n\n const sendTransfer = getFeature<BitcoinSendTransferFeature>(\n wallet,\n \"bitcoin:sendTransfer\",\n \"sendTransfer\",\n );\n const signMessage = getFeature<BitcoinSignMessageFeature>(\n wallet,\n \"bitcoin:signMessage\",\n \"signMessage\",\n );\n const signPsbt = getFeature<BitcoinSignPsbtFeature>(wallet, \"bitcoin:signPsbt\", \"signPsbt\");\n\n return {\n ...core.base,\n chainPlatform: \"bitcoin\",\n ...(sendTransfer !== undefined && {\n async sendTx({ amount, recipient }: BitcoinTransfer, options?: TransactionOptions) {\n const output = await sendTransfer.sendTransfer({\n account: core.resolveAccount(options?.account),\n amount,\n chain: core.resolveChainId(options?.chain),\n recipient,\n });\n return output.txid;\n },\n }),\n ...(signMessage !== undefined && {\n async signMessage(message: Uint8Array, options?: AccountOptions) {\n const output = await signMessage.signMessage({\n account: core.resolveAccount(options?.account),\n message,\n });\n return { signature: output.signature, signedMessage: output.signedMessage };\n },\n }),\n ...(signPsbt !== undefined && {\n async signTransaction(psbt: Uint8Array, options?: TransactionOptions) {\n const output = await signPsbt.signPsbt({\n account: core.resolveAccount(options?.account),\n chain: core.resolveChainId(options?.chain),\n psbt,\n });\n return output.signedPsbt;\n },\n }),\n };\n};\n\n/** Requires the optional `@wallet-standard/app` peer dep. */\nconst discoverBitcoinAdapters = (onAdapter: (adapter: WalletAdapter) => void): (() => void) =>\n discoverWalletStandard(onAdapter, buildBitcoinAdapter);\n\nexport { buildBitcoinAdapter, discoverBitcoinAdapters };\n","import type { ChainBase } from \"@usebutr/core\";\nimport { ConnectionError } from \"@usebutr/core\";\n\nconst assertBitcoinChain = (chain: ChainBase): void => {\n if (chain.namespace !== \"bip122\") {\n throw new Error(\n `Bitcoin adapter received non-Bitcoin chain \"${chain.id}\". Pass a chain with namespace \"bip122\".`,\n );\n }\n};\n\n/** An injected wallet has one network for every call, so a target it cannot\n * move to is refused rather than sent on the network it happens to be on. */\nconst chainMismatch = (wallet: string, current: ChainBase, target: ChainBase): ConnectionError =>\n new ConnectionError(\n \"ChainMismatch\",\n `Wallet ${wallet} is on ${current.name} and cannot switch to ${target.name}`,\n );\n\nexport { assertBitcoinChain, chainMismatch };\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 BitcoinAdapter,\n BitcoinTransfer,\n ChainBase,\n ConnectorEvent,\n TransactionOptions,\n} from \"@usebutr/core\";\nimport {\n BITCOIN_CHAINS,\n BITCOIN_CHAINS_LIST,\n base64ToBytes,\n buildAccount,\n bytesToBase64,\n resolveChain,\n} from \"@usebutr/core\";\nimport { z } from \"zod\";\n\nimport { assertBitcoinChain, chainMismatch } from \"./chain\";\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 ACCOUNT_PURPOSES = [\"payment\", \"ordinals\"];\nconst CONNECT_MESSAGE = \"Connect to butr\";\n\nconst networkNameSchema = z.enum([\"Mainnet\", \"Regtest\", \"Signet\", \"Testnet\", \"Testnet4\"]);\n\n/** sats-connect names networks, not chains. Regtest sits outside butr's\n * registry, so `resolveChain` names it by its CAIP-2 id. */\nconst NETWORK_CHAIN_IDS: Readonly<Record<z.output<typeof networkNameSchema>, string>> = {\n Mainnet: BITCOIN_CHAINS.mainnet.id,\n Regtest: \"bip122:0f9188f13cb7b2c71f2a335e3a4fc328\",\n Signet: BITCOIN_CHAINS.signet.id,\n Testnet: BITCOIN_CHAINS.testnet.id,\n Testnet4: BITCOIN_CHAINS.testnet4.id,\n};\n\nconst satsAddressSchema = z.object({\n address: z.string(),\n publicKey: z.string().optional(),\n purpose: z.string().optional(),\n});\n\n// Legacy `getAccounts` resolves the address list itself; `wallet_getAccount`\n// wraps it alongside the wallet id and network.\nconst accountResultSchema = z.union([\n z.array(satsAddressSchema),\n z.object({ addresses: z.array(satsAddressSchema) }).transform((result) => result.addresses),\n]);\nconst networkResultSchema = z.object({ bitcoin: z.object({ name: networkNameSchema }) });\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// Module-level like the rest: a schema built per call gets hoisted by apps\n// compiling with zod-compiler, which then needs zod in the app itself.\nconst changeNetworkResultSchema = z.unknown();\n\nconst pickPaymentAddress = (addresses: ReadonlyArray<SatsAddress>): SatsAddress | undefined =>\n addresses.find((a) => a.purpose === \"payment\") ?? addresses[0];\n\nconst networkFor = (chain: ChainBase): string | undefined =>\n Object.entries(NETWORK_CHAIN_IDS).find(([, chainId]) => chainId === chain.id)?.[0];\n\n/**\n * Only the payment address is an Account (`sendTransfer` always debits it); `signMessage` also\n * takes the ordinals address. The network is wallet-wide, so a call targeting another chain\n * moves it with `wallet_changeNetwork` first.\n */\nconst buildSatsConnectAdapter = (\n id: string,\n name: string,\n provider: SatsConnectProvider,\n): BitcoinAdapter => {\n let chain: ChainBase = BITCOIN_CHAINS.mainnet;\n let session: Session = { status: \"disconnected\" };\n const listeners = new Set<(event: ConnectorEvent) => void>();\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 addresses = await callRequest(method, accountResultSchema, params);\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 // `wallet_getAccount` reads already-granted permissions without showing\n // wallet UI; `getAccounts` IS Xverse's approval prompt.\n const readSession = () => startSession(\"wallet_getAccount\", { addresses: ACCOUNT_PURPOSES });\n\n const readChain = async (): Promise<ChainBase> => {\n const result = await callRequest(\"wallet_getNetwork\", networkResultSchema);\n return resolveChain(NETWORK_CHAIN_IDS[result.bitcoin.name], BITCOIN_CHAINS_LIST);\n };\n\n const exposedAccounts = (): ReadonlyArray<Account> =>\n session.status === \"connected\" ? [buildAccount(session.payment, chain)] : [];\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 moveTo = async (target: ChainBase): Promise<void> => {\n assertBitcoinChain(target);\n const current = await readChain();\n if (current.id !== target.id) {\n const network = networkFor(target);\n if (network === undefined) {\n throw chainMismatch(name, current, target);\n }\n await callRequest(\"wallet_changeNetwork\", changeNetworkResultSchema, { name: network });\n }\n if (chain.id === target.id) {\n return;\n }\n // A Bitcoin address encodes its network, so the session is re-read on\n // the new one before anything is labelled with it.\n await readSession();\n chain = resolveChain(target.id, BITCOIN_CHAINS_LIST);\n const accounts = exposedAccounts();\n for (const listener of listeners) {\n listener({ accounts, type: \"accountsChanged\" });\n }\n };\n\n // The account is matched after the move: its address only exists on the\n // network it was built for.\n const prepare = async (options?: TransactionOptions): Promise<string> => {\n if (options?.chain !== undefined) {\n await moveTo(options.chain);\n }\n return resolveAddress(options?.account);\n };\n\n return {\n chainPlatform: \"bitcoin\",\n\n async connect(options) {\n // Builds predating `wallet_getAccount` reject a silent connect, which\n // butr's hydration treats as a clean restore failure.\n await (options?.silent === true\n ? readSession()\n : startSession(\"getAccounts\", { message: CONNECT_MESSAGE, purposes: ACCOUNT_PURPOSES }));\n try {\n chain = await readChain();\n } catch {\n // Builds predating `wallet_getNetwork` keep the last known label;\n // a transaction naming a chain still reads the network strictly.\n }\n },\n\n disconnect: () => {\n session = { status: \"disconnected\" };\n return Promise.resolve();\n },\n\n getAccounts: () => Promise.resolve(exposedAccounts()),\n\n getSigner: () => Promise.resolve({ kind: \"sats-connect\", provider }),\n\n icon: GENERIC_BITCOIN_ICON,\n id,\n name,\n\n async sendTx({ amount, recipient }: BitcoinTransfer, options?: TransactionOptions) {\n const sender = await prepare(options);\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 const result = await callRequest(\"sendTransfer\", sendTransferResultSchema, {\n recipients: [{ address: recipient, amount: Number(amount) }],\n });\n return result.txid;\n },\n\n async signMessage(message, options) {\n const address = resolveAddress(options?.account);\n const result = await callRequest(\"signMessage\", signMessageResultSchema, {\n address,\n message: new TextDecoder().decode(message),\n });\n return { signature: base64ToBytes(result.signature), signedMessage: message };\n },\n\n async signTransaction(psbt, options) {\n await prepare(options);\n const result = await callRequest(\"signPsbt\", signPsbtResultSchema, {\n psbt: bytesToBase64(psbt),\n });\n return base64ToBytes(result.psbt);\n },\n\n // Xverse's own wallet events are not bridged: this reports the network\n // moves butr makes, which re-address the payment account.\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n\n switchChain: moveTo,\n };\n};\n\nexport type { RpcValue, SatsConnectProvider };\nexport { buildSatsConnectAdapter };\n","import type {\n Account,\n BitcoinAdapter,\n BitcoinTransfer,\n ChainBase,\n ConnectorEvent,\n TransactionOptions,\n} from \"@usebutr/core\";\nimport {\n BITCOIN_CHAINS,\n BITCOIN_CHAINS_LIST,\n base64ToBytes,\n buildAccount,\n bytesToHex,\n hexToBytes,\n logWarn,\n resolveChain,\n} from \"@usebutr/core\";\n\nimport { assertBitcoinChain, chainMismatch } from \"./chain\";\nimport { GENERIC_BITCOIN_ICON } from \"./icon\";\n\ntype UnisatNetwork = \"livenet\" | \"mainnet\" | \"signet\" | \"testnet\";\n\n/** The only networks UniSat's `switchNetwork` accepts. */\ntype SwitchableNetwork = \"livenet\" | \"testnet\";\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<UnisatNetwork>;\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 /** UniSat has it; OKX's `window.okxwallet.bitcoin` is pinned to mainnet. */\n switchNetwork?: (network: SwitchableNetwork) => Promise<void>;\n};\n\ntype UnisatEventItem = number | string | null;\ntype UnisatEventValue = ReadonlyArray<UnisatEventItem> | string | undefined;\n\ntype UnisatSignPsbtOptions = {\n autoFinalized?: boolean;\n};\n\nconst NETWORK_CHAINS: ReadonlyMap<string, ChainBase> = new Map<UnisatNetwork, ChainBase>([\n [\"livenet\", BITCOIN_CHAINS.mainnet],\n [\"mainnet\", BITCOIN_CHAINS.mainnet],\n [\"signet\", BITCOIN_CHAINS.signet],\n [\"testnet\", BITCOIN_CHAINS.testnet],\n]);\n\nconst SWITCH_NETWORKS: ReadonlyMap<string, SwitchableNetwork> = new Map([\n [BITCOIN_CHAINS.mainnet.id, \"livenet\"],\n [BITCOIN_CHAINS.testnet.id, \"testnet\"],\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 * One adapter covers `window.unisat`, `window.okxwallet.bitcoin` and `window.btc`.\n * The network is wallet-wide, so a call targeting another chain switches it\n * first, or rejects when the provider cannot switch.\n */\nconst buildUnisatAdapter = (id: string, name: string, provider: UnisatProvider): BitcoinAdapter => {\n // Bound so they keep `this`: they are methods on the wallet's own object.\n const getNetwork = provider.getNetwork?.bind(provider);\n const on = provider.on?.bind(provider);\n const sendBitcoin = provider.sendBitcoin?.bind(provider);\n const switchNetwork = provider.switchNetwork?.bind(provider);\n\n // Providers without `getNetwork` (legacy `window.btc`) are mainnet wallets.\n let chain: ChainBase = BITCOIN_CHAINS.mainnet;\n\n const readChain = async (): Promise<ChainBase> => {\n if (getNetwork === undefined) {\n return chain;\n }\n const network = await getNetwork();\n const match = NETWORK_CHAINS.get(network);\n if (match === undefined) {\n throw new Error(`Wallet ${name} reported an unknown network \"${network}\"`);\n }\n return match;\n };\n\n const readAccounts = async (): Promise<ReadonlyArray<Account>> => {\n const addresses = await provider.getAccounts();\n if (addresses.length === 0) {\n return [];\n }\n try {\n chain = await readChain();\n } catch {\n // Labels only: a locked wallet may refuse `getNetwork` while still\n // listing its accounts, which keep the last known chain.\n }\n return addresses.map((address) => buildAccount(address, chain));\n };\n\n // UniSat takes no sender: it always signs with its active account.\n const assertActive = async (account?: Account): Promise<void> => {\n if (account === undefined) {\n return;\n }\n const [active] = await provider.getAccounts();\n if (account.walletAddress !== active) {\n throw new Error(\n `Wallet ${name} signs only with its active account, and ${account.walletAddress} is not it`,\n );\n }\n };\n\n const moveTo = async (target: ChainBase): Promise<void> => {\n assertBitcoinChain(target);\n chain = await readChain();\n if (chain.id === target.id) {\n return;\n }\n const network = SWITCH_NETWORKS.get(target.id);\n if (switchNetwork === undefined || network === undefined) {\n throw chainMismatch(name, chain, target);\n }\n await switchNetwork(network);\n chain = resolveChain(target.id, BITCOIN_CHAINS_LIST);\n };\n\n // A Bitcoin address encodes its network, so the wallet moves first and\n // the account is matched on the network it will sign on.\n const prepare = async (options?: TransactionOptions): Promise<void> => {\n if (options?.chain !== undefined) {\n await moveTo(options.chain);\n }\n await assertActive(options?.account);\n };\n\n return {\n chainPlatform: \"bitcoin\",\n\n async connect(options) {\n if (options?.silent !== true) {\n await provider.requestAccounts();\n return;\n }\n const accounts = await provider.getAccounts();\n if (accounts.length === 0) {\n throw new Error(\"No authorized accounts for silent reconnect\");\n }\n },\n\n getAccounts: readAccounts,\n\n getSigner: () => Promise.resolve({ kind: \"unisat\", provider }),\n\n icon: GENERIC_BITCOIN_ICON,\n id,\n name,\n\n async signMessage(message, options) {\n await assertActive(options?.account);\n const signature = await provider.signMessage(new TextDecoder().decode(message));\n return { signature: base64ToBytes(signature), signedMessage: message };\n },\n\n async signTransaction(psbt, options) {\n await prepare(options);\n return hexToBytes(await provider.signPsbt(bytesToHex(psbt)));\n },\n\n ...(sendBitcoin !== undefined && {\n async sendTx({ amount, recipient }: BitcoinTransfer, options?: TransactionOptions) {\n await prepare(options);\n return sendBitcoin(recipient, Number(amount));\n },\n }),\n\n ...(on !== undefined && {\n subscribe: (listener: (event: ConnectorEvent) => void) => {\n const emit = (accounts: ReadonlyArray<Account>) => {\n listener(\n accounts.length === 0\n ? { type: \"disconnected\" }\n : { accounts, type: \"accountsChanged\" },\n );\n };\n const onAccountsChanged = (...args: ReadonlyArray<UnisatEventValue>) => {\n emit(toStringArray(args[0]).map((address) => buildAccount(address, chain)));\n };\n // The network is wallet-wide, so a change moves every account with it.\n const reemitAccounts = async () => {\n try {\n emit(await readAccounts());\n } catch (error) {\n logWarn(`[butr] ${name} accounts could not be re-read after a network change:`, error);\n }\n };\n const onNetworkChanged = () => {\n void reemitAccounts();\n };\n on(\"accountsChanged\", onAccountsChanged);\n on(\"networkChanged\", onNetworkChanged);\n return () => {\n provider.removeListener?.(\"accountsChanged\", onAccountsChanged);\n provider.removeListener?.(\"networkChanged\", onNetworkChanged);\n };\n },\n }),\n\n ...(switchNetwork !== undefined && { switchChain: moveTo }),\n };\n};\n\nexport type { UnisatProvider };\nexport { buildUnisatAdapter };\n","import type { BitcoinAdapter, 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<BitcoinAdapter> => {\n const adapters: Array<BitcoinAdapter> = [];\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,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebutr/bitcoin",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "Wallet Standard + injected (sats-connect / Unisat / OKX) wallet discovery and adapters for butr (Bitcoin).",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -42,14 +42,14 @@
42
42
  "access": "public"
43
43
  },
44
44
  "dependencies": {
45
- "zod": "^4.4.3",
46
- "@usebutr/core": "2.0.1",
47
- "@usebutr/wallet-standard-shared": "1.0.0"
45
+ "zod": "^4.6.1",
46
+ "@usebutr/core": "3.0.0",
47
+ "@usebutr/wallet-standard-shared": "2.0.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@wallet-standard/app": "^1.1.1",
51
51
  "typescript": "^7.0.2",
52
- "vitest": "^4.1.10",
52
+ "vitest": "^5.0.0",
53
53
  "@repo/config-vitest": "0.0.0",
54
54
  "@repo/typescript-config": "0.0.0"
55
55
  },