@rango-dev/provider-phantom 0.0.0-experimental-936229e8-20251208

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/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@rango-dev/provider-phantom",
3
+ "version": "0.0.0-experimental-936229e8-20251208",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "source": "./src/mod.ts",
7
+ "main": "./dist/mod.js",
8
+ "exports": {
9
+ ".": "./dist/mod.js"
10
+ },
11
+ "typings": "dist/mod.d.ts",
12
+ "files": [
13
+ "dist",
14
+ "src"
15
+ ],
16
+ "scripts": {
17
+ "build": "node ../../scripts/build/command.mjs --path wallets/provider-phantom --inputs src/mod.ts",
18
+ "ts-check": "tsc --declaration --emitDeclarationOnly -p ./tsconfig.json",
19
+ "clean": "rimraf dist",
20
+ "format": "prettier --write '{.,src}/**/*.{ts,tsx}'",
21
+ "lint": "eslint \"**/*.{ts,tsx}\""
22
+ },
23
+ "dependencies": {
24
+ "@bitcoinerlab/secp256k1": "^1.2.0",
25
+ "@mysten/sui": "^1.21.2",
26
+ "@mysten/wallet-standard": "^0.13.26",
27
+ "@rango-dev/signer-solana": "^0.46.1",
28
+ "@rango-dev/signer-sui": "^0.8.0",
29
+ "@rango-dev/wallets-core": "^0.0.0-experimental-936229e8-20251208",
30
+ "@rango-dev/wallets-shared": "^0.0.0-experimental-936229e8-20251208",
31
+ "bitcoinjs-lib": "^6.1.7",
32
+ "rango-types": "^0.1.89"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ }
37
+ }
package/readme.md ADDED
@@ -0,0 +1,25 @@
1
+ # Phantom Provider
2
+ Phantom integration for hub.
3
+ [Homepage](https://phantom.com/) | [Docs](https://docs.phantom.com/)
4
+
5
+ More about implementation status can be found [here](../readme.md).
6
+
7
+ ## Implementation notes/limitation
8
+
9
+ ### Group
10
+
11
+ #### ⚠️ UTXO
12
+ Only supports Bitcoin.
13
+
14
+ #### ⚠️ EVM
15
+ Only supports Ethereum, Base, and Polygon.
16
+
17
+ ### Feature
18
+
19
+ #### ⚠️ Auto Connect
20
+ On Sui, Phantom uses Solana's auto-connect mechanism.
21
+ This means if Solana is not connected simultaneously, the auto-connect feature on Sui will not work properly.
22
+
23
+ ---
24
+
25
+ More wallet information can be found in [readme.md](../readme.md).
@@ -0,0 +1,86 @@
1
+ import { type ProviderMetadata } from '@rango-dev/wallets-core';
2
+ import { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
3
+ import { Networks } from '@rango-dev/wallets-shared';
4
+ import {
5
+ type BlockchainMeta,
6
+ type EvmBlockchainMeta,
7
+ isEvmBlockchain,
8
+ solanaBlockchain,
9
+ type SuiBlockchainMeta,
10
+ TransactionType,
11
+ type TransferBlockchainMeta,
12
+ } from 'rango-types';
13
+
14
+ import getSigners from './signer.js';
15
+ import { getInstanceOrThrow } from './utils.js';
16
+
17
+ export const EVM_SUPPORTED_CHAINS = [
18
+ LegacyNetworks.ETHEREUM,
19
+ LegacyNetworks.POLYGON,
20
+ LegacyNetworks.BASE,
21
+ ];
22
+
23
+ export const WALLET_ID = 'phantom';
24
+ export const WALLET_NAME_IN_WALLET_STANDARD = 'Phantom';
25
+
26
+ export const metadata: ProviderMetadata = {
27
+ name: 'Phantom',
28
+ icon: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/phantom/icon.svg',
29
+ extensions: {
30
+ chrome:
31
+ 'https://chrome.google.com/webstore/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa',
32
+ homepage: 'https://phantom.app/',
33
+ },
34
+ properties: [
35
+ {
36
+ name: 'namespaces',
37
+ value: {
38
+ selection: 'multiple',
39
+ data: [
40
+ {
41
+ label: 'EVM',
42
+ value: 'EVM',
43
+ id: 'ETH',
44
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
45
+ allBlockchains.filter(
46
+ (chain): chain is EvmBlockchainMeta =>
47
+ isEvmBlockchain(chain) &&
48
+ EVM_SUPPORTED_CHAINS.includes(chain.name as Networks)
49
+ ),
50
+ },
51
+ {
52
+ label: 'Solana',
53
+ value: 'Solana',
54
+ id: 'SOLANA',
55
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
56
+ solanaBlockchain(allBlockchains),
57
+ },
58
+ {
59
+ label: 'BTC',
60
+ value: 'UTXO',
61
+ id: 'BTC',
62
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
63
+ allBlockchains.filter(
64
+ (chain): chain is TransferBlockchainMeta =>
65
+ chain.name === Networks.BTC
66
+ ),
67
+ },
68
+ {
69
+ label: 'Sui',
70
+ value: 'Sui',
71
+ id: 'SUI',
72
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
73
+ allBlockchains.filter(
74
+ (chain): chain is SuiBlockchainMeta =>
75
+ chain.type === TransactionType.SUI
76
+ ),
77
+ },
78
+ ],
79
+ },
80
+ },
81
+ {
82
+ name: 'signers',
83
+ value: { getSigners: async () => getSigners(getInstanceOrThrow()) },
84
+ },
85
+ ],
86
+ };
@@ -0,0 +1,175 @@
1
+ import type { LegacyProviderInterface } from '@rango-dev/wallets-core/legacy';
2
+ import type {
3
+ CanEagerConnect,
4
+ CanSwitchNetwork,
5
+ Connect,
6
+ Subscribe,
7
+ WalletInfo,
8
+ } from '@rango-dev/wallets-shared';
9
+ import type {
10
+ BlockchainMeta,
11
+ EvmBlockchainMeta,
12
+ SignerFactory,
13
+ SuiBlockchainMeta,
14
+ TransferBlockchainMeta,
15
+ } from 'rango-types';
16
+
17
+ import { LegacyNetworks as Networks } from '@rango-dev/wallets-core/legacy';
18
+ import {
19
+ chooseInstance,
20
+ getSolanaAccounts,
21
+ WalletTypes,
22
+ } from '@rango-dev/wallets-shared';
23
+ import {
24
+ isEvmBlockchain,
25
+ solanaBlockchain,
26
+ TransactionType,
27
+ } from 'rango-types';
28
+
29
+ import { EVM_SUPPORTED_CHAINS } from '../constants.js';
30
+ import signer from '../signer.js';
31
+ import { phantom as phantom_instance, type Provider } from '../utils.js';
32
+
33
+ const WALLET = WalletTypes.PHANTOM;
34
+
35
+ export const config = {
36
+ type: WALLET,
37
+ };
38
+
39
+ export const getInstance = phantom_instance;
40
+
41
+ /*
42
+ * NOTE: Phantom's Hub version has support for EVM as well since we are deprecating the legacy,
43
+ * we just want to keep the implementation for some time and then legacy provider will be removed soon.
44
+ * So we don't add new namespaces (like EVM) to legacy.
45
+ */
46
+ const connect: Connect = async ({ instance, meta }) => {
47
+ const solanaInstance = instance.get(Networks.SOLANA);
48
+ const result = await getSolanaAccounts({
49
+ instance: solanaInstance,
50
+ meta,
51
+ });
52
+
53
+ return result;
54
+ };
55
+
56
+ export const subscribe: Subscribe = ({ instance, updateAccounts, connect }) => {
57
+ const handleAccountsChanged = async (publicKey: string) => {
58
+ const network = Networks.SOLANA;
59
+ if (publicKey) {
60
+ const account = publicKey.toString();
61
+ updateAccounts([account]);
62
+ } else {
63
+ connect(network);
64
+ }
65
+ };
66
+ instance?.on?.('accountChanged', handleAccountsChanged);
67
+
68
+ return () => {
69
+ instance?.off?.('accountChanged', handleAccountsChanged);
70
+ };
71
+ };
72
+ const canSwitchNetworkTo: CanSwitchNetwork = ({ network }) => {
73
+ return EVM_SUPPORTED_CHAINS.includes(network as Networks);
74
+ };
75
+ export const getSigners: (provider: Provider) => Promise<SignerFactory> =
76
+ signer;
77
+
78
+ const canEagerConnect: CanEagerConnect = async ({ instance, meta }) => {
79
+ const solanaInstance = chooseInstance(instance, meta, Networks.SOLANA);
80
+ try {
81
+ const result = await solanaInstance.connect({ onlyIfTrusted: true });
82
+ return !!result;
83
+ } catch {
84
+ return false;
85
+ }
86
+ };
87
+ export const getWalletInfo: (allBlockChains: BlockchainMeta[]) => WalletInfo = (
88
+ allBlockChains
89
+ ) => {
90
+ let supportedChains: BlockchainMeta[] = [];
91
+ const sui = allBlockChains.filter(
92
+ (blockchain) => blockchain.type === TransactionType.SUI
93
+ );
94
+ const solana = solanaBlockchain(allBlockChains);
95
+ const evms = allBlockChains.filter(
96
+ (chain): chain is EvmBlockchainMeta =>
97
+ isEvmBlockchain(chain) &&
98
+ EVM_SUPPORTED_CHAINS.includes(chain.name as Networks)
99
+ );
100
+ const btc = allBlockChains.find((chain) => chain.name === Networks.BTC);
101
+ supportedChains = supportedChains.concat(solana, evms, sui);
102
+ if (btc) {
103
+ supportedChains.push(btc);
104
+ }
105
+
106
+ return {
107
+ name: 'Phantom',
108
+ img: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/phantom/icon.svg',
109
+ installLink: {
110
+ CHROME:
111
+ 'https://chrome.google.com/webstore/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa',
112
+
113
+ DEFAULT: 'https://phantom.app/',
114
+ },
115
+ color: '#4d40c6',
116
+ // if you are adding a new namespace, don't forget to also update `properties`
117
+ needsNamespace: {
118
+ selection: 'multiple',
119
+ data: [
120
+ {
121
+ label: 'EVM',
122
+ value: 'EVM',
123
+ id: 'ETH',
124
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
125
+ allBlockchains.filter(
126
+ (chain): chain is EvmBlockchainMeta =>
127
+ isEvmBlockchain(chain) &&
128
+ EVM_SUPPORTED_CHAINS.includes(chain.name as Networks)
129
+ ),
130
+ },
131
+ {
132
+ label: 'Solana',
133
+ value: 'Solana',
134
+ id: 'SOLANA',
135
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
136
+ solanaBlockchain(allBlockchains),
137
+ },
138
+ {
139
+ label: 'BTC',
140
+ value: 'UTXO',
141
+ id: 'BTC',
142
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
143
+ allBlockchains.filter(
144
+ (chain): chain is TransferBlockchainMeta =>
145
+ chain.name === Networks.BTC
146
+ ),
147
+ },
148
+ {
149
+ label: 'Sui',
150
+ value: 'Sui',
151
+ id: 'SUI',
152
+ getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
153
+ allBlockchains.filter(
154
+ (chain): chain is SuiBlockchainMeta =>
155
+ chain.type === TransactionType.SUI
156
+ ),
157
+ },
158
+ ],
159
+ },
160
+ supportedChains,
161
+ };
162
+ };
163
+
164
+ const buildLegacyProvider: () => LegacyProviderInterface = () => ({
165
+ config,
166
+ getInstance,
167
+ connect,
168
+ subscribe,
169
+ getSigners,
170
+ getWalletInfo,
171
+ canSwitchNetworkTo,
172
+ canEagerConnect,
173
+ });
174
+
175
+ export { buildLegacyProvider };
package/src/mod.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { defineVersions } from '@rango-dev/wallets-core/utils';
2
+
3
+ import { buildLegacyProvider } from './legacy/index.js';
4
+ import { buildProvider } from './provider.js';
5
+
6
+ const versions = () =>
7
+ defineVersions()
8
+ .version('0.0.0', buildLegacyProvider())
9
+ .version('1.0.0', buildProvider())
10
+ .build();
11
+
12
+ export { versions };
@@ -0,0 +1,62 @@
1
+ import type { EvmActions } from '@rango-dev/wallets-core/namespaces/evm';
2
+
3
+ import { NamespaceBuilder } from '@rango-dev/wallets-core';
4
+ import {
5
+ builders as commonBuilders,
6
+ standardizeAndThrowError,
7
+ } from '@rango-dev/wallets-core/namespaces/common';
8
+ import {
9
+ actions,
10
+ builders,
11
+ hooks,
12
+ } from '@rango-dev/wallets-core/namespaces/evm';
13
+
14
+ import { WALLET_ID } from '../constants.js';
15
+ import { evmPhantom } from '../utils.js';
16
+
17
+ const [changeAccountSubscriber, changeAccountCleanup] =
18
+ hooks.changeAccountSubscriber(evmPhantom);
19
+
20
+ /*
21
+ * TODO: If user imported a private key for EVM, it hasn't solana.
22
+ * when trying to connect to solana for this user we go through `-32603` which is an internal error.
23
+ * If phantom added an specific error code for this situation, we can consider handling the error here.
24
+ * @see https://docs.phantom.app/solana/errors
25
+ */
26
+ const connect = builders
27
+ .connect()
28
+ .action(actions.connect(evmPhantom))
29
+ .before(changeAccountSubscriber)
30
+ .or(changeAccountCleanup)
31
+ .or(standardizeAndThrowError)
32
+ .build();
33
+
34
+ const disconnect = commonBuilders
35
+ .disconnect<EvmActions>()
36
+ .after(changeAccountCleanup)
37
+ .build();
38
+
39
+ const canEagerConnect = builders
40
+ .canEagerConnect()
41
+ .action(actions.canEagerConnect(evmPhantom))
42
+ .build();
43
+
44
+ const canSwitchNetwork = builders
45
+ .canSwitchNetwork()
46
+ .action(actions.canSwitchNetwork())
47
+ .build();
48
+
49
+ const getChainId = builders
50
+ .getChainId()
51
+ .action(actions.getChainId(evmPhantom))
52
+ .build();
53
+
54
+ const evm = new NamespaceBuilder<EvmActions>('EVM', WALLET_ID)
55
+ .action(connect)
56
+ .action(disconnect)
57
+ .action(canEagerConnect)
58
+ .action(canSwitchNetwork)
59
+ .action(getChainId)
60
+ .build();
61
+
62
+ export { evm };
@@ -0,0 +1,80 @@
1
+ import type { CaipAccount } from '@rango-dev/wallets-core/namespaces/common';
2
+ import type { SolanaActions } from '@rango-dev/wallets-core/namespaces/solana';
3
+
4
+ import { ActionBuilder, NamespaceBuilder } from '@rango-dev/wallets-core';
5
+ import {
6
+ builders as commonBuilders,
7
+ standardizeAndThrowError,
8
+ } from '@rango-dev/wallets-core/namespaces/common';
9
+ import {
10
+ actions,
11
+ builders,
12
+ CAIP_NAMESPACE,
13
+ CAIP_SOLANA_CHAIN_ID,
14
+ hooks,
15
+ } from '@rango-dev/wallets-core/namespaces/solana';
16
+ import { CAIP } from '@rango-dev/wallets-core/utils';
17
+ import { getSolanaAccounts } from '@rango-dev/wallets-shared';
18
+
19
+ import { WALLET_ID } from '../constants.js';
20
+ import { solanaPhantom } from '../utils.js';
21
+
22
+ const [changeAccountSubscriber, changeAccountCleanup] =
23
+ hooks.changeAccountSubscriber(solanaPhantom);
24
+
25
+ /*
26
+ * TODO: If user imported a private key for EVM, it hasn't solana.
27
+ * when trying to connect to solana for this user we go through `-32603` which is an internal error.
28
+ * If phantom added an specific error code for this situation, we can consider handling the error here.
29
+ * @see https://docs.phantom.app/solana/errors
30
+ */
31
+ const connect = builders
32
+ .connect()
33
+ .action(async function () {
34
+ const solanaInstance = solanaPhantom();
35
+ const result = await getSolanaAccounts({
36
+ instance: solanaInstance,
37
+ meta: [],
38
+ });
39
+ if (Array.isArray(result)) {
40
+ throw new Error(
41
+ 'Expecting solana response to be a single value, not an array.'
42
+ );
43
+ }
44
+
45
+ const formatAccounts = result.accounts.map(
46
+ (account) =>
47
+ CAIP.AccountId.format({
48
+ address: account,
49
+ chainId: {
50
+ namespace: CAIP_NAMESPACE,
51
+ reference: CAIP_SOLANA_CHAIN_ID,
52
+ },
53
+ }) as CaipAccount
54
+ );
55
+
56
+ return formatAccounts;
57
+ })
58
+ .before(changeAccountSubscriber)
59
+ .or(changeAccountCleanup)
60
+ .or(standardizeAndThrowError)
61
+ .build();
62
+
63
+ const disconnect = commonBuilders
64
+ .disconnect<SolanaActions>()
65
+ .after(changeAccountCleanup)
66
+ .build();
67
+
68
+ const canEagerConnect = new ActionBuilder<SolanaActions, 'canEagerConnect'>(
69
+ 'canEagerConnect'
70
+ )
71
+ .action(actions.canEagerConnect(solanaPhantom))
72
+ .build();
73
+
74
+ const solana = new NamespaceBuilder<SolanaActions>('Solana', WALLET_ID)
75
+ .action(connect)
76
+ .action(disconnect)
77
+ .action(canEagerConnect)
78
+ .build();
79
+
80
+ export { solana };
@@ -0,0 +1,51 @@
1
+ import type { SolanaActions } from '@rango-dev/wallets-core/namespaces/solana';
2
+ import type { SuiActions } from '@rango-dev/wallets-core/namespaces/sui';
3
+
4
+ import { ActionBuilder, NamespaceBuilder } from '@rango-dev/wallets-core';
5
+ import {
6
+ builders as commonBuilders,
7
+ standardizeAndThrowError,
8
+ } from '@rango-dev/wallets-core/namespaces/common';
9
+ import { actions as solanaActions } from '@rango-dev/wallets-core/namespaces/solana';
10
+ import { builders, hooks } from '@rango-dev/wallets-core/namespaces/sui';
11
+
12
+ import { WALLET_ID, WALLET_NAME_IN_WALLET_STANDARD } from '../constants.js';
13
+ import { solanaPhantom } from '../utils.js';
14
+
15
+ const [changeAccountSubscriber, changeAccountCleanup] =
16
+ hooks.changeAccountSubscriber({
17
+ name: WALLET_NAME_IN_WALLET_STANDARD,
18
+ });
19
+
20
+ const connect = builders
21
+ .connect({
22
+ name: WALLET_NAME_IN_WALLET_STANDARD,
23
+ })
24
+ .before(changeAccountSubscriber)
25
+ .or(changeAccountCleanup)
26
+ .or(standardizeAndThrowError)
27
+ .build();
28
+
29
+ const disconnect = commonBuilders
30
+ .disconnect<SuiActions>()
31
+ .after(changeAccountCleanup)
32
+ .build();
33
+
34
+ /*
35
+ * TODO: We are currently using `solanaCanEagerConnectAction` to establish an eager connection to the Sui instance.
36
+ * This is a temporary workaround due to Phantom's limitation in silently connecting to a Sui account.
37
+ * Once Phantom introduces support for silent Sui connections, this implementation should be updated accordingly.
38
+ */
39
+ const canEagerConnect = new ActionBuilder<SolanaActions, 'canEagerConnect'>(
40
+ 'canEagerConnect'
41
+ )
42
+ .action(solanaActions.canEagerConnect(solanaPhantom))
43
+ .build();
44
+
45
+ const sui = new NamespaceBuilder<SuiActions>('Sui', WALLET_ID)
46
+ .action(connect)
47
+ .action(disconnect)
48
+ .action(canEagerConnect)
49
+ .build();
50
+
51
+ export { sui };
@@ -0,0 +1,171 @@
1
+ import type {
2
+ ProviderAPI,
3
+ UtxoActions,
4
+ } from '@rango-dev/wallets-core/namespaces/utxo';
5
+
6
+ import {
7
+ ActionBuilder,
8
+ NamespaceBuilder,
9
+ type Subscriber,
10
+ type SubscriberCleanUp,
11
+ } from '@rango-dev/wallets-core';
12
+ import {
13
+ type CaipAccount,
14
+ standardizeAndThrowError,
15
+ } from '@rango-dev/wallets-core/namespaces/common';
16
+ import { builders as commonBuilders } from '@rango-dev/wallets-core/namespaces/common';
17
+ import {
18
+ actions as solanaActions,
19
+ type SolanaActions,
20
+ } from '@rango-dev/wallets-core/namespaces/solana';
21
+ import {
22
+ builders,
23
+ CAIP_BITCOIN_CHAIN_ID,
24
+ CAIP_NAMESPACE,
25
+ } from '@rango-dev/wallets-core/namespaces/utxo';
26
+ import { CAIP } from '@rango-dev/wallets-core/utils';
27
+ import {
28
+ Networks,
29
+ type ProviderConnectResult,
30
+ } from '@rango-dev/wallets-shared';
31
+
32
+ import { WALLET_ID } from '../constants.js';
33
+ import { bitcoinPhantom, solanaPhantom } from '../utils.js';
34
+
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ type AnyFunction = (...args: any[]) => any;
37
+
38
+ type BtcAccount = {
39
+ address: string;
40
+ publicKey: string;
41
+ addressType: 'p2tr' | 'p2wpkh' | 'p2sh' | 'p2pkh';
42
+ purpose: 'payment' | 'ordinals';
43
+ };
44
+
45
+ const getBitcoinAccounts: () => Promise<ProviderConnectResult> = async () => {
46
+ const instance = bitcoinPhantom();
47
+ const accounts = await instance.requestAccounts();
48
+
49
+ return {
50
+ accounts: accounts
51
+ .filter((account: BtcAccount) => account.purpose === 'payment')
52
+ .map((account: BtcAccount) => account.address),
53
+ chainId: Networks.BTC,
54
+ };
55
+ };
56
+
57
+ function getChangeAccountSubscriber(
58
+ instance: () => ProviderAPI | undefined
59
+ ): [Subscriber<UtxoActions>, SubscriberCleanUp<UtxoActions>] {
60
+ let eventCallback: AnyFunction;
61
+
62
+ // subscriber can be passed to `or`, it will get the error and should rethrow error to pass the error to next `or` or throw error.
63
+ return [
64
+ (context, err) => {
65
+ const bitcoinInstance = instance();
66
+
67
+ if (!bitcoinInstance) {
68
+ throw new Error(
69
+ 'Trying to subscribe to your Solana wallet, but seems its instance is not available.'
70
+ );
71
+ }
72
+
73
+ const [, setState] = context.state();
74
+
75
+ eventCallback = (accounts: BtcAccount[]) => {
76
+ /*
77
+ * In Phantom, when user is switching to an account which is not connected to dApp yet, it returns an empty array on `accountsChanged`.
78
+ * So empty array means we don't have access to account and we need to disconnect and let the user connect the account.
79
+ */
80
+ if (!accounts.length) {
81
+ context.action('disconnect');
82
+ return;
83
+ }
84
+
85
+ const formatAccounts = accounts
86
+ .filter((account) => account.purpose === 'payment')
87
+ .map(
88
+ (account: BtcAccount) =>
89
+ CAIP.AccountId.format({
90
+ address: account.address,
91
+ chainId: {
92
+ namespace: CAIP_NAMESPACE,
93
+ reference: CAIP_BITCOIN_CHAIN_ID,
94
+ },
95
+ }) as CaipAccount
96
+ );
97
+
98
+ setState('accounts', formatAccounts);
99
+ };
100
+ bitcoinInstance.on('accountsChanged', eventCallback);
101
+
102
+ if (err instanceof Error) {
103
+ throw err;
104
+ }
105
+ },
106
+ (_context, err) => {
107
+ const bitcoinInstance = instance();
108
+
109
+ if (eventCallback && bitcoinInstance) {
110
+ bitcoinInstance.off('accountsChanged', eventCallback);
111
+ }
112
+
113
+ return err;
114
+ },
115
+ ];
116
+ }
117
+
118
+ const [changeAccountSubscriber, changeAccountCleanup] =
119
+ getChangeAccountSubscriber(bitcoinPhantom);
120
+
121
+ const connect = builders
122
+ .connect()
123
+ .action(async function () {
124
+ const result = await getBitcoinAccounts();
125
+ if (Array.isArray(result)) {
126
+ throw new Error(
127
+ 'Expecting bitcoin response to be a single value, not an array.'
128
+ );
129
+ }
130
+
131
+ const formatAccounts = result.accounts.map(
132
+ (account) =>
133
+ CAIP.AccountId.format({
134
+ address: account,
135
+ chainId: {
136
+ namespace: CAIP_NAMESPACE,
137
+ reference: CAIP_BITCOIN_CHAIN_ID,
138
+ },
139
+ }) as CaipAccount
140
+ );
141
+
142
+ return formatAccounts;
143
+ })
144
+ .before(changeAccountSubscriber)
145
+ .or(changeAccountCleanup)
146
+ .or(standardizeAndThrowError)
147
+ .build();
148
+
149
+ const disconnect = commonBuilders
150
+ .disconnect<UtxoActions>()
151
+ .after(changeAccountCleanup)
152
+ .build();
153
+
154
+ /*
155
+ * TODO: We are currently using `solanaCanEagerConnectAction` to establish an eager connection to the BTC instance.
156
+ * This is a temporary workaround due to Phantom's limitation in silently connecting to a BTC account.
157
+ * Once Phantom introduces support for silent BTC connections, this implementation should be updated accordingly.
158
+ */
159
+ const canEagerConnect = new ActionBuilder<SolanaActions, 'canEagerConnect'>(
160
+ 'canEagerConnect'
161
+ )
162
+ .action(solanaActions.canEagerConnect(solanaPhantom))
163
+ .build();
164
+
165
+ const utxo = new NamespaceBuilder<UtxoActions>('UTXO', WALLET_ID)
166
+ .action(connect)
167
+ .action(disconnect)
168
+ .action(canEagerConnect)
169
+ .build();
170
+
171
+ export { utxo };