-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #270 from Uniswap/add-hard-quote
add hard quote
- Loading branch information
Showing
12 changed files
with
1,197 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { TradeType } from '@uniswap/sdk-core'; | ||
import { UnsignedV2DutchOrder } from '@uniswap/uniswapx-sdk'; | ||
import { BigNumber, utils } from 'ethers'; | ||
|
||
import { HardQuoteRequestBody } from '../handlers/hard-quote'; | ||
import { QuoteRequestDataJSON } from '.'; | ||
|
||
export class HardQuoteRequest { | ||
public order: UnsignedV2DutchOrder; | ||
|
||
public static fromHardRequestBody(_body: HardQuoteRequestBody): HardQuoteRequest { | ||
// TODO: parse hard request into the same V2 request object format | ||
throw new Error('Method not implemented.'); | ||
} | ||
|
||
constructor(private data: HardQuoteRequestBody) { | ||
this.order = UnsignedV2DutchOrder.parse(data.encodedInnerOrder, data.tokenInChainId); | ||
} | ||
|
||
public toCleanJSON(): QuoteRequestDataJSON { | ||
return { | ||
tokenInChainId: this.tokenInChainId, | ||
tokenOutChainId: this.tokenOutChainId, | ||
swapper: utils.getAddress(this.swapper), | ||
requestId: this.requestId, | ||
tokenIn: this.tokenIn, | ||
tokenOut: this.tokenOut, | ||
amount: this.amount.toString(), | ||
type: TradeType[this.type], | ||
numOutputs: this.numOutputs, | ||
...(this.quoteId && { quoteId: this.quoteId }), | ||
}; | ||
} | ||
|
||
// return an opposing quote request, | ||
// i.e. quoting the other side of the trade | ||
public toOpposingCleanJSON(): QuoteRequestDataJSON { | ||
const type = this.type === TradeType.EXACT_INPUT ? TradeType.EXACT_OUTPUT : TradeType.EXACT_INPUT; | ||
return { | ||
tokenInChainId: this.tokenInChainId, | ||
tokenOutChainId: this.tokenOutChainId, | ||
requestId: this.requestId, | ||
swapper: utils.getAddress(this.swapper), | ||
// switch tokenIn/tokenOut | ||
tokenIn: utils.getAddress(this.tokenOut), | ||
tokenOut: utils.getAddress(this.tokenIn), | ||
amount: this.amount.toString(), | ||
// switch tradeType | ||
type: TradeType[type], | ||
numOutputs: this.numOutputs, | ||
...(this.quoteId && { quoteId: this.quoteId }), | ||
}; | ||
} | ||
|
||
public get requestId(): string { | ||
return this.data.requestId; | ||
} | ||
|
||
public get tokenInChainId(): number { | ||
return this.data.tokenInChainId; | ||
} | ||
|
||
public get tokenOutChainId(): number { | ||
return this.data.tokenInChainId; | ||
} | ||
|
||
public get swapper(): string { | ||
return this.order.info.swapper; | ||
} | ||
|
||
public get tokenIn(): string { | ||
return utils.getAddress(this.order.info.baseInput.token); | ||
} | ||
|
||
public get tokenOut(): string { | ||
return utils.getAddress(this.order.info.baseOutputs[0].token); | ||
} | ||
|
||
public get amount(): BigNumber { | ||
if (this.type === TradeType.EXACT_INPUT) { | ||
return this.order.info.baseInput.startAmount; | ||
} else { | ||
const amount = BigNumber.from(0); | ||
for (const output of this.order.info.baseOutputs) { | ||
amount.add(output.startAmount); | ||
} | ||
|
||
return amount; | ||
} | ||
} | ||
|
||
public get type(): TradeType { | ||
return this.order.info.baseInput.startAmount.eq(this.order.info.baseInput.endAmount) | ||
? TradeType.EXACT_INPUT | ||
: TradeType.EXACT_OUTPUT; | ||
} | ||
|
||
public get numOutputs(): number { | ||
return this.order.info.baseOutputs.length; | ||
} | ||
|
||
public get cosigner(): string { | ||
return this.order.info.cosigner; | ||
} | ||
|
||
public get quoteId(): string | undefined { | ||
return this.data.quoteId; | ||
} | ||
|
||
public set quoteId(quoteId: string | undefined) { | ||
this.data.quoteId = quoteId; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export * from './analytics-events'; | ||
export * from './aws-metrics-logger'; | ||
export * from './HardQuoteRequest'; | ||
export * from './QuoteRequest'; | ||
export * from './QuoteResponse'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { MetricLoggerUnit } from '@uniswap/smart-order-router'; | ||
import Joi from 'joi'; | ||
|
||
import { HardQuoteRequest, Metric } from '../../entities'; | ||
import { timestampInMstoSeconds } from '../../util/time'; | ||
import { APIGLambdaHandler } from '../base'; | ||
import { APIHandleRequestParams, ErrorResponse, Response } from '../base/api-handler'; | ||
import { ContainerInjected, RequestInjected } from './injector'; | ||
import { HardQuoteRequestBody, HardQuoteRequestBodyJoi } from './schema'; | ||
|
||
export class QuoteHandler extends APIGLambdaHandler< | ||
ContainerInjected, | ||
RequestInjected, | ||
HardQuoteRequestBody, | ||
void, | ||
null | ||
> { | ||
public async handleRequest( | ||
params: APIHandleRequestParams<ContainerInjected, RequestInjected, HardQuoteRequestBody, void> | ||
): Promise<ErrorResponse | Response<null>> { | ||
const { | ||
requestInjected: { log, metric }, | ||
requestBody, | ||
} = params; | ||
const start = Date.now(); | ||
|
||
metric.putMetric(Metric.QUOTE_REQUESTED, 1, MetricLoggerUnit.Count); | ||
|
||
const request = HardQuoteRequest.fromHardRequestBody(requestBody); | ||
|
||
// TODO: finalize on v2 metrics logging | ||
log.info({ | ||
eventType: 'HardQuoteRequest', | ||
body: { | ||
requestId: request.requestId, | ||
tokenInChainId: request.tokenInChainId, | ||
tokenOutChainId: request.tokenInChainId, | ||
encoded: requestBody.encodedInnerOrder, | ||
sig: requestBody.innerSig, | ||
createdAt: timestampInMstoSeconds(start), | ||
createdAtMs: start.toString(), | ||
}, | ||
}); | ||
|
||
return { | ||
statusCode: 200, | ||
body: null, | ||
}; | ||
} | ||
|
||
protected requestBodySchema(): Joi.ObjectSchema | null { | ||
return HardQuoteRequestBodyJoi; | ||
} | ||
|
||
protected requestQueryParamsSchema(): Joi.ObjectSchema | null { | ||
return null; | ||
} | ||
|
||
protected responseBodySchema(): Joi.ObjectSchema | null { | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { QuoteHandler as HardQuoteHandler } from './handler'; | ||
export { QuoteInjector as HardQuoteInjector } from './injector'; | ||
export * from './schema'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { IMetric, setGlobalLogger, setGlobalMetric } from '@uniswap/smart-order-router'; | ||
import { MetricsLogger } from 'aws-embedded-metrics'; | ||
import { APIGatewayProxyEvent, Context } from 'aws-lambda'; | ||
import { default as bunyan, default as Logger } from 'bunyan'; | ||
|
||
import { | ||
BETA_S3_KEY, | ||
FADE_RATE_BUCKET, | ||
FADE_RATE_S3_KEY, | ||
PRODUCTION_S3_KEY, | ||
WEBHOOK_CONFIG_BUCKET, | ||
} from '../../constants'; | ||
import { AWSMetricsLogger, UniswapXParamServiceMetricDimension } from '../../entities/aws-metrics-logger'; | ||
import { S3WebhookConfigurationProvider } from '../../providers'; | ||
import { FirehoseLogger } from '../../providers/analytics'; | ||
import { S3CircuitBreakerConfigurationProvider } from '../../providers/circuit-breaker/s3'; | ||
import { MockFillerComplianceConfigurationProvider } from '../../providers/compliance'; | ||
import { Quoter, WebhookQuoter } from '../../quoters'; | ||
import { STAGE } from '../../util/stage'; | ||
import { ApiInjector, ApiRInj } from '../base/api-handler'; | ||
import { HardQuoteRequestBody } from './schema'; | ||
|
||
export interface ContainerInjected { | ||
quoters: Quoter[]; | ||
firehose: FirehoseLogger; | ||
} | ||
|
||
export interface RequestInjected extends ApiRInj { | ||
metric: IMetric; | ||
} | ||
|
||
export class QuoteInjector extends ApiInjector<ContainerInjected, RequestInjected, HardQuoteRequestBody, void> { | ||
public async buildContainerInjected(): Promise<ContainerInjected> { | ||
const log: Logger = bunyan.createLogger({ | ||
name: this.injectorName, | ||
serializers: bunyan.stdSerializers, | ||
level: bunyan.INFO, | ||
}); | ||
|
||
const stage = process.env['stage']; | ||
const s3Key = stage === STAGE.BETA ? BETA_S3_KEY : PRODUCTION_S3_KEY; | ||
|
||
const circuitBreakerProvider = new S3CircuitBreakerConfigurationProvider( | ||
log, | ||
`${FADE_RATE_BUCKET}-${stage}-1`, | ||
FADE_RATE_S3_KEY | ||
); | ||
|
||
const webhookProvider = new S3WebhookConfigurationProvider(log, `${WEBHOOK_CONFIG_BUCKET}-${stage}-1`, s3Key); | ||
await webhookProvider.fetchEndpoints(); | ||
|
||
// TODO: decide if we should handle filler compliance differently | ||
//const complianceKey = stage === STAGE.BETA ? BETA_COMPLIANCE_S3_KEY : PROD_COMPLIANCE_S3_KEY; | ||
//const fillerComplianceProvider = new S3FillerComplianceConfigurationProvider( | ||
// log, | ||
// `${COMPLIANCE_CONFIG_BUCKET}-${stage}-1`, | ||
// complianceKey | ||
//); | ||
const fillerComplianceProvider = new MockFillerComplianceConfigurationProvider([]); | ||
|
||
const firehose = new FirehoseLogger(log, process.env.ANALYTICS_STREAM_ARN!); | ||
|
||
const quoters: Quoter[] = [ | ||
new WebhookQuoter(log, firehose, webhookProvider, circuitBreakerProvider, fillerComplianceProvider), | ||
]; | ||
return { | ||
quoters: quoters, | ||
firehose: firehose, | ||
}; | ||
} | ||
|
||
public async getRequestInjected( | ||
_containerInjected: ContainerInjected, | ||
requestBody: HardQuoteRequestBody, | ||
_requestQueryParams: void, | ||
_event: APIGatewayProxyEvent, | ||
context: Context, | ||
log: Logger, | ||
metricsLogger: MetricsLogger | ||
): Promise<RequestInjected> { | ||
const requestId = context.awsRequestId; | ||
|
||
log = log.child({ | ||
serializers: bunyan.stdSerializers, | ||
requestBody, | ||
requestId, | ||
}); | ||
setGlobalLogger(log); | ||
|
||
metricsLogger.setNamespace('Uniswap'); | ||
metricsLogger.setDimensions(UniswapXParamServiceMetricDimension); | ||
const metric = new AWSMetricsLogger(metricsLogger); | ||
setGlobalMetric(metric); | ||
|
||
return { | ||
log, | ||
metric, | ||
requestId, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import Joi from 'joi'; | ||
|
||
import { FieldValidator } from '../../util/validator'; | ||
|
||
/* Hard quote request from user */ | ||
export const HardQuoteRequestBodyJoi = Joi.object({ | ||
requestId: FieldValidator.requestId.required(), | ||
quoteId: FieldValidator.uuid.optional(), | ||
encodedInnerOrder: Joi.string().required(), | ||
innerSig: FieldValidator.rawSignature.required(), | ||
tokenInChainId: FieldValidator.chainId.required(), | ||
tokenOutChainId: Joi.number().integer().valid(Joi.ref('tokenInChainId')).required(), | ||
}); | ||
|
||
export type HardQuoteRequestBody = { | ||
requestId: string; | ||
quoteId?: string; | ||
encodedInnerOrder: string; | ||
innerSig: string; | ||
tokenInChainId: number; | ||
tokenOutChainId: number; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.