@bigmi/client
Version:
Reactive primitives for Bitcoin apps.
133 lines (132 loc) • 7.18 kB
JavaScript
import { ConnectorChainIdDetectionError } from "../errors/connectors.js";
import { createConnector } from "../factories/createConnector.js";
import { MethodNotSupportedRpcError, ProviderNotFoundError, UserRejectedRequestError, getAddressChainId, getAddressInfo, hexToUnit8Array } from "@bigmi/core";
import { getWallets } from "@wallet-standard/app";
//#region src/connectors/metamask.ts
const METAMASK_WALLET_NAME = "MetaMask";
/** Bigmi numeric SIGHASH → MetaMask string flag (undefined = wallet default). */
function toSigHashFlag(sigHash) {
switch (sigHash) {
case 1: return "ALL";
case 2: return "NONE";
case 3: return "SINGLE";
case 129: return "ALL|ANYONECANPAY";
case 130: return "NONE|ANYONECANPAY";
case 131: return "SINGLE|ANYONECANPAY";
default: return;
}
}
function toAccount(account) {
const { type, purpose } = getAddressInfo(account.address);
const publicKey = Array.from(account.publicKey, (byte) => byte.toString(16).padStart(2, "0")).join("");
return {
address: account.address,
addressType: type,
publicKey,
purpose
};
}
function metamask(parameters = {}) {
const { chainId, shimDisconnect = true } = parameters;
let unsubscribe;
return createConnector((config) => ({
id: "io.metamask.bitcoin",
name: "MetaMask",
type: metamask.type,
icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc1MDAnIGhlaWdodD0nNTAwJyBmaWxsPSdub25lJz4gPGcgY2xpcC1wYXRoPSd1cmwoI2EpJz4gPHBhdGggZmlsbD0nI0ZGNUMxNicgZD0nTTQyMC40MiA0MjUuMzIgMzM2IDQwMC4xOGwtNjMuNjYgMzguMDYtNDQuNDItLjAyLTYzLjctMzguMDQtODQuMzkgMjUuMTQtMjUuNjYtODYuNjUgMjUuNjYtOTYuMTctMjUuNjYtODEuMzFMNzkuODMgNjAuNDJsMTMxLjg2IDc4Ljc3aDc2Ljg4bDEzMS44NS03OC43NyAyNS42NyAxMDAuNzctMjUuNjcgODEuMyAyNS42NyA5Ni4xOHonLz4gPHBhdGggZmlsbD0nI0ZGNUMxNicgZD0nbTc5Ljg1IDYwLjQyIDEzMS44NiA3OC44My01LjI1IDU0LjFMNzkuODcgNjAuNDJabTg0LjM5IDI3OC4yOCA1OC4wMSA0NC4yLTU4LjAxIDE3LjI4em01My4zOC03My4wNi0xMS4xNi03Mi4yNS03MS4zNyA0OS4xMy0uMDMtLjAydi4wM2wuMjIgNTAuNTcgMjguOTQtMjcuNDZ6bTIwMi44LTIwNS4yMi0xMzEuODYgNzguODMgNS4yMyA1NC4xek0zMzYuMDQgMzM4LjdsLTU4LjAyIDQ0LjIgNTguMDIgMTcuMjh6bTI5LjE2LTk2LjE3aC4wMnp2LS4wM2wtLjAyLjAxLTcxLjM3LTQ5LjEyLTExLjE1IDcyLjI1aDUzLjM4TDM2NSAyOTMuMXonLz4gPHBhdGggZmlsbD0nI0UzNDgwNycgZD0nTTE2NC4yMiA0MDAuMTggNzkuODMgNDI1LjNsLTI1LjY2LTg2LjZoMTEwLjA1djYxLjQ3Wm01My4zOC0xMzQuNTYgMTYuMTIgMTA0LjQ1LTIyLjM0LTU4LjA4LTc2LjE0LTE4Ljg5IDI4Ljk2LTI3LjQ4em0xMTguNDQgMTM0LjU2IDg0LjM4IDI1LjEzIDI1LjY3LTg2LjZIMzM2LjA0em0tNTMuMzgtMTM0LjU2LTE2LjEyIDEwNC40NSAyMi4zNC01OC4wOCA3Ni4xNC0xOC44OS0yOC45OC0yNy40OHonLz4gPHBhdGggZmlsbD0nI0ZGOEQ1RCcgZD0nbTU0LjE3IDMzOC42NiAyNS42Ni05Ni4xN2g1NS4ybC4yIDUwLjYgNzYuMTUgMTguODggMjIuMzMgNTguMDgtMTEuNDggMTIuNzktNTguMDEtNDQuMkg1NC4xN3ptMzkxLjkyIDAtMjUuNjctOTYuMTdoLTU1LjJsLS4yIDUwLjYtNzYuMTQgMTguODgtMjIuMzQgNTguMDggMTEuNDggMTIuNzkgNTguMDItNDQuMmgxMTAuMDV6TTI4OC41NiAxMzkuMkgyMTEuN2wtNS4yMyA1NC4xTDIzMy43MSAzNzBoMzIuODNsMjcuMjctMTc2Ljd6Jy8+IDxwYXRoIGZpbGw9JyM2NjE4MDAnIGQ9J003OS44MyA2MC40MiA1NC4xNyAxNjEuMTlsMjUuNjYgODEuM2g1NS4ybDcxLjQyLTQ5LjE0TDc5Ljg0IDYwLjQyWk0yMDEuNjQgMjg2LjZoLTI1bC0xMy42MiAxMy4zNCA0OC4zOCAxMi05Ljc2LTI1LjM2ek00MjAuNDIgNjAuNDJsMjUuNjcgMTAwLjc3LTI1LjY3IDgxLjNoLTU1LjJsLTcxLjQxLTQ5LjE0TDQyMC40IDYwLjQyWk0yOTguNjUgMjg2LjZoMjUuMDRsMTMuNjIgMTMuMzYtNDguNDMgMTIuMDIgOS43Ny0yNS40em0tMjYuMzMgMTE3LjE2IDUuNy0yMC44OC0xMS40OC0xMi44aC0zMi44NWwtMTEuNDggMTIuOCA1LjcgMjAuODgnLz4gPHBhdGggZmlsbD0nI0MwQzRDRCcgZD0nTTI3Mi4zMiA0MDMuNzZ2MzQuNWgtNDQuNHYtMzQuNXonLz4gPHBhdGggZmlsbD0nI0U3RUJGNicgZD0nbTE2NC4yNCA0MDAuMTQgNjMuNzIgMzguMXYtMzQuNWwtNS43LTIwLjg4em0xNzEuOCAwLTYzLjcyIDM4LjF2LTM0LjVsNS43LTIwLjg4eicvPiA8L2c+IDxkZWZzPiA8Y2xpcFBhdGggaWQ9J2EnPiA8cGF0aCBmaWxsPSd3aGl0ZScgZD0nTTAgMGg1MDB2NTAwSDB6Jy8+IDwvY2xpcFBhdGg+IDwvZGVmcz4gPC9zdmc+",
async setup() {},
async getInternalProvider() {
if (typeof window === "undefined") return;
const { get } = getWallets();
return get().find((wallet) => wallet.name === METAMASK_WALLET_NAME && "bitcoin:connect" in wallet.features);
},
async getProvider() {
const wallet = await this.getInternalProvider();
if (!wallet) return;
return { request: this.request.bind(wallet) };
},
async request({ method, params }) {
switch (method) {
case "signPsbt": {
const { psbt, inputsToSign } = params;
const signInputs = inputsToSign.map((input) => {
const account = this.accounts.find((account) => account.address === input.address);
if (!account) throw new Error(`Account with address ${input.address} not found`);
return {
account,
signingIndexes: input.signingIndexes,
sigHash: toSigHashFlag(input.sigHash)
};
});
const [result] = await this.features["bitcoin:signTransaction"].signTransaction({
psbt: hexToUnit8Array(psbt),
inputsToSign: signInputs
});
return Array.from(result.signedPsbt, (byte) => byte.toString(16).padStart(2, "0")).join("");
}
default: throw new MethodNotSupportedRpcError(method);
}
},
async connect() {
const wallet = await this.getInternalProvider();
if (!wallet) throw new ProviderNotFoundError();
try {
const accounts = await this.getAccounts();
const chainId = getAddressChainId(accounts[0].address);
if (!unsubscribe) {
const onAccountsChanged = this.onAccountsChanged.bind(this);
unsubscribe = wallet.features["bitcoin:events"].on("change", ({ accounts }) => {
onAccountsChanged((accounts ?? []).map((account) => toAccount(account)));
});
}
if (shimDisconnect) await Promise.all([config.storage?.setItem(`${this.id}.connected`, true), config.storage?.removeItem(`${this.id}.disconnected`)]);
return {
accounts,
chainId
};
} catch (error) {
throw new UserRejectedRequestError(error.message);
}
},
async disconnect() {
if (unsubscribe) {
unsubscribe();
unsubscribe = void 0;
}
if (shimDisconnect) await Promise.all([config.storage?.setItem(`${this.id}.disconnected`, true), config.storage?.removeItem(`${this.id}.connected`)]);
},
async getAccounts() {
const wallet = await this.getInternalProvider();
if (!wallet) throw new ProviderNotFoundError();
const { accounts } = await wallet.features["bitcoin:connect"].connect({ purposes: ["payment"] });
return accounts.map(toAccount).filter((account) => account.purpose === "payment");
},
async getChainId() {
if (chainId) return chainId;
const accounts = await this.getAccounts();
if (accounts.length === 0) throw new ConnectorChainIdDetectionError({ connector: this.name });
return getAddressChainId(accounts[0].address);
},
async isAuthorized() {
try {
return shimDisconnect && Boolean(await config.storage?.getItem(`${this.id}.connected`));
} catch {
return false;
}
},
async onAccountsChanged(accounts) {
if (accounts.length === 0) this.onDisconnect();
else config.emitter.emit("change", { accounts: accounts.filter((account) => account.purpose === "payment") });
},
onChainChanged(chainId) {
config.emitter.emit("change", { chainId });
},
async onDisconnect(_error) {
config.emitter.emit("disconnect");
}
}));
}
metamask.type = "UTXO";
//#endregion
export { metamask };
//# sourceMappingURL=metamask.js.map