@meshconnect/uwc-wallet-connect-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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/services/connection-service.d.ts +60 -0
- package/dist/services/connection-service.d.ts.map +1 -0
- package/dist/services/connection-service.js +235 -0
- package/dist/services/connection-service.js.map +1 -0
- package/dist/services/network-switch-service.d.ts +16 -0
- package/dist/services/network-switch-service.d.ts.map +1 -0
- package/dist/services/network-switch-service.js +71 -0
- package/dist/services/network-switch-service.js.map +1 -0
- package/dist/services/signature-service.d.ts +15 -0
- package/dist/services/signature-service.d.ts.map +1 -0
- package/dist/services/signature-service.js +102 -0
- package/dist/services/signature-service.js.map +1 -0
- package/dist/services/transaction-service.d.ts +33 -0
- package/dist/services/transaction-service.d.ts.map +1 -0
- package/dist/services/transaction-service.js +287 -0
- package/dist/services/transaction-service.js.map +1 -0
- package/dist/services/walletconnect-client.d.ts +28 -0
- package/dist/services/walletconnect-client.d.ts.map +1 -0
- package/dist/services/walletconnect-client.js +92 -0
- package/dist/services/walletconnect-client.js.map +1 -0
- package/dist/utils/account-utils.d.ts +41 -0
- package/dist/utils/account-utils.d.ts.map +1 -0
- package/dist/utils/account-utils.js +81 -0
- package/dist/utils/account-utils.js.map +1 -0
- package/dist/utils/encoding-utils.d.ts +6 -0
- package/dist/utils/encoding-utils.d.ts.map +1 -0
- package/dist/utils/encoding-utils.js +27 -0
- package/dist/utils/encoding-utils.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/utils/solana-transactions.d.ts +4 -0
- package/dist/utils/solana-transactions.d.ts.map +1 -0
- package/dist/utils/solana-transactions.js +15 -0
- package/dist/utils/solana-transactions.js.map +1 -0
- package/dist/wallet-connect-connector.d.ts +17 -0
- package/dist/wallet-connect-connector.d.ts.map +1 -0
- package/dist/wallet-connect-connector.js +47 -0
- package/dist/wallet-connect-connector.js.map +1 -0
- package/package.json +41 -0
- package/src/index.ts +1 -0
- package/src/services/connection-service.ts +304 -0
- package/src/services/network-switch-service.ts +83 -0
- package/src/services/signature-service.ts +128 -0
- package/src/services/transaction-service.ts +397 -0
- package/src/services/walletconnect-client.ts +103 -0
- package/src/utils/account-utils.ts +116 -0
- package/src/utils/encoding-utils.ts +32 -0
- package/src/utils/error-utils.ts +62 -0
- package/src/utils/solana-transactions.ts +24 -0
- package/src/wallet-connect-connector.ts +83 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { WalletConnectClient } from './walletconnect-client'
|
|
2
|
+
import { ConnectionService } from './connection-service'
|
|
3
|
+
import { findAccountInNamespaces, parseAccount } from '../utils/account-utils'
|
|
4
|
+
import { base58Encode } from '../utils/encoding-utils'
|
|
5
|
+
import { parseError } from '../utils/error-utils'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Service for handling message signing in WalletConnect
|
|
9
|
+
*/
|
|
10
|
+
export class SignatureService {
|
|
11
|
+
constructor(
|
|
12
|
+
private walletConnectClient: WalletConnectClient,
|
|
13
|
+
private connectionService: ConnectionService
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sign a message with the connected wallet
|
|
18
|
+
*/
|
|
19
|
+
async signMessage(message: string): Promise<string> {
|
|
20
|
+
try {
|
|
21
|
+
const session = this.connectionService.getSession()
|
|
22
|
+
if (!session) {
|
|
23
|
+
throw new Error('No active session. Please connect first.')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const activeNetwork = this.connectionService.getActiveNetwork()
|
|
27
|
+
const activeAddress = this.connectionService.getActiveAddress()
|
|
28
|
+
|
|
29
|
+
if (!activeNetwork) {
|
|
30
|
+
throw new Error('No active network. Please connect first.')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!activeAddress) {
|
|
34
|
+
throw new Error('No active address. Please connect first.')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const client = await this.walletConnectClient.getClient()
|
|
38
|
+
|
|
39
|
+
// Find the account that matches the active network and address
|
|
40
|
+
const targetAccount = findAccountInNamespaces(
|
|
41
|
+
session.namespaces,
|
|
42
|
+
activeNetwork.id,
|
|
43
|
+
activeAddress
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if (!targetAccount) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`No account found for network ${activeNetwork.id} and address ${activeAddress}`
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
namespace: accountNamespace,
|
|
54
|
+
chainId,
|
|
55
|
+
address
|
|
56
|
+
} = parseAccount(targetAccount)
|
|
57
|
+
|
|
58
|
+
// Determine the signing method based on namespace
|
|
59
|
+
let method: string
|
|
60
|
+
let params: unknown[] | unknown
|
|
61
|
+
|
|
62
|
+
switch (accountNamespace) {
|
|
63
|
+
case 'eip155':
|
|
64
|
+
method = 'personal_sign'
|
|
65
|
+
// For personal_sign: [message, address]
|
|
66
|
+
params = [message, address]
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
case 'solana': {
|
|
70
|
+
method = 'solana_signMessage'
|
|
71
|
+
// For Solana: message needs to be base58 encoded
|
|
72
|
+
const base58Message = base58Encode(message)
|
|
73
|
+
|
|
74
|
+
// For Solana, params is an object, not an array
|
|
75
|
+
params = {
|
|
76
|
+
message: base58Message,
|
|
77
|
+
pubkey: address
|
|
78
|
+
}
|
|
79
|
+
break
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'bip122':
|
|
83
|
+
method = 'signMessage'
|
|
84
|
+
params = [
|
|
85
|
+
{
|
|
86
|
+
message,
|
|
87
|
+
address
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
case 'tron':
|
|
93
|
+
method = 'tron_signMessage'
|
|
94
|
+
params = [
|
|
95
|
+
{
|
|
96
|
+
message,
|
|
97
|
+
address
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
default:
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Signing not supported for namespace: ${accountNamespace}`
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
// Make the signing request
|
|
108
|
+
const result = await client.request({
|
|
109
|
+
topic: session.topic,
|
|
110
|
+
chainId: `${accountNamespace}:${chainId}`,
|
|
111
|
+
request: {
|
|
112
|
+
method,
|
|
113
|
+
params
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// Handle different response formats
|
|
118
|
+
if (accountNamespace === 'solana') {
|
|
119
|
+
// Solana returns an object with a signature property
|
|
120
|
+
return (result as { signature: string }).signature
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return result as string
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw parseError(error)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { ethers } from 'ethers'
|
|
2
|
+
import { Buffer } from 'buffer'
|
|
3
|
+
import {
|
|
4
|
+
Connection,
|
|
5
|
+
PublicKey,
|
|
6
|
+
Transaction,
|
|
7
|
+
VersionedTransaction
|
|
8
|
+
} from '@solana/web3.js'
|
|
9
|
+
import type {
|
|
10
|
+
TransactionRequest,
|
|
11
|
+
TransactionResult,
|
|
12
|
+
EVMNativeTransferRequest,
|
|
13
|
+
EVMContractCallRequest,
|
|
14
|
+
SolanaNativeTransferRequest,
|
|
15
|
+
NetworkId,
|
|
16
|
+
NetworkRpcMap
|
|
17
|
+
} from '@meshconnect/uwc-types'
|
|
18
|
+
import type SignClient from '@walletconnect/sign-client'
|
|
19
|
+
import { WalletConnectClient } from './walletconnect-client'
|
|
20
|
+
import { ConnectionService } from './connection-service'
|
|
21
|
+
import { getChainIdFromNetworkId } from '../utils/account-utils'
|
|
22
|
+
import { parseError } from '../utils/error-utils'
|
|
23
|
+
import { getSolanaTransaction } from '../utils/solana-transactions'
|
|
24
|
+
import type { SessionTypes } from '@walletconnect/types'
|
|
25
|
+
import base58 from 'bs58'
|
|
26
|
+
import { standardizeSignature } from '../utils/encoding-utils'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Service for handling transactions in WalletConnect
|
|
30
|
+
*/
|
|
31
|
+
export class TransactionService {
|
|
32
|
+
constructor(
|
|
33
|
+
private walletConnectClient: WalletConnectClient,
|
|
34
|
+
private connectionService: ConnectionService,
|
|
35
|
+
private networkRpcMap: NetworkRpcMap = {}
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Send a transaction with the connected wallet
|
|
40
|
+
*/
|
|
41
|
+
async sendTransaction(
|
|
42
|
+
request: TransactionRequest
|
|
43
|
+
): Promise<TransactionResult> {
|
|
44
|
+
const session = this.connectionService.getSession()
|
|
45
|
+
if (!session) {
|
|
46
|
+
throw new Error('No active session. Please connect first.')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const activeNetwork = this.connectionService.getActiveNetwork()
|
|
50
|
+
const activeAddress = this.connectionService.getActiveAddress()
|
|
51
|
+
|
|
52
|
+
if (!activeNetwork) {
|
|
53
|
+
throw new Error('No active network. Please connect first.')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!activeAddress) {
|
|
57
|
+
throw new Error('No active address. Please connect first.')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const client = await this.walletConnectClient.getClient()
|
|
61
|
+
|
|
62
|
+
// Determine namespace from network ID
|
|
63
|
+
const namespace = activeNetwork.id.split(':')[0]
|
|
64
|
+
|
|
65
|
+
if (namespace === 'eip155') {
|
|
66
|
+
return this.sendEthereumTransaction(
|
|
67
|
+
client,
|
|
68
|
+
session,
|
|
69
|
+
request,
|
|
70
|
+
activeNetwork.id,
|
|
71
|
+
activeAddress
|
|
72
|
+
)
|
|
73
|
+
} else if (namespace === 'solana') {
|
|
74
|
+
return this.sendSolanaTransaction(
|
|
75
|
+
client,
|
|
76
|
+
session,
|
|
77
|
+
request as SolanaNativeTransferRequest,
|
|
78
|
+
activeNetwork.id,
|
|
79
|
+
activeAddress
|
|
80
|
+
)
|
|
81
|
+
} else {
|
|
82
|
+
throw new Error(`Unsupported namespace: ${namespace}`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Send an Ethereum transaction
|
|
88
|
+
*/
|
|
89
|
+
private async sendEthereumTransaction(
|
|
90
|
+
client: SignClient,
|
|
91
|
+
session: SessionTypes.Struct,
|
|
92
|
+
request: TransactionRequest,
|
|
93
|
+
activeNetworkId: string,
|
|
94
|
+
activeAddress: string
|
|
95
|
+
): Promise<string> {
|
|
96
|
+
try {
|
|
97
|
+
// Handle different types of EVM transactions
|
|
98
|
+
if ('contractAddress' in request && 'abi' in request) {
|
|
99
|
+
// Contract call
|
|
100
|
+
return await this.sendContractCall(
|
|
101
|
+
client,
|
|
102
|
+
session,
|
|
103
|
+
request as EVMContractCallRequest,
|
|
104
|
+
activeNetworkId,
|
|
105
|
+
activeAddress
|
|
106
|
+
)
|
|
107
|
+
} else if ('calls' in request) {
|
|
108
|
+
// Batch transaction - not yet supported
|
|
109
|
+
throw new Error(
|
|
110
|
+
'Batch transactions are not yet supported for WalletConnect'
|
|
111
|
+
)
|
|
112
|
+
} else if ('amount' in request && typeof request.amount === 'bigint') {
|
|
113
|
+
// Native transfer
|
|
114
|
+
return await this.sendNativeTransfer(
|
|
115
|
+
client,
|
|
116
|
+
session,
|
|
117
|
+
request as EVMNativeTransferRequest,
|
|
118
|
+
activeNetworkId,
|
|
119
|
+
activeAddress
|
|
120
|
+
)
|
|
121
|
+
} else {
|
|
122
|
+
throw new Error('Invalid transaction request type')
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw parseError(error)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Send a Solana transaction
|
|
131
|
+
*/
|
|
132
|
+
private async sendSolanaTransaction(
|
|
133
|
+
client: SignClient,
|
|
134
|
+
session: SessionTypes.Struct,
|
|
135
|
+
request: SolanaNativeTransferRequest,
|
|
136
|
+
activeNetworkId: string,
|
|
137
|
+
activeAddress: string
|
|
138
|
+
): Promise<string> {
|
|
139
|
+
try {
|
|
140
|
+
// Verify the from address matches active address
|
|
141
|
+
if (request.from !== activeAddress) {
|
|
142
|
+
throw new Error('From address does not match connected wallet')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Blockhash is required
|
|
146
|
+
if (!request.blockhash) {
|
|
147
|
+
throw new Error('Blockhash is required for Solana transactions')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create the transaction using the utility function
|
|
151
|
+
let transaction: Transaction | VersionedTransaction =
|
|
152
|
+
getSolanaTransaction(request)
|
|
153
|
+
|
|
154
|
+
// Serialize the transaction for WalletConnect
|
|
155
|
+
const serializedTransaction = transaction
|
|
156
|
+
.serialize({
|
|
157
|
+
verifySignatures: false
|
|
158
|
+
})
|
|
159
|
+
.toString('base64')
|
|
160
|
+
|
|
161
|
+
// Get the chain ID (e.g., "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" for mainnet)
|
|
162
|
+
const chainId = activeNetworkId
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// First try signAndSendTransaction (preferred method)
|
|
166
|
+
const result = await client.request({
|
|
167
|
+
topic: session.topic,
|
|
168
|
+
chainId,
|
|
169
|
+
request: {
|
|
170
|
+
method: 'solana_signAndSendTransaction',
|
|
171
|
+
params: {
|
|
172
|
+
transaction: serializedTransaction
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// WalletConnect returns the signature directly for Solana
|
|
178
|
+
return result as string
|
|
179
|
+
} catch (signAndSendError: unknown) {
|
|
180
|
+
const errorMessage =
|
|
181
|
+
signAndSendError instanceof Error
|
|
182
|
+
? signAndSendError.message
|
|
183
|
+
: String(signAndSendError)
|
|
184
|
+
// If signAndSendTransaction is not supported, fallback to signTransaction
|
|
185
|
+
if (
|
|
186
|
+
errorMessage.includes('solana_signAndSendTransaction') ||
|
|
187
|
+
errorMessage.includes('Missing or invalid')
|
|
188
|
+
) {
|
|
189
|
+
// Sign the transaction only
|
|
190
|
+
const result: { transaction: string } | { signature: string } =
|
|
191
|
+
await client.request({
|
|
192
|
+
topic: session.topic,
|
|
193
|
+
chainId,
|
|
194
|
+
request: {
|
|
195
|
+
method: 'solana_signTransaction',
|
|
196
|
+
params: {
|
|
197
|
+
transaction: serializedTransaction
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
if ('signature' in result) {
|
|
203
|
+
const decoded = base58.decode(result.signature)
|
|
204
|
+
transaction.addSignature(
|
|
205
|
+
new PublicKey(request.from),
|
|
206
|
+
Buffer.from(decoded) as Buffer & Uint8Array
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if ('transaction' in result) {
|
|
211
|
+
const decodedTransaction = Buffer.from(result.transaction, 'base64')
|
|
212
|
+
|
|
213
|
+
if (transaction instanceof VersionedTransaction) {
|
|
214
|
+
transaction = VersionedTransaction.deserialize(
|
|
215
|
+
new Uint8Array(decodedTransaction)
|
|
216
|
+
)
|
|
217
|
+
} else {
|
|
218
|
+
transaction = Transaction.from(decodedTransaction)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Get RPC URL from the network RPC map or fall back to public endpoint
|
|
223
|
+
const rpcUrl =
|
|
224
|
+
this.networkRpcMap[activeNetworkId as NetworkId] ||
|
|
225
|
+
'https://api.mainnet-beta.solana.com'
|
|
226
|
+
const connection = new Connection(rpcUrl)
|
|
227
|
+
const rawSignature = await connection.sendRawTransaction(
|
|
228
|
+
transaction.serialize(),
|
|
229
|
+
{
|
|
230
|
+
skipPreflight: false,
|
|
231
|
+
maxRetries: 3,
|
|
232
|
+
preflightCommitment: 'confirmed'
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return standardizeSignature(rawSignature)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// If it's a different error, throw it
|
|
240
|
+
throw signAndSendError
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
throw parseError(error)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Send native token transfer (ETH, BNB, MATIC, etc.)
|
|
249
|
+
*/
|
|
250
|
+
private async sendNativeTransfer(
|
|
251
|
+
client: SignClient,
|
|
252
|
+
session: SessionTypes.Struct,
|
|
253
|
+
request: EVMNativeTransferRequest,
|
|
254
|
+
activeNetworkId: string,
|
|
255
|
+
activeAddress: string
|
|
256
|
+
): Promise<string> {
|
|
257
|
+
try {
|
|
258
|
+
// Use the active network chainId
|
|
259
|
+
const chainId = getChainIdFromNetworkId(activeNetworkId as NetworkId)
|
|
260
|
+
|
|
261
|
+
// Build transaction using ethers
|
|
262
|
+
const tx: ethers.TransactionRequest = {
|
|
263
|
+
from: request.from || activeAddress,
|
|
264
|
+
to: request.to,
|
|
265
|
+
value: request.amount
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Add gas configuration if provided
|
|
269
|
+
if (request.gasConfig?.gasLimit) {
|
|
270
|
+
tx.gasLimit = BigInt(Math.floor(request.gasConfig.gasLimit))
|
|
271
|
+
}
|
|
272
|
+
if (request.gasConfig?.maxFeePerGas) {
|
|
273
|
+
tx.maxFeePerGas = BigInt(Math.floor(request.gasConfig.maxFeePerGas))
|
|
274
|
+
}
|
|
275
|
+
if (request.gasConfig?.maxPriorityFeePerGas) {
|
|
276
|
+
tx.maxPriorityFeePerGas = BigInt(
|
|
277
|
+
Math.floor(request.gasConfig.maxPriorityFeePerGas)
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Convert to hex format for WalletConnect
|
|
282
|
+
const txParams: Record<string, string> = {
|
|
283
|
+
from: tx.from as string,
|
|
284
|
+
to: tx.to as string,
|
|
285
|
+
value: '0x' + (tx.value ? BigInt(tx.value).toString(16) : '0'),
|
|
286
|
+
data: '0x' // Empty data for native transfers
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Add gas params if present
|
|
290
|
+
if (tx.gasLimit) {
|
|
291
|
+
txParams['gasLimit'] = '0x' + tx.gasLimit.toString(16)
|
|
292
|
+
}
|
|
293
|
+
if (tx.maxFeePerGas) {
|
|
294
|
+
txParams['maxFeePerGas'] = '0x' + tx.maxFeePerGas.toString(16)
|
|
295
|
+
}
|
|
296
|
+
if (tx.maxPriorityFeePerGas) {
|
|
297
|
+
txParams['maxPriorityFeePerGas'] =
|
|
298
|
+
'0x' + tx.maxPriorityFeePerGas.toString(16)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const hash = await client.request({
|
|
302
|
+
topic: session.topic,
|
|
303
|
+
chainId: `eip155:${chainId}`,
|
|
304
|
+
request: {
|
|
305
|
+
method: 'eth_sendTransaction',
|
|
306
|
+
params: [txParams]
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
return hash as string
|
|
311
|
+
} catch (error) {
|
|
312
|
+
throw parseError(error)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Send smart contract transaction (including ERC-20 transfers)
|
|
318
|
+
*/
|
|
319
|
+
private async sendContractCall(
|
|
320
|
+
client: SignClient,
|
|
321
|
+
session: SessionTypes.Struct,
|
|
322
|
+
request: EVMContractCallRequest,
|
|
323
|
+
activeNetworkId: string,
|
|
324
|
+
activeAddress: string
|
|
325
|
+
): Promise<string> {
|
|
326
|
+
try {
|
|
327
|
+
// Use the active network chainId
|
|
328
|
+
const chainId = getChainIdFromNetworkId(activeNetworkId as NetworkId)
|
|
329
|
+
|
|
330
|
+
// Encode the contract call data using ethers
|
|
331
|
+
const iface = new ethers.Interface(request.abi)
|
|
332
|
+
const data = iface.encodeFunctionData(request.functionName, request.args)
|
|
333
|
+
|
|
334
|
+
// Build transaction using ethers
|
|
335
|
+
const tx: ethers.TransactionRequest = {
|
|
336
|
+
from: request.from || activeAddress,
|
|
337
|
+
to: request.contractAddress,
|
|
338
|
+
data
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Add value for payable functions
|
|
342
|
+
if (request.value) {
|
|
343
|
+
tx.value = BigInt(request.value)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Add gas configuration if provided
|
|
347
|
+
if (request.gasConfig?.gasLimit) {
|
|
348
|
+
tx.gasLimit = BigInt(Math.floor(request.gasConfig.gasLimit))
|
|
349
|
+
}
|
|
350
|
+
if (request.gasConfig?.maxFeePerGas) {
|
|
351
|
+
tx.maxFeePerGas = BigInt(Math.floor(request.gasConfig.maxFeePerGas))
|
|
352
|
+
}
|
|
353
|
+
if (request.gasConfig?.maxPriorityFeePerGas) {
|
|
354
|
+
tx.maxPriorityFeePerGas = BigInt(
|
|
355
|
+
Math.floor(request.gasConfig.maxPriorityFeePerGas)
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Convert to hex format for WalletConnect
|
|
360
|
+
const txParams: Record<string, string> = {
|
|
361
|
+
from: tx.from as string,
|
|
362
|
+
to: tx.to as string,
|
|
363
|
+
data: tx.data as string
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Add value if present
|
|
367
|
+
if (tx.value) {
|
|
368
|
+
txParams['value'] = '0x' + BigInt(tx.value).toString(16)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Add gas params if present
|
|
372
|
+
if (tx.gasLimit) {
|
|
373
|
+
txParams['gasLimit'] = '0x' + tx.gasLimit.toString(16)
|
|
374
|
+
}
|
|
375
|
+
if (tx.maxFeePerGas) {
|
|
376
|
+
txParams['maxFeePerGas'] = '0x' + tx.maxFeePerGas.toString(16)
|
|
377
|
+
}
|
|
378
|
+
if (tx.maxPriorityFeePerGas) {
|
|
379
|
+
txParams['maxPriorityFeePerGas'] =
|
|
380
|
+
'0x' + tx.maxPriorityFeePerGas.toString(16)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const hash = await client.request({
|
|
384
|
+
topic: session.topic,
|
|
385
|
+
chainId: `eip155:${chainId}`,
|
|
386
|
+
request: {
|
|
387
|
+
method: 'eth_sendTransaction',
|
|
388
|
+
params: [txParams]
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
return hash as string
|
|
393
|
+
} catch (error) {
|
|
394
|
+
throw parseError(error)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import SignClient from '@walletconnect/sign-client'
|
|
2
|
+
import type { WalletConnectConfig } from '@meshconnect/uwc-types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Service for managing WalletConnect client instance
|
|
6
|
+
*/
|
|
7
|
+
export class WalletConnectClient {
|
|
8
|
+
private client: SignClient | null = null
|
|
9
|
+
private initPromise: Promise<SignClient> | null = null
|
|
10
|
+
|
|
11
|
+
constructor(private config: WalletConnectConfig) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize or get the WalletConnect client
|
|
15
|
+
*/
|
|
16
|
+
async getClient(): Promise<SignClient> {
|
|
17
|
+
// If already initialized, return the client
|
|
18
|
+
if (this.client) {
|
|
19
|
+
return this.client
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// If initialization is in progress, wait for it
|
|
23
|
+
if (this.initPromise) {
|
|
24
|
+
return this.initPromise
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Start initialization
|
|
28
|
+
this.initPromise = this.initClient()
|
|
29
|
+
this.client = await this.initPromise
|
|
30
|
+
this.initPromise = null
|
|
31
|
+
|
|
32
|
+
return this.client
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialize the WalletConnect client
|
|
37
|
+
*/
|
|
38
|
+
private async initClient(): Promise<SignClient> {
|
|
39
|
+
if (!this.config.projectId) {
|
|
40
|
+
throw new Error('WalletConnect projectId is required')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let url = this.config.metadata?.url || ''
|
|
44
|
+
if (!url && typeof globalThis !== 'undefined') {
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
+
const global = globalThis as any
|
|
47
|
+
if (global.location?.origin) {
|
|
48
|
+
url = global.location.origin
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const metadata = {
|
|
53
|
+
name: this.config.metadata?.name || 'Universal Wallet Connector',
|
|
54
|
+
description: this.config.metadata?.description || 'Connect your wallet',
|
|
55
|
+
url,
|
|
56
|
+
icons: this.config.metadata?.icons || []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const client = await SignClient.init({
|
|
60
|
+
projectId: this.config.projectId,
|
|
61
|
+
metadata
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return client
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Disconnect all sessions
|
|
69
|
+
*/
|
|
70
|
+
async disconnectAll(): Promise<void> {
|
|
71
|
+
const client = await this.getClient()
|
|
72
|
+
const sessions = client.session.getAll()
|
|
73
|
+
|
|
74
|
+
for (const session of sessions) {
|
|
75
|
+
try {
|
|
76
|
+
await client.disconnect({
|
|
77
|
+
topic: session.topic,
|
|
78
|
+
reason: {
|
|
79
|
+
code: 6000,
|
|
80
|
+
message: 'User disconnected'
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
} catch {
|
|
84
|
+
// Silently ignore disconnect errors
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Disconnect a specific session
|
|
91
|
+
*/
|
|
92
|
+
async disconnect(topic: string): Promise<void> {
|
|
93
|
+
const client = await this.getClient()
|
|
94
|
+
|
|
95
|
+
await client.disconnect({
|
|
96
|
+
topic,
|
|
97
|
+
reason: {
|
|
98
|
+
code: 6000,
|
|
99
|
+
message: 'User disconnected'
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
}
|