1818using Thirdweb . Wallets ;
1919using Nethereum . Signer ;
2020using System . Security . Cryptography ;
21+ using Newtonsoft . Json . Linq ;
2122
2223namespace Thirdweb . AccountAbstraction
2324{
@@ -50,6 +51,7 @@ public class SmartWallet
5051 public bool IsDeploying => _deploying ;
5152
5253 private readonly ThirdwebSDK _sdk ;
54+ private bool IsZkSync => _sdk . Session . ChainId == 300 || _sdk . Session . ChainId == 324 ;
5355
5456 public SmartWallet ( IThirdwebWallet personalWallet , ThirdwebSDK sdk )
5557 {
@@ -72,6 +74,13 @@ internal async Task Initialize(string smartWalletOverride = null)
7274 if ( _initialized )
7375 return ;
7476
77+ if ( IsZkSync )
78+ {
79+ Accounts = new List < string > ( ) { await GetPersonalAddress ( ) } ;
80+ _initialized = true ;
81+ return ;
82+ }
83+
7584 var predictedAccount =
7685 smartWalletOverride
7786 ?? (
@@ -95,18 +104,33 @@ internal async Task Initialize(string smartWalletOverride = null)
95104
96105 internal async Task UpdateDeploymentStatus ( )
97106 {
107+ if ( IsZkSync )
108+ {
109+ _deployed = true ;
110+ return ;
111+ }
112+
98113 var web3 = Utils . GetWeb3 ( _sdk . Session . ChainId , _sdk . Session . Options . clientId , _sdk . Session . Options . bundleId ) ;
99114 var bytecode = await web3 . Eth . GetCode . SendRequestAsync ( Accounts [ 0 ] ) ;
100115 _deployed = bytecode != "0x" ;
101116 }
102117
103118 internal async Task < TransactionResult > SetPermissionsForSigner ( SignerPermissionRequest signerPermissionRequest , byte [ ] signature )
104119 {
120+ if ( IsZkSync )
121+ {
122+ throw new NotImplementedException ( "SetPermissionsForSigner is not supported on zkSync" ) ;
123+ }
105124 return await TransactionManager . ThirdwebWrite ( _sdk , Accounts [ 0 ] , new SetPermissionsForSignerFunction ( ) { Req = signerPermissionRequest , Signature = signature } ) ;
106125 }
107126
108127 internal async Task ForceDeploy ( )
109128 {
129+ if ( IsZkSync )
130+ {
131+ return ;
132+ }
133+
110134 if ( _deployed )
111135 return ;
112136
@@ -118,6 +142,11 @@ internal async Task ForceDeploy()
118142
119143 internal async Task < bool > VerifySignature ( byte [ ] hash , byte [ ] signature )
120144 {
145+ if ( IsZkSync )
146+ {
147+ throw new NotImplementedException ( "VerifySignature is not supported on zkSync" ) ;
148+ }
149+
121150 try
122151 {
123152 var verifyRes = await TransactionManager . ThirdwebRead < AccountContract . IsValidSignatureFunction , AccountContract . IsValidSignatureOutputDTO > (
@@ -136,6 +165,11 @@ internal async Task<bool> VerifySignature(byte[] hash, byte[] signature)
136165
137166 internal async Task < ( byte [ ] initCode , BigInteger gas ) > GetInitCode ( )
138167 {
168+ if ( IsZkSync )
169+ {
170+ throw new NotImplementedException ( "GetInitCode is not supported on zkSync" ) ;
171+ }
172+
139173 if ( _deployed )
140174 return ( new byte [ ] { } , 0 ) ;
141175
@@ -153,13 +187,25 @@ internal async Task<RpcResponseMessage> Request(RpcRequestMessage requestMessage
153187
154188 if ( requestMessage . Method == "eth_signTransaction" )
155189 {
190+ if ( IsZkSync )
191+ {
192+ throw new NotImplementedException ( "eth_signTransaction is not supported on zkSync" ) ;
193+ }
194+
156195 var parameters = JsonConvert . DeserializeObject < object [ ] > ( JsonConvert . SerializeObject ( requestMessage . RawParameters ) ) ;
157196 var txInput = JsonConvert . DeserializeObject < TransactionInput > ( JsonConvert . SerializeObject ( parameters [ 0 ] ) ) ;
158197 var partialUserOp = await SignTransactionAsUserOp ( txInput , requestMessage . Id ) ;
159198 return new RpcResponseMessage ( requestMessage . Id , JsonConvert . SerializeObject ( EncodeUserOperation ( partialUserOp ) ) ) ;
160199 }
161200 else if ( requestMessage . Method == "eth_sendTransaction" )
162201 {
202+ if ( IsZkSync )
203+ {
204+ var paramList = JsonConvert . DeserializeObject < List < object > > ( JsonConvert . SerializeObject ( requestMessage . RawParameters ) ) ;
205+ var transactionInput = JsonConvert . DeserializeObject < TransactionInput > ( JsonConvert . SerializeObject ( paramList [ 0 ] ) ) ;
206+ var hash = await SendZkSyncAATransaction ( transactionInput ) ;
207+ return new RpcResponseMessage ( requestMessage . Id , hash ) ;
208+ }
163209 return await CreateUserOpAndSend ( requestMessage ) ;
164210 }
165211 else if ( requestMessage . Method == "eth_chainId" )
@@ -188,6 +234,82 @@ internal async Task<RpcResponseMessage> Request(RpcRequestMessage requestMessage
188234 }
189235 }
190236
237+ private async Task < string > SendZkSyncAATransaction ( TransactionInput transactionInput )
238+ {
239+ var transaction = new Transaction ( _sdk , transactionInput ) ;
240+ var web3 = Utils . GetWeb3 ( _sdk . Session . ChainId , _sdk . Session . Options . clientId , _sdk . Session . Options . bundleId ) ;
241+
242+ if ( transactionInput . Nonce == null )
243+ {
244+ var nonce = await web3 . Client . SendRequestAsync < HexBigInteger > ( method : "eth_getTransactionCount" , route : null , paramList : new object [ ] { Accounts [ 0 ] , "latest" } ) ;
245+ _ = transaction . SetNonce ( nonce . Value . ToString ( ) ) ;
246+ }
247+
248+ var feeData = await web3 . Client . SendRequestAsync < JToken > ( method : "zks_estimateFee" , route : null , paramList : new object [ ] { transactionInput , "latest" } ) ;
249+ var maxFee = feeData [ "max_fee_per_gas" ] . ToObject < HexBigInteger > ( ) . Value * 10 / 5 ;
250+ var maxPriorityFee = feeData [ "max_priority_fee_per_gas" ] . ToObject < HexBigInteger > ( ) . Value * 10 / 5 ;
251+ var gasPerPubData = feeData [ "gas_per_pubdata_limit" ] . ToObject < HexBigInteger > ( ) . Value ;
252+ var gasLimit = feeData [ "gas_limit" ] . ToObject < HexBigInteger > ( ) . Value * 10 / 5 ;
253+
254+ if ( _sdk . Session . Options . smartWalletConfig ? . gasless == true )
255+ {
256+ var pmDataResult = await BundlerClient . ZkPaymasterData (
257+ _sdk . Session . Options . smartWalletConfig ? . paymasterUrl ,
258+ _sdk . Session . Options . clientId ,
259+ _sdk . Session . Options . bundleId ,
260+ 1 ,
261+ transactionInput
262+ ) ;
263+
264+ var zkTx = new AccountAbstraction . ZkSyncAATransaction
265+ {
266+ TxType = 0x71 ,
267+ From = new HexBigInteger ( transaction . Input . From ) . Value ,
268+ To = new HexBigInteger ( transaction . Input . To ) . Value ,
269+ GasLimit = gasLimit ,
270+ GasPerPubdataByteLimit = gasPerPubData ,
271+ MaxFeePerGas = maxFee ,
272+ MaxPriorityFeePerGas = maxPriorityFee ,
273+ Paymaster = new HexBigInteger ( pmDataResult . paymaster ) . Value ,
274+ Nonce = transaction . Input . Nonce . Value ,
275+ Value = transaction . Input . Value ? . Value ?? 0 ,
276+ Data = transaction . Input . Data ? . HexToByteArray ( ) ?? new byte [ 0 ] ,
277+ FactoryDeps = new List < byte [ ] > ( ) ,
278+ PaymasterInput = pmDataResult . paymasterInput ? . HexToByteArray ( ) ?? new byte [ 0 ]
279+ } ;
280+
281+ var zkTxSigned = await EIP712 . GenerateSignature_ZkSyncTransaction ( _sdk , "zkSync" , "2" , _sdk . Session . ChainId , zkTx ) ;
282+
283+ // Match bundler ZkTransactionInput type without recreating
284+ var zkBroadcastResult = await BundlerClient . ZkBroadcastTransaction (
285+ _sdk . Session . Options . smartWalletConfig ? . paymasterUrl ,
286+ _sdk . Session . Options . clientId ,
287+ _sdk . Session . Options . bundleId ,
288+ 1 ,
289+ new
290+ {
291+ nonce = zkTx . Nonce . ToString ( ) ,
292+ from = zkTx . From ,
293+ to = zkTx . To ,
294+ gas = zkTx . GasLimit . ToString ( ) ,
295+ gasPrice = string . Empty ,
296+ value = zkTx . Value . ToString ( ) ,
297+ data = Utils . ByteArrayToHexString ( zkTx . Data ) ,
298+ maxFeePerGas = zkTx . MaxFeePerGas . ToString ( ) ,
299+ maxPriorityFeePerGas = zkTx . MaxPriorityFeePerGas . ToString ( ) ,
300+ chainId = _sdk . Session . ChainId . ToString ( ) ,
301+ signedTransaction = zkTxSigned ,
302+ paymaster = pmDataResult . paymaster ,
303+ }
304+ ) ;
305+ return zkBroadcastResult . transactionHash ;
306+ }
307+ else
308+ {
309+ throw new NotImplementedException ( "ZkSync Smart Wallet transactions are not supported without gasless mode" ) ;
310+ }
311+ }
312+
191313 private async Task < EntryPointContract . UserOperation > SignTransactionAsUserOp ( TransactionInput transactionInput , object requestId = null )
192314 {
193315 requestId ??= SmartWalletClient . GenerateRpcId ( ) ;
0 commit comments