From 27341693ea1b8c41a5214b04bfb854acf7642dc0 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Mon, 13 Nov 2023 13:42:17 +0200 Subject: [PATCH 01/15] MEX-410: position creator initial module Signed-off-by: Claudiu Lataretu --- src/abis/auto-pos-creator.abi.json | 253 ++++++++++++++++++ src/config/devnet2.json | 3 +- .../models/position.creator.model.ts | 28 ++ .../position.creator.module.ts | 18 ++ src/public.app.module.ts | 2 + .../mx.proxy.service.ts | 8 + 6 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 src/abis/auto-pos-creator.abi.json create mode 100644 src/modules/position-creator/models/position.creator.model.ts create mode 100644 src/modules/position-creator/position.creator.module.ts diff --git a/src/abis/auto-pos-creator.abi.json b/src/abis/auto-pos-creator.abi.json new file mode 100644 index 000000000..57682d9f5 --- /dev/null +++ b/src/abis/auto-pos-creator.abi.json @@ -0,0 +1,253 @@ +{ + "buildInfo": { + "rustc": { + "version": "1.73.0-nightly", + "commitHash": "4c8bb79d9f565115637cc6da739f8389e79f3a29", + "commitDate": "2023-07-15", + "channel": "Nightly", + "short": "rustc 1.73.0-nightly (4c8bb79d9 2023-07-15)" + }, + "contractCrate": { + "name": "auto-pos-creator", + "version": "0.0.0" + }, + "framework": { + "name": "multiversx-sc", + "version": "0.43.3" + } + }, + "name": "AutoPosCreator", + "constructor": { + "inputs": [ + { + "name": "egld_wrapper_address", + "type": "Address" + }, + { + "name": "wegld_token_id", + "type": "TokenIdentifier" + } + ], + "outputs": [] + }, + "endpoints": [ + { + "name": "upgrade", + "mutability": "mutable", + "inputs": [], + "outputs": [] + }, + { + "name": "addPairsToWhitelist", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "pair_addresses", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "removePairsFromWhitelist", + "onlyOwner": true, + "mutability": "mutable", + "inputs": [ + { + "name": "pair_addresses", + "type": "variadic
", + "multi_arg": true + } + ], + "outputs": [] + }, + { + "name": "createPosFromSingleToken", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "dest_pair_address", + "type": "Address" + }, + { + "name": "steps", + "type": "StepsToPerform" + }, + { + "name": "add_liq_first_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "add_liq_second_token_min_amount_out", + "type": "BigUint" + } + ], + "outputs": [ + { + "type": "List" + } + ] + }, + { + "docs": [ + "Create pos from two payments, entering the pair for the two tokens", + "It will try doing this with the optimal amounts,", + "performing swaps before adding liqudity if necessary" + ], + "name": "createPosFromTwoTokens", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "steps", + "type": "StepsToPerform" + }, + { + "name": "add_liq_first_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "add_liq_second_token_min_amount_out", + "type": "BigUint" + } + ], + "outputs": [ + { + "type": "List" + } + ] + }, + { + "name": "createPosFromLp", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "steps", + "type": "StepsToPerform" + } + ], + "outputs": [ + { + "type": "List" + } + ] + }, + { + "name": "fullExitPos", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ + { + "name": "first_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "second_token_min_amont_out", + "type": "BigUint" + } + ], + "outputs": [ + { + "type": "List" + } + ] + } + ], + "events": [], + "hasCallback": false, + "types": { + "EsdtTokenPayment": { + "type": "struct", + "fields": [ + { + "name": "token_identifier", + "type": "TokenIdentifier" + }, + { + "name": "token_nonce", + "type": "u64" + }, + { + "name": "amount", + "type": "BigUint" + } + ] + }, + "FarmConfig": { + "type": "struct", + "fields": [ + { + "name": "state", + "type": "State" + }, + { + "name": "farm_token_id", + "type": "TokenIdentifier" + }, + { + "name": "farming_token_id", + "type": "TokenIdentifier" + } + ] + }, + "MetastakingConfig": { + "type": "struct", + "fields": [ + { + "name": "dual_yield_token_id", + "type": "TokenIdentifier" + }, + { + "name": "lp_farm_token_id", + "type": "TokenIdentifier" + } + ] + }, + "State": { + "type": "enum", + "variants": [ + { + "name": "Inactive", + "discriminant": 0 + }, + { + "name": "Active", + "discriminant": 1 + }, + { + "name": "PartialActive", + "discriminant": 2 + } + ] + }, + "StepsToPerform": { + "type": "enum", + "variants": [ + { + "name": "AddLiquidity", + "discriminant": 0 + }, + { + "name": "EnterFarm", + "discriminant": 1 + }, + { + "name": "EnterMetastaking", + "discriminant": 2 + } + ] + } + } +} diff --git a/src/config/devnet2.json b/src/config/devnet2.json index c9fc12f6c..2b6c925c0 100644 --- a/src/config/devnet2.json +++ b/src/config/devnet2.json @@ -28,7 +28,8 @@ "energyUpdate": "erd1qqqqqqqqqqqqqpgqz2ctz77j9we0r99mnhehv24he3pxmsmq0n4sntf4n7", "tokenUnstake": "erd1qqqqqqqqqqqqqpgqu6s4e5mndgf37psxw9mdlmjavp2er3f00n4snwyk3q", "lockedTokenWrapper": "erd1qqqqqqqqqqqqqpgqasr2asq07e6ur274eecx3vd0pnej2vxs0n4sqqyp52", - "escrow": "erd1qqqqqqqqqqqqqpgqnx4t9kwy5n9atuumvnrluv5yrwmpxzx60n4sj497ms" + "escrow": "erd1qqqqqqqqqqqqqpgqnx4t9kwy5n9atuumvnrluv5yrwmpxzx60n4sj497ms", + "positionCreator": "erd1qqqqqqqqqqqqqpgqh3zcutxk3wmfvevpyymaehvc3k0knyq70n4sg6qcj6" }, "governance": { "oldEnergy": { diff --git a/src/modules/position-creator/models/position.creator.model.ts b/src/modules/position-creator/models/position.creator.model.ts new file mode 100644 index 000000000..e605aeeb8 --- /dev/null +++ b/src/modules/position-creator/models/position.creator.model.ts @@ -0,0 +1,28 @@ +import { EnumType, EnumVariantDefinition } from '@multiversx/sdk-core/out'; +import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; + +export enum StepsToPerform { + ADD_LIQUIDITY, + ENTER_FARM, + ENTER_METASTAKING, +} + +registerEnumType(StepsToPerform, { + name: 'StepsToPerform', +}); + +export const StepsToPerformEnumType = new EnumType('StepsToPerform', [ + new EnumVariantDefinition('AddLiquidity', 0), + new EnumVariantDefinition('EnterFarm', 1), + new EnumVariantDefinition('EnterMetastaking', 2), +]); + +@ObjectType() +export class PositionCreatorModel { + @Field() + address: string; + + constructor(init: Partial) { + Object.assign(this, init); + } +} diff --git a/src/modules/position-creator/position.creator.module.ts b/src/modules/position-creator/position.creator.module.ts new file mode 100644 index 000000000..91a26523f --- /dev/null +++ b/src/modules/position-creator/position.creator.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { PositionCreatorResolver } from './position.creator.resolver'; +import { PairModule } from '../pair/pair.module'; +import { RouterModule } from '../router/router.module'; +import { PositionCreatorComputeService } from './services/position.creator.compute'; +import { PositionCreatorTransactionService } from './services/position.creator.transaction'; +import { MXCommunicationModule } from 'src/services/multiversx-communication/mx.communication.module'; + +@Module({ + imports: [PairModule, RouterModule, MXCommunicationModule], + providers: [ + PositionCreatorComputeService, + PositionCreatorTransactionService, + PositionCreatorResolver, + ], + exports: [], +}) +export class PositionCreatorModule {} diff --git a/src/public.app.module.ts b/src/public.app.module.ts index 45af2ce73..b1a056181 100644 --- a/src/public.app.module.ts +++ b/src/public.app.module.ts @@ -36,6 +36,7 @@ import { EscrowModule } from './modules/escrow/escrow.module'; import { GovernanceModule } from './modules/governance/governance.module'; import { DynamicModuleUtils } from './utils/dynamic.module.utils'; import '@multiversx/sdk-nestjs-common/lib/utils/extensions/array.extensions'; +import { PositionCreatorModule } from './modules/position-creator/position.creator.module'; @Module({ imports: [ @@ -95,6 +96,7 @@ import '@multiversx/sdk-nestjs-common/lib/utils/extensions/array.extensions'; LockedTokenWrapperModule, EscrowModule, GovernanceModule, + PositionCreatorModule, DynamicModuleUtils.getCacheModule(), ], }) diff --git a/src/services/multiversx-communication/mx.proxy.service.ts b/src/services/multiversx-communication/mx.proxy.service.ts index 16487fb11..106627498 100644 --- a/src/services/multiversx-communication/mx.proxy.service.ts +++ b/src/services/multiversx-communication/mx.proxy.service.ts @@ -223,6 +223,14 @@ export class MXProxyService { ); } + async getPostitionCreatorContract(): Promise { + return this.getSmartContract( + scAddress.positionCreator, + abiConfig.positionCreator, + 'AutoPosCreator', + ); + } + async getSmartContract( contractAddress: string, contractAbiPath: string, From 004275c89d81fcfc1b193790ce8015891dc77db2 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Mon, 20 Nov 2023 16:59:18 +0200 Subject: [PATCH 02/15] MEX-410: create lp position from single token Signed-off-by: Claudiu Lataretu --- src/abis/auto-pos-creator.abi.json | 252 ++++++++++++------ src/config/default.json | 6 +- src/modules/auto-router/auto-router.module.ts | 2 +- .../models/position.creator.model.ts | 19 +- .../position.creator.module.ts | 8 +- .../position.creator.resolver.ts | 38 +++ .../services/position.creator.compute.ts | 42 +++ .../services/position.creator.transaction.ts | 114 ++++++++ 8 files changed, 377 insertions(+), 104 deletions(-) create mode 100644 src/modules/position-creator/position.creator.resolver.ts create mode 100644 src/modules/position-creator/services/position.creator.compute.ts create mode 100644 src/modules/position-creator/services/position.creator.transaction.ts diff --git a/src/abis/auto-pos-creator.abi.json b/src/abis/auto-pos-creator.abi.json index 57682d9f5..916ad1919 100644 --- a/src/abis/auto-pos-creator.abi.json +++ b/src/abis/auto-pos-creator.abi.json @@ -13,7 +13,7 @@ }, "framework": { "name": "multiversx-sc", - "version": "0.43.3" + "version": "0.44.0" } }, "name": "AutoPosCreator", @@ -24,8 +24,8 @@ "type": "Address" }, { - "name": "wegld_token_id", - "type": "TokenIdentifier" + "name": "router_address", + "type": "Address" } ], "outputs": [] @@ -38,46 +38,73 @@ "outputs": [] }, { - "name": "addPairsToWhitelist", - "onlyOwner": true, + "name": "createLpPosFromSingleToken", "mutability": "mutable", + "payableInTokens": [ + "*" + ], "inputs": [ { - "name": "pair_addresses", - "type": "variadic
", + "name": "pair_address", + "type": "Address" + }, + { + "name": "add_liq_first_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "add_liq_second_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "swap_operations", + "type": "variadic>", "multi_arg": true } ], - "outputs": [] + "outputs": [ + { + "type": "List" + } + ] }, { - "name": "removePairsFromWhitelist", - "onlyOwner": true, + "name": "createLpPosFromTwoTokens", "mutability": "mutable", + "payableInTokens": [ + "*" + ], "inputs": [ { - "name": "pair_addresses", - "type": "variadic
", - "multi_arg": true + "name": "pair_address", + "type": "Address" + }, + { + "name": "add_liq_first_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "add_liq_second_token_min_amount_out", + "type": "BigUint" } ], - "outputs": [] + "outputs": [ + { + "type": "List" + } + ] }, { - "name": "createPosFromSingleToken", + "name": "createFarmPosFromSingleToken", "mutability": "mutable", "payableInTokens": [ "*" ], "inputs": [ { - "name": "dest_pair_address", + "name": "farm_address", "type": "Address" }, - { - "name": "steps", - "type": "StepsToPerform" - }, { "name": "add_liq_first_token_min_amount_out", "type": "BigUint" @@ -85,6 +112,11 @@ { "name": "add_liq_second_token_min_amount_out", "type": "BigUint" + }, + { + "name": "swap_operations", + "type": "variadic>", + "multi_arg": true } ], "outputs": [ @@ -94,20 +126,15 @@ ] }, { - "docs": [ - "Create pos from two payments, entering the pair for the two tokens", - "It will try doing this with the optimal amounts,", - "performing swaps before adding liqudity if necessary" - ], - "name": "createPosFromTwoTokens", + "name": "createFarmPosFromTwoTokens", "mutability": "mutable", "payableInTokens": [ "*" ], "inputs": [ { - "name": "steps", - "type": "StepsToPerform" + "name": "farm_address", + "type": "Address" }, { "name": "add_liq_first_token_min_amount_out", @@ -125,15 +152,28 @@ ] }, { - "name": "createPosFromLp", + "name": "createMetastakingPosFromSingleToken", "mutability": "mutable", "payableInTokens": [ "*" ], "inputs": [ { - "name": "steps", - "type": "StepsToPerform" + "name": "metastaking_address", + "type": "Address" + }, + { + "name": "add_liq_first_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "add_liq_second_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "swap_operations", + "type": "variadic>", + "multi_arg": true } ], "outputs": [ @@ -143,18 +183,22 @@ ] }, { - "name": "fullExitPos", + "name": "createMetastakingPosFromTwoTokens", "mutability": "mutable", "payableInTokens": [ "*" ], "inputs": [ { - "name": "first_token_min_amount_out", + "name": "metastaking_address", + "type": "Address" + }, + { + "name": "add_liq_first_token_min_amount_out", "type": "BigUint" }, { - "name": "second_token_min_amont_out", + "name": "add_liq_second_token_min_amount_out", "type": "BigUint" } ], @@ -163,89 +207,131 @@ "type": "List" } ] - } - ], - "events": [], - "hasCallback": false, - "types": { - "EsdtTokenPayment": { - "type": "struct", - "fields": [ + }, + { + "name": "createFarmStakingPosFromSingleToken", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ { - "name": "token_identifier", - "type": "TokenIdentifier" + "name": "farm_staking_address", + "type": "Address" }, { - "name": "token_nonce", - "type": "u64" + "name": "min_amount_out", + "type": "BigUint" }, { - "name": "amount", - "type": "BigUint" + "name": "swap_operations", + "type": "variadic>", + "multi_arg": true + } + ], + "outputs": [ + { + "type": "List" } ] }, - "FarmConfig": { - "type": "struct", - "fields": [ + { + "name": "exitMetastakingPos", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ { - "name": "state", - "type": "State" + "name": "metastaking_address", + "type": "Address" }, { - "name": "farm_token_id", - "type": "TokenIdentifier" + "name": "first_token_min_amount_out", + "type": "BigUint" }, { - "name": "farming_token_id", - "type": "TokenIdentifier" + "name": "second_token_min_amont_out", + "type": "BigUint" + } + ], + "outputs": [ + { + "type": "List" } ] }, - "MetastakingConfig": { - "type": "struct", - "fields": [ + { + "name": "exitFarmPos", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ { - "name": "dual_yield_token_id", - "type": "TokenIdentifier" + "name": "farm_address", + "type": "Address" }, { - "name": "lp_farm_token_id", - "type": "TokenIdentifier" + "name": "first_token_min_amount_out", + "type": "BigUint" + }, + { + "name": "second_token_min_amont_out", + "type": "BigUint" + } + ], + "outputs": [ + { + "type": "List" } ] }, - "State": { - "type": "enum", - "variants": [ + { + "name": "exitLpPos", + "mutability": "mutable", + "payableInTokens": [ + "*" + ], + "inputs": [ { - "name": "Inactive", - "discriminant": 0 + "name": "pair_address", + "type": "Address" }, { - "name": "Active", - "discriminant": 1 + "name": "first_token_min_amount_out", + "type": "BigUint" }, { - "name": "PartialActive", - "discriminant": 2 + "name": "second_token_min_amont_out", + "type": "BigUint" + } + ], + "outputs": [ + { + "type": "List" } ] - }, - "StepsToPerform": { - "type": "enum", - "variants": [ + } + ], + "events": [], + "esdtAttributes": [], + "hasCallback": false, + "types": { + "EsdtTokenPayment": { + "type": "struct", + "fields": [ { - "name": "AddLiquidity", - "discriminant": 0 + "name": "token_identifier", + "type": "TokenIdentifier" }, { - "name": "EnterFarm", - "discriminant": 1 + "name": "token_nonce", + "type": "u64" }, { - "name": "EnterMetastaking", - "discriminant": 2 + "name": "amount", + "type": "BigUint" } ] } diff --git a/src/config/default.json b/src/config/default.json index 91dbd8220..2bef8e077 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -492,6 +492,9 @@ "governance": { "vote": 50000000 }, + "positionCreator": { + "singleToken": 50000000 + }, "lockedAssetCreate": 5000000, "wrapeGLD": 4200000, "claimLockedAssets": 45500000 @@ -532,7 +535,8 @@ "governance": { "energy": "./src/abis/governance-v2.abi.json", "tokenSnapshot": "./src/abis/governance-v2-merkle-proof.abi.json" - } + }, + "positionCreator": "./src/abis/auto-pos-creator.abi.json" }, "cron": { "transactionCollectorMaxHyperblocks": 10, diff --git a/src/modules/auto-router/auto-router.module.ts b/src/modules/auto-router/auto-router.module.ts index 7075dc840..8918f96e1 100644 --- a/src/modules/auto-router/auto-router.module.ts +++ b/src/modules/auto-router/auto-router.module.ts @@ -33,6 +33,6 @@ import { TokenModule } from '../tokens/token.module'; AutoRouterTransactionService, PairTransactionService, ], - exports: [], + exports: [AutoRouterService], }) export class AutoRouterModule {} diff --git a/src/modules/position-creator/models/position.creator.model.ts b/src/modules/position-creator/models/position.creator.model.ts index e605aeeb8..bb01757e1 100644 --- a/src/modules/position-creator/models/position.creator.model.ts +++ b/src/modules/position-creator/models/position.creator.model.ts @@ -1,21 +1,4 @@ -import { EnumType, EnumVariantDefinition } from '@multiversx/sdk-core/out'; -import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; - -export enum StepsToPerform { - ADD_LIQUIDITY, - ENTER_FARM, - ENTER_METASTAKING, -} - -registerEnumType(StepsToPerform, { - name: 'StepsToPerform', -}); - -export const StepsToPerformEnumType = new EnumType('StepsToPerform', [ - new EnumVariantDefinition('AddLiquidity', 0), - new EnumVariantDefinition('EnterFarm', 1), - new EnumVariantDefinition('EnterMetastaking', 2), -]); +import { Field, ObjectType } from '@nestjs/graphql'; @ObjectType() export class PositionCreatorModel { diff --git a/src/modules/position-creator/position.creator.module.ts b/src/modules/position-creator/position.creator.module.ts index 91a26523f..658e92c56 100644 --- a/src/modules/position-creator/position.creator.module.ts +++ b/src/modules/position-creator/position.creator.module.ts @@ -5,9 +5,15 @@ import { RouterModule } from '../router/router.module'; import { PositionCreatorComputeService } from './services/position.creator.compute'; import { PositionCreatorTransactionService } from './services/position.creator.transaction'; import { MXCommunicationModule } from 'src/services/multiversx-communication/mx.communication.module'; +import { AutoRouterModule } from '../auto-router/auto-router.module'; @Module({ - imports: [PairModule, RouterModule, MXCommunicationModule], + imports: [ + PairModule, + RouterModule, + AutoRouterModule, + MXCommunicationModule, + ], providers: [ PositionCreatorComputeService, PositionCreatorTransactionService, diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts new file mode 100644 index 000000000..6f8aa6f76 --- /dev/null +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -0,0 +1,38 @@ +import { Args, Query, Resolver } from '@nestjs/graphql'; +import { PositionCreatorModel } from './models/position.creator.model'; +import { scAddress } from 'src/config'; +import { TransactionModel } from 'src/models/transaction.model'; +import { EsdtTokenPayment } from '@multiversx/sdk-exchange'; +import { PositionCreatorTransactionService } from './services/position.creator.transaction'; +import { InputTokenModel } from 'src/models/inputToken.model'; + +@Resolver(PositionCreatorModel) +export class PositionCreatorResolver { + constructor( + private readonly posCreatorTransaction: PositionCreatorTransactionService, + ) {} + + @Query(() => PositionCreatorModel) + async getPositionCreator(): Promise { + return new PositionCreatorModel({ + address: scAddress.positionCreator, + }); + } + + @Query(() => TransactionModel) + async createPositionSingleToken( + @Args('pairAddress') pairAddress: string, + @Args('payment') payment: InputTokenModel, + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createLiquidityPositionSingleToken( + pairAddress, + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + tolerance, + ); + } +} diff --git a/src/modules/position-creator/services/position.creator.compute.ts b/src/modules/position-creator/services/position.creator.compute.ts new file mode 100644 index 000000000..90e3a8c7d --- /dev/null +++ b/src/modules/position-creator/services/position.creator.compute.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import BigNumber from 'bignumber.js'; +import { PairService } from 'src/modules/pair/services/pair.service'; +import { RouterService } from 'src/modules/router/services/router.service'; + +@Injectable() +export class PositionCreatorComputeService { + constructor( + private readonly pairService: PairService, + private readonly routerService: RouterService, + ) {} + + async computeSwap( + fromTokenID: string, + toTokenID: string, + amount: string, + ): Promise { + if (fromTokenID === toTokenID) { + return new BigNumber(amount); + } + + const pairs = await this.routerService.getAllPairs(0, 1, { + address: null, + issuedLpToken: true, + firstTokenID: fromTokenID, + secondTokenID: toTokenID, + state: 'Active', + }); + + if (pairs.length === 0) { + throw new Error('Pair not found'); + } + + const amountOut = await this.pairService.getAmountOut( + pairs[0].address, + fromTokenID, + amount, + ); + + return new BigNumber(amountOut); + } +} diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts new file mode 100644 index 000000000..3ca0aef08 --- /dev/null +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -0,0 +1,114 @@ +import { + Address, + AddressValue, + BigUIntValue, + BytesValue, + TokenIdentifierValue, + TokenTransfer, +} from '@multiversx/sdk-core/out'; +import { EsdtTokenPayment } from '@multiversx/sdk-exchange'; +import { Injectable } from '@nestjs/common'; +import BigNumber from 'bignumber.js'; +import { gasConfig, mxConfig } from 'src/config'; +import { TransactionModel } from 'src/models/transaction.model'; +import { PairAbiService } from 'src/modules/pair/services/pair.abi.service'; +import { MXProxyService } from 'src/services/multiversx-communication/mx.proxy.service'; +import { PositionCreatorComputeService } from './position.creator.compute'; +import { AutoRouterService } from 'src/modules/auto-router/services/auto-router.service'; +import { RouterAbiService } from 'src/modules/router/services/router.abi.service'; + +@Injectable() +export class PositionCreatorTransactionService { + constructor( + private readonly autoRouterService: AutoRouterService, + private readonly routerAbi: RouterAbiService, + private readonly posCreatorCompute: PositionCreatorComputeService, + private readonly pairAbi: PairAbiService, + private readonly mxProxy: MXProxyService, + ) {} + + async createLiquidityPositionSingleToken( + pairAddress: string, + payment: EsdtTokenPayment, + tolerance: number, + ): Promise { + const acceptedPairedTokensIDs = + await this.routerAbi.commonTokensForUserPairs(); + + const [firstTokenID, secondTokenID] = await Promise.all([ + this.pairAbi.firstTokenID(pairAddress), + this.pairAbi.secondTokenID(pairAddress), + ]); + + const swapToTokenID = acceptedPairedTokensIDs.includes(firstTokenID) + ? firstTokenID + : secondTokenID; + + const swapRoute = await this.autoRouterService.swap({ + tokenInID: payment.tokenIdentifier, + amountIn: payment.amount, + tokenOutID: swapToTokenID, + tolerance, + }); + + const halfPayment = new BigNumber(swapRoute.amountOut) + .dividedBy(2) + .integerValue() + .toFixed(); + + const remainingPayment = new BigNumber(swapRoute.amountOut) + .minus(halfPayment) + .toFixed(); + + const [amount0, amount1] = await Promise.all([ + await this.posCreatorCompute.computeSwap( + swapRoute.tokenOutID, + firstTokenID, + halfPayment, + ), + await this.posCreatorCompute.computeSwap( + swapRoute.tokenOutID, + secondTokenID, + remainingPayment, + ), + ]); + + const amount0Min = new BigNumber(amount0) + .multipliedBy(1 - tolerance) + .integerValue(); + const amount1Min = new BigNumber(amount1) + .multipliedBy(1 - tolerance) + .integerValue(); + + const contract = await this.mxProxy.getPostitionCreatorContract(); + + return contract.methodsExplicit + .createLpPosFromSingleToken([ + new AddressValue(Address.fromBech32(pairAddress)), + new BigUIntValue(amount0Min), + new BigUIntValue(amount1Min), + new AddressValue( + Address.fromBech32(swapRoute.pairs[0].address), + ), + new BytesValue(Buffer.from('swapTokensFixedInput')), + new TokenIdentifierValue(swapRoute.tokenOutID), + new BigUIntValue( + new BigNumber( + swapRoute.intermediaryAmounts[ + swapRoute.intermediaryAmounts.length - 1 + ], + ), + ), + ]) + .withSingleESDTTransfer( + TokenTransfer.fungibleFromBigInteger( + payment.tokenIdentifier, + new BigNumber(payment.amount), + ), + ) + .withGasLimit(gasConfig.positionCreator.singleToken) + .withChainID(mxConfig.chainID) + .buildTransaction() + .toPlainObject(); + } +} From 9e7277d864db3608293f4ac41edfc15233cfacf4 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Tue, 21 Nov 2023 21:23:56 +0200 Subject: [PATCH 03/15] MEX-410: refactor liquidity position creator transaction Signed-off-by: Claudiu Lataretu --- src/modules/auto-router/auto-router.module.ts | 2 +- .../auto-router.transactions.service.ts | 17 ++-- .../position.creator.module.ts | 8 ++ .../services/position.creator.compute.ts | 88 +++++++++++++++++++ .../services/position.creator.transaction.ts | 55 ++++++++++-- 5 files changed, 151 insertions(+), 19 deletions(-) diff --git a/src/modules/auto-router/auto-router.module.ts b/src/modules/auto-router/auto-router.module.ts index 8918f96e1..15b8ad6d9 100644 --- a/src/modules/auto-router/auto-router.module.ts +++ b/src/modules/auto-router/auto-router.module.ts @@ -33,6 +33,6 @@ import { TokenModule } from '../tokens/token.module'; AutoRouterTransactionService, PairTransactionService, ], - exports: [AutoRouterService], + exports: [AutoRouterService, AutoRouterTransactionService], }) export class AutoRouterModule {} diff --git a/src/modules/auto-router/services/auto-router.transactions.service.ts b/src/modules/auto-router/services/auto-router.transactions.service.ts index d43caab38..43b0f8791 100644 --- a/src/modules/auto-router/services/auto-router.transactions.service.ts +++ b/src/modules/auto-router/services/auto-router.transactions.service.ts @@ -4,6 +4,7 @@ import { BigUIntValue, BytesValue, TokenTransfer, + TypedValue, } from '@multiversx/sdk-core'; import { Injectable } from '@nestjs/common'; import BigNumber from 'bignumber.js'; @@ -55,8 +56,8 @@ export class AutoRouterTransactionService { const transactionArgs = args.swapType == SWAP_TYPE.fixedInput - ? await this.multiPairFixedInputSwaps(args) - : await this.multiPairFixedOutputSwaps(args); + ? this.multiPairFixedInputSwaps(args) + : this.multiPairFixedOutputSwaps(args); transactions.push( contract.methodsExplicit @@ -78,10 +79,8 @@ export class AutoRouterTransactionService { return transactions; } - private async multiPairFixedInputSwaps( - args: MultiSwapTokensArgs, - ): Promise { - const swaps = []; + multiPairFixedInputSwaps(args: MultiSwapTokensArgs): TypedValue[] { + const swaps: TypedValue[] = []; const intermediaryTolerance = args.tolerance / args.addressRoute.length; @@ -113,10 +112,8 @@ export class AutoRouterTransactionService { return swaps; } - private async multiPairFixedOutputSwaps( - args: MultiSwapTokensArgs, - ): Promise { - const swaps = []; + multiPairFixedOutputSwaps(args: MultiSwapTokensArgs): TypedValue[] { + const swaps: TypedValue[] = []; const intermediaryTolerance = args.tolerance / args.addressRoute.length; diff --git a/src/modules/position-creator/position.creator.module.ts b/src/modules/position-creator/position.creator.module.ts index 658e92c56..7f12240d6 100644 --- a/src/modules/position-creator/position.creator.module.ts +++ b/src/modules/position-creator/position.creator.module.ts @@ -6,12 +6,20 @@ import { PositionCreatorComputeService } from './services/position.creator.compu import { PositionCreatorTransactionService } from './services/position.creator.transaction'; import { MXCommunicationModule } from 'src/services/multiversx-communication/mx.communication.module'; import { AutoRouterModule } from '../auto-router/auto-router.module'; +import { FarmModuleV2 } from '../farm/v2/farm.v2.module'; +import { StakingProxyModule } from '../staking-proxy/staking.proxy.module'; +import { StakingModule } from '../staking/staking.module'; +import { TokenModule } from '../tokens/token.module'; @Module({ imports: [ PairModule, RouterModule, AutoRouterModule, + FarmModuleV2, + StakingModule, + StakingProxyModule, + TokenModule, MXCommunicationModule, ], providers: [ diff --git a/src/modules/position-creator/services/position.creator.compute.ts b/src/modules/position-creator/services/position.creator.compute.ts index 90e3a8c7d..b02b37eaf 100644 --- a/src/modules/position-creator/services/position.creator.compute.ts +++ b/src/modules/position-creator/services/position.creator.compute.ts @@ -1,13 +1,30 @@ +import { TypedValue } from '@multiversx/sdk-core/out'; +import { EsdtTokenPayment } from '@multiversx/sdk-exchange'; import { Injectable } from '@nestjs/common'; import BigNumber from 'bignumber.js'; +import { SWAP_TYPE } from 'src/modules/auto-router/models/auto-route.model'; +import { AutoRouterService } from 'src/modules/auto-router/services/auto-router.service'; +import { AutoRouterTransactionService } from 'src/modules/auto-router/services/auto-router.transactions.service'; +import { PairAbiService } from 'src/modules/pair/services/pair.abi.service'; import { PairService } from 'src/modules/pair/services/pair.service'; +import { RouterAbiService } from 'src/modules/router/services/router.abi.service'; import { RouterService } from 'src/modules/router/services/router.service'; +export type PositionCreatorSingleTokenPairInput = { + swapRouteArgs: TypedValue[]; + amount0Min: BigNumber; + amount1Min: BigNumber; +}; + @Injectable() export class PositionCreatorComputeService { constructor( + private readonly pairAbi: PairAbiService, private readonly pairService: PairService, + private readonly routerAbi: RouterAbiService, private readonly routerService: RouterService, + private readonly autoRouterService: AutoRouterService, + private readonly autoRouterTransaction: AutoRouterTransactionService, ) {} async computeSwap( @@ -39,4 +56,75 @@ export class PositionCreatorComputeService { return new BigNumber(amountOut); } + + async computeSingleTokenPairInput( + pairAddress: string, + payment: EsdtTokenPayment, + tolerance: number, + ): Promise { + const acceptedPairedTokensIDs = + await this.routerAbi.commonTokensForUserPairs(); + + const [firstTokenID, secondTokenID] = await Promise.all([ + this.pairAbi.firstTokenID(pairAddress), + this.pairAbi.secondTokenID(pairAddress), + ]); + + const swapToTokenID = acceptedPairedTokensIDs.includes(firstTokenID) + ? firstTokenID + : secondTokenID; + + const swapRoute = await this.autoRouterService.swap({ + tokenInID: payment.tokenIdentifier, + amountIn: payment.amount, + tokenOutID: swapToTokenID, + tolerance, + }); + + const halfPayment = new BigNumber(swapRoute.amountOut) + .dividedBy(2) + .integerValue() + .toFixed(); + + const remainingPayment = new BigNumber(swapRoute.amountOut) + .minus(halfPayment) + .toFixed(); + + const [amount0, amount1] = await Promise.all([ + await this.computeSwap( + swapRoute.tokenOutID, + firstTokenID, + halfPayment, + ), + await this.computeSwap( + swapRoute.tokenOutID, + secondTokenID, + remainingPayment, + ), + ]); + + const amount0Min = new BigNumber(amount0) + .multipliedBy(1 - tolerance) + .integerValue(); + const amount1Min = new BigNumber(amount1) + .multipliedBy(1 - tolerance) + .integerValue(); + + const swapRouteArgs = + this.autoRouterTransaction.multiPairFixedInputSwaps({ + tokenInID: swapRoute.tokenInID, + tokenOutID: swapRoute.tokenOutID, + swapType: SWAP_TYPE.fixedInput, + tolerance, + addressRoute: swapRoute.pairs.map((pair) => pair.address), + intermediaryAmounts: swapRoute.intermediaryAmounts, + tokenRoute: swapRoute.tokenRoute, + }); + + return { + swapRouteArgs, + amount0Min, + amount1Min, + }; + } } diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index 3ca0aef08..05976b182 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -15,15 +15,26 @@ import { PairAbiService } from 'src/modules/pair/services/pair.abi.service'; import { MXProxyService } from 'src/services/multiversx-communication/mx.proxy.service'; import { PositionCreatorComputeService } from './position.creator.compute'; import { AutoRouterService } from 'src/modules/auto-router/services/auto-router.service'; -import { RouterAbiService } from 'src/modules/router/services/router.abi.service'; +import { FarmAbiServiceV2 } from 'src/modules/farm/v2/services/farm.v2.abi.service'; +import { StakingProxyAbiService } from 'src/modules/staking-proxy/services/staking.proxy.abi.service'; +import { StakingAbiService } from 'src/modules/staking/services/staking.abi.service'; +import { AutoRouterTransactionService } from 'src/modules/auto-router/services/auto-router.transactions.service'; +import { SWAP_TYPE } from 'src/modules/auto-router/models/auto-route.model'; +import { PairService } from 'src/modules/pair/services/pair.service'; +import { TokenService } from 'src/modules/tokens/services/token.service'; @Injectable() export class PositionCreatorTransactionService { constructor( private readonly autoRouterService: AutoRouterService, - private readonly routerAbi: RouterAbiService, + private readonly autoRouterTransaction: AutoRouterTransactionService, private readonly posCreatorCompute: PositionCreatorComputeService, private readonly pairAbi: PairAbiService, + private readonly pairService: PairService, + private readonly farmAbiV2: FarmAbiServiceV2, + private readonly stakingAbi: StakingAbiService, + private readonly stakingProxyAbi: StakingProxyAbiService, + private readonly tokenService: TokenService, private readonly mxProxy: MXProxyService, ) {} @@ -32,13 +43,41 @@ export class PositionCreatorTransactionService { payment: EsdtTokenPayment, tolerance: number, ): Promise { - const acceptedPairedTokensIDs = - await this.routerAbi.commonTokensForUserPairs(); + const uniqueTokensIDs = await this.tokenService.getUniqueTokenIDs( + false, + ); - const [firstTokenID, secondTokenID] = await Promise.all([ - this.pairAbi.firstTokenID(pairAddress), - this.pairAbi.secondTokenID(pairAddress), - ]); + if (!uniqueTokensIDs.includes(payment.tokenIdentifier)) { + throw new Error('Invalid token'); + } + + const singleTokenPairInput = + await this.posCreatorCompute.computeSingleTokenPairInput( + pairAddress, + payment, + tolerance, + ); + + const contract = await this.mxProxy.getPostitionCreatorContract(); + + return contract.methodsExplicit + .createLpPosFromSingleToken([ + new AddressValue(Address.fromBech32(pairAddress)), + new BigUIntValue(singleTokenPairInput.amount0Min), + new BigUIntValue(singleTokenPairInput.amount1Min), + ...singleTokenPairInput.swapRouteArgs, + ]) + .withSingleESDTTransfer( + TokenTransfer.fungibleFromBigInteger( + payment.tokenIdentifier, + new BigNumber(payment.amount), + ), + ) + .withGasLimit(gasConfig.positionCreator.singleToken) + .withChainID(mxConfig.chainID) + .buildTransaction() + .toPlainObject(); + } const swapToTokenID = acceptedPairedTokensIDs.includes(firstTokenID) ? firstTokenID From 820f387edf866be44b19c4b3f2933f21b8aab4ae Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Tue, 21 Nov 2023 21:25:20 +0200 Subject: [PATCH 04/15] MEX-410: add query for farm position single token transaction Signed-off-by: Claudiu Lataretu --- .../position.creator.resolver.ts | 22 ++++++++ .../services/position.creator.transaction.ts | 55 ++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts index 6f8aa6f76..53471efac 100644 --- a/src/modules/position-creator/position.creator.resolver.ts +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -35,4 +35,26 @@ export class PositionCreatorResolver { tolerance, ); } + + @Query(() => TransactionModel) + async createFarmPositionSingleToken( + @Args('farmAddress') farmAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createFarmPositionSingleToken( + farmAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + } diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index 05976b182..773af057b 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -79,9 +79,58 @@ export class PositionCreatorTransactionService { .toPlainObject(); } - const swapToTokenID = acceptedPairedTokensIDs.includes(firstTokenID) - ? firstTokenID - : secondTokenID; + async createFarmPositionSingleToken( + farmAddress: string, + payments: EsdtTokenPayment[], + tolerance: number, + ): Promise { + const [pairAddress, farmTokenID, uniqueTokensIDs] = await Promise.all([ + this.farmAbiV2.pairContractAddress(farmAddress), + this.farmAbiV2.farmTokenID(farmAddress), + this.tokenService.getUniqueTokenIDs(false), + ]); + + if (!uniqueTokensIDs.includes(payments[0].tokenIdentifier)) { + throw new Error('Invalid ESDT token payment'); + } + + for (const payment of payments.slice(1)) { + if (payment.tokenIdentifier !== farmTokenID) { + throw new Error('Invalid farm token payment'); + } + } + + const singleTokenPairInput = + await this.posCreatorCompute.computeSingleTokenPairInput( + pairAddress, + payments[0], + tolerance, + ); + + const contract = await this.mxProxy.getPostitionCreatorContract(); + + return contract.methodsExplicit + .createFarmPosFromSingleToken([ + new AddressValue(Address.fromBech32(farmAddress)), + new BigUIntValue(singleTokenPairInput.amount0Min), + new BigUIntValue(singleTokenPairInput.amount1Min), + ...singleTokenPairInput.swapRouteArgs, + ]) + .withMultiESDTNFTTransfer( + payments.map((payment) => + TokenTransfer.metaEsdtFromBigInteger( + payment.tokenIdentifier, + payment.tokenNonce, + new BigNumber(payment.amount), + ), + ), + ) + .withGasLimit(gasConfig.positionCreator.singleToken) + .withChainID(mxConfig.chainID) + .buildTransaction() + .toPlainObject(); + } + const swapRoute = await this.autoRouterService.swap({ tokenInID: payment.tokenIdentifier, From 4547569e0d14cb662809d172a49057d7b563d562 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Tue, 21 Nov 2023 21:26:55 +0200 Subject: [PATCH 05/15] MEX-410: add query for dual farm single token position transaction Signed-off-by: Claudiu Lataretu --- .../position.creator.resolver.ts | 21 ++++++++ .../services/position.creator.transaction.ts | 53 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts index 53471efac..a70a7525b 100644 --- a/src/modules/position-creator/position.creator.resolver.ts +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -57,4 +57,25 @@ export class PositionCreatorResolver { ); } + @Query(() => TransactionModel) + async createDualFarmPositionSingleToken( + @Args('dualFarmAddress') dualFarmAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createDualFarmPositionSingleToken( + dualFarmAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + } diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index 773af057b..17a344cd0 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -131,6 +131,59 @@ export class PositionCreatorTransactionService { .toPlainObject(); } + async createDualFarmPositionSingleToken( + stakingProxyAddress: string, + payments: EsdtTokenPayment[], + tolerance: number, + ): Promise { + const [pairAddress, dualYieldTokenID, uniqueTokensIDs] = + await Promise.all([ + this.stakingProxyAbi.pairAddress(stakingProxyAddress), + this.stakingProxyAbi.dualYieldTokenID(stakingProxyAddress), + this.tokenService.getUniqueTokenIDs(false), + ]); + + if (!uniqueTokensIDs.includes(payments[0].tokenIdentifier)) { + throw new Error('Invalid ESDT token payment'); + } + + for (const payment of payments.slice(1)) { + if (payment.tokenIdentifier !== dualYieldTokenID) { + throw new Error('Invalid dual yield token payment'); + } + } + + const singleTokenPairInput = + await this.posCreatorCompute.computeSingleTokenPairInput( + pairAddress, + payments[0], + tolerance, + ); + + const contract = await this.mxProxy.getPostitionCreatorContract(); + + return contract.methodsExplicit + .createMetastakingPosFromSingleToken([ + new AddressValue(Address.fromBech32(stakingProxyAddress)), + new BigUIntValue(singleTokenPairInput.amount0Min), + new BigUIntValue(singleTokenPairInput.amount1Min), + ...singleTokenPairInput.swapRouteArgs, + ]) + .withMultiESDTNFTTransfer( + payments.map((payment) => + TokenTransfer.metaEsdtFromBigInteger( + payment.tokenIdentifier, + payment.tokenNonce, + new BigNumber(payment.amount), + ), + ), + ) + .withGasLimit(gasConfig.positionCreator.singleToken) + .withChainID(mxConfig.chainID) + .buildTransaction() + .toPlainObject(); + } + const swapRoute = await this.autoRouterService.swap({ tokenInID: payment.tokenIdentifier, From 73f4f2cb1e0d6a5c693d3234ac7f0fa35d4b184f Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Tue, 21 Nov 2023 21:28:15 +0200 Subject: [PATCH 06/15] MEX-410: add query for staking any single token transaction Signed-off-by: Claudiu Lataretu --- .../position.creator.resolver.ts | 17 +++++ .../services/position.creator.transaction.ts | 68 ++++++++----------- 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts index a70a7525b..a5fb739f3 100644 --- a/src/modules/position-creator/position.creator.resolver.ts +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -78,4 +78,21 @@ export class PositionCreatorResolver { ); } + @Query(() => TransactionModel) + async createStakingPositionSingleToken( + @Args('stakingAddress') stakingAddress: string, + @Args('payment') payment: InputTokenModel, + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createStakingPositionSingleToken( + stakingAddress, + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + tolerance, + ); + } + } diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index 17a344cd0..347c9ee7b 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -184,55 +184,43 @@ export class PositionCreatorTransactionService { .toPlainObject(); } + async createStakingPositionSingleToken( + stakingAddress: string, + payment: EsdtTokenPayment, + tolerance: number, + ): Promise { + const [farmingTokenID, uniqueTokensIDs] = await Promise.all([ + this.stakingAbi.farmingTokenID(stakingAddress), + this.tokenService.getUniqueTokenIDs(false), + ]); + + if (!uniqueTokensIDs.includes(payment.tokenIdentifier)) { + throw new Error('Invalid ESDT token payment'); + } const swapRoute = await this.autoRouterService.swap({ tokenInID: payment.tokenIdentifier, amountIn: payment.amount, - tokenOutID: swapToTokenID, + tokenOutID: farmingTokenID, tolerance, }); - const halfPayment = new BigNumber(swapRoute.amountOut) - .dividedBy(2) - .integerValue() - .toFixed(); - - const remainingPayment = new BigNumber(swapRoute.amountOut) - .minus(halfPayment) - .toFixed(); - - const [amount0, amount1] = await Promise.all([ - await this.posCreatorCompute.computeSwap( - swapRoute.tokenOutID, - firstTokenID, - halfPayment, - ), - await this.posCreatorCompute.computeSwap( - swapRoute.tokenOutID, - secondTokenID, - remainingPayment, - ), - ]); - - const amount0Min = new BigNumber(amount0) - .multipliedBy(1 - tolerance) - .integerValue(); - const amount1Min = new BigNumber(amount1) - .multipliedBy(1 - tolerance) - .integerValue(); - const contract = await this.mxProxy.getPostitionCreatorContract(); + const multiSwapArgs = + this.autoRouterTransaction.multiPairFixedInputSwaps({ + tokenInID: swapRoute.tokenInID, + tokenOutID: swapRoute.tokenOutID, + swapType: SWAP_TYPE.fixedInput, + tolerance, + addressRoute: swapRoute.pairs.map((pair) => pair.address), + intermediaryAmounts: swapRoute.intermediaryAmounts, + tokenRoute: swapRoute.tokenRoute, + }); + return contract.methodsExplicit - .createLpPosFromSingleToken([ - new AddressValue(Address.fromBech32(pairAddress)), - new BigUIntValue(amount0Min), - new BigUIntValue(amount1Min), - new AddressValue( - Address.fromBech32(swapRoute.pairs[0].address), - ), - new BytesValue(Buffer.from('swapTokensFixedInput')), - new TokenIdentifierValue(swapRoute.tokenOutID), + .createFarmStakingPosFromSingleToken([ + new AddressValue(Address.fromBech32(stakingAddress)), new BigUIntValue( new BigNumber( swapRoute.intermediaryAmounts[ @@ -240,6 +228,7 @@ export class PositionCreatorTransactionService { ], ), ), + ...multiSwapArgs, ]) .withSingleESDTTransfer( TokenTransfer.fungibleFromBigInteger( @@ -252,4 +241,5 @@ export class PositionCreatorTransactionService { .buildTransaction() .toPlainObject(); } + } From cc21bc144b73c7b2f4cf0fb374094a38a5a594cf Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Tue, 21 Nov 2023 21:29:18 +0200 Subject: [PATCH 07/15] MEX-410: add query for farm position dual tokens transaction Signed-off-by: Claudiu Lataretu --- .../position.creator.resolver.ts | 21 ++++++ .../services/position.creator.transaction.ts | 66 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts index a5fb739f3..4dd2495c4 100644 --- a/src/modules/position-creator/position.creator.resolver.ts +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -95,4 +95,25 @@ export class PositionCreatorResolver { ); } + @Query(() => TransactionModel) + async createFarmPositionDualTokens( + @Args('farmAddress') farmAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createFarmPositionDualTokens( + farmAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + } diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index 347c9ee7b..3e18f5055 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -242,4 +242,70 @@ export class PositionCreatorTransactionService { .toPlainObject(); } + async createFarmPositionDualTokens( + farmAddress: string, + payments: EsdtTokenPayment[], + tolerance: number, + ): Promise { + const pairAddress = await this.farmAbiV2.pairContractAddress( + farmAddress, + ); + const [firstTokenID, secondTokenID] = await Promise.all([ + this.pairAbi.firstTokenID(pairAddress), + this.pairAbi.secondTokenID(pairAddress), + ]); + + if (!this.checkTokensPayments(payments, firstTokenID, secondTokenID)) { + throw new Error('Invalid tokens payments'); + } + + const [firstPayment, secondPayment] = + payments[0].tokenIdentifier === firstTokenID + ? [payments[0], payments[1]] + : [payments[1], payments[0]]; + + const amount0Min = new BigNumber(firstPayment.amount) + .multipliedBy(1 - tolerance) + .integerValue(); + const amount1Min = new BigNumber(secondPayment.amount) + .multipliedBy(1 - tolerance) + .integerValue(); + + const contract = await this.mxProxy.getPostitionCreatorContract(); + + return contract.methodsExplicit + .createFarmPosFromTwoTokens([ + new AddressValue(Address.fromBech32(farmAddress)), + new BigUIntValue(amount0Min), + new BigUIntValue(amount1Min), + ]) + .withMultiESDTNFTTransfer([ + TokenTransfer.fungibleFromBigInteger( + firstPayment.tokenIdentifier, + new BigNumber(firstPayment.amount), + ), + TokenTransfer.fungibleFromBigInteger( + secondPayment.tokenIdentifier, + + new BigNumber(secondPayment.amount), + ), + ]) + .withGasLimit(gasConfig.positionCreator.singleToken) + .withChainID(mxConfig.chainID) + .buildTransaction() + .toPlainObject(); + } + + private checkTokensPayments( + payments: EsdtTokenPayment[], + firstTokenID: string, + secondTokenID: string, + ): boolean { + return ( + (payments[0].tokenIdentifier === firstTokenID && + payments[1].tokenIdentifier === secondTokenID) || + (payments[1].tokenIdentifier === firstTokenID && + payments[0].tokenIdentifier === secondTokenID) + ); + } } From f652e10c71117f877dca8c241366f3e49207523b Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Tue, 21 Nov 2023 21:33:07 +0200 Subject: [PATCH 08/15] MEX-410: add query for dual farm position transaction with dual tokens Signed-off-by: Claudiu Lataretu --- .../position.creator.resolver.ts | 21 +++++++ .../services/position.creator.transaction.ts | 55 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts index 4dd2495c4..0c05edbdf 100644 --- a/src/modules/position-creator/position.creator.resolver.ts +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -116,4 +116,25 @@ export class PositionCreatorResolver { ); } + @Query(() => TransactionModel) + async createDualFarmPositionDualTokens( + @Args('dualFarmAddress') dualFarmAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createDualFarmPositionDualTokens( + dualFarmAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + } diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index 3e18f5055..75a2d54ff 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -296,6 +296,61 @@ export class PositionCreatorTransactionService { .toPlainObject(); } + async createDualFarmPositionDualTokens( + stakingProxyAddress: string, + payments: EsdtTokenPayment[], + tolerance: number, + ): Promise { + const pairAddress = await this.stakingProxyAbi.pairAddress( + stakingProxyAddress, + ); + const [firstTokenID, secondTokenID] = await Promise.all([ + this.pairAbi.firstTokenID(pairAddress), + this.pairAbi.secondTokenID(pairAddress), + ]); + + if (!this.checkTokensPayments(payments, firstTokenID, secondTokenID)) { + throw new Error('Invalid tokens payments'); + } + + const [firstPayment, secondPayment] = + payments[0].tokenIdentifier === firstTokenID + ? [payments[0], payments[1]] + : [payments[1], payments[0]]; + + const amount0Min = new BigNumber(firstPayment.amount) + .multipliedBy(1 - tolerance) + .integerValue(); + const amount1Min = new BigNumber(secondPayment.amount) + .multipliedBy(1 - tolerance) + .integerValue(); + + const contract = await this.mxProxy.getPostitionCreatorContract(); + + return contract.methodsExplicit + .createMetastakingPosFromTwoTokens([ + new AddressValue(Address.fromBech32(stakingProxyAddress)), + new BigUIntValue(amount0Min), + new BigUIntValue(amount1Min), + ]) + .withMultiESDTNFTTransfer([ + TokenTransfer.fungibleFromBigInteger( + firstPayment.tokenIdentifier, + new BigNumber(firstPayment.amount), + ), + TokenTransfer.fungibleFromBigInteger( + secondPayment.tokenIdentifier, + + new BigNumber(secondPayment.amount), + ), + ]) + .withGasLimit(gasConfig.positionCreator.singleToken) + .withChainID(mxConfig.chainID) + .buildTransaction() + .toPlainObject(); + } + + private checkTokensPayments( payments: EsdtTokenPayment[], firstTokenID: string, From 5c4f862b5534f2bc9a466c100c1f4c071eead52c Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Tue, 21 Nov 2023 21:34:07 +0200 Subject: [PATCH 09/15] MEX-410: add query for exit farm into dual tokens transaction Signed-off-by: Claudiu Lataretu --- .../position.creator.resolver.ts | 16 ++++++++ .../services/position.creator.transaction.ts | 41 ++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts index 0c05edbdf..015353760 100644 --- a/src/modules/position-creator/position.creator.resolver.ts +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -137,4 +137,20 @@ export class PositionCreatorResolver { ); } + @Query(() => TransactionModel) + async exitFarmPositionDualTokens( + @Args('farmAddress') farmAddress: string, + @Args('payment') payment: InputTokenModel, + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.exitFarmPositionDualTokens( + farmAddress, + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + tolerance, + ); + } } diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index 75a2d54ff..39ad7ba59 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -2,8 +2,6 @@ import { Address, AddressValue, BigUIntValue, - BytesValue, - TokenIdentifierValue, TokenTransfer, } from '@multiversx/sdk-core/out'; import { EsdtTokenPayment } from '@multiversx/sdk-exchange'; @@ -350,6 +348,45 @@ export class PositionCreatorTransactionService { .toPlainObject(); } + async exitFarmPositionDualTokens( + farmAddress: string, + payment: EsdtTokenPayment, + tolerance: number, + ): Promise { + const pairAddress = await this.farmAbiV2.pairContractAddress( + farmAddress, + ); + const liquidityPosition = await this.pairService.getLiquidityPosition( + pairAddress, + payment.amount, + ); + const amount0Min = new BigNumber(liquidityPosition.firstTokenAmount) + .multipliedBy(1 - tolerance) + .integerValue(); + const amount1Min = new BigNumber(liquidityPosition.secondTokenAmount) + .multipliedBy(1 - tolerance) + .integerValue(); + + const contract = await this.mxProxy.getPostitionCreatorContract(); + + return contract.methodsExplicit + .exitFarmPos([ + new AddressValue(Address.fromBech32(farmAddress)), + new BigUIntValue(amount0Min), + new BigUIntValue(amount1Min), + ]) + .withSingleESDTNFTTransfer( + TokenTransfer.metaEsdtFromBigInteger( + payment.tokenIdentifier, + payment.tokenNonce, + new BigNumber(payment.amount), + ), + ) + .withGasLimit(gasConfig.positionCreator.singleToken) + .withChainID(mxConfig.chainID) + .buildTransaction() + .toPlainObject(); + } private checkTokensPayments( payments: EsdtTokenPayment[], From 71f8741ddafd75df715953fef3792cdfd9a9a768 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Thu, 23 Nov 2023 19:42:18 +0200 Subject: [PATCH 10/15] MEX-410: add additional payments for position creator transactions Signed-off-by: Claudiu Lataretu --- .../position.creator.resolver.ts | 16 ++-- .../services/position.creator.transaction.ts | 92 ++++++++++++++----- 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts index 015353760..ced82e9f0 100644 --- a/src/modules/position-creator/position.creator.resolver.ts +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -81,16 +81,20 @@ export class PositionCreatorResolver { @Query(() => TransactionModel) async createStakingPositionSingleToken( @Args('stakingAddress') stakingAddress: string, - @Args('payment') payment: InputTokenModel, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], @Args('tolerance') tolerance: number, ): Promise { return this.posCreatorTransaction.createStakingPositionSingleToken( stakingAddress, - new EsdtTokenPayment({ - tokenIdentifier: payment.tokenID, - tokenNonce: payment.nonce, - amount: payment.amount, - }), + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), tolerance, ); } diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index 39ad7ba59..ba6d7bb5a 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -46,7 +46,7 @@ export class PositionCreatorTransactionService { ); if (!uniqueTokensIDs.includes(payment.tokenIdentifier)) { - throw new Error('Invalid token'); + throw new Error('Invalid ESDT token payment'); } const singleTokenPairInput = @@ -184,21 +184,29 @@ export class PositionCreatorTransactionService { async createStakingPositionSingleToken( stakingAddress: string, - payment: EsdtTokenPayment, + payments: EsdtTokenPayment[], tolerance: number, ): Promise { - const [farmingTokenID, uniqueTokensIDs] = await Promise.all([ - this.stakingAbi.farmingTokenID(stakingAddress), - this.tokenService.getUniqueTokenIDs(false), - ]); + const [farmingTokenID, farmTokenID, uniqueTokensIDs] = + await Promise.all([ + this.stakingAbi.farmingTokenID(stakingAddress), + this.stakingAbi.farmTokenID(stakingAddress), + this.tokenService.getUniqueTokenIDs(false), + ]); - if (!uniqueTokensIDs.includes(payment.tokenIdentifier)) { + if (!uniqueTokensIDs.includes(payments[0].tokenIdentifier)) { throw new Error('Invalid ESDT token payment'); } + for (const payment of payments.slice(1)) { + if (payment.tokenIdentifier !== farmTokenID) { + throw new Error('Invalid staking token payment'); + } + } + const swapRoute = await this.autoRouterService.swap({ - tokenInID: payment.tokenIdentifier, - amountIn: payment.amount, + tokenInID: payments[0].tokenIdentifier, + amountIn: payments[0].amount, tokenOutID: farmingTokenID, tolerance, }); @@ -228,10 +236,13 @@ export class PositionCreatorTransactionService { ), ...multiSwapArgs, ]) - .withSingleESDTTransfer( - TokenTransfer.fungibleFromBigInteger( - payment.tokenIdentifier, - new BigNumber(payment.amount), + .withMultiESDTNFTTransfer( + payments.map((payment) => + TokenTransfer.metaEsdtFromBigInteger( + payment.tokenIdentifier, + payment.tokenNonce, + new BigNumber(payment.amount), + ), ), ) .withGasLimit(gasConfig.positionCreator.singleToken) @@ -248,15 +259,22 @@ export class PositionCreatorTransactionService { const pairAddress = await this.farmAbiV2.pairContractAddress( farmAddress, ); - const [firstTokenID, secondTokenID] = await Promise.all([ + const [firstTokenID, secondTokenID, farmTokenID] = await Promise.all([ this.pairAbi.firstTokenID(pairAddress), this.pairAbi.secondTokenID(pairAddress), + this.farmAbiV2.farmTokenID(farmAddress), ]); if (!this.checkTokensPayments(payments, firstTokenID, secondTokenID)) { throw new Error('Invalid tokens payments'); } + for (const payment of payments.slice(2)) { + if (payment.tokenIdentifier !== farmTokenID) { + throw new Error('Invalid farm token payment'); + } + } + const [firstPayment, secondPayment] = payments[0].tokenIdentifier === firstTokenID ? [payments[0], payments[1]] @@ -287,6 +305,15 @@ export class PositionCreatorTransactionService { new BigNumber(secondPayment.amount), ), + ...payments + .slice(2) + .map((payment) => + TokenTransfer.metaEsdtFromBigInteger( + payment.tokenIdentifier, + payment.tokenNonce, + new BigNumber(payment.amount), + ), + ), ]) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) @@ -302,15 +329,23 @@ export class PositionCreatorTransactionService { const pairAddress = await this.stakingProxyAbi.pairAddress( stakingProxyAddress, ); - const [firstTokenID, secondTokenID] = await Promise.all([ - this.pairAbi.firstTokenID(pairAddress), - this.pairAbi.secondTokenID(pairAddress), - ]); + const [firstTokenID, secondTokenID, dualYieldTokenID] = + await Promise.all([ + this.pairAbi.firstTokenID(pairAddress), + this.pairAbi.secondTokenID(pairAddress), + this.stakingProxyAbi.dualYieldTokenID(stakingProxyAddress), + ]); if (!this.checkTokensPayments(payments, firstTokenID, secondTokenID)) { throw new Error('Invalid tokens payments'); } + for (const payment of payments.slice(2)) { + if (payment.tokenIdentifier !== dualYieldTokenID) { + throw new Error('Invalid dual farm token payment'); + } + } + const [firstPayment, secondPayment] = payments[0].tokenIdentifier === firstTokenID ? [payments[0], payments[1]] @@ -341,6 +376,15 @@ export class PositionCreatorTransactionService { new BigNumber(secondPayment.amount), ), + ...payments + .slice(2) + .map((payment) => + TokenTransfer.metaEsdtFromBigInteger( + payment.tokenIdentifier, + payment.tokenNonce, + new BigNumber(payment.amount), + ), + ), ]) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) @@ -353,9 +397,15 @@ export class PositionCreatorTransactionService { payment: EsdtTokenPayment, tolerance: number, ): Promise { - const pairAddress = await this.farmAbiV2.pairContractAddress( - farmAddress, - ); + const [pairAddress, farmTokenID] = await Promise.all([ + this.farmAbiV2.pairContractAddress(farmAddress), + this.farmAbiV2.farmTokenID(farmAddress), + ]); + + if (payment.tokenIdentifier !== farmTokenID) { + throw new Error('Invalid farm token payment'); + } + const liquidityPosition = await this.pairService.getLiquidityPosition( pairAddress, payment.amount, From e7d47537509eda20df49a094b82290c7d8830727 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Thu, 23 Nov 2023 19:43:31 +0200 Subject: [PATCH 11/15] MEX-410: add unit tests for position creator transactions Signed-off-by: Claudiu Lataretu --- src/config/default.json | 3 +- .../position.creator.transaction.spec.ts | 925 ++++++++++++++++++ .../router/mocks/router.abi.service.mock.ts | 2 +- 3 files changed, 928 insertions(+), 2 deletions(-) create mode 100644 src/modules/position-creator/specs/position.creator.transaction.spec.ts diff --git a/src/config/default.json b/src/config/default.json index 2bef8e077..fef9e1dbf 100644 --- a/src/config/default.json +++ b/src/config/default.json @@ -42,7 +42,8 @@ "energyUpdate": "erd1qqqqqqqqqqqqqpgqqns0u3hw0e3j0km9h77emuear4xq7k7fd8ss0cwgja", "tokenUnstake": "erd1qqqqqqqqqqqqqpgqnysvq99c2t4a9pvvv22elnl6p73el8vw0n4spyfv7p", "lockedTokenWrapper": "erd1qqqqqqqqqqqqqpgq9ej9vcnr38l69rgkc735kgv0qlu2ptrsd8ssu9rwtu", - "escrow": "erd1qqqqqqqqqqqqqpgqz0wkk0j6y4h0mcxfxsg023j4x5sfgrmz0n4s4swp7a" + "escrow": "erd1qqqqqqqqqqqqqpgqz0wkk0j6y4h0mcxfxsg023j4x5sfgrmz0n4s4swp7a", + "positionCreator": "erd1qqqqqqqqqqqqqpgqh3zcutxk3wmfvevpyymaehvc3k0knyq70n4sg6qcj6" }, "tokenProviderUSD": "WEGLD-71e90a", "tokensSupply": ["MEX-27f4cd"], diff --git a/src/modules/position-creator/specs/position.creator.transaction.spec.ts b/src/modules/position-creator/specs/position.creator.transaction.spec.ts new file mode 100644 index 000000000..ee33e0532 --- /dev/null +++ b/src/modules/position-creator/specs/position.creator.transaction.spec.ts @@ -0,0 +1,925 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PositionCreatorTransactionService } from '../services/position.creator.transaction'; +import { PositionCreatorComputeService } from '../services/position.creator.compute'; +import { PairAbiServiceProvider } from 'src/modules/pair/mocks/pair.abi.service.mock'; +import { PairService } from 'src/modules/pair/services/pair.service'; +import { RouterAbiServiceProvider } from 'src/modules/router/mocks/router.abi.service.mock'; +import { RouterService } from 'src/modules/router/services/router.service'; +import { AutoRouterService } from 'src/modules/auto-router/services/auto-router.service'; +import { AutoRouterTransactionService } from 'src/modules/auto-router/services/auto-router.transactions.service'; +import { FarmAbiServiceProviderV2 } from 'src/modules/farm/mocks/farm.v2.abi.service.mock'; +import { StakingAbiServiceProvider } from 'src/modules/staking/mocks/staking.abi.service.mock'; +import { StakingProxyAbiServiceProvider } from 'src/modules/staking-proxy/mocks/staking.proxy.abi.service.mock'; +import { TokenServiceProvider } from 'src/modules/tokens/mocks/token.service.mock'; +import { MXProxyServiceProvider } from 'src/services/multiversx-communication/mx.proxy.service.mock'; +import { PairComputeServiceProvider } from 'src/modules/pair/mocks/pair.compute.service.mock'; +import { WrapAbiServiceProvider } from 'src/modules/wrapping/mocks/wrap.abi.service.mock'; +import { WinstonModule } from 'nest-winston'; +import winston from 'winston'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { DynamicModuleUtils } from 'src/utils/dynamic.module.utils'; +import { ApiConfigService } from 'src/helpers/api.config.service'; +import { ContextGetterServiceProvider } from 'src/services/context/mocks/context.getter.service.mock'; +import { AutoRouterComputeService } from 'src/modules/auto-router/services/auto-router.compute.service'; +import { PairTransactionService } from 'src/modules/pair/services/pair.transactions.service'; +import { WrapTransactionsService } from 'src/modules/wrapping/services/wrap.transactions.service'; +import { WrapService } from 'src/modules/wrapping/services/wrap.service'; +import { RemoteConfigGetterServiceProvider } from 'src/modules/remote-config/mocks/remote-config.getter.mock'; +import { Address } from '@multiversx/sdk-core/out'; +import { EsdtTokenPayment } from '@multiversx/sdk-exchange'; +import { encodeTransactionData } from 'src/helpers/helpers'; +import exp from 'constants'; +import { StakingProxyAbiService } from 'src/modules/staking-proxy/services/staking.proxy.abi.service'; + +describe('PositionCreatorTransaction', () => { + let module: TestingModule; + + beforeEach(async () => { + module = await Test.createTestingModule({ + imports: [ + WinstonModule.forRoot({ + transports: [new winston.transports.Console({})], + }), + ConfigModule.forRoot({}), + DynamicModuleUtils.getCacheModule(), + ], + providers: [ + PositionCreatorTransactionService, + PositionCreatorComputeService, + PairAbiServiceProvider, + PairService, + PairComputeServiceProvider, + PairTransactionService, + WrapService, + WrapAbiServiceProvider, + WrapTransactionsService, + RouterAbiServiceProvider, + RouterService, + AutoRouterService, + AutoRouterTransactionService, + AutoRouterComputeService, + FarmAbiServiceProviderV2, + StakingAbiServiceProvider, + StakingProxyAbiServiceProvider, + TokenServiceProvider, + RemoteConfigGetterServiceProvider, + MXProxyServiceProvider, + ConfigService, + ApiConfigService, + ContextGetterServiceProvider, + ], + }).compile(); + }); + + it('should be defined', () => { + expect(module).toBeDefined(); + }); + + describe('Create liquidity position single token', () => { + it('should return error on ESDT token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createLiquidityPositionSingleToken( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000012', + ).bech32(), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-abcdef', + tokenNonce: 0, + amount: '100000000000000000000', + }), + 0.01, + ), + ).rejects.toThrowError('Invalid ESDT token payment'); + }); + + it('should return transaction with single token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const transaction = + await service.createLiquidityPositionSingleToken( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000012', + ).bech32(), + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: + 'erd1qqqqqqqqqqqqqpgqh3zcutxk3wmfvevpyymaehvc3k0knyq70n4sg6qcj6', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + `ESDTTransfer@USDC-123456@100000000000000000000@createLpPosFromSingleToken@0000000000000000000000000000000000000000000000000000000000000012@494999999950351053163@329339339317295273252718@0000000000000000000000000000000000000000000000000000000000000013@swapTokensFixedInput@WEGLD-123456@989999999900702106327`, + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + }); + + describe('Create farm position single token', () => { + it('should return error on ESDT token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createFarmPositionSingleToken( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-abcdef', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid ESDT token payment'); + }); + + it('should return error on farm token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createFarmPositionSingleToken( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'EGLDMEXFL-123456', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid farm token payment'); + }); + + it('should return transaction no merge farm tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const transaction = await service.createFarmPositionSingleToken( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + `MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@01@USDC-123456@@100000000000000000000@createFarmPosFromSingleToken@0000000000000000000000000000000000000000000000000000000000000021@494999999950351053163@329339339317295273252718@0000000000000000000000000000000000000000000000000000000000000013@swapTokensFixedInput@WEGLD-123456@989999999900702106327`, + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + + it('should return transaction with merge farm tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const transaction = await service.createFarmPositionSingleToken( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'EGLDMEXFL-abcdef', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + `MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@02@USDC-123456@@100000000000000000000@EGLDMEXFL-abcdef@01@100000000000000000000@createFarmPosFromSingleToken@0000000000000000000000000000000000000000000000000000000000000021@494999999950351053163@329339339317295273252718@0000000000000000000000000000000000000000000000000000000000000013@swapTokensFixedInput@WEGLD-123456@989999999900702106327`, + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + }); + + describe('Create dual farm position single token', () => { + it('should return error on ESDT token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createDualFarmPositionSingleToken( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-abcdef', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid ESDT token payment'); + }); + + it('should return error on dual farm token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createDualFarmPositionSingleToken( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'METASTAKE-abcdef', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid dual yield token payment'); + }); + + it('should return transaction no merge dual farm tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const stakingProxyAbi = module.get( + StakingProxyAbiService, + ); + jest.spyOn(stakingProxyAbi, 'pairAddress').mockResolvedValue( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000012', + ).bech32(), + ); + + const transaction = await service.createDualFarmPositionSingleToken( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@01@USDC-123456@@100000000000000000000@createMetastakingPosFromSingleToken@0000000000000000000000000000000000000000000000000000000000000000@494999999950351053163@329339339317295273252718@0000000000000000000000000000000000000000000000000000000000000013@swapTokensFixedInput@WEGLD-123456@989999999900702106327', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + + it('should return transaction with merge dual farm tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const stakingProxyAbi = module.get( + StakingProxyAbiService, + ); + jest.spyOn(stakingProxyAbi, 'pairAddress').mockResolvedValue( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000012', + ).bech32(), + ); + + const transaction = await service.createDualFarmPositionSingleToken( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'METASTAKE-1234', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@02@USDC-123456@@100000000000000000000@METASTAKE-1234@01@100000000000000000000@createMetastakingPosFromSingleToken@0000000000000000000000000000000000000000000000000000000000000000@494999999950351053163@329339339317295273252718@0000000000000000000000000000000000000000000000000000000000000013@swapTokensFixedInput@WEGLD-123456@989999999900702106327', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + }); + + describe('Create staking position single token', () => { + it('should return error on ESDT token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createStakingPositionSingleToken( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-abcdef', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid ESDT token payment'); + }); + + it('should return error on staking token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createStakingPositionSingleToken( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'STAKETOK-abcdef', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid staking token payment'); + }); + + it('should return transaction no merge staking tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const transaction = await service.createStakingPositionSingleToken( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@01@USDC-123456@@100000000000000000000@createFarmStakingPosFromSingleToken@0000000000000000000000000000000000000000000000000000000000000000@999999999899699097301@0000000000000000000000000000000000000000000000000000000000000013@swapTokensFixedInput@WEGLD-123456@989999999900702106327', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + + it('should return transaction with merge staking tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const transaction = await service.createStakingPositionSingleToken( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'USDC-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'STAKETOK-1111', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@02@USDC-123456@@100000000000000000000@STAKETOK-1111@01@100000000000000000000@createFarmStakingPosFromSingleToken@0000000000000000000000000000000000000000000000000000000000000000@999999999899699097301@0000000000000000000000000000000000000000000000000000000000000013@swapTokensFixedInput@WEGLD-123456@989999999900702106327', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + }); + + describe('Create farm position dual tokens', () => { + it('should return error on invalid payments', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createFarmPositionDualTokens( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'WEGLD-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-abcdef', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid tokens payments'); + }); + + it('should return error on invalid farm token merge', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.createFarmPositionDualTokens( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'WEGLD-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'EGLDMEXFL-123456', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid farm token payment'); + }); + + it('should return transaction no merge farm tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const transaction = await service.createFarmPositionDualTokens( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'WEGLD-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@02@WEGLD-123456@@100000000000000000000@MEX-123456@@100000000000000000000@createFarmPosFromTwoTokens@0000000000000000000000000000000000000000000000000000000000000021@99000000000000000000@99000000000000000000', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + + it('should return transaction no merge farm tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const transaction = await service.createFarmPositionDualTokens( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'WEGLD-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'EGLDMEXFL-abcdef', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@03@WEGLD-123456@@100000000000000000000@MEX-123456@@100000000000000000000@EGLDMEXFL-abcdef@01@100000000000000000000@createFarmPosFromTwoTokens@0000000000000000000000000000000000000000000000000000000000000021@99000000000000000000@99000000000000000000', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + }); + + describe('Create dual farm position dual tokens', () => { + it('should return error on invalid payments', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const stakingProxyAbi = module.get( + StakingProxyAbiService, + ); + jest.spyOn(stakingProxyAbi, 'pairAddress').mockResolvedValue( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000012', + ).bech32(), + ); + + expect( + service.createDualFarmPositionDualTokens( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'WEGLD-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-abcdef', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid tokens payments'); + }); + + it('should return error on invalid farm token merge', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const stakingProxyAbi = module.get( + StakingProxyAbiService, + ); + jest.spyOn(stakingProxyAbi, 'pairAddress').mockResolvedValue( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000012', + ).bech32(), + ); + + expect( + service.createDualFarmPositionDualTokens( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'WEGLD-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'METASTAKE-abcdef', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ), + ).rejects.toThrowError('Invalid dual farm token payment'); + }); + + it('should return transaction no merge farm tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const stakingProxyAbi = module.get( + StakingProxyAbiService, + ); + jest.spyOn(stakingProxyAbi, 'pairAddress').mockResolvedValue( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000012', + ).bech32(), + ); + + const transaction = await service.createDualFarmPositionDualTokens( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'WEGLD-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@02@WEGLD-123456@@100000000000000000000@MEX-123456@@100000000000000000000@createMetastakingPosFromTwoTokens@0000000000000000000000000000000000000000000000000000000000000000@99000000000000000000@99000000000000000000', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + + it('should return transaction no merge farm tokens', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const stakingProxyAbi = module.get( + StakingProxyAbiService, + ); + jest.spyOn(stakingProxyAbi, 'pairAddress').mockResolvedValue( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000012', + ).bech32(), + ); + + const transaction = await service.createDualFarmPositionDualTokens( + Address.Zero().bech32(), + [ + new EsdtTokenPayment({ + tokenIdentifier: 'WEGLD-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-123456', + tokenNonce: 0, + amount: '100000000000000000000', + }), + new EsdtTokenPayment({ + tokenIdentifier: 'METASTAKE-1234', + tokenNonce: 1, + amount: '100000000000000000000', + }), + ], + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'MultiESDTNFTTransfer@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@03@WEGLD-123456@@100000000000000000000@MEX-123456@@100000000000000000000@METASTAKE-1234@01@100000000000000000000@createMetastakingPosFromTwoTokens@0000000000000000000000000000000000000000000000000000000000000000@99000000000000000000@99000000000000000000', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + }); + + describe('Exit farm position dual tokens', () => { + it('should return error on invalid farm token', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + expect( + service.exitFarmPositionDualTokens( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + new EsdtTokenPayment({ + tokenIdentifier: 'MEX-abcdef', + tokenNonce: 0, + amount: '100000000000000000000', + }), + 0.01, + ), + ).rejects.toThrowError('Invalid farm token payment'); + }); + + it('should return transaction', async () => { + const service = module.get( + PositionCreatorTransactionService, + ); + const transaction = await service.exitFarmPositionDualTokens( + Address.fromHex( + '0000000000000000000000000000000000000000000000000000000000000021', + ).bech32(), + new EsdtTokenPayment({ + tokenIdentifier: 'EGLDMEXFL-abcdef', + tokenNonce: 1, + amount: '100000000000000000000', + }), + 0.01, + ); + + expect(transaction).toEqual({ + nonce: 0, + value: '0', + receiver: '', + sender: '', + senderUsername: undefined, + receiverUsername: undefined, + gasPrice: 1000000000, + gasLimit: 50000000, + data: encodeTransactionData( + 'ESDTNFTTransfer@EGLDMEXFL-abcdef@01@100000000000000000000@00000000000000000500bc458e2cd68bb69665812137dcdd988d9f69901e7ceb@exitFarmPos@0000000000000000000000000000000000000000000000000000000000000021@99000000000000000000@99000000000000000000000', + ), + chainID: 'T', + version: 1, + options: undefined, + guardian: undefined, + signature: undefined, + guardianSignature: undefined, + }); + }); + }); +}); diff --git a/src/modules/router/mocks/router.abi.service.mock.ts b/src/modules/router/mocks/router.abi.service.mock.ts index 4bcacaf8e..d3b9afcb7 100644 --- a/src/modules/router/mocks/router.abi.service.mock.ts +++ b/src/modules/router/mocks/router.abi.service.mock.ts @@ -52,7 +52,7 @@ export class RouterAbiServiceMock implements IRouterAbiService { }); } async commonTokensForUserPairs(): Promise { - return ['USDC-123456']; + return ['USDC-123456', 'WEGLD-123456']; } } From 586eb9023c5c898b5f83f5ae7279216dd9e0b5e6 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Thu, 23 Nov 2023 22:02:41 +0200 Subject: [PATCH 12/15] MEX-410: add auth guard to position creator transactions resolver Signed-off-by: Claudiu Lataretu --- .../position.creator.resolver.ts | 149 +-------------- .../position.creator.transaction.resolver.ts | 170 ++++++++++++++++++ .../services/position.creator.transaction.ts | 14 ++ 3 files changed, 185 insertions(+), 148 deletions(-) create mode 100644 src/modules/position-creator/position.creator.transaction.resolver.ts diff --git a/src/modules/position-creator/position.creator.resolver.ts b/src/modules/position-creator/position.creator.resolver.ts index ced82e9f0..05810fa33 100644 --- a/src/modules/position-creator/position.creator.resolver.ts +++ b/src/modules/position-creator/position.creator.resolver.ts @@ -1,160 +1,13 @@ -import { Args, Query, Resolver } from '@nestjs/graphql'; +import { Query, Resolver } from '@nestjs/graphql'; import { PositionCreatorModel } from './models/position.creator.model'; import { scAddress } from 'src/config'; -import { TransactionModel } from 'src/models/transaction.model'; -import { EsdtTokenPayment } from '@multiversx/sdk-exchange'; -import { PositionCreatorTransactionService } from './services/position.creator.transaction'; -import { InputTokenModel } from 'src/models/inputToken.model'; @Resolver(PositionCreatorModel) export class PositionCreatorResolver { - constructor( - private readonly posCreatorTransaction: PositionCreatorTransactionService, - ) {} - @Query(() => PositionCreatorModel) async getPositionCreator(): Promise { return new PositionCreatorModel({ address: scAddress.positionCreator, }); } - - @Query(() => TransactionModel) - async createPositionSingleToken( - @Args('pairAddress') pairAddress: string, - @Args('payment') payment: InputTokenModel, - @Args('tolerance') tolerance: number, - ): Promise { - return this.posCreatorTransaction.createLiquidityPositionSingleToken( - pairAddress, - new EsdtTokenPayment({ - tokenIdentifier: payment.tokenID, - tokenNonce: payment.nonce, - amount: payment.amount, - }), - tolerance, - ); - } - - @Query(() => TransactionModel) - async createFarmPositionSingleToken( - @Args('farmAddress') farmAddress: string, - @Args('payments', { type: () => [InputTokenModel] }) - payments: InputTokenModel[], - @Args('tolerance') tolerance: number, - ): Promise { - return this.posCreatorTransaction.createFarmPositionSingleToken( - farmAddress, - payments.map( - (payment) => - new EsdtTokenPayment({ - tokenIdentifier: payment.tokenID, - tokenNonce: payment.nonce, - amount: payment.amount, - }), - ), - tolerance, - ); - } - - @Query(() => TransactionModel) - async createDualFarmPositionSingleToken( - @Args('dualFarmAddress') dualFarmAddress: string, - @Args('payments', { type: () => [InputTokenModel] }) - payments: InputTokenModel[], - @Args('tolerance') tolerance: number, - ): Promise { - return this.posCreatorTransaction.createDualFarmPositionSingleToken( - dualFarmAddress, - payments.map( - (payment) => - new EsdtTokenPayment({ - tokenIdentifier: payment.tokenID, - tokenNonce: payment.nonce, - amount: payment.amount, - }), - ), - tolerance, - ); - } - - @Query(() => TransactionModel) - async createStakingPositionSingleToken( - @Args('stakingAddress') stakingAddress: string, - @Args('payments', { type: () => [InputTokenModel] }) - payments: InputTokenModel[], - @Args('tolerance') tolerance: number, - ): Promise { - return this.posCreatorTransaction.createStakingPositionSingleToken( - stakingAddress, - payments.map( - (payment) => - new EsdtTokenPayment({ - tokenIdentifier: payment.tokenID, - tokenNonce: payment.nonce, - amount: payment.amount, - }), - ), - tolerance, - ); - } - - @Query(() => TransactionModel) - async createFarmPositionDualTokens( - @Args('farmAddress') farmAddress: string, - @Args('payments', { type: () => [InputTokenModel] }) - payments: InputTokenModel[], - @Args('tolerance') tolerance: number, - ): Promise { - return this.posCreatorTransaction.createFarmPositionDualTokens( - farmAddress, - payments.map( - (payment) => - new EsdtTokenPayment({ - tokenIdentifier: payment.tokenID, - tokenNonce: payment.nonce, - amount: payment.amount, - }), - ), - tolerance, - ); - } - - @Query(() => TransactionModel) - async createDualFarmPositionDualTokens( - @Args('dualFarmAddress') dualFarmAddress: string, - @Args('payments', { type: () => [InputTokenModel] }) - payments: InputTokenModel[], - @Args('tolerance') tolerance: number, - ): Promise { - return this.posCreatorTransaction.createDualFarmPositionDualTokens( - dualFarmAddress, - payments.map( - (payment) => - new EsdtTokenPayment({ - tokenIdentifier: payment.tokenID, - tokenNonce: payment.nonce, - amount: payment.amount, - }), - ), - tolerance, - ); - } - - @Query(() => TransactionModel) - async exitFarmPositionDualTokens( - @Args('farmAddress') farmAddress: string, - @Args('payment') payment: InputTokenModel, - @Args('tolerance') tolerance: number, - ): Promise { - return this.posCreatorTransaction.exitFarmPositionDualTokens( - farmAddress, - new EsdtTokenPayment({ - tokenIdentifier: payment.tokenID, - tokenNonce: payment.nonce, - amount: payment.amount, - }), - tolerance, - ); - } } diff --git a/src/modules/position-creator/position.creator.transaction.resolver.ts b/src/modules/position-creator/position.creator.transaction.resolver.ts new file mode 100644 index 000000000..6885f73c5 --- /dev/null +++ b/src/modules/position-creator/position.creator.transaction.resolver.ts @@ -0,0 +1,170 @@ +import { UseGuards } from '@nestjs/common'; +import { Args, Query, Resolver } from '@nestjs/graphql'; +import { JwtOrNativeAuthGuard } from '../auth/jwt.or.native.auth.guard'; +import { TransactionModel } from 'src/models/transaction.model'; +import { EsdtTokenPayment } from '@multiversx/sdk-exchange'; +import { PositionCreatorTransactionService } from './services/position.creator.transaction'; +import { InputTokenModel } from 'src/models/inputToken.model'; +import { UserAuthResult } from '../auth/user.auth.result'; +import { AuthUser } from '../auth/auth.user'; + +@Resolver() +@UseGuards(JwtOrNativeAuthGuard) +export class PositionCreatorTransactionResolver { + constructor( + private readonly posCreatorTransaction: PositionCreatorTransactionService, + ) {} + + @Query(() => TransactionModel) + async createPositionSingleToken( + @AuthUser() user: UserAuthResult, + @Args('pairAddress') pairAddress: string, + @Args('payment') payment: InputTokenModel, + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createLiquidityPositionSingleToken( + user.address, + pairAddress, + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + tolerance, + ); + } + + @Query(() => TransactionModel) + async createFarmPositionSingleToken( + @AuthUser() user: UserAuthResult, + @Args('farmAddress') farmAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createFarmPositionSingleToken( + user.address, + farmAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + + @Query(() => TransactionModel) + async createDualFarmPositionSingleToken( + @AuthUser() user: UserAuthResult, + @Args('dualFarmAddress') dualFarmAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createDualFarmPositionSingleToken( + user.address, + dualFarmAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + + @Query(() => TransactionModel) + async createStakingPositionSingleToken( + @AuthUser() user: UserAuthResult, + @Args('stakingAddress') stakingAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createStakingPositionSingleToken( + user.address, + stakingAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + + @Query(() => TransactionModel) + async createFarmPositionDualTokens( + @AuthUser() user: UserAuthResult, + @Args('farmAddress') farmAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createFarmPositionDualTokens( + user.address, + farmAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + + @Query(() => TransactionModel) + async createDualFarmPositionDualTokens( + @AuthUser() user: UserAuthResult, + @Args('dualFarmAddress') dualFarmAddress: string, + @Args('payments', { type: () => [InputTokenModel] }) + payments: InputTokenModel[], + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.createDualFarmPositionDualTokens( + user.address, + dualFarmAddress, + payments.map( + (payment) => + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + ), + tolerance, + ); + } + + @Query(() => TransactionModel) + async exitFarmPositionDualTokens( + @AuthUser() user: UserAuthResult, + @Args('farmAddress') farmAddress: string, + @Args('payment') payment: InputTokenModel, + @Args('tolerance') tolerance: number, + ): Promise { + return this.posCreatorTransaction.exitFarmPositionDualTokens( + user.address, + farmAddress, + new EsdtTokenPayment({ + tokenIdentifier: payment.tokenID, + tokenNonce: payment.nonce, + amount: payment.amount, + }), + tolerance, + ); + } +} diff --git a/src/modules/position-creator/services/position.creator.transaction.ts b/src/modules/position-creator/services/position.creator.transaction.ts index ba6d7bb5a..b8b476641 100644 --- a/src/modules/position-creator/services/position.creator.transaction.ts +++ b/src/modules/position-creator/services/position.creator.transaction.ts @@ -37,6 +37,7 @@ export class PositionCreatorTransactionService { ) {} async createLiquidityPositionSingleToken( + sender: string, pairAddress: string, payment: EsdtTokenPayment, tolerance: number, @@ -71,6 +72,7 @@ export class PositionCreatorTransactionService { new BigNumber(payment.amount), ), ) + .withSender(Address.fromBech32(sender)) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) .buildTransaction() @@ -78,6 +80,7 @@ export class PositionCreatorTransactionService { } async createFarmPositionSingleToken( + sender: string, farmAddress: string, payments: EsdtTokenPayment[], tolerance: number, @@ -123,6 +126,7 @@ export class PositionCreatorTransactionService { ), ), ) + .withSender(Address.fromBech32(sender)) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) .buildTransaction() @@ -130,6 +134,7 @@ export class PositionCreatorTransactionService { } async createDualFarmPositionSingleToken( + sender: string, stakingProxyAddress: string, payments: EsdtTokenPayment[], tolerance: number, @@ -176,6 +181,7 @@ export class PositionCreatorTransactionService { ), ), ) + .withSender(Address.fromBech32(sender)) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) .buildTransaction() @@ -183,6 +189,7 @@ export class PositionCreatorTransactionService { } async createStakingPositionSingleToken( + sender: string, stakingAddress: string, payments: EsdtTokenPayment[], tolerance: number, @@ -245,6 +252,7 @@ export class PositionCreatorTransactionService { ), ), ) + .withSender(Address.fromBech32(sender)) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) .buildTransaction() @@ -252,6 +260,7 @@ export class PositionCreatorTransactionService { } async createFarmPositionDualTokens( + sender: string, farmAddress: string, payments: EsdtTokenPayment[], tolerance: number, @@ -315,6 +324,7 @@ export class PositionCreatorTransactionService { ), ), ]) + .withSender(Address.fromBech32(sender)) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) .buildTransaction() @@ -322,6 +332,7 @@ export class PositionCreatorTransactionService { } async createDualFarmPositionDualTokens( + sender: string, stakingProxyAddress: string, payments: EsdtTokenPayment[], tolerance: number, @@ -386,6 +397,7 @@ export class PositionCreatorTransactionService { ), ), ]) + .withSender(Address.fromBech32(sender)) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) .buildTransaction() @@ -393,6 +405,7 @@ export class PositionCreatorTransactionService { } async exitFarmPositionDualTokens( + sender: string, farmAddress: string, payment: EsdtTokenPayment, tolerance: number, @@ -432,6 +445,7 @@ export class PositionCreatorTransactionService { new BigNumber(payment.amount), ), ) + .withSender(Address.fromBech32(sender)) .withGasLimit(gasConfig.positionCreator.singleToken) .withChainID(mxConfig.chainID) .buildTransaction() From e00629a734e1afcc63b73f0e184c3ed82dc71c21 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Thu, 23 Nov 2023 22:16:49 +0200 Subject: [PATCH 13/15] MEX-410: add sender for position creator transactions unit tests Signed-off-by: Claudiu Lataretu --- .../position.creator.transaction.spec.ts | 70 +++++++++++++------ .../specs/router.transactions.service.spec.ts | 4 +- 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/modules/position-creator/specs/position.creator.transaction.spec.ts b/src/modules/position-creator/specs/position.creator.transaction.spec.ts index ee33e0532..0acd9f074 100644 --- a/src/modules/position-creator/specs/position.creator.transaction.spec.ts +++ b/src/modules/position-creator/specs/position.creator.transaction.spec.ts @@ -82,6 +82,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createLiquidityPositionSingleToken( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000012', ).bech32(), @@ -101,6 +102,7 @@ describe('PositionCreatorTransaction', () => { ); const transaction = await service.createLiquidityPositionSingleToken( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000012', ).bech32(), @@ -117,7 +119,7 @@ describe('PositionCreatorTransaction', () => { value: '0', receiver: 'erd1qqqqqqqqqqqqqpgqh3zcutxk3wmfvevpyymaehvc3k0knyq70n4sg6qcj6', - sender: '', + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -142,6 +144,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createFarmPositionSingleToken( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -163,6 +166,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createFarmPositionSingleToken( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -188,6 +192,7 @@ describe('PositionCreatorTransaction', () => { PositionCreatorTransactionService, ); const transaction = await service.createFarmPositionSingleToken( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -204,8 +209,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -227,6 +232,7 @@ describe('PositionCreatorTransaction', () => { PositionCreatorTransactionService, ); const transaction = await service.createFarmPositionSingleToken( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -248,8 +254,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -274,6 +280,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createDualFarmPositionSingleToken( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -293,6 +300,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createDualFarmPositionSingleToken( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -325,6 +333,7 @@ describe('PositionCreatorTransaction', () => { ); const transaction = await service.createDualFarmPositionSingleToken( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -339,8 +348,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -371,6 +380,7 @@ describe('PositionCreatorTransaction', () => { ); const transaction = await service.createDualFarmPositionSingleToken( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -390,8 +400,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -416,6 +426,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createStakingPositionSingleToken( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -435,6 +446,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createStakingPositionSingleToken( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -458,6 +470,7 @@ describe('PositionCreatorTransaction', () => { PositionCreatorTransactionService, ); const transaction = await service.createStakingPositionSingleToken( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -472,8 +485,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -495,6 +508,7 @@ describe('PositionCreatorTransaction', () => { PositionCreatorTransactionService, ); const transaction = await service.createStakingPositionSingleToken( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -514,8 +528,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -540,6 +554,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createFarmPositionDualTokens( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -566,6 +581,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.createFarmPositionDualTokens( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -596,6 +612,7 @@ describe('PositionCreatorTransaction', () => { PositionCreatorTransactionService, ); const transaction = await service.createFarmPositionDualTokens( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -617,8 +634,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -640,6 +657,7 @@ describe('PositionCreatorTransaction', () => { PositionCreatorTransactionService, ); const transaction = await service.createFarmPositionDualTokens( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -666,8 +684,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -701,6 +719,7 @@ describe('PositionCreatorTransaction', () => { expect( service.createDualFarmPositionDualTokens( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -734,6 +753,7 @@ describe('PositionCreatorTransaction', () => { expect( service.createDualFarmPositionDualTokens( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -771,6 +791,7 @@ describe('PositionCreatorTransaction', () => { ); const transaction = await service.createDualFarmPositionDualTokens( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -790,8 +811,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -822,6 +843,7 @@ describe('PositionCreatorTransaction', () => { ); const transaction = await service.createDualFarmPositionDualTokens( + Address.Zero().bech32(), Address.Zero().bech32(), [ new EsdtTokenPayment({ @@ -846,8 +868,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, @@ -872,6 +894,7 @@ describe('PositionCreatorTransaction', () => { ); expect( service.exitFarmPositionDualTokens( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -890,6 +913,7 @@ describe('PositionCreatorTransaction', () => { PositionCreatorTransactionService, ); const transaction = await service.exitFarmPositionDualTokens( + Address.Zero().bech32(), Address.fromHex( '0000000000000000000000000000000000000000000000000000000000000021', ).bech32(), @@ -904,8 +928,8 @@ describe('PositionCreatorTransaction', () => { expect(transaction).toEqual({ nonce: 0, value: '0', - receiver: '', - sender: '', + receiver: Address.Zero().bech32(), + sender: Address.Zero().bech32(), senderUsername: undefined, receiverUsername: undefined, gasPrice: 1000000000, diff --git a/src/modules/router/specs/router.transactions.service.spec.ts b/src/modules/router/specs/router.transactions.service.spec.ts index 23a10f8bc..2bd4a242a 100644 --- a/src/modules/router/specs/router.transactions.service.spec.ts +++ b/src/modules/router/specs/router.transactions.service.spec.ts @@ -655,7 +655,7 @@ describe('RouterService', () => { nonce: 1, amount: '1000000000000000000', attributes: - 'AAAAEEVHTERNRVhMUC1hYmNkZWYAAAAAAAAAAAAAAAAAAAAB', + 'AAAAEVRPSzVUT0s2TFAtYWJjZGVmAAAAAAAAAAAAAAAAAAAAAQ==', }), ), ).rejects.toThrow('Not a valid user defined pair'); @@ -678,7 +678,7 @@ describe('RouterService', () => { nonce: 1, amount: '1000', attributes: - 'AAAAEUVHTERVU0RDTFAtYWJjZGVmAAAAAAAAAAAAAAAAAAAAAg==', + 'AAAAEVRPSzVVU0RDTFAtYWJjZGVmAAAAAAAAAAAAAAAAAAAAAg==', }), ), ).rejects.toThrow('Not enough value locked'); From eb5d78d58f4f35cec630b13b45584f66f29e8336 Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Fri, 24 Nov 2023 18:27:35 +0200 Subject: [PATCH 14/15] MEX-410: add missing position creator module provider Signed-off-by: Claudiu Lataretu --- src/modules/position-creator/position.creator.module.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/position-creator/position.creator.module.ts b/src/modules/position-creator/position.creator.module.ts index 7f12240d6..bfd8391b2 100644 --- a/src/modules/position-creator/position.creator.module.ts +++ b/src/modules/position-creator/position.creator.module.ts @@ -10,6 +10,7 @@ import { FarmModuleV2 } from '../farm/v2/farm.v2.module'; import { StakingProxyModule } from '../staking-proxy/staking.proxy.module'; import { StakingModule } from '../staking/staking.module'; import { TokenModule } from '../tokens/token.module'; +import { PositionCreatorTransactionResolver } from './position.creator.transaction.resolver'; @Module({ imports: [ @@ -26,6 +27,7 @@ import { TokenModule } from '../tokens/token.module'; PositionCreatorComputeService, PositionCreatorTransactionService, PositionCreatorResolver, + PositionCreatorTransactionResolver, ], exports: [], }) From 497a86097e43af03acb4ce0e13c944784dc5357c Mon Sep 17 00:00:00 2001 From: Claudiu Lataretu Date: Mon, 27 Nov 2023 18:18:52 +0200 Subject: [PATCH 15/15] MEX-410: imporve position creator compute swap method Signed-off-by: Claudiu Lataretu --- .../services/position.creator.compute.ts | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/modules/position-creator/services/position.creator.compute.ts b/src/modules/position-creator/services/position.creator.compute.ts index b02b37eaf..95168b5e1 100644 --- a/src/modules/position-creator/services/position.creator.compute.ts +++ b/src/modules/position-creator/services/position.creator.compute.ts @@ -1,5 +1,6 @@ import { TypedValue } from '@multiversx/sdk-core/out'; import { EsdtTokenPayment } from '@multiversx/sdk-exchange'; +import { PerformanceProfiler } from '@multiversx/sdk-nestjs-monitoring'; import { Injectable } from '@nestjs/common'; import BigNumber from 'bignumber.js'; import { SWAP_TYPE } from 'src/modules/auto-router/models/auto-route.model'; @@ -8,7 +9,6 @@ import { AutoRouterTransactionService } from 'src/modules/auto-router/services/a import { PairAbiService } from 'src/modules/pair/services/pair.abi.service'; import { PairService } from 'src/modules/pair/services/pair.service'; import { RouterAbiService } from 'src/modules/router/services/router.abi.service'; -import { RouterService } from 'src/modules/router/services/router.service'; export type PositionCreatorSingleTokenPairInput = { swapRouteArgs: TypedValue[]; @@ -22,12 +22,12 @@ export class PositionCreatorComputeService { private readonly pairAbi: PairAbiService, private readonly pairService: PairService, private readonly routerAbi: RouterAbiService, - private readonly routerService: RouterService, private readonly autoRouterService: AutoRouterService, private readonly autoRouterTransaction: AutoRouterTransactionService, ) {} async computeSwap( + pairAddress: string, fromTokenID: string, toTokenID: string, amount: string, @@ -36,20 +36,8 @@ export class PositionCreatorComputeService { return new BigNumber(amount); } - const pairs = await this.routerService.getAllPairs(0, 1, { - address: null, - issuedLpToken: true, - firstTokenID: fromTokenID, - secondTokenID: toTokenID, - state: 'Active', - }); - - if (pairs.length === 0) { - throw new Error('Pair not found'); - } - const amountOut = await this.pairService.getAmountOut( - pairs[0].address, + pairAddress, fromTokenID, amount, ); @@ -74,6 +62,8 @@ export class PositionCreatorComputeService { ? firstTokenID : secondTokenID; + const profiler = new PerformanceProfiler(); + const swapRoute = await this.autoRouterService.swap({ tokenInID: payment.tokenIdentifier, amountIn: payment.amount, @@ -81,6 +71,8 @@ export class PositionCreatorComputeService { tolerance, }); + profiler.stop('swap route', true); + const halfPayment = new BigNumber(swapRoute.amountOut) .dividedBy(2) .integerValue() @@ -92,11 +84,13 @@ export class PositionCreatorComputeService { const [amount0, amount1] = await Promise.all([ await this.computeSwap( + pairAddress, swapRoute.tokenOutID, firstTokenID, halfPayment, ), await this.computeSwap( + pairAddress, swapRoute.tokenOutID, secondTokenID, remainingPayment,