bitcoin-wallet-connector 0.1.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/README.md +208 -0
- package/lib/BitcoinConnectionProvider.d.ts +23 -0
- package/lib/BitcoinWalletAdapterConnector-Bq835yj0.mjs +123 -0
- package/lib/BitcoinWalletAdapterConnector-Bq835yj0.mjs.map +1 -0
- package/lib/BitcoinWalletAdapterConnector-DMef0iHV.js +2 -0
- package/lib/BitcoinWalletAdapterConnector-DMef0iHV.js.map +1 -0
- package/lib/BitcoinWalletAdapterConnector.d.ts +30 -0
- package/lib/BitgetWalletAdapter.impl-C_HLO7Oi.mjs +10 -0
- package/lib/BitgetWalletAdapter.impl-C_HLO7Oi.mjs.map +1 -0
- package/lib/BitgetWalletAdapter.impl-CxnKMf7U.js +2 -0
- package/lib/BitgetWalletAdapter.impl-CxnKMf7U.js.map +1 -0
- package/lib/LeatherWalletAdapter.impl-B2QgX_tO.js +2 -0
- package/lib/LeatherWalletAdapter.impl-B2QgX_tO.js.map +1 -0
- package/lib/LeatherWalletAdapter.impl-RUYx555r.mjs +184 -0
- package/lib/LeatherWalletAdapter.impl-RUYx555r.mjs.map +1 -0
- package/lib/MagicEdenWalletAdapter.impl-CrA6SGvG.mjs +235 -0
- package/lib/MagicEdenWalletAdapter.impl-CrA6SGvG.mjs.map +1 -0
- package/lib/MagicEdenWalletAdapter.impl-Di3Nu2S5.js +2 -0
- package/lib/MagicEdenWalletAdapter.impl-Di3Nu2S5.js.map +1 -0
- package/lib/OkxWalletAdapter.impl-BepoUL1B.mjs +67 -0
- package/lib/OkxWalletAdapter.impl-BepoUL1B.mjs.map +1 -0
- package/lib/OkxWalletAdapter.impl-C8kesjGu.js +2 -0
- package/lib/OkxWalletAdapter.impl-C8kesjGu.js.map +1 -0
- package/lib/UnisatCompatibleWalletAdapterImpl-Cq2Oqk1b.js +2 -0
- package/lib/UnisatCompatibleWalletAdapterImpl-Cq2Oqk1b.js.map +1 -0
- package/lib/UnisatCompatibleWalletAdapterImpl-M38FqkZI.mjs +137 -0
- package/lib/UnisatCompatibleWalletAdapterImpl-M38FqkZI.mjs.map +1 -0
- package/lib/UnisatWalletAdapter.impl-CJB22se8.mjs +14 -0
- package/lib/UnisatWalletAdapter.impl-CJB22se8.mjs.map +1 -0
- package/lib/UnisatWalletAdapter.impl-EISvxdpc.js +2 -0
- package/lib/UnisatWalletAdapter.impl-EISvxdpc.js.map +1 -0
- package/lib/WalletAdapters.types-CnvOqHFH.mjs +32 -0
- package/lib/WalletAdapters.types-CnvOqHFH.mjs.map +1 -0
- package/lib/WalletAdapters.types-De_x1lzr.js +2 -0
- package/lib/WalletAdapters.types-De_x1lzr.js.map +1 -0
- package/lib/WalletAdapters.types.d.ts +110 -0
- package/lib/XverseCompatibleWalletAdapterImpl-Bf-BK5VK.js +2 -0
- package/lib/XverseCompatibleWalletAdapterImpl-Bf-BK5VK.js.map +1 -0
- package/lib/XverseCompatibleWalletAdapterImpl-DXKnO_-V.mjs +151 -0
- package/lib/XverseCompatibleWalletAdapterImpl-DXKnO_-V.mjs.map +1 -0
- package/lib/XverseWalletAdapter.impl-CZO0RQva.mjs +105 -0
- package/lib/XverseWalletAdapter.impl-CZO0RQva.mjs.map +1 -0
- package/lib/XverseWalletAdapter.impl-lJwMi-Iv.js +2 -0
- package/lib/XverseWalletAdapter.impl-lJwMi-Iv.js.map +1 -0
- package/lib/adapters/BitgetWalletAdapter.d.ts +2 -0
- package/lib/adapters/BitgetWalletAdapter.impl.d.ts +8 -0
- package/lib/adapters/LeatherWalletAdapter.d.ts +2 -0
- package/lib/adapters/LeatherWalletAdapter.impl.d.ts +41 -0
- package/lib/adapters/MagicEdenWalletAdapter.d.ts +11 -0
- package/lib/adapters/MagicEdenWalletAdapter.impl.d.ts +22 -0
- package/lib/adapters/MockAddressWalletAdapter.d.ts +33 -0
- package/lib/adapters/OkxWalletAdapter.d.ts +2 -0
- package/lib/adapters/OkxWalletAdapter.impl.d.ts +51 -0
- package/lib/adapters/UnisatWalletAdapter.d.ts +2 -0
- package/lib/adapters/UnisatWalletAdapter.impl.d.ts +14 -0
- package/lib/adapters/XverseWalletAdapter.d.ts +3 -0
- package/lib/adapters/XverseWalletAdapter.impl.d.ts +14 -0
- package/lib/adapters/index.d.ts +7 -0
- package/lib/adapters.js +2 -0
- package/lib/adapters.js.map +1 -0
- package/lib/adapters.mjs +11 -0
- package/lib/adapters.mjs.map +1 -0
- package/lib/bitget-C7oB4Ffq.mjs +5 -0
- package/lib/bitget-C7oB4Ffq.mjs.map +1 -0
- package/lib/bitget-DXnsxx_y.js +2 -0
- package/lib/bitget-DXnsxx_y.js.map +1 -0
- package/lib/index-CaV3F1Nm.js +424 -0
- package/lib/index-CaV3F1Nm.js.map +1 -0
- package/lib/index-CcQUdePc.mjs +12224 -0
- package/lib/index-CcQUdePc.mjs.map +1 -0
- package/lib/index-D7YwhNAG.mjs +3946 -0
- package/lib/index-D7YwhNAG.mjs.map +1 -0
- package/lib/index-Zx0KcpYx.js +2 -0
- package/lib/index-Zx0KcpYx.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +20 -0
- package/lib/index.mjs.map +1 -0
- package/lib/leather-BoQG_CPn.mjs +5 -0
- package/lib/leather-BoQG_CPn.mjs.map +1 -0
- package/lib/leather-DJ8nWmM8.js +2 -0
- package/lib/leather-DJ8nWmM8.js.map +1 -0
- package/lib/magiceden-B36CEQa6.js +2 -0
- package/lib/magiceden-B36CEQa6.js.map +1 -0
- package/lib/magiceden-Cg7d3agI.mjs +5 -0
- package/lib/magiceden-Cg7d3agI.mjs.map +1 -0
- package/lib/misc-B5EWO_dn.mjs +10 -0
- package/lib/misc-B5EWO_dn.mjs.map +1 -0
- package/lib/misc-CigR0RqC.js +2 -0
- package/lib/misc-CigR0RqC.js.map +1 -0
- package/lib/okx-ChwzM0dK.js +2 -0
- package/lib/okx-ChwzM0dK.js.map +1 -0
- package/lib/okx-DWbHwazu.mjs +5 -0
- package/lib/okx-DWbHwazu.mjs.map +1 -0
- package/lib/react.d.ts +2 -0
- package/lib/react.js +2 -0
- package/lib/react.js.map +1 -0
- package/lib/react.mjs +128 -0
- package/lib/react.mjs.map +1 -0
- package/lib/transaction-CiLOYSE_.mjs +1063 -0
- package/lib/transaction-CiLOYSE_.mjs.map +1 -0
- package/lib/transaction-CzdnbXSo.js +2 -0
- package/lib/transaction-CzdnbXSo.js.map +1 -0
- package/lib/unisat-BvZW5h0U.js +2 -0
- package/lib/unisat-BvZW5h0U.js.map +1 -0
- package/lib/unisat-pLgab4nG.mjs +5 -0
- package/lib/unisat-pLgab4nG.mjs.map +1 -0
- package/lib/utils/StateChannel.d.ts +14 -0
- package/lib/utils/UnisatCompatibleWalletAdapterImpl.d.ts +99 -0
- package/lib/utils/XverseCompatibleWalletAdapterImpl.d.ts +80 -0
- package/lib/utils/XverseCompatibleWalletAdapterImpl_legacy.d.ts +44 -0
- package/lib/utils/bitcoinAddressHelpers.d.ts +14 -0
- package/lib/utils/bitcoinNetworkHelpers.d.ts +4 -0
- package/lib/utils/createAdapterAvailability.d.ts +15 -0
- package/lib/utils/error.d.ts +6 -0
- package/lib/utils/misc.d.ts +3 -0
- package/lib/xverse-IKOHyGi-.js +2 -0
- package/lib/xverse-IKOHyGi-.js.map +1 -0
- package/lib/xverse-iHLNanCB.mjs +5 -0
- package/lib/xverse-iHLNanCB.mjs.map +1 -0
- package/package.json +86 -0
- package/src/BitcoinConnectionProvider.stories.tsx +329 -0
- package/src/BitcoinConnectionProvider.tsx +234 -0
- package/src/BitcoinWalletAdapterConnector.ts +166 -0
- package/src/WalletAdapters.types.ts +154 -0
- package/src/_/bitget.png +0 -0
- package/src/_/leather.svg +4 -0
- package/src/_/magiceden.png +0 -0
- package/src/_/okx.png +0 -0
- package/src/_/unisat.svg +31 -0
- package/src/_/xverse.png +0 -0
- package/src/adapters/BitgetWalletAdapter.impl.ts +22 -0
- package/src/adapters/BitgetWalletAdapter.ts +44 -0
- package/src/adapters/LeatherWalletAdapter.impl.ts +324 -0
- package/src/adapters/LeatherWalletAdapter.ts +35 -0
- package/src/adapters/MagicEdenWalletAdapter.impl.ts +139 -0
- package/src/adapters/MagicEdenWalletAdapter.ts +51 -0
- package/src/adapters/MockAddressWalletAdapter.ts +199 -0
- package/src/adapters/OkxWalletAdapter.impl.ts +168 -0
- package/src/adapters/OkxWalletAdapter.ts +37 -0
- package/src/adapters/UnisatWalletAdapter.impl.ts +32 -0
- package/src/adapters/UnisatWalletAdapter.ts +50 -0
- package/src/adapters/XverseWalletAdapter.impl.ts +150 -0
- package/src/adapters/XverseWalletAdapter.ts +37 -0
- package/src/adapters/index.ts +7 -0
- package/src/env.d.ts +9 -0
- package/src/index.ts +3 -0
- package/src/react.ts +9 -0
- package/src/utils/StateChannel.ts +39 -0
- package/src/utils/UnisatCompatibleWalletAdapterImpl.ts +342 -0
- package/src/utils/XverseCompatibleWalletAdapterImpl.ts +288 -0
- package/src/utils/XverseCompatibleWalletAdapterImpl_legacy.ts +278 -0
- package/src/utils/bitcoinAddressHelpers.ts +132 -0
- package/src/utils/bitcoinNetworkHelpers.ts +17 -0
- package/src/utils/createAdapterAvailability.ts +92 -0
- package/src/utils/error.ts +13 -0
- package/src/utils/misc.ts +10 -0
package/src/react.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type StateChannelListener<T> = (value: T) => void
|
|
2
|
+
|
|
3
|
+
export interface StateChannelSubscription {
|
|
4
|
+
unsubscribe: () => void
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class StateChannel<T> {
|
|
8
|
+
private listeners = new Set<StateChannelListener<T>>()
|
|
9
|
+
constructor(private value: T) {}
|
|
10
|
+
|
|
11
|
+
getValue(): T {
|
|
12
|
+
return this.value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setValue(value: T): void {
|
|
16
|
+
this.value = value
|
|
17
|
+
this.emit()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
update(updater: (current: T) => T): void {
|
|
21
|
+
this.setValue(updater(this.value))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
subscribe(listener: StateChannelListener<T>): StateChannelSubscription {
|
|
25
|
+
this.listeners.add(listener)
|
|
26
|
+
listener(this.value)
|
|
27
|
+
return {
|
|
28
|
+
unsubscribe: () => {
|
|
29
|
+
this.listeners.delete(listener)
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private emit(): void {
|
|
35
|
+
for (const listener of this.listeners) {
|
|
36
|
+
listener(this.value)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { hex } from "@scure/base"
|
|
2
|
+
import * as btc from "@scure/btc-signer"
|
|
3
|
+
import { hasAny } from "./misc"
|
|
4
|
+
import {
|
|
5
|
+
addressToScriptPubKey,
|
|
6
|
+
getAddressType,
|
|
7
|
+
getRedeemScriptOf_P2SH_P2WPKH_publicKey,
|
|
8
|
+
getTapInternalKeyOf_P2TR_publicKey,
|
|
9
|
+
} from "./bitcoinAddressHelpers"
|
|
10
|
+
import { getBitcoinNetwork } from "./bitcoinNetworkHelpers"
|
|
11
|
+
import { UserRejectError, BitcoinWalletAdapterError } from "../utils/error"
|
|
12
|
+
import {
|
|
13
|
+
SignMessageAlgorithm,
|
|
14
|
+
SignMessageResult,
|
|
15
|
+
WalletAdapter,
|
|
16
|
+
WalletAdapter_onAddressesChanged_callback,
|
|
17
|
+
WalletAdapterAddress,
|
|
18
|
+
WalletAdapterAddressPurpose,
|
|
19
|
+
WalletAdapterAddressType,
|
|
20
|
+
WalletAdapterBitcoinNetwork,
|
|
21
|
+
WalletAdapterNotConnectedError,
|
|
22
|
+
} from "../WalletAdapters.types"
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* https://docs.unisat.io/dev-center/open-api-documentation/unisat-wallet#events
|
|
26
|
+
*/
|
|
27
|
+
export interface UniSatEvents {
|
|
28
|
+
accountsChanged: [accounts: Array<string>]
|
|
29
|
+
networkChanged: [network: string]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UnisatCompatibleProviderAPI {
|
|
33
|
+
/**
|
|
34
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#getaccounts
|
|
35
|
+
*/
|
|
36
|
+
getAccounts(): Promise<string[]>
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#requestaccounts
|
|
40
|
+
*/
|
|
41
|
+
requestAccounts(): Promise<string[]>
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#getpublickey
|
|
45
|
+
*/
|
|
46
|
+
getPublicKey(): Promise<string>
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#getnetwork
|
|
50
|
+
*/
|
|
51
|
+
getNetwork(): Promise<"livenet" | "testnet">
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#signmessage
|
|
55
|
+
*/
|
|
56
|
+
signMessage(
|
|
57
|
+
message: string,
|
|
58
|
+
algorithm: "ecdsa" | "bip322-simple",
|
|
59
|
+
): Promise<string>
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#sendbitcoin
|
|
63
|
+
*/
|
|
64
|
+
sendBitcoin(
|
|
65
|
+
receiverAddress: string,
|
|
66
|
+
satoshiAmount: number,
|
|
67
|
+
options?: { feeRate?: number },
|
|
68
|
+
): Promise<string>
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#sendInscription
|
|
72
|
+
*/
|
|
73
|
+
sendInscription(
|
|
74
|
+
receiverAddress: string,
|
|
75
|
+
inscriptionId: string,
|
|
76
|
+
options?: { feeRate?: number },
|
|
77
|
+
): Promise<string>
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#signpsbt
|
|
81
|
+
*/
|
|
82
|
+
signPsbt(
|
|
83
|
+
psbtHex: string,
|
|
84
|
+
options?: {
|
|
85
|
+
autoFinalized?: boolean
|
|
86
|
+
toSignInputs?: {
|
|
87
|
+
index: number
|
|
88
|
+
address?: string
|
|
89
|
+
publicKey?: string
|
|
90
|
+
sighashTypes?: number[]
|
|
91
|
+
disableTweakSigner?: boolean
|
|
92
|
+
}[]
|
|
93
|
+
},
|
|
94
|
+
): Promise<string>
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet#events
|
|
98
|
+
*/
|
|
99
|
+
on<K extends keyof UniSatEvents>(
|
|
100
|
+
event: K,
|
|
101
|
+
handler: (...args: UniSatEvents[K]) => void,
|
|
102
|
+
): void
|
|
103
|
+
removeListener(event: keyof UniSatEvents, handler: () => void): void
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type UnisatCompatibleWalletAdapterAddresses = (WalletAdapterAddress & {
|
|
107
|
+
publicKey: string
|
|
108
|
+
})[]
|
|
109
|
+
|
|
110
|
+
export class UnisatCompatibleWalletAdapterImpl implements WalletAdapter {
|
|
111
|
+
constructor(
|
|
112
|
+
private unisat: UnisatCompatibleProviderAPI,
|
|
113
|
+
private walletDisplayName: string,
|
|
114
|
+
) {}
|
|
115
|
+
|
|
116
|
+
async connect(): Promise<void> {
|
|
117
|
+
if ((await this.unisat.getAccounts()).length === 0) {
|
|
118
|
+
await this.unisat.requestAccounts()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
disconnect(): Promise<void> {
|
|
123
|
+
// do nothing
|
|
124
|
+
return Promise.resolve()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async getAddresses(): Promise<UnisatCompatibleWalletAdapterAddresses> {
|
|
128
|
+
const addresses: string[] = await this.unisat.getAccounts()
|
|
129
|
+
|
|
130
|
+
if (addresses.length === 0) {
|
|
131
|
+
throw new WalletAdapterNotConnectedError(this.walletDisplayName)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const publicKey: string = await this.unisat.getPublicKey()
|
|
135
|
+
|
|
136
|
+
const _network: "livenet" | "testnet" = await this.unisat.getNetwork()
|
|
137
|
+
const network = _network === "livenet" ? "mainnet" : "testnet"
|
|
138
|
+
|
|
139
|
+
if (!hasAny(addresses)) {
|
|
140
|
+
throw new BitcoinWalletAdapterError("Request wallet addresses failed")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const address = addresses[0]
|
|
144
|
+
const addrType = getAddressType(getBitcoinNetwork(network), address)
|
|
145
|
+
// prettier-ignore
|
|
146
|
+
const addressType =
|
|
147
|
+
addrType === "p2tr" ? WalletAdapterAddressType.P2TR :
|
|
148
|
+
addrType === "p2sh" ? WalletAdapterAddressType.P2SH_P2WPKH :
|
|
149
|
+
addrType === "p2wpkh" ? WalletAdapterAddressType.P2WPKH :
|
|
150
|
+
undefined
|
|
151
|
+
|
|
152
|
+
if (addressType == null) {
|
|
153
|
+
throw new BitcoinWalletAdapterError("Please select a SegWit address")
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const scriptPubKey = hex.encode(
|
|
157
|
+
addressToScriptPubKey(getBitcoinNetwork(network), address),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const tapInternalKey =
|
|
161
|
+
addressType !== WalletAdapterAddressType.P2TR
|
|
162
|
+
? undefined
|
|
163
|
+
: hex.encode(
|
|
164
|
+
getTapInternalKeyOf_P2TR_publicKey(
|
|
165
|
+
getBitcoinNetwork(network),
|
|
166
|
+
hex.decode(publicKey),
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
const redeemScript =
|
|
171
|
+
addressType !== WalletAdapterAddressType.P2SH_P2WPKH
|
|
172
|
+
? undefined
|
|
173
|
+
: hex.encode(
|
|
174
|
+
getRedeemScriptOf_P2SH_P2WPKH_publicKey(
|
|
175
|
+
getBitcoinNetwork(network),
|
|
176
|
+
hex.decode(publicKey),
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
addressType,
|
|
183
|
+
address,
|
|
184
|
+
scriptPubKey,
|
|
185
|
+
redeemScript,
|
|
186
|
+
tapInternalKey,
|
|
187
|
+
publicKey,
|
|
188
|
+
network:
|
|
189
|
+
network === "mainnet"
|
|
190
|
+
? WalletAdapterBitcoinNetwork.MAINNET
|
|
191
|
+
: WalletAdapterBitcoinNetwork.TESTNET,
|
|
192
|
+
purposes: [
|
|
193
|
+
WalletAdapterAddressPurpose.Bitcoin,
|
|
194
|
+
WalletAdapterAddressPurpose.Ordinals,
|
|
195
|
+
WalletAdapterAddressPurpose.BRC20,
|
|
196
|
+
WalletAdapterAddressPurpose.Runes,
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async signMessage(
|
|
203
|
+
address: string,
|
|
204
|
+
message: string,
|
|
205
|
+
): Promise<SignMessageResult> {
|
|
206
|
+
const result = await handleRpcError(
|
|
207
|
+
this.unisat.signMessage(message, "bip322-simple"),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
result,
|
|
212
|
+
address,
|
|
213
|
+
algorithm: SignMessageAlgorithm.BIP322,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
sendBitcoinFeeRateCapability: WalletAdapter["sendBitcoinFeeRateCapability"] =
|
|
218
|
+
"available" as const
|
|
219
|
+
async sendBitcoin(
|
|
220
|
+
fromAddress: string,
|
|
221
|
+
receiverAddress: string,
|
|
222
|
+
satoshiAmount: bigint,
|
|
223
|
+
options?: { feeRate?: number },
|
|
224
|
+
): Promise<{ txid: string }> {
|
|
225
|
+
const txid = await handleRpcError(
|
|
226
|
+
this.unisat.sendBitcoin(receiverAddress, Number(satoshiAmount), {
|
|
227
|
+
feeRate: options?.feeRate,
|
|
228
|
+
}),
|
|
229
|
+
)
|
|
230
|
+
return { txid }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
sendInscriptionFeeRateCapability: WalletAdapter["sendInscriptionFeeRateCapability"] =
|
|
234
|
+
"available" as const
|
|
235
|
+
async sendInscription(
|
|
236
|
+
fromAddress: string,
|
|
237
|
+
receiverAddress: string,
|
|
238
|
+
inscriptionId: string,
|
|
239
|
+
options?: { feeRate?: number },
|
|
240
|
+
): Promise<{ txid: string }> {
|
|
241
|
+
const txid = await handleRpcError(
|
|
242
|
+
this.unisat.sendInscription(receiverAddress, inscriptionId, {
|
|
243
|
+
feeRate: options?.feeRate,
|
|
244
|
+
}),
|
|
245
|
+
)
|
|
246
|
+
return { txid }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async signAndFinalizePsbt(
|
|
250
|
+
psbtHex: string,
|
|
251
|
+
signIndices: [address: string, signIndex: number][],
|
|
252
|
+
): Promise<{
|
|
253
|
+
signedPsbtHex: string
|
|
254
|
+
}> {
|
|
255
|
+
const addr = await this.getAddresses()
|
|
256
|
+
|
|
257
|
+
const signedPsbtHex = await handleRpcError(
|
|
258
|
+
this.unisat.signPsbt(psbtHex, {
|
|
259
|
+
autoFinalized: false,
|
|
260
|
+
toSignInputs: signIndices.map(([address, signIndex]) => ({
|
|
261
|
+
index: signIndex,
|
|
262
|
+
address,
|
|
263
|
+
})),
|
|
264
|
+
}),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Some version of unisat's signPsbt API does not working well with the autoFinalized option,
|
|
269
|
+
* so we finalize the PSBT manually.
|
|
270
|
+
*/
|
|
271
|
+
const tx = btc.Transaction.fromPSBT(hex.decode(signedPsbtHex), {
|
|
272
|
+
allowUnknownInputs: true,
|
|
273
|
+
allowUnknownOutputs: true,
|
|
274
|
+
disableScriptCheck: true,
|
|
275
|
+
allowLegacyWitnessUtxo: true,
|
|
276
|
+
})
|
|
277
|
+
tx.finalize()
|
|
278
|
+
|
|
279
|
+
return { signedPsbtHex: hex.encode(tx.toPSBT()) }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
onAddressesChanged(callback: WalletAdapter_onAddressesChanged_callback): {
|
|
283
|
+
unsubscribe: () => void
|
|
284
|
+
} {
|
|
285
|
+
const handler = async (): Promise<void> => {
|
|
286
|
+
try {
|
|
287
|
+
const addresses = await this.getAddresses()
|
|
288
|
+
callback({ addresses })
|
|
289
|
+
} catch (error) {
|
|
290
|
+
// Ignore errors from getAddresses (e.g., wallet disconnected)
|
|
291
|
+
console.warn(
|
|
292
|
+
`[${this.walletDisplayName}] Failed to get addresses on change:`,
|
|
293
|
+
error,
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Listen to both account and network changes
|
|
299
|
+
this.unisat.on("accountsChanged", handler)
|
|
300
|
+
this.unisat.on("networkChanged", handler)
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
unsubscribe: () => {
|
|
304
|
+
this.unisat.removeListener("accountsChanged", handler)
|
|
305
|
+
this.unisat.removeListener("networkChanged", handler)
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
interface UnisatCompatibleProviderAPIThrownError {
|
|
312
|
+
code: number
|
|
313
|
+
message: string
|
|
314
|
+
}
|
|
315
|
+
function isUnisatCompatibleProviderAPIThrownError(
|
|
316
|
+
err: any,
|
|
317
|
+
): err is UnisatCompatibleProviderAPIThrownError {
|
|
318
|
+
return err != null && "code" in err && "message" in err
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export class UnisatCompatibleProviderError extends Error {
|
|
322
|
+
readonly code: number
|
|
323
|
+
constructor(err: UnisatCompatibleProviderAPIThrownError) {
|
|
324
|
+
super(err.message)
|
|
325
|
+
this.code = err.code
|
|
326
|
+
this.cause = err
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const handleRpcError = async <T>(promise: Promise<T>): Promise<T> => {
|
|
331
|
+
try {
|
|
332
|
+
return await promise
|
|
333
|
+
} catch (e: any) {
|
|
334
|
+
if (isUnisatCompatibleProviderAPIThrownError(e)) {
|
|
335
|
+
if (e.code === 4001) {
|
|
336
|
+
throw new UserRejectError()
|
|
337
|
+
}
|
|
338
|
+
throw new UnisatCompatibleProviderError(e)
|
|
339
|
+
}
|
|
340
|
+
throw e
|
|
341
|
+
}
|
|
342
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { base64, hex } from "@scure/base"
|
|
2
|
+
import * as btc from "@scure/btc-signer"
|
|
3
|
+
import { UserRejectError } from "../utils/error"
|
|
4
|
+
import {
|
|
5
|
+
SignMessageAlgorithm,
|
|
6
|
+
SignMessageResult,
|
|
7
|
+
WalletAdapter,
|
|
8
|
+
WalletAdapter_onAddressesChanged_callback,
|
|
9
|
+
WalletAdapterAddress,
|
|
10
|
+
WalletAdapterAddressPurpose,
|
|
11
|
+
WalletAdapterNotConnectedError,
|
|
12
|
+
} from "../WalletAdapters.types"
|
|
13
|
+
|
|
14
|
+
export type XverseCompatibleWalletAdapterGetProviderIdFn = () => Promise<string>
|
|
15
|
+
|
|
16
|
+
export type XverseCompatibleWalletAdapterParsedAddressesFn = (info: {
|
|
17
|
+
sdk: typeof import("sats-connect")
|
|
18
|
+
network: import("sats-connect").BitcoinNetworkType
|
|
19
|
+
addresses: import("sats-connect").Address[]
|
|
20
|
+
}) => Promise<XverseCompatibleWalletAdapterImplAddress[]>
|
|
21
|
+
|
|
22
|
+
export type XverseCompatibleWalletAdapterImplAddress = WalletAdapterAddress & {
|
|
23
|
+
publicKey: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class XverseCompatibleWalletAdapterImpl implements WalletAdapter {
|
|
27
|
+
protected readonly walletDisplayName: string
|
|
28
|
+
protected readonly getProviderId: XverseCompatibleWalletAdapterGetProviderIdFn
|
|
29
|
+
protected readonly parseAddresses: XverseCompatibleWalletAdapterParsedAddressesFn
|
|
30
|
+
|
|
31
|
+
constructor(info: {
|
|
32
|
+
walletDisplayName: string
|
|
33
|
+
getProviderId: XverseCompatibleWalletAdapterGetProviderIdFn
|
|
34
|
+
parseAddresses: XverseCompatibleWalletAdapterParsedAddressesFn
|
|
35
|
+
}) {
|
|
36
|
+
this.walletDisplayName = info.walletDisplayName
|
|
37
|
+
this.getProviderId = info.getProviderId
|
|
38
|
+
this.parseAddresses = info.parseAddresses
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async getSdk(): Promise<typeof import("sats-connect")> {
|
|
42
|
+
return import("sats-connect")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async connect(): Promise<void> {
|
|
46
|
+
await (await this.getSdk()).request("wallet_connect", null)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async disconnect(): Promise<void> {
|
|
50
|
+
return Promise.resolve()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getAddresses(): Promise<XverseCompatibleWalletAdapterImplAddress[]> {
|
|
54
|
+
const sdk = await this.getSdk()
|
|
55
|
+
|
|
56
|
+
const resp = await handleRpcError(
|
|
57
|
+
sdk.request(
|
|
58
|
+
"getAddresses",
|
|
59
|
+
{
|
|
60
|
+
purposes: [sdk.AddressPurpose.Ordinals, sdk.AddressPurpose.Payment],
|
|
61
|
+
},
|
|
62
|
+
await this.getProviderId(),
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if (resp == null) {
|
|
67
|
+
throw new WalletAdapterNotConnectedError(this.walletDisplayName)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return this.parseAddresses({
|
|
71
|
+
sdk,
|
|
72
|
+
network: resp.network.bitcoin.name,
|
|
73
|
+
addresses: resp.addresses,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async signMessage(
|
|
78
|
+
address: string,
|
|
79
|
+
message: string,
|
|
80
|
+
): Promise<SignMessageResult> {
|
|
81
|
+
const sdk = await this.getSdk()
|
|
82
|
+
|
|
83
|
+
const result = await handleRpcError(
|
|
84
|
+
sdk.request(
|
|
85
|
+
"signMessage",
|
|
86
|
+
{
|
|
87
|
+
address,
|
|
88
|
+
message,
|
|
89
|
+
protocol: sdk.MessageSigningProtocols.BIP322,
|
|
90
|
+
},
|
|
91
|
+
await this.getProviderId(),
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
result: result.signature,
|
|
97
|
+
address,
|
|
98
|
+
algorithm: SignMessageAlgorithm.BIP322,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
sendBitcoinFeeRateCapability = "unavailable" as const
|
|
103
|
+
async sendBitcoin(
|
|
104
|
+
fromAddress: string,
|
|
105
|
+
receiverAddress: string,
|
|
106
|
+
satoshiAmount: bigint,
|
|
107
|
+
): Promise<{ txid: string }> {
|
|
108
|
+
const senderAddress = (await this.getAddresses()).find(a =>
|
|
109
|
+
a.purposes.includes(WalletAdapterAddressPurpose.Bitcoin),
|
|
110
|
+
)
|
|
111
|
+
if (senderAddress == null) {
|
|
112
|
+
throw new XverseCompatibleProviderError({
|
|
113
|
+
code: XverseRpcErrorCode.INVALID_PARAMS,
|
|
114
|
+
message: "Bitcoin address not found",
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const sdk = await this.getSdk()
|
|
119
|
+
|
|
120
|
+
return await handleRpcError(
|
|
121
|
+
sdk.request(
|
|
122
|
+
"sendTransfer",
|
|
123
|
+
{
|
|
124
|
+
recipients: [
|
|
125
|
+
{
|
|
126
|
+
address: receiverAddress,
|
|
127
|
+
amount: Number(satoshiAmount),
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
await this.getProviderId(),
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
sendInscriptionFeeRateCapability = "unavailable" as const
|
|
137
|
+
|
|
138
|
+
async signAndFinalizePsbt(
|
|
139
|
+
psbtHex: string,
|
|
140
|
+
signIndices: [address: string, signIndex: number][],
|
|
141
|
+
): Promise<{
|
|
142
|
+
signedPsbtHex: string
|
|
143
|
+
}> {
|
|
144
|
+
const sdk = await this.getSdk()
|
|
145
|
+
|
|
146
|
+
const psbtBase64 = base64.encode(hex.decode(psbtHex))
|
|
147
|
+
|
|
148
|
+
const result = await handleRpcError(
|
|
149
|
+
sdk.request(
|
|
150
|
+
"signPsbt",
|
|
151
|
+
{
|
|
152
|
+
psbt: psbtBase64,
|
|
153
|
+
signInputs: signIndices.reduce(
|
|
154
|
+
(acc, [address, signIndex]) => {
|
|
155
|
+
acc[address] = [signIndex]
|
|
156
|
+
return acc
|
|
157
|
+
},
|
|
158
|
+
{} as Record<string, number[]>,
|
|
159
|
+
),
|
|
160
|
+
broadcast: false,
|
|
161
|
+
},
|
|
162
|
+
await this.getProviderId(),
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
const tx = btc.Transaction.fromPSBT(base64.decode(result.psbt), {
|
|
167
|
+
allowUnknownInputs: true,
|
|
168
|
+
allowUnknownOutputs: true,
|
|
169
|
+
disableScriptCheck: true,
|
|
170
|
+
allowLegacyWitnessUtxo: true,
|
|
171
|
+
})
|
|
172
|
+
tx.finalize()
|
|
173
|
+
|
|
174
|
+
return { signedPsbtHex: hex.encode(tx.toPSBT()) }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
onAddressesChanged(callback: WalletAdapter_onAddressesChanged_callback): {
|
|
178
|
+
unsubscribe: () => void
|
|
179
|
+
} {
|
|
180
|
+
// Xverse uses sats-connect Wallet.addListener for events
|
|
181
|
+
// https://docs.xverse.app/sats-connect/xverse-wallet-events
|
|
182
|
+
const removeListeners: Array<() => void> = []
|
|
183
|
+
|
|
184
|
+
void (async () => {
|
|
185
|
+
const sdk = await this.getSdk()
|
|
186
|
+
|
|
187
|
+
const handler = async (): Promise<void> => {
|
|
188
|
+
try {
|
|
189
|
+
const addresses = await this.getAddresses()
|
|
190
|
+
callback({ addresses })
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.warn(
|
|
193
|
+
`[${this.walletDisplayName}] Failed to get addresses on change:`,
|
|
194
|
+
error,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Listen to account and network changes
|
|
200
|
+
const removeAccountListener = sdk.addListener("accountChange", handler)
|
|
201
|
+
const removeNetworkListener = sdk.addListener("networkChange", handler)
|
|
202
|
+
|
|
203
|
+
removeListeners.push(removeAccountListener, removeNetworkListener)
|
|
204
|
+
})()
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
unsubscribe: () => {
|
|
208
|
+
removeListeners.forEach(remove => remove())
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export enum XverseRpcErrorCode {
|
|
215
|
+
/**
|
|
216
|
+
* Parse error Invalid JSON
|
|
217
|
+
**/
|
|
218
|
+
PARSE_ERROR = -32700,
|
|
219
|
+
/**
|
|
220
|
+
* The JSON sent is not a valid Request object.
|
|
221
|
+
**/
|
|
222
|
+
INVALID_REQUEST = -32600,
|
|
223
|
+
/**
|
|
224
|
+
* The method does not exist/is not available.
|
|
225
|
+
**/
|
|
226
|
+
METHOD_NOT_FOUND = -32601,
|
|
227
|
+
/**
|
|
228
|
+
* Invalid method parameter(s).
|
|
229
|
+
*/
|
|
230
|
+
INVALID_PARAMS = -32602,
|
|
231
|
+
/**
|
|
232
|
+
* Internal JSON-RPC error.
|
|
233
|
+
* This is a generic error, used when the server encounters an error in performing the request.
|
|
234
|
+
**/
|
|
235
|
+
INTERNAL_ERROR = -32603,
|
|
236
|
+
/**
|
|
237
|
+
* user rejected/canceled the request
|
|
238
|
+
*/
|
|
239
|
+
USER_REJECTION = -32000,
|
|
240
|
+
/**
|
|
241
|
+
* method is not supported for the address provided
|
|
242
|
+
*/
|
|
243
|
+
METHOD_NOT_SUPPORTED = -32001,
|
|
244
|
+
/**
|
|
245
|
+
* The client does not have permission to access the requested resource.
|
|
246
|
+
*/
|
|
247
|
+
ACCESS_DENIED = -32002,
|
|
248
|
+
}
|
|
249
|
+
export interface XverseCompatibleProviderAPIThrownError {
|
|
250
|
+
code: XverseRpcErrorCode
|
|
251
|
+
message: string
|
|
252
|
+
}
|
|
253
|
+
export function isXverseCompatibleProviderAPIThrownError(
|
|
254
|
+
err: any,
|
|
255
|
+
): err is XverseCompatibleProviderAPIThrownError {
|
|
256
|
+
return err != null && "code" in err && "message" in err
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export class XverseCompatibleProviderError extends Error {
|
|
260
|
+
readonly code: number
|
|
261
|
+
constructor(err: XverseCompatibleProviderAPIThrownError) {
|
|
262
|
+
super(err.message)
|
|
263
|
+
this.code = err.code
|
|
264
|
+
this.cause = err
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const handleRpcError = async <T>(
|
|
269
|
+
promise: Promise<
|
|
270
|
+
{ status: "success"; result: T } | { status: "error"; error: unknown }
|
|
271
|
+
>,
|
|
272
|
+
): Promise<T> => {
|
|
273
|
+
try {
|
|
274
|
+
const res = await promise
|
|
275
|
+
if (res.status === "error") {
|
|
276
|
+
throw res.error
|
|
277
|
+
}
|
|
278
|
+
return res.result
|
|
279
|
+
} catch (e: any) {
|
|
280
|
+
if (isXverseCompatibleProviderAPIThrownError(e)) {
|
|
281
|
+
if (e.code === XverseRpcErrorCode.USER_REJECTION) {
|
|
282
|
+
throw new UserRejectError()
|
|
283
|
+
}
|
|
284
|
+
throw new XverseCompatibleProviderError(e)
|
|
285
|
+
}
|
|
286
|
+
throw e
|
|
287
|
+
}
|
|
288
|
+
}
|