@meshconnect/uwc-injected-connector 0.2.0
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/dist/eip6963-discovery.d.ts +47 -0
- package/dist/eip6963-discovery.d.ts.map +1 -0
- package/dist/eip6963-discovery.js +58 -0
- package/dist/eip6963-discovery.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/injected-connector.d.ts +40 -0
- package/dist/injected-connector.d.ts.map +1 -0
- package/dist/injected-connector.js +183 -0
- package/dist/injected-connector.js.map +1 -0
- package/dist/injected-connector.old.d.ts +44 -0
- package/dist/injected-connector.old.d.ts.map +1 -0
- package/dist/injected-connector.old.js +491 -0
- package/dist/injected-connector.old.js.map +1 -0
- package/dist/providers/evm-provider.d.ts +18 -0
- package/dist/providers/evm-provider.d.ts.map +1 -0
- package/dist/providers/evm-provider.js +86 -0
- package/dist/providers/evm-provider.js.map +1 -0
- package/dist/providers/solana-provider.d.ts +22 -0
- package/dist/providers/solana-provider.d.ts.map +1 -0
- package/dist/providers/solana-provider.js +137 -0
- package/dist/providers/solana-provider.js.map +1 -0
- package/dist/services/connection-manager.d.ts +56 -0
- package/dist/services/connection-manager.d.ts.map +1 -0
- package/dist/services/connection-manager.js +303 -0
- package/dist/services/connection-manager.js.map +1 -0
- package/dist/services/ethereum-transaction-service.d.ts +28 -0
- package/dist/services/ethereum-transaction-service.d.ts.map +1 -0
- package/dist/services/ethereum-transaction-service.js +143 -0
- package/dist/services/ethereum-transaction-service.js.map +1 -0
- package/dist/services/ethereum-wallet-service.d.ts +55 -0
- package/dist/services/ethereum-wallet-service.d.ts.map +1 -0
- package/dist/services/ethereum-wallet-service.js +133 -0
- package/dist/services/ethereum-wallet-service.js.map +1 -0
- package/dist/services/index.d.ts +8 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +8 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/signature-service.d.ts +16 -0
- package/dist/services/signature-service.d.ts.map +1 -0
- package/dist/services/signature-service.js +63 -0
- package/dist/services/signature-service.js.map +1 -0
- package/dist/services/solana-transaction-service.d.ts +14 -0
- package/dist/services/solana-transaction-service.d.ts.map +1 -0
- package/dist/services/solana-transaction-service.js +48 -0
- package/dist/services/solana-transaction-service.js.map +1 -0
- package/dist/services/solana-wallet-service.d.ts +55 -0
- package/dist/services/solana-wallet-service.d.ts.map +1 -0
- package/dist/services/solana-wallet-service.js +129 -0
- package/dist/services/solana-wallet-service.js.map +1 -0
- package/dist/services/storage-service.d.ts +19 -0
- package/dist/services/storage-service.d.ts.map +1 -0
- package/dist/services/storage-service.js +41 -0
- package/dist/services/storage-service.js.map +1 -0
- package/dist/services/transaction-service.d.ts +28 -0
- package/dist/services/transaction-service.d.ts.map +1 -0
- package/dist/services/transaction-service.js +67 -0
- package/dist/services/transaction-service.js.map +1 -0
- package/dist/utils/error-utils.d.ts +6 -0
- package/dist/utils/error-utils.d.ts.map +1 -0
- package/dist/utils/error-utils.js +55 -0
- package/dist/utils/error-utils.js.map +1 -0
- package/dist/wallet-standard-discovery.d.ts +24 -0
- package/dist/wallet-standard-discovery.d.ts.map +1 -0
- package/dist/wallet-standard-discovery.js +74 -0
- package/dist/wallet-standard-discovery.js.map +1 -0
- package/package.json +42 -0
- package/src/eip6963-discovery.ts +103 -0
- package/src/index.ts +7 -0
- package/src/injected-connector.ts +265 -0
- package/src/services/connection-manager.ts +437 -0
- package/src/services/ethereum-transaction-service.ts +196 -0
- package/src/services/ethereum-wallet-service.ts +161 -0
- package/src/services/index.ts +7 -0
- package/src/services/signature-service.ts +84 -0
- package/src/services/solana-transaction-service.ts +68 -0
- package/src/services/solana-wallet-service.ts +161 -0
- package/src/services/storage-service.ts +44 -0
- package/src/services/transaction-service.ts +106 -0
- package/src/utils/error-utils.ts +62 -0
- package/src/wallet-standard-discovery.ts +102 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { ethers } from 'ethers'
|
|
2
|
+
import type { EthereumProvider } from '../eip6963-discovery'
|
|
3
|
+
import type {
|
|
4
|
+
EVMGasConfig,
|
|
5
|
+
EVMNativeTransferRequest,
|
|
6
|
+
EVMContractCallRequest,
|
|
7
|
+
EVMBatchTransactionRequest,
|
|
8
|
+
EVMCapabilities,
|
|
9
|
+
BatchTransactionResult
|
|
10
|
+
} from '@meshconnect/uwc-types'
|
|
11
|
+
import { parseError } from '../utils/error-utils'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Service for handling Ethereum/EVM transaction operations
|
|
15
|
+
*/
|
|
16
|
+
export class EthereumTransactionService {
|
|
17
|
+
/**
|
|
18
|
+
* Send native token transfer (ETH, BNB, MATIC, etc.)
|
|
19
|
+
*/
|
|
20
|
+
async sendNativeTransfer(
|
|
21
|
+
provider: EthereumProvider,
|
|
22
|
+
request: EVMNativeTransferRequest
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
try {
|
|
25
|
+
// Get current chain ID
|
|
26
|
+
const chainIdHex = await provider.request({
|
|
27
|
+
method: 'eth_chainId'
|
|
28
|
+
})
|
|
29
|
+
const chainId = parseInt(chainIdHex, 16)
|
|
30
|
+
|
|
31
|
+
// Create ethers provider and signer
|
|
32
|
+
const ethersProvider = new ethers.BrowserProvider(provider)
|
|
33
|
+
const signer = await ethersProvider.getSigner(request.from)
|
|
34
|
+
|
|
35
|
+
// Verify network hasn't changed
|
|
36
|
+
const network = await ethersProvider.getNetwork()
|
|
37
|
+
if (Number(network.chainId) !== chainId) {
|
|
38
|
+
throw new Error('Network changed during transaction setup')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Build transaction
|
|
42
|
+
const tx = await signer.sendTransaction({
|
|
43
|
+
to: request.to,
|
|
44
|
+
value: request.amount,
|
|
45
|
+
...this.formatGasConfig(request.gasConfig)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Wait for confirmation
|
|
49
|
+
const receipt = await tx.wait()
|
|
50
|
+
return receipt ? receipt.hash : ''
|
|
51
|
+
} catch (error) {
|
|
52
|
+
parseError(error)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Send smart contract transaction (including ERC-20 transfers)
|
|
58
|
+
*/
|
|
59
|
+
async sendContractCall(
|
|
60
|
+
provider: EthereumProvider,
|
|
61
|
+
request: EVMContractCallRequest
|
|
62
|
+
): Promise<string> {
|
|
63
|
+
try {
|
|
64
|
+
// Get current chain ID
|
|
65
|
+
const chainIdHex = await provider.request({
|
|
66
|
+
method: 'eth_chainId'
|
|
67
|
+
})
|
|
68
|
+
const chainId = parseInt(chainIdHex, 16)
|
|
69
|
+
|
|
70
|
+
// Create ethers provider and signer
|
|
71
|
+
const ethersProvider = new ethers.BrowserProvider(provider)
|
|
72
|
+
const signer = await ethersProvider.getSigner(request.from)
|
|
73
|
+
|
|
74
|
+
// Verify network hasn't changed
|
|
75
|
+
const network = await ethersProvider.getNetwork()
|
|
76
|
+
if (Number(network.chainId) !== chainId) {
|
|
77
|
+
throw new Error('Network changed during transaction setup')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create contract instance
|
|
81
|
+
const contract = new ethers.Contract(
|
|
82
|
+
request.contractAddress,
|
|
83
|
+
request.abi,
|
|
84
|
+
signer
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
// Build transaction options
|
|
88
|
+
const txOptions: ethers.Overrides = {
|
|
89
|
+
...this.formatGasConfig(request.gasConfig)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add value for payable functions
|
|
93
|
+
if (request.value) {
|
|
94
|
+
txOptions.value = request.value
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Call the contract function
|
|
98
|
+
const contractFunction = contract[request.functionName]
|
|
99
|
+
if (!contractFunction) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Function ${request.functionName} not found in contract`
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
const tx = await contractFunction(...request.args, txOptions)
|
|
105
|
+
|
|
106
|
+
// Wait for confirmation
|
|
107
|
+
const receipt = await tx.wait()
|
|
108
|
+
return receipt ? receipt.hash : ''
|
|
109
|
+
} catch (error) {
|
|
110
|
+
parseError(error)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Send batch of transactions (EIP-5792)
|
|
116
|
+
*/
|
|
117
|
+
async sendBatch(
|
|
118
|
+
provider: EthereumProvider,
|
|
119
|
+
request: EVMBatchTransactionRequest
|
|
120
|
+
): Promise<string> {
|
|
121
|
+
try {
|
|
122
|
+
const ethersProvider = new ethers.BrowserProvider(provider)
|
|
123
|
+
|
|
124
|
+
// Send batch transaction
|
|
125
|
+
const response: { id: string } = await ethersProvider.send(
|
|
126
|
+
'wallet_sendCalls',
|
|
127
|
+
[request]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
// Poll for completion
|
|
131
|
+
let result: BatchTransactionResult
|
|
132
|
+
do {
|
|
133
|
+
result = await ethersProvider.send('wallet_getCallsStatus', [
|
|
134
|
+
response.id
|
|
135
|
+
])
|
|
136
|
+
|
|
137
|
+
// Wait 1 second if still pending
|
|
138
|
+
if (result.status === 100) {
|
|
139
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
140
|
+
}
|
|
141
|
+
} while (result.status === 100)
|
|
142
|
+
|
|
143
|
+
// Check if successful
|
|
144
|
+
if (result.status !== 200) {
|
|
145
|
+
throw new Error('Batch transaction failed')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Return first transaction hash
|
|
149
|
+
const firstReceipt = result.receipts.find(r => r)
|
|
150
|
+
if (!firstReceipt) {
|
|
151
|
+
throw new Error('No transaction receipt found')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return firstReceipt.transactionHash
|
|
155
|
+
} catch (error) {
|
|
156
|
+
parseError(error)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get wallet capabilities (EIP-5792 support)
|
|
162
|
+
*/
|
|
163
|
+
async getCapabilities(
|
|
164
|
+
provider: EthereumProvider,
|
|
165
|
+
from: string,
|
|
166
|
+
chainId: string
|
|
167
|
+
): Promise<EVMCapabilities> {
|
|
168
|
+
const ethersProvider = new ethers.BrowserProvider(provider)
|
|
169
|
+
const capabilities = await ethersProvider.send('wallet_getCapabilities', [
|
|
170
|
+
from,
|
|
171
|
+
[chainId]
|
|
172
|
+
])
|
|
173
|
+
return capabilities[chainId] || {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Helper to format gas configuration for ethers
|
|
178
|
+
*/
|
|
179
|
+
private formatGasConfig(gasConfig?: EVMGasConfig): ethers.Overrides {
|
|
180
|
+
const txOptions: ethers.Overrides = {}
|
|
181
|
+
|
|
182
|
+
if (gasConfig?.gasLimit) {
|
|
183
|
+
txOptions.gasLimit = BigInt(Math.floor(gasConfig.gasLimit))
|
|
184
|
+
}
|
|
185
|
+
if (gasConfig?.maxFeePerGas) {
|
|
186
|
+
txOptions.maxFeePerGas = BigInt(Math.floor(gasConfig.maxFeePerGas))
|
|
187
|
+
}
|
|
188
|
+
if (gasConfig?.maxPriorityFeePerGas) {
|
|
189
|
+
txOptions.maxPriorityFeePerGas = BigInt(
|
|
190
|
+
Math.floor(gasConfig.maxPriorityFeePerGas)
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return txOptions
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { NetworkId, AvailableAddress } from '@meshconnect/uwc-types'
|
|
2
|
+
import type {
|
|
3
|
+
EthereumProvider,
|
|
4
|
+
DetectedWallet as EIP6963DetectedWallet
|
|
5
|
+
} from '../eip6963-discovery'
|
|
6
|
+
import { getAvailableWallets as getEIP6963Wallets } from '../eip6963-discovery'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Service for managing Ethereum wallet connections
|
|
10
|
+
*/
|
|
11
|
+
export class EthereumWalletService {
|
|
12
|
+
private detectedWallets: EIP6963DetectedWallet[] = []
|
|
13
|
+
private connectedProvider: EthereumProvider | null = null
|
|
14
|
+
private account: string | null = null
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initialize wallet discovery
|
|
18
|
+
*/
|
|
19
|
+
async initializeDiscovery(): Promise<void> {
|
|
20
|
+
this.detectedWallets = await getEIP6963Wallets()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get detected wallets
|
|
25
|
+
*/
|
|
26
|
+
getDetectedWallets(): EIP6963DetectedWallet[] {
|
|
27
|
+
return this.detectedWallets
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Find wallet by UUID
|
|
32
|
+
*/
|
|
33
|
+
findWalletByUuid(uuid: string): EIP6963DetectedWallet | undefined {
|
|
34
|
+
return this.detectedWallets.find(w => w.uuid === uuid)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if a wallet is already connected without requesting access
|
|
39
|
+
*/
|
|
40
|
+
async checkExistingConnection(
|
|
41
|
+
provider: EthereumProvider
|
|
42
|
+
): Promise<string[] | null> {
|
|
43
|
+
try {
|
|
44
|
+
// Try eth_accounts which doesn't prompt user
|
|
45
|
+
const accounts = await provider.request({
|
|
46
|
+
method: 'eth_accounts'
|
|
47
|
+
})
|
|
48
|
+
return accounts && accounts.length > 0 ? accounts : null
|
|
49
|
+
} catch {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Connect to wallet
|
|
56
|
+
*/
|
|
57
|
+
async connect(provider: EthereumProvider): Promise<string> {
|
|
58
|
+
const accounts = await provider.request({
|
|
59
|
+
method: 'eth_requestAccounts'
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (!accounts || accounts.length === 0) {
|
|
63
|
+
throw new Error('No accounts returned from wallet')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const address = accounts[0]
|
|
67
|
+
this.connectedProvider = provider
|
|
68
|
+
this.account = address
|
|
69
|
+
|
|
70
|
+
// Request chain ID to ensure we're connected
|
|
71
|
+
await provider.request({
|
|
72
|
+
method: 'eth_chainId'
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
return address
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Switch network
|
|
80
|
+
*/
|
|
81
|
+
async switchNetwork(networkId: string): Promise<void> {
|
|
82
|
+
if (!this.connectedProvider) {
|
|
83
|
+
throw new Error('No Ethereum wallet connected')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Extract chain ID from network.id (format: "eip155:1")
|
|
87
|
+
const chainIdParts = networkId.split(':')
|
|
88
|
+
if (chainIdParts.length !== 2 || !chainIdParts[1]) {
|
|
89
|
+
throw new Error(`Invalid network ID format: ${networkId}`)
|
|
90
|
+
}
|
|
91
|
+
const chainId = chainIdParts[1]
|
|
92
|
+
const chainIdHex = `0x${parseInt(chainId, 10).toString(16)}`
|
|
93
|
+
|
|
94
|
+
// Try to switch to the requested chain
|
|
95
|
+
try {
|
|
96
|
+
await this.connectedProvider.request({
|
|
97
|
+
method: 'wallet_switchEthereumChain',
|
|
98
|
+
params: [{ chainId: chainIdHex }]
|
|
99
|
+
})
|
|
100
|
+
} catch (switchError: unknown) {
|
|
101
|
+
// This error code indicates that the chain has not been added to the wallet
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
+
if ((switchError as any).code === 4902) {
|
|
104
|
+
throw new Error(`Chain ${networkId} is not added to the wallet`)
|
|
105
|
+
}
|
|
106
|
+
throw switchError
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Build available addresses for all supported networks
|
|
112
|
+
*/
|
|
113
|
+
buildAvailableAddresses(
|
|
114
|
+
supportedNetworkIds: string[],
|
|
115
|
+
address: string
|
|
116
|
+
): AvailableAddress[] {
|
|
117
|
+
const addresses: AvailableAddress[] = []
|
|
118
|
+
supportedNetworkIds.forEach(networkId => {
|
|
119
|
+
if (networkId.startsWith('eip155:')) {
|
|
120
|
+
addresses.push({
|
|
121
|
+
address,
|
|
122
|
+
networkId: networkId as NetworkId
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
return addresses
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Set connection state
|
|
131
|
+
*/
|
|
132
|
+
setConnectionState(
|
|
133
|
+
provider: EthereumProvider | null,
|
|
134
|
+
account: string | null
|
|
135
|
+
): void {
|
|
136
|
+
this.connectedProvider = provider
|
|
137
|
+
this.account = account
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get current account
|
|
142
|
+
*/
|
|
143
|
+
getAccount(): string | null {
|
|
144
|
+
return this.account
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get connected provider
|
|
149
|
+
*/
|
|
150
|
+
getConnectedProvider(): EthereumProvider | null {
|
|
151
|
+
return this.connectedProvider
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Disconnect (clear state only)
|
|
156
|
+
*/
|
|
157
|
+
disconnect(): void {
|
|
158
|
+
this.connectedProvider = null
|
|
159
|
+
this.account = null
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { StorageService } from './storage-service'
|
|
2
|
+
export { EthereumWalletService } from './ethereum-wallet-service'
|
|
3
|
+
export { SolanaWalletService } from './solana-wallet-service'
|
|
4
|
+
export { ConnectionManager } from './connection-manager'
|
|
5
|
+
export { SignatureService } from './signature-service'
|
|
6
|
+
export { TransactionService } from './transaction-service'
|
|
7
|
+
export { EthereumTransactionService } from './ethereum-transaction-service'
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { EthereumProvider } from '../eip6963-discovery'
|
|
2
|
+
import type { WalletAdapter } from '@solana/wallet-adapter-base'
|
|
3
|
+
import bs58 from 'bs58'
|
|
4
|
+
import { parseError } from '../utils/error-utils'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Service for handling message signing operations for injected wallets
|
|
8
|
+
*/
|
|
9
|
+
export class SignatureService {
|
|
10
|
+
/**
|
|
11
|
+
* Sign a message with an Ethereum wallet
|
|
12
|
+
*/
|
|
13
|
+
async signEthereumMessage(
|
|
14
|
+
message: string,
|
|
15
|
+
address: string,
|
|
16
|
+
provider: EthereumProvider
|
|
17
|
+
): Promise<string> {
|
|
18
|
+
if (!provider) {
|
|
19
|
+
throw new Error('Provider is required for Ethereum message signing')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!message) {
|
|
23
|
+
throw new Error('Message is required for signing')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!address) {
|
|
27
|
+
throw new Error('Address is required for signing')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Use personal_sign for Ethereum
|
|
32
|
+
const signature = await provider.request({
|
|
33
|
+
method: 'personal_sign',
|
|
34
|
+
params: [message, address]
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return signature
|
|
38
|
+
} catch (error) {
|
|
39
|
+
parseError(error)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sign a message with a Solana wallet
|
|
45
|
+
*/
|
|
46
|
+
async signSolanaMessage(
|
|
47
|
+
message: string,
|
|
48
|
+
adapter: WalletAdapter
|
|
49
|
+
): Promise<string> {
|
|
50
|
+
if (!adapter) {
|
|
51
|
+
throw new Error('Adapter is required for Solana message signing')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!message) {
|
|
55
|
+
throw new Error('Message is required for signing')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if adapter supports signMessage
|
|
59
|
+
const signingAdapter = adapter as WalletAdapter & {
|
|
60
|
+
signMessage?: (message: Uint8Array) => Promise<Uint8Array>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!signingAdapter.signMessage) {
|
|
64
|
+
throw new Error('Solana adapter does not support message signing')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Convert message to Uint8Array for Solana
|
|
69
|
+
const encoder = new TextEncoder()
|
|
70
|
+
const messageBytes = encoder.encode(message)
|
|
71
|
+
|
|
72
|
+
// Sign the message
|
|
73
|
+
const signedMessage = await signingAdapter.signMessage(messageBytes)
|
|
74
|
+
|
|
75
|
+
// Encode signature using base58 for Solana (standard encoding for Solana)
|
|
76
|
+
// The signature is returned as a Uint8Array, encode it to base58 string
|
|
77
|
+
const signatureBase58 = bs58.encode(signedMessage)
|
|
78
|
+
|
|
79
|
+
return signatureBase58
|
|
80
|
+
} catch (error) {
|
|
81
|
+
parseError(error)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PublicKey,
|
|
3
|
+
SystemProgram,
|
|
4
|
+
Transaction,
|
|
5
|
+
Connection
|
|
6
|
+
} from '@solana/web3.js'
|
|
7
|
+
import type { WalletAdapter } from '@solana/wallet-adapter-base'
|
|
8
|
+
import type { SolanaNativeTransferRequest, NetworkRpcMap, NetworkId } from '@meshconnect/uwc-types'
|
|
9
|
+
import { parseError } from '../utils/error-utils'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service for handling Solana transaction operations
|
|
13
|
+
*/
|
|
14
|
+
export class SolanaTransactionService {
|
|
15
|
+
private networkRpcMap: NetworkRpcMap
|
|
16
|
+
|
|
17
|
+
constructor(networkRpcMap: NetworkRpcMap = {}) {
|
|
18
|
+
this.networkRpcMap = networkRpcMap
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Send native SOL transfer
|
|
23
|
+
*/
|
|
24
|
+
async sendNativeTransfer(
|
|
25
|
+
adapter: WalletAdapter,
|
|
26
|
+
request: SolanaNativeTransferRequest,
|
|
27
|
+
networkId?: NetworkId
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
try {
|
|
30
|
+
if (!adapter.publicKey) {
|
|
31
|
+
throw new Error('Wallet not connected')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Verify the from address matches connected wallet
|
|
35
|
+
if (adapter.publicKey.toBase58() !== request.from) {
|
|
36
|
+
throw new Error('From address does not match connected wallet')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Blockhash is required
|
|
40
|
+
if (!request.blockhash) {
|
|
41
|
+
throw new Error('Blockhash is required for Solana transactions')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create transaction
|
|
45
|
+
const transaction = new Transaction().add(
|
|
46
|
+
SystemProgram.transfer({
|
|
47
|
+
fromPubkey: new PublicKey(request.from),
|
|
48
|
+
toPubkey: new PublicKey(request.to),
|
|
49
|
+
lamports: Number(request.amount)
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
transaction.recentBlockhash = request.blockhash
|
|
54
|
+
transaction.feePayer = adapter.publicKey
|
|
55
|
+
|
|
56
|
+
// Get RPC URL from the network RPC map or fall back to public endpoint
|
|
57
|
+
const rpcUrl = networkId && this.networkRpcMap[networkId]
|
|
58
|
+
? this.networkRpcMap[networkId]
|
|
59
|
+
: 'https://api.mainnet-beta.solana.com'
|
|
60
|
+
const connection = new Connection(rpcUrl)
|
|
61
|
+
|
|
62
|
+
const signature = await adapter.sendTransaction(transaction, connection)
|
|
63
|
+
return signature
|
|
64
|
+
} catch (error) {
|
|
65
|
+
parseError(error)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { NetworkId, AvailableAddress } from '@meshconnect/uwc-types'
|
|
2
|
+
import type { WalletAdapter } from '@solana/wallet-adapter-base'
|
|
3
|
+
import {
|
|
4
|
+
getSolanaWallets,
|
|
5
|
+
type WalletStandardInfo
|
|
6
|
+
} from '../wallet-standard-discovery'
|
|
7
|
+
import { StorageService } from './storage-service'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Service for managing Solana wallet connections
|
|
11
|
+
*/
|
|
12
|
+
export class SolanaWalletService {
|
|
13
|
+
private detectedWallets: WalletStandardInfo[] = []
|
|
14
|
+
private connectedAdapter: WalletAdapter | null = null
|
|
15
|
+
private account: string | null = null
|
|
16
|
+
private storageService: StorageService
|
|
17
|
+
|
|
18
|
+
constructor(storageService: StorageService) {
|
|
19
|
+
this.storageService = storageService
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize wallet discovery
|
|
24
|
+
*/
|
|
25
|
+
initializeDiscovery(): void {
|
|
26
|
+
this.detectedWallets = getSolanaWallets()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get detected wallets
|
|
31
|
+
*/
|
|
32
|
+
getDetectedWallets(): WalletStandardInfo[] {
|
|
33
|
+
return this.detectedWallets
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Find wallet by UUID
|
|
38
|
+
*/
|
|
39
|
+
findWalletByUuid(uuid: string): WalletStandardInfo | undefined {
|
|
40
|
+
return this.detectedWallets.find(w => w.uuid === uuid)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if a Solana wallet is already connected
|
|
45
|
+
*/
|
|
46
|
+
async checkExistingConnection(
|
|
47
|
+
adapter: WalletAdapter,
|
|
48
|
+
walletUuid: string
|
|
49
|
+
): Promise<string | null> {
|
|
50
|
+
try {
|
|
51
|
+
// Check if adapter has a publicKey (means it's connected)
|
|
52
|
+
if (adapter.publicKey) {
|
|
53
|
+
return adapter.publicKey.toBase58()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// If this wallet was previously connected, try to reconnect
|
|
57
|
+
// This should work without prompting for wallets that support it
|
|
58
|
+
if (this.storageService.wasSolanaWalletPreviouslyConnected(walletUuid)) {
|
|
59
|
+
if (
|
|
60
|
+
'connecting' in adapter &&
|
|
61
|
+
!adapter.connecting &&
|
|
62
|
+
'connect' in adapter
|
|
63
|
+
) {
|
|
64
|
+
try {
|
|
65
|
+
// Type assertion needed because TypeScript can't narrow the type properly
|
|
66
|
+
await adapter.connect()
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
const publicKey = (adapter as any).publicKey
|
|
69
|
+
if (publicKey && 'toBase58' in publicKey) {
|
|
70
|
+
return publicKey.toBase58()
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// Silent fail - wallet requires user interaction or doesn't support silent reconnect
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null
|
|
78
|
+
} catch {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Connect to wallet
|
|
85
|
+
*/
|
|
86
|
+
async connect(adapter: WalletAdapter, walletUuid: string): Promise<string> {
|
|
87
|
+
// Connect to the Solana wallet
|
|
88
|
+
await adapter.connect()
|
|
89
|
+
|
|
90
|
+
if (!adapter.publicKey) {
|
|
91
|
+
throw new Error('No public key returned from wallet')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const address = adapter.publicKey.toBase58()
|
|
95
|
+
this.connectedAdapter = adapter
|
|
96
|
+
this.account = address
|
|
97
|
+
|
|
98
|
+
// Store this wallet as connected
|
|
99
|
+
this.storageService.storeSolanaWalletUUID(walletUuid)
|
|
100
|
+
|
|
101
|
+
return address
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build available addresses for all supported networks
|
|
106
|
+
*/
|
|
107
|
+
buildAvailableAddresses(
|
|
108
|
+
supportedNetworkIds: string[],
|
|
109
|
+
address: string
|
|
110
|
+
): AvailableAddress[] {
|
|
111
|
+
const addresses: AvailableAddress[] = []
|
|
112
|
+
supportedNetworkIds.forEach(networkId => {
|
|
113
|
+
if (networkId.startsWith('solana:')) {
|
|
114
|
+
addresses.push({
|
|
115
|
+
address,
|
|
116
|
+
networkId: networkId as NetworkId
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
return addresses
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set connection state
|
|
125
|
+
*/
|
|
126
|
+
setConnectionState(
|
|
127
|
+
adapter: WalletAdapter | null,
|
|
128
|
+
account: string | null,
|
|
129
|
+
walletUuid?: string
|
|
130
|
+
): void {
|
|
131
|
+
this.connectedAdapter = adapter
|
|
132
|
+
this.account = account
|
|
133
|
+
|
|
134
|
+
// Store wallet UUID if provided and connected
|
|
135
|
+
if (adapter && account && walletUuid) {
|
|
136
|
+
this.storageService.storeSolanaWalletUUID(walletUuid)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get current account
|
|
142
|
+
*/
|
|
143
|
+
getAccount(): string | null {
|
|
144
|
+
return this.account
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get connected adapter
|
|
149
|
+
*/
|
|
150
|
+
getConnectedAdapter(): WalletAdapter | null {
|
|
151
|
+
return this.connectedAdapter
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Disconnect (clear state only, don't actually disconnect from wallet)
|
|
156
|
+
*/
|
|
157
|
+
disconnect(): void {
|
|
158
|
+
this.connectedAdapter = null
|
|
159
|
+
this.account = null
|
|
160
|
+
}
|
|
161
|
+
}
|