Skip to content

Commit

Permalink
fix: transaction fees of walletConnectConnector (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
LuizAsFight authored Jun 22, 2024
1 parent 5e1159f commit 204bf27
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/violet-grapes-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-connectors/walletconnect-connector": minor
---

Fix transaction fees in WalletConnectConnector. Now users don't need to force maxFee or gasLimit
5 changes: 1 addition & 4 deletions examples/react-app/src/components/counter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,7 @@ export default function ContractCounter({ isSigning, setIsSigning }: Props) {
setIsSigning(true);
const contract = CounterAbi__factory.connect(COUNTER_CONTRACT_ID, wallet);
try {
await contract.functions
.increment_counter()
.txParams({ gasLimit: bn(200_000), maxFee: bn(150_000) })
.call();
await contract.functions.increment_counter().call();

getCount();

Expand Down
51 changes: 46 additions & 5 deletions packages/walletconnect-connector/src/WalletConnectConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import {
type Network,
type TransactionRequestLike,
type Version,
ZeroBytes32,
bn,
calculateGasFee,
concat,
transactionRequestify,
} from 'fuels';

Expand All @@ -28,6 +31,7 @@ import { ETHEREUM_ICON, TESTNET_URL } from './constants';
import type { Predicate, PredicateConfig, WalletConnectConfig } from './types';
import { PredicateAccount } from './utils/Predicate';
import { createModalConfig } from './utils/wagmiConfig';

export class WalletConnectConnector extends FuelConnector {
name = 'Ethereum Wallets';

Expand Down Expand Up @@ -300,28 +304,64 @@ export class WalletConnectConnector extends FuelConnector {
throw Error(`No account found for ${address}`);
}
const transactionRequest = transactionRequestify(transaction);
const transactionFee = transactionRequest.maxFee.toNumber();

const predicateSignatureIndex = transactionRequest.witnesses.length - 1;

// Create a predicate and set the witness index to call in predicate`
const predicate = this.predicateAccount.createPredicate(
evmAccount,
fuelProvider,
[transactionRequest.witnesses.length - 1],
[predicateSignatureIndex],
);
predicate.connect(fuelProvider);

// Attach missing inputs (including estimated predicate gas usage) / outputs to the request
await predicate.provider.estimateTxDependencies(transactionRequest);

// To each input of the request, attach the predicate and its data
const requestWithPredicateAttached =
predicate.populateTransactionPredicateData(transactionRequest);

const maxGasUsed =
await this.predicateAccount.getMaxPredicateGasUsed(fuelProvider);

let predictedGasUsedPredicate = bn(0);
requestWithPredicateAttached.inputs.forEach((input) => {
if ('predicate' in input && input.predicate) {
input.witnessIndex = 0;
predictedGasUsedPredicate = predictedGasUsedPredicate.add(maxGasUsed);
}
});

// Add a placeholder for the predicate signature to count on bytes measurement from start. It will be replaced later
requestWithPredicateAttached.witnesses[predicateSignatureIndex] = concat([
ZeroBytes32,
ZeroBytes32,
]);

const { gasPriceFactor } = await predicate.provider.getGasConfig();
const { maxFee, gasPrice } = await predicate.provider.estimateTxGasAndFee({
transactionRequest: requestWithPredicateAttached,
});

const predicateSuccessFeeDiff = calculateGasFee({
gas: predictedGasUsedPredicate,
priceFactor: gasPriceFactor,
gasPrice,
});

const feeWithFat = maxFee.add(predicateSuccessFeeDiff);
const isNeededFatFee = feeWithFat.gt(transactionFee);

if (isNeededFatFee) {
// add more 10 just in case sdk fee estimation is not accurate
requestWithPredicateAttached.maxFee = feeWithFat.add(10);
}

// Attach missing inputs (including estimated predicate gas usage) / outputs to the request
await predicate.provider.estimateTxDependencies(
requestWithPredicateAttached,
);

// gets the transactionID in fuel and ask to sign in eth wallet
const txID = requestWithPredicateAttached.getTransactionId(chainId);
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const provider: any = await getAccount(
Expand All @@ -334,7 +374,8 @@ export class WalletConnectConnector extends FuelConnector {

// Transform the signature into compact form for Sway to understand
const compactSignature = splitSignature(hexToBytes(signature)).compact;
transactionRequest.witnesses.push(compactSignature);
requestWithPredicateAttached.witnesses[predicateSignatureIndex] =
compactSignature;

const transactionWithPredicateEstimated =
await fuelProvider.estimatePredicates(requestWithPredicateAttached);
Expand Down
35 changes: 35 additions & 0 deletions packages/walletconnect-connector/src/utils/Predicate.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { arrayify } from '@ethersproject/bytes';
import {
Address,
type BN,
type InputValue,
type JsonAbi,
Predicate,
type Provider,
ScriptTransactionRequest,
ZeroBytes32,
bn,
getPredicateRoot,
} from 'fuels';
import memoize from 'memoizee';
import { privateKeyToAccount } from 'viem/accounts';

import type { PredicateConfig } from '../types';

export class PredicateAccount {
Expand Down Expand Up @@ -54,6 +60,35 @@ export class PredicateAccount {
},
);

getMaxPredicateGasUsed = memoize(async (provider: Provider): Promise<BN> => {
const account = privateKeyToAccount(
'0x0000000000000000000000000000000000000000000000000000000000000001',
);
const chainId = provider.getChainId();
const fakePredicate = this.createPredicate(account.address, provider, [0]);
const request = new ScriptTransactionRequest();
request.addCoinInput({
id: ZeroBytes32,
assetId: ZeroBytes32,
amount: bn(),
owner: fakePredicate.address,
blockCreated: bn(),
txCreatedIdx: bn(),
});
fakePredicate.populateTransactionPredicateData(request);
const txId = request.getTransactionId(chainId);
const signature = await account.signMessage({
message: txId,
});
request.witnesses = [signature];
await fakePredicate.provider.estimatePredicates(request);
const predicateInput = request.inputs[0];
if (predicateInput && 'predicate' in predicateInput) {
return bn(predicateInput.predicateGasUsed);
}
return bn();
});

getEVMAddress(address: string, evmAccounts: Array<string> = []) {
return evmAccounts.find(
(account) => this.getPredicateAddress(account) === address,
Expand Down

0 comments on commit 204bf27

Please sign in to comment.