@meshconnect/web-link-sdk 3.2.14 → 3.2.15

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 (101) hide show
  1. package/jest.setup.ts +4 -0
  2. package/package.json +20 -8
  3. package/src/Link.test.ts +434 -0
  4. package/src/Link.ts +491 -0
  5. package/src/index.ts +3 -0
  6. package/src/utils/__snapshots__/popup.test.ts.snap +89 -0
  7. package/src/utils/connectors/evm/chainConfigs.ts +120 -0
  8. package/src/utils/connectors/evm/chainSwitching.ts +165 -0
  9. package/src/utils/connectors/evm/index.ts +8 -0
  10. package/src/utils/connectors/evm/provider.ts +22 -0
  11. package/src/utils/connectors/evm/signing.ts +39 -0
  12. package/src/utils/connectors/evm/transactions.ts +356 -0
  13. package/src/utils/connectors/evm/types.ts +63 -0
  14. package/src/utils/connectors/evm/walletConnection.ts +140 -0
  15. package/src/utils/connectors/evm/walletDiscovery.ts +67 -0
  16. package/src/utils/connectors/solana/connection.ts +69 -0
  17. package/src/utils/connectors/solana/index.ts +5 -0
  18. package/src/utils/connectors/solana/providerDiscovery.ts +153 -0
  19. package/src/utils/connectors/solana/signing.ts +18 -0
  20. package/src/utils/connectors/solana/transaction.ts +382 -0
  21. package/src/utils/connectors/solana/types.ts +66 -0
  22. package/{utils/event-types.js → src/utils/event-types.test.ts} +15 -5
  23. package/src/utils/event-types.ts +350 -0
  24. package/src/utils/popup.test.ts +50 -0
  25. package/src/utils/popup.ts +123 -0
  26. package/src/utils/sdk-specs.test.ts +18 -0
  27. package/src/utils/sdk-specs.ts +7 -0
  28. package/src/utils/style.test.ts +33 -0
  29. package/src/utils/style.ts +15 -0
  30. package/src/utils/types.ts +270 -0
  31. package/src/utils/version.ts +1 -0
  32. package/src/utils/wallet/EVMWalletStrategy.ts +176 -0
  33. package/src/utils/wallet/SolanaWalletStrategy.ts +207 -0
  34. package/src/utils/wallet/WalletStrategy.ts +99 -0
  35. package/src/utils/wallet/WalletStrategyFactory.ts +46 -0
  36. package/src/utils/wallet/__tests__/EVMWalletStrategy.test.ts +233 -0
  37. package/src/utils/wallet/__tests__/SolanaWalletStrategy.test.ts +253 -0
  38. package/src/utils/wallet/__tests__/WalletStrategy.test.ts +77 -0
  39. package/src/utils/wallet/__tests__/WalletStrategyFactory.test.ts +65 -0
  40. package/src/utils/wallet/index.ts +4 -0
  41. package/src/utils/wallet-browser-event-types.ts +190 -0
  42. package/tools/copy.js +26 -0
  43. package/tools/update-version.js +10 -0
  44. package/tsconfig.json +14 -0
  45. package/Link.d.ts +0 -2
  46. package/Link.js +0 -530
  47. package/index.d.ts +0 -3
  48. package/index.js +0 -3
  49. package/utils/connectors/evm/chainConfigs.d.ts +0 -2
  50. package/utils/connectors/evm/chainConfigs.js +0 -115
  51. package/utils/connectors/evm/chainSwitching.d.ts +0 -15
  52. package/utils/connectors/evm/chainSwitching.js +0 -242
  53. package/utils/connectors/evm/index.d.ts +0 -8
  54. package/utils/connectors/evm/index.js +0 -8
  55. package/utils/connectors/evm/provider.d.ts +0 -6
  56. package/utils/connectors/evm/provider.js +0 -13
  57. package/utils/connectors/evm/signing.d.ts +0 -1
  58. package/utils/connectors/evm/signing.js +0 -78
  59. package/utils/connectors/evm/transactions.d.ts +0 -28
  60. package/utils/connectors/evm/transactions.js +0 -381
  61. package/utils/connectors/evm/types.d.ts +0 -57
  62. package/utils/connectors/evm/types.js +0 -1
  63. package/utils/connectors/evm/walletConnection.d.ts +0 -20
  64. package/utils/connectors/evm/walletConnection.js +0 -160
  65. package/utils/connectors/evm/walletDiscovery.d.ts +0 -10
  66. package/utils/connectors/evm/walletDiscovery.js +0 -55
  67. package/utils/connectors/solana/connection.d.ts +0 -4
  68. package/utils/connectors/solana/connection.js +0 -108
  69. package/utils/connectors/solana/index.d.ts +0 -5
  70. package/utils/connectors/solana/index.js +0 -5
  71. package/utils/connectors/solana/providerDiscovery.d.ts +0 -3
  72. package/utils/connectors/solana/providerDiscovery.js +0 -127
  73. package/utils/connectors/solana/signing.d.ts +0 -1
  74. package/utils/connectors/solana/signing.js +0 -59
  75. package/utils/connectors/solana/transaction.d.ts +0 -17
  76. package/utils/connectors/solana/transaction.js +0 -362
  77. package/utils/connectors/solana/types.d.ts +0 -71
  78. package/utils/connectors/solana/types.js +0 -8
  79. package/utils/event-types.d.ts +0 -233
  80. package/utils/popup.d.ts +0 -3
  81. package/utils/popup.js +0 -36
  82. package/utils/sdk-specs.d.ts +0 -5
  83. package/utils/sdk-specs.js +0 -6
  84. package/utils/style.d.ts +0 -3
  85. package/utils/style.js +0 -13
  86. package/utils/types.d.ts +0 -234
  87. package/utils/types.js +0 -1
  88. package/utils/version.d.ts +0 -1
  89. package/utils/version.js +0 -1
  90. package/utils/wallet/EVMWalletStrategy.d.ts +0 -31
  91. package/utils/wallet/EVMWalletStrategy.js +0 -265
  92. package/utils/wallet/SolanaWalletStrategy.d.ts +0 -33
  93. package/utils/wallet/SolanaWalletStrategy.js +0 -293
  94. package/utils/wallet/WalletStrategy.d.ts +0 -61
  95. package/utils/wallet/WalletStrategy.js +0 -25
  96. package/utils/wallet/WalletStrategyFactory.d.ts +0 -15
  97. package/utils/wallet/WalletStrategyFactory.js +0 -31
  98. package/utils/wallet/index.d.ts +0 -4
  99. package/utils/wallet/index.js +0 -4
  100. package/utils/wallet-browser-event-types.d.ts +0 -116
  101. package/utils/wallet-browser-event-types.js +0 -17
@@ -0,0 +1,99 @@
1
+ import {
2
+ WalletBrowserPayload,
3
+ SignRequestPayload,
4
+ ChainSwitchPayload,
5
+ TransferPayload,
6
+ SmartContractPayload,
7
+ DisconnectPayload,
8
+ TransactionBatchPayload,
9
+ WalletCapabilitiesPayload,
10
+ SolanaTransferWithInstructionsPayload
11
+ } from '../types'
12
+
13
+ export interface WalletStrategy {
14
+ connect(payload: WalletBrowserPayload): Promise<{
15
+ accounts: string[]
16
+ chainId: string | number
17
+ isConnected: boolean
18
+ }>
19
+ disconnect(payload: DisconnectPayload): Promise<void>
20
+ signMessage(payload: SignRequestPayload): Promise<string>
21
+ switchChain(payload: ChainSwitchPayload): Promise<{
22
+ chainId: number | string
23
+ accounts: string[]
24
+ }>
25
+ sendNativeTransfer(payload: TransferPayload): Promise<string>
26
+ sendSmartContractInteraction(payload: SmartContractPayload): Promise<string>
27
+ sendNativeSmartContractInteraction(
28
+ payload: SmartContractPayload
29
+ ): Promise<string>
30
+ sendTransactionBatch(payload: TransactionBatchPayload): Promise<string>
31
+ getWalletCapabilities(payload: WalletCapabilitiesPayload): Promise<{
32
+ atomic: {
33
+ status: string
34
+ }
35
+ }>
36
+ sendTransactionWithInstructions(
37
+ payload: SolanaTransferWithInstructionsPayload
38
+ ): Promise<string>
39
+ getProviders(): { id: string; type: string; name?: string; icon?: string }[]
40
+ }
41
+
42
+ export abstract class BaseWalletStrategy implements WalletStrategy {
43
+ abstract connect(payload: WalletBrowserPayload): Promise<{
44
+ accounts: string[]
45
+ chainId: string | number
46
+ isConnected: boolean
47
+ }>
48
+ abstract disconnect(payload: DisconnectPayload): Promise<void>
49
+ abstract signMessage(payload: SignRequestPayload): Promise<string>
50
+ abstract switchChain(payload: ChainSwitchPayload): Promise<{
51
+ chainId: number | string
52
+ accounts: string[]
53
+ }>
54
+ abstract sendNativeTransfer(payload: TransferPayload): Promise<string>
55
+ abstract sendSmartContractInteraction(
56
+ payload: SmartContractPayload
57
+ ): Promise<string>
58
+ abstract sendNativeSmartContractInteraction(
59
+ payload: SmartContractPayload
60
+ ): Promise<string>
61
+ abstract sendTransactionBatch(
62
+ payload: TransactionBatchPayload
63
+ ): Promise<string>
64
+ abstract getWalletCapabilities(
65
+ payload: WalletCapabilitiesPayload
66
+ ): Promise<{ atomic: { status: string } }>
67
+ abstract getProviders(): {
68
+ id: string
69
+ type: string
70
+ name?: string
71
+ icon?: string
72
+ }[]
73
+ abstract sendTransactionWithInstructions(
74
+ payload: SolanaTransferWithInstructionsPayload
75
+ ): Promise<string>
76
+
77
+ protected handleError(error: unknown, operation: string): Error {
78
+ console.error(`${operation} error:`, error)
79
+ if (error instanceof Error) {
80
+ return error
81
+ }
82
+ return new Error(`Failed to ${operation}`)
83
+ }
84
+
85
+ protected isUserRejection(error: any): boolean {
86
+ if (!error) return false
87
+
88
+ const message = error.message?.toLowerCase() || ''
89
+ //4001 - user reject, -32603 internal error
90
+ const errorCodes = [4001, -32603]
91
+
92
+ return (
93
+ message.includes('user rejected') ||
94
+ message.includes('declined') ||
95
+ message.includes('cancelled') ||
96
+ errorCodes.includes(error.code)
97
+ )
98
+ }
99
+ }
@@ -0,0 +1,46 @@
1
+ import { WalletStrategy } from './WalletStrategy'
2
+ import { EVMWalletStrategy } from './EVMWalletStrategy'
3
+ import { SolanaWalletStrategy } from './SolanaWalletStrategy'
4
+
5
+ export type NetworkType = 'evm' | 'solana'
6
+
7
+ export class WalletStrategyFactory {
8
+ private static instance: WalletStrategyFactory
9
+ private strategies: Map<NetworkType, WalletStrategy>
10
+
11
+ private constructor() {
12
+ this.strategies = new Map()
13
+ this.strategies.set('evm', new EVMWalletStrategy())
14
+ this.strategies.set('solana', new SolanaWalletStrategy())
15
+ }
16
+
17
+ public static getInstance(): WalletStrategyFactory {
18
+ if (!WalletStrategyFactory.instance) {
19
+ WalletStrategyFactory.instance = new WalletStrategyFactory()
20
+ }
21
+ return WalletStrategyFactory.instance
22
+ }
23
+
24
+ public getStrategy(networkType: NetworkType): WalletStrategy {
25
+ const strategy = this.strategies.get(networkType)
26
+ if (!strategy) {
27
+ throw new Error(`No strategy found for network type: ${networkType}`)
28
+ }
29
+ return strategy
30
+ }
31
+
32
+ public getAllProviders() {
33
+ const allProviders: {
34
+ id: string
35
+ type: string
36
+ name?: string
37
+ icon?: string
38
+ }[] = []
39
+
40
+ this.strategies.forEach(strategy => {
41
+ allProviders.push(...strategy.getProviders())
42
+ })
43
+
44
+ return allProviders
45
+ }
46
+ }
@@ -0,0 +1,233 @@
1
+ import { EVMWalletStrategy } from '../EVMWalletStrategy'
2
+ import * as evmConnectors from '../../connectors/evm'
3
+
4
+ jest.mock('../../connectors/evm', () => ({
5
+ connectToEVMWallet: jest.fn(),
6
+ disconnectFromEVMWallet: jest.fn(),
7
+ signEVMMessage: jest.fn(),
8
+ sendEVMTransaction: jest.fn(),
9
+ sendEVMTokenTransaction: jest.fn(),
10
+ switchEVMChain: jest.fn(),
11
+ findAvailableProviders: jest.fn()
12
+ }))
13
+
14
+ describe('EVMWalletStrategy', () => {
15
+ let strategy: EVMWalletStrategy
16
+
17
+ beforeEach(() => {
18
+ strategy = new EVMWalletStrategy()
19
+ jest.clearAllMocks()
20
+ })
21
+
22
+ describe('connect', () => {
23
+ const mockPayload = {
24
+ integrationName: 'MetaMask',
25
+ targetChainId: '1'
26
+ }
27
+
28
+ it('should successfully connect to wallet', async () => {
29
+ const mockResult = {
30
+ accounts: ['0x123'],
31
+ chainId: 1,
32
+ isConnected: true
33
+ }
34
+ ;(evmConnectors.connectToEVMWallet as jest.Mock).mockResolvedValue(
35
+ mockResult
36
+ )
37
+
38
+ const result = await strategy.connect(mockPayload)
39
+ expect(result).toEqual(mockResult)
40
+ expect(evmConnectors.connectToEVMWallet).toHaveBeenCalledWith(
41
+ mockPayload.integrationName,
42
+ parseInt(mockPayload.targetChainId)
43
+ )
44
+ })
45
+
46
+ it('should handle connection error', async () => {
47
+ const mockError = new Error('Connection failed')
48
+ ;(evmConnectors.connectToEVMWallet as jest.Mock).mockResolvedValue(
49
+ mockError
50
+ )
51
+
52
+ await expect(strategy.connect(mockPayload)).rejects.toThrow(
53
+ 'Connection failed'
54
+ )
55
+ })
56
+ })
57
+
58
+ describe('disconnect', () => {
59
+ it('should successfully disconnect', async () => {
60
+ await strategy.disconnect({ walletName: 'MetaMask' })
61
+ expect(evmConnectors.disconnectFromEVMWallet).toHaveBeenCalledWith(
62
+ 'MetaMask'
63
+ )
64
+ })
65
+
66
+ it('should handle disconnect error', async () => {
67
+ const mockError = new Error('Disconnect failed')
68
+ ;(evmConnectors.disconnectFromEVMWallet as jest.Mock).mockResolvedValue(
69
+ mockError
70
+ )
71
+
72
+ await expect(
73
+ strategy.disconnect({ walletName: 'MetaMask' })
74
+ ).rejects.toThrow('Disconnect failed')
75
+ })
76
+ })
77
+
78
+ describe('signMessage', () => {
79
+ const mockPayload = {
80
+ walletName: 'MetaMask',
81
+ address: '0x123',
82
+ message: 'Test message'
83
+ }
84
+
85
+ it('should successfully sign message', async () => {
86
+ const mockSignature = '0xsignature'
87
+ ;(evmConnectors.signEVMMessage as jest.Mock).mockResolvedValue(
88
+ mockSignature
89
+ )
90
+
91
+ const result = await strategy.signMessage(mockPayload)
92
+ expect(result).toBe(mockSignature)
93
+ expect(evmConnectors.signEVMMessage).toHaveBeenCalledWith(
94
+ mockPayload.walletName,
95
+ mockPayload.address,
96
+ mockPayload.message
97
+ )
98
+ })
99
+
100
+ it('should handle signing error', async () => {
101
+ const mockError = new Error('Signing failed')
102
+ ;(evmConnectors.signEVMMessage as jest.Mock).mockResolvedValue(mockError)
103
+
104
+ await expect(strategy.signMessage(mockPayload)).rejects.toThrow(
105
+ 'Signing failed'
106
+ )
107
+ })
108
+ })
109
+
110
+ describe('switchChain', () => {
111
+ const mockPayload = {
112
+ chainId: 137 // Polygon network
113
+ }
114
+
115
+ it('should successfully switch chain', async () => {
116
+ const mockResult = {
117
+ chainId: 137,
118
+ accounts: ['0x123']
119
+ }
120
+ ;(evmConnectors.switchEVMChain as jest.Mock).mockResolvedValue(mockResult)
121
+
122
+ const result = await strategy.switchChain(mockPayload)
123
+ expect(result).toEqual(mockResult)
124
+ expect(evmConnectors.switchEVMChain).toHaveBeenCalledWith(
125
+ mockPayload.chainId
126
+ )
127
+ })
128
+
129
+ it('should handle chain switch error', async () => {
130
+ const mockError = new Error('Chain switch failed')
131
+ ;(evmConnectors.switchEVMChain as jest.Mock).mockResolvedValue(mockError)
132
+
133
+ await expect(strategy.switchChain(mockPayload)).rejects.toThrow(
134
+ 'Chain switch failed'
135
+ )
136
+ })
137
+ })
138
+
139
+ describe('sendNativeTransfer', () => {
140
+ const mockPayload = {
141
+ toAddress: '0x456',
142
+ amount: 1,
143
+ account: '0x123',
144
+ decimalPlaces: 18,
145
+ chainId: 1,
146
+ network: 'ethereum',
147
+ gasLimit: 21000,
148
+ maxFeePerGas: 50000000000,
149
+ maxPriorityFeePerGas: 1500000000
150
+ }
151
+
152
+ it('should successfully send native transfer', async () => {
153
+ const mockTxHash = '0xtxhash'
154
+ ;(evmConnectors.sendEVMTransaction as jest.Mock).mockResolvedValue(
155
+ mockTxHash
156
+ )
157
+
158
+ const result = await strategy.sendNativeTransfer(mockPayload)
159
+ expect(result).toBe(mockTxHash)
160
+ expect(evmConnectors.sendEVMTransaction).toHaveBeenCalledWith(
161
+ mockPayload.toAddress,
162
+ BigInt(mockPayload.amount * Math.pow(10, mockPayload.decimalPlaces)),
163
+ mockPayload.account,
164
+ mockPayload.gasLimit,
165
+ mockPayload.maxFeePerGas,
166
+ mockPayload.maxPriorityFeePerGas
167
+ )
168
+ })
169
+
170
+ it('should handle transfer error', async () => {
171
+ const mockError = new Error('Transfer failed')
172
+ ;(evmConnectors.sendEVMTransaction as jest.Mock).mockResolvedValue(
173
+ mockError
174
+ )
175
+
176
+ await expect(strategy.sendNativeTransfer(mockPayload)).rejects.toThrow(
177
+ 'Transfer failed'
178
+ )
179
+ })
180
+ })
181
+
182
+ describe('sendSmartContractInteraction', () => {
183
+ const mockPayload = {
184
+ address: '0xcontract',
185
+ abi: '["function transfer(address to, uint256 amount)"]',
186
+ functionName: 'transfer',
187
+ args: ['0x456', '1000000000000000000'],
188
+ account: '0x123'
189
+ }
190
+
191
+ it('should successfully send contract interaction', async () => {
192
+ const mockTxHash = '0xtxhash'
193
+ ;(evmConnectors.sendEVMTokenTransaction as jest.Mock).mockResolvedValue(
194
+ mockTxHash
195
+ )
196
+
197
+ const result = await strategy.sendSmartContractInteraction(mockPayload)
198
+ expect(result).toBe(mockTxHash)
199
+ expect(evmConnectors.sendEVMTokenTransaction).toHaveBeenCalledWith(
200
+ mockPayload.address,
201
+ JSON.parse(mockPayload.abi),
202
+ mockPayload.functionName,
203
+ mockPayload.args,
204
+ mockPayload.account
205
+ )
206
+ })
207
+
208
+ it('should handle contract interaction error', async () => {
209
+ const mockError = new Error('Contract interaction failed')
210
+ ;(evmConnectors.sendEVMTokenTransaction as jest.Mock).mockResolvedValue(
211
+ mockError
212
+ )
213
+
214
+ await expect(
215
+ strategy.sendSmartContractInteraction(mockPayload)
216
+ ).rejects.toThrow('Contract interaction failed')
217
+ })
218
+ })
219
+
220
+ describe('getProviders', () => {
221
+ it('should return available providers with correct type', () => {
222
+ const mockProviders = [
223
+ { id: 'metamask', name: 'MetaMask', icon: 'icon-url' }
224
+ ]
225
+ ;(evmConnectors.findAvailableProviders as jest.Mock).mockReturnValue(
226
+ mockProviders
227
+ )
228
+
229
+ const result = strategy.getProviders()
230
+ expect(result).toEqual([{ ...mockProviders[0], type: 'evm' }])
231
+ })
232
+ })
233
+ })
@@ -0,0 +1,253 @@
1
+ import { SolanaWalletStrategy } from '../SolanaWalletStrategy'
2
+ import * as solanaConnectors from '../../connectors/solana'
3
+
4
+ jest.mock('../../connectors/solana', () => ({
5
+ connectToSolanaWallet: jest.fn(),
6
+ disconnectFromSolanaWallet: jest.fn(),
7
+ signSolanaMessage: jest.fn(),
8
+ sendSOLTransaction: jest.fn(),
9
+ findAvailableSolanaProviders: jest.fn(),
10
+ getSolanaProvider: jest.fn()
11
+ }))
12
+
13
+ describe('SolanaWalletStrategy', () => {
14
+ let strategy: SolanaWalletStrategy
15
+
16
+ beforeEach(() => {
17
+ strategy = new SolanaWalletStrategy()
18
+ jest.clearAllMocks()
19
+ })
20
+
21
+ describe('connect', () => {
22
+ const mockPayload = {
23
+ integrationName: 'Phantom'
24
+ }
25
+
26
+ it('should successfully connect to wallet', async () => {
27
+ const mockResult = {
28
+ accounts: ['solana_address'],
29
+ chainId: '101',
30
+ isConnected: true
31
+ }
32
+ ;(solanaConnectors.connectToSolanaWallet as jest.Mock).mockResolvedValue(
33
+ mockResult
34
+ )
35
+
36
+ const result = await strategy.connect(mockPayload)
37
+ expect(result).toEqual(mockResult)
38
+ expect(solanaConnectors.connectToSolanaWallet).toHaveBeenCalledWith(
39
+ mockPayload
40
+ )
41
+ })
42
+
43
+ it('should handle connection error', async () => {
44
+ const mockError = new Error('Connection failed')
45
+ ;(solanaConnectors.connectToSolanaWallet as jest.Mock).mockResolvedValue(
46
+ mockError
47
+ )
48
+
49
+ await expect(strategy.connect(mockPayload)).rejects.toThrow(
50
+ 'Connection failed'
51
+ )
52
+ })
53
+ })
54
+
55
+ describe('disconnect', () => {
56
+ it('should successfully disconnect', async () => {
57
+ await strategy.disconnect({ walletName: 'Phantom' })
58
+ expect(solanaConnectors.disconnectFromSolanaWallet).toHaveBeenCalledWith(
59
+ 'Phantom'
60
+ )
61
+ })
62
+
63
+ it('should handle disconnect error', async () => {
64
+ const mockError = new Error('Disconnect failed')
65
+ ;(
66
+ solanaConnectors.disconnectFromSolanaWallet as jest.Mock
67
+ ).mockResolvedValue(mockError)
68
+
69
+ await expect(
70
+ strategy.disconnect({ walletName: 'Phantom' })
71
+ ).rejects.toThrow('Disconnect failed')
72
+ })
73
+ })
74
+
75
+ describe('signMessage', () => {
76
+ const mockPayload = {
77
+ walletName: 'Phantom',
78
+ message: 'Test message',
79
+ address: 'solana_address'
80
+ }
81
+
82
+ it('should successfully sign message', async () => {
83
+ const mockSignature = 'solana_signature'
84
+ ;(solanaConnectors.signSolanaMessage as jest.Mock).mockResolvedValue(
85
+ mockSignature
86
+ )
87
+
88
+ const result = await strategy.signMessage(mockPayload)
89
+ expect(result).toBe(mockSignature)
90
+ expect(solanaConnectors.signSolanaMessage).toHaveBeenCalledWith(
91
+ mockPayload.walletName,
92
+ mockPayload.message
93
+ )
94
+ })
95
+
96
+ it('should handle signing error', async () => {
97
+ const mockError = new Error('Signing failed')
98
+ ;(solanaConnectors.signSolanaMessage as jest.Mock).mockResolvedValue(
99
+ mockError
100
+ )
101
+
102
+ await expect(strategy.signMessage(mockPayload)).rejects.toThrow(
103
+ 'Signing failed'
104
+ )
105
+ })
106
+ })
107
+
108
+ describe('switchChain', () => {
109
+ it('should return fixed Solana chain ID', async () => {
110
+ const result = await strategy.switchChain({ chainId: 1 })
111
+ expect(result).toEqual({
112
+ chainId: '101',
113
+ accounts: []
114
+ })
115
+ })
116
+ })
117
+
118
+ describe('sendNativeTransfer', () => {
119
+ const mockPayload = {
120
+ toAddress: 'recipient_address',
121
+ amount: 1,
122
+ account: 'sender_address',
123
+ decimalPlaces: 9,
124
+ walletName: 'Phantom',
125
+ blockhash: 'test_blockhash',
126
+ chainId: 101,
127
+ network: 'solana',
128
+ tokenProgram: 'token_program_id'
129
+ }
130
+
131
+ it('should successfully send native transfer', async () => {
132
+ const mockTxHash = 'tx_hash'
133
+ ;(solanaConnectors.sendSOLTransaction as jest.Mock).mockResolvedValue(
134
+ mockTxHash
135
+ )
136
+
137
+ const result = await strategy.sendNativeTransfer(mockPayload)
138
+ expect(result).toBe(mockTxHash)
139
+ expect(solanaConnectors.sendSOLTransaction).toHaveBeenCalledWith({
140
+ toAddress: mockPayload.toAddress,
141
+ amount: BigInt(
142
+ mockPayload.amount * Math.pow(10, mockPayload.decimalPlaces)
143
+ ),
144
+ fromAddress: mockPayload.account,
145
+ blockhash: mockPayload.blockhash,
146
+ walletName: mockPayload.walletName
147
+ })
148
+ })
149
+
150
+ it('should handle transfer error', async () => {
151
+ const mockError = new Error('Transfer failed')
152
+ ;(solanaConnectors.sendSOLTransaction as jest.Mock).mockResolvedValue(
153
+ mockError
154
+ )
155
+
156
+ await expect(strategy.sendNativeTransfer(mockPayload)).rejects.toThrow(
157
+ 'Transfer failed'
158
+ )
159
+ })
160
+ })
161
+
162
+ describe('sendSmartContractInteraction', () => {
163
+ const mockPayload = {
164
+ address: 'token_mint_address',
165
+ abi: '[]',
166
+ functionName: 'transfer',
167
+ args: ['recipient_address', 1000000n, 6],
168
+ account: 'sender_address',
169
+ walletName: 'Phantom',
170
+ blockhash: 'test_blockhash',
171
+ tokenProgram: 'token_program_id'
172
+ }
173
+
174
+ it('should successfully send token transfer', async () => {
175
+ const mockTxHash = 'tx_hash'
176
+ const mockPublicKey = { toString: () => mockPayload.account }
177
+ ;(solanaConnectors.getSolanaProvider as jest.Mock).mockReturnValue({
178
+ publicKey: mockPublicKey
179
+ })
180
+ ;(solanaConnectors.sendSOLTransaction as jest.Mock).mockResolvedValue(
181
+ mockTxHash
182
+ )
183
+
184
+ const result = await strategy.sendSmartContractInteraction(mockPayload)
185
+ expect(result).toBe(mockTxHash)
186
+ expect(solanaConnectors.sendSOLTransaction).toHaveBeenCalledWith({
187
+ toAddress: mockPayload.args[0],
188
+ amount: mockPayload.args[1],
189
+ fromAddress: mockPayload.account,
190
+ blockhash: mockPayload.blockhash,
191
+ createATA: false,
192
+ tokenProgram: mockPayload.tokenProgram,
193
+ walletName: mockPayload.walletName,
194
+ tokenMint: mockPayload.address,
195
+ tokenDecimals: mockPayload.args[2]
196
+ })
197
+ })
198
+
199
+ it('should handle missing sender address', async () => {
200
+ ;(solanaConnectors.getSolanaProvider as jest.Mock).mockReturnValue({
201
+ publicKey: null
202
+ })
203
+
204
+ await expect(
205
+ strategy.sendSmartContractInteraction({
206
+ ...mockPayload,
207
+ account: ''
208
+ })
209
+ ).rejects.toThrow('Sender account address is required')
210
+ })
211
+
212
+ it('should handle missing blockhash', async () => {
213
+ const mockPublicKey = { toString: () => mockPayload.account }
214
+ ;(solanaConnectors.getSolanaProvider as jest.Mock).mockReturnValue({
215
+ publicKey: mockPublicKey
216
+ })
217
+
218
+ await expect(
219
+ strategy.sendSmartContractInteraction({
220
+ ...mockPayload,
221
+ blockhash: ''
222
+ })
223
+ ).rejects.toThrow('Blockhash is required for Solana transactions')
224
+ })
225
+ })
226
+
227
+ describe('getProviders', () => {
228
+ it('should return available providers with correct type', () => {
229
+ const mockProviders = {
230
+ phantom: true,
231
+ solflare: true
232
+ }
233
+ ;(
234
+ solanaConnectors.findAvailableSolanaProviders as jest.Mock
235
+ ).mockReturnValue(mockProviders)
236
+
237
+ const result = strategy.getProviders()
238
+ expect(result).toEqual([
239
+ { id: 'phantom', type: 'solana' },
240
+ { id: 'solflare', type: 'solana' }
241
+ ])
242
+ })
243
+
244
+ it('should handle empty providers', () => {
245
+ ;(
246
+ solanaConnectors.findAvailableSolanaProviders as jest.Mock
247
+ ).mockReturnValue({})
248
+
249
+ const result = strategy.getProviders()
250
+ expect(result).toEqual([])
251
+ })
252
+ })
253
+ })
@@ -0,0 +1,77 @@
1
+ import { BaseWalletStrategy } from '../WalletStrategy'
2
+
3
+ // Create a concrete implementation for testing
4
+ class TestWalletStrategy extends BaseWalletStrategy {
5
+ connect = jest.fn()
6
+ disconnect = jest.fn()
7
+ signMessage = jest.fn()
8
+ switchChain = jest.fn()
9
+ sendNativeTransfer = jest.fn()
10
+ sendSmartContractInteraction = jest.fn()
11
+ getProviders = jest.fn()
12
+ sendTransactionBatch = jest.fn()
13
+ }
14
+
15
+ describe('BaseWalletStrategy', () => {
16
+ let strategy: TestWalletStrategy
17
+
18
+ beforeEach(() => {
19
+ strategy = new TestWalletStrategy()
20
+ })
21
+
22
+ describe('handleError', () => {
23
+ it('should return the error if it is an Error instance', () => {
24
+ const error = new Error('Test error')
25
+ // @ts-expect-error - accessing protected method for testing
26
+ const result = strategy.handleError(error, 'test operation')
27
+ expect(result).toBe(error)
28
+ })
29
+
30
+ it('should create a new Error if input is not an Error instance', () => {
31
+ const errorString = 'string error'
32
+ // @ts-expect-error - accessing protected method for testing
33
+ const result = strategy.handleError(errorString, 'test operation')
34
+ expect(result).toBeInstanceOf(Error)
35
+ expect(result.message).toBe('Failed to test operation')
36
+ })
37
+ })
38
+
39
+ describe('isUserRejection', () => {
40
+ it('should return true for user rejection error code 4001', () => {
41
+ // @ts-expect-error - accessing protected method for testing
42
+ expect(strategy.isUserRejection({ code: 4001 })).toBe(true)
43
+ })
44
+
45
+ it('should return true for user rejection error code -32603', () => {
46
+ // @ts-expect-error - accessing protected method for testing
47
+ expect(strategy.isUserRejection({ code: -32603 })).toBe(true)
48
+ })
49
+
50
+ it('should return true for rejection message patterns', () => {
51
+ const rejectionMessages = [
52
+ { message: 'User rejected' },
53
+ { message: 'Transaction declined' },
54
+ { message: 'Operation cancelled' }
55
+ ]
56
+
57
+ rejectionMessages.forEach(error => {
58
+ // @ts-expect-error - accessing protected method for testing
59
+ expect(strategy.isUserRejection(error)).toBe(true)
60
+ })
61
+ })
62
+
63
+ it('should return false for other errors', () => {
64
+ const nonRejectionErrors = [
65
+ { code: 5000 },
66
+ { message: 'Network error' },
67
+ null,
68
+ undefined
69
+ ]
70
+
71
+ nonRejectionErrors.forEach(error => {
72
+ // @ts-expect-error - accessing protected method for testing
73
+ expect(strategy.isUserRejection(error)).toBe(false)
74
+ })
75
+ })
76
+ })
77
+ })