Эх сурвалжийг харах

feat!(express_relay): Update bid's signature to eip712 (#1455)

Dani Mehrjerdi 1 жил өмнө
parent
commit
93efd61ea4

+ 1 - 1
express_relay/sdk/js/package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "@pythnetwork/express-relay-evm-js",
-  "version": "0.1.1",
+  "version": "0.4.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {

+ 1 - 1
express_relay/sdk/js/package.json

@@ -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",

+ 56 - 58
express_relay/sdk/js/src/index.ts

@@ -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" },
+      ],
+      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" },
       ],
-      [
-        opportunity.sellTokens.map(convertTokenQty),
-        opportunity.buyTokens.map(convertTokenQty),
-        opportunity.targetContract,
-        opportunity.targetCalldata,
-        opportunity.targetCallValue,
-        bidParams.amount,
-        bidParams.validUntil,
-      ]
-    );
+    };
 
-    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,
     };
   }

+ 24 - 0
express_relay/sdk/js/src/serverTypes.d.ts

@@ -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

+ 29 - 1
express_relay/sdk/js/src/types.ts

@@ -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
  */

+ 58 - 30
express_relay/sdk/python/express_relay/client.py

@@ -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"},
+        ],
+        "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"},
         ],
-        [
-            sell_tokens,
-            buy_tokens,
-            opportunity.target_contract,
-            target_calldata,
-            opportunity.target_call_value,
-            bid_amount,
-            valid_until,
+        "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

+ 9 - 0
express_relay/sdk/python/express_relay/types.py

@@ -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 - 1
express_relay/sdk/python/pyproject.toml

@@ -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"

+ 1 - 1
package-lock.json

@@ -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",