Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

!WIP feat: add bitcoin connect and signer to phantom wallet #654

7 changes: 6 additions & 1 deletion wallets/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import type {
WalletInfo,
WalletType,
} from '@rango-dev/wallets-shared';
import type { BlockchainMeta, SignerFactory } from 'rango-types';
import type {
BlockchainMeta,
SignerFactory,
TransactionType,
} from 'rango-types';

export type State = {
[key: string]: WalletState | undefined;
Expand Down Expand Up @@ -59,6 +63,7 @@ export type Connect = (options: {
instance: any;
network?: Network;
meta: BlockchainMeta[];
transactionTypes?: TransactionType[];
}) => Promise<ProviderConnectResult | ProviderConnectResult[]>;

export type Disconnect = (options: {
Expand Down
5 changes: 3 additions & 2 deletions wallets/core/src/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { GetInstanceOptions, WalletActions, WalletConfig } from './types';
import type { Network, WalletType } from '@rango-dev/wallets-shared';
import type { BlockchainMeta } from 'rango-types';
import type { BlockchainMeta, TransactionType } from 'rango-types';

import { getBlockChainNameFromId, Networks } from '@rango-dev/wallets-shared';

Expand Down Expand Up @@ -76,7 +76,7 @@ class Wallet<InstanceType = any> {
return await this.connect(network);
}

async connect(network?: Network) {
async connect(network?: Network, transactionTypes?: TransactionType[]) {
// If it's connecting, nothing do.
if (this.state.connecting) {
throw new Error('Connecting...');
Expand Down Expand Up @@ -165,6 +165,7 @@ class Wallet<InstanceType = any> {
instance,
network: requestedNetwork || undefined,
meta: this.info.supportedBlockchains || [],
transactionTypes: transactionTypes,
});
} catch (e) {
this.resetState();
Expand Down
4 changes: 3 additions & 1 deletion wallets/provider-phantom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
"dependencies": {
"@rango-dev/signer-solana": "^0.26.0",
"@rango-dev/wallets-shared": "^0.30.0",
"bitcoinjs-lib": "6.1.5",
"coinselect": "^3.1.12",
"rango-types": "^0.1.59"
},
"publishConfig": {
"access": "public"
}
}
}
31 changes: 28 additions & 3 deletions wallets/provider-phantom/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
import type { Connect } from '@rango-dev/wallets-shared';

import { Networks } from '@rango-dev/wallets-shared';

export function phantom() {
if ('phantom' in window) {
const instance = window.phantom?.solana;
const instances = new Map();

if (window.phantom?.solana?.isPhantom) {
instances.set(Networks.SOLANA, window.phantom.solana);
}

if (instance?.isPhantom) {
return instance;
if (window.phantom?.bitcoin?.isPhantom) {
instances.set(Networks.BTC, window.phantom.bitcoin);
}

return instances;
}

return null;
}

export const getBitcoinAccounts: Connect = async ({ instance }) => {
const accounts = await instance.requestAccounts();
return {
accounts: accounts.map(
(account: {
address: string;
publicKey: string;
addressType: 'p2tr' | 'p2wpkh' | 'p2sh' | 'p2pkh';
purpose: 'payment' | 'ordinals';
}) => account.address
),
chainId: Networks.BTC,
};
};
102 changes: 92 additions & 10 deletions wallets/provider-phantom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,103 @@ import type {
CanEagerConnect,
CanSwitchNetwork,
Connect,
ProviderConnectResult,
Subscribe,
WalletInfo,
} from '@rango-dev/wallets-shared';
import type { BlockchainMeta, SignerFactory } from 'rango-types';

import {
chooseInstance,
getSolanaAccounts,
Networks,
WalletTypes,
} from '@rango-dev/wallets-shared';
import { solanaBlockchain } from 'rango-types';
import { solanaBlockchain, TransactionType } from 'rango-types';

import { phantom as phantom_instance } from './helpers';
import { getBitcoinAccounts, phantom as phantom_instance } from './helpers';
import signer from './signer';

const SOLANA_CONNECT_ERROR_MSG =
'Could not connect Solana account. Consider adding Solana to your wallet.';
const BTC_CONNECT_ERROR_MSG =
'Could not connect Bitcoin account. Consider adding Bitcoin to your wallet.';

const WALLET = WalletTypes.PHANTOM;

export const config = {
type: WALLET,
};

export const getInstance = phantom_instance;
export const connect: Connect = getSolanaAccounts;
export const connect: Connect = async ({
instance,
meta,
transactionTypes,
}) => {
const solanaInstance = chooseInstance(instance, meta, Networks.SOLANA);
const bitcoinInstance = chooseInstance(instance, meta, Networks.BTC);

const result = [];

if (!solanaInstance && transactionTypes?.includes(TransactionType.SOLANA)) {
// If user opted to connect Solana and Solana instance is not available
throw new Error(SOLANA_CONNECT_ERROR_MSG);
}

if (
solanaInstance &&
(!transactionTypes || transactionTypes.includes(TransactionType.SOLANA))
) {
try {
const solanaAccounts = (await getSolanaAccounts({
instance: solanaInstance,
meta,
})) as ProviderConnectResult;
result.push(solanaAccounts);
} catch (error) {
// If transactionTypes is not available it means that autoConnect is happening so we ignore the error
if (transactionTypes) {
throw new Error(SOLANA_CONNECT_ERROR_MSG);
}
}
}

if (
!bitcoinInstance &&
transactionTypes?.includes(TransactionType.TRANSFER)
) {
throw new Error(BTC_CONNECT_ERROR_MSG);
}

if (
bitcoinInstance &&
(!transactionTypes || transactionTypes?.includes(TransactionType.TRANSFER))
) {
try {
const bitcoinAccounts = (await getBitcoinAccounts({
instance: bitcoinInstance,
meta,
})) as ProviderConnectResult;
result.push(bitcoinAccounts);
} catch (error) {
if (transactionTypes) {
throw new Error(BTC_CONNECT_ERROR_MSG);
}
}
}

return result;
};

export const subscribe: Subscribe = ({
instance,
updateAccounts,
connect,
meta,
}) => {
const solanaInstance = chooseInstance(instance, meta, Networks.SOLANA);

export const subscribe: Subscribe = ({ instance, updateAccounts, connect }) => {
const handleAccountsChanged = async (publicKey: string) => {
const network = Networks.SOLANA;
if (publicKey) {
Expand All @@ -36,30 +108,40 @@ export const subscribe: Subscribe = ({ instance, updateAccounts, connect }) => {
connect(network);
}
};
instance?.on?.('accountChanged', handleAccountsChanged);
solanaInstance?.on('accountChanged', handleAccountsChanged);

return () => {
instance?.off?.('accountChanged', handleAccountsChanged);
solanaInstance?.off('accountChanged', handleAccountsChanged);
};
};

export const canSwitchNetworkTo: CanSwitchNetwork = () => false;

export const getSigners: (provider: any) => SignerFactory = signer;

export const canEagerConnect: CanEagerConnect = async ({ instance }) => {
export const canEagerConnect: CanEagerConnect = async ({ instance, meta }) => {
const solanaInstance = chooseInstance(instance, meta, Networks.SOLANA);

try {
const result = await instance.connect({ onlyIfTrusted: true });
return !!result;
const result = await solanaInstance.connect({ onlyIfTrusted: true });
if (result) {
return true;
}
} catch (error) {
return false;
}

return false;
};

export const getWalletInfo: (allBlockChains: BlockchainMeta[]) => WalletInfo = (
allBlockChains
) => {
const solana = solanaBlockchain(allBlockChains);
const bitcoin = allBlockChains.filter(
(blockchain) => blockchain.name === Networks.BTC
);

return {
name: 'Phantom',
img: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/phantom/icon.svg',
Expand All @@ -70,6 +152,6 @@ export const getWalletInfo: (allBlockChains: BlockchainMeta[]) => WalletInfo = (
DEFAULT: 'https://phantom.app/',
},
color: '#4d40c6',
supportedChains: solana,
supportedChains: [...solana, ...bitcoin],
};
};
2 changes: 2 additions & 0 deletions wallets/provider-phantom/src/modules.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This statement declares a module augmentation for the external module 'coinselect/accumulative'.
declare module 'coinselect/accumulative';
17 changes: 11 additions & 6 deletions wallets/provider-phantom/src/signer.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import type { SignerFactory } from 'rango-types';

import { DefaultSolanaSigner } from '@rango-dev/signer-solana';
import { Networks, getNetworkInstance } from '@rango-dev/wallets-shared';
import {
DefaultSignerFactory,
SignerFactory,
TransactionType as TxType,
} from 'rango-types';
import { getNetworkInstance, Networks } from '@rango-dev/wallets-shared';
import { DefaultSignerFactory, TransactionType as TxType } from 'rango-types';

import { PhantomTransferSigner } from './signers/bitcoinSigner';

export default function getSigners(provider: any): SignerFactory {
const solProvider = getNetworkInstance(provider, Networks.SOLANA);
const bitcoinProvider = getNetworkInstance(provider, Networks.BTC);
const signers = new DefaultSignerFactory();
signers.registerSigner(TxType.SOLANA, new DefaultSolanaSigner(solProvider));
signers.registerSigner(
TxType.TRANSFER,
new PhantomTransferSigner(bitcoinProvider)
);
return signers;
}
Loading