Kaynağa Gözat

finish swapper impl, change kim -> odos

ani 1 yıl önce
ebeveyn
işleme
a0c4cc7171

+ 100 - 0
express_relay/swap-beacon/src/abi.ts

@@ -0,0 +1,100 @@
+export const multicallAbi = {
+  type: "function",
+  name: "multicall",
+  inputs: [
+    {
+      name: "params",
+      type: "tuple",
+      internalType: "struct MulticallParams",
+      components: [
+        {
+          name: "sellTokens",
+          type: "tuple[]",
+          internalType: "struct TokenAmount[]",
+          components: [
+            {
+              name: "token",
+              type: "address",
+              internalType: "address",
+            },
+            {
+              name: "amount",
+              type: "uint256",
+              internalType: "uint256",
+            },
+          ],
+        },
+        {
+          name: "buyTokens",
+          type: "tuple[]",
+          internalType: "struct TokenAmount[]",
+          components: [
+            {
+              name: "token",
+              type: "address",
+              internalType: "address",
+            },
+            {
+              name: "amount",
+              type: "uint256",
+              internalType: "uint256",
+            },
+          ],
+        },
+        {
+          name: "targetCalls",
+          type: "tuple[]",
+          internalType: "struct TargetCall[]",
+          components: [
+            {
+              name: "targetContract",
+              type: "address",
+              internalType: "address",
+            },
+            {
+              name: "targetCalldata",
+              type: "bytes",
+              internalType: "bytes",
+            },
+            {
+              name: "targetCallValue",
+              type: "uint256",
+              internalType: "uint256",
+            },
+            {
+              name: "tokensToSend",
+              type: "tuple[]",
+              internalType: "struct TokenToSend[]",
+              components: [
+                {
+                  name: "tokenAmount",
+                  type: "tuple",
+                  internalType: "struct TokenAmount",
+                  components: [
+                    {
+                      name: "token",
+                      type: "address",
+                      internalType: "address",
+                    },
+                    {
+                      name: "amount",
+                      type: "uint256",
+                      internalType: "uint256",
+                    },
+                  ],
+                },
+                {
+                  name: "destination",
+                  type: "address",
+                  internalType: "address",
+                },
+              ],
+            },
+          ],
+        },
+      ],
+    },
+  ],
+  outputs: [],
+  stateMutability: "payable",
+};

+ 12 - 10
express_relay/swap-beacon/src/adapter/kim.ts → express_relay/swap-beacon/src/adapter/odos.ts

@@ -2,18 +2,18 @@ import { Adapter, Pair, ExtendedTargetCall, TokenToSend } from "../types";
 import { Address, Hex } from "viem";
 import { TokenAmount } from "@pythnetwork/express-relay-evm-js";
 
-export class KimAdapter implements Adapter {
+export class OdosAdapter implements Adapter {
   chainIds: string[] = ["34443"];
   getPrice(chainId: string, pair: Pair): Promise<number> {
     return Promise.resolve(1);
   }
-  constructSwap(
+  constructSwaps(
     chainId: string,
     tokenIn: Address,
     tokenOut: Address,
     amountIn?: bigint,
     amountOut?: bigint
-  ): ExtendedTargetCall {
+  ): ExtendedTargetCall[] {
     // construct the swap calldata manually
     let targetContract: Address = "0x59F78DE21a0b05d96Ae00c547BA951a3B905602f";
     let targetCalldata: Hex = "0x";
@@ -23,12 +23,14 @@ export class KimAdapter implements Adapter {
     // TODO: figure out this
     let tokensToReceive: TokenAmount[] = [];
 
-    return {
-      targetContract,
-      targetCalldata,
-      targetCallValue,
-      tokensToSend,
-      tokensToReceive,
-    };
+    return [
+      {
+        targetContract,
+        targetCalldata,
+        targetCallValue,
+        tokensToSend,
+        tokensToReceive,
+      },
+    ];
   }
 }

+ 130 - 53
express_relay/swap-beacon/src/index.ts

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

+ 2 - 2
express_relay/swap-beacon/src/types.ts

@@ -41,11 +41,11 @@ export type ExtendedTargetCall = TargetCall & {
 export interface Adapter {
   chainIds: string[];
   getPrice: (chainId: string, pair: Pair) => Promise<number>;
-  constructSwap: (
+  constructSwaps: (
     chainId: string,
     tokenIn: Address,
     tokenOut: Address,
     amountIn?: bigint,
     amountOut?: bigint
-  ) => ExtendedTargetCall;
+  ) => ExtendedTargetCall[];
 }

+ 29 - 0
pnpm-lock.yaml

@@ -4,6 +4,16 @@ settings:
   autoInstallPeers: true
   excludeLinksFromLockfile: false
 
+overrides:
+  '@injectivelabs/sdk-ts@1.10.72>@injectivelabs/token-metadata': 1.10.42
+  eslint-config-next>@typescript-eslint/parser: ^7.0.0
+  '@solana/web3.js@^1.93.0': 1.92.3
+
+patchedDependencies:
+  eccrypto@1.1.6:
+    hash: rjcfmtfgn3z72mudpdif5oxmye
+    path: patches/eccrypto@1.1.6.patch
+
 importers:
 
   .:
@@ -537,6 +547,25 @@ importers:
         specifier: ^1.0.0-rc.1
         version: 1.1.3(prettier@2.8.8)
 
+  express_relay/swap-beacon:
+    dependencies:
+      '@graphprotocol/graph-ts':
+        specifier: ^0.35.1
+        version: 0.35.1
+      '@pythnetwork/express-relay-evm-js':
+        specifier: ^0.8.1
+        version: link:../sdk/js
+      viem:
+        specifier: ^2.18.6
+        version: 2.18.6(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@6.0.4)(zod@3.23.8)
+    devDependencies:
+      '@types/node':
+        specifier: ^22.0.0
+        version: 22.0.0
+      typescript:
+        specifier: ^5.5.4
+        version: 5.5.4
+
   express_relay/swaps-beacon:
     dependencies:
       '@graphprotocol/graph-ts':