@@ -54,6 +54,7 @@ import { useNavigate } from "react-router";
5454import { ApproveIcon , CancelIcon } from "../../../../components/button" ;
5555import { HeaderProps } from "../../../../layouts/header/types" ;
5656import { getKeplrFromWindow } from "@keplr-wallet/stores" ;
57+ import { UnsignedEVMTransactionWithErc20Approvals } from "@keplr-wallet/stores-eth" ;
5758
5859export const EthereumSignTxView : FunctionComponent < {
5960 interactionData : NonNullable < SignEthereumInteractionStore [ "waitingData" ] > ;
@@ -103,30 +104,89 @@ export const EthereumSignTxView: FunctionComponent<{
103104 gasConfig
104105 ) ;
105106
106- const [ signingDataBuff , setSigningDataBuff ] = useState ( Buffer . from ( message ) ) ;
107+ const [ requiredErc20Approvals ] = useState <
108+ NonNullable <
109+ UnsignedEVMTransactionWithErc20Approvals [ "requiredErc20Approvals" ]
110+ >
111+ > ( ( ) => {
112+ const parsed = JSON . parse ( Buffer . from ( message ) . toString ( "utf8" ) ) ;
113+ if (
114+ parsed . requiredErc20Approvals &&
115+ Array . isArray ( parsed . requiredErc20Approvals ) &&
116+ parsed . requiredErc20Approvals . length > 0
117+ ) {
118+ return parsed . requiredErc20Approvals ;
119+ }
120+ return [ ] ;
121+ } ) ;
122+
123+ const [ signingDataBuff , setSigningDataBuff ] = useState ( ( ) => {
124+ const parsed = JSON . parse ( Buffer . from ( message ) . toString ( "utf8" ) ) ;
125+ if ( parsed . requiredErc20Approvals ) {
126+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
127+ const { requiredErc20Approvals : _ , ...unsignedTx } = parsed ;
128+ return Buffer . from ( JSON . stringify ( unsignedTx ) , "utf8" ) ;
129+ }
130+ return Buffer . from ( message ) ;
131+ } ) ;
107132 const [ preferNoSetFee , setPreferNoSetFee ] = useState < boolean > ( false ) ;
108133
134+ const simulatorKey = useMemo ( ( ) => {
135+ if ( requiredErc20Approvals . length === 0 ) {
136+ return "evm/native" ;
137+ }
138+ return "evm/native/bundle" ;
139+ } , [ requiredErc20Approvals ] ) ;
140+
109141 const gasSimulator = useGasSimulator (
110142 new MemoryKVStore ( "gas-simulator.ethereum.sign" ) ,
111143 chainStore ,
112144 chainInfo . chainId ,
113145 gasConfig ,
114146 feeConfig ,
115- "evm/native" ,
147+ simulatorKey ,
116148 ( ) => {
117149 if ( chainInfo . evm == null ) {
118150 throw new Error ( "Gas simulator is only working with EVM info" ) ;
119151 }
120152
121153 const unsignedTx = JSON . parse ( Buffer . from ( message ) . toString ( "utf8" ) ) ;
154+ if ( requiredErc20Approvals . length === 0 ) {
155+ return {
156+ simulate : ( ) =>
157+ ethereumAccount . simulateGas ( account . ethereumHexAddress , {
158+ to : unsignedTx . to ,
159+ data : unsignedTx . data ,
160+ value : unsignedTx . value ,
161+ } ) ,
162+ } ;
163+ }
122164
165+ if ( requiredErc20Approvals . length > 1 ) {
166+ throw new Error ( "Multiple required ERC20 approvals are not supported" ) ;
167+ }
168+
169+ // NOTE:
170+ // If multiple ERC20 approvals are needed, or
171+ // bundle simulation using state diff tracing is not possible,
172+ // execute ERC20 approvals first—then simulate the main transaction on this page.
123173 return {
124174 simulate : ( ) =>
125- ethereumAccount . simulateGas ( account . ethereumHexAddress , {
126- to : unsignedTx . to ,
127- data : unsignedTx . data ,
128- value : unsignedTx . value ,
129- } ) ,
175+ ethereumAccount
176+ . simulateGasWithPendingErc20Approval ( account . ethereumHexAddress , {
177+ to : unsignedTx . to ,
178+ data : unsignedTx . data ,
179+ value : unsignedTx . value ,
180+ requiredErc20Approvals,
181+ } )
182+ . then ( ( result ) => {
183+ const { gasUsed } = result ;
184+ // only consider the gas used for the main tx
185+ if ( gasUsed == null || gasUsed <= 0 ) {
186+ throw new Error ( "Gas used is not positive" ) ;
187+ }
188+ return { gasUsed } ;
189+ } ) ,
130190 } ;
131191 }
132192 ) ;
@@ -185,6 +245,7 @@ export const EthereumSignTxView: FunctionComponent<{
185245 } , [ ] ) ;
186246
187247 useEffect ( ( ) => {
248+ // NOTE: set fee only for external requests
188249 if ( ! interactionData . isInternal ) {
189250 const unsignedTx = JSON . parse ( Buffer . from ( message ) . toString ( "utf8" ) ) ;
190251
@@ -298,6 +359,7 @@ export const EthereumSignTxView: FunctionComponent<{
298359 const { to, gasLimit, value, data, chainId } : UnsignedTransaction =
299360 JSON . parse ( Buffer . from ( message ) . toString ( "utf8" ) ) ;
300361
362+ // TODO: bundle simulation인 경우에 대한 처리 필요
301363 const l1DataFee = await ethereumAccount . simulateOpStackL1Fee ( {
302364 to,
303365 gasLimit,
0 commit comments