From 27656f9e609abaa5943489f1407c6595cfdbfc2e Mon Sep 17 00:00:00 2001 From: Dani Mehrjerdi Date: Wed, 10 Jul 2024 13:10:24 +0200 Subject: [PATCH] fix(express-relay): Restore opportunity bid (#1760) --- express_relay/sdk/js/package.json | 2 +- express_relay/sdk/js/src/index.ts | 84 +++++++++++++++--- express_relay/sdk/js/src/types.ts | 23 +++++ .../sdk/python/express_relay/client.py | 87 ++++++++++++++++--- express_relay/sdk/python/pyproject.toml | 2 +- 5 files changed, 172 insertions(+), 26 deletions(-) diff --git a/express_relay/sdk/js/package.json b/express_relay/sdk/js/package.json index 56219e66fe..86349b0fc5 100644 --- a/express_relay/sdk/js/package.json +++ b/express_relay/sdk/js/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/express-relay-evm-js", - "version": "0.8.0", + "version": "0.8.1", "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", diff --git a/express_relay/sdk/js/src/index.ts b/express_relay/sdk/js/src/index.ts index 106d01135b..08f0a6063f 100644 --- a/express_relay/sdk/js/src/index.ts +++ b/express_relay/sdk/js/src/index.ts @@ -22,6 +22,7 @@ import { TokenAmount, BidsResponse, TokenPermissions, + OpportunityBid, } from "./types"; import { executeOpportunityAbi } from "./abi"; import { OPPORTUNITY_ADAPTER_CONFIGS } from "./const"; @@ -67,6 +68,16 @@ export function checkTokenQty(token: { }; } +function getOpportunityConfig(chainId: string) { + const opportunityAdapterConfig = OPPORTUNITY_ADAPTER_CONFIGS[chainId]; + if (!opportunityAdapterConfig) { + throw new ClientError( + `Opportunity adapter config not found for chain id: ${chainId}` + ); + } + return opportunityAdapterConfig; +} + /** * Converts sellTokens, bidAmount, and callValue to permitted tokens * @param tokens List of sellTokens @@ -367,17 +378,17 @@ export class Client { } /** - * Creates a signed bid for an opportunity + * Creates a signature for the bid and opportunity * @param opportunity Opportunity to bid on * @param bidParams Bid amount, nonce, and deadline timestamp * @param privateKey Private key to sign the bid with - * @returns Signed bid + * @returns Signature for the bid and opportunity */ - async signBid( + async getSignature( opportunity: Opportunity, bidParams: BidParams, privateKey: Hex - ): Promise { + ): Promise<`0x${string}`> { const types = { PermitBatchWitnessTransferFrom: [ { name: "permitted", type: "TokenPermissions[]" }, @@ -406,13 +417,7 @@ export class Client { const account = privateKeyToAccount(privateKey); const executor = account.address; - const opportunityAdapterConfig = - OPPORTUNITY_ADAPTER_CONFIGS[opportunity.chainId]; - if (!opportunityAdapterConfig) { - throw new ClientError( - `Opportunity adapter config not found for chain id: ${opportunity.chainId}` - ); - } + const opportunityAdapterConfig = getOpportunityConfig(opportunity.chainId); const permitted = getPermittedTokens( opportunity.sellTokens, bidParams.amount, @@ -427,7 +432,7 @@ export class Client { salt: `0x${executor.replace("0x", "").padStart(64, "0")}`, }); - const signature = await signTypedData({ + return signTypedData({ privateKey, domain: { name: "Permit2", @@ -451,6 +456,61 @@ export class Client { }, }, }); + } + + /** + * Creates a signed opportunity bid for an opportunity + * @param opportunity Opportunity to bid on + * @param bidParams Bid amount and valid until timestamp + * @param privateKey Private key to sign the bid with + * @returns Signed opportunity bid + */ + async signOpportunityBid( + opportunity: Opportunity, + bidParams: BidParams, + privateKey: Hex + ): Promise { + const account = privateKeyToAccount(privateKey); + const signature = await this.getSignature( + opportunity, + bidParams, + privateKey + ); + + return { + permissionKey: opportunity.permissionKey, + bid: bidParams, + executor: account.address, + signature, + opportunityId: opportunity.opportunityId, + }; + } + + /** + * Creates a signed bid for an opportunity + * @param opportunity Opportunity to bid on + * @param bidParams Bid amount, nonce, and deadline timestamp + * @param privateKey Private key to sign the bid with + * @returns Signed bid + */ + async signBid( + opportunity: Opportunity, + bidParams: BidParams, + privateKey: Hex + ): Promise { + const opportunityAdapterConfig = getOpportunityConfig(opportunity.chainId); + const executor = privateKeyToAccount(privateKey).address; + const permitted = getPermittedTokens( + opportunity.sellTokens, + bidParams.amount, + opportunity.targetCallValue, + checkAddress(opportunityAdapterConfig.weth) + ); + const signature = await this.getSignature( + opportunity, + bidParams, + privateKey + ); const calldata = this.makeAdapterCalldata( opportunity, diff --git a/express_relay/sdk/js/src/types.ts b/express_relay/sdk/js/src/types.ts index bc1adf26ce..0108f53c20 100644 --- a/express_relay/sdk/js/src/types.ts +++ b/express_relay/sdk/js/src/types.ts @@ -96,6 +96,29 @@ export type Opportunity = { */ buyTokens: TokenAmount[]; }; +/** + * Represents a bid for an opportunity + */ +export type OpportunityBid = { + /** + * Opportunity unique identifier in uuid format + */ + opportunityId: string; + /** + * The permission key required for successful execution of the opportunity. + */ + permissionKey: Hex; + /** + * Executor address + */ + executor: Address; + /** + * Signature of the executor + */ + signature: Hex; + + bid: BidParams; +}; /** * All the parameters necessary to represent an opportunity */ diff --git a/express_relay/sdk/python/express_relay/client.py b/express_relay/sdk/python/express_relay/client.py index 29594519fd..91cd5c9b83 100644 --- a/express_relay/sdk/python/express_relay/client.py +++ b/express_relay/sdk/python/express_relay/client.py @@ -20,12 +20,14 @@ BidStatusUpdate, ClientMessage, Bid, + OpportunityBid, OpportunityParams, Address, Bytes32, TokenAmount, OpportunityBidParams, ) +from eth_account.datastructures import SignedMessage from express_relay.constants import ( OPPORTUNITY_ADAPTER_CONFIGS, EXECUTION_PARAMS_TYPESTRING, @@ -491,25 +493,31 @@ def make_adapter_calldata( return calldata -def sign_bid( - opportunity: Opportunity, bid_params: OpportunityBidParams, private_key: str -) -> Bid: +def get_opportunity_adapter_config(chain_id: str): + opportunity_adapter_config = OPPORTUNITY_ADAPTER_CONFIGS.get(chain_id) + if not opportunity_adapter_config: + raise ExpressRelayClientException( + f"Opportunity adapter config not found for chain id {chain_id}" + ) + return opportunity_adapter_config + + +def get_signature( + opportunity: Opportunity, + bid_params: OpportunityBidParams, + private_key: str, +) -> SignedMessage: """ - Constructs a signature for a searcher's bid and returns the Bid object to be submitted to the server. + Constructs a signature for a searcher's bid and opportunity. Args: opportunity: An object representing the opportunity, of type Opportunity. bid_params: An object representing the bid parameters, of type OpportunityBidParams. private_key: A 0x-prefixed hex string representing the searcher's private key. Returns: - A Bid object, representing the transaction to submit to the server. This object contains the searcher's signature. + A SignedMessage object, representing the signature of the searcher's bid. """ - - opportunity_adapter_config = OPPORTUNITY_ADAPTER_CONFIGS.get(opportunity.chain_id) - if not opportunity_adapter_config: - raise ExpressRelayClientException( - f"Opportunity adapter config not found for chain id {opportunity.chain_id}" - ) + opportunity_adapter_config = get_opportunity_adapter_config(opportunity.chain_id) domain_data = { "name": "Permit2", "chainId": opportunity_adapter_config.chain_id, @@ -582,8 +590,63 @@ def sign_bid( private_key, domain_data, message_types, message_data ) + return signed_typed_data + + +def sign_opportunity_bid( + opportunity: Opportunity, + bid_params: OpportunityBidParams, + private_key: str, +) -> OpportunityBid: + """ + Constructs a signature for a searcher's bid and returns the OpportunityBid object to be submitted to the server. + + Args: + opportunity: An object representing the opportunity, of type Opportunity. + bid_params: An object representing the bid parameters, of type OpportunityBidParams. + private_key: A 0x-prefixed hex string representing the searcher's private key. + Returns: + A OpportunityBid object, representing the transaction to submit to the server. This object contains the searcher's signature. + """ + executor = Account.from_key(private_key).address + opportunity_bid = OpportunityBid( + opportunity_id=opportunity.opportunity_id, + permission_key=opportunity.permission_key, + amount=bid_params.amount, + deadline=bid_params.deadline, + nonce=bid_params.nonce, + executor=executor, + signature=get_signature(opportunity, bid_params, private_key), + ) + + return opportunity_bid + + +def sign_bid( + opportunity: Opportunity, bid_params: OpportunityBidParams, private_key: str +) -> Bid: + """ + Constructs a signature for a searcher's bid and returns the Bid object to be submitted to the server. + + Args: + opportunity: An object representing the opportunity, of type Opportunity. + bid_params: An object representing the bid parameters, of type OpportunityBidParams. + private_key: A 0x-prefixed hex string representing the searcher's private key. + Returns: + A Bid object, representing the transaction to submit to the server. This object contains the searcher's signature. + """ + opportunity_adapter_config = get_opportunity_adapter_config(opportunity.chain_id) + permitted = _get_permitted_tokens( + opportunity.sell_tokens, + bid_params.amount, + opportunity.target_call_value, + opportunity_adapter_config.weth, + ) + executor = Account.from_key(private_key).address + + signature = get_signature(opportunity, bid_params, private_key).signature calldata = make_adapter_calldata( - opportunity, permitted, executor, bid_params, signed_typed_data.signature + opportunity, permitted, executor, bid_params, signature ) return Bid( diff --git a/express_relay/sdk/python/pyproject.toml b/express_relay/sdk/python/pyproject.toml index 74580e9ed4..3b4be56d51 100644 --- a/express_relay/sdk/python/pyproject.toml +++ b/express_relay/sdk/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "express-relay" -version = "0.8.0" +version = "0.8.1" description = "Utilities for searchers and protocols to interact with the Express Relay protocol." authors = ["dourolabs"] license = "Apache-2.0"