Skip to content

Commit 93efd61

Browse files
authored
feat!(express_relay): Update bid's signature to eip712 (#1455)
1 parent 8be6a9a commit 93efd61

File tree

9 files changed

+180
-93
lines changed

9 files changed

+180
-93
lines changed

express_relay/sdk/js/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

express_relay/sdk/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/express-relay-evm-js",
3-
"version": "0.2.1",
3+
"version": "0.4.0",
44
"description": "Utilities for interacting with the express relay protocol",
55
"homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/express_relay/sdk/js",
66
"author": "Douro Labs",

express_relay/sdk/js/src/index.ts

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,16 @@ import type { components, paths } from "./serverTypes";
22
import createClient, {
33
ClientOptions as FetchClientOptions,
44
} from "openapi-fetch";
5-
import {
6-
Address,
7-
encodeAbiParameters,
8-
Hex,
9-
isAddress,
10-
isHex,
11-
keccak256,
12-
} from "viem";
13-
import { privateKeyToAccount, sign, signatureToHex } from "viem/accounts";
5+
import { Address, Hex, isAddress, isHex } from "viem";
6+
import { privateKeyToAccount, signTypedData } from "viem/accounts";
147
import WebSocket from "isomorphic-ws";
158
import {
169
Bid,
1710
BidId,
1811
BidParams,
1912
BidStatusUpdate,
2013
Opportunity,
14+
EIP712Domain,
2115
OpportunityBid,
2216
OpportunityParams,
2317
TokenAmount,
@@ -136,6 +130,17 @@ export class Client {
136130
});
137131
}
138132

133+
private convertEIP712Domain(
134+
eip712Domain: components["schemas"]["EIP712Domain"]
135+
): EIP712Domain {
136+
return {
137+
name: eip712Domain.name,
138+
version: eip712Domain.version,
139+
verifyingContract: checkAddress(eip712Domain.verifying_contract),
140+
chainId: BigInt(eip712Domain.chain_id),
141+
};
142+
}
143+
139144
/**
140145
* Converts an opportunity from the server to the client format
141146
* Returns undefined if the opportunity version is not supported
@@ -159,6 +164,7 @@ export class Client {
159164
targetCallValue: BigInt(opportunity.target_call_value),
160165
sellTokens: opportunity.sell_tokens.map(checkTokenQty),
161166
buyTokens: opportunity.buy_tokens.map(checkTokenQty),
167+
eip712Domain: this.convertEIP712Domain(opportunity.eip_712_domain),
162168
};
163169
}
164170

@@ -293,62 +299,54 @@ export class Client {
293299
bidParams: BidParams,
294300
privateKey: Hex
295301
): Promise<OpportunityBid> {
296-
const account = privateKeyToAccount(privateKey);
297-
const convertTokenQty = ({ token, amount }: TokenAmount): [Hex, bigint] => [
298-
token,
299-
amount,
300-
];
301-
const payload = encodeAbiParameters(
302-
[
303-
{
304-
name: "repayTokens",
305-
type: "tuple[]",
306-
components: [
307-
{
308-
type: "address",
309-
},
310-
{
311-
type: "uint256",
312-
},
313-
],
314-
},
315-
{
316-
name: "receiptTokens",
317-
type: "tuple[]",
318-
components: [
319-
{
320-
type: "address",
321-
},
322-
{
323-
type: "uint256",
324-
},
325-
],
326-
},
327-
{ name: "contract", type: "address" },
328-
{ name: "calldata", type: "bytes" },
329-
{ name: "value", type: "uint256" },
330-
{ name: "bid", type: "uint256" },
331-
{ name: "validUntil", type: "uint256" },
302+
const types = {
303+
SignedParams: [
304+
{ name: "executionParams", type: "ExecutionParams" },
305+
{ name: "signer", type: "address" },
306+
{ name: "deadline", type: "uint256" },
307+
],
308+
ExecutionParams: [
309+
{ name: "sellTokens", type: "TokenAmount[]" },
310+
{ name: "buyTokens", type: "TokenAmount[]" },
311+
{ name: "targetContract", type: "address" },
312+
{ name: "targetCalldata", type: "bytes" },
313+
{ name: "targetCallValue", type: "uint256" },
314+
{ name: "bidAmount", type: "uint256" },
315+
],
316+
TokenAmount: [
317+
{ name: "token", type: "address" },
318+
{ name: "amount", type: "uint256" },
332319
],
333-
[
334-
opportunity.sellTokens.map(convertTokenQty),
335-
opportunity.buyTokens.map(convertTokenQty),
336-
opportunity.targetContract,
337-
opportunity.targetCalldata,
338-
opportunity.targetCallValue,
339-
bidParams.amount,
340-
bidParams.validUntil,
341-
]
342-
);
320+
};
343321

344-
const msgHash = keccak256(payload);
322+
const account = privateKeyToAccount(privateKey);
323+
const signature = await signTypedData({
324+
privateKey,
325+
domain: {
326+
...opportunity.eip712Domain,
327+
chainId: Number(opportunity.eip712Domain.chainId),
328+
},
329+
types,
330+
primaryType: "SignedParams",
331+
message: {
332+
executionParams: {
333+
sellTokens: opportunity.sellTokens,
334+
buyTokens: opportunity.buyTokens,
335+
targetContract: opportunity.targetContract,
336+
targetCalldata: opportunity.targetCalldata,
337+
targetCallValue: opportunity.targetCallValue,
338+
bidAmount: bidParams.amount,
339+
},
340+
signer: account.address,
341+
deadline: bidParams.validUntil,
342+
},
343+
});
345344

346-
const hash = signatureToHex(await sign({ hash: msgHash, privateKey }));
347345
return {
348346
permissionKey: opportunity.permissionKey,
349347
bid: bidParams,
350348
executor: account.address,
351-
signature: hash,
349+
signature,
352350
opportunityId: opportunity.opportunityId,
353351
};
354352
}

express_relay/sdk/js/src/serverTypes.d.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,28 @@ export interface components {
144144
ClientRequest: components["schemas"]["ClientMessage"] & {
145145
id: string;
146146
};
147+
EIP712Domain: {
148+
/**
149+
* @description The network chain id parameter for EIP712 domain.
150+
* @example 31337
151+
*/
152+
chain_id: string;
153+
/**
154+
* @description The name parameter for the EIP712 domain.
155+
* @example OpportunityAdapter
156+
*/
157+
name: string;
158+
/**
159+
* @description The verifying contract address parameter for the EIP712 domain.
160+
* @example 0xcA11bde05977b3631167028862bE2a173976CA11
161+
*/
162+
verifying_contract: string;
163+
/**
164+
* @description The version parameter for the EIP712 domain.
165+
* @example 1
166+
*/
167+
version: string;
168+
};
147169
ErrorBodyResponse: {
148170
error: string;
149171
};
@@ -220,6 +242,7 @@ export interface components {
220242
* @example 1700000000000000
221243
*/
222244
creation_time: number;
245+
eip_712_domain: components["schemas"]["EIP712Domain"];
223246
/**
224247
* @description The opportunity unique id
225248
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
@@ -302,6 +325,7 @@ export interface components {
302325
* @example 1700000000000000
303326
*/
304327
creation_time: number;
328+
eip_712_domain: components["schemas"]["EIP712Domain"];
305329
/**
306330
* @description The opportunity unique id
307331
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479

express_relay/sdk/js/src/types.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,27 @@ export type BidParams = {
2323
*/
2424
validUntil: bigint;
2525
};
26+
/**
27+
* Represents the configuration for signing an opportunity
28+
*/
29+
export type EIP712Domain = {
30+
/**
31+
* The network chain id for the EIP712 domain.
32+
*/
33+
chainId: bigint;
34+
/**
35+
* The verifying contract address for the EIP712 domain.
36+
*/
37+
verifyingContract: Address;
38+
/**
39+
* The name parameter for the EIP712 domain.
40+
*/
41+
name: string;
42+
/**
43+
* The version parameter for the EIP712 domain.
44+
*/
45+
version: string;
46+
};
2647
/**
2748
* Represents a valid opportunity ready to be executed
2849
*/
@@ -60,11 +81,18 @@ export type Opportunity = {
6081
* Tokens to receive after the opportunity is executed
6182
*/
6283
buyTokens: TokenAmount[];
84+
/**
85+
* The data required to sign the opportunity
86+
*/
87+
eip712Domain: EIP712Domain;
6388
};
6489
/**
6590
* All the parameters necessary to represent an opportunity
6691
*/
67-
export type OpportunityParams = Omit<Opportunity, "opportunityId">;
92+
export type OpportunityParams = Omit<
93+
Opportunity,
94+
"opportunityId" | "eip712Domain"
95+
>;
6896
/**
6997
* Represents a bid for an opportunity
7098
*/

express_relay/sdk/python/express_relay/client.py

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@
66
from collections.abc import Coroutine
77
from uuid import UUID
88
import httpx
9-
import web3
109
import websockets
1110
from websockets.client import WebSocketClientProtocol
12-
from eth_abi import encode
1311
from eth_account.account import Account
14-
from web3.auto import w3
1512
from express_relay.types import (
1613
Opportunity,
1714
BidStatusUpdate,
@@ -405,42 +402,73 @@ def sign_bid(
405402
Returns:
406403
A OpportunityBid object, representing the transaction to submit to the server. This object contains the searcher's signature.
407404
"""
408-
sell_tokens = [
409-
(token.token, int(token.amount)) for token in opportunity.sell_tokens
410-
]
411-
buy_tokens = [(token.token, int(token.amount)) for token in opportunity.buy_tokens]
412-
target_calldata = bytes.fromhex(opportunity.target_calldata.replace("0x", ""))
413-
414-
digest = encode(
415-
[
416-
"(address,uint256)[]",
417-
"(address,uint256)[]",
418-
"address",
419-
"bytes",
420-
"uint256",
421-
"uint256",
422-
"uint256",
405+
406+
executor = Account.from_key(private_key).address
407+
domain_data = {
408+
"name": opportunity.eip_712_domain.name,
409+
"version": opportunity.eip_712_domain.version,
410+
"chainId": opportunity.eip_712_domain.chain_id,
411+
"verifyingContract": opportunity.eip_712_domain.verifying_contract,
412+
}
413+
message_types = {
414+
"SignedParams": [
415+
{"name": "executionParams", "type": "ExecutionParams"},
416+
{"name": "signer", "type": "address"},
417+
{"name": "deadline", "type": "uint256"},
418+
],
419+
"ExecutionParams": [
420+
{"name": "sellTokens", "type": "TokenAmount[]"},
421+
{"name": "buyTokens", "type": "TokenAmount[]"},
422+
{"name": "targetContract", "type": "address"},
423+
{"name": "targetCalldata", "type": "bytes"},
424+
{"name": "targetCallValue", "type": "uint256"},
425+
{"name": "bidAmount", "type": "uint256"},
423426
],
424-
[
425-
sell_tokens,
426-
buy_tokens,
427-
opportunity.target_contract,
428-
target_calldata,
429-
opportunity.target_call_value,
430-
bid_amount,
431-
valid_until,
427+
"TokenAmount": [
428+
{"name": "token", "type": "address"},
429+
{"name": "amount", "type": "uint256"},
432430
],
431+
}
432+
433+
# the data to be signed
434+
message_data = {
435+
"executionParams": {
436+
"sellTokens": [
437+
{
438+
"token": token.token,
439+
"amount": int(token.amount),
440+
}
441+
for token in opportunity.sell_tokens
442+
],
443+
"buyTokens": [
444+
{
445+
"token": token.token,
446+
"amount": int(token.amount),
447+
}
448+
for token in opportunity.buy_tokens
449+
],
450+
"targetContract": opportunity.target_contract,
451+
"targetCalldata": bytes.fromhex(
452+
opportunity.target_calldata.replace("0x", "")
453+
),
454+
"targetCallValue": opportunity.target_call_value,
455+
"bidAmount": bid_amount,
456+
},
457+
"signer": executor,
458+
"deadline": valid_until,
459+
}
460+
461+
signed_typed_data = Account.sign_typed_data(
462+
private_key, domain_data, message_types, message_data
433463
)
434-
msg_data = web3.Web3.solidity_keccak(["bytes"], [digest])
435-
signature = w3.eth.account.signHash(msg_data, private_key=private_key)
436464

437465
opportunity_bid = OpportunityBid(
438466
opportunity_id=opportunity.opportunity_id,
439467
permission_key=opportunity.permission_key,
440468
amount=bid_amount,
441469
valid_until=valid_until,
442-
executor=Account.from_key(private_key).address,
443-
signature=signature,
470+
executor=executor,
471+
signature=signed_typed_data,
444472
)
445473

446474
return opportunity_bid

0 commit comments

Comments
 (0)