Skip to content

Commit

Permalink
Merge pull request #384 from Uniswap/feat-dutch-v3
Browse files Browse the repository at this point in the history
feat: Support DutchV3 Orders
  • Loading branch information
alanhwu authored Dec 2, 2024
2 parents a417183 + 6b91e15 commit c3dc16b
Show file tree
Hide file tree
Showing 15 changed files with 520 additions and 132 deletions.
31 changes: 29 additions & 2 deletions bin/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import dotenv from 'dotenv';
import { STAGE } from '../lib/util/stage';
import { SERVICE_NAME } from './constants';
import { APIStack } from './stacks/api-stack';
import { ChainId, supportedChains } from '../lib/util/chains';

dotenv.config();

Expand Down Expand Up @@ -113,14 +114,29 @@ export class APIPipeline extends Stack {
'arn:aws:secretsmanager:us-east-2:644039819003:secret:gouda-parameterization-api-internal-api-key-uw4sIa',
});

// Beta us-east-2
const rpcUrls = sm.Secret.fromSecretAttributes(this, 'rpcUrls', {
secretCompleteArn:
'arn:aws:secretsmanager:us-east-2:644039819003:secret:prod/param-api/rpc-urls-HJyniu',
});

const jsonRpcProviders = {} as {[chainKey: string]: string};
supportedChains.forEach(
(chainId: ChainId) => {
const mapKey = `RPC_${chainId}`;
jsonRpcProviders[mapKey] = rpcUrls
.secretValueFromJson(mapKey)
.toString();
}
);

// Beta us-east-2
const betaUsEast2Stage = new APIStage(this, 'beta-us-east-2', {
env: { account: '801328487475', region: 'us-east-2' },
provisionedConcurrency: 2,
internalApiKey: internalApiKey.secretValue.toString(),
stage: STAGE.BETA,
envVars: {
...jsonRpcProviders,
RFQ_WEBHOOK_CONFIG: rfqWebhookConfig.secretValue.toString(),
ORDER_SERVICE_URL: urlSecrets.secretValueFromJson('GOUDA_SERVICE_BETA').toString(),
FILL_LOG_SENDER_ACCOUNT: '321377678687',
Expand All @@ -141,6 +157,7 @@ export class APIPipeline extends Stack {
internalApiKey: internalApiKey.secretValue.toString(),
chatbotSNSArn: 'arn:aws:sns:us-east-2:644039819003:SlackChatbotTopic',
envVars: {
...jsonRpcProviders,
RFQ_WEBHOOK_CONFIG: rfqWebhookConfig.secretValue.toString(),
ORDER_SERVICE_URL: urlSecrets.secretValueFromJson('GOUDA_SERVICE_PROD').toString(),
FILL_LOG_SENDER_ACCOUNT: '316116520258',
Expand Down Expand Up @@ -244,6 +261,13 @@ envVars['URA_ACCOUNT'] = process.env['URA_ACCOUNT'] || '';
envVars['BOT_ACCOUNT'] = process.env['BOT_ACCOUNT'] || '';
envVars['UNISWAP_API'] = process.env['UNISWAP_API'] || '';
envVars['ORDER_SERVICE_URL'] = process.env['ORDER_SERVICE_URL'] || '';
const jsonRpcProviders = {} as {[chainKey: string]: string};
supportedChains.forEach(
(chainId: ChainId) => {
const mapKey = `RPC_${chainId}`;
jsonRpcProviders[mapKey] = process.env[mapKey] || '';
}
);

new APIStack(app, `${SERVICE_NAME}Stack`, {
env: {
Expand All @@ -255,7 +279,10 @@ new APIStack(app, `${SERVICE_NAME}Stack`, {
throttlingOverride: process.env.THROTTLE_PER_FIVE_MINS,
chatbotSNSArn: process.env.CHATBOT_SNS_ARN,
stage: STAGE.LOCAL,
envVars,
envVars: {
...envVars,
...jsonRpcProviders,
},
});

new APIPipeline(app, `${SERVICE_NAME}PipelineStack`, {
Expand Down
1 change: 1 addition & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ export const POST_ORDER_ERROR_REASON = {

export const WEBHOOK_TIMEOUT_MS = 500;
export const NOTIFICATION_TIMEOUT_MS = 10;
export const V3_BLOCK_BUFFER = 4;
37 changes: 28 additions & 9 deletions lib/entities/HardQuoteRequest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TradeType } from '@uniswap/sdk-core';
import { UnsignedV2DutchOrder } from '@uniswap/uniswapx-sdk';
import { OrderType, UnsignedV2DutchOrder, UnsignedV3DutchOrder, V3DutchOrderBuilder } from '@uniswap/uniswapx-sdk';
import { BigNumber, ethers, utils } from 'ethers';
import { v4 as uuidv4 } from 'uuid';

Expand All @@ -8,19 +8,25 @@ import { HardQuoteRequestBody } from '../handlers/hard-quote';
import { ProtocolVersion } from '../providers';

export class HardQuoteRequest {
public order: UnsignedV2DutchOrder;
public order: UnsignedV2DutchOrder | UnsignedV3DutchOrder;
private data: HardQuoteRequestBody;

public static fromHardRequestBody(body: HardQuoteRequestBody): HardQuoteRequest {
return new HardQuoteRequest(body);
public static fromHardRequestBody(body: HardQuoteRequestBody, orderType: OrderType): HardQuoteRequest {
return new HardQuoteRequest(body, orderType);
}

constructor(_data: HardQuoteRequestBody) {
constructor(_data: HardQuoteRequestBody, orderType: OrderType) {
this.data = {
..._data,
requestId: _data.quoteId ?? uuidv4(),
};
this.order = UnsignedV2DutchOrder.parse(_data.encodedInnerOrder, _data.tokenInChainId);
if (orderType === OrderType.Dutch_V2) {
this.order = UnsignedV2DutchOrder.parse(_data.encodedInnerOrder, _data.tokenInChainId);
} else if (orderType === OrderType.Dutch_V3) {
this.order = UnsignedV3DutchOrder.parse(_data.encodedInnerOrder, _data.tokenInChainId);
} else {
throw new Error('Unsupported order type');
}
}

public toCleanJSON(): QuoteRequestDataJSON {
Expand Down Expand Up @@ -110,9 +116,22 @@ export class HardQuoteRequest {
}

public get type(): TradeType {
return this.order.info.input.startAmount.eq(this.order.info.input.endAmount)
? TradeType.EXACT_INPUT
: TradeType.EXACT_OUTPUT;
if (this.order instanceof UnsignedV2DutchOrder) {
return this.order.info.input.startAmount.eq(this.order.info.input.endAmount)
? TradeType.EXACT_INPUT
: TradeType.EXACT_OUTPUT
}
else if (this.order instanceof UnsignedV3DutchOrder) {
const startAmount = this.order.info.input.startAmount;
// If curve doesn't exist OR startAmount equals minAmountOut, then it's EXACT_INPUT
return !this.order.info.input.curve ||
startAmount.eq(V3DutchOrderBuilder.getMinAmountOut(startAmount, this.order.info.input.curve.relativeAmounts))
? TradeType.EXACT_INPUT
: TradeType.EXACT_OUTPUT;
}
else {
throw new Error('Unsupported order type');
}
}

public get numOutputs(): number {
Expand Down
44 changes: 4 additions & 40 deletions lib/entities/HardQuoteResponse.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { CosignedV2DutchOrder } from '@uniswap/uniswapx-sdk';
import { BigNumber } from 'ethers';
import { CosignedV2DutchOrder, CosignedV3DutchOrder } from '@uniswap/uniswapx-sdk';
import { v4 as uuidv4 } from 'uuid';

import { HardQuoteRequest } from '.';
import { HardQuoteResponseData } from '../handlers/hard-quote/schema';
import { currentTimestampInMs, timestampInMstoSeconds } from '../util/time';

// data class for hard quote response helpers and conversions
export class HardQuoteResponse {
export abstract class HardQuoteResponse<T extends CosignedV2DutchOrder | CosignedV3DutchOrder> {
public createdAt: string;

constructor(
public request: HardQuoteRequest,
public order: CosignedV2DutchOrder,
public order: T,
public createdAtMs = currentTimestampInMs()
) {
this.createdAt = timestampInMstoSeconds(parseInt(this.createdAtMs));
Expand All @@ -29,23 +28,7 @@ export class HardQuoteResponse {
};
}

public toLog() {
return {
quoteId: this.quoteId,
requestId: this.requestId,
tokenInChainId: this.chainId,
tokenOutChainId: this.chainId,
tokenIn: this.tokenIn,
amountIn: this.amountIn.toString(),
tokenOut: this.tokenOut,
amountOut: this.amountOut.toString(),
swapper: this.swapper,
filler: this.filler,
orderHash: this.order.hash(),
createdAt: this.createdAt,
createdAtMs: this.createdAtMs,
};
}
public abstract toLog(): any;

public get quoteId(): string {
return this.request.quoteId ?? uuidv4();
Expand All @@ -67,25 +50,6 @@ export class HardQuoteResponse {
return this.request.tokenIn;
}

public get amountOut(): BigNumber {
const resolved = this.order.resolve({
timestamp: this.order.info.cosignerData.decayStartTime,
});
let amount = BigNumber.from(0);
for (const output of resolved.outputs) {
amount = amount.add(output.amount);
}

return amount;
}

public get amountIn(): BigNumber {
const resolved = this.order.resolve({
timestamp: this.order.info.cosignerData.decayStartTime,
});
return resolved.input.amount;
}

public get tokenOut(): string {
return this.request.tokenOut;
}
Expand Down
42 changes: 42 additions & 0 deletions lib/entities/V2HardQuoteResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { BigNumber } from "ethers";
import { HardQuoteResponse } from "./HardQuoteResponse";
import { CosignedV2DutchOrder } from "@uniswap/uniswapx-sdk";

export class V2HardQuoteResponse extends HardQuoteResponse<CosignedV2DutchOrder> {
public toLog() {
return {
quoteId: this.quoteId,
requestId: this.requestId,
tokenInChainId: this.chainId,
tokenOutChainId: this.chainId,
tokenIn: this.tokenIn,
amountIn: this.amountIn.toString(),
tokenOut: this.tokenOut,
amountOut: this.amountOut.toString(),
swapper: this.swapper,
filler: this.filler,
orderHash: this.order.hash(),
createdAt: this.createdAt,
createdAtMs: this.createdAtMs,
};
}

public get amountOut(): BigNumber {
const resolved = this.order.resolve({
timestamp: this.order.info.cosignerData.decayStartTime,
});
let amount = BigNumber.from(0);
for (const output of resolved.outputs) {
amount = amount.add(output.amount);
}

return amount;
}

public get amountIn(): BigNumber {
const resolved = this.order.resolve({
timestamp: this.order.info.cosignerData.decayStartTime,
});
return resolved.input.amount;
}
}
48 changes: 48 additions & 0 deletions lib/entities/V3HardQuoteResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { CosignedV3DutchOrder } from "@uniswap/uniswapx-sdk";
import { HardQuoteResponse } from "./HardQuoteResponse";

export class V3HardQuoteResponse extends HardQuoteResponse<CosignedV3DutchOrder> {
public toLog() {
return {
quoteId: this.quoteId,
requestId: this.requestId,
tokenInChainId: this.chainId,
tokenOutChainId: this.chainId,
tokenIn: this.tokenIn,
input: this.input,
tokenOut: this.tokenOut,
outputs: this.outputs,
swapper: this.swapper,
filler: this.filler,
orderHash: this.order.hash(),
createdAt: this.createdAt,
createdAtMs: this.createdAtMs,
};
}

get input() {
const input = this.order.info.input;
const relativeAmounts = input.curve.relativeAmounts.map((amount) => amount.toString());

return {
...input,
curve: {
...input.curve,
relativeAmounts,
},
}
}

get outputs() {
const processedOutputs = this.order.info.outputs.map((output) => {
return {
...output,
curve: {
...output.curve,
relativeAmounts: output.curve.relativeAmounts.map((amount) => amount.toString()),
}
}
});
return processedOutputs;
}
}
Loading

0 comments on commit c3dc16b

Please sign in to comment.