|
|
@@ -1,4 +1,4 @@
|
|
|
-import { KimAdapter } from "./adapter/kim";
|
|
|
+import { OdosAdapter } from "./adapter/odos";
|
|
|
import { SWAP_ADAPTER_CONFIGS } from "./const";
|
|
|
import {
|
|
|
Client,
|
|
|
@@ -6,9 +6,11 @@ import {
|
|
|
OpportunityParams,
|
|
|
ChainId,
|
|
|
TokenAmount,
|
|
|
+ OPPORTUNITY_ADAPTER_CONFIGS,
|
|
|
} from "@pythnetwork/express-relay-evm-js";
|
|
|
import { Adapter, ExtendedTargetCall, TargetCall } from "./types";
|
|
|
-import { Address } from "viem";
|
|
|
+import { Address, Hex, encodeFunctionData } from "viem";
|
|
|
+import { multicallAbi } from "./abi";
|
|
|
|
|
|
export class SwapBeaconError extends Error {}
|
|
|
|
|
|
@@ -34,7 +36,7 @@ export class SwapBeacon {
|
|
|
undefined,
|
|
|
this.opportunityHandler.bind(this)
|
|
|
);
|
|
|
- this.adapters = [new KimAdapter()];
|
|
|
+ this.adapters = [new OdosAdapter()];
|
|
|
}
|
|
|
|
|
|
private async getOptimalAdapter(
|
|
|
@@ -58,9 +60,97 @@ export class SwapBeacon {
|
|
|
];
|
|
|
}
|
|
|
|
|
|
+ private makeMulticallCalldata(
|
|
|
+ opportunity: Opportunity,
|
|
|
+ swapsSell: ExtendedTargetCall[],
|
|
|
+ swapsBuy: ExtendedTargetCall[],
|
|
|
+ sellTokens: TokenAmount[],
|
|
|
+ buyTokens: TokenAmount[]
|
|
|
+ ): Hex {
|
|
|
+ const originalTargetCall = {
|
|
|
+ targetContract: opportunity.targetContract,
|
|
|
+ targetCalldata: opportunity.targetCalldata,
|
|
|
+ targetCallValue: opportunity.targetCallValue,
|
|
|
+ tokensToSend: opportunity.sellTokens.map((token) => ({
|
|
|
+ tokenAmount: token,
|
|
|
+ destination: opportunity.targetContract,
|
|
|
+ })),
|
|
|
+ };
|
|
|
+ const swapsSellTargetCalls = swapsSell.map((swap) => ({
|
|
|
+ targetContract: swap.targetContract,
|
|
|
+ targetCalldata: swap.targetCalldata,
|
|
|
+ targetCallValue: swap.targetCallValue,
|
|
|
+ tokensToSend: swap.tokensToSend,
|
|
|
+ }));
|
|
|
+ const swapsBuyTargetCalls = swapsBuy.map((swap) => ({
|
|
|
+ targetContract: swap.targetContract,
|
|
|
+ targetCalldata: swap.targetCalldata,
|
|
|
+ targetCallValue: swap.targetCallValue,
|
|
|
+ tokensToSend: swap.tokensToSend,
|
|
|
+ }));
|
|
|
+ const multicallTargetCalls = [
|
|
|
+ ...swapsSellTargetCalls,
|
|
|
+ originalTargetCall,
|
|
|
+ ...swapsBuyTargetCalls,
|
|
|
+ ];
|
|
|
+
|
|
|
+ return encodeFunctionData({
|
|
|
+ abi: [multicallAbi],
|
|
|
+ args: [[sellTokens, buyTokens, multicallTargetCalls]],
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private extractTokenAmounts(
|
|
|
+ extendedTargetCall: ExtendedTargetCall[]
|
|
|
+ ): [TokenAmount[], TokenAmount[]] {
|
|
|
+ let inputsAll: Record<Address, bigint> = {};
|
|
|
+ let outputsAll: Record<Address, bigint> = {};
|
|
|
+
|
|
|
+ for (let call of extendedTargetCall) {
|
|
|
+ call.tokensToSend.forEach((tokenToSend) => {
|
|
|
+ const token = tokenToSend.tokenAmount.token;
|
|
|
+ let amount = tokenToSend.tokenAmount.amount;
|
|
|
+
|
|
|
+ if (token in outputsAll) {
|
|
|
+ const deduction = Math.min(Number(outputsAll[token]), Number(amount));
|
|
|
+ outputsAll[token] -= BigInt(deduction);
|
|
|
+ amount -= BigInt(deduction);
|
|
|
+
|
|
|
+ if (outputsAll[token] === 0n) {
|
|
|
+ delete outputsAll[token];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (amount > 0n) {
|
|
|
+ inputsAll[token] = amount;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ call.tokensToReceive.forEach((tokenToReceive) => {
|
|
|
+ const token = tokenToReceive.token;
|
|
|
+ const amount = tokenToReceive.amount;
|
|
|
+
|
|
|
+ if (token in outputsAll) {
|
|
|
+ outputsAll[token] += amount;
|
|
|
+ } else {
|
|
|
+ outputsAll[token] = amount;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ const inputsTokenAmount: TokenAmount[] = Object.entries(inputsAll).map(
|
|
|
+ ([token, amount]) => ({ token: token as Address, amount: amount })
|
|
|
+ );
|
|
|
+ const outputsTokenAmount: TokenAmount[] = Object.entries(outputsAll).map(
|
|
|
+ ([token, amount]) => ({ token: token as Address, amount: amount })
|
|
|
+ );
|
|
|
+
|
|
|
+ return [inputsTokenAmount, outputsTokenAmount];
|
|
|
+ }
|
|
|
+
|
|
|
private createSwapOpportunity(
|
|
|
opportunity: Opportunity,
|
|
|
- asset: Address,
|
|
|
+ base: Address,
|
|
|
swapsSell: ExtendedTargetCall[],
|
|
|
swapsBuy: ExtendedTargetCall[]
|
|
|
): Opportunity {
|
|
|
@@ -70,34 +160,17 @@ export class SwapBeacon {
|
|
|
swapsSell.reduce((prev, curr) => prev + curr.targetCallValue, 0n) +
|
|
|
swapsBuy.reduce((prev, curr) => prev + curr.targetCallValue, 0n) +
|
|
|
opportunity.targetCallValue;
|
|
|
- const targetCalldata = "0x"; // TODO: construct multicall calldata
|
|
|
|
|
|
- // TODO: extract new sellTokens and buyTokens correctly!
|
|
|
- const sellTokens: TokenAmount[] = [
|
|
|
- {
|
|
|
- token: asset,
|
|
|
- amount: swapsSell.reduce(
|
|
|
- (prev, curr) =>
|
|
|
- prev +
|
|
|
- curr.tokensToSend.reduce(
|
|
|
- (prev, curr) => prev + curr.tokenAmount.amount,
|
|
|
- 0n
|
|
|
- ),
|
|
|
- 0n
|
|
|
- ),
|
|
|
- },
|
|
|
- ];
|
|
|
- const buyTokens: TokenAmount[] = [
|
|
|
- {
|
|
|
- token: asset,
|
|
|
- amount: swapsBuy.reduce(
|
|
|
- (prev, curr) =>
|
|
|
- prev +
|
|
|
- curr.tokensToReceive.reduce((prev, curr) => prev + curr.amount, 0n),
|
|
|
- 0n
|
|
|
- ),
|
|
|
- },
|
|
|
- ];
|
|
|
+ const sellTokens: TokenAmount[] = this.extractTokenAmounts(swapsSell)[0];
|
|
|
+ const buyTokens: TokenAmount[] = this.extractTokenAmounts(swapsBuy)[1];
|
|
|
+
|
|
|
+ const targetCalldata = this.makeMulticallCalldata(
|
|
|
+ opportunity,
|
|
|
+ swapsSell,
|
|
|
+ swapsBuy,
|
|
|
+ sellTokens,
|
|
|
+ buyTokens
|
|
|
+ );
|
|
|
|
|
|
return {
|
|
|
...opportunity,
|
|
|
@@ -111,14 +184,14 @@ export class SwapBeacon {
|
|
|
|
|
|
async convertOpportunity(
|
|
|
opportunity: Opportunity,
|
|
|
- asset: Address
|
|
|
+ base: Address
|
|
|
): Promise<Opportunity> {
|
|
|
const promisesOptimalAdaptersSell = opportunity.sellTokens.map(
|
|
|
(sellToken) =>
|
|
|
- this.getOptimalAdapter(opportunity.chainId, asset, sellToken.token)
|
|
|
+ this.getOptimalAdapter(opportunity.chainId, base, sellToken.token)
|
|
|
);
|
|
|
const promisesOptimalAdaptersBuy = opportunity.buyTokens.map((buyToken) =>
|
|
|
- this.getOptimalAdapter(opportunity.chainId, buyToken.token, asset)
|
|
|
+ this.getOptimalAdapter(opportunity.chainId, buyToken.token, base)
|
|
|
);
|
|
|
|
|
|
const [optimalAdaptersSell, optimalAdaptersBuy] = await Promise.all([
|
|
|
@@ -126,26 +199,30 @@ export class SwapBeacon {
|
|
|
Promise.all(promisesOptimalAdaptersBuy),
|
|
|
]);
|
|
|
|
|
|
- const swapsSell = optimalAdaptersSell.map((adapter, index) =>
|
|
|
- adapter.constructSwap(
|
|
|
- opportunity.chainId,
|
|
|
- asset,
|
|
|
- opportunity.sellTokens[index].token,
|
|
|
- undefined,
|
|
|
- opportunity.sellTokens[index].amount
|
|
|
+ const swapsSell = optimalAdaptersSell
|
|
|
+ .map((adapter, index) =>
|
|
|
+ adapter.constructSwaps(
|
|
|
+ opportunity.chainId,
|
|
|
+ base,
|
|
|
+ opportunity.sellTokens[index].token,
|
|
|
+ undefined,
|
|
|
+ opportunity.sellTokens[index].amount
|
|
|
+ )
|
|
|
)
|
|
|
- );
|
|
|
- const swapsBuy = optimalAdaptersBuy.map((adapter, index) =>
|
|
|
- adapter.constructSwap(
|
|
|
- opportunity.chainId,
|
|
|
- opportunity.buyTokens[index].token,
|
|
|
- asset,
|
|
|
- opportunity.buyTokens[index].amount,
|
|
|
- undefined
|
|
|
+ .reduce((acc, val) => acc.concat(val), []);
|
|
|
+ const swapsBuy = optimalAdaptersBuy
|
|
|
+ .map((adapter, index) =>
|
|
|
+ adapter.constructSwaps(
|
|
|
+ opportunity.chainId,
|
|
|
+ opportunity.buyTokens[index].token,
|
|
|
+ base,
|
|
|
+ opportunity.buyTokens[index].amount,
|
|
|
+ undefined
|
|
|
+ )
|
|
|
)
|
|
|
- );
|
|
|
+ .reduce((acc, val) => acc.concat(val), []);
|
|
|
|
|
|
- return this.createSwapOpportunity(opportunity, asset, swapsSell, swapsBuy);
|
|
|
+ return this.createSwapOpportunity(opportunity, base, swapsSell, swapsBuy);
|
|
|
}
|
|
|
|
|
|
async opportunityHandler(opportunity: Opportunity) {
|
|
|
@@ -153,10 +230,10 @@ export class SwapBeacon {
|
|
|
const swapAdapterConfig = getSwapAdapterConfig(opportunity.chainId);
|
|
|
|
|
|
await Promise.all(
|
|
|
- swapAdapterConfig.liquidAssets.map(async (asset) => {
|
|
|
+ swapAdapterConfig.liquidAssets.map(async (base) => {
|
|
|
const { opportunityId, ...params } = await this.convertOpportunity(
|
|
|
opportunity,
|
|
|
- asset
|
|
|
+ base
|
|
|
);
|
|
|
await this.client.submitOpportunity(params);
|
|
|
})
|