Skip to content

Commit

Permalink
feat: add priority order trade (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
marktoda authored Sep 20, 2024
1 parent 984b0f1 commit ff1d31c
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 0 deletions.
127 changes: 127 additions & 0 deletions sdks/uniswapx-sdk/src/trade/PriorityOrderTrade.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Currency, Ether, Token, TradeType } from "@uniswap/sdk-core";
import { BigNumber, constants, ethers } from "ethers";

import { UnsignedPriorityOrderInfo } from "../order";

import { NativeAssets } from "./utils";

import { PriorityOrderTrade } from ".";

const USDC = new Token(
1,
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
6,
"USDC"
);
const DAI = new Token(
1,
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
18,
"DAI"
);

describe("PriorityOrderTrade", () => {
const NON_FEE_OUTPUT_AMOUNT = BigNumber.from("1000000000000000000");

const orderInfo: UnsignedPriorityOrderInfo = {
deadline: Math.floor(new Date().getTime() / 1000) + 1000,
reactor: "0x0000000000000000000000000000000000000000",
swapper: "0x0000000000000000000000000000000000000000",
nonce: BigNumber.from(10),
cosigner: "0x0000000000000000000000000000000000000000",
additionalValidationContract: ethers.constants.AddressZero,
additionalValidationData: "0x",
auctionStartBlock: BigNumber.from(100000),
baselinePriorityFeeWei: BigNumber.from(2),
input: {
token: USDC.address,
amount: BigNumber.from(1000),
mpsPerPriorityFeeWei: BigNumber.from(0),
},
outputs: [
{
token: DAI.address,
amount: NON_FEE_OUTPUT_AMOUNT,
mpsPerPriorityFeeWei: BigNumber.from(5),
recipient: "0x0000000000000000000000000000000000000000",
},
{
token: DAI.address,
amount: BigNumber.from("1000"),
mpsPerPriorityFeeWei: BigNumber.from(5),
recipient: "0x0000000000000000000000000000000000000000",
},
],
};

const trade = new PriorityOrderTrade<Currency, Currency, TradeType>({
currencyIn: USDC,
currenciesOut: [DAI],
orderInfo,
tradeType: TradeType.EXACT_INPUT,
});

it("returns the right input amount for an exact-in trade", () => {
expect(trade.inputAmount.quotient.toString()).toEqual(
orderInfo.input.amount.toString()
);
});

it("returns the correct non-fee output amount", () => {
expect(trade.outputAmount.quotient.toString()).toEqual(
NON_FEE_OUTPUT_AMOUNT.toString()
);
});

it("returns the correct minimum amount out", () => {
expect(trade.minimumAmountOut().quotient.toString()).toEqual(
NON_FEE_OUTPUT_AMOUNT.toString()
);
});

it("works for native output trades", () => {
const ethOutputOrderInfo = {
...orderInfo,
outputs: [
{
token: NativeAssets.ETH,
amount: NON_FEE_OUTPUT_AMOUNT,
mpsPerPriorityFeeWei: BigNumber.from(5),
recipient: "0x0000000000000000000000000000000000000000",
},
],
};
const ethOutputTrade = new PriorityOrderTrade<Currency, Currency, TradeType>(
{
currencyIn: USDC,
currenciesOut: [Ether.onChain(1)],
orderInfo: ethOutputOrderInfo,
tradeType: TradeType.EXACT_INPUT,
}
);
expect(ethOutputTrade.outputAmount.currency).toEqual(Ether.onChain(1));
});

it("works for native output trades where order info has 0 address", () => {
const ethOutputOrderInfo = {
...orderInfo,
outputs: [
{
token: constants.AddressZero,
amount: NON_FEE_OUTPUT_AMOUNT,
mpsPerPriorityFeeWei: BigNumber.from(5),
recipient: "0x0000000000000000000000000000000000000000",
},
],
};
const ethOutputTrade = new PriorityOrderTrade<Currency, Currency, TradeType>(
{
currencyIn: USDC,
currenciesOut: [Ether.onChain(1)],
orderInfo: ethOutputOrderInfo,
tradeType: TradeType.EXACT_INPUT,
}
);
expect(ethOutputTrade.outputAmount.currency).toEqual(Ether.onChain(1));
});
});
154 changes: 154 additions & 0 deletions sdks/uniswapx-sdk/src/trade/PriorityOrderTrade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Currency, CurrencyAmount, Price, TradeType } from "@uniswap/sdk-core";

import { UnsignedPriorityOrder, UnsignedPriorityOrderInfo } from "../order";

import { areCurrenciesEqual } from "./utils";

export class PriorityOrderTrade<
TInput extends Currency,
TOutput extends Currency,
TTradeType extends TradeType
> {
public readonly tradeType: TTradeType;
public readonly order: UnsignedPriorityOrder;

private _inputAmount: CurrencyAmount<TInput> | undefined;
private _outputAmounts: CurrencyAmount<TOutput>[] | undefined;

private _currencyIn: TInput;
private _currenciesOut: TOutput[];

public constructor({
currencyIn,
currenciesOut,
orderInfo,
tradeType,
}: {
currencyIn: TInput;
currenciesOut: TOutput[];
orderInfo: UnsignedPriorityOrderInfo;
tradeType: TTradeType;
}) {
this._currencyIn = currencyIn;
this._currenciesOut = currenciesOut;
this.tradeType = tradeType;

// assume single-chain for now
this.order = new UnsignedPriorityOrder(orderInfo, currencyIn.chainId);
}

public get inputAmount(): CurrencyAmount<TInput> {
if (this._inputAmount) return this._inputAmount;

const amount = CurrencyAmount.fromRawAmount(
this._currencyIn,
this.order.info.input.amount.toString()
);
this._inputAmount = amount;
return amount;
}

public get outputAmounts(): CurrencyAmount<TOutput>[] {
if (this._outputAmounts) return this._outputAmounts;

const amounts = this.order.info.outputs.map((output) => {
// assume single chain ids across all outputs for now
const currencyOut = this._currenciesOut.find((currency) =>
areCurrenciesEqual(currency, output.token, currency.chainId)
);

if (!currencyOut) {
throw new Error("currency not found in output array");
}

return CurrencyAmount.fromRawAmount(
currencyOut,
output.amount.toString()
);
});

this._outputAmounts = amounts;
return amounts;
}

private _firstNonFeeOutputAmount:
| CurrencyAmount<TOutput>
| undefined;

private getFirstNonFeeOutputAmount(): CurrencyAmount<TOutput> {
if (this._firstNonFeeOutputAmount)
return this._firstNonFeeOutputAmount;

if (this.order.info.outputs.length === 0) {
throw new Error("there must be at least one output token");
}
const output = this.order.info.outputs[0];

// assume single chain ids across all outputs for now
const currencyOut = this._currenciesOut.find((currency) =>
areCurrenciesEqual(currency, output.token, currency.chainId)
);

if (!currencyOut) {
throw new Error(
"currency output from order must exist in currenciesOut list"
);
}

const amount =
CurrencyAmount.fromRawAmount(
currencyOut,
output.amount.toString()
);

this._firstNonFeeOutputAmount = amount;
return amount;
}

// TODO: revise when there are actually multiple output amounts. for now, assume only one non-fee output at a time
public get outputAmount(): CurrencyAmount<TOutput> {
// TODO: estimate epected amount, using classic quote or expected priority
return this.getFirstNonFeeOutputAmount();
}

public minimumAmountOut(): CurrencyAmount<TOutput> {
return this.getFirstNonFeeOutputAmount();
}

public maximumAmountIn(): CurrencyAmount<TInput> {
return CurrencyAmount.fromRawAmount(
this._currencyIn,
this.order.info.input.amount.toString()
);
}

private _executionPrice: Price<TInput, TOutput> | undefined;

/**
* The price expressed in terms of output amount/input amount.
*/
public get executionPrice(): Price<TInput, TOutput> {
return (
this._executionPrice ??
(this._executionPrice = new Price(
this.inputAmount.currency,
this.outputAmount.currency,
this.inputAmount.quotient,
this.outputAmount.quotient
))
);
}

/**
* Return the execution price after accounting for slippage tolerance
* @returns The execution price
*/
public worstExecutionPrice(): Price<TInput, TOutput> {
return new Price(
this.inputAmount.currency,
this.outputAmount.currency,
this.maximumAmountIn().quotient,
this.minimumAmountOut().quotient
);
}
}
1 change: 1 addition & 0 deletions sdks/uniswapx-sdk/src/trade/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./DutchOrderTrade";
export * from "./V2DutchOrderTrade";
export * from "./PriorityOrderTrade";
export * from "./RelayOrderTrade";

0 comments on commit ff1d31c

Please sign in to comment.