@@ -14,6 +14,7 @@ import { buildApprovalTransactionData } from './ethUtils';
1414import {
1515 constructTransactionReviewOutputs ,
1616 isClearSignedEvmTradingSwapTransaction ,
17+ isClearSignedWrappedNativeTransaction ,
1718} from './reviewTransactionUtils' ;
1819
1920const buildPrecomposedTx = ( to : string | undefined ) : GeneralPrecomposedTransactionFinal =>
@@ -118,6 +119,15 @@ const buildPrecomposedTransaction = ({
118119 isTokenKnown,
119120 } ) as unknown as GeneralPrecomposedTransactionFinal ;
120121
122+ const wethToken : TokenInfo = {
123+ balance : '1000000' ,
124+ contract : WETH_MAINNET . toLowerCase ( ) ,
125+ decimals : 18 ,
126+ name : 'Wrapped Ether' ,
127+ standard : 'ERC20' ,
128+ symbol : 'WETH' ,
129+ } ;
130+
121131const usdcToken : TokenInfo = {
122132 balance : '1000000' ,
123133 contract : '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' ,
@@ -185,6 +195,61 @@ describe('isClearSignedEvmTradingSwapTransaction', () => {
185195 } ) ;
186196} ) ;
187197
198+ describe ( 'isClearSignedWrappedNativeTransaction' , ( ) => {
199+ const account = buildEthereumAccount ( ) ;
200+ const device = buildUpdatedDevice ( ) ;
201+
202+ it . each ( [
203+ { op : 'wrap' , transactionData : WETH_DEPOSIT_DATA } ,
204+ { op : 'unwrap' , transactionData : WETH_WITHDRAW_DATA } ,
205+ ] ) ( 'returns true for a canonical WETH $op' , ( { transactionData } ) => {
206+ const result = isClearSignedWrappedNativeTransaction ( {
207+ account,
208+ device,
209+ precomposedTx : buildPrecomposedTransaction ( { to : WETH_MAINNET } ) ,
210+ transactionData,
211+ } ) ;
212+
213+ expect ( result ) . toBe ( true ) ;
214+ } ) ;
215+
216+ it ( 'returns false for a wrapped native the firmware does not clear-sign (WBNB on BSC)' , ( ) => {
217+ const result = isClearSignedWrappedNativeTransaction ( {
218+ account : buildEthereumAccount ( { symbol : 'bsc' } ) ,
219+ device,
220+ precomposedTx : buildPrecomposedTransaction ( { to : WBNB_BSC } ) ,
221+ transactionData : WETH_DEPOSIT_DATA ,
222+ } ) ;
223+
224+ expect ( result ) . toBe ( false ) ;
225+ } ) ;
226+
227+ it ( 'returns false when the device cannot clear-sign' , ( ) => {
228+ const result = isClearSignedWrappedNativeTransaction ( {
229+ account,
230+ device : mockSuiteDevice (
231+ { unavailableCapabilities : { evmClearSigning : 'no-support' } } ,
232+ { major_version : 2 , minor_version : 8 , patch_version : 0 } ,
233+ ) ,
234+ precomposedTx : buildPrecomposedTransaction ( { to : WETH_MAINNET } ) ,
235+ transactionData : WETH_DEPOSIT_DATA ,
236+ } ) ;
237+
238+ expect ( result ) . toBe ( false ) ;
239+ } ) ;
240+
241+ it ( 'returns false for an unrelated contract call to the WETH address' , ( ) => {
242+ const result = isClearSignedWrappedNativeTransaction ( {
243+ account,
244+ device,
245+ precomposedTx : buildPrecomposedTransaction ( { to : WETH_MAINNET } ) ,
246+ transactionData : ERC20_TRANSFER_DATA ,
247+ } ) ;
248+
249+ expect ( result ) . toBe ( false ) ;
250+ } ) ;
251+ } ) ;
252+
188253describe ( 'constructTransactionReviewOutputs' , ( ) => {
189254 const account = buildEthereumAccount ( ) ;
190255 const device = buildUpdatedDevice ( ) ;
@@ -393,25 +458,52 @@ describe('constructTransactionReviewOutputs', () => {
393458 it . each ( [
394459 { op : 'wrap' , transactionData : WETH_DEPOSIT_DATA } ,
395460 { op : 'unwrap' , transactionData : WETH_WITHDRAW_DATA } ,
396- ] ) ( 'suppresses the raw data row for a clear-signed WETH $op' , ( { transactionData } ) => {
461+ ] ) ( 'mirrors the four device screens for a clear-signed WETH $op' , ( { transactionData, op } ) => {
397462 const outputs = constructTransactionReviewOutputs ( {
398463 account,
399464 device,
400465 decreaseOutputId : undefined ,
401466 precomposedForm : buildFormState ( { transactionData } ) ,
467+ precomposedTx : buildPrecomposedTransaction ( {
468+ to : WETH_MAINNET ,
469+ // An unwrap review carries the WETH token; the amount row must still be
470+ // native, matching the device's AmountFormatter.
471+ token : op === 'unwrap' ? wethToken : undefined ,
472+ } ) ,
473+ } ) ;
474+
475+ // `confirm_ethereum_clear_signing` walks provider → intent → amount → summary. The
476+ // summary is the review's own total row, so three outputs precede it.
477+ expect ( outputs ) . toEqual ( [
478+ { type : 'recipient_name' , value : 'WETH' } ,
479+ { type : 'contract_intent' , value : '' } ,
480+ { type : 'amount' , value : '1000000' } ,
481+ ] ) ;
482+ // No token on the amount row: wrapping is 1:1 and the device prints ETH both ways.
483+ expect ( outputs [ 2 ] ) . not . toHaveProperty ( 'token' ) ;
484+ } ) ;
485+
486+ it ( 'renders the blind-signing rows for a WETH wrap on firmware without clear signing' , ( ) => {
487+ const outputs = constructTransactionReviewOutputs ( {
488+ account,
489+ device : mockSuiteDevice (
490+ { unavailableCapabilities : { evmClearSigning : 'update-required' } } ,
491+ { major_version : 2 , minor_version : 8 , patch_version : 0 } ,
492+ ) ,
493+ decreaseOutputId : undefined ,
494+ precomposedForm : buildFormState ( { transactionData : WETH_DEPOSIT_DATA } ) ,
402495 precomposedTx : buildPrecomposedTransaction ( { to : WETH_MAINNET } ) ,
403496 } ) ;
404497
405- // The device clear-signs the intent + amount, so the raw calldata row is dropped...
406- expect ( outputs ) . not . toEqual (
407- expect . arrayContaining ( [ expect . objectContaining ( { type : 'data' } ) ] ) ,
408- ) ;
409- // ...but the contract address (and the fee/total summary) still show.
410498 expect ( outputs ) . toEqual (
411499 expect . arrayContaining ( [
500+ expect . objectContaining ( { type : 'data' , value : WETH_DEPOSIT_DATA } ) ,
412501 expect . objectContaining ( { type : 'contract' , value : WETH_MAINNET } ) ,
413502 ] ) ,
414503 ) ;
504+ expect ( outputs ) . not . toEqual (
505+ expect . arrayContaining ( [ expect . objectContaining ( { type : 'contract_intent' } ) ] ) ,
506+ ) ;
415507 } ) ;
416508
417509 it ( 'keeps the raw data row for a wrapped native the firmware does not clear-sign (WBNB on BSC)' , ( ) => {
0 commit comments