@rango-dev/provider-metamask 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/CHANGELOG.md +239 -0
- package/dist/actions/solana.d.ts +11 -0
- package/dist/actions/solana.d.ts.map +1 -0
- package/dist/builders/solana.d.ts +8 -0
- package/dist/builders/solana.d.ts.map +1 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/legacy/index.d.ts +20 -0
- package/dist/legacy/index.d.ts.map +1 -0
- package/dist/mod.d.ts +3 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +2 -0
- package/dist/mod.js.map +7 -0
- package/dist/namespaces/evm.d.ts +4 -0
- package/dist/namespaces/evm.d.ts.map +1 -0
- package/dist/namespaces/solana.d.ts +4 -0
- package/dist/namespaces/solana.d.ts.map +1 -0
- package/dist/provider-metamask.build.json +1 -0
- package/dist/provider.d.ts +3 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/signer.d.ts +4 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signers/solana.d.ts +12 -0
- package/dist/signers/solana.d.ts.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +40 -0
- package/readme.md +26 -0
- package/src/actions/solana.ts +42 -0
- package/src/builders/solana.ts +36 -0
- package/src/constants.ts +56 -0
- package/src/legacy/index.ts +91 -0
- package/src/mod.ts +12 -0
- package/src/namespaces/evm.ts +81 -0
- package/src/namespaces/solana.ts +45 -0
- package/src/provider.ts +23 -0
- package/src/signer.ts +29 -0
- package/src/signers/solana.ts +88 -0
- package/src/types.ts +54 -0
- package/src/utils.ts +144 -0
package/readme.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# MetaMask
|
|
2
|
+
MetaMask Wallet integration for hub.
|
|
3
|
+
[Homepage](https://metamask.io/) | [Docs](https://docs.metamask.io/)
|
|
4
|
+
|
|
5
|
+
More about implementation status can be found [here](../readme.md).
|
|
6
|
+
|
|
7
|
+
## Implementation notes/limitations
|
|
8
|
+
|
|
9
|
+
### Group
|
|
10
|
+
MetaMask supports both **EVM** and **Solana**.
|
|
11
|
+
|
|
12
|
+
### Feature
|
|
13
|
+
|
|
14
|
+
#### ⚠️ Switch Account
|
|
15
|
+
|
|
16
|
+
In MetaMask, you can have only one active account at a time, which may belong to either the Solana or EVM namespace. When you connect to MetaMask, it connects to the currently active account—this account could belong to either namespace. Additionally, MetaMask may also establish a random connection to another account from the opposite namespace.
|
|
17
|
+
|
|
18
|
+
When a user switches accounts in MetaMask, the wallet emits an update event tied to the currently active namespace—either EVM or Solana.
|
|
19
|
+
The switchAccount notification is scoped to that active namespace, meaning only the relevant provider will receive the update.
|
|
20
|
+
|
|
21
|
+
Regardless of which namespace triggers the event, MetaMask always executes transactions using the correct account for the selected chain.
|
|
22
|
+
This ensures consistent behavior and prevents cross-namespace conflicts between Solana and EVM contexts.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
More wallet information can be found in [readme.md](../readme.md).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { WalletStandardSolanaInstance } from '../types.js';
|
|
2
|
+
import type { Context, FunctionWithContext } from '@rango-dev/wallets-core';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
type SolanaActions,
|
|
6
|
+
utils,
|
|
7
|
+
} from '@rango-dev/wallets-core/namespaces/solana';
|
|
8
|
+
|
|
9
|
+
function connect(
|
|
10
|
+
getInstance: () => WalletStandardSolanaInstance
|
|
11
|
+
): FunctionWithContext<SolanaActions['connect'], Context> {
|
|
12
|
+
return async () => {
|
|
13
|
+
const solanaInstance = getInstance();
|
|
14
|
+
const connectResult = await solanaInstance.features[
|
|
15
|
+
'standard:connect'
|
|
16
|
+
].connect();
|
|
17
|
+
return utils.formatAccountsToCAIP(
|
|
18
|
+
connectResult.accounts.map((account) => account.address)
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function canEagerConnect(getInstance: () => WalletStandardSolanaInstance) {
|
|
23
|
+
return async () => {
|
|
24
|
+
const solanaInstance = getInstance();
|
|
25
|
+
|
|
26
|
+
if (!solanaInstance) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'Trying to eagerly connect to your Solana wallet, but it seems that its instance is not available.'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const result = await solanaInstance.features['standard:connect'].connect({
|
|
34
|
+
silent: true,
|
|
35
|
+
});
|
|
36
|
+
return !!result.accounts.length;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export const solanaActions = { connect, canEagerConnect };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { WalletStandardSolanaInstance } from '../types.js';
|
|
2
|
+
import type { StandardEventsChangeProperties } from '@wallet-standard/features';
|
|
3
|
+
|
|
4
|
+
import { ChangeAccountSubscriberBuilder } from '@rango-dev/wallets-core/namespaces/common';
|
|
5
|
+
import {
|
|
6
|
+
type SolanaActions,
|
|
7
|
+
utils,
|
|
8
|
+
} from '@rango-dev/wallets-core/namespaces/solana';
|
|
9
|
+
|
|
10
|
+
// Hooks
|
|
11
|
+
const changeAccountSubscriber = (
|
|
12
|
+
getInstance: () => WalletStandardSolanaInstance
|
|
13
|
+
) =>
|
|
14
|
+
new ChangeAccountSubscriberBuilder<
|
|
15
|
+
StandardEventsChangeProperties,
|
|
16
|
+
WalletStandardSolanaInstance,
|
|
17
|
+
SolanaActions
|
|
18
|
+
>()
|
|
19
|
+
.getInstance(getInstance)
|
|
20
|
+
.onSwitchAccount((event, context) => {
|
|
21
|
+
if (!event.payload.accounts?.length) {
|
|
22
|
+
context.action('disconnect');
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
.format(async (_, event) =>
|
|
27
|
+
utils.formatAccountsToCAIP(
|
|
28
|
+
event.accounts!.map((account) => account.address)
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
.addEventListener((instance, callback) => {
|
|
32
|
+
instance.features['standard:events'].on('change', callback);
|
|
33
|
+
})
|
|
34
|
+
.removeEventListener((_, __) => {});
|
|
35
|
+
|
|
36
|
+
export const solanaBuilders = { changeAccountSubscriber };
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ProviderMetadata } from '@rango-dev/wallets-core';
|
|
2
|
+
import {
|
|
3
|
+
type BlockchainMeta,
|
|
4
|
+
evmBlockchains,
|
|
5
|
+
solanaBlockchain,
|
|
6
|
+
} from 'rango-types';
|
|
7
|
+
|
|
8
|
+
import getSigners from './signer.js';
|
|
9
|
+
import { getInstanceOrThrow } from './utils.js';
|
|
10
|
+
|
|
11
|
+
export const WALLET_ID = 'metamask';
|
|
12
|
+
export const WALLET_STANDARD_NAME = 'MetaMask';
|
|
13
|
+
export const SOLANA_WALLET_STANDARD_MAINNET = 'solana:mainnet';
|
|
14
|
+
export const metadata: ProviderMetadata = {
|
|
15
|
+
name: 'MetaMask',
|
|
16
|
+
icon: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/metamask/icon.svg',
|
|
17
|
+
extensions: {
|
|
18
|
+
chrome:
|
|
19
|
+
'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en',
|
|
20
|
+
brave:
|
|
21
|
+
'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en',
|
|
22
|
+
firefox: 'https://addons.mozilla.org/en-US/firefox/addon/ether-metamask',
|
|
23
|
+
edge: 'https://microsoftedge.microsoft.com/addons/detail/metamask/ejbalbakoplchlghecdalmeeeajnimhm?hl=en-US',
|
|
24
|
+
homepage: 'https://metamask.io/download/',
|
|
25
|
+
},
|
|
26
|
+
properties: [
|
|
27
|
+
{
|
|
28
|
+
name: 'namespaces',
|
|
29
|
+
value: {
|
|
30
|
+
selection: 'multiple',
|
|
31
|
+
data: [
|
|
32
|
+
{
|
|
33
|
+
label: 'EVM',
|
|
34
|
+
value: 'EVM',
|
|
35
|
+
id: 'ETH',
|
|
36
|
+
getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
|
|
37
|
+
evmBlockchains(allBlockchains),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
label: 'Solana',
|
|
41
|
+
value: 'Solana',
|
|
42
|
+
id: 'SOLANA',
|
|
43
|
+
getSupportedChains: (allBlockchains: BlockchainMeta[]) =>
|
|
44
|
+
solanaBlockchain(allBlockchains),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'signers',
|
|
51
|
+
value: {
|
|
52
|
+
getSigners: async () => getSigners(getInstanceOrThrow()),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { LegacyProviderInterface } from '@rango-dev/wallets-core/legacy';
|
|
2
|
+
import type {
|
|
3
|
+
CanEagerConnect,
|
|
4
|
+
CanSwitchNetwork,
|
|
5
|
+
Connect,
|
|
6
|
+
Subscribe,
|
|
7
|
+
SwitchNetwork,
|
|
8
|
+
WalletInfo,
|
|
9
|
+
} from '@rango-dev/wallets-shared';
|
|
10
|
+
import type { BlockchainMeta, SignerFactory } from 'rango-types';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
canEagerlyConnectToEvm,
|
|
14
|
+
canSwitchNetworkToEvm,
|
|
15
|
+
getEvmAccounts,
|
|
16
|
+
subscribeToEvm,
|
|
17
|
+
switchNetworkForEvm,
|
|
18
|
+
WalletTypes,
|
|
19
|
+
} from '@rango-dev/wallets-shared';
|
|
20
|
+
import { evmBlockchains } from 'rango-types';
|
|
21
|
+
|
|
22
|
+
import signer from '../signer.js';
|
|
23
|
+
import { metamask as metamask_instance } from '../utils.js';
|
|
24
|
+
|
|
25
|
+
const WALLET = WalletTypes.META_MASK;
|
|
26
|
+
|
|
27
|
+
export const config = {
|
|
28
|
+
type: WALLET,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const getInstance = metamask_instance;
|
|
32
|
+
export const connect: Connect = async ({ instance }) => {
|
|
33
|
+
/*
|
|
34
|
+
* Note: We need to get `chainId` here, because for the first time
|
|
35
|
+
* after opening the browser, wallet is locked, and don't give us accounts and chainId
|
|
36
|
+
* on `check` phase, so `network` will be null. For this case we need to get chainId
|
|
37
|
+
* whenever we are requesting accounts.
|
|
38
|
+
*/
|
|
39
|
+
const { accounts, chainId } = await getEvmAccounts(instance);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
accounts,
|
|
43
|
+
chainId,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const subscribe: Subscribe = subscribeToEvm;
|
|
48
|
+
|
|
49
|
+
export const switchNetwork: SwitchNetwork = switchNetworkForEvm;
|
|
50
|
+
|
|
51
|
+
export const canSwitchNetworkTo: CanSwitchNetwork = canSwitchNetworkToEvm;
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
type Provider = any;
|
|
54
|
+
export const getSigners: (provider: Provider) => Promise<SignerFactory> =
|
|
55
|
+
signer;
|
|
56
|
+
|
|
57
|
+
export const canEagerConnect: CanEagerConnect = canEagerlyConnectToEvm;
|
|
58
|
+
|
|
59
|
+
export const getWalletInfo: (allBlockChains: BlockchainMeta[]) => WalletInfo = (
|
|
60
|
+
allBlockChains
|
|
61
|
+
) => {
|
|
62
|
+
const evms = evmBlockchains(allBlockChains);
|
|
63
|
+
return {
|
|
64
|
+
name: 'MetaMask',
|
|
65
|
+
img: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/metamask/icon.svg',
|
|
66
|
+
installLink: {
|
|
67
|
+
CHROME:
|
|
68
|
+
'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en',
|
|
69
|
+
BRAVE:
|
|
70
|
+
'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en',
|
|
71
|
+
|
|
72
|
+
FIREFOX: 'https://addons.mozilla.org/en-US/firefox/addon/ether-metamask',
|
|
73
|
+
EDGE: 'https://microsoftedge.microsoft.com/addons/detail/metamask/ejbalbakoplchlghecdalmeeeajnimhm?hl=en-US',
|
|
74
|
+
DEFAULT: 'https://metamask.io/download/',
|
|
75
|
+
},
|
|
76
|
+
color: '#dac7ae',
|
|
77
|
+
supportedChains: evms,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
const buildLegacyProvider: () => LegacyProviderInterface = () => ({
|
|
81
|
+
config,
|
|
82
|
+
getInstance,
|
|
83
|
+
connect,
|
|
84
|
+
subscribe,
|
|
85
|
+
getSigners,
|
|
86
|
+
getWalletInfo,
|
|
87
|
+
canSwitchNetworkTo,
|
|
88
|
+
canEagerConnect,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
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,81 @@
|
|
|
1
|
+
import type { EvmActions } from '@rango-dev/wallets-core/namespaces/evm';
|
|
2
|
+
|
|
3
|
+
import { ActionBuilder, NamespaceBuilder } from '@rango-dev/wallets-core';
|
|
4
|
+
import {
|
|
5
|
+
builders as commonBuilders,
|
|
6
|
+
connectAndUpdateStateForMultiNetworks,
|
|
7
|
+
intoConnecting,
|
|
8
|
+
intoConnectionFinished,
|
|
9
|
+
standardizeAndThrowError,
|
|
10
|
+
} from '@rango-dev/wallets-core/namespaces/common';
|
|
11
|
+
import {
|
|
12
|
+
actions,
|
|
13
|
+
builders,
|
|
14
|
+
utils,
|
|
15
|
+
} from '@rango-dev/wallets-core/namespaces/evm';
|
|
16
|
+
|
|
17
|
+
import { WALLET_ID } from '../constants.js';
|
|
18
|
+
import { evmMetamask } from '../utils.js';
|
|
19
|
+
|
|
20
|
+
const [changeAccountSubscriber, changeAccountCleanup] = builders
|
|
21
|
+
.changeAccountSubscriber(evmMetamask)
|
|
22
|
+
/*
|
|
23
|
+
* Metamask returns an array of connected accounts with the active one first.
|
|
24
|
+
* Since we only need the active account, we take the first element as a workaround.
|
|
25
|
+
*/
|
|
26
|
+
.format(async (instance, accounts) => {
|
|
27
|
+
const chainId = await instance.request({ method: 'eth_chainId' });
|
|
28
|
+
return utils.formatAccountsToCAIP([accounts[0]], chainId);
|
|
29
|
+
})
|
|
30
|
+
.build();
|
|
31
|
+
|
|
32
|
+
const connect = new ActionBuilder<EvmActions, 'connect'>('connect')
|
|
33
|
+
.action(actions.connect(evmMetamask))
|
|
34
|
+
/*
|
|
35
|
+
* Metamask Wallet's `connect` returns a list where the currently selected account
|
|
36
|
+
* is always the first item. We're directly taking this first item as the active account.
|
|
37
|
+
*
|
|
38
|
+
* ***NOTE***: Please keep it synced with `wallets/core/src/namespaces/solana/builders.ts`.
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
.and((_, connectResult) => ({
|
|
42
|
+
...connectResult,
|
|
43
|
+
accounts: [connectResult.accounts[0]],
|
|
44
|
+
}))
|
|
45
|
+
.and(connectAndUpdateStateForMultiNetworks)
|
|
46
|
+
.before(intoConnecting)
|
|
47
|
+
.before(changeAccountSubscriber)
|
|
48
|
+
.or(changeAccountCleanup)
|
|
49
|
+
.or(standardizeAndThrowError)
|
|
50
|
+
.after(intoConnectionFinished)
|
|
51
|
+
.build();
|
|
52
|
+
|
|
53
|
+
const canEagerConnect = builders
|
|
54
|
+
.canEagerConnect()
|
|
55
|
+
.action(actions.canEagerConnect(evmMetamask))
|
|
56
|
+
.build();
|
|
57
|
+
|
|
58
|
+
const disconnect = commonBuilders
|
|
59
|
+
.disconnect<EvmActions>()
|
|
60
|
+
.after(changeAccountCleanup)
|
|
61
|
+
.build();
|
|
62
|
+
|
|
63
|
+
const canSwitchNetwork = builders
|
|
64
|
+
.canSwitchNetwork()
|
|
65
|
+
.action(actions.canSwitchNetwork())
|
|
66
|
+
.build();
|
|
67
|
+
|
|
68
|
+
const getChainId = builders
|
|
69
|
+
.getChainId()
|
|
70
|
+
.action(actions.getChainId(evmMetamask))
|
|
71
|
+
.build();
|
|
72
|
+
|
|
73
|
+
const evm = new NamespaceBuilder<EvmActions>('EVM', WALLET_ID)
|
|
74
|
+
.action(connect)
|
|
75
|
+
.action(disconnect)
|
|
76
|
+
.action(canSwitchNetwork)
|
|
77
|
+
.action(canEagerConnect)
|
|
78
|
+
.action(getChainId)
|
|
79
|
+
.build();
|
|
80
|
+
|
|
81
|
+
export { evm };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ActionBuilder, NamespaceBuilder } from '@rango-dev/wallets-core';
|
|
2
|
+
import {
|
|
3
|
+
builders as commonBuilders,
|
|
4
|
+
standardizeAndThrowError,
|
|
5
|
+
} from '@rango-dev/wallets-core/namespaces/common';
|
|
6
|
+
import {
|
|
7
|
+
builders,
|
|
8
|
+
type SolanaActions,
|
|
9
|
+
} from '@rango-dev/wallets-core/namespaces/solana';
|
|
10
|
+
|
|
11
|
+
import { solanaActions } from '../actions/solana.js';
|
|
12
|
+
import { solanaBuilders } from '../builders/solana.js';
|
|
13
|
+
import { WALLET_ID } from '../constants.js';
|
|
14
|
+
import { solanaMetamask } from '../utils.js';
|
|
15
|
+
|
|
16
|
+
const [changeAccountSubscriber, changeAccountCleanup] = solanaBuilders
|
|
17
|
+
.changeAccountSubscriber(solanaMetamask)
|
|
18
|
+
.build();
|
|
19
|
+
|
|
20
|
+
const connect = builders
|
|
21
|
+
.connect()
|
|
22
|
+
.action(solanaActions.connect(solanaMetamask))
|
|
23
|
+
.before(changeAccountSubscriber)
|
|
24
|
+
.or(changeAccountCleanup)
|
|
25
|
+
.or(standardizeAndThrowError)
|
|
26
|
+
.build();
|
|
27
|
+
|
|
28
|
+
const canEagerConnect = new ActionBuilder<SolanaActions, 'canEagerConnect'>(
|
|
29
|
+
'canEagerConnect'
|
|
30
|
+
)
|
|
31
|
+
.action(solanaActions.canEagerConnect(solanaMetamask))
|
|
32
|
+
.build();
|
|
33
|
+
|
|
34
|
+
const disconnect = commonBuilders
|
|
35
|
+
.disconnect<SolanaActions>()
|
|
36
|
+
.after(changeAccountCleanup)
|
|
37
|
+
.build();
|
|
38
|
+
|
|
39
|
+
const solana = new NamespaceBuilder<SolanaActions>('Solana', WALLET_ID)
|
|
40
|
+
.action(connect)
|
|
41
|
+
.action(disconnect)
|
|
42
|
+
.action(canEagerConnect)
|
|
43
|
+
.build();
|
|
44
|
+
|
|
45
|
+
export { solana };
|
package/src/provider.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ProviderBuilder } from '@rango-dev/wallets-core';
|
|
2
|
+
|
|
3
|
+
import { metadata, WALLET_ID } from './constants.js';
|
|
4
|
+
import { evm } from './namespaces/evm.js';
|
|
5
|
+
import { solana } from './namespaces/solana.js';
|
|
6
|
+
import { metamask as metamask } from './utils.js';
|
|
7
|
+
|
|
8
|
+
const buildProvider = () =>
|
|
9
|
+
new ProviderBuilder(WALLET_ID)
|
|
10
|
+
.init(function (context) {
|
|
11
|
+
const [, setState] = context.state();
|
|
12
|
+
|
|
13
|
+
if (metamask()) {
|
|
14
|
+
setState('installed', true);
|
|
15
|
+
console.debug('[metamask] instance detected.', context);
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
.config('metadata', metadata)
|
|
19
|
+
.add('evm', evm)
|
|
20
|
+
.add('solana', solana)
|
|
21
|
+
.build();
|
|
22
|
+
|
|
23
|
+
export { buildProvider };
|
package/src/signer.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Provider } from './types.js';
|
|
2
|
+
import type { SignerFactory } from 'rango-types';
|
|
3
|
+
|
|
4
|
+
import { LegacyNetworks as Networks } from '@rango-dev/wallets-core/legacy';
|
|
5
|
+
import {
|
|
6
|
+
dynamicImportWithRefinedError,
|
|
7
|
+
getNetworkInstance,
|
|
8
|
+
} from '@rango-dev/wallets-shared';
|
|
9
|
+
import { DefaultSignerFactory, TransactionType as TxType } from 'rango-types';
|
|
10
|
+
|
|
11
|
+
import { MetamaskSolanaSigner } from './signers/solana.js';
|
|
12
|
+
|
|
13
|
+
export default async function getSigners(
|
|
14
|
+
provider: Provider
|
|
15
|
+
): Promise<SignerFactory> {
|
|
16
|
+
const ethProvider = getNetworkInstance(provider, Networks.ETHEREUM);
|
|
17
|
+
const solanaProvider = getNetworkInstance(provider, Networks.SOLANA);
|
|
18
|
+
|
|
19
|
+
const signers = new DefaultSignerFactory();
|
|
20
|
+
const { DefaultEvmSigner } = await dynamicImportWithRefinedError(
|
|
21
|
+
async () => await import('@rango-dev/signer-evm')
|
|
22
|
+
);
|
|
23
|
+
signers.registerSigner(TxType.EVM, new DefaultEvmSigner(ethProvider));
|
|
24
|
+
signers.registerSigner(
|
|
25
|
+
TxType.SOLANA,
|
|
26
|
+
new MetamaskSolanaSigner(solanaProvider)
|
|
27
|
+
);
|
|
28
|
+
return signers;
|
|
29
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { WalletStandardSolanaInstance } from '../types.js';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
generalSolanaTransactionExecutor,
|
|
5
|
+
type SolanaWeb3Signer,
|
|
6
|
+
} from '@rango-dev/signer-solana';
|
|
7
|
+
import base58 from 'bs58';
|
|
8
|
+
import {
|
|
9
|
+
type GenericSigner,
|
|
10
|
+
SignerError,
|
|
11
|
+
SignerErrorCode,
|
|
12
|
+
type SolanaTransaction,
|
|
13
|
+
} from 'rango-types';
|
|
14
|
+
|
|
15
|
+
async function executeSolanaTransaction(
|
|
16
|
+
tx: SolanaTransaction,
|
|
17
|
+
solanaProvider: WalletStandardSolanaInstance
|
|
18
|
+
): Promise<string> {
|
|
19
|
+
const DefaultSolanaSigner: SolanaWeb3Signer = async (
|
|
20
|
+
solanaWeb3Transaction
|
|
21
|
+
) => {
|
|
22
|
+
const [currentAccount] = solanaProvider.accounts;
|
|
23
|
+
if (!currentAccount.publicKey) {
|
|
24
|
+
throw new SignerError(
|
|
25
|
+
SignerErrorCode.SIGN_TX_ERROR,
|
|
26
|
+
'Please make sure the required account is connected properly.'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (tx.from !== currentAccount.address) {
|
|
31
|
+
throw new SignerError(
|
|
32
|
+
SignerErrorCode.SIGN_TX_ERROR,
|
|
33
|
+
`Your connected account doesn't match with the required account. Please ensure that you are connected with the correct account and try again.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const [signOutput] = await solanaProvider.features[
|
|
39
|
+
'solana:signTransaction'
|
|
40
|
+
].signTransaction({
|
|
41
|
+
account: currentAccount,
|
|
42
|
+
transaction: solanaWeb3Transaction.serialize(),
|
|
43
|
+
});
|
|
44
|
+
return signOutput.signedTransaction;
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
} catch (e: any) {
|
|
47
|
+
const REJECTION_CODE = 4001;
|
|
48
|
+
if (e && Object.hasOwn(e, 'code') && e.code === REJECTION_CODE) {
|
|
49
|
+
throw new SignerError(SignerErrorCode.REJECTED_BY_USER, undefined, e);
|
|
50
|
+
}
|
|
51
|
+
throw new SignerError(SignerErrorCode.SIGN_TX_ERROR, undefined, e);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return await generalSolanaTransactionExecutor(tx, DefaultSolanaSigner);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class MetamaskSolanaSigner implements GenericSigner<SolanaTransaction> {
|
|
58
|
+
private _provider: WalletStandardSolanaInstance;
|
|
59
|
+
|
|
60
|
+
constructor(provider: WalletStandardSolanaInstance) {
|
|
61
|
+
this._provider = provider;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get provider(): WalletStandardSolanaInstance {
|
|
65
|
+
return this._provider;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async signMessage(msg: string): Promise<string> {
|
|
69
|
+
try {
|
|
70
|
+
const encodedMessage = new TextEncoder().encode(msg);
|
|
71
|
+
const [account] = this.provider.accounts;
|
|
72
|
+
const [signOutput] = await this._provider.features[
|
|
73
|
+
'solana:signMessage'
|
|
74
|
+
].signMessage({
|
|
75
|
+
message: encodedMessage,
|
|
76
|
+
account,
|
|
77
|
+
});
|
|
78
|
+
return base58.encode(signOutput.signature);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new SignerError(SignerErrorCode.SIGN_TX_ERROR, undefined, error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async signAndSendTx(tx: SolanaTransaction): Promise<{ hash: string }> {
|
|
85
|
+
const hash = await executeSolanaTransaction(tx, this._provider);
|
|
86
|
+
return { hash };
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { LegacyNetworks } from '@rango-dev/wallets-core/legacy';
|
|
2
|
+
import type { ProviderAPI as EvmProviderApi } from '@rango-dev/wallets-core/namespaces/evm';
|
|
3
|
+
import type { WalletWithFeatures as StandardWalletWithFeatures } from '@wallet-standard/base';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type SolanaSignMessageFeature,
|
|
7
|
+
type SolanaSignTransactionFeature,
|
|
8
|
+
} from '@solana/wallet-standard-features';
|
|
9
|
+
import {
|
|
10
|
+
type StandardConnectFeature,
|
|
11
|
+
type StandardEventsFeature,
|
|
12
|
+
} from '@wallet-standard/features';
|
|
13
|
+
|
|
14
|
+
export type WalletStandardSolanaInstance = StandardWalletWithFeatures<
|
|
15
|
+
StandardConnectFeature &
|
|
16
|
+
StandardEventsFeature &
|
|
17
|
+
SolanaSignTransactionFeature &
|
|
18
|
+
SolanaSignMessageFeature
|
|
19
|
+
>;
|
|
20
|
+
export type MetamaskEvmProviderApi = EvmProviderApi & {
|
|
21
|
+
isMetaMask?: boolean;
|
|
22
|
+
isBraveWallet?: boolean;
|
|
23
|
+
_events?: boolean;
|
|
24
|
+
_state?: boolean;
|
|
25
|
+
isApexWallet?: boolean;
|
|
26
|
+
isAvalanche?: boolean;
|
|
27
|
+
isBitKeep?: boolean;
|
|
28
|
+
isBlockWallet?: boolean;
|
|
29
|
+
isCoin98?: boolean;
|
|
30
|
+
isFordefi?: boolean;
|
|
31
|
+
__XDEFI?: boolean;
|
|
32
|
+
isMathWallet?: boolean;
|
|
33
|
+
isOkxWallet?: boolean;
|
|
34
|
+
isOKExWallet?: boolean;
|
|
35
|
+
isOneInchIOSWallet?: boolean;
|
|
36
|
+
isOneInchAndroidWallet?: boolean;
|
|
37
|
+
isOpera?: boolean;
|
|
38
|
+
isPortal?: boolean;
|
|
39
|
+
isRabby?: boolean;
|
|
40
|
+
isDefiant?: boolean;
|
|
41
|
+
isTokenPocket?: boolean;
|
|
42
|
+
isTokenary?: boolean;
|
|
43
|
+
isZeal?: boolean;
|
|
44
|
+
isZerion?: boolean;
|
|
45
|
+
isSafePal?: boolean;
|
|
46
|
+
};
|
|
47
|
+
export type ProviderObject = {
|
|
48
|
+
[LegacyNetworks.ETHEREUM]: MetamaskEvmProviderApi;
|
|
49
|
+
[LegacyNetworks.SOLANA]: WalletStandardSolanaInstance;
|
|
50
|
+
};
|
|
51
|
+
export type Provider = Map<
|
|
52
|
+
keyof ProviderObject,
|
|
53
|
+
ProviderObject[keyof ProviderObject]
|
|
54
|
+
>;
|