@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 +33 -0
- package/README.md +56 -4
- package/dist/index.d.ts +22 -57
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +205 -277
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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
|
|
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 {
|
|
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
|
|
24
|
+
const config: WalletManagerConfig = { sources: [discoverBitcoinAdapters] };
|
|
25
|
+
|
|
25
26
|
export const WalletProvider = ({ children }: { children: ReactNode }) => (
|
|
26
|
-
<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
|
-
* `
|
|
53
|
-
* `signTransaction
|
|
54
|
-
*
|
|
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) =>
|
|
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<
|
|
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
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
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
|
|
207
|
-
|
|
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 {
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/wallet-standard-types.ts","../src/wallet-standard-adapter.ts","../src/
|
|
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,
|
|
2
|
-
import {
|
|
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
|
-
* `
|
|
30
|
-
* `signTransaction
|
|
31
|
-
*
|
|
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
|
-
|
|
13
|
+
chains: BITCOIN_CHAINS_LIST,
|
|
36
14
|
id: slugify("btc", wallet.name),
|
|
37
15
|
label: "Bitcoin",
|
|
38
16
|
namespace: "bip122",
|
|
39
|
-
|
|
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
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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,
|
|
57
|
+
const discoverBitcoinAdapters = (onAdapter) => discoverWalletStandard(onAdapter, buildBitcoinAdapter);
|
|
120
58
|
|
|
121
59
|
//#endregion
|
|
122
|
-
//#region src/
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
189
|
-
*
|
|
190
|
-
*
|
|
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 =
|
|
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
|
|
211
|
-
|
|
212
|
-
|
|
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
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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(
|
|
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(
|
|
209
|
+
message: new TextDecoder().decode(message)
|
|
273
210
|
});
|
|
274
211
|
return {
|
|
275
212
|
signature: base64ToBytes(result.signature),
|
|
276
|
-
signedMessage:
|
|
213
|
+
signedMessage: message
|
|
277
214
|
};
|
|
278
215
|
},
|
|
279
|
-
async signTransaction(
|
|
280
|
-
|
|
281
|
-
const result = await callRequest("signPsbt", signPsbtResultSchema, { psbt: bytesToBase64(
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
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
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
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
|
|
321
|
-
|
|
259
|
+
const readAccounts = async () => {
|
|
260
|
+
const addresses = await provider.getAccounts();
|
|
261
|
+
if (addresses.length === 0) return [];
|
|
322
262
|
try {
|
|
323
|
-
|
|
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(
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
await refreshChain();
|
|
287
|
+
async connect(options) {
|
|
288
|
+
if (options?.silent !== true) {
|
|
289
|
+
await provider.requestAccounts();
|
|
336
290
|
return;
|
|
337
291
|
}
|
|
338
|
-
await provider.
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
366
|
-
await
|
|
367
|
-
await
|
|
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(
|
|
379
|
-
signedMessage:
|
|
306
|
+
signature: base64ToBytes(signature),
|
|
307
|
+
signedMessage: message
|
|
380
308
|
};
|
|
381
309
|
},
|
|
382
|
-
async signTransaction(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
return hexToBytes(signedHex);
|
|
310
|
+
async signTransaction(psbt, options) {
|
|
311
|
+
await prepare(options);
|
|
312
|
+
return hexToBytes(await provider.signPsbt(bytesToHex(psbt)));
|
|
386
313
|
},
|
|
387
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
336
|
+
reemitAccounts();
|
|
405
337
|
};
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
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 {
|
|
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": "
|
|
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.
|
|
46
|
-
"@usebutr/core": "
|
|
47
|
-
"@usebutr/wallet-standard-shared": "
|
|
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": "^
|
|
52
|
+
"vitest": "^5.0.0",
|
|
53
53
|
"@repo/config-vitest": "0.0.0",
|
|
54
54
|
"@repo/typescript-config": "0.0.0"
|
|
55
55
|
},
|