18
18
using Thirdweb . Wallets ;
19
19
using Nethereum . Signer ;
20
20
using System . Security . Cryptography ;
21
+ using Newtonsoft . Json . Linq ;
21
22
22
23
namespace Thirdweb . AccountAbstraction
23
24
{
@@ -50,6 +51,7 @@ public class SmartWallet
50
51
public bool IsDeploying => _deploying ;
51
52
52
53
private readonly ThirdwebSDK _sdk ;
54
+ private bool IsZkSync => _sdk . Session . ChainId == 300 || _sdk . Session . ChainId == 324 ;
53
55
54
56
public SmartWallet ( IThirdwebWallet personalWallet , ThirdwebSDK sdk )
55
57
{
@@ -72,6 +74,13 @@ internal async Task Initialize(string smartWalletOverride = null)
72
74
if ( _initialized )
73
75
return ;
74
76
77
+ if ( IsZkSync )
78
+ {
79
+ Accounts = new List < string > ( ) { await GetPersonalAddress ( ) } ;
80
+ _initialized = true ;
81
+ return ;
82
+ }
83
+
75
84
var predictedAccount =
76
85
smartWalletOverride
77
86
?? (
@@ -95,18 +104,33 @@ internal async Task Initialize(string smartWalletOverride = null)
95
104
96
105
internal async Task UpdateDeploymentStatus ( )
97
106
{
107
+ if ( IsZkSync )
108
+ {
109
+ _deployed = true ;
110
+ return ;
111
+ }
112
+
98
113
var web3 = Utils . GetWeb3 ( _sdk . Session . ChainId , _sdk . Session . Options . clientId , _sdk . Session . Options . bundleId ) ;
99
114
var bytecode = await web3 . Eth . GetCode . SendRequestAsync ( Accounts [ 0 ] ) ;
100
115
_deployed = bytecode != "0x" ;
101
116
}
102
117
103
118
internal async Task < TransactionResult > SetPermissionsForSigner ( SignerPermissionRequest signerPermissionRequest , byte [ ] signature )
104
119
{
120
+ if ( IsZkSync )
121
+ {
122
+ throw new NotImplementedException ( "SetPermissionsForSigner is not supported on zkSync" ) ;
123
+ }
105
124
return await TransactionManager . ThirdwebWrite ( _sdk , Accounts [ 0 ] , new SetPermissionsForSignerFunction ( ) { Req = signerPermissionRequest , Signature = signature } ) ;
106
125
}
107
126
108
127
internal async Task ForceDeploy ( )
109
128
{
129
+ if ( IsZkSync )
130
+ {
131
+ return ;
132
+ }
133
+
110
134
if ( _deployed )
111
135
return ;
112
136
@@ -118,6 +142,11 @@ internal async Task ForceDeploy()
118
142
119
143
internal async Task < bool > VerifySignature ( byte [ ] hash , byte [ ] signature )
120
144
{
145
+ if ( IsZkSync )
146
+ {
147
+ throw new NotImplementedException ( "VerifySignature is not supported on zkSync" ) ;
148
+ }
149
+
121
150
try
122
151
{
123
152
var verifyRes = await TransactionManager . ThirdwebRead < AccountContract . IsValidSignatureFunction , AccountContract . IsValidSignatureOutputDTO > (
@@ -136,6 +165,11 @@ internal async Task<bool> VerifySignature(byte[] hash, byte[] signature)
136
165
137
166
internal async Task < ( byte [ ] initCode , BigInteger gas ) > GetInitCode ( )
138
167
{
168
+ if ( IsZkSync )
169
+ {
170
+ throw new NotImplementedException ( "GetInitCode is not supported on zkSync" ) ;
171
+ }
172
+
139
173
if ( _deployed )
140
174
return ( new byte [ ] { } , 0 ) ;
141
175
@@ -153,13 +187,25 @@ internal async Task<RpcResponseMessage> Request(RpcRequestMessage requestMessage
153
187
154
188
if ( requestMessage . Method == "eth_signTransaction" )
155
189
{
190
+ if ( IsZkSync )
191
+ {
192
+ throw new NotImplementedException ( "eth_signTransaction is not supported on zkSync" ) ;
193
+ }
194
+
156
195
var parameters = JsonConvert . DeserializeObject < object [ ] > ( JsonConvert . SerializeObject ( requestMessage . RawParameters ) ) ;
157
196
var txInput = JsonConvert . DeserializeObject < TransactionInput > ( JsonConvert . SerializeObject ( parameters [ 0 ] ) ) ;
158
197
var partialUserOp = await SignTransactionAsUserOp ( txInput , requestMessage . Id ) ;
159
198
return new RpcResponseMessage ( requestMessage . Id , JsonConvert . SerializeObject ( EncodeUserOperation ( partialUserOp ) ) ) ;
160
199
}
161
200
else if ( requestMessage . Method == "eth_sendTransaction" )
162
201
{
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
+ }
163
209
return await CreateUserOpAndSend ( requestMessage ) ;
164
210
}
165
211
else if ( requestMessage . Method == "eth_chainId" )
@@ -188,6 +234,82 @@ internal async Task<RpcResponseMessage> Request(RpcRequestMessage requestMessage
188
234
}
189
235
}
190
236
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
+
191
313
private async Task < EntryPointContract . UserOperation > SignTransactionAsUserOp ( TransactionInput transactionInput , object requestId = null )
192
314
{
193
315
requestId ??= SmartWalletClient . GenerateRpcId ( ) ;
0 commit comments