feat!(express_relay): Update bid's signature to eip712 (#1455)
This commit is contained in:
parent
8be6a9ad1c
commit
93efd61ea4
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/express-relay-evm-js",
|
||||
"version": "0.1.1",
|
||||
"version": "0.4.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/express-relay-evm-js",
|
||||
"version": "0.2.1",
|
||||
"version": "0.4.0",
|
||||
"description": "Utilities for interacting with the express relay protocol",
|
||||
"homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/express_relay/sdk/js",
|
||||
"author": "Douro Labs",
|
||||
|
|
|
@ -2,15 +2,8 @@ import type { components, paths } from "./serverTypes";
|
|||
import createClient, {
|
||||
ClientOptions as FetchClientOptions,
|
||||
} from "openapi-fetch";
|
||||
import {
|
||||
Address,
|
||||
encodeAbiParameters,
|
||||
Hex,
|
||||
isAddress,
|
||||
isHex,
|
||||
keccak256,
|
||||
} from "viem";
|
||||
import { privateKeyToAccount, sign, signatureToHex } from "viem/accounts";
|
||||
import { Address, Hex, isAddress, isHex } from "viem";
|
||||
import { privateKeyToAccount, signTypedData } from "viem/accounts";
|
||||
import WebSocket from "isomorphic-ws";
|
||||
import {
|
||||
Bid,
|
||||
|
@ -18,6 +11,7 @@ import {
|
|||
BidParams,
|
||||
BidStatusUpdate,
|
||||
Opportunity,
|
||||
EIP712Domain,
|
||||
OpportunityBid,
|
||||
OpportunityParams,
|
||||
TokenAmount,
|
||||
|
@ -136,6 +130,17 @@ export class Client {
|
|||
});
|
||||
}
|
||||
|
||||
private convertEIP712Domain(
|
||||
eip712Domain: components["schemas"]["EIP712Domain"]
|
||||
): EIP712Domain {
|
||||
return {
|
||||
name: eip712Domain.name,
|
||||
version: eip712Domain.version,
|
||||
verifyingContract: checkAddress(eip712Domain.verifying_contract),
|
||||
chainId: BigInt(eip712Domain.chain_id),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an opportunity from the server to the client format
|
||||
* Returns undefined if the opportunity version is not supported
|
||||
|
@ -159,6 +164,7 @@ export class Client {
|
|||
targetCallValue: BigInt(opportunity.target_call_value),
|
||||
sellTokens: opportunity.sell_tokens.map(checkTokenQty),
|
||||
buyTokens: opportunity.buy_tokens.map(checkTokenQty),
|
||||
eip712Domain: this.convertEIP712Domain(opportunity.eip_712_domain),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -293,62 +299,54 @@ export class Client {
|
|||
bidParams: BidParams,
|
||||
privateKey: Hex
|
||||
): Promise<OpportunityBid> {
|
||||
const account = privateKeyToAccount(privateKey);
|
||||
const convertTokenQty = ({ token, amount }: TokenAmount): [Hex, bigint] => [
|
||||
token,
|
||||
amount,
|
||||
];
|
||||
const payload = encodeAbiParameters(
|
||||
[
|
||||
{
|
||||
name: "repayTokens",
|
||||
type: "tuple[]",
|
||||
components: [
|
||||
{
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
type: "uint256",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "receiptTokens",
|
||||
type: "tuple[]",
|
||||
components: [
|
||||
{
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
type: "uint256",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "contract", type: "address" },
|
||||
{ name: "calldata", type: "bytes" },
|
||||
{ name: "value", type: "uint256" },
|
||||
{ name: "bid", type: "uint256" },
|
||||
{ name: "validUntil", type: "uint256" },
|
||||
const types = {
|
||||
SignedParams: [
|
||||
{ name: "executionParams", type: "ExecutionParams" },
|
||||
{ name: "signer", type: "address" },
|
||||
{ name: "deadline", type: "uint256" },
|
||||
],
|
||||
[
|
||||
opportunity.sellTokens.map(convertTokenQty),
|
||||
opportunity.buyTokens.map(convertTokenQty),
|
||||
opportunity.targetContract,
|
||||
opportunity.targetCalldata,
|
||||
opportunity.targetCallValue,
|
||||
bidParams.amount,
|
||||
bidParams.validUntil,
|
||||
]
|
||||
);
|
||||
ExecutionParams: [
|
||||
{ name: "sellTokens", type: "TokenAmount[]" },
|
||||
{ name: "buyTokens", type: "TokenAmount[]" },
|
||||
{ name: "targetContract", type: "address" },
|
||||
{ name: "targetCalldata", type: "bytes" },
|
||||
{ name: "targetCallValue", type: "uint256" },
|
||||
{ name: "bidAmount", type: "uint256" },
|
||||
],
|
||||
TokenAmount: [
|
||||
{ name: "token", type: "address" },
|
||||
{ name: "amount", type: "uint256" },
|
||||
],
|
||||
};
|
||||
|
||||
const msgHash = keccak256(payload);
|
||||
const account = privateKeyToAccount(privateKey);
|
||||
const signature = await signTypedData({
|
||||
privateKey,
|
||||
domain: {
|
||||
...opportunity.eip712Domain,
|
||||
chainId: Number(opportunity.eip712Domain.chainId),
|
||||
},
|
||||
types,
|
||||
primaryType: "SignedParams",
|
||||
message: {
|
||||
executionParams: {
|
||||
sellTokens: opportunity.sellTokens,
|
||||
buyTokens: opportunity.buyTokens,
|
||||
targetContract: opportunity.targetContract,
|
||||
targetCalldata: opportunity.targetCalldata,
|
||||
targetCallValue: opportunity.targetCallValue,
|
||||
bidAmount: bidParams.amount,
|
||||
},
|
||||
signer: account.address,
|
||||
deadline: bidParams.validUntil,
|
||||
},
|
||||
});
|
||||
|
||||
const hash = signatureToHex(await sign({ hash: msgHash, privateKey }));
|
||||
return {
|
||||
permissionKey: opportunity.permissionKey,
|
||||
bid: bidParams,
|
||||
executor: account.address,
|
||||
signature: hash,
|
||||
signature,
|
||||
opportunityId: opportunity.opportunityId,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -144,6 +144,28 @@ export interface components {
|
|||
ClientRequest: components["schemas"]["ClientMessage"] & {
|
||||
id: string;
|
||||
};
|
||||
EIP712Domain: {
|
||||
/**
|
||||
* @description The network chain id parameter for EIP712 domain.
|
||||
* @example 31337
|
||||
*/
|
||||
chain_id: string;
|
||||
/**
|
||||
* @description The name parameter for the EIP712 domain.
|
||||
* @example OpportunityAdapter
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @description The verifying contract address parameter for the EIP712 domain.
|
||||
* @example 0xcA11bde05977b3631167028862bE2a173976CA11
|
||||
*/
|
||||
verifying_contract: string;
|
||||
/**
|
||||
* @description The version parameter for the EIP712 domain.
|
||||
* @example 1
|
||||
*/
|
||||
version: string;
|
||||
};
|
||||
ErrorBodyResponse: {
|
||||
error: string;
|
||||
};
|
||||
|
@ -220,6 +242,7 @@ export interface components {
|
|||
* @example 1700000000000000
|
||||
*/
|
||||
creation_time: number;
|
||||
eip_712_domain: components["schemas"]["EIP712Domain"];
|
||||
/**
|
||||
* @description The opportunity unique id
|
||||
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
|
||||
|
@ -302,6 +325,7 @@ export interface components {
|
|||
* @example 1700000000000000
|
||||
*/
|
||||
creation_time: number;
|
||||
eip_712_domain: components["schemas"]["EIP712Domain"];
|
||||
/**
|
||||
* @description The opportunity unique id
|
||||
* @example obo3ee3e-58cc-4372-a567-0e02b2c3d479
|
||||
|
|
|
@ -23,6 +23,27 @@ export type BidParams = {
|
|||
*/
|
||||
validUntil: bigint;
|
||||
};
|
||||
/**
|
||||
* Represents the configuration for signing an opportunity
|
||||
*/
|
||||
export type EIP712Domain = {
|
||||
/**
|
||||
* The network chain id for the EIP712 domain.
|
||||
*/
|
||||
chainId: bigint;
|
||||
/**
|
||||
* The verifying contract address for the EIP712 domain.
|
||||
*/
|
||||
verifyingContract: Address;
|
||||
/**
|
||||
* The name parameter for the EIP712 domain.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The version parameter for the EIP712 domain.
|
||||
*/
|
||||
version: string;
|
||||
};
|
||||
/**
|
||||
* Represents a valid opportunity ready to be executed
|
||||
*/
|
||||
|
@ -60,11 +81,18 @@ export type Opportunity = {
|
|||
* Tokens to receive after the opportunity is executed
|
||||
*/
|
||||
buyTokens: TokenAmount[];
|
||||
/**
|
||||
* The data required to sign the opportunity
|
||||
*/
|
||||
eip712Domain: EIP712Domain;
|
||||
};
|
||||
/**
|
||||
* All the parameters necessary to represent an opportunity
|
||||
*/
|
||||
export type OpportunityParams = Omit<Opportunity, "opportunityId">;
|
||||
export type OpportunityParams = Omit<
|
||||
Opportunity,
|
||||
"opportunityId" | "eip712Domain"
|
||||
>;
|
||||
/**
|
||||
* Represents a bid for an opportunity
|
||||
*/
|
||||
|
|
|
@ -6,12 +6,9 @@ from typing import Callable, Any
|
|||
from collections.abc import Coroutine
|
||||
from uuid import UUID
|
||||
import httpx
|
||||
import web3
|
||||
import websockets
|
||||
from websockets.client import WebSocketClientProtocol
|
||||
from eth_abi import encode
|
||||
from eth_account.account import Account
|
||||
from web3.auto import w3
|
||||
from express_relay.types import (
|
||||
Opportunity,
|
||||
BidStatusUpdate,
|
||||
|
@ -405,42 +402,73 @@ def sign_bid(
|
|||
Returns:
|
||||
A OpportunityBid object, representing the transaction to submit to the server. This object contains the searcher's signature.
|
||||
"""
|
||||
sell_tokens = [
|
||||
(token.token, int(token.amount)) for token in opportunity.sell_tokens
|
||||
]
|
||||
buy_tokens = [(token.token, int(token.amount)) for token in opportunity.buy_tokens]
|
||||
target_calldata = bytes.fromhex(opportunity.target_calldata.replace("0x", ""))
|
||||
|
||||
digest = encode(
|
||||
[
|
||||
"(address,uint256)[]",
|
||||
"(address,uint256)[]",
|
||||
"address",
|
||||
"bytes",
|
||||
"uint256",
|
||||
"uint256",
|
||||
"uint256",
|
||||
executor = Account.from_key(private_key).address
|
||||
domain_data = {
|
||||
"name": opportunity.eip_712_domain.name,
|
||||
"version": opportunity.eip_712_domain.version,
|
||||
"chainId": opportunity.eip_712_domain.chain_id,
|
||||
"verifyingContract": opportunity.eip_712_domain.verifying_contract,
|
||||
}
|
||||
message_types = {
|
||||
"SignedParams": [
|
||||
{"name": "executionParams", "type": "ExecutionParams"},
|
||||
{"name": "signer", "type": "address"},
|
||||
{"name": "deadline", "type": "uint256"},
|
||||
],
|
||||
[
|
||||
sell_tokens,
|
||||
buy_tokens,
|
||||
opportunity.target_contract,
|
||||
target_calldata,
|
||||
opportunity.target_call_value,
|
||||
bid_amount,
|
||||
valid_until,
|
||||
"ExecutionParams": [
|
||||
{"name": "sellTokens", "type": "TokenAmount[]"},
|
||||
{"name": "buyTokens", "type": "TokenAmount[]"},
|
||||
{"name": "targetContract", "type": "address"},
|
||||
{"name": "targetCalldata", "type": "bytes"},
|
||||
{"name": "targetCallValue", "type": "uint256"},
|
||||
{"name": "bidAmount", "type": "uint256"},
|
||||
],
|
||||
"TokenAmount": [
|
||||
{"name": "token", "type": "address"},
|
||||
{"name": "amount", "type": "uint256"},
|
||||
],
|
||||
}
|
||||
|
||||
# the data to be signed
|
||||
message_data = {
|
||||
"executionParams": {
|
||||
"sellTokens": [
|
||||
{
|
||||
"token": token.token,
|
||||
"amount": int(token.amount),
|
||||
}
|
||||
for token in opportunity.sell_tokens
|
||||
],
|
||||
"buyTokens": [
|
||||
{
|
||||
"token": token.token,
|
||||
"amount": int(token.amount),
|
||||
}
|
||||
for token in opportunity.buy_tokens
|
||||
],
|
||||
"targetContract": opportunity.target_contract,
|
||||
"targetCalldata": bytes.fromhex(
|
||||
opportunity.target_calldata.replace("0x", "")
|
||||
),
|
||||
"targetCallValue": opportunity.target_call_value,
|
||||
"bidAmount": bid_amount,
|
||||
},
|
||||
"signer": executor,
|
||||
"deadline": valid_until,
|
||||
}
|
||||
|
||||
signed_typed_data = Account.sign_typed_data(
|
||||
private_key, domain_data, message_types, message_data
|
||||
)
|
||||
msg_data = web3.Web3.solidity_keccak(["bytes"], [digest])
|
||||
signature = w3.eth.account.signHash(msg_data, private_key=private_key)
|
||||
|
||||
opportunity_bid = OpportunityBid(
|
||||
opportunity_id=opportunity.opportunity_id,
|
||||
permission_key=opportunity.permission_key,
|
||||
amount=bid_amount,
|
||||
valid_until=valid_until,
|
||||
executor=Account.from_key(private_key).address,
|
||||
signature=signature,
|
||||
executor=executor,
|
||||
signature=signed_typed_data,
|
||||
)
|
||||
|
||||
return opportunity_bid
|
||||
|
|
|
@ -193,6 +193,13 @@ class OpportunityParams(BaseModel):
|
|||
params: Union[OpportunityParamsV1] = Field(..., discriminator="version")
|
||||
|
||||
|
||||
class EIP712Domain(BaseModel):
|
||||
name: str
|
||||
version: str
|
||||
chain_id: IntString
|
||||
verifying_contract: Address
|
||||
|
||||
|
||||
class Opportunity(BaseModel):
|
||||
"""
|
||||
Attributes:
|
||||
|
@ -206,6 +213,7 @@ class Opportunity(BaseModel):
|
|||
version: The version of the opportunity.
|
||||
creation_time: The creation time of the opportunity.
|
||||
opportunity_id: The ID of the opportunity.
|
||||
eip_712_domain: The EIP712 domain data needed for signing.
|
||||
"""
|
||||
|
||||
target_calldata: HexString
|
||||
|
@ -218,6 +226,7 @@ class Opportunity(BaseModel):
|
|||
version: str
|
||||
creation_time: IntString
|
||||
opportunity_id: UUIDString
|
||||
eip_712_domain: EIP712Domain
|
||||
|
||||
supported_versions: ClassVar[list[str]] = ["v1"]
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "express-relay"
|
||||
version = "0.2.1"
|
||||
version = "0.4.0"
|
||||
description = "Utilities for searchers and protocols to interact with the Express Relay protocol."
|
||||
authors = ["dourolabs"]
|
||||
license = "Proprietary"
|
||||
|
|
|
@ -1755,7 +1755,7 @@
|
|||
},
|
||||
"express_relay/sdk/js": {
|
||||
"name": "@pythnetwork/express-relay-evm-js",
|
||||
"version": "0.2.1",
|
||||
"version": "0.4.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
|
|
Loading…
Reference in New Issue