17
17
18
18
#![ cfg_attr( not( test) , warn( unused_crate_dependencies) ) ]
19
19
20
- use alloy_primitives:: { map:: HashMap , Address , ChainId , TxHash } ;
20
+ use alloy_network:: {
21
+ eip2718:: Encodable2718 , Ethereum , EthereumWallet , NetworkWallet , TransactionBuilder ,
22
+ } ;
23
+ use alloy_primitives:: { map:: HashMap , Address , ChainId , TxHash , TxKind , U256 } ;
21
24
use alloy_rpc_types:: TransactionRequest ;
22
- use jsonrpsee:: { core:: RpcResult , proc_macros:: rpc} ;
25
+ use jsonrpsee:: {
26
+ core:: { async_trait, RpcResult } ,
27
+ proc_macros:: rpc,
28
+ } ;
29
+ use reth_primitives:: { revm_primitives:: Bytecode , BlockId } ;
30
+ use reth_rpc_eth_api:: helpers:: { EthCall , EthState , EthTransactions , FullEthApi } ;
31
+ use reth_storage_api:: { StateProvider , StateProviderFactory } ;
23
32
use serde:: { Deserialize , Serialize } ;
33
+ use tracing:: trace;
24
34
25
35
/// The capability to perform [EIP-7702][eip-7702] delegations, sponsored by the sequencer.
26
36
///
@@ -45,6 +55,13 @@ pub struct Capabilities {
45
55
#[ derive( Debug , Clone , Deserialize , Serialize ) ]
46
56
pub struct WalletCapabilities ( pub HashMap < ChainId , Capabilities > ) ;
47
57
58
+ impl WalletCapabilities {
59
+ /// Get the capabilities of the wallet API for the specified chain ID.
60
+ pub fn get ( & self , chain_id : ChainId ) -> Option < & Capabilities > {
61
+ self . 0 . get ( & chain_id)
62
+ }
63
+ }
64
+
48
65
/// AlphaNet `wallet_` RPC namespace.
49
66
#[ cfg_attr( not( test) , rpc( server, namespace = "wallet" ) ) ]
50
67
#[ cfg_attr( test, rpc( server, client, namespace = "wallet" ) ) ]
@@ -75,5 +92,200 @@ pub trait AlphaNetWalletApi {
75
92
/// [eip-7702]: https://eips.ethereum.org/EIPS/eip-7702
76
93
/// [eip-1559]: https://eips.ethereum.org/EIPS/eip-1559
77
94
#[ method( name = "sendTransaction" ) ]
78
- fn send_transaction ( & self , request : TransactionRequest ) -> RpcResult < TxHash > ;
95
+ async fn send_transaction ( & self , request : TransactionRequest ) -> RpcResult < TxHash > ;
96
+ }
97
+
98
+ /// Errors returned by the wallet API.
99
+ #[ derive( Debug , thiserror:: Error ) ]
100
+ pub enum AlphaNetWalletError {
101
+ /// The transaction value is not 0.
102
+ ///
103
+ /// The value should be 0 to prevent draining the sequencer.
104
+ #[ error( "tx value not zero" ) ]
105
+ ValueNotZero ,
106
+ /// The from field is set on the transaction.
107
+ ///
108
+ /// Requests with the from field are rejected, since it is implied that it will always be the
109
+ /// sequencer.
110
+ #[ error( "tx from field is set" ) ]
111
+ FromSet ,
112
+ /// The nonce field is set on the transaction.
113
+ ///
114
+ /// Requests with the nonce field set are rejected, as this is managed by the sequencer.
115
+ #[ error( "tx nonce is set" ) ]
116
+ NonceSet ,
117
+ /// An authorization item was invalid.
118
+ ///
119
+ /// The item is invalid if it tries to delegate an account to a contract that is not
120
+ /// whitelisted.
121
+ #[ error( "invalid authorization address" ) ]
122
+ InvalidAuthorization ,
123
+ /// The to field of the transaction was invalid.
124
+ ///
125
+ /// The destination is invalid if:
126
+ ///
127
+ /// - There is no bytecode at the destination, or
128
+ /// - The bytecode is not an EIP-7702 delegation designator, or
129
+ /// - The delegation designator points to a contract that is not whitelisted
130
+ #[ error( "the destination of the transaction is not a delegated account" ) ]
131
+ IllegalDestination ,
132
+ /// The transaction request was invalid.
133
+ ///
134
+ /// This is likely an internal error, as most of the request is built by the sequencer.
135
+ #[ error( "invalid tx request" ) ]
136
+ InvalidTransactionRequest ,
137
+ }
138
+
139
+ impl From < AlphaNetWalletError > for jsonrpsee:: types:: error:: ErrorObject < ' static > {
140
+ fn from ( error : AlphaNetWalletError ) -> Self {
141
+ jsonrpsee:: types:: error:: ErrorObject :: owned :: < ( ) > (
142
+ jsonrpsee:: types:: error:: INVALID_PARAMS_CODE ,
143
+ error. to_string ( ) ,
144
+ None ,
145
+ )
146
+ }
147
+ }
148
+
149
+ /// Implementation of the AlphaNet `wallet_` namespace.
150
+ pub struct AlphaNetWallet < Provider , Eth > {
151
+ provider : Provider ,
152
+ wallet : EthereumWallet ,
153
+ chain_id : ChainId ,
154
+ capabilities : WalletCapabilities ,
155
+ eth_api : Eth ,
156
+ }
157
+
158
+ impl < Provider , Eth > AlphaNetWallet < Provider , Eth > {
159
+ /// Create a new AlphaNet wallet module.
160
+ pub fn new (
161
+ provider : Provider ,
162
+ wallet : EthereumWallet ,
163
+ eth_api : Eth ,
164
+ chain_id : ChainId ,
165
+ valid_designations : Vec < Address > ,
166
+ ) -> Self {
167
+ let mut caps = HashMap :: default ( ) ;
168
+ caps. insert (
169
+ chain_id,
170
+ Capabilities { delegation : DelegationCapability { addresses : valid_designations } } ,
171
+ ) ;
172
+
173
+ Self { provider, wallet, eth_api, chain_id, capabilities : WalletCapabilities ( caps) }
174
+ }
175
+ }
176
+
177
+ #[ async_trait]
178
+ impl < Provider , Eth > AlphaNetWalletApiServer for AlphaNetWallet < Provider , Eth >
179
+ where
180
+ Provider : StateProviderFactory + Send + Sync + ' static ,
181
+ Eth : FullEthApi + Send + Sync + ' static ,
182
+ {
183
+ fn get_capabilities ( & self ) -> RpcResult < WalletCapabilities > {
184
+ trace ! ( target: "rpc::wallet" , "Serving wallet_getCapabilities" ) ;
185
+ Ok ( self . capabilities . clone ( ) )
186
+ }
187
+
188
+ async fn send_transaction ( & self , mut request : TransactionRequest ) -> RpcResult < TxHash > {
189
+ trace ! ( target: "rpc::wallet" , ?request, "Serving wallet_sendTransaction" ) ;
190
+
191
+ // validate fields common to eip-7702 and eip-1559
192
+ validate_tx_request ( & request) ?;
193
+
194
+ let valid_delegations: & [ Address ] = self
195
+ . capabilities
196
+ . get ( self . chain_id )
197
+ . map ( |caps| caps. delegation . addresses . as_ref ( ) )
198
+ . unwrap_or_default ( ) ;
199
+ if let Some ( authorizations) = & request. authorization_list {
200
+ // check that all auth items delegate to a valid address
201
+ if authorizations. iter ( ) . any ( |auth| !valid_delegations. contains ( & auth. address ) ) {
202
+ return Err ( AlphaNetWalletError :: InvalidAuthorization . into ( ) ) ;
203
+ }
204
+ }
205
+
206
+ // validate destination
207
+ match ( request. authorization_list . is_some ( ) , request. to ) {
208
+ // if this is an eip-1559 tx, ensure that it is an account that delegates to a
209
+ // whitelisted address
210
+ ( false , Some ( TxKind :: Call ( addr) ) ) => {
211
+ let state = self . provider . latest ( ) . unwrap ( ) ;
212
+ let delegated_address = state
213
+ . account_code ( addr)
214
+ . ok ( )
215
+ . flatten ( )
216
+ . and_then ( |code| match code. 0 {
217
+ Bytecode :: Eip7702 ( code) => Some ( code. address ( ) ) ,
218
+ _ => None ,
219
+ } )
220
+ . unwrap_or_default ( ) ;
221
+
222
+ // not a whitelisted address, or not an eip-7702 bytecode
223
+ if delegated_address == Address :: ZERO
224
+ || !valid_delegations. contains ( & delegated_address)
225
+ {
226
+ return Err ( AlphaNetWalletError :: IllegalDestination . into ( ) ) ;
227
+ }
228
+ }
229
+ // if it's an eip-7702 tx, let it through
230
+ ( true , _) => ( ) ,
231
+ // create tx's disallowed
232
+ _ => return Err ( AlphaNetWalletError :: IllegalDestination . into ( ) ) ,
233
+ }
234
+
235
+ // set nonce
236
+ let tx_count = EthState :: transaction_count (
237
+ & self . eth_api ,
238
+ NetworkWallet :: < Ethereum > :: default_signer_address ( & self . wallet ) ,
239
+ Some ( BlockId :: pending ( ) ) ,
240
+ )
241
+ . await
242
+ . map_err ( Into :: into) ?;
243
+ request. nonce = Some ( tx_count. to ( ) ) ;
244
+
245
+ // set chain id
246
+ request. chain_id = Some ( self . chain_id ) ;
247
+
248
+ // set gas limit
249
+ let estimate =
250
+ EthCall :: estimate_gas_at ( & self . eth_api , request. clone ( ) , BlockId :: latest ( ) , None )
251
+ . await
252
+ . map_err ( Into :: into) ?;
253
+ request = request. gas_limit ( estimate. to ( ) ) ;
254
+
255
+ // build and sign
256
+ let envelope =
257
+ <TransactionRequest as TransactionBuilder < Ethereum > >:: build :: < EthereumWallet > (
258
+ request,
259
+ & self . wallet ,
260
+ )
261
+ . await
262
+ . map_err ( |_| AlphaNetWalletError :: InvalidTransactionRequest ) ?;
263
+
264
+ // this uses the internal `OpEthApi` to either forward the tx to the sequencer, or add it to
265
+ // the txpool
266
+ //
267
+ // see: https://github.com/paradigmxyz/reth/blob/b67f004fbe8e1b7c05f84f314c4c9f2ed9be1891/crates/optimism/rpc/src/eth/transaction.rs#L35-L57
268
+ EthTransactions :: send_raw_transaction ( & self . eth_api , envelope. encoded_2718 ( ) . into ( ) )
269
+ . await
270
+ . map_err ( Into :: into)
271
+ }
272
+ }
273
+
274
+ fn validate_tx_request ( request : & TransactionRequest ) -> Result < ( ) , AlphaNetWalletError > {
275
+ // reject transactions that have a non-zero value to prevent draining the sequencer.
276
+ if request. value . is_some_and ( |val| val > U256 :: ZERO ) {
277
+ return Err ( AlphaNetWalletError :: ValueNotZero . into ( ) ) ;
278
+ }
279
+
280
+ // reject transactions that have from set, as this will be the sequencer.
281
+ if request. from . is_some ( ) {
282
+ return Err ( AlphaNetWalletError :: FromSet . into ( ) ) ;
283
+ }
284
+
285
+ // reject transaction requests that have nonce set, as this is managed by the sequencer.
286
+ if request. nonce . is_some ( ) {
287
+ return Err ( AlphaNetWalletError :: NonceSet . into ( ) ) ;
288
+ }
289
+
290
+ Ok ( ( ) )
79
291
}
0 commit comments