Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into staking-app-release…
Browse files Browse the repository at this point in the history
…-batch
  • Loading branch information
cprussin committed Oct 9, 2024
2 parents 5e54027 + 46d0d2a commit 37fb9b6
Show file tree
Hide file tree
Showing 22 changed files with 1,280 additions and 583 deletions.
5 changes: 5 additions & 0 deletions apps/staking/src/config/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ export const PYTHNET_RPC = getOr("PYTHNET_RPC", "https://pythnet.rpcpool.com");
export const HERMES_URL = getOr("HERMES_URL", "https://hermes.pyth.network");
export const BLOCKED_REGIONS = transformOr("BLOCKED_REGIONS", fromCsv, []);
export const IP_ALLOWLIST = transformOr("IP_ALLOWLIST", fromCsv, []);
export const VPN_ORGANIZATION_ALLOWLIST = transformOr(
"VPN_ORGANIZATION_ALLOWLIST",
fromCsv,
["iCloud Private Relay"],
);
export const GOVERNANCE_ONLY_REGIONS = transformOr(
"GOVERNANCE_ONLY_REGIONS",
fromCsv,
Expand Down
10 changes: 8 additions & 2 deletions apps/staking/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
GOVERNANCE_ONLY_REGIONS,
PROXYCHECK_API_KEY,
IP_ALLOWLIST,
VPN_ORGANIZATION_ALLOWLIST,
} from "./config/server";

const GEO_BLOCKED_PATH = `/${GEO_BLOCKED_SEGMENT}`;
Expand Down Expand Up @@ -61,8 +62,13 @@ const isProxyBlocked = async ({ ip }: NextRequest) => {
if (proxyCheckClient === undefined || ip === undefined) {
return false;
} else {
const result = await proxyCheckClient.checkIP(ip, { vpn: 2 });
return result[ip]?.proxy === "yes";
const response = await proxyCheckClient.checkIP(ip, { vpn: 2 });
const result = response[ip];
return (
result &&
result.proxy === "yes" &&
!VPN_ORGANIZATION_ALLOWLIST.includes(result.organisation)
);
}
};

Expand Down
4 changes: 2 additions & 2 deletions express_relay/examples/easy_lend/src/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { hideBin } from "yargs/helpers";
import {
checkAddress,
Client,
OpportunityParams,
OpportunityCreate,
} from "@pythnetwork/express-relay-js";
import type { ContractFunctionReturnType } from "viem";
import {
Expand Down Expand Up @@ -133,7 +133,7 @@ class ProtocolMonitor {
{ token: this.wethContract, amount: targetCallValue },
];
}
const opportunity: OpportunityParams = {
const opportunity: OpportunityCreate = {
chainId: this.chainId,
targetContract: this.vaultContract,
targetCalldata: calldata,
Expand Down
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-js",
"version": "0.10.0",
"version": "0.11.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",
Expand Down
6 changes: 0 additions & 6 deletions express_relay/sdk/js/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ export const OPPORTUNITY_ADAPTER_CONFIGS: Record<

export const SVM_CONSTANTS: Record<string, SvmConstantsConfig> = {
"development-solana": {
relayerSigner: new PublicKey(
"GEeEguHhepHtPVo3E9RA1wvnxgxJ61iSc9dJfd433w3K"
),
feeReceiverRelayer: new PublicKey(
"feesJcX9zwLiEZs9iQGXeBd65b9m2Zc1LjjyHngQF29"
),
expressRelayProgram: new PublicKey(
"PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou"
),
Expand Down
212 changes: 212 additions & 0 deletions express_relay/sdk/js/src/evm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import {
Bid,
BidParams,
OpportunityBid,
OpportunityEvm,
TokenAmount,
TokenPermissions,
} from "./types";
import { Address, encodeFunctionData, getContractAddress, Hex } from "viem";
import { privateKeyToAccount, signTypedData } from "viem/accounts";
import { checkAddress, ClientError } from "./index";
import { OPPORTUNITY_ADAPTER_CONFIGS } from "./const";
import { executeOpportunityAbi } from "./abi";

/**
* Converts sellTokens, bidAmount, and callValue to permitted tokens
* @param tokens List of sellTokens
* @param bidAmount
* @param callValue
* @param weth
* @returns List of permitted tokens
*/
function getPermittedTokens(
tokens: TokenAmount[],
bidAmount: bigint,
callValue: bigint,
weth: Address
): TokenPermissions[] {
const permitted: TokenPermissions[] = tokens.map(({ token, amount }) => ({
token,
amount,
}));
const wethIndex = permitted.findIndex(({ token }) => token === weth);
const extraWethNeeded = bidAmount + callValue;
if (wethIndex !== -1) {
permitted[wethIndex].amount += extraWethNeeded;
return permitted;
}
if (extraWethNeeded > 0) {
permitted.push({ token: weth, amount: extraWethNeeded });
}
return permitted;
}

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;
}

export async function signBid(
opportunity: OpportunityEvm,
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 getSignature(opportunity, bidParams, privateKey);

const calldata = makeAdapterCalldata(
opportunity,
permitted,
executor,
bidParams,
signature
);

return {
amount: bidParams.amount,
targetCalldata: calldata,
chainId: opportunity.chainId,
targetContract: opportunityAdapterConfig.opportunity_adapter_factory,
permissionKey: opportunity.permissionKey,
env: "evm",
};
}

/**
* Constructs the calldata for the opportunity adapter contract.
* @param opportunity Opportunity to bid on
* @param permitted Permitted tokens
* @param executor Address of the searcher's wallet
* @param bidParams Bid amount, nonce, and deadline timestamp
* @param signature Searcher's signature for opportunity params and bidParams
* @returns Calldata for the opportunity adapter contract
*/
function makeAdapterCalldata(
opportunity: OpportunityEvm,
permitted: TokenPermissions[],
executor: Address,
bidParams: BidParams,
signature: Hex
): Hex {
return encodeFunctionData({
abi: [executeOpportunityAbi],
args: [
[
[permitted, bidParams.nonce, bidParams.deadline],
[
opportunity.buyTokens,
executor,
opportunity.targetContract,
opportunity.targetCalldata,
opportunity.targetCallValue,
bidParams.amount,
],
],
signature,
],
});
}

export async function getSignature(
opportunity: OpportunityEvm,
bidParams: BidParams,
privateKey: Hex
): Promise<`0x${string}`> {
const types = {
PermitBatchWitnessTransferFrom: [
{ name: "permitted", type: "TokenPermissions[]" },
{ name: "spender", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
{ name: "witness", type: "OpportunityWitness" },
],
OpportunityWitness: [
{ name: "buyTokens", type: "TokenAmount[]" },
{ name: "executor", type: "address" },
{ 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" },
],
TokenPermissions: [
{ name: "token", type: "address" },
{ name: "amount", type: "uint256" },
],
};

const account = privateKeyToAccount(privateKey);
const executor = account.address;
const opportunityAdapterConfig = getOpportunityConfig(opportunity.chainId);
const permitted = getPermittedTokens(
opportunity.sellTokens,
bidParams.amount,
opportunity.targetCallValue,
checkAddress(opportunityAdapterConfig.weth)
);
const create2Address = getContractAddress({
bytecodeHash:
opportunityAdapterConfig.opportunity_adapter_init_bytecode_hash,
from: opportunityAdapterConfig.opportunity_adapter_factory,
opcode: "CREATE2",
salt: `0x${executor.replace("0x", "").padStart(64, "0")}`,
});

return signTypedData({
privateKey,
domain: {
name: "Permit2",
verifyingContract: checkAddress(opportunityAdapterConfig.permit2),
chainId: opportunityAdapterConfig.chain_id,
},
types,
primaryType: "PermitBatchWitnessTransferFrom",
message: {
permitted,
spender: create2Address,
nonce: bidParams.nonce,
deadline: bidParams.deadline,
witness: {
buyTokens: opportunity.buyTokens,
executor,
targetContract: opportunity.targetContract,
targetCalldata: opportunity.targetCalldata,
targetCallValue: opportunity.targetCallValue,
bidAmount: bidParams.amount,
},
},
});
}

export async function signOpportunityBid(
opportunity: OpportunityEvm,
bidParams: BidParams,
privateKey: Hex
): Promise<OpportunityBid> {
const account = privateKeyToAccount(privateKey);
const signature = await getSignature(opportunity, bidParams, privateKey);

return {
permissionKey: opportunity.permissionKey,
bid: bidParams,
executor: account.address,
signature,
opportunityId: opportunity.opportunityId,
};
}
2 changes: 2 additions & 0 deletions express_relay/sdk/js/src/examples/simpleSearcherEvm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class SimpleSearcherEvm {
}

async opportunityHandler(opportunity: Opportunity) {
if (!("targetContract" in opportunity))
throw new Error("Not a valid EVM opportunity");
const bidAmount = BigInt(argv.bid);
// Bid info should be generated by evaluating the opportunity
// here for simplicity we are using a constant bid and 24 hours of validity
Expand Down
Loading

0 comments on commit 37fb9b6

Please sign in to comment.