@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.
- package/jest.setup.ts +4 -0
- package/package.json +20 -8
- package/src/Link.test.ts +434 -0
- package/src/Link.ts +491 -0
- package/src/index.ts +3 -0
- package/src/utils/__snapshots__/popup.test.ts.snap +89 -0
- package/src/utils/connectors/evm/chainConfigs.ts +120 -0
- package/src/utils/connectors/evm/chainSwitching.ts +165 -0
- package/src/utils/connectors/evm/index.ts +8 -0
- package/src/utils/connectors/evm/provider.ts +22 -0
- package/src/utils/connectors/evm/signing.ts +39 -0
- package/src/utils/connectors/evm/transactions.ts +356 -0
- package/src/utils/connectors/evm/types.ts +63 -0
- package/src/utils/connectors/evm/walletConnection.ts +140 -0
- package/src/utils/connectors/evm/walletDiscovery.ts +67 -0
- package/src/utils/connectors/solana/connection.ts +69 -0
- package/src/utils/connectors/solana/index.ts +5 -0
- package/src/utils/connectors/solana/providerDiscovery.ts +153 -0
- package/src/utils/connectors/solana/signing.ts +18 -0
- package/src/utils/connectors/solana/transaction.ts +382 -0
- package/src/utils/connectors/solana/types.ts +66 -0
- package/{utils/event-types.js → src/utils/event-types.test.ts} +15 -5
- package/src/utils/event-types.ts +350 -0
- package/src/utils/popup.test.ts +50 -0
- package/src/utils/popup.ts +123 -0
- package/src/utils/sdk-specs.test.ts +18 -0
- package/src/utils/sdk-specs.ts +7 -0
- package/src/utils/style.test.ts +33 -0
- package/src/utils/style.ts +15 -0
- package/src/utils/types.ts +270 -0
- package/src/utils/version.ts +1 -0
- package/src/utils/wallet/EVMWalletStrategy.ts +176 -0
- package/src/utils/wallet/SolanaWalletStrategy.ts +207 -0
- package/src/utils/wallet/WalletStrategy.ts +99 -0
- package/src/utils/wallet/WalletStrategyFactory.ts +46 -0
- package/src/utils/wallet/__tests__/EVMWalletStrategy.test.ts +233 -0
- package/src/utils/wallet/__tests__/SolanaWalletStrategy.test.ts +253 -0
- package/src/utils/wallet/__tests__/WalletStrategy.test.ts +77 -0
- package/src/utils/wallet/__tests__/WalletStrategyFactory.test.ts +65 -0
- package/src/utils/wallet/index.ts +4 -0
- package/src/utils/wallet-browser-event-types.ts +190 -0
- package/tools/copy.js +26 -0
- package/tools/update-version.js +10 -0
- package/tsconfig.json +14 -0
- package/Link.d.ts +0 -2
- package/Link.js +0 -530
- package/index.d.ts +0 -3
- package/index.js +0 -3
- package/utils/connectors/evm/chainConfigs.d.ts +0 -2
- package/utils/connectors/evm/chainConfigs.js +0 -115
- package/utils/connectors/evm/chainSwitching.d.ts +0 -15
- package/utils/connectors/evm/chainSwitching.js +0 -242
- package/utils/connectors/evm/index.d.ts +0 -8
- package/utils/connectors/evm/index.js +0 -8
- package/utils/connectors/evm/provider.d.ts +0 -6
- package/utils/connectors/evm/provider.js +0 -13
- package/utils/connectors/evm/signing.d.ts +0 -1
- package/utils/connectors/evm/signing.js +0 -78
- package/utils/connectors/evm/transactions.d.ts +0 -28
- package/utils/connectors/evm/transactions.js +0 -381
- package/utils/connectors/evm/types.d.ts +0 -57
- package/utils/connectors/evm/types.js +0 -1
- package/utils/connectors/evm/walletConnection.d.ts +0 -20
- package/utils/connectors/evm/walletConnection.js +0 -160
- package/utils/connectors/evm/walletDiscovery.d.ts +0 -10
- package/utils/connectors/evm/walletDiscovery.js +0 -55
- package/utils/connectors/solana/connection.d.ts +0 -4
- package/utils/connectors/solana/connection.js +0 -108
- package/utils/connectors/solana/index.d.ts +0 -5
- package/utils/connectors/solana/index.js +0 -5
- package/utils/connectors/solana/providerDiscovery.d.ts +0 -3
- package/utils/connectors/solana/providerDiscovery.js +0 -127
- package/utils/connectors/solana/signing.d.ts +0 -1
- package/utils/connectors/solana/signing.js +0 -59
- package/utils/connectors/solana/transaction.d.ts +0 -17
- package/utils/connectors/solana/transaction.js +0 -362
- package/utils/connectors/solana/types.d.ts +0 -71
- package/utils/connectors/solana/types.js +0 -8
- package/utils/event-types.d.ts +0 -233
- package/utils/popup.d.ts +0 -3
- package/utils/popup.js +0 -36
- package/utils/sdk-specs.d.ts +0 -5
- package/utils/sdk-specs.js +0 -6
- package/utils/style.d.ts +0 -3
- package/utils/style.js +0 -13
- package/utils/types.d.ts +0 -234
- package/utils/types.js +0 -1
- package/utils/version.d.ts +0 -1
- package/utils/version.js +0 -1
- package/utils/wallet/EVMWalletStrategy.d.ts +0 -31
- package/utils/wallet/EVMWalletStrategy.js +0 -265
- package/utils/wallet/SolanaWalletStrategy.d.ts +0 -33
- package/utils/wallet/SolanaWalletStrategy.js +0 -293
- package/utils/wallet/WalletStrategy.d.ts +0 -61
- package/utils/wallet/WalletStrategy.js +0 -25
- package/utils/wallet/WalletStrategyFactory.d.ts +0 -15
- package/utils/wallet/WalletStrategyFactory.js +0 -31
- package/utils/wallet/index.d.ts +0 -4
- package/utils/wallet/index.js +0 -4
- package/utils/wallet-browser-event-types.d.ts +0 -116
- 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
|
+
})
|