@sodax/wallet-sdk-react 0.0.1-rc.2

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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -0
  3. package/dist/index.cjs +1589 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +15970 -0
  6. package/dist/index.d.ts +15970 -0
  7. package/dist/index.mjs +1513 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +74 -0
  10. package/src/SodaxWalletProvider.tsx +55 -0
  11. package/src/actions/getXChainType.ts +10 -0
  12. package/src/actions/getXService.ts +25 -0
  13. package/src/actions/index.ts +2 -0
  14. package/src/assets/wallets/hana.svg +6 -0
  15. package/src/assets/wallets/havah.svg +76 -0
  16. package/src/assets/wallets/keplr.svg +30 -0
  17. package/src/assets/wallets/metamask.svg +60 -0
  18. package/src/assets/wallets/phantom.svg +4 -0
  19. package/src/assets/wallets/sui.svg +20 -0
  20. package/src/constants/index.ts +1 -0
  21. package/src/constants/xChains.ts +164 -0
  22. package/src/core/XConnector.ts +54 -0
  23. package/src/core/XService.ts +91 -0
  24. package/src/core/index.ts +2 -0
  25. package/src/hooks/index.ts +11 -0
  26. package/src/hooks/useEthereumChainId.ts +36 -0
  27. package/src/hooks/useEvmSwitchChain.ts +92 -0
  28. package/src/hooks/useWalletProvider.ts +149 -0
  29. package/src/hooks/useWalletProviderOptions.ts +51 -0
  30. package/src/hooks/useXAccount.ts +51 -0
  31. package/src/hooks/useXAccounts.ts +56 -0
  32. package/src/hooks/useXBalances.ts +66 -0
  33. package/src/hooks/useXConnect.ts +119 -0
  34. package/src/hooks/useXConnection.ts +72 -0
  35. package/src/hooks/useXConnectors.ts +56 -0
  36. package/src/hooks/useXDisconnect.ts +65 -0
  37. package/src/hooks/useXService.ts +8 -0
  38. package/src/index.ts +18 -0
  39. package/src/types/index.ts +44 -0
  40. package/src/useXWagmiStore.tsx +164 -0
  41. package/src/utils/index.ts +33 -0
  42. package/src/xchains/evm/EvmXConnector.ts +27 -0
  43. package/src/xchains/evm/EvmXService.ts +189 -0
  44. package/src/xchains/evm/index.ts +2 -0
  45. package/src/xchains/icon/IconHanaXConnector.ts +39 -0
  46. package/src/xchains/icon/IconXService.ts +115 -0
  47. package/src/xchains/icon/iconex/index.tsx +46 -0
  48. package/src/xchains/icon/index.ts +2 -0
  49. package/src/xchains/injective/InjectiveKelprXConnector.ts +37 -0
  50. package/src/xchains/injective/InjectiveMetamaskXConnector.ts +40 -0
  51. package/src/xchains/injective/InjectiveXService.ts +71 -0
  52. package/src/xchains/injective/index.ts +4 -0
  53. package/src/xchains/injective/utils.ts +17 -0
  54. package/src/xchains/solana/SolanaXConnector.ts +26 -0
  55. package/src/xchains/solana/SolanaXService.ts +50 -0
  56. package/src/xchains/solana/hooks/useAnchorProvider.tsx +9 -0
  57. package/src/xchains/solana/index.ts +2 -0
  58. package/src/xchains/stellar/CustomSorobanServer.ts +93 -0
  59. package/src/xchains/stellar/StellarWalletsKitXConnector.ts +45 -0
  60. package/src/xchains/stellar/StellarXService.ts +61 -0
  61. package/src/xchains/stellar/index.tsx +2 -0
  62. package/src/xchains/stellar/useStellarXConnectors.ts +30 -0
  63. package/src/xchains/stellar/utils.ts +49 -0
  64. package/src/xchains/sui/SuiXConnector.ts +28 -0
  65. package/src/xchains/sui/SuiXService.ts +56 -0
  66. package/src/xchains/sui/index.ts +2 -0
@@ -0,0 +1,56 @@
1
+ import type { ChainType } from '@sodax/types';
2
+ import { useWallets } from '@mysten/dapp-kit';
3
+ import { useWallet } from '@solana/wallet-adapter-react';
4
+ import { useMemo } from 'react';
5
+ import { useConnectors } from 'wagmi';
6
+ import type { XConnector } from '../core';
7
+ import { EvmXConnector } from '../xchains/evm';
8
+ import { SolanaXConnector } from '../xchains/solana';
9
+ import { useStellarXConnectors } from '../xchains/stellar/useStellarXConnectors';
10
+ import { SuiXConnector } from '../xchains/sui';
11
+ import { useXService } from './useXService';
12
+
13
+ /**
14
+ * Hook to retrieve available wallet connectors for a specific blockchain type.
15
+ *
16
+ * This hook aggregates wallet connectors from different blockchain ecosystems:
17
+ * - EVM: Uses wagmi connectors
18
+ * - Sui: Uses Sui wallet adapters
19
+ * - Stellar: Uses custom Stellar connectors
20
+ * - Solana: Uses Solana wallet adapters (filtered to installed wallets only)
21
+ *
22
+ * @param xChainType - The blockchain type to get connectors for ('EVM' | 'SUI' | 'STELLAR' | 'SOLANA')
23
+ * @returns An array of XConnector instances compatible with the specified chain type
24
+ */
25
+
26
+ export function useXConnectors(xChainType: ChainType | undefined): XConnector[] {
27
+ const xService = useXService(xChainType);
28
+ const evmConnectors = useConnectors();
29
+ const suiWallets = useWallets();
30
+ const { data: stellarXConnectors } = useStellarXConnectors();
31
+
32
+ const { wallets: solanaWallets } = useWallet();
33
+
34
+ const xConnectors = useMemo((): XConnector[] => {
35
+ if (!xChainType || !xService) {
36
+ return [];
37
+ }
38
+
39
+ switch (xChainType) {
40
+ case 'EVM':
41
+ return evmConnectors.map(connector => new EvmXConnector(connector));
42
+ case 'SUI':
43
+ return suiWallets.map(wallet => new SuiXConnector(wallet));
44
+ case 'STELLAR':
45
+ return stellarXConnectors || [];
46
+ case 'SOLANA':
47
+ return solanaWallets
48
+ .filter(wallet => wallet.readyState === 'Installed')
49
+ .map(wallet => new SolanaXConnector(wallet));
50
+ default:
51
+ return xService.getXConnectors();
52
+ }
53
+ }, [xService, xChainType, evmConnectors, suiWallets, stellarXConnectors, solanaWallets]);
54
+
55
+ return xConnectors;
56
+ }
@@ -0,0 +1,65 @@
1
+ import type { ChainType } from '@sodax/types';
2
+ import { useDisconnectWallet } from '@mysten/dapp-kit';
3
+ import { useWallet } from '@solana/wallet-adapter-react';
4
+ import { useCallback } from 'react';
5
+ import { useDisconnect } from 'wagmi';
6
+ import { getXService } from '../actions';
7
+ import { useXWagmiStore } from '../useXWagmiStore';
8
+
9
+ /**
10
+ * Hook for disconnecting from a specific blockchain wallet
11
+ *
12
+ * Handles disconnection logic for EVM, SUI, Solana and other supported chains.
13
+ * Clears connection state from XWagmiStore.
14
+ *
15
+ * @param {void} - No parameters required
16
+ * @returns {(xChainType: ChainType) => Promise<void>} Async function that disconnects from the specified chain
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const disconnect = useXDisconnect();
21
+ *
22
+ * const handleDisconnect = async (xChainType: ChainType) => {
23
+ * await disconnect(xChainType);
24
+ * };
25
+ * ```
26
+ */
27
+ export function useXDisconnect(): (xChainType: ChainType) => Promise<void> {
28
+ // Get connection state and disconnect handler from store
29
+ const xConnections = useXWagmiStore(state => state.xConnections);
30
+ const unsetXConnection = useXWagmiStore(state => state.unsetXConnection);
31
+
32
+ // Get chain-specific disconnect handlers
33
+ const { disconnectAsync } = useDisconnect();
34
+ const { mutateAsync: suiDisconnectAsync } = useDisconnectWallet();
35
+ const solanaWallet = useWallet();
36
+
37
+ return useCallback(
38
+ async (xChainType: ChainType) => {
39
+ // Handle disconnection based on chain type
40
+ switch (xChainType) {
41
+ case 'EVM':
42
+ await disconnectAsync();
43
+ break;
44
+ case 'SUI':
45
+ await suiDisconnectAsync();
46
+ break;
47
+ case 'SOLANA':
48
+ await solanaWallet.disconnect();
49
+ break;
50
+ default: {
51
+ // Handle other chain types
52
+ const xService = getXService(xChainType);
53
+ const xConnectorId = xConnections[xChainType]?.xConnectorId;
54
+ const xConnector = xConnectorId ? xService.getXConnectorById(xConnectorId) : undefined;
55
+ await xConnector?.disconnect();
56
+ break;
57
+ }
58
+ }
59
+
60
+ // Clear connection state from store
61
+ unsetXConnection(xChainType);
62
+ },
63
+ [xConnections, unsetXConnection, disconnectAsync, suiDisconnectAsync, solanaWallet],
64
+ );
65
+ }
@@ -0,0 +1,8 @@
1
+ import type { ChainType } from '@sodax/types';
2
+ import type { XService } from '../core';
3
+ import { useXWagmiStore } from '../useXWagmiStore';
4
+
5
+ export function useXService(xChainType: ChainType | undefined): XService | undefined {
6
+ const xService = useXWagmiStore(state => (xChainType ? state.xServices[xChainType] : undefined));
7
+ return xService;
8
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ export * from './actions';
2
+ export * from './constants';
3
+ export * from './core';
4
+
5
+ export * from './utils';
6
+
7
+ export * from './xchains/evm';
8
+ export * from './xchains/icon';
9
+ export * from './xchains/injective';
10
+ export * from './xchains/solana';
11
+ export * from './xchains/stellar';
12
+ export * from './xchains/sui';
13
+ export * from './hooks';
14
+ export * from './useXWagmiStore';
15
+ export * from './SodaxWalletProvider';
16
+
17
+ export * from './types';
18
+ export type * from './types';
@@ -0,0 +1,44 @@
1
+ import type { ChainType } from '@sodax/types';
2
+ import type { EvmChainId } from '@/xchains/evm/EvmXService';
3
+
4
+ export type XAccount = {
5
+ address: string | undefined;
6
+ xChainType: ChainType | undefined;
7
+ };
8
+
9
+ export type XConnection = {
10
+ xAccount: XAccount;
11
+ xConnectorId: string;
12
+ };
13
+
14
+ export type CurrencyKey = string;
15
+
16
+ export enum WalletId {
17
+ METAMASK = 'metamask',
18
+ HANA = 'hana',
19
+ PHANTOM = 'phantom',
20
+ SUI = 'sui',
21
+ KEPLR = 'keplr',
22
+ }
23
+
24
+ export type EVMConfig = {
25
+ chains: EvmChainId[];
26
+ };
27
+
28
+ export type SuiConfig = {
29
+ isMainnet: boolean;
30
+ };
31
+
32
+ export type SolanaConfig = {
33
+ endpoint: string;
34
+ };
35
+
36
+ export type XConfig = {
37
+ [key in ChainType]: key extends 'EVM'
38
+ ? EVMConfig
39
+ : key extends 'SUI'
40
+ ? SuiConfig
41
+ : key extends 'SOLANA'
42
+ ? SolanaConfig
43
+ : unknown;
44
+ };
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+
3
+ import type { ChainType } from '@sodax/types';
4
+ import type { XConfig } from './types';
5
+ import { useCurrentAccount, useCurrentWallet, useSuiClient } from '@mysten/dapp-kit';
6
+ import React, { useEffect } from 'react';
7
+ import { create } from 'zustand';
8
+ import { createJSONStorage, persist } from 'zustand/middleware';
9
+ import { immer } from 'zustand/middleware/immer';
10
+ import { getXService } from '.';
11
+ import type { XService } from './core';
12
+ import type { XConnection } from './types';
13
+ import { EvmXService } from './xchains/evm';
14
+ import { InjectiveMetamaskXConnector, InjectiveXService } from './xchains/injective';
15
+ import { SolanaXService } from './xchains/solana/SolanaXService';
16
+ import { StellarXService } from './xchains/stellar';
17
+ import { SuiXService } from './xchains/sui';
18
+ import { IconXService } from './xchains/icon';
19
+ import { IconHanaXConnector } from './xchains/icon/IconHanaXConnector';
20
+ import { useAnchorProvider } from './xchains/solana/hooks/useAnchorProvider';
21
+ import { useConnection, useWallet } from '@solana/wallet-adapter-react';
22
+
23
+ type XWagmiStore = {
24
+ xServices: Partial<Record<ChainType, XService>>;
25
+ xConnections: Partial<Record<ChainType, XConnection>>;
26
+
27
+ setXConnection: (xChainType: ChainType, xConnection: XConnection) => void;
28
+ unsetXConnection: (xChainType: ChainType) => void;
29
+ };
30
+
31
+ export const useXWagmiStore = create<XWagmiStore>()(
32
+ persist(
33
+ immer((set, get) => ({
34
+ xServices: {},
35
+ xConnections: {},
36
+ setXConnection: (xChainType: ChainType, xConnection: XConnection) => {
37
+ set(state => {
38
+ state.xConnections[xChainType] = xConnection;
39
+ });
40
+ },
41
+ unsetXConnection: (xChainType: ChainType) => {
42
+ set(state => {
43
+ delete state.xConnections[xChainType];
44
+ });
45
+ },
46
+ })),
47
+ {
48
+ name: 'xwagmi-store',
49
+ storage: createJSONStorage(() => localStorage),
50
+ partialize: state => ({ xConnections: state.xConnections }),
51
+
52
+ // TODO: better way to handle rehydration of xConnections?
53
+ onRehydrateStorage: state => {
54
+ console.log('hydration starts');
55
+
56
+ return (state, error) => {
57
+ if (state?.xConnections) {
58
+ console.log('rehydrating xConnections', state.xConnections);
59
+ Object.entries(state.xConnections).forEach(([xChainType, xConnection]) => {
60
+ const xConnector = getXService(xChainType as ChainType).getXConnectorById(xConnection.xConnectorId);
61
+ xConnector?.connect();
62
+ });
63
+ }
64
+ if (error) {
65
+ console.log('an error happened during hydration', error);
66
+ } else {
67
+ console.log('hydration finished');
68
+ }
69
+ };
70
+ },
71
+ },
72
+ ),
73
+ );
74
+
75
+ const initXServices = (config: XConfig) => {
76
+ const xServices = {};
77
+ Object.keys(config).forEach(key => {
78
+ const xChainType = key as ChainType;
79
+
80
+ switch (xChainType) {
81
+ case 'EVM':
82
+ if (config[xChainType]) {
83
+ xServices[xChainType] = EvmXService.getInstance();
84
+ xServices[xChainType].setXConnectors([]);
85
+ xServices[xChainType].setConfig(config[xChainType]);
86
+ }
87
+ break;
88
+ case 'INJECTIVE':
89
+ xServices[xChainType] = InjectiveXService.getInstance();
90
+ xServices[xChainType].setXConnectors([new InjectiveMetamaskXConnector()]);
91
+ break;
92
+ case 'STELLAR':
93
+ xServices[xChainType] = StellarXService.getInstance();
94
+ xServices[xChainType].setXConnectors([]);
95
+ break;
96
+ case 'SUI':
97
+ xServices[xChainType] = SuiXService.getInstance();
98
+ xServices[xChainType].setXConnectors([]);
99
+ break;
100
+ case 'SOLANA':
101
+ xServices[xChainType] = SolanaXService.getInstance();
102
+ xServices[xChainType].setXConnectors([]);
103
+ break;
104
+ case 'ICON':
105
+ xServices[xChainType] = IconXService.getInstance();
106
+ xServices[xChainType].setXConnectors([new IconHanaXConnector()]);
107
+ break;
108
+ default:
109
+ break;
110
+ }
111
+ });
112
+
113
+ return xServices;
114
+ };
115
+
116
+ export const initXWagmiStore = (config: XConfig) => {
117
+ useXWagmiStore.setState({
118
+ xServices: initXServices(config),
119
+ });
120
+ };
121
+
122
+ export const InitXWagmiStore = () => {
123
+ // sui
124
+ const suiClient = useSuiClient();
125
+ useEffect(() => {
126
+ if (suiClient) {
127
+ SuiXService.getInstance().suiClient = suiClient;
128
+ }
129
+ }, [suiClient]);
130
+ const { currentWallet: suiWallet } = useCurrentWallet();
131
+ useEffect(() => {
132
+ if (suiWallet) {
133
+ SuiXService.getInstance().suiWallet = suiWallet;
134
+ }
135
+ }, [suiWallet]);
136
+ const suiAccount = useCurrentAccount();
137
+ useEffect(() => {
138
+ if (suiAccount) {
139
+ SuiXService.getInstance().suiAccount = suiAccount;
140
+ }
141
+ }, [suiAccount]);
142
+
143
+ // solana
144
+ const { connection: solanaConnection } = useConnection();
145
+ const solanaWallet = useWallet();
146
+ const solanaProvider = useAnchorProvider();
147
+ useEffect(() => {
148
+ if (solanaConnection) {
149
+ SolanaXService.getInstance().connection = solanaConnection;
150
+ }
151
+ }, [solanaConnection]);
152
+ useEffect(() => {
153
+ if (solanaWallet) {
154
+ SolanaXService.getInstance().wallet = solanaWallet;
155
+ }
156
+ }, [solanaWallet]);
157
+ useEffect(() => {
158
+ if (solanaProvider) {
159
+ SolanaXService.getInstance().provider = solanaProvider;
160
+ }
161
+ }, [solanaProvider]);
162
+
163
+ return <></>;
164
+ };
@@ -0,0 +1,33 @@
1
+ import type { ChainId, XToken } from '@sodax/types';
2
+
3
+ export const isNativeToken = (xToken: XToken) => {
4
+ const nativeAddresses = [
5
+ 'cx0000000000000000000000000000000000000000',
6
+ '0x0000000000000000000000000000000000000000',
7
+ 'inj',
8
+ '0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI',
9
+ 'hx0000000000000000000000000000000000000000',
10
+ '11111111111111111111111111111111', // solana
11
+ 'CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA', // stellar,
12
+ ];
13
+
14
+ return nativeAddresses.includes(xToken.address);
15
+ };
16
+
17
+ // TODO: remove this? move to dapp-kit?
18
+ export const getWagmiChainId = (xChainId: ChainId): number => {
19
+ const xChainMap = {
20
+ '0xa869.fuji': 43113,
21
+ 'sonic-blaze': 57054,
22
+ sonic: 146,
23
+ '0xa86a.avax': 43114,
24
+ '0x38.bsc': 56,
25
+ '0xa4b1.arbitrum': 42161,
26
+ '0x2105.base': 8453,
27
+ '0xa.optimism': 10,
28
+ '0x89.polygon': 137,
29
+ hyper: 999,
30
+ lightlink: 1890,
31
+ };
32
+ return xChainMap[xChainId] ?? 0;
33
+ };
@@ -0,0 +1,27 @@
1
+ import { XConnector } from '@/core/XConnector';
2
+ import type { XAccount } from '@/types';
3
+ import type { Config, Connector } from 'wagmi';
4
+
5
+ export class EvmXConnector extends XConnector {
6
+ connector: Connector;
7
+
8
+ constructor(connector: Connector) {
9
+ super('EVM', connector.name, connector.id);
10
+ this.connector = connector;
11
+ }
12
+
13
+ async connect(): Promise<XAccount | undefined> {
14
+ return;
15
+ }
16
+
17
+ async disconnect(): Promise<void> {
18
+ return;
19
+ }
20
+
21
+ public get id() {
22
+ return this.connector.id;
23
+ }
24
+ public get icon() {
25
+ return this.connector.icon;
26
+ }
27
+ }
@@ -0,0 +1,189 @@
1
+ import { XService } from '@/core/XService';
2
+ import type { ChainId, XToken } from '@sodax/types';
3
+ import type { EVMConfig } from '@/types';
4
+ import { getWagmiChainId, isNativeToken } from '@/utils';
5
+
6
+ import { type Address, type PublicClient, type WalletClient, defineChain, erc20Abi } from 'viem';
7
+ import { getPublicClient, getWalletClient } from 'wagmi/actions';
8
+ import { createConfig, http, type Transport } from 'wagmi';
9
+ import {
10
+ mainnet,
11
+ avalanche,
12
+ base,
13
+ optimism,
14
+ polygon,
15
+ arbitrum,
16
+ bsc,
17
+ sonic,
18
+ nibiru,
19
+ lightlinkPhoenix,
20
+ } from 'wagmi/chains';
21
+
22
+ import {
23
+ AVALANCHE_MAINNET_CHAIN_ID,
24
+ ARBITRUM_MAINNET_CHAIN_ID,
25
+ BASE_MAINNET_CHAIN_ID,
26
+ BSC_MAINNET_CHAIN_ID,
27
+ SONIC_MAINNET_CHAIN_ID,
28
+ OPTIMISM_MAINNET_CHAIN_ID,
29
+ POLYGON_MAINNET_CHAIN_ID,
30
+ NIBIRU_MAINNET_CHAIN_ID,
31
+ HYPEREVM_MAINNET_CHAIN_ID,
32
+ LIGHTLINK_MAINNET_CHAIN_ID,
33
+ } from '@sodax/types';
34
+
35
+ // HyperEVM chain is not supported by viem, so we need to define it manually
36
+ export const hyper = /*#__PURE__*/ defineChain({
37
+ id: 999,
38
+ name: 'HyperEVM',
39
+ nativeCurrency: {
40
+ decimals: 18,
41
+ name: 'HYPE',
42
+ symbol: 'HYPE',
43
+ },
44
+ rpcUrls: {
45
+ default: { http: ['https://rpc.hyperliquid.xyz/evm'] },
46
+ },
47
+ blockExplorers: {
48
+ default: {
49
+ name: 'HyperEVMScan',
50
+ url: 'https://hyperevmscan.io/',
51
+ },
52
+ },
53
+ contracts: {
54
+ multicall3: {
55
+ address: '0xcA11bde05977b3631167028862bE2a173976CA11',
56
+ blockCreated: 13051,
57
+ },
58
+ },
59
+ });
60
+
61
+ const evmChainMap = {
62
+ [AVALANCHE_MAINNET_CHAIN_ID]: avalanche,
63
+ [ARBITRUM_MAINNET_CHAIN_ID]: arbitrum,
64
+ [BASE_MAINNET_CHAIN_ID]: base,
65
+ [BSC_MAINNET_CHAIN_ID]: bsc,
66
+ [SONIC_MAINNET_CHAIN_ID]: sonic,
67
+ [OPTIMISM_MAINNET_CHAIN_ID]: optimism,
68
+ [POLYGON_MAINNET_CHAIN_ID]: polygon,
69
+ [NIBIRU_MAINNET_CHAIN_ID]: nibiru,
70
+ [HYPEREVM_MAINNET_CHAIN_ID]: hyper,
71
+ [LIGHTLINK_MAINNET_CHAIN_ID]: lightlinkPhoenix,
72
+ } as const;
73
+
74
+ export type EvmChainId = keyof typeof evmChainMap;
75
+
76
+ export const getWagmiConfig = (chains: EvmChainId[]) => {
77
+ const mappedChains = chains.map(chain => evmChainMap[chain]);
78
+ const finalChains = mappedChains.length > 0 ? mappedChains : [mainnet];
79
+
80
+ const transports = finalChains.reduce(
81
+ (acc, chain) => {
82
+ acc[chain.id] = http();
83
+ return acc;
84
+ },
85
+ {} as Record<number, Transport>,
86
+ );
87
+
88
+ return createConfig({
89
+ chains: finalChains as [typeof mainnet, ...(typeof mainnet)[]],
90
+ transports,
91
+ // ssr: true,
92
+ });
93
+ };
94
+
95
+ /**
96
+ * Service class for handling EVM chain interactions.
97
+ * Implements singleton pattern and provides methods for wallet/chain operations.
98
+ */
99
+
100
+ export class EvmXService extends XService {
101
+ private static instance: EvmXService;
102
+ private config: EVMConfig | undefined;
103
+ private constructor() {
104
+ super('EVM');
105
+ }
106
+
107
+ getXConnectors() {
108
+ return [];
109
+ }
110
+
111
+ public static getInstance(): EvmXService {
112
+ if (!EvmXService.instance) {
113
+ EvmXService.instance = new EvmXService();
114
+ }
115
+ return EvmXService.instance;
116
+ }
117
+
118
+ public setConfig(config: EVMConfig) {
119
+ this.config = config;
120
+ }
121
+
122
+ getPublicClient(chainId: number): PublicClient | undefined {
123
+ if (!this.config) {
124
+ throw new Error('EvmXService: config is not initialized yet');
125
+ }
126
+
127
+ // @ts-ignore
128
+ return getPublicClient(getWagmiConfig(this.config.chains), { chainId });
129
+ }
130
+
131
+ public async getWalletClient(chainId: number): Promise<WalletClient> {
132
+ if (!this.config) {
133
+ throw new Error('EvmXService: config is not initialized yet');
134
+ }
135
+ return await getWalletClient(getWagmiConfig(this.config.chains), { chainId });
136
+ }
137
+
138
+ async getBalance(address: string | undefined, xToken: XToken, xChainId: ChainId): Promise<bigint> {
139
+ if (!address) return 0n;
140
+
141
+ const chainId = getWagmiChainId(xChainId);
142
+
143
+ if (isNativeToken(xToken)) {
144
+ const balance = await this.getPublicClient(chainId)?.getBalance({ address: address as Address });
145
+ return balance || 0n;
146
+ }
147
+
148
+ throw new Error(`Unsupported token: ${xToken.symbol}`);
149
+ }
150
+
151
+ async getBalances(address: string | undefined, xTokens: XToken[], xChainId: ChainId) {
152
+ if (!address) return {};
153
+
154
+ const balancePromises = xTokens
155
+ .filter(xToken => isNativeToken(xToken))
156
+ .map(async xToken => {
157
+ const balance = await this.getBalance(address, xToken, xChainId);
158
+ return { symbol: xToken.symbol, address: xToken.address, balance };
159
+ });
160
+
161
+ const balances = await Promise.all(balancePromises);
162
+ const tokenMap = balances.reduce((map, { address, balance }) => {
163
+ if (balance) map[address] = balance;
164
+ return map;
165
+ }, {});
166
+
167
+ const nonNativeXTokens = xTokens.filter(xToken => !isNativeToken(xToken));
168
+ const result = await this.getPublicClient(getWagmiChainId(xChainId))?.multicall({
169
+ contracts: nonNativeXTokens.map(token => ({
170
+ abi: erc20Abi,
171
+ address: token.address as `0x${string}`,
172
+ functionName: 'balanceOf',
173
+ args: [address],
174
+ chainId: getWagmiChainId(xChainId),
175
+ })),
176
+ });
177
+
178
+ return nonNativeXTokens
179
+ .map((token, index) => ({
180
+ symbol: token.symbol,
181
+ address: token.address,
182
+ balance: result?.[index]?.result?.toString() || '0',
183
+ }))
184
+ .reduce((acc, balance) => {
185
+ acc[balance.address] = balance.balance;
186
+ return acc;
187
+ }, tokenMap);
188
+ }
189
+ }
@@ -0,0 +1,2 @@
1
+ export { EvmXService } from './EvmXService';
2
+ export { EvmXConnector } from './EvmXConnector';
@@ -0,0 +1,39 @@
1
+ import type { XAccount } from '@/types';
2
+ import { ICONexRequestEventType, ICONexResponseEventType, request } from './iconex';
3
+
4
+ import { XConnector } from '@/core/XConnector';
5
+
6
+ export class IconHanaXConnector extends XConnector {
7
+ constructor() {
8
+ super('ICON', 'Hana Wallet', 'hana');
9
+ }
10
+
11
+ async connect(): Promise<XAccount | undefined> {
12
+ const { hanaWallet } = window as any;
13
+ if (window && !hanaWallet && !hanaWallet?.isAvailable) {
14
+ window.open('https://chromewebstore.google.com/detail/hana-wallet/jfdlamikmbghhapbgfoogdffldioobgl', '_blank');
15
+ return;
16
+ }
17
+
18
+ const detail = await request({
19
+ type: ICONexRequestEventType.REQUEST_ADDRESS,
20
+ });
21
+
22
+ if (detail?.type === ICONexResponseEventType.RESPONSE_ADDRESS) {
23
+ return {
24
+ address: detail?.payload,
25
+ xChainType: this.xChainType,
26
+ };
27
+ }
28
+
29
+ return undefined;
30
+ }
31
+
32
+ async disconnect(): Promise<void> {
33
+ console.log('HanaIconXConnector disconnected');
34
+ }
35
+
36
+ public get icon() {
37
+ return 'https://raw.githubusercontent.com/balancednetwork/icons/master/wallets/hana.svg';
38
+ }
39
+ }