@bigmi/client
Version:
Reactive primitives for Bitcoin apps.
153 lines • 7.45 kB
JavaScript
import { BaseError, base64ToHex, hexToBase64, MethodNotSupportedRpcError, ProviderNotFoundError, UserRejectedRequestError, } from '@bigmi/core';
import { createConnector } from '../factories/createConnector.js';
ctrl.type = 'UTXO';
export function ctrl(parameters = {}) {
const { chainId, shimDisconnect = true } = parameters;
let accountsChanged;
return createConnector((config) => ({
id: 'io.xdefi',
name: 'Ctrl Wallet',
type: ctrl.type,
icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTc1IiBoZWlnaHQ9IjE3NSIgdmlld0JveD0iMCAwIDE3NSAxNzUiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNzUiIGhlaWdodD0iMTc1IiByeD0iODcuNSIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTY3LjMzMyAxMTYuMzI1VjE0Mi4zMzdDNjcuMzMzIDE0Ni41NzYgNzAuNzY3MSAxNTAuMDAxIDc0Ljk5NTMgMTUwLjAwMUgxMDEuMDA0QzEwNS4yNDIgMTUwLjAwMSAxMDguNjY2IDE0Ni41NjYgMTA4LjY2NiAxNDIuMzM3VjEyOS4zMzFDMTA4LjY2NiAxMjMuNjE3IDExMC45NzYgMTE4LjQ1IDExNC43MiAxMTQuNzE2QzExOC40NjMgMTEwLjk3MiAxMjMuNjMgMTA4LjY2MiAxMjkuMzMzIDEwOC42NjJIMTQyLjMzN0MxNDYuNTc2IDEwOC42NjIgMTUwIDEwNS4yMjggMTUwIDEwMC45OTlWNzQuOTk3M0MxNTAgNzAuNzU4MiAxNDYuNTY2IDY3LjMzNCAxNDIuMzM3IDY3LjMzNEgxMTYuMzM5QzExMi4xIDY3LjMzNCAxMDguNjc3IDcwLjc2ODUgMTA4LjY3NyA3NC45OTczVjg4LjYzMjRDMTA4LjY3NyA5OS42OTkzIDk5LjcwNDYgMTA4LjY2MiA4OC42NDk0IDEwOC42NjJINzUuMDE2QzcwLjc3NzQgMTA4LjY2MiA2Ny4zNTM2IDExMi4wOTcgNjcuMzUzNiAxMTYuMzI1SDY3LjMzM1oiIGZpbGw9IiMwMDE0MDUiLz4KPHBhdGggZD0iTTI2IDc0Ljk5NTdWMTAxLjAwNEMyNiAxMDUuMjQzIDI5LjQzNDEgMTA4LjY2NyAzMy42NjIzIDEwOC42NjdINTkuNjcxQzYzLjkwOTUgMTA4LjY2NyA2Ny4zMzMzIDEwNS4yMzMgNjcuMzMzMyAxMDEuMDA0Vjg4QzY3LjMzMzMgODIuMjg2OCA2OS42NDM0IDc3LjEyMDEgNzMuMzg2OSA3My4zODY5Qzc3LjEzMDQgNjkuNjQzNCA4Mi4yOTcxIDY3LjMzMzMgODggNjcuMzMzM0gxMDEuMDA0QzEwNS4yNDMgNjcuMzMzMyAxMDguNjY3IDYzLjg5OTIgMTA4LjY2NyA1OS42NzFWMzMuNjYyM0MxMDguNjY3IDI5LjQyMzggMTA1LjIzMyAyNiAxMDEuMDA0IDI2SDc1LjAwNkM3MC43Njc1IDI2IDY3LjM0MzYgMjkuNDM0MSA2Ny4zNDM2IDMzLjY2MjNWNDcuMjk1N0M2Ny4zNDM2IDU4LjM2MTMgNTguMzcxNiA2Ny4zMjMgNDcuMzE2NCA2Ny4zMjNIMzMuNjYyM0MyOS40MjM4IDY3LjMyMyAyNiA3MC43NTcyIDI2IDc0Ljk4NTRWNzQuOTk1N1oiIGZpbGw9IiMwMDE0MDUiLz4KPC9zdmc+Cg==',
async setup() {
//
},
async getInternalProvider() {
if (typeof window === 'undefined') {
return;
}
if ('xfi' in window) {
const anyWindow = window;
return anyWindow.xfi?.bitcoin;
}
},
async getProvider() {
const internalProvider = await this.getInternalProvider();
if (!internalProvider) {
return;
}
const provider = {
request: this.request.bind(internalProvider),
};
return provider;
},
async request({ method, params }) {
switch (method) {
case 'signPsbt': {
const { psbt, ...options } = params;
const psbt64 = hexToBase64(psbt);
const response = await this.signPsbt({
psbt: psbt64,
broadcast: Boolean(options.finalize),
});
if (response.status === 'success') {
const signedHex = base64ToHex(response.result.psbt);
return signedHex;
}
throw new BaseError(response.error);
}
default:
throw new MethodNotSupportedRpcError(method);
}
},
async connect() {
const provider = await this.getInternalProvider();
if (!provider) {
throw new ProviderNotFoundError();
}
try {
const accounts = await this.getAccounts();
const chainId = await this.getChainId();
if (!accountsChanged) {
accountsChanged = this.onAccountsChanged.bind(this);
provider.addListener('accountsChanged', accountsChanged);
}
// Remove disconnected shim if it exists
if (shimDisconnect) {
await Promise.all([
config.storage?.setItem(`${this.id}.connected`, true),
config.storage?.removeItem(`${this.id}.disconnected`),
]);
}
return { accounts, chainId };
}
catch (error) {
console.error({
error,
});
throw new UserRejectedRequestError(error.message);
}
},
async disconnect() {
const provider = await this.getInternalProvider();
if (accountsChanged) {
provider?.removeListener('accountsChanged', accountsChanged);
accountsChanged = undefined;
}
// Add shim signalling connector is disconnected
if (shimDisconnect) {
await Promise.all([
config.storage?.setItem(`${this.id}.disconnected`, true),
config.storage?.removeItem(`${this.id}.connected`),
]);
}
},
async getAccounts() {
const provider = await this.getInternalProvider();
if (!provider) {
throw new ProviderNotFoundError();
}
const { status, result } = await provider.request({
method: 'request_accounts_and_keys',
params: {
purposes: ['payment'],
},
});
if (status === 'success') {
return result.map((account) => ({
address: account.address,
addressType: account.addressType.toLowerCase(),
publicKey: account.publicKey,
purpose: account.purpose,
}));
}
throw new BaseError('Error getting accounts');
},
async getChainId() {
return chainId;
},
async isAuthorized() {
try {
const isConnected = shimDisconnect &&
// If shim exists in storage, connector is disconnected
Boolean(await config.storage?.getItem(`${this.id}.connected`));
return isConnected;
}
catch {
return false;
}
},
async onAccountsChanged(accounts) {
if (accounts.length === 0) {
this.onDisconnect();
}
else {
const newAccounts = await this.getAccounts();
config.emitter.emit('change', {
accounts: newAccounts,
});
}
},
onChainChanged(chain) {
const chainId = Number(chain);
config.emitter.emit('change', { chainId });
},
async onDisconnect(_error) {
// No need to remove `${this.id}.disconnected` from storage because `onDisconnect` is typically
// only called when the wallet is disconnected through the wallet's interface, meaning the wallet
// actually disconnected and we don't need to simulate it.
config.emitter.emit('disconnect');
},
}));
}
//# sourceMappingURL=ctrl.js.map