Skip to content

Commit 138a6b4

Browse files
fix: react-wallet-v2 fixed for Tezos
1 parent f3256d9 commit 138a6b4

File tree

8 files changed

+343
-126
lines changed

8 files changed

+343
-126
lines changed

advanced/wallets/react-wallet-v2/README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ Your `.env.local` now contains the following environment variables:
3939

4040
## Navigating through example
4141

42-
1. Initial setup and initializations happen in [_app.ts](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/pages/_app.tsx) file
43-
2. WalletConnect client, ethers and cosmos wallets are initialized in [useInitialization.ts ](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/hooks/useInitialization.ts) hook
44-
3. Subscription and handling of WalletConnect events happens in [useWalletConnectEventsManager.ts](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts) hook, that opens related [Modal views](https://github.com/WalletConnect/web-examples/tree/main/wallets/react-wallet-v2/src/views) and passes them all necessary data
45-
4. [Modal views](https://github.com/WalletConnect/web-examples/tree/main/wallets/react-wallet-v2/src/views) are responsible for data display and handling approval or rejection actions
46-
5. Upon approval or rejection, modals pass the request data to [RequestHandlerUtil.ts](https://github.com/WalletConnect/web-examples/blob/main/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts) that performs all necessary work based on the request method and returns formated json rpc result data that can be then used for WalletConnect client responses
42+
1. Initial setup and initializations happen in [_app.ts](https://github.com/WalletConnect/web-examples/blob/main/advanced/wallets/react-wallet-v2/src/pages/_app.tsx) file
43+
2. WalletConnect client, ethers and cosmos wallets are initialized in [useInitialization.ts ](https://github.com/WalletConnect/web-examples/blob/main/advanced/wallets/react-wallet-v2/src/hooks/useInitialization.ts) hook
44+
3. Subscription and handling of WalletConnect events happens in [useWalletConnectEventsManager.ts](https://github.com/WalletConnect/web-examples/blob/main/advanced/wallets/react-wallet-v2/src/hooks/useWalletConnectEventsManager.ts) hook, that opens related [Modal views](https://github.com/WalletConnect/web-examples/tree/main/wallets/react-wallet-v2/src/views) and passes them all necessary data
45+
4. [Modal views](https://github.com/WalletConnect/web-examples/tree/main/advanced/wallets/react-wallet-v2/src/views) are responsible for data display and handling approval or rejection actions
46+
5. Upon approval or rejection, modals pass the request data to [RequestHandlerUtil.ts](https://github.com/WalletConnect/web-examples/blob/main/advanced/wallets/react-wallet-v2/src/utils/RequestHandlerUtil.ts) that performs all necessary work based on the request method and returns formated json rpc result data that can be then used for WalletConnect client responses
4747

4848
## Preview of wallet and dapp examples in action
4949

advanced/wallets/react-wallet-v2/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"prettier:write": "prettier --write '**/*.{js,ts,jsx,tsx}'"
1111
},
1212
"dependencies": {
13+
"@airgap/beacon-types": "^4.2.2",
1314
"@biconomy/permission-context-builder": "^1.0.9",
1415
"@cosmjs/amino": "0.32.3",
1516
"@cosmjs/encoding": "0.32.3",
@@ -31,8 +32,9 @@
3132
"@polkadot/types": "^9.3.3",
3233
"@rhinestone/module-sdk": "0.1.2",
3334
"@solana/web3.js": "1.89.2",
34-
"@taquito/signer": "^15.1.0",
35-
"@taquito/taquito": "^15.1.0",
35+
"@taquito/rpc": "^20.0.1",
36+
"@taquito/signer": "^20.0.1",
37+
"@taquito/taquito": "^20.0.1",
3638
"@types/semver": "^7.5.8",
3739
"@walletconnect/web3wallet": "1.15.1",
3840
"@zerodev/ecdsa-validator": "5.3.0",

advanced/wallets/react-wallet-v2/src/components/ModalFooter.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default function ModalFooter({
5353
auto
5454
flat
5555
style={{ color: 'white', backgroundColor: 'grey' }}
56-
onPress={onReject}
56+
onClick={onReject}
5757
data-testid="session-reject-button"
5858
disabled={disableReject || rejectLoader?.active}
5959
>
@@ -68,7 +68,7 @@ export default function ModalFooter({
6868
flat
6969
color={approveButtonColor}
7070
disabled={disableApprove || approveLoader?.active}
71-
onPress={onApprove}
71+
onClick={onApprove}
7272
data-testid="session-approve-button"
7373
>
7474
{approveLoader && approveLoader.active ? (

advanced/wallets/react-wallet-v2/src/data/TezosData.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type ChainMetadata = {
55
rgb: string
66
rpc: string
77
namespace: string
8+
api?: string
89
}
910

1011
/**
@@ -21,7 +22,7 @@ export const TEZOS_MAINNET_CHAINS: Record<string, ChainMetadata> = {
2122
name: 'Tezos',
2223
logo: '/chain-logos/tezos.svg',
2324
rgb: '44, 125, 247',
24-
rpc: 'https://mainnet.api.tez.ie',
25+
rpc: 'https://rpc.tzbeta.net',
2526
namespace: 'tezos'
2627
}
2728
}
@@ -32,7 +33,7 @@ export const TEZOS_TEST_CHAINS: Record<string, ChainMetadata> = {
3233
name: 'Tezos Testnet',
3334
logo: '/chain-logos/tezos.svg',
3435
rgb: '44, 125, 247',
35-
rpc: 'https://ghostnet.ecadinfra.com',
36+
rpc: 'https://rpc.ghostnet.teztnets.com',
3637
namespace: 'tezos'
3738
}
3839
}

advanced/wallets/react-wallet-v2/src/lib/TezosLib.ts

+180-27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import { TezosToolkit } from '@taquito/taquito'
1+
import { OpKind, ParamsWithKind, TezosToolkit } from '@taquito/taquito';
22
import { InMemorySigner } from '@taquito/signer'
3-
import { localForger } from '@taquito/local-forging'
3+
44
import { Wallet } from 'ethers/'
5+
import { PvmKind, ScriptedContracts } from '@taquito/rpc';
6+
import { PartialTezosOperation, TezosOperationType } from '@airgap/beacon-types'
7+
import { TEZOS_CHAINS } from '@/data/TezosData';
58

69
/**
710
* Constants
@@ -22,7 +25,7 @@ interface IInitArguments {
2225
* Library
2326
*/
2427
export default class TezosLib {
25-
tezos: TezosToolkit
28+
toolkits: Record<string, TezosToolkit>
2629
signer: InMemorySigner
2730
mnemonic: string
2831
secretKey: string
@@ -31,15 +34,15 @@ export default class TezosLib {
3134
curve: 'ed25519' | 'secp256k1'
3235

3336
constructor(
34-
tezos: TezosToolkit,
37+
toolkits: Record<string, TezosToolkit>,
3538
mnemonic: string,
3639
signer: InMemorySigner,
3740
secretKey: string,
3841
publicKey: string,
3942
address: string,
4043
curve: 'ed25519' | 'secp256k1'
4144
) {
42-
this.tezos = tezos
45+
this.toolkits = toolkits
4346
this.mnemonic = mnemonic
4447
this.signer = signer
4548
this.secretKey = secretKey
@@ -55,17 +58,27 @@ export default class TezosLib {
5558
curve: curve ?? DEFAULT_CURVE
5659
}
5760

58-
const Tezos = new TezosToolkit('https://mainnet.api.tez.ie')
59-
60-
const signer = InMemorySigner.fromMnemonic(params)
61-
62-
Tezos.setSignerProvider(signer)
61+
const signer = InMemorySigner.fromMnemonic(params);
6362

64-
const secretKey = await signer.secretKey()
65-
const publicKey = await signer.publicKey()
66-
const address = await signer.publicKeyHash()
63+
// we have wallets for multiple networks
64+
// later we will determine the chain from the request
65+
const toolkits: Record<string, TezosToolkit> = {};
66+
for (const chainKey in TEZOS_CHAINS) {
67+
const chain = TEZOS_CHAINS[chainKey];
68+
const toolkit = new TezosToolkit(chain.rpc);
69+
70+
toolkit.setSignerProvider(signer);
71+
toolkits[chainKey] = toolkit;
72+
}
73+
const toolkit = toolkits['tezos:mainnet'];
74+
const secretKey = await toolkit.signer.secretKey();
75+
const publicKey = await toolkit.signer.publicKey();
76+
const address = await toolkit.signer.publicKeyHash();
6777

68-
return new TezosLib(Tezos, params.mnemonic, signer, secretKey, publicKey, address, params.curve)
78+
if (!secretKey) {
79+
throw new Error("Failed to generate secret key");
80+
}
81+
return new TezosLib(toolkits, params.mnemonic, signer, secretKey, publicKey, address, params.curve)
6982
}
7083

7184
public getMnemonic() {
@@ -84,23 +97,163 @@ export default class TezosLib {
8497
return this.address
8598
}
8699

87-
public async signTransaction(transaction: any) {
88-
const prepared = await this.tezos.prepare.batch(
89-
transaction.map((tx: any) => ({
90-
amount: tx.amount,
91-
to: tx.destination,
92-
kind: tx.kind,
93-
mutez: true
94-
}))
95-
)
100+
public convertToPartialParamsWithKind(op: PartialTezosOperation): ParamsWithKind {
101+
switch (op.kind) {
102+
case TezosOperationType.ACTIVATE_ACCOUNT:
103+
return {
104+
kind: OpKind.ACTIVATION,
105+
pkh: op.pkh,
106+
secret: op.secret,
107+
};
108+
case TezosOperationType.DELEGATION:
109+
return {
110+
kind: OpKind.DELEGATION,
111+
source: op.source ?? "source not provided",
112+
delegate: op.delegate,
113+
fee: op.fee ? Number(op.fee) : undefined,
114+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
115+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
116+
};
117+
case TezosOperationType.FAILING_NOOP:
118+
return {
119+
kind: OpKind.FAILING_NOOP,
120+
arbitrary: op.arbitrary,
121+
basedOnBlock: 'head',
122+
};
123+
case TezosOperationType.INCREASE_PAID_STORAGE:
124+
return {
125+
kind: OpKind.INCREASE_PAID_STORAGE,
126+
source: op.source,
127+
fee: op.fee ? Number(op.fee) : undefined,
128+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
129+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
130+
amount: Number(op.amount),
131+
destination: op.destination,
132+
};
133+
case TezosOperationType.ORIGINATION:
134+
let script : ScriptedContracts = op.script as unknown as ScriptedContracts;
135+
return {
136+
kind: OpKind.ORIGINATION,
137+
balance: Number(op.balance),
138+
code: script.code,
139+
init: script.storage,
140+
delegate: op.delegate,
141+
fee: op.fee ? Number(op.fee) : undefined,
142+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
143+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
144+
};
145+
case TezosOperationType.REGISTER_GLOBAL_CONSTANT:
146+
return {
147+
kind: OpKind.REGISTER_GLOBAL_CONSTANT,
148+
source: op.source,
149+
fee: op.fee ? Number(op.fee) : undefined,
150+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
151+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
152+
value: op.value,
153+
};
154+
case TezosOperationType.SMART_ROLLUP_ADD_MESSAGES:
155+
return {
156+
kind: OpKind.SMART_ROLLUP_ADD_MESSAGES,
157+
source: op.source,
158+
fee: op.fee ? Number(op.fee) : undefined,
159+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
160+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
161+
message: op.message,
162+
};
163+
case TezosOperationType.SMART_ROLLUP_ORIGINATE:
164+
if (!Object.values(PvmKind).includes(op.pvm_kind)) {
165+
throw new Error(`Invalid PvmKind: ${op.pvm_kind}`);
166+
}
167+
return {
168+
kind: OpKind.SMART_ROLLUP_ORIGINATE,
169+
source: op.source,
170+
fee: op.fee ? Number(op.fee) : undefined,
171+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
172+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
173+
pvmKind: op.pvm_kind,
174+
kernel: op.kernel,
175+
parametersType: op.parameters_ty,
176+
};
177+
case TezosOperationType.SMART_ROLLUP_EXECUTE_OUTBOX_MESSAGE:
178+
return {
179+
kind: OpKind.SMART_ROLLUP_EXECUTE_OUTBOX_MESSAGE,
180+
source: op.source,
181+
fee: op.fee ? Number(op.fee) : undefined,
182+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
183+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
184+
rollup: op.rollup,
185+
cementedCommitment: op.cemented_commitment,
186+
outputProof: op.output_proof,
187+
};
188+
case TezosOperationType.TRANSACTION:
189+
return {
190+
kind: OpKind.TRANSACTION,
191+
to: op.destination,
192+
amount: Number(op.amount),
193+
mutez: true,
194+
source: op.source,
195+
fee: op.fee ? Number(op.fee) : undefined,
196+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
197+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
198+
parameter: op.parameters,
199+
};
200+
case TezosOperationType.TRANSFER_TICKET:
201+
return {
202+
kind: OpKind.TRANSFER_TICKET,
203+
source: op.source,
204+
fee: op.fee ? Number(op.fee) : undefined,
205+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
206+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
207+
ticketContents: op.ticket_contents,
208+
ticketTy: op.ticket_ty,
209+
ticketTicketer: op.ticket_ticketer,
210+
ticketAmount: Number(op.ticket_amount),
211+
destination: op.destination,
212+
entrypoint: op.entrypoint,
213+
};
214+
case TezosOperationType.UPDATE_CONSENSUS_KEY:
215+
return {
216+
kind: OpKind.UPDATE_CONSENSUS_KEY,
217+
source: op.source,
218+
fee: op.fee ? Number(op.fee) : undefined,
219+
gasLimit: op.gas_limit ? Number(op.gas_limit) : undefined,
220+
storageLimit: op.storage_limit ? Number(op.storage_limit) : undefined,
221+
pk: op.pk,
222+
};
223+
default:
224+
throw new Error(`Operation kind cannot be converted to ParamsWithKind: ${op.kind}`);
225+
}
226+
}
227+
228+
public async signTransaction(transaction: any, chainId: string) {
229+
// Map the transactions and prepare the batch
230+
console.log(`Wallet: handling transaction: `, transaction);
231+
const batchTransactions: ParamsWithKind[] = transaction.map((tx: PartialTezosOperation) => {
232+
if (tx.kind === TezosOperationType.DELEGATION && !tx.source) {
233+
tx.source = this.address;
234+
}
235+
const op: ParamsWithKind = this.convertToPartialParamsWithKind(tx);
236+
return op;
237+
});
238+
239+
const toolkit = this.toolkits[chainId];
240+
if (!toolkit) {
241+
throw new Error(`Toolkit not found for chainId: ${chainId}`);
242+
}
96243

97-
const forged = await localForger.forge(prepared.opOb)
244+
// Prepare the batch
245+
console.log(`Wallet: prepared batchTransactions `, batchTransactions);
246+
const batch = toolkit.contract.batch(batchTransactions);
98247

99-
const tx = await this.signer.sign(forged, new Uint8Array([3]))
248+
// Send the batch and wait for the operation hash
249+
console.log(`Wallet: sending batch `, batch);
250+
const operation = await batch.send();
100251

101-
const hash = await this.tezos.rpc.injectOperation(tx.sbytes)
252+
// Wait for confirmation
253+
await operation.confirmation();
102254

103-
return hash
255+
console.log('Wallet: operation confirmed:', operation);
256+
return operation.hash;
104257
}
105258

106259
public async signPayload(payload: any) {

advanced/wallets/react-wallet-v2/src/utils/HelperUtil.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import toast from 'react-hot-toast'
22
import { COSMOS_MAINNET_CHAINS, TCosmosChain } from '@/data/COSMOSData'
33
import { EIP155_CHAINS, TEIP155Chain } from '@/data/EIP155Data'
44
import { MULTIVERSX_CHAINS, TMultiversxChain } from '@/data/MultiversxData'
5-
import { NEAR_CHAINS, NEAR_TEST_CHAINS, TNearChain } from '@/data/NEARData'
5+
import { NEAR_TEST_CHAINS, TNearChain } from '@/data/NEARData'
66
import { POLKADOT_CHAINS, TPolkadotChain } from '@/data/PolkadotData'
77
import { SOLANA_CHAINS, TSolanaChain } from '@/data/SolanaData'
88
import { TEZOS_CHAINS, TTezosChain } from '@/data/TezosData'
99
import { TRON_CHAINS, TTronChain } from '@/data/TronData'
1010
import { KADENA_CHAINS, TKadenaChain } from '@/data/KadenaData'
1111

1212
import { utils } from 'ethers'
13-
import { Verify } from '@walletconnect/types'
1413
import bs58 from 'bs58'
1514

1615
/**

advanced/wallets/react-wallet-v2/src/utils/TezosRequestHandlerUtil.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ export async function approveTezosRequest(
88
requestEvent: SignClientTypes.EventArguments['session_request']
99
) {
1010
const { params, id } = requestEvent
11-
const { request } = params
11+
const { request, chainId } = params
12+
console.log("Approving Tezos request: ", request);
13+
14+
if (!tezosWallets || Object.keys(tezosWallets).length === 0) {
15+
console.error("No wallets found on Approve. Try reloading the wallet page.")
16+
return formatJsonRpcError(id, "No Tezos wallets available. See the error log on wallet");
17+
}
1218

1319
const wallet = tezosWallets[request.params.account ?? Object.keys(tezosWallets)[0]]
1420
const allWallets = Object.keys(tezosWallets).map(key => tezosWallets[key])
@@ -25,9 +31,18 @@ export async function approveTezosRequest(
2531
)
2632

2733
case TEZOS_SIGNING_METHODS.TEZOS_SEND:
28-
const sendResponse = await wallet.signTransaction(request.params.operations)
29-
30-
return formatJsonRpcResult(id, { hash: sendResponse })
34+
try {
35+
const sendResponse = await wallet.signTransaction(request.params.operations, chainId)
36+
return formatJsonRpcResult(id, { hash: sendResponse })
37+
} catch (error) {
38+
if (error instanceof Error) {
39+
console.error("Tezos_send operation failed with error: ", error.message);
40+
return formatJsonRpcError(id, error.message);
41+
} else {
42+
console.error("Tezos_send operation failed with unknown error: ", error);
43+
return formatJsonRpcError(id, 'TEZOS_SEND failed with unknown error.');
44+
}
45+
}
3146

3247
case TEZOS_SIGNING_METHODS.TEZOS_SIGN:
3348
const signResponse = await wallet.signPayload(request.params.payload)

0 commit comments

Comments
 (0)