@meshconnect/uwc-wallet-connect-connector 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/services/connection-service.d.ts.map +1 -1
  2. package/dist/services/connection-service.js +0 -2
  3. package/dist/services/connection-service.js.map +1 -1
  4. package/dist/services/ethereum/ethereum-transaction-builder.d.ts +14 -0
  5. package/dist/services/ethereum/ethereum-transaction-builder.d.ts.map +1 -0
  6. package/dist/services/ethereum/ethereum-transaction-builder.js +60 -0
  7. package/dist/services/ethereum/ethereum-transaction-builder.js.map +1 -0
  8. package/dist/services/ethereum/ethereum-transaction-service.d.ts +21 -0
  9. package/dist/services/ethereum/ethereum-transaction-service.d.ts.map +1 -0
  10. package/dist/services/ethereum/ethereum-transaction-service.js +107 -0
  11. package/dist/services/ethereum/ethereum-transaction-service.js.map +1 -0
  12. package/dist/services/solana/solana-transaction-builder.d.ts +20 -0
  13. package/dist/services/solana/solana-transaction-builder.d.ts.map +1 -0
  14. package/dist/services/solana/solana-transaction-builder.js +112 -0
  15. package/dist/services/solana/solana-transaction-builder.js.map +1 -0
  16. package/dist/services/solana/solana-transaction-service.d.ts +17 -0
  17. package/dist/services/solana/solana-transaction-service.d.ts.map +1 -0
  18. package/dist/services/solana/solana-transaction-service.js +178 -0
  19. package/dist/services/solana/solana-transaction-service.js.map +1 -0
  20. package/dist/services/transaction-service.d.ts +2 -17
  21. package/dist/services/transaction-service.d.ts.map +1 -1
  22. package/dist/services/transaction-service.js +8 -246
  23. package/dist/services/transaction-service.js.map +1 -1
  24. package/package.json +3 -2
  25. package/src/services/connection-service.ts +0 -3
  26. package/src/services/ethereum/ethereum-transaction-builder.ts +82 -0
  27. package/src/services/ethereum/ethereum-transaction-service.ts +152 -0
  28. package/src/services/solana/solana-transaction-builder.ts +214 -0
  29. package/src/services/solana/solana-transaction-service.ts +294 -0
  30. package/src/services/transaction-service.ts +17 -339
  31. package/src/utils/account-utils.ts +1 -1
  32. package/src/utils/solana-transactions.ts +0 -24
@@ -0,0 +1,214 @@
1
+ import {
2
+ AddressLookupTableAccount,
3
+ PublicKey,
4
+ SystemProgram,
5
+ TransactionInstruction,
6
+ TransactionMessage,
7
+ VersionedTransaction,
8
+ type AddressLookupTableState
9
+ } from '@solana/web3.js'
10
+ import type {
11
+ SolanaNativeTransferRequest,
12
+ SolanaTokenTransferRequest,
13
+ SolanaGenericTransferRequest,
14
+ SolanaAccountMeta,
15
+ SolanaTransactionRequest
16
+ } from '@meshconnect/uwc-types'
17
+ import {
18
+ createTransferCheckedInstruction,
19
+ createAssociatedTokenAccountIdempotentInstruction,
20
+ getAssociatedTokenAddress,
21
+ TOKEN_PROGRAM_ID,
22
+ TOKEN_2022_PROGRAM_ID
23
+ } from '@solana/spl-token'
24
+ import { Buffer } from 'buffer'
25
+
26
+ /**
27
+ * Service for handling Solana transaction operations
28
+ */
29
+ export class SolanaTransactionBuilder {
30
+ async buildTransferInstructions(
31
+ request: SolanaTransactionRequest
32
+ ): Promise<TransactionInstruction[]> {
33
+ if ('tokenMint' in request) {
34
+ return await this.buildSplTokenTransferInstructions(
35
+ request as SolanaTokenTransferRequest
36
+ )
37
+ } else if ('instructions' in request) {
38
+ return await this.buildGenericTransferInstructions(
39
+ request as SolanaGenericTransferRequest
40
+ )
41
+ } else {
42
+ return [
43
+ this.buildNativeTransferInstruction(
44
+ request as SolanaNativeTransferRequest
45
+ )
46
+ ]
47
+ }
48
+ }
49
+
50
+ async getVersionedTransaction(
51
+ feePayer: PublicKey,
52
+ request: SolanaTransactionRequest
53
+ ): Promise<VersionedTransaction> {
54
+ const lookupTable = this.parseLookupTable(request)
55
+
56
+ return new VersionedTransaction(
57
+ new TransactionMessage({
58
+ payerKey: feePayer,
59
+ recentBlockhash: request.blockhash,
60
+ instructions: await this.buildTransferInstructions(request)
61
+ }).compileToV0Message(lookupTable)
62
+ )
63
+ }
64
+
65
+ /**
66
+ * Build native SOL transfer
67
+ */
68
+ private buildNativeTransferInstruction(
69
+ request: SolanaNativeTransferRequest
70
+ ): TransactionInstruction {
71
+ return SystemProgram.transfer({
72
+ fromPubkey: new PublicKey(request.from),
73
+ toPubkey: new PublicKey(request.to),
74
+ lamports: Number(request.amount)
75
+ })
76
+ }
77
+
78
+ /**
79
+ * Build SPL Token transfer
80
+ */
81
+ private async buildSplTokenTransferInstructions(
82
+ request: SolanaTokenTransferRequest
83
+ ): Promise<TransactionInstruction[]> {
84
+ let instructions: TransactionInstruction[] = []
85
+
86
+ // Create Public Keys
87
+ const mint = new PublicKey(request.tokenMint)
88
+ const from = new PublicKey(request.from)
89
+ const to = new PublicKey(request.to)
90
+
91
+ const fromTokenAccount = await getAssociatedTokenAddress(
92
+ mint,
93
+ from,
94
+ false,
95
+ request.tokenProgram
96
+ ? new PublicKey(request.tokenProgram)
97
+ : TOKEN_PROGRAM_ID
98
+ )
99
+
100
+ const toTokenAccount = await getAssociatedTokenAddress(
101
+ mint,
102
+ to,
103
+ false,
104
+ request.tokenProgram
105
+ ? new PublicKey(request.tokenProgram)
106
+ : TOKEN_PROGRAM_ID
107
+ )
108
+
109
+ // Create target associated token account
110
+ instructions.push(
111
+ createAssociatedTokenAccountIdempotentInstruction(
112
+ from,
113
+ toTokenAccount,
114
+ to,
115
+ mint,
116
+ request.tokenProgram
117
+ ? new PublicKey(request.tokenProgram)
118
+ : TOKEN_PROGRAM_ID
119
+ )
120
+ )
121
+
122
+ const programId =
123
+ request.tokenProgram === TOKEN_2022_PROGRAM_ID.toBase58()
124
+ ? TOKEN_2022_PROGRAM_ID
125
+ : TOKEN_PROGRAM_ID
126
+
127
+ instructions.push(
128
+ createTransferCheckedInstruction(
129
+ fromTokenAccount,
130
+ mint,
131
+ toTokenAccount,
132
+ from,
133
+ request.amount,
134
+ request.tokenDecimals,
135
+ [],
136
+ programId
137
+ )
138
+ )
139
+
140
+ return instructions
141
+ }
142
+
143
+ private async buildGenericTransferInstructions(
144
+ request: SolanaGenericTransferRequest
145
+ ): Promise<TransactionInstruction[]> {
146
+ const result: TransactionInstruction[] = []
147
+
148
+ const instructions = request.instructions
149
+ for (let instrIndex = 0; instrIndex < instructions.length; instrIndex++) {
150
+ const ix = instructions[instrIndex]!
151
+ const programId = new PublicKey(ix.programId)
152
+
153
+ const keys = ix.accounts.map(
154
+ (meta: SolanaAccountMeta, accountIndex: number) => {
155
+ if (!meta.pubKey) {
156
+ throw new Error(
157
+ `Account at instruction ${instrIndex}, index ${accountIndex} has no pubKey and is not fillable`
158
+ )
159
+ }
160
+
161
+ const resolvedPubkey: PublicKey = new PublicKey(meta.pubKey)
162
+ return {
163
+ pubkey: resolvedPubkey,
164
+ isSigner: meta.isSigner,
165
+ isWritable: meta.isWritable
166
+ }
167
+ }
168
+ )
169
+
170
+ result.push(
171
+ new TransactionInstruction({
172
+ keys,
173
+ programId,
174
+ data: Buffer.from(ix.data, 'base64')
175
+ })
176
+ )
177
+ }
178
+
179
+ if (request.additionalRequests) {
180
+ const extraInstructions = await Promise.all(
181
+ request.additionalRequests.map(req =>
182
+ this.buildTransferInstructions(req)
183
+ )
184
+ )
185
+
186
+ result.push(...extraInstructions.flat())
187
+ }
188
+
189
+ return result
190
+ }
191
+
192
+ private parseLookupTable = (
193
+ request: SolanaTransactionRequest
194
+ ): AddressLookupTableAccount[] | undefined => {
195
+ if (!('states' in request)) {
196
+ return undefined
197
+ }
198
+
199
+ return (request.states ?? []).map(state => {
200
+ const currentState: AddressLookupTableState = {
201
+ deactivationSlot: state.deactivationSlot,
202
+ lastExtendedSlot: state.lastExtendedSlot,
203
+ lastExtendedSlotStartIndex: state.lastExtendedStartIndex,
204
+ addresses: [],
205
+ ...(state.authority && { authority: new PublicKey(state.authority) })
206
+ }
207
+
208
+ return new AddressLookupTableAccount({
209
+ key: new PublicKey(state.key),
210
+ state: currentState
211
+ })
212
+ })
213
+ }
214
+ }
@@ -0,0 +1,294 @@
1
+ import type {
2
+ NetworkId,
3
+ NetworkRpcMap,
4
+ SolanaGenericTransferRequest,
5
+ SolanaNativeTransferRequest,
6
+ SolanaTokenTransferRequest,
7
+ SolanaTransactionRequest
8
+ } from '@meshconnect/uwc-types'
9
+ import {
10
+ VersionedTransaction,
11
+ PublicKey,
12
+ Connection,
13
+ Transaction
14
+ } from '@solana/web3.js'
15
+ import type SignClient from '@walletconnect/sign-client'
16
+ import type { SessionTypes } from '@walletconnect/types'
17
+ import { standardizeSignature } from '../../utils/encoding-utils'
18
+ import { parseError } from '../../utils/error-utils'
19
+ import { Buffer } from 'buffer'
20
+ import base58 from 'bs58'
21
+ import { SolanaTransactionBuilder } from './solana-transaction-builder'
22
+
23
+ export class SolanaTransactionService {
24
+ private builder: SolanaTransactionBuilder
25
+
26
+ constructor(private networkRpcMap: NetworkRpcMap = {}) {
27
+ this.builder = new SolanaTransactionBuilder()
28
+ }
29
+
30
+ /**
31
+ * Send a Solana transaction
32
+ */
33
+ async sendTransaction(
34
+ client: SignClient,
35
+ session: SessionTypes.Struct,
36
+ request: SolanaTransactionRequest,
37
+ activeNetworkId: string,
38
+ activeAddress: string
39
+ ): Promise<string> {
40
+ try {
41
+ // Handle different types of Solana transactions
42
+ if ('tokenMint' in request) {
43
+ // SPL token transfer
44
+ return await this.sendSplTokenTransaction(
45
+ client,
46
+ session,
47
+ request as SolanaTokenTransferRequest,
48
+ activeNetworkId,
49
+ activeAddress
50
+ )
51
+ } else if ('instructions' in request) {
52
+ // Generic transaction with instructions
53
+ return await this.sendGenericTransferWithInstructions(
54
+ client,
55
+ session,
56
+ request as SolanaGenericTransferRequest,
57
+ activeNetworkId,
58
+ activeAddress
59
+ )
60
+ } else if ('from' in request) {
61
+ // Native transfer
62
+ return await this.sendNativeTransaction(
63
+ client,
64
+ session,
65
+ request as SolanaNativeTransferRequest,
66
+ activeNetworkId,
67
+ activeAddress
68
+ )
69
+ } else {
70
+ throw new Error('Invalid transaction request type')
71
+ }
72
+ } catch (error) {
73
+ throw parseError(error)
74
+ }
75
+ }
76
+
77
+ private async sendGenericTransferWithInstructions(
78
+ client: SignClient,
79
+ session: SessionTypes.Struct,
80
+ request: SolanaGenericTransferRequest,
81
+ activeNetworkId: string,
82
+ activeAddress: string
83
+ ) {
84
+ try {
85
+ if (!activeNetworkId) {
86
+ throw new Error('Active network not found')
87
+ }
88
+
89
+ // Verify the from address matches active address
90
+ if (request.feePayer !== activeAddress) {
91
+ throw new Error('Fee payer address does not match connected wallet')
92
+ }
93
+
94
+ // Blockhash is required
95
+ if (!request.blockhash) {
96
+ throw new Error('Blockhash is required for Solana transactions')
97
+ }
98
+
99
+ let transaction = await this.builder.getVersionedTransaction(
100
+ new PublicKey(activeAddress),
101
+ request
102
+ )
103
+
104
+ return this.submitTransaction(
105
+ client,
106
+ session,
107
+ transaction,
108
+ activeNetworkId,
109
+ activeAddress
110
+ )
111
+ } catch (error) {
112
+ throw parseError(error)
113
+ }
114
+ }
115
+
116
+ private async sendNativeTransaction(
117
+ client: SignClient,
118
+ session: SessionTypes.Struct,
119
+ request: SolanaNativeTransferRequest,
120
+ activeNetworkId: string,
121
+ activeAddress: string
122
+ ) {
123
+ try {
124
+ // Verify the from address matches active address
125
+ if (request.from !== activeAddress) {
126
+ throw new Error('From address does not match connected wallet')
127
+ }
128
+
129
+ // Blockhash is required
130
+ if (!request.blockhash) {
131
+ throw new Error('Blockhash is required for Solana transactions')
132
+ }
133
+
134
+ let transaction: Transaction | VersionedTransaction = new Transaction()
135
+ transaction.recentBlockhash = request.blockhash
136
+ transaction.feePayer = new PublicKey(request.from)
137
+ transaction.instructions =
138
+ await this.builder.buildTransferInstructions(request)
139
+
140
+ // Serialize the transaction for WalletConnect
141
+
142
+ return this.submitTransaction(
143
+ client,
144
+ session,
145
+ transaction,
146
+ activeNetworkId,
147
+ activeAddress
148
+ )
149
+ } catch (error) {
150
+ throw parseError(error)
151
+ }
152
+ }
153
+
154
+ private async sendSplTokenTransaction(
155
+ client: SignClient,
156
+ session: SessionTypes.Struct,
157
+ request: SolanaTokenTransferRequest,
158
+ activeNetworkId: string,
159
+ activeAddress: string
160
+ ) {
161
+ try {
162
+ if (!activeNetworkId) {
163
+ throw new Error('Active network not found')
164
+ }
165
+
166
+ // Verify the from address matches active address
167
+ if (request.from !== activeAddress) {
168
+ throw new Error('From address does not match connected wallet')
169
+ }
170
+
171
+ // Blockhash is required
172
+ if (!request.blockhash) {
173
+ throw new Error('Blockhash is required for Solana transactions')
174
+ }
175
+
176
+ let transaction = await this.builder.getVersionedTransaction(
177
+ new PublicKey(activeAddress),
178
+ request
179
+ )
180
+
181
+ return this.submitTransaction(
182
+ client,
183
+ session,
184
+ transaction,
185
+ activeNetworkId,
186
+ activeAddress
187
+ )
188
+ } catch (error) {
189
+ throw parseError(error)
190
+ }
191
+ }
192
+
193
+ private async submitTransaction(
194
+ client: SignClient,
195
+ session: SessionTypes.Struct,
196
+ transaction: Transaction | VersionedTransaction,
197
+ activeNetworkId: string,
198
+ activeAddress: string
199
+ ) {
200
+ // Get the chain ID (e.g., "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" for mainnet)
201
+ const chainId = activeNetworkId
202
+ let serializedTransaction: string
203
+ if (transaction instanceof Transaction) {
204
+ serializedTransaction = transaction
205
+ .serialize({
206
+ verifySignatures: false
207
+ })
208
+ .toString('base64')
209
+ } else {
210
+ serializedTransaction = Buffer.from(
211
+ new Uint8Array(transaction.serialize())
212
+ ).toString('base64')
213
+ }
214
+
215
+ try {
216
+ // First try signAndSendTransaction (preferred method)
217
+ const result = await client.request({
218
+ topic: session.topic,
219
+ chainId,
220
+ request: {
221
+ method: 'solana_signAndSendTransaction',
222
+ params: {
223
+ transaction: serializedTransaction
224
+ }
225
+ }
226
+ })
227
+
228
+ // WalletConnect returns the signature directly for Solana
229
+ return result as string
230
+ } catch (signAndSendError: unknown) {
231
+ const errorMessage =
232
+ signAndSendError instanceof Error
233
+ ? signAndSendError.message
234
+ : String(signAndSendError)
235
+ // If signAndSendTransaction is not supported, fallback to signTransaction
236
+ if (
237
+ errorMessage.includes('solana_signAndSendTransaction') ||
238
+ errorMessage.includes('Missing or invalid')
239
+ ) {
240
+ // Sign the transaction only
241
+ const result: { transaction: string } | { signature: string } =
242
+ await client.request({
243
+ topic: session.topic,
244
+ chainId,
245
+ request: {
246
+ method: 'solana_signTransaction',
247
+ params: {
248
+ transaction: serializedTransaction
249
+ }
250
+ }
251
+ })
252
+
253
+ if ('signature' in result) {
254
+ const decoded = base58.decode(result.signature)
255
+ transaction.addSignature(
256
+ new PublicKey(activeAddress),
257
+ Buffer.from(decoded) as Buffer & Uint8Array
258
+ )
259
+ }
260
+
261
+ if ('transaction' in result) {
262
+ const decodedTransaction = Buffer.from(result.transaction, 'base64')
263
+
264
+ if (transaction instanceof VersionedTransaction) {
265
+ transaction = VersionedTransaction.deserialize(
266
+ new Uint8Array(decodedTransaction)
267
+ )
268
+ } else {
269
+ transaction = Transaction.from(decodedTransaction)
270
+ }
271
+ }
272
+
273
+ // Get RPC URL from the network RPC map or fall back to public endpoint
274
+ const rpcUrl =
275
+ this.networkRpcMap[activeNetworkId as NetworkId] ||
276
+ 'https://alien-newest-vineyard.solana-mainnet.quiknode.pro/ebe5e35661d7edb7a5e48ab84bd9d477e472a40b'
277
+ const connection = new Connection(rpcUrl)
278
+ const rawSignature = await connection.sendRawTransaction(
279
+ transaction.serialize(),
280
+ {
281
+ skipPreflight: false,
282
+ maxRetries: 3,
283
+ preflightCommitment: 'confirmed'
284
+ }
285
+ )
286
+
287
+ return standardizeSignature(rawSignature)
288
+ }
289
+
290
+ // If it's a different error, throw it
291
+ throw signAndSendError
292
+ }
293
+ }
294
+ }