Skip to content

Commit

Permalink
fix(express-relay): Restore opportunity bid (#1760)
Browse files Browse the repository at this point in the history
  • Loading branch information
danimhr authored Jul 10, 2024
1 parent aac0584 commit 27656f9
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 26 deletions.
2 changes: 1 addition & 1 deletion express_relay/sdk/js/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
84 changes: 72 additions & 12 deletions express_relay/sdk/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
TokenAmount,
BidsResponse,
TokenPermissions,
OpportunityBid,
} from "./types";
import { executeOpportunityAbi } from "./abi";
import { OPPORTUNITY_ADAPTER_CONFIGS } from "./const";
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Bid> {
): Promise<`0x${string}`> {
const types = {
PermitBatchWitnessTransferFrom: [
{ name: "permitted", type: "TokenPermissions[]" },
Expand Down Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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<OpportunityBid> {
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<Bid> {
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,
Expand Down
23 changes: 23 additions & 0 deletions express_relay/sdk/js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
87 changes: 75 additions & 12 deletions express_relay/sdk/python/express_relay/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion express_relay/sdk/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down

0 comments on commit 27656f9

Please sign in to comment.