@@ -2,24 +2,25 @@ use std::{path::PathBuf, str::FromStr, time::Duration};
22
33use alloy_consensus:: { SignableTransaction , Signed } ;
44use alloy_ens:: NameOrAddress ;
5- use alloy_network:: { Ethereum , EthereumWallet , Network , TransactionBuilder } ;
6- use alloy_primitives:: Address ;
5+ use alloy_network:: { Ethereum , EthereumWallet , Network } ;
6+ use alloy_primitives:: { Address , hex } ;
77use alloy_provider:: { Provider , ProviderBuilder as AlloyProviderBuilder } ;
88use alloy_signer:: { Signature , Signer } ;
99use clap:: Parser ;
1010use eyre:: { Result , eyre} ;
1111use foundry_cli:: { opts:: TransactionOpts , utils:: LoadConfig } ;
1212use foundry_common:: {
13- FoundryTransactionBuilder ,
13+ FoundryTransactionBuilder , QUANTUM_BOOTSTRAP_SELECTOR , QUANTUM_KEYVAULT_ADDRESS ,
14+ derive_primary_pubkey, parse_seed_file, sign_quantum_transaction_request,
1415 fmt:: { UIfmt , UIfmtReceiptExt } ,
1516 provider:: ProviderBuilder ,
1617} ;
18+ use foundry_primitives:: QuantumNetwork ;
1719use foundry_wallets:: { TempoAccessKeyConfig , WalletSigner } ;
1820use tempo_alloy:: TempoNetwork ;
1921
2022use crate :: {
2123 cmd:: tip20:: iso4217_warning_message,
22- quantum:: { QuantumWriteContractV1 , build_phase0_payload, parse_seed_file} ,
2324 tx:: { self , CastTxBuilder , CastTxSender , SendTxOpts } ,
2425} ;
2526use tempo_contracts:: precompiles:: { TIP20_FACTORY_ADDRESS , is_iso4217_currency} ;
@@ -109,38 +110,57 @@ impl SendTxArgs {
109110 }
110111
111112 async fn run_quantum ( self ) -> Result < ( ) > {
112- let Self { to, mut sig, args, data, send_tx, command, unlocked, force : _, tx, path } =
113- self ;
113+ let Self {
114+ to,
115+ mut sig,
116+ args,
117+ data,
118+ send_tx,
119+ command,
120+ unlocked,
121+ force : _,
122+ mut tx,
123+ path,
124+ } = self ;
114125
115126 if unlocked {
116- return Err ( eyre ! ( "the Phase 0 Quantum seam does not support --unlocked" ) ) ;
127+ return Err ( eyre ! ( "the Quantum adapter path does not support --unlocked" ) ) ;
117128 }
118129 if send_tx. browser . browser {
119- return Err ( eyre ! ( "the Phase 0 Quantum seam does not support browser signing" ) ) ;
130+ return Err ( eyre ! ( "the Quantum adapter path does not support browser signing" ) ) ;
120131 }
121132 if tx. tempo . is_tempo ( ) {
122133 return Err ( eyre ! ( "Quantum and Tempo options cannot be combined" ) ) ;
123134 }
124135 if command. is_some ( ) {
125- return Err ( eyre ! ( "the Phase 0 Quantum seam only supports cast send-style call flows" ) ) ;
136+ return Err ( eyre ! ( "the Quantum adapter path only supports cast send-style call flows" ) ) ;
126137 }
127138 if path. is_some ( ) {
128- return Err ( eyre ! ( "the Phase 0 Quantum seam does not support blob data" ) ) ;
139+ return Err ( eyre ! ( "the Quantum adapter path does not support blob data" ) ) ;
129140 }
130141 if let Some ( data) = data {
131142 sig = Some ( data) ;
132143 }
133144
134- let sender = tx. quantum . sender . ok_or_else ( || {
135- eyre ! ( "--quantum.sender is required for the Phase 0 Quantum seam" )
136- } ) ?;
137- let seed_path = tx. quantum . primary_seed_file . as_ref ( ) . ok_or_else ( || {
138- eyre ! ( "--quantum.primary-seed-file is required for the Phase 0 Quantum seam" )
139- } ) ?;
145+ let sender = tx
146+ . quantum
147+ . sender
148+ . ok_or_else ( || eyre ! ( "--quantum.sender is required for Quantum writes" ) ) ?;
149+ validate_quantum_sender ( send_tx. eth . wallet . from , sender) ?;
150+ let seed_path =
151+ tx. quantum . primary_seed_file . as_ref ( ) . ok_or_else ( || {
152+ eyre ! ( "--quantum.primary-seed-file is required for Quantum writes" )
153+ } ) ?;
140154 let primary_seed = parse_seed_file ( seed_path) ?;
141155
156+ if quantum_send_requests_bootstrap ( to. as_ref ( ) , sig. as_deref ( ) )
157+ && tx. quantum . init_primary_pubkey . is_none ( )
158+ {
159+ tx. quantum . init_primary_pubkey = Some ( derive_primary_pubkey ( primary_seed) ) ;
160+ }
161+
142162 let config = send_tx. eth . load_config ( ) ?;
143- let provider = ProviderBuilder :: < Ethereum > :: from_config ( & config) ?. build ( ) ?;
163+ let provider = ProviderBuilder :: < QuantumNetwork > :: from_config ( & config) ?. build ( ) ?;
144164
145165 if let Some ( interval) = send_tx. poll_interval {
146166 provider. client ( ) . set_poll_interval ( Duration :: from_secs ( interval) ) ;
@@ -154,37 +174,7 @@ impl SendTxArgs {
154174 . await ?;
155175
156176 let ( tx_request, _) = builder. build ( sender) . await ?;
157- let kind = TransactionBuilder :: kind ( & tx_request)
158- . ok_or_else ( || eyre ! ( "Quantum Phase 0 requires an explicit call destination" ) ) ?;
159- let max_fee_per_gas = TransactionBuilder :: max_fee_per_gas ( & tx_request)
160- . ok_or_else ( || eyre ! ( "failed to resolve max fee per gas for Quantum transaction" ) ) ?;
161- let max_priority_fee_per_gas = TransactionBuilder :: max_priority_fee_per_gas ( & tx_request)
162- . ok_or_else ( || {
163- eyre ! ( "failed to resolve max priority fee per gas for Quantum transaction" )
164- } ) ?;
165- let gas_limit = TransactionBuilder :: gas_limit ( & tx_request)
166- . ok_or_else ( || eyre ! ( "failed to resolve gas limit for Quantum transaction" ) ) ?;
167- let nonce = TransactionBuilder :: nonce ( & tx_request)
168- . ok_or_else ( || eyre ! ( "failed to resolve nonce for Quantum transaction" ) ) ?;
169- let chain_id = TransactionBuilder :: chain_id ( & tx_request)
170- . ok_or_else ( || eyre ! ( "failed to resolve chain ID for Quantum transaction" ) ) ?;
171-
172- let payload = build_phase0_payload ( QuantumWriteContractV1 {
173- sender,
174- key_id : tx. quantum . resolved_key_id ( ) ,
175- nonce,
176- chain_id,
177- max_priority_fee_per_gas,
178- max_fee_per_gas,
179- gas_limit,
180- kind,
181- value : TransactionBuilder :: value ( & tx_request) . unwrap_or_default ( ) ,
182- input : TransactionBuilder :: input ( & tx_request) . cloned ( ) . unwrap_or_default ( ) ,
183- access_list : TransactionBuilder :: access_list ( & tx_request)
184- . cloned ( )
185- . unwrap_or_default ( ) ,
186- primary_seed,
187- } ) ?;
177+ let payload = sign_quantum_transaction_request ( tx_request, primary_seed) ?;
188178
189179 let timeout = send_tx. timeout . unwrap_or ( config. transaction_timeout ) ;
190180 let cast = CastTxSender :: new ( & provider) ;
@@ -418,6 +408,41 @@ impl SendTxArgs {
418408 }
419409}
420410
411+ fn validate_quantum_sender ( cli_from : Option < Address > , quantum_sender : Address ) -> Result < ( ) > {
412+ if let Some ( from) = cli_from
413+ && from != quantum_sender
414+ {
415+ eyre:: bail!(
416+ "--from must match --quantum.sender when using the Quantum adapter path"
417+ )
418+ }
419+
420+ Ok ( ( ) )
421+ }
422+
423+ fn quantum_send_requests_bootstrap ( to : Option < & NameOrAddress > , input : Option < & str > ) -> bool {
424+ quantum_destination_is_keyvault ( to) && quantum_input_is_bootstrap ( input)
425+ }
426+
427+ fn quantum_destination_is_keyvault ( to : Option < & NameOrAddress > ) -> bool {
428+ match to {
429+ Some ( NameOrAddress :: Address ( addr) ) => * addr == QUANTUM_KEYVAULT_ADDRESS ,
430+ Some ( NameOrAddress :: Name ( name) ) => {
431+ Address :: from_str ( name) . ok ( ) == Some ( QUANTUM_KEYVAULT_ADDRESS )
432+ }
433+ None => false ,
434+ }
435+ }
436+
437+ fn quantum_input_is_bootstrap ( input : Option < & str > ) -> bool {
438+ let Some ( input) = input else { return false } ;
439+ input. starts_with ( "bootstrapKey(" )
440+ || input
441+ . trim ( )
442+ . trim_start_matches ( "0x" )
443+ . starts_with ( & hex:: encode ( QUANTUM_BOOTSTRAP_SELECTOR ) )
444+ }
445+
421446pub ( crate ) async fn cast_send < N : Network , P : Provider < N > > (
422447 provider : P ,
423448 tx : N :: TransactionRequest ,
0 commit comments