@@ -2,7 +2,7 @@ import type { components, paths } from "./serverTypes";
2
2
import createClient , {
3
3
ClientOptions as FetchClientOptions ,
4
4
} from "openapi-fetch" ;
5
- import { Address , Hex , isAddress , isHex } from "viem" ;
5
+ import { Address , Hex , isAddress , isHex , getContractAddress } from "viem" ;
6
6
import { privateKeyToAccount , signTypedData } from "viem/accounts" ;
7
7
import WebSocket from "isomorphic-ws" ;
8
8
import {
@@ -11,11 +11,12 @@ import {
11
11
BidParams ,
12
12
BidStatusUpdate ,
13
13
Opportunity ,
14
- EIP712Domain ,
14
+ OpportunityAdapterConfig ,
15
15
OpportunityBid ,
16
16
OpportunityParams ,
17
17
TokenAmount ,
18
18
BidsResponse ,
19
+ TokenPermissions ,
19
20
} from "./types" ;
20
21
21
22
export * from "./types" ;
@@ -59,6 +60,50 @@ export function checkTokenQty(token: {
59
60
} ;
60
61
}
61
62
63
+ export const OPPORTUNITY_ADAPTER_CONFIGS : Record <
64
+ string ,
65
+ OpportunityAdapterConfig
66
+ > = {
67
+ op_sepolia : {
68
+ chain_id : 11155420 ,
69
+ opportunity_adapter_factory : "0xfA119693864b2F185742A409c66f04865c787754" ,
70
+ opportunity_adapter_init_bytecode_hash :
71
+ "0x3d71516d94b96a8fdca4e3a5825a6b41c9268a8e94610367e69a8462cc543533" ,
72
+ permit2 : "0x000000000022D473030F116dDEE9F6B43aC78BA3" ,
73
+ weth : "0x74A4A85C611679B73F402B36c0F84A7D2CcdFDa3" ,
74
+ } ,
75
+ } ;
76
+
77
+ /**
78
+ * Converts sellTokens, bidAmount, and callValue to permitted tokens
79
+ * @param tokens List of sellTokens
80
+ * @param bidAmount
81
+ * @param callValue
82
+ * @param weth
83
+ * @returns List of permitted tokens
84
+ */
85
+ function getPermittedTokens (
86
+ tokens : TokenAmount [ ] ,
87
+ bidAmount : bigint ,
88
+ callValue : bigint ,
89
+ weth : Address
90
+ ) : TokenPermissions [ ] {
91
+ const permitted : TokenPermissions [ ] = tokens . map ( ( { token, amount } ) => ( {
92
+ token,
93
+ amount,
94
+ } ) ) ;
95
+ const wethIndex = permitted . findIndex ( ( { token } ) => token === weth ) ;
96
+ const extraWethNeeded = bidAmount + callValue ;
97
+ if ( wethIndex !== - 1 ) {
98
+ permitted [ wethIndex ] . amount += extraWethNeeded ;
99
+ return permitted ;
100
+ }
101
+ if ( extraWethNeeded > 0 ) {
102
+ permitted . push ( { token : weth , amount : extraWethNeeded } ) ;
103
+ }
104
+ return permitted ;
105
+ }
106
+
62
107
export class Client {
63
108
public clientOptions : ClientOptions ;
64
109
public wsOptions : WsOptions ;
@@ -145,21 +190,11 @@ export class Client {
145
190
} ) ;
146
191
}
147
192
148
- private convertEIP712Domain (
149
- eip712Domain : components [ "schemas" ] [ "EIP712Domain" ]
150
- ) : EIP712Domain {
151
- return {
152
- name : eip712Domain . name ,
153
- version : eip712Domain . version ,
154
- verifyingContract : checkAddress ( eip712Domain . verifying_contract ) ,
155
- chainId : BigInt ( eip712Domain . chain_id ) ,
156
- } ;
157
- }
158
-
159
193
/**
160
194
* Converts an opportunity from the server to the client format
161
195
* Returns undefined if the opportunity version is not supported
162
196
* @param opportunity
197
+ * @returns Opportunity in the converted client format
163
198
*/
164
199
private convertOpportunity (
165
200
opportunity : components [ "schemas" ] [ "OpportunityParamsWithMetadata" ]
@@ -179,7 +214,6 @@ export class Client {
179
214
targetCallValue : BigInt ( opportunity . target_call_value ) ,
180
215
sellTokens : opportunity . sell_tokens . map ( checkTokenQty ) ,
181
216
buyTokens : opportunity . buy_tokens . map ( checkTokenQty ) ,
182
- eip712Domain : this . convertEIP712Domain ( opportunity . eip_712_domain ) ,
183
217
} ;
184
218
}
185
219
@@ -256,6 +290,7 @@ export class Client {
256
290
/**
257
291
* Fetches opportunities
258
292
* @param chainId Chain id to fetch opportunities for. e.g: sepolia
293
+ * @returns List of opportunities
259
294
*/
260
295
async getOpportunities ( chainId ?: string ) : Promise < Opportunity [ ] > {
261
296
const client = createClient < paths > ( this . clientOptions ) ;
@@ -308,47 +343,77 @@ export class Client {
308
343
* @param opportunity Opportunity to bid on
309
344
* @param bidParams Bid amount and valid until timestamp
310
345
* @param privateKey Private key to sign the bid with
346
+ * @returns Signed opportunity bid
311
347
*/
312
348
async signOpportunityBid (
313
349
opportunity : Opportunity ,
314
350
bidParams : BidParams ,
315
351
privateKey : Hex
316
352
) : Promise < OpportunityBid > {
317
353
const types = {
318
- ExecutionParams : [
319
- { name : "sellTokens" , type : "TokenAmount[]" } ,
354
+ PermitBatchWitnessTransferFrom : [
355
+ { name : "permitted" , type : "TokenPermissions[]" } ,
356
+ { name : "spender" , type : "address" } ,
357
+ { name : "nonce" , type : "uint256" } ,
358
+ { name : "deadline" , type : "uint256" } ,
359
+ { name : "witness" , type : "OpportunityWitness" } ,
360
+ ] ,
361
+ OpportunityWitness : [
320
362
{ name : "buyTokens" , type : "TokenAmount[]" } ,
321
363
{ name : "executor" , type : "address" } ,
322
364
{ name : "targetContract" , type : "address" } ,
323
365
{ name : "targetCalldata" , type : "bytes" } ,
324
366
{ name : "targetCallValue" , type : "uint256" } ,
325
- { name : "validUntil" , type : "uint256" } ,
326
367
{ name : "bidAmount" , type : "uint256" } ,
327
368
] ,
328
369
TokenAmount : [
329
370
{ name : "token" , type : "address" } ,
330
371
{ name : "amount" , type : "uint256" } ,
331
372
] ,
373
+ TokenPermissions : [
374
+ { name : "token" , type : "address" } ,
375
+ { name : "amount" , type : "uint256" } ,
376
+ ] ,
332
377
} ;
333
378
334
379
const account = privateKeyToAccount ( privateKey ) ;
380
+ const opportunityAdapterConfig =
381
+ OPPORTUNITY_ADAPTER_CONFIGS [ opportunity . chainId ] ;
382
+ const create2Address = getContractAddress ( {
383
+ bytecodeHash :
384
+ opportunityAdapterConfig . opportunity_adapter_init_bytecode_hash ,
385
+ from : opportunityAdapterConfig . opportunity_adapter_factory ,
386
+ opcode : "CREATE2" ,
387
+ salt : `0x${ account . address . replace ( "0x" , "" ) . padStart ( 64 , "0" ) } ` ,
388
+ } ) ;
389
+
335
390
const signature = await signTypedData ( {
336
391
privateKey,
337
392
domain : {
338
- ...opportunity . eip712Domain ,
339
- chainId : Number ( opportunity . eip712Domain . chainId ) ,
393
+ name : "Permit2" ,
394
+ verifyingContract : checkAddress ( opportunityAdapterConfig . permit2 ) ,
395
+ chainId : opportunityAdapterConfig . chain_id ,
340
396
} ,
341
397
types,
342
- primaryType : "ExecutionParams " ,
398
+ primaryType : "PermitBatchWitnessTransferFrom " ,
343
399
message : {
344
- sellTokens : opportunity . sellTokens ,
345
- buyTokens : opportunity . buyTokens ,
346
- executor : account . address ,
347
- targetContract : opportunity . targetContract ,
348
- targetCalldata : opportunity . targetCalldata ,
349
- targetCallValue : opportunity . targetCallValue ,
350
- validUntil : bidParams . validUntil ,
351
- bidAmount : bidParams . amount ,
400
+ permitted : getPermittedTokens (
401
+ opportunity . sellTokens ,
402
+ bidParams . amount ,
403
+ opportunity . targetCallValue ,
404
+ checkAddress ( opportunityAdapterConfig . weth )
405
+ ) ,
406
+ spender : create2Address ,
407
+ nonce : bidParams . nonce ,
408
+ deadline : bidParams . deadline ,
409
+ witness : {
410
+ buyTokens : opportunity . buyTokens ,
411
+ executor : account . address ,
412
+ targetContract : opportunity . targetContract ,
413
+ targetCalldata : opportunity . targetCalldata ,
414
+ targetCallValue : opportunity . targetCallValue ,
415
+ bidAmount : bidParams . amount ,
416
+ } ,
352
417
} ,
353
418
} ) ;
354
419
@@ -369,7 +434,8 @@ export class Client {
369
434
executor : bid . executor ,
370
435
permission_key : bid . permissionKey ,
371
436
signature : bid . signature ,
372
- valid_until : bid . bid . validUntil . toString ( ) ,
437
+ deadline : bid . bid . deadline . toString ( ) ,
438
+ nonce : bid . bid . nonce . toString ( ) ,
373
439
} ;
374
440
}
375
441
0 commit comments