Skip to content

Commit b9a3940

Browse files
authored
feat: only send 1 meta transaction when sponsored [ID-1152] (#1080)
1 parent abc4a2d commit b9a3940

File tree

2 files changed

+101
-30
lines changed

2 files changed

+101
-30
lines changed

packages/passport/sdk/src/zkEvm/sendTransaction.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,55 @@ describe('sendTransaction', () => {
8787
);
8888
});
8989

90+
it('calls relayerClient.ethSendTransaction with sponsored meta transaction', async () => {
91+
(retryWithDelay as jest.Mock).mockResolvedValue({
92+
status: RelayerTransactionStatus.SUCCESSFUL,
93+
hash: transactionHash,
94+
} as RelayerTransaction);
95+
96+
const mockImxFeeOption = {
97+
tokenPrice: '0',
98+
tokenSymbol: 'IMX',
99+
tokenDecimals: 18,
100+
tokenAddress: '0x1337',
101+
recipientAddress: '0x7331',
102+
};
103+
104+
relayerClient.imGetFeeOptions.mockResolvedValue([mockImxFeeOption]);
105+
106+
const result = await sendTransaction({
107+
params: [transactionRequest],
108+
magicProvider,
109+
jsonRpcProvider: jsonRpcProvider as JsonRpcProvider,
110+
relayerClient: relayerClient as unknown as RelayerClient,
111+
user: mockUserZkEvm,
112+
guardianClient: guardianClient as unknown as GuardianClient,
113+
});
114+
115+
expect(result).toEqual(transactionHash);
116+
expect(guardianClient.validateEVMTransaction).toHaveBeenCalledWith(
117+
{
118+
chainId: chainIdEip155,
119+
nonce,
120+
user: mockUserZkEvm,
121+
metaTransactions: [
122+
{
123+
data: transactionRequest.data,
124+
revertOnError: true,
125+
to: mockUserZkEvm.zkEvm.ethAddress,
126+
value: '0x00',
127+
nonce,
128+
},
129+
],
130+
},
131+
);
132+
133+
expect(relayerClient.ethSendTransaction).toHaveBeenCalledWith(
134+
mockUserZkEvm.zkEvm.ethAddress,
135+
signedTransactions,
136+
);
137+
});
138+
90139
it('calls guardian.evaluateTransaction with the correct arguments', async () => {
91140
(retryWithDelay as jest.Mock).mockResolvedValue({
92141
status: RelayerTransactionStatus.SUCCESSFUL,

packages/passport/sdk/src/zkEvm/sendTransaction.ts

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
2-
ExternalProvider, JsonRpcProvider, TransactionRequest, Web3Provider,
2+
ExternalProvider, JsonRpcProvider, JsonRpcSigner, TransactionRequest, Web3Provider,
33
} from '@ethersproject/providers';
4-
import { BigNumber } from 'ethers';
4+
import { BigNumber, BigNumberish } from 'ethers';
55
import { getEip155ChainId, getNonce, getSignedMetaTransactions } from './walletHelpers';
66
import { MetaTransaction, RelayerTransactionStatus } from './types';
77
import { JsonRpcError, RpcErrorCode } from './JsonRpcError';
@@ -22,6 +22,51 @@ export type EthSendTransactionParams = {
2222
params: Array<any>;
2323
};
2424

25+
const getMetaTransactions = async (
26+
metaTransaction: MetaTransaction,
27+
nonce: BigNumberish,
28+
chainId: BigNumber,
29+
walletAddress: string,
30+
signer: JsonRpcSigner,
31+
relayerClient: RelayerClient,
32+
): Promise<MetaTransaction[]> => {
33+
// NOTE: We sign the transaction before getting the fee options because
34+
// accurate estimation of a transaction gas cost is only possible if the smart
35+
// wallet contract can actually execute it (in a simulated environment) - and
36+
// it can only execute signed transactions.
37+
const signedTransaction = await getSignedMetaTransactions(
38+
[metaTransaction],
39+
nonce,
40+
chainId,
41+
walletAddress,
42+
signer,
43+
);
44+
45+
// TODO: ID-698 Add support for non-native gas payments (e.g ERC20, feeTransaction initialisation must change)
46+
47+
// NOTE: "Fee Options" represent the multiple ways we could pay for the gas
48+
// used in this transaction. Each fee option has a "recipientAddress" we
49+
// should transfer the payment to, an amount and a currency. We choose one
50+
// option and build a transaction that sends the expected currency amount for
51+
// that option to the specified address.
52+
const feeOptions = await relayerClient.imGetFeeOptions(walletAddress, signedTransaction);
53+
const imxFeeOption = feeOptions.find((feeOption) => feeOption.tokenSymbol === 'IMX');
54+
if (!imxFeeOption) {
55+
throw new Error('Failed to retrieve fees for IMX token');
56+
}
57+
58+
const feeMetaTransaction: MetaTransaction = {
59+
nonce,
60+
to: imxFeeOption.recipientAddress,
61+
value: imxFeeOption.tokenPrice,
62+
revertOnError: true,
63+
};
64+
if (BigNumber.from(feeMetaTransaction.value).isZero()) {
65+
return [metaTransaction];
66+
}
67+
return [metaTransaction, feeMetaTransaction];
68+
};
69+
2570
export const sendTransaction = ({
2671
params,
2772
magicProvider,
@@ -50,49 +95,26 @@ export const sendTransaction = ({
5095
revertOnError: true,
5196
};
5297

53-
// NOTE: We sign the transaction before getting the fee options because
54-
// accurate estimation of a transaction gas cost is only possible if the smart
55-
// wallet contract can actually execute it (in a simulated environment) - and
56-
// it can only execute signed transactions.
57-
const signedTransaction = await getSignedMetaTransactions(
58-
[metaTransaction],
98+
const metaTransactions = await getMetaTransactions(
99+
metaTransaction,
59100
nonce,
60101
chainIdBigNumber,
61102
user.zkEvm.ethAddress,
62103
signer,
104+
relayerClient,
63105
);
64106

65-
// TODO: ID-698 Add support for non-native gas payments (e.g ERC20, feeTransaction initialisation must change)
66-
67-
// NOTE: "Fee Options" represent the multiple ways we could pay for the gas
68-
// used in this transaction. Each fee option has a "recipientAddress" we
69-
// should transfer the payment to, an amount and a currency. We choose one
70-
// option and build a transaction that sends the expected currency amount for
71-
// that option to the specified address.
72-
const feeOptions = await relayerClient.imGetFeeOptions(user.zkEvm.ethAddress, signedTransaction);
73-
const imxFeeOption = feeOptions.find((feeOption) => feeOption.tokenSymbol === 'IMX');
74-
if (!imxFeeOption) {
75-
throw new Error('Failed to retrieve fees for IMX token');
76-
}
77-
78-
const feeMetaTransaction: MetaTransaction = {
79-
nonce,
80-
to: imxFeeOption.recipientAddress,
81-
value: imxFeeOption.tokenPrice,
82-
revertOnError: true,
83-
};
84-
85107
await guardianClient.validateEVMTransaction({
86108
chainId: getEip155ChainId(chainId),
87109
nonce: convertBigNumberishToString(nonce),
88110
user,
89-
metaTransactions: [metaTransaction, feeMetaTransaction],
111+
metaTransactions,
90112
});
91113

92114
// NOTE: We sign again because we now are adding the fee transaction, so the
93115
// whole payload is different and needs a new signature.
94116
const signedTransactions = await getSignedMetaTransactions(
95-
[metaTransaction, feeMetaTransaction],
117+
metaTransactions,
96118
nonce,
97119
chainIdBigNumber,
98120
user.zkEvm.ethAddress,

0 commit comments

Comments
 (0)