@gobob/sats-wagmi
Version:
1,248 lines (1,223 loc) • 52.3 kB
JavaScript
"use client";
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// src/provider.tsx
import { Network as BitcoinNetwork } from "bitcoin-address-validation";
import { createContext, useCallback, useContext, useEffect, useState } from "react";
import { useLocalStorage } from "usehooks-ts";
import { QueryClientProvider } from "@tanstack/react-query";
// src/connectors/xverse.ts
import {
AddressPurpose,
BitcoinNetworkType,
getAddress,
sendBtcTransaction,
signTransaction,
signMessage
} from "sats-connect";
import { base64 as base642, hex as hex2 } from "@scure/base";
import validate from "bitcoin-address-validation";
// src/connectors/base.ts
import { EsploraClient, createBitcoinPsbt } from "@gobob/bob-sdk";
import retry from "async-retry";
import { Transaction } from "@scure/btc-signer";
import { hex, base64 } from "@scure/base";
import { getAddressInfo } from "bitcoin-address-validation";
var SatsConnector = class {
constructor(network, id, name, homepage, icon, esploraBaseUrl) {
/** Unique connector id */
__publicField(this, "id");
/** Connector name */
__publicField(this, "name");
/** Extension or Snap homepage */
__publicField(this, "homepage");
/** Connector icon */
__publicField(this, "icon");
/** Whether connector is usable */
__publicField(this, "ready", false);
/** Address types depend on which wallet is connected. Some wallets support connecting multiple addresses.
For example:
- Xverse: payment (P2SH) and ordinals (P2TR)
- UniSat: depends on the user selection in the extension
- Leather: payment (P2WPKH) and ordinals (P2TR)
- BOB MM Snap: payment (P2WPKH) and ordinals (P2TR) */
__publicField(this, "paymentAddress", "");
/** P2TR address for ordinals and runes is kept separate from the payment address to ensure
that the user does not accidentally spend from the ordinals address */
__publicField(this, "ordinalsAddress", "");
/** The public key is required to spend from P2SH and P2WSH addresses */
__publicField(this, "publicKey");
/** The Bitcoin network (mainnet, testnet, regtest) */
// NOTE: signet is currently not supported
__publicField(this, "network");
/** Override for the Esplora API */
__publicField(this, "esploraBaseUrl");
this.network = network;
this.id = id;
this.name = name;
this.homepage = homepage;
this.icon = icon;
this.esploraBaseUrl = esploraBaseUrl;
}
/** Disconnect from the wallet */
disconnect() {
this.paymentAddress = void 0;
this.ordinalsAddress = void 0;
this.publicKey = void 0;
}
/** Get the payment address
* @returns The payment address
*/
getPaymentAddress() {
return this.paymentAddress;
}
/** Get the ordinals address
* @returns The ordinals address
*/
getOrdinalsAddress() {
return this.ordinalsAddress;
}
/** Convenience wrapper around the getAddressInfo function
* @param address - The address to get the type of.
* @returns The address type of the address.
*/
getAddressType(address) {
return getAddressInfo(address).type;
}
/** Check if the address is authorized */
isAuthorized() {
const address = this.getPaymentAddress();
return !!address;
}
/** Return the public key of the connected address.
* @returns The public key of the connected address.
*/
getPublicKey() {
return this.publicKey;
}
get esploraClient() {
let esploraClient;
if (this.esploraBaseUrl) {
esploraClient = new EsploraClient(this.esploraBaseUrl);
} else {
esploraClient = new EsploraClient(this.network);
}
return esploraClient;
}
/** Get the transaction from the transaction ID
* @param txId - The transaction ID to get the transaction from.
* @returns The transaction hex.
*
* @example
* ```typescript
* import { Transaction } from '@scure/btc-signer';
*
* const txId = 'f5e7 ... 3b';
* const txHex = await connector.getTransaction(txId);
*
* // Decode the transaction
* Transaction.fromRaw(Buffer.from(txHex));
* ```
*/
async getTransaction(txId) {
const esploraClient = this.esploraClient;
return retry(
async (bail) => {
const txHex = await esploraClient.getTransactionHex(txId);
if (!txHex) {
bail(new Error("Failed"));
}
return txHex;
},
{
retries: 20,
minTimeout: 2e3,
maxTimeout: 5e3
}
);
}
/** Send BTC to an address
* @param toAddress - The address to send BTC to. Can be any valid BTC address.
* @param amount - The BTC to send denomination in satoshis.
* @returns The transaction ID of the sent transaction.
*
* @example
* ```typescript
* const toAddress = 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq';
* const amount = 10000; // 0.0001 BTC
* const txId = await connector.sendToAddress(toAddress, amount);
* ```
*/
async sendToAddress(toAddress, amount) {
const esploraClient = this.esploraClient;
const signedTx = await this.createAndSignTx(toAddress, amount);
const txId = await esploraClient.broadcastTx(signedTx);
return txId;
}
/** Send BTC to an address with data in an OP_RETURN output
* @param toAddress - The address to send BTC to. Can be any valid BTC address.
* @param amount - The BTC to send denomination in satoshis.
* @param data - Optional OP_RETURN data to include in the transaction.
* @returns The transaction ID of the sent transaction.
*
* @example
* ```typescript
* const toAddress = 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq';
* const amount = 10000; // 0.0001 BTC
* const data = 'Hello, World!';
* const txId = await connector.sendToAddressWithData(toAddress, amount, data);
* ```
* @note Most wallets don't support transfers with OP_RETURN data, so we need to handle this case separately
* where wallets do not support it. This function is overwritten in the connectors that support transfer with OP_RETURN.
* @note Most Bitcoin nodes accept 80 bytes maximum for OP_RETURN data. If you want to include more data,
* consider using a dedicated service that can include such transactions in a block.
*/
async sendToAddressWithOpReturn(toAddress, amount, data) {
const esploraClient = this.esploraClient;
const signedTx = await this.createAndSignTx(toAddress, amount, data);
const txId = await esploraClient.broadcastTx(signedTx);
return txId;
}
/** Create and sign a transaction with an OP_RETURN output
* @param toAddress - The address to send BTC to. Can be any valid BTC address.
* @param amount - The BTC to send denomination in satoshis.
* @param data - The OP_RETURN data to include in the transaction.
* @returns The signed transaction hex.
*
* @example
* ```typescript
* const toAddress = 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq';
* const amount = 10000; // 0.0001 BTC
* const data = 'Hello, World!';
* const signedTx = await connector.createTxWithOpReturn(toAddress, amount, data);
* ```
*/
async createAndSignTx(toAddress, amount, data) {
this.validatePaymentAddress();
const psbtBase64 = await createBitcoinPsbt(this.paymentAddress, toAddress, amount, this.publicKey, data);
return this.signAllInputs(psbtBase64);
}
/** Sign all PSBT inputs
* @param psbtBase64 - The Base64 encoded PSBT.
* @returns The signed transaction hex.
*/
async signAllInputs(psbtBase64) {
this.validatePaymentAddress();
const unsignedTx = Transaction.fromPSBT(base64.decode(psbtBase64));
const inputLength = unsignedTx.inputsLength;
const inputsToSign = Array.from({ length: inputLength }, (_, i) => i);
const psbt = unsignedTx.toPSBT(0);
const psbtHex = hex.encode(psbt);
const signedPsbtHex = await this.signPsbt(psbtHex, [
{
address: this.paymentAddress,
signingIndexes: inputsToSign
}
]);
const signedTx = Transaction.fromPSBT(hex.decode(signedPsbtHex));
signedTx.finalize();
return signedTx.hex;
}
validatePaymentAddress() {
if (!this.paymentAddress) {
throw new Error("No payment address specified");
}
const addressType = this.getAddressType(this.paymentAddress);
if (!addressType) {
throw new Error("Invalid address type");
}
}
};
// src/connectors/xverse.ts
var getWalletNetwork = (network) => ({
type: network === "mainnet" ? BitcoinNetworkType.Mainnet : BitcoinNetworkType.Testnet
});
var XverseConnector = class extends SatsConnector {
constructor(network) {
super(network, "xverse", "Xverse", "https://www.xverse.app/");
}
async connect() {
return new Promise(async (resolve, reject) => {
await getAddress({
payload: {
purposes: [AddressPurpose.Ordinals, AddressPurpose.Payment],
message: "Address for receiving Ordinals and payments",
network: getWalletNetwork(this.network)
},
onFinish: (res) => {
const { address: ordinalsAddress } = res.addresses.find(
(address) => address.purpose === AddressPurpose.Ordinals
);
const { address: paymentAddress, publicKey } = res.addresses.find(
(address) => address.purpose === AddressPurpose.Payment
);
if (!validate(paymentAddress, this.network)) {
throw new Error(`Invalid Network. Please switch to Bitcoin ${this.network}.`);
}
this.paymentAddress = paymentAddress;
this.ordinalsAddress = ordinalsAddress;
this.publicKey = publicKey;
resolve();
},
onCancel: () => {
reject(new Error("User rejected connect"));
}
});
});
}
on() {
}
removeListener() {
}
async isReady() {
this.ready = !!window.XverseProviders;
return this.ready;
}
async signMessage(message) {
return new Promise(async (resolve, reject) => {
if (!this.paymentAddress) {
return reject(new Error("Something went wrong while connecting"));
}
await signMessage({
payload: {
address: this.paymentAddress,
message,
network: getWalletNetwork(this.network)
},
onFinish: (response) => {
resolve(response);
},
onCancel: () => reject(new Error("Canceled"))
});
});
}
async sendToAddress(toAddress, amount) {
return new Promise(async (resolve, reject) => {
if (!this.paymentAddress) {
return reject(new Error("Something went wrong while connecting"));
}
await sendBtcTransaction({
payload: {
network: getWalletNetwork(this.network),
recipients: [{ address: toAddress, amountSats: BigInt(amount) }],
senderAddress: this.paymentAddress
},
onFinish: (response) => {
resolve(response);
},
onCancel: () => {
reject(new Error("Send BTC Transaction canceled"));
}
});
});
}
async signPsbt(psbtHex, psbtInputAccounts) {
return new Promise(async (resolve, reject) => {
if (!this.ordinalsAddress) {
return reject(new Error("Something went wrong while connecting"));
}
const psbtBase64 = base642.encode(hex2.decode(psbtHex));
await signTransaction({
payload: {
network: getWalletNetwork(this.network),
message: "Sign Transaction",
psbtBase64,
broadcast: false,
inputsToSign: psbtInputAccounts
},
onFinish: (response) => {
resolve(hex2.encode(base642.decode(response.psbtBase64)));
},
onCancel: () => reject(new Error("Canceled"))
});
});
}
};
// src/connectors/leather.ts
import validate2 from "bitcoin-address-validation";
var LeatherConnector = class extends SatsConnector {
constructor(network) {
super(network, "leather", "Leather", "https://leather.io/");
__publicField(this, "derivationPath");
}
async connect() {
const userAddresses = await window.LeatherProvider.request("getAddresses");
const paymentAccount = userAddresses.result.addresses.find((el) => el.type === "p2wpkh");
const ordinalsAccount = userAddresses.result.addresses.find((el) => el.type === "p2tr");
if (!paymentAccount || !ordinalsAccount) {
throw new Error("Failed to connect wallet");
}
if (!validate2(paymentAccount.address, this.network)) {
throw new Error(`Invalid Network. Please switch to Bitcoin ${this.network}.`);
}
this.paymentAddress = paymentAccount.address;
this.ordinalsAddress = ordinalsAccount.address;
this.publicKey = paymentAccount.publicKey;
this.derivationPath = paymentAccount.derivationPath;
}
on() {
}
removeListener() {
}
async isReady() {
this.ready = !!window.LeatherProvider;
return this.ready;
}
async signMessage(message) {
const resp = await window.LeatherProvider.request("signMessage", {
message,
network: this.network
});
return resp.result.signature;
}
async sendToAddress(toAddress, amount) {
const resp = await window.LeatherProvider.request("sendTransfer", {
address: toAddress,
amount: amount.toString(),
network: this.network
});
return resp.result.txid;
}
async signPsbt(psbtHex, psbtInputAccounts) {
let inputs = [];
for (const input of psbtInputAccounts) {
for (const index of input.signingIndexes) {
inputs.push(index);
}
}
const response = await window.LeatherProvider.request("signPsbt", {
hex: psbtHex,
signAtIndex: inputs,
network: this.network,
broadcast: false
});
return response.result.hex;
}
};
// src/connectors/unisat.ts
import { AddressType as AddressType2 } from "bitcoin-address-validation";
// src/assets/bitget.ts
var bitgetLogo = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAMAAAAKE/YAAAAC8VBMVEUAAACS3/uZ5/rF4v7h/f9K6Pdi0vtTyvqnt/5T1PlH9fakpf/p//88/fXs//5G8vXy//5F8/Zj1vqvuv514PrI7v2J3/o6/PTE7/1A9fbU+/3Y+/1R0vo7/fXh//7G5/3l//6jov6k8fuxs/6ppP6/1f5O6Pfv//7n//+bsv4AAADy//7r//7l//45//RE/vTg//1I/vVB/vRL+vXb//09//RM/vXV/v19/vhQ/vXw+/5z/fc++vVh+feC/fii/fpf9PdQ1Pmxr/6y/vuM/fm8/vun/frB/vyt/vpq9vi3/vtZ9fdRyvq1s/5N4Ph4/vfj9v6tqv7e+f1q/PbK//xS3fhS6fdl+/bX2f6c/fmS/flw6fng7/7f4/66uP5O2fmB8Pla5/jQ//2H/fjT1P6X/fl47vlV/vXLy/5p7fhc+fbHx/5v7/hL5vdR5fhT+fbP0P6T7/p15Pp09PhB9fWL7fp6+vhn8/di8PdG8Pbb3f7F/vyppf586Ppu/feK8/rX+v1W2PlN8vbW7/1Vzvrb8/6D6fpE+vXO+/zj6P5x+PjV9v1Qz/rn8/7m7v7Y4v5X7vfCwv+lo/7R6f3I/Py/vf9H9fbq+P7Q4f5Z0/qt9/ub8PuC9vlU4fiT9/rs/P5t4frQ2v4KGBje6f5+3/pI6/bCxv569Plj4/nY6P7B1P67wP7K5/2gtP2/+vzJ4P7P8v1c7vi2+PzL7/1b3/nJ0/5P7vdm3Ppj6fnEzf7Q9/2zuP6k9vtM7Peb9/qJ3fpf1vq5xv6TyfyO5fpd2/lZ/faG1vrC3P6rs/6k8PuU1fvJ2f6+y/6Xvvyl6Pu2vf633P205vxqeX5Cf32lxv2i2fyW3vuorP6hq/6+5P2wyf2u7fys3/zD6/2gvf2puv0dHiC40P2w1f2n0PyN0PuvwP667f2d0fybx/yi4ftYe3xPn5oufHvW7+5U7uZwm5oQLy3R3N5G29eEnp53zco5WF0qMC+dzM2MzcwsLzAtbGshLA3xAAAAKnRSTlMAIf47Wjuk17BXvH1536di36KDXdSkaIV1cd+/6c/v5s/Pvd/f0+PKk9+fkOCeAAAZMUlEQVR42szVvarqQBSGYS2iJHAQbUTIaXdpceo06cPuYruvIKnSpFQCaayFFCm9BbuAl3a+NWtmPojljj/vGqwfFkudTdo8DDeLdRTHcSqd43i5XCw2YRDMPrJwt/76Rj+2VNlalmXLxeaj5HN4i0LEVKORGkEefoR8tf66FtrYrerUsQmfvbVddDVZc2HJqk7xqBY24eF89p6CqK6VjKFa8ux0xGbv2Hewvdboatkm7hrDCxmreSevWTeXXEFMNRLyw67pJhvR/bp1r6IK1RXN7j6IBjtXM9VvZK/+VNaskY3ozsHWyHa9lE0y1byPK81ctardbb+FHUQkc9mebfPsPM8h98smvDk3WdbIWPbz/nLmUZIkVeLYY/Og7BKjZrJzzz5jGkFLmX4K/Vm/JLsKZKIfVj2I2ubMUEuy7KNlN7JtQdOdYeLNbPqCv4lk0CRfatswAD0UQ3HAFGVZfpdAO7NeyTHFOHfz0HLyZW8dmepLdbkY9CCjaoGbSqTL1o6C/jHmTp7UjdnTLntu16zkxJEVfbJwax6wa1WD3ZOtt30E24BBxqTPW/au2icJnoe3To2gxqNa4Hoiyu773CVoyV5J1ym689PEq6nWvN3v985c6aDWmq16GG7OfDgcQLb1YMuy/Zlg1H3EtiWoUSMPbSY6DTXjeXfbVq1ZNE/kdEODo+NEvLrslU042Z2Dd74pTiSoQJbx5ARisFtnPl3qExI2yCIHGpW+XnJiGdaNunf33//T7MyayQZZxphbr1Y21EjoBk24+UYqm3S6IfdifNzvvzxsPWdH/qfqSsxKtmrthufgh4Forptmkh37LmP7T2odu6YVRXEcJ0OyZWiQdO/YoSERBaGDu7uL01ssOkSQBrI4xMGsb+oWeIQMrsKjvlFo/pwOJdC933PPvfc8S4hX8zvn7h9+nLz4rsP+gNmrW4y4fc/e3WRomglxYjYmXrZns6XKfTYsZgns96oxawL6yszfIYtZFrapresIt659UDN3ZWA/s5vN3SZmvTk5/LNhPRN/0jHN+6aEsutkXlCTN9wUjpXcMQQ268jsunGoOUZqlqav6DlGwZ7c5Glyz+7L1tj9h7q6fCgpu6RuuDGwRUwOVEdzS4uGTO6Z0PMP557zME/mFsiv1y1uR9aVUHMZyevnNWSeqg++Z/3iyT0r+cKKdlUrG+nLn8c38xc15MyNk4dE9VrGsvddf7SeIX+5avmeL0Cz2rS6Vf3y+PnNYCbX2XXmyaCj25N5IcW6KIqTvc2mjrdxISO5vES9lSRzxogad5aVGVqre/nM1MmiPtvr/6CJGaJmF4rGHLUTZj5JMgc3ZNioiReX61LRSwYz43Kebj6u9UwwG5qaKfpSvObeac6dOO/D5IWUzDKDvHT5XzwtptPj5I/dqRVtZn8Z96iJiRPMv3NJn8lYczszD7WcB5GuixDM06ejw8yADe3Jiu6oO8X8E7FLxtazxGwpIFvNksY+f4Rm9mo7DSsadWrPBDZkHmNkpm5mt9Vn+/4RuhiZqLnd9uZOp5NstqZXwVyJm2QBzRiYPE2fZucJx/HVzAFtaiWDFna705nsNvfy3rxXZ68kwBHzmKpassuqkBm4mYbMZrOEsz599Z6NTMSMuA06wdxDLWPmHLJnV2r2U4zUPB0oGDLmRSPxoFuvoaMYbpNJMhN1s4AtFQM5BHEwDzCzkCWLxeJsx3HEg3bk7YOOZlVPks2531Uvp+Y6O2ZQVaPRCK+QGcjBTI72PQ4j180k2UyUzHTVe6vm6B6xA8w+8TYwSxppXzszR7I8I+9nzru9XlfN3VtlS6qKlYpDzSNtejgd1sg3i5vzlC+HS71myO0t9Lfd5vF4HNBdIXeFLF7EIag9mfVVDweQTY355tfRzt/QLUMrmYEM18hJZtTjgCarrkSsuFnIoxhqDuShkQUt+bTjd1JL0du3gTmKnfofZ3YP2lQUhnE8fg0WURz8RgcHcRQVWgmxgrUaHRwiSOsSlCpKQEJoKoiNk0hmp6Zk7ebg5BDcHDp3dHdQUREFcfL/vu+5901yYu6NzznnJoPDz4dzr/HcfGaiXruYWVaah0wz12p3akY2c/VZ9UVVa6bo5fqOjLtwuGeCeFKziYM8IpvYi67VHtaImoPaatbU6/V/VL0viGMzPbsZ8YMHX7/nMZf0cqlELjmbGdwrJJjpmXG/ZmTM1aoVbWhycEzRTo57NnJOMwHLErIMy4qNJwwhMxBbEIeYGHAgk872kUUrOTKL2sk5zaU4Tge+wkStcTLDyPfZz2S5mpo79U7n4KiiI7MX7eQLE5tdzHVFhplTctnMjUYjrVmbdjNosn1k0SaOzRP3fLt0m8lFpgWsLcyeMgnkGmQzB7WR3UzVcdFoxz82aDmX+TZRMrGvTF18L5dLKZiotwx5uOjWckvJbt7cjB4gu+OWHX0BM+jc5jjYWWUmbKanDZmoeYNUA7q6LG43g94cflZP9ZvxMoe3M3n//j/N5i0p0821djmA2RyNjY37G1VDt1rVFqnXmfWEHFW9LXh9a7i5r+ac5gqzwuTCNyOzTOvsdrvdaDdCMHvRgOlZyd1O512n00MdVb3/7Nn4h2j/wzlfz1sYoyi+XAFf4VpmBHGZ0Q5iM1P0OmlpMBMzK3lt8/DgbegtR0+N/D1/Vh9rZNCqebVcUa8XDdmyvl6tJuZuq1tnYH63Kere5trah239z7uoZcj/ZSar/zQbeVU6TqNoE2NOySbuYiY9yZqkf39MGVrFkTmQs82rq5XVJBUmST+5shiQ2ywnY24yEIckPSPGLEX3pObNtQ9rayfdvM2OjywDZMT5zZlpsxBXEnKz2WywRKxmJ4dYzV70mzfb+ndHAAdyVHNOc3G1WAxrlJlhNRdlNlFjpmXcqfkeZBlha3R6plYy6MO+O0wc7YyJzMXRaetfoCjL/oiRCUWHgHVyay6p2YtGLGji+yOYnWyHGupFnMdczBfAem2m4g3MzfWLjHsM0ETJ8d5A7ftjt6HdDFkyqfkmgysfOkanKcOjZDUreW5urmtNYyZ95JCdAb1fxedkndOe5Yzu8YPHmEles2GHUmSmfvmq5ptOvti8SIRMMLfmTOxm0K5+/fpA8i8L5nMyEjNBnJhLmWZo2WkyBsEzDMBSs4jVvNjtMpfemXm215vvJ4P+aOZd6RsUTkKlY5LW/Oj9o0xz6pqxi82ZmWE1PevAPTOjYswSyLClZsQEtPfsZsjENvU+E4cDZxWzgjhHzwEK818RsS0r2MQh66lYyKoGvPBudhbxvBVtZIs99Kb6Xp+EUDNkTelPlnkAe1HXuGjBM2hZ1jKZs1twcdFqBi3m+V4wX1a0sQ/Yljaxk98/puJALv3IMA+Kocjgc4wf6hCZKBnx0hLiBcyz1Owb+rqJX716elTRVrKbHz1OyWQrp1mpcVztf4jpYsiaxbnF7uIi5CXIbI7eLGRVa82Yg/qVbOpdSmYmZLSJmd/uX8aZf5lkOFtj8zkVO7mLGLJExAQz5GuYL1/2niU7QR9Sq5NvGFjIgs5o2sVGkXnv5/gzs617LmbSMgMyaMi2NdgckK8Z+fqVK1cwX70q5pdyJ06l4htiTtBEzJWvGXs65fYln9r3BUlqRp3UDPka5MvXQav5qZpfyp14PCVzJAvZxGYu88M96+nh3EnUaGMzYjdDxgz5zaD5pdyJwWtmI9tZUNnS/pWh9s3pyVSnZhdDJr6dzWx7g63xFDRmwn0oaGI1c4ycklfM3M5U442TpXaxmyGnPbvZezb03W2FXTc8lywlEwcyydohSWsTqcFavOXYHNBX3Xx3enpnYZ+LA3mFBLKgG2QrS83OjJJH7befk4mSid+DtqHJtKCPDHQsR8eEoiE3ZGg2stQKmFSN2MxxzYa2nq+a+ZbUfHea7CnsV3KiHjgcVHOzIf9VzqNWg37KWMqhVnFE9prNrPcgaDOT04X9/l4BrQVwuZEknKJkqZdGJUvtYt8Zkth8y83n9xam4PpLEEkNMoev/WTQ+dULE6jHkUebn0+fP1aYMq6TV2rEWyZCzlZ/40fDQpQM9SfIlpTsNQczaMxe9PmjhVOJ+Im8s3loZCbu+3jt8JXjqtZ6K1MNe2J1AHvNI8237pJgPnO08NbM6eu8/hcgcmBMkhO2TLV1thA+mIwcahc7uc98i7xMzeREIZBFXTNzeMkEuRoiR9z51D20HqNnqQfIXjNmEvVM02cK2rHX7OaY3G3V83Q9bxfP7ww15OgGFLMXPWAG/TbdGbyeZrg5qJfl7QfH8jLqWeqf88PJo453BmTvWYt2M+i/zdxfaJVlHMDxczwRi8WIypCELCr64wZdCIGXhRRBMKULoSkRXgjdWNBNKQhjkgyTWqHUxTKH4PxLsgvbxZiC1rSp4dTN6dp06oKQiP7d9f39fs/z/s7x0b3vdFbf5zlnXX749Zyje9+3TPymiDUFr6sma+++j5grxjnqr//CmXb9+Ry1ilMy5nTOoAM5iFmIWes22K1pzIEcb5z+nKN+1bNPWBF1aoacnmdDN5VcDPi9+EjLWxsswI5WcxH1F7Jry1PDTcaM2NBOVnNT6Su08XmndWxtwzoVZ2ZNxQXV/Pt+9eWZqWvJZGQ3k5kfKz38ZnioDPJ7Rnaz7MNOjnf08tTy2yivqgqo8Ro5NVOVuekB0ORP76k5dBgxZllz3p3z5ZxALqDGbKu6PHVCTs2Gfrz0MNgoxtwFWcEbDhNmEZOKBzEXU38jv4++zHoNLa9C6kCexgyZ5pfu5WFUM3chxtzF6gtowmxznjOI+vtBxOe/P38+R/0naDarujx1Qk7nTI0NoKUuIVuIMYfmxCAPDmJmCTpfvYReW4J0Jmonuxmym7WG0oNmZpFOua/v8Ia+YGYZWZaqB8+LuZB675KkHPWPJnZyaqb7S49ARhxC3NV3OJDPAM7MGmTQVES9d+8SNvSZqJ2MWbvR3Nh4T+k+qBlZlgYc85kzZ4KZbeZMfeX8lXy19QotWcKL9Uq+GnJynN0MulIq15jNywLMnsOamjM1NTU4FQZNgUx56lf2sjR+xPLOdUJ2tJIFXcrImENn+hCTqCGTmU8Nnh89f0rWqSuw89UfutX7Lec/LwlkSsiYiWt5D3d1Hek6coRXlZmtTZ0BbJMeZaFGDBkxFVDfrBz1z3DNrOpAdvPjoO/FDLnvyJE+WQO2jBzVNDqKehQwoT5VUP1Z6MPPPmQXU/+i4lD1mA3dAPoRnbIk7r4gNvXQ1JCJp1SsZidfuHLhwoUCau8NW29Mr76uZrabg1q7H/R9R7wBWZqoh84MDQl7CDXVqIevDAf19R+n6/pHGbi636f9BjFvejasSoky8uVq8tAAYsJsZEcPnxoeBg04dCBrP32rfVQbFw/5ndrEuZN2cWKmsqAfDmLWZdQx1EyaRlnRfJKFGjJmtohZkXwAMuiMfQP9bfYbrJwz/WtqbozmhY0PlKR7VSwNXB5gS1chx6L55OhJFVtGNrVPWcxx1MLmvcos8Z737ZGQlR3VDYp+BDLbCuaBqzR0NZgnRicmMDNmOnXS1budDDqwvaC3y8oBnWv+45f0ZDi5ceE9ii5DDeBDA2zxBvMl1oSaJzA7WdG7h3df2E0HdpuZFN2Ou729HS5v6+lFCbaVa4acjNnNCyslLUMfGjjEjupLVzGbuoo9LIv2DWNmHVB1x4GO/a1CRpu1Xl7f6voIue58M9hkzk5eyJHW7oV7+ZB1VVZUaxOXAEOeOGkdPTl8dPiomDXIHR1x0q2trcJ29fqqIM/EXDNlZzcE9COihR3d165dvSZktqNRB/NRaXjfvn3D+wK7g1o7Wgl2e6ura8nFzYjT08wijrSFNHTt0DXCfFrItaMeh9wN+SSDPoo4kAHTfsxk5ra2NkcvC+pFi9YvKmRG7FVN2SpHdF2N2Tp9+tLpKvP4BObx7jhp1IR76+4O2NmoqQ0zW1rftgxzRi5irhU3OTmM+hm4fj5+YEFmq5hRq3rk0sjIxAhmFuzu7m4zG5lhb8Vr6GBuYxPw9mWYswqZU3Ekk58ODTDrGkvJbA31CGpBW92oa9hbSdk7O3buVDM7qJfJRhvpueZqcC05PR1U9wOpmi3koBayND5i5nEhC/pYNXkrZMygqcXECvYKml3tOZgaSl5FyNq1ixcvIg5mtobZ1biPHT12bN+xfVsjGzLmnTtbBC2rWd3NMzKnU/aj0einw4OrXfwBNBkaMhuyq9WMmhBbHA2rZTtg24iblzXzxntxc/S6mOVk/ZPFq3e1mtljp09Pwrb6R8b7x8ePj3cfVzZkVW8z9ebNQt7Obtneqmp1I4Ysu/l6AbPP2Mm8LD8dXrlKbOrTqCcnJ0cmzQwZNGSpR9XbyMhRTS2h5pZmduzH6c1/Y47c9GBkbv8Y+kdxD+rIHrs4NjY2ORnZoPv7jx8fPw77IOieHlOTqimgmbWrC5sbbyo2the+pL2KoPewzcwCDVvV/dJ4/3E6ePBg98EeKYwatZpRuxkyrzswkzmd7R9Drw4waroIXMyZun+SSRNqYZOYeyCLejloCbGqrU0tzZs2NW+audnmm/3w/GPoPaTgoBaxqiP7hJpVbWYyNOblQb3C1BtNjZlXYbOT2Wk26KTnXD3GZtQk5hOTJzBrhs7YqzBvWx7MK9S8kVF/bEGm4mafrX/PJYNORu19NzbGFnOvqGHv6u/fJaPekaHXbFsFehvoDzZ/gFnavpFAs/EWNgM0p67ig/ZRQ9aXsHvZmGXUsIHv2LHD0KtRg16FGTVoIa9YsVHYH4eKmF3sXFfnDFpHfe7cHhZiWcS8e+mEtIvEfNzYq3vWrFmzigS9GTVoEnNLVOeaf/qlk1zs8IKDpjrEdPbcnrOqpt7vxlQd2TskMa9eDbpHZi2TphXKxkxBnWv+tVM0vCXggoOmCmKd9VmGHYLss47qd95RtI06U2tmtnLNgEW6OAesVaIyHbWGWgpqcxv6k4jWUUc0KdrVxcyLhcsb4NwaSreq/JyizzJqzFGt2ahhfyJoRu1qRZOSQUvFzExad36d5dItq4dMYdLsl1hx1CtXKvoTR5OjUUtK3ljMrLtQ95em6YlgDmzp9d5edu/KlTrqz3NGDZqKno2UXfxT6JXPnfuUBdhi0uZGTaBRY3Z1NdrV+WYjF6xcmrb6T0HrsZaWLpUD8tLrr+usCbSrk1HPxKyTLibncOT0xKfa2rVrMaN+iURtaMygUaejdnURM2j2nR0Or4z4HOssatiobda0UhI1+aiDWsiCLng2bNGdfXP4AametagpUdd+Fn3SVGzO8XTc+eFwNWZamqgx0y1GzawLmouK6VlTFT3WpGZXZ8eabnaqi5r9TOf2QNH/Yd5Tho5qR7s6QZu6oNkmPYtmqkCWMKN29i1PNRn698JzLsiulApXH9HCdjSlXyA+6qJm71bsxrj5EN6WGjSZmQSdqo2db95SDWbdDGxeNxev7gZ1NmlXJ+hc8xbM7KpqsLZ80v7FUVzt6PSz6Gg/IL//NG1/Z+ZOH7WxjVmLp4bSjHvC1QVONSV/Q/XfFWmLJlafNvyFnUHsk46/nnMR7PbUjna1oikZNTlaq0FvWqxsyJ0WZDY8FbO1+A/PlEu3pwbtakenp9rQqdrMgb1pi8y4083yhje5HCY9jvm2qnN0jbr2b3vT/AoDmjKzBV3Njfam4KSG0m1X72rMS6cdNTnaLoGwHY3axJhRq3gx5oWzZnb12vRrr3bUhvZR20WyeOHG0GHWMcxBzb5ZfD/fmdrRPmlydDLqzYaOlyNbIPuslcwW8c3J8lzmHVZ5CrWjXV2LJrmy5+rtoIXdImwRN7OzSbOaOpswp+KmxyqlO678lKLXJucjHbWiN4NWNWy/xq7s5i0sRQNm+72hqtuefG3MRvXT/AGzA7Wjw5VfbSdk0kmjxqyBbrJpZ0wyvuxnMc+a+uZf1Y6mbYZebmZSNGy7bWRqG7Y6t7g29pgf51k4Io5WtV8DQQ26Z/WaHg61thV1YNfeows16drCWxJHYzard7Wjd4EmLkfCXiN3YfSGBvcVIRu6FbWa2R5ezMZ+gc2SHz7mWRt2Ldqv7NlF1J4eY9tNOj8gLa3b2/TOc5vdwQ2r+YWmZTDBsq3HK6XZr77qfPRGdbzK7ve7YKM2tA1bxHqTHy3bQrmMlcVpviuV6yKai2SKRm1oZ2NWdYeiW3e2arBrH6fAG+BWQ7l0t6o86eiqWwOZ2W+Yo8ZMasYb1J6RFy1iL5pfKd3NHoKdol3NswmgpQ6KaDJzxl4ki+Tt0XtKdznYoCneOurnjlc1Okxan7eBHc2o21k8gwVZXia++2RnO9pv43ZjpvjADWrY/hRZfFQPd0jI8+862dl1UQ16h6kdjVrNZOb9bkZdFWf536xc9+SNo+bpFTWjpvCYIfE0p6x2czv7sfv/RbKPG7Tf5+8WtLCzxwxhA0ettbOtMORy6T+p/FBdLVrMR33SvPTh2aiWp5O/lebP+2/EPu+nFd1t+ROdiDEfYEU1aHp07j3l0n9fBbijpYim6ifCAc/7P4BdPm/u06DNjDqSQWsL5s77X0w4rVJ5aN7cuU8/vWABo+Y59gULFsyfi3aWuf8ADHnSl6eWnZwAAAAASUVORK5CYII=";
// src/connectors/unisat.ts
var metadata = {
bitkeep: {
id: "bitget",
name: "Bitget Wallet",
homepage: "https://web3.bitget.com",
icon: bitgetLogo
},
binancew3w: {
id: "binancew3w",
name: "Binance Web3 Wallet",
homepage: "https://www.binance.com/en-GB"
},
unisat: {
id: "unisat",
name: "UniSat",
homepage: "https://unisat.io/"
}
};
var UnisatConnector = class extends SatsConnector {
constructor(network, source) {
const { homepage, id, name, icon } = metadata[source];
super(network, id, name, homepage, icon);
__publicField(this, "source");
this.source = source;
}
getSource() {
var _a, _b;
switch (this.source) {
case "bitkeep":
return ((_a = window == null ? void 0 : window.bitkeep) == null ? void 0 : _a.unisat) || void 0;
case "binancew3w":
return ((_b = window == null ? void 0 : window.binancew3w) == null ? void 0 : _b.bitcoin) || void 0;
case "unisat":
return (window == null ? void 0 : window.unisat) || void 0;
}
}
async connect() {
const walletSource = this.getSource();
await walletSource.switchChain("BITCOIN_SIGNET");
const [accounts, publicKey] = await Promise.all([walletSource.requestAccounts(), walletSource.getPublicKey()]);
this.paymentAddress = accounts[0];
this.ordinalsAddress = accounts[0];
this.publicKey = publicKey;
walletSource.on("accountsChanged", ([account]) => this.changeAccount(account));
}
disconnect() {
this.paymentAddress = void 0;
this.publicKey = void 0;
}
signMessage(message) {
return this.getSource().signMessage(message);
}
on(callback) {
this.getSource().on("accountsChanged", ([account]) => {
callback(account);
this.changeAccount(account);
});
}
removeListener(callback) {
this.getSource().removeListener("accountsChanged", ([account]) => {
callback(account);
this.changeAccount(account);
});
}
async changeAccount(account) {
this.paymentAddress = account;
this.publicKey = await this.getSource().getPublicKey();
}
async isReady() {
this.ready = typeof this.getSource() !== "undefined";
return this.ready;
}
async sendToAddress(toAddress, amount) {
const source = this.getSource();
if ("sendBitcoin" in source) {
return source.sendBitcoin(toAddress, amount);
} else {
return super.sendToAddress(toAddress, amount);
}
}
async signPsbt(psbtHex, psbtInputAccounts) {
if (!this.publicKey) {
throw new Error("Something went wrong while connecting");
}
let inputs = [];
for (const input of psbtInputAccounts) {
for (const index of input.signingIndexes) {
inputs.push(index);
}
}
if (!this.paymentAddress) {
throw new Error("No payment address specified");
}
const toSignInputs = inputs.map((index) => {
return {
index,
publicKey: this.publicKey,
disableTweakSigner: this.getAddressType(this.paymentAddress) !== AddressType2.p2tr
};
});
const signedPsbtHex = await this.getSource().signPsbt(psbtHex, {
autoFinalized: false,
toSignInputs
});
return signedPsbtHex;
}
};
// src/connectors/mm-snap.ts
import * as ecc from "@bitcoin-js/tiny-secp256k1-asmjs";
import { BIP32Factory } from "bip32";
import * as bitcoin from "bitcoinjs-lib";
import bs58check from "bs58check";
import { base64 as base643, hex as hex3 } from "@scure/base";
// src/utils/constants.ts
var INTERVAL = {
HOUR: 36e5,
MINUTE: 6e4,
SECONDS_30: 3e4,
SECONDS_15: 15e3,
SECONDS_10: 1e4
};
// src/utils/metamask.ts
var PsbtValidateErrors = [
{
code: 10001,
name: "InputsDataInsufficient",
message: "Not all inputs have prev Tx raw hex"
},
{
code: 10002,
name: "InputsNetworkNotMatch",
message: "Not every input matches network"
},
{
code: 10003,
name: "OutputsNetworkNotMatch",
message: "Not every input matches network"
},
{
code: 10004,
name: "InputNotSpendable",
message: "Not all inputs belongs to current account"
},
{
code: 10005,
name: "ChangeAddressInvalid",
message: "Change address doesn't belongs to current account"
},
{
code: 10006,
name: "FeeTooHigh",
message: "Too much fee"
},
{
code: 10007,
name: "AmountNotMatch",
message: "Transaction input amount not match"
}
];
var SnapRequestErrors = [
{
code: 2e4,
name: "NoPermission",
message: "Unauthorized to perform action."
},
{
code: 20001,
name: "RejectKey",
message: "User reject to access the key"
},
{
code: 20002,
name: "RejectSign",
message: "User reject the sign request"
},
{
code: 20003,
name: "SignInvalidPath",
message: "invalid path"
},
{
code: 20004,
name: "SignFailed",
message: "Sign transaction failed"
},
{
code: 20005,
name: "NetworkNotMatch",
message: "Network not match"
},
{
code: 20006,
name: "ScriptTypeNotSupport",
message: "ScriptType is not supported."
},
{
code: 20007,
name: "MethodNotSupport",
message: "Method not found."
},
{
code: 20008,
name: "ActionNotSupport",
message: "Action not supported"
},
{
code: 20009,
name: "UserReject",
message: "User rejected the request."
}
];
var BaseError = class extends Error {
constructor(code) {
super();
__publicField(this, "code");
__publicField(this, "resolve", (fn) => {
fn();
});
this.code = code;
}
};
var SnapError = class extends BaseError {
constructor(message) {
const userFriendlyError = mapErrorToUserFriendlyError(message);
super(userFriendlyError.code);
this.name = userFriendlyError.name;
this.message = userFriendlyError.message;
}
};
var mapErrorToUserFriendlyError = (message) => {
const psbtValidateError = PsbtValidateErrors.find((item) => message.startsWith(item.message));
const snapRequestError = SnapRequestErrors.find((item) => message.startsWith(item.message));
if (psbtValidateError) {
switch (psbtValidateError.name) {
case "FeeTooHigh":
return { ...psbtValidateError, message: "Fee too high" };
default:
return { ...psbtValidateError, message: "Transaction is invalid" };
}
}
if (snapRequestError) {
switch (snapRequestError.name) {
case "NoPermission":
return {
...snapRequestError,
message: "This error is usually caused by resetting the recovery phrase, please try to reinstall MetaMask"
};
case "SignInvalidPath":
return { ...snapRequestError, message: "Sign transaction failed" };
case "ScriptTypeNotSupport":
case "MethodNotSupport":
case "ActionNotSupport":
return { ...snapRequestError, message: "Request error" };
default:
return snapRequestError;
}
}
return { message, code: 0, name: "UnknownSnapError" };
};
// src/connectors/mm-snap.ts
var getSnapNetwork = (network) => {
switch (network) {
default:
case "mainnet":
return "main";
case "testnet":
return "test";
}
};
var getBitcoinJsNetwork = (network) => {
switch (network) {
default:
case "mainnet":
return bitcoin.networks.bitcoin;
case "testnet":
return bitcoin.networks.testnet;
}
};
function anyPubToXpub(xyzpub, network) {
let data = bs58check.decode(xyzpub);
data = data.subarray(4);
const tpubPrefix = "043587cf";
const xpubPrefix = "0488b21e";
const prefix = network === bitcoin.networks.testnet ? tpubPrefix : xpubPrefix;
data = Buffer.concat([Buffer.from(prefix, "hex"), data]);
return bs58check.encode(data);
}
function addressFromExtPubKey(bip32, xyzpub, network) {
const forcedXpub = anyPubToXpub(xyzpub, network);
const pubkey = bip32.fromBase58(forcedXpub, network).derive(0).derive(0).publicKey;
return bitcoin.payments.p2wpkh({ pubkey, network }).address;
}
var BitcoinScriptType = /* @__PURE__ */ ((BitcoinScriptType2) => {
BitcoinScriptType2["P2WPKH"] = "P2WPKH";
return BitcoinScriptType2;
})(BitcoinScriptType || {});
var getDefaultBip32Path = (scriptType, network) => {
switch (scriptType) {
case "P2WPKH" /* P2WPKH */:
return `m/84'/${network === "main" ? "0" : "1"}'/0'/0/0`;
}
};
var DEFAULT_SCRIPT_TYPE = "P2WPKH" /* P2WPKH */;
var snapId = "npm:@gobob/bob-snap";
var MMSnapConnector = class extends SatsConnector {
constructor(network) {
super(network, "metamask_snap", "MetaMask", "https://snaps.metamask.io/snap/npm/gobob/bob-snap/");
__publicField(this, "extendedPublicKey");
__publicField(this, "snapNetwork", "main");
__publicField(this, "bip32");
this.snapNetwork = getSnapNetwork(network);
bitcoin.initEccLib(ecc);
this.bip32 = BIP32Factory(ecc);
}
async connect() {
var _a;
try {
const result = await window.ethereum.request({
method: "wallet_requestSnaps",
params: {
[snapId]: {
version: "2.2.2"
}
}
});
console.log("Using snap version:", (_a = result == null ? void 0 : result[snapId]) == null ? void 0 : _a.version);
} finally {
const actualNetwork = await this.getNetworkInSnap();
if (actualNetwork === "" || actualNetwork !== this.snapNetwork) {
await this.setNetworkInSnap(this.snapNetwork);
}
this.extendedPublicKey = await this.getExtendedPublicKey();
this.publicKey = this.getPublicKey();
this.paymentAddress = addressFromExtPubKey(
this.bip32,
this.extendedPublicKey.xpub,
getBitcoinJsNetwork(this.network)
);
}
}
async isReady() {
const snaps = await window.ethereum.request({
method: "wallet_getSnaps"
});
return Object.keys(snaps || {}).includes(snapId);
}
on() {
}
removeListener() {
}
async getExtendedPublicKey() {
if (this.extendedPublicKey) {
return this.extendedPublicKey;
}
return await this.snapRequest({
method: "btc_getPublicExtendedKey",
params: {
network: this.snapNetwork,
scriptType: DEFAULT_SCRIPT_TYPE
},
errMsg: "Get extended public key failed"
});
}
getPublicKey() {
if (!this.extendedPublicKey) {
throw new Error("Something wrong with connect");
}
const network = getBitcoinJsNetwork(this.network);
const forcedXpub = anyPubToXpub(this.extendedPublicKey.xpub, network);
const pubkey = this.bip32.fromBase58(forcedXpub, network).derive(0).derive(0).publicKey;
return pubkey.toString("hex");
}
async signMessage(message) {
return await this.snapRequest({
method: "btc_signMessage",
params: {
message,
hdPath: getDefaultBip32Path(DEFAULT_SCRIPT_TYPE, this.snapNetwork)
},
errMsg: "Could not sign message"
});
}
async signInput(inputIndex, psbt) {
const psbtBase64 = await this.snapRequest({
method: "btc_signInput",
params: {
psbt: psbt.toBase64(),
network: this.snapNetwork,
scriptType: DEFAULT_SCRIPT_TYPE,
inputIndex,
path: getDefaultBip32Path(DEFAULT_SCRIPT_TYPE, this.snapNetwork)
},
errMsg: "Sign input failed"
});
return bitcoin.Psbt.fromBase64(psbtBase64);
}
async getMasterFingerprint() {
return await this.snapRequest({
method: "btc_getMasterFingerprint",
errMsg: "Snap get master fingerprint failed"
});
}
async signPsbt(psbtHex, _psbtInputAccounts) {
const psbt = bitcoin.Psbt.fromHex(psbtHex);
const masterFingerprint = Buffer.from(await this.getMasterFingerprint(), "hex");
const publicKey = Buffer.from(this.publicKey, "hex");
const bip32Path = getDefaultBip32Path(DEFAULT_SCRIPT_TYPE, this.snapNetwork);
psbt.data.inputs.forEach(
(psbtInput) => psbtInput.bip32Derivation = [
{
masterFingerprint,
path: bip32Path,
pubkey: publicKey
}
]
);
const psbtBase64 = await this.snapRequest({
method: "btc_signPsbt",
params: {
psbt: psbt.toBase64(),
network: this.snapNetwork,
scriptType: DEFAULT_SCRIPT_TYPE,
opts: {
autoFinalize: false
}
},
errMsg: "Could not sign psbt"
});
return hex3.encode(base643.decode(psbtBase64));
}
async getNetworkInSnap() {
return await this.snapRequest({
method: "btc_network",
params: {
action: "get"
},
errMsg: "Snap get network failed"
});
}
async setNetworkInSnap(expectedNetwork) {
return await this.snapRequest({
method: "btc_network",
params: {
action: "set",
network: expectedNetwork
},
errMsg: "Snap set network failed"
});
}
async snapRequest(req) {
try {
return await window.ethereum.request({
method: "wallet_invokeSnap",
params: {
snapId,
request: {
method: req.method,
params: req.params
}
}
});
} catch (err) {
const error = new SnapError((err == null ? void 0 : err.message) || req.errMsg);
throw error;
}
}
};
// src/connectors/okx.ts
import { AddressType as AddressType3, Network as Network5 } from "bitcoin-address-validation";
// src/assets/okx.ts
var okxLogo = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAJDSURBVHgB7Zq9jtpAEMfHlhEgQLiioXEkoAGECwoKxMcTRHmC5E3IoyRPkPAEkI7unJYmTgEFTYwA8a3NTKScLnCHN6c9r1e3P2llWQy7M/s1Gv1twCP0ej37dDq9x+Zut1t3t9vZjDEHIiSRSPg4ZpDL5fxkMvn1cDh8m0wmfugfO53OoFQq/crn8wxfY9EymQyrVCqMfHvScZx1p9ls3pFxXBy/bKlUipGPrVbLuQqAfsCliq3zl0H84zwtjQrOw4Mt1W63P5LvBm2d+Xz+YzqdgkqUy+WgWCy+Mc/nc282m4FqLBYL+3g8fjDxenq72WxANZbLJeA13zDX67UDioL5ybXwafMYu64Ltn3bdDweQ5R97fd7GyhBQMipx4POeEDHIu2LfDdBIGGz+hJ9CQ1ABjoA2egAZPM6AgiCAEQhsi/C4jHyPA/6/f5NG3Ks2+3CYDC4aTccDrn6ojG54MnEvG00GoVmWLIRNZ7wTCwDHYBsdACy0QHIhiuRETxlICWpMMhGZHmqS8qH6JLyGegAZKMDkI0uKf8X4SWlaZo+Pp1bRrwlJU8ZKLIvUjKh0WiQ3sRUbNVq9c5Ebew7KEo2m/1p4jJ4qAmDaqDQBzj5XyiAT4VCQezJigAU+IDU+z8vJFnGWeC+bKQV/5VZ71FV6L7PA3gg3tXrdQ+DgLhC+75Wq3no69P3MC0NFQpx2lL04Ql9gHK1bRDjsSBIvScBnDTk1WrlGIZBorIDEYJj+rhdgnQ67VmWRe0zlplXl81vcyEt0rSoYDUAAAAASUVORK5CYII=";
// src/connectors/okx.ts
var getLibNetwork = (network) => {
switch (network) {
case "livenet":
return Network5.mainnet;
case "testnet":
return Network5.testnet;
}
};
var OKXConnector = class extends SatsConnector {
constructor(network) {
super(network, "OKX Wallet", "OKX Wallet", "https://www.okx.com/web3", okxLogo);
}
async connect() {
const network = await window.okxwallet.bitcoin.getNetwork();
const mappedNetwork = getLibNetwork(network);
if (mappedNetwork !== this.network) {
throw new Error(`Invalid Network. Please switch to Bitcoin ${this.network}.`);
}
const [accounts, publickKey] = await Promise.all([
window.okxwallet.bitcoin.requestAccounts(),
window.okxwallet.bitcoin.getPublicKey()
]);
this.paymentAddress = accounts[0];
this.ordinalsAddress = accounts[0];
this.publicKey = publickKey;
window.okxwallet.bitcoin.on("accountChanged", this.changeAccount);
}
signMessage(message) {
return window.okxwallet.bitcoin.signMessage(message);
}
on(callback) {
window.okxwallet.bitcoin.on("accountChanged", ({ address, publicKey, compressedPublicKey }) => {
callback(address);
this.changeAccount({ address, publicKey, compressedPublicKey });
});
}
removeListener() {
}
async changeAccount({ address, publicKey }) {
this.paymentAddress = address;
this.publicKey = publicKey;
}
async isReady() {
var _a;
this.ready = typeof ((_a = window.okxwallet) == null ? void 0 : _a.bitcoin) !== "undefined";
return this.ready;
}
async sendToAddress(toAddress, amount) {
return window.okxwallet.bitcoin.sendBitcoin(toAddress, amount);
}
async signPsbt(psbtHex, psbtInputAccounts) {
const publicKey = this.getPublicKey();
if (!publicKey) {
throw new Error("Something went wrong while connecting");
}
let inputs = [];
for (const input of psbtInputAccounts) {
for (const index of input.signingIndexes) {
inputs.push(index);
}
}
const addressType = this.getAddressType(this.paymentAddress);
const toSignInputs = inputs.map((index) => {
return {
index,
...addressType === AddressType3.p2tr ? { address: this.paymentAddress, disableTweakSigner: false } : { publicKey, disableTweakSigner: true }
};
});
const signedPsbtHex = await window.okxwallet.bitcoin.signPsbt(psbtHex, {
autoFinalized: false,
toSignInputs
});
return signedPsbtHex;
}
};
// src/provider.tsx
import { jsx } from "react/jsx-runtime";
var SatsWagmiContext = createContext({
connector: void 0,
connectors: [],
setConnector: () => {
},
network: BitcoinNetwork.mainnet
});
var useSatsWagmi = () => {
const context = useContext(SatsWagmiContext);
if (context === void 0) {
throw new Error("useSatsWagmi must be used within a SatsWagmiConfig!");
}
return context;
};
var SatsWagmiConfig = ({ children, queryClient, network = BitcoinNetwork.mainnet }) => {
const [connectors, setConnectors] = useState([]);
const [connector, setCurrentConnector] = useState();
const [storedConnector, setStoredConnector] = useLocalStorage(
"satsWagmiConnector" /* CONNECTOR */,
void 0,
{
initializeWithValue: typeof window !== "undefined"
}
);
useEffect(() => {
const init = () => {
const readyConnectors = [];
if (network === "mainnet") {
const okx = new OKXConnector(network);
readyConnectors.push(okx);
}
const xverse = new XverseConnector(network);
readyConnectors.push(xverse);
const unisat = new UnisatConnector(network, "unisat");
readyConnectors.push(unisat);
const bitkeep = new UnisatConnector(network, "bitkeep");
readyConnectors.push(bitkeep);
const binancew3w = new UnisatConnector(network, "binancew3w");
readyConnectors.push(binancew3w);
const mmSnap = new MMSnapConnector(network);
readyConnectors.push(mmSnap);
const leather = new LeatherConnector(network);
readyConnectors.push(leather);
setConnectors(readyConnectors);
};
init();
}, [network]);
const setConnector = useCallback(
(connector2) => {
setCurrentConnector(connector2);
setStoredConnector(connector2 == null ? void 0 : connector2.id);
},
[setStoredConnector]
);
useEffect(() => {
const autoConnect = async () => {
const connector2 = connectors.find((connector3) => connector3.id === storedConnector);
if (!connector2)
return;
try {
await connector2.connect();
setConnector(connector2);
} catch (e) {
setStoredConnector(void 0);
}
};
if (!connector && storedConnector && connectors.length) {
autoConnect();
}
}, [connectors]);
return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx(SatsWagmiContext.Provider, { value: { connectors, connector, setConnector, network }, children }) });
};
// src/hooks/useAccount.tsx
import { useQuery } from "@tanstack/react-query";
import { getAddressInfo as getAddressInfo2 } from "bitcoin-address-validation";
import { useEffect as useEffect2 } from "react";
var useAccount = ({ onConnect } = {}) => {
const { connector } = useSatsWagmi();
const { data, error, isError, isLoading, isSuccess, refetch } = useQuery({
queryKey: ["sats-account", connector],
queryFn: () => {
if (!connector)
return void 0;
const address = connector.getPaymentAddress();
onConnect == null ? void 0 : onConnect({ address, connector });
const publicKey = connector.getPublicKey();
const addressType = address ? getAddressInfo2(address).type : void 0;
return { address, type: addressType, publicKey };
},
enabled: !!connector
});
useEffect2(() => {
if (!connector)
return;
connector.on(() => refetch());
return () => {
connector.removeListener(() => refetch());
};
}, [connector, refetch]);
return {
connector,
address: data == null ? void 0 : data.address,
addressType: data == null ? void 0 : data.type,
publicKey: data == null ? void 0 : data.publicKey,
error,
isError,
isLoading,
isSuccess,
refetch
};
};
// src/hooks/useBalance.tsx
import { useQuery as useQuery2 } from "@tanstack/react-query";
import { getBalance } from "@gobob/bob-sdk";
var useBalance = (props = {}) => {
const { network } = useSatsWagmi();
const { address } = useAccount();
return useQuery2({
enabled: Boolean(address),
queryKey: ["sats-balance", network, address],
refetchInterval: INTERVAL.SECONDS_30,
queryFn: () => getBalance(address),
...props
});
};
// src/hooks/useConnect.tsx
import { useMutation } from "@tanstack/react-query";
var useConnect = () => {
const { connectors, setConnector } = useSatsWagmi();
const { mutate, mutateAsync, ...query } = useMutation({
mutationKey: ["sats-connect"],
mutationFn: async ({ connector }) => {
if (!connector) {
throw new Error("invalid connector id");
}
if (connector.name !== "MetaMask" && !await connector.isReady()) {
window.open(connector.homepage, "_blank", "noopener");
throw new Error("Wallet is not installed");
}
await connector.connect();
return { address: connector.paymentAddress };
},
onSuccess: (_, { connector }) => {
setConnector(connector);
}
});
return {
...query,
connectors,
connect: mutate,
connectAsync: mutateAsync
};
};
// src/hooks/useDisconnect.tsx
import { useMutation as useMutation2 } from "@tanstack/react-query";
var useDisconnect = () => {
const { setConnector } = useSatsWagmi();
const { mutate, mutateAsync, ...mutation } = useMutation2({
mutationKey: ["sats-disconnect"],
mutationFn: async () => {
setConnector(void 0);
}
});
return {
...mutation,
disconnect: mutate,
disconnectAsync: mutateAsync
};
};
// src/hooks/useFeeEstimate.tsx
import { estimateTxFee } from "@gobob/bob-sdk";
import { useQuery as useQuery4 } from "@tanstack/react-query";
// src/hooks/useFeeRate.tsx
import { EsploraClient as EsploraClient2, MempoolClient } from "@gobob/bob-sdk";
import { useQuery as useQuery3 } from "@tanstack/react-query";
function useFeeRate({ query } = {}) {
const { network } = useSatsWagmi();
return useQuery3({
queryKey: ["sats-fee-rate", network],
queryFn: async () => {
const memPoolClient = new MempoolClient(network);
const esploraClient = new EsploraClient2(network);
const [memPoolFeeRate, esploraFeeRate] = await Promise.all([
memPoolClient.getRecommendedFees(),
esploraClient.getFeeEstimates()
]);
return {
memPool: memPoolFeeRate,
esplora: esploraFeeRate
};
},
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchInterval: INTERVAL.MINUTE,
...query
});
}
// src/hooks/useFeeEstimate.tsx
function useFeeEstimate({
amount,
opReturnData,
feeRate: feeRateProp,
query
} = {}) {
const { address, publicKey } = useAccount();
const { data: feeRateData } = useFeeRate();
const { network } = useSatsWagmi();
const enabled = Boolean(feeRateData && address && ((query == null ? void 0 : query.enabled) !== void 0 ? query.enabled : true));
const feeRate = feeRateProp || (feeRateData == null ? void 0 : feeRateData.esplora[6]);
return useQuery4({
queryKey: ["sats-fee-estimate", amount, address, opReturnData, network, feeRate],
queryFn: async () => {
if (!address || !feeRate) {
throw new Error("Failed to estimate fee");
}
return { feeRate, amount: await estimateTxFee(address, amount, publicKey, opReturnData, feeRate) };
},
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchInterval: INTERVAL.MINUTE,
...query,
enabled
});
}
// src/hooks/useSendGatewayTransaction.tsx
import { useMutation as useMutation3 } from "@tanstack/react-query";
import { GatewaySDK } from "@gobob/bob-sdk";
var useSendGatewayTransaction = ({ gatewaySDK, toChain = "bob", ...props }) => {
const { address: btcAddress, publicKey: btcPublicKey, connector } = useAccount();
const { mutate, mutateAsync, ...result } = useMutation3({
mutationKey: ["sats-send-gateway-transaction", btcAddress],
mutationFn: async ({ toToken, evmAddress, value }) => {
if (!connector)
return void 0;
if (!btcAddress)
return void 0;
const gatewayClient = gatewaySDK || new GatewaySDK(toChain);
const params = {
...props,
fromChain: props.fromChain || "bitcoin",
fromToken: props.fromToken || "BTC",
toChain,
toToken,
gasRefill: props.gasRefill || 2e3,
fromUserAddress: btcAddress,
fromUserPublicKey: btcPublicKey,
toUserAddress: evmAddress,
amount: Number(value)
};
const quote = await gatewayClient.getQuote(params);
const { uuid, psbtBase64 } = await gatewayClient.startOrder(quote, params);
if (!psbtBase64)
throw new Error("No psbt");
const bitcoinTxHex = await connector.signAllInputs(psbtBase64);
return await gatewayClient.finalizeOrder(uuid, bitcoinTxHex);
},
...props
});
return {
...result,
sendGatewayTransaction: mutate,
sendGatewayTransactionAsync: mutateAsync
};
};
// src/hooks/useSendTransaction.tsx
import { useMutation as useMutation4 } from "@tanstack/react-query";
var useSendTransaction = (props = {}) => {
const { connector } = useSatsWagmi();
const { address } =