From 169ac45f77aebb9773d795cbe0185267a1d70d9f Mon Sep 17 00:00:00 2001 From: jinoosss Date: Fri, 15 Dec 2023 16:59:10 +0900 Subject: [PATCH 1/2] feat: [GSW-695] Integrate Create Incentive --- .../transaction-messages/pool.ts | 57 +++++++- .../web/src/common/errors/pool/pool-error.ts | 8 ++ .../IncentivizePoolModalContainer.tsx | 29 ++++- .../PoolIncentivizeContainer.tsx | 4 +- .../repositories/pool/pool-repository-impl.ts | 123 +++++++++++++++--- .../repositories/pool/pool-repository-mock.ts | 14 +- .../src/repositories/pool/pool-repository.ts | 9 ++ .../create-external-incentive-request.ts | 13 ++ .../remove-external-incentive-request.ts | 7 + 9 files changed, 237 insertions(+), 27 deletions(-) create mode 100644 packages/web/src/repositories/pool/request/create-external-incentive-request.ts create mode 100644 packages/web/src/repositories/pool/request/remove-external-incentive-request.ts diff --git a/packages/web/src/common/clients/wallet-client/transaction-messages/pool.ts b/packages/web/src/common/clients/wallet-client/transaction-messages/pool.ts index 1148c279c..cb9ae674d 100644 --- a/packages/web/src/common/clients/wallet-client/transaction-messages/pool.ts +++ b/packages/web/src/common/clients/wallet-client/transaction-messages/pool.ts @@ -1,4 +1,10 @@ -import { makeApproveMessage, PACKAGE_POOL_ADDRESS } from "./common"; +import { + makeApproveMessage, + makeTransactionMessage, + PACKAGE_POOL_ADDRESS, + PACKAGE_STAKER_ADDRESS, + PACKAGE_STAKER_PATH, +} from "./common"; export function makePoolTokenApproveMessage( packagePath: string, @@ -11,3 +17,52 @@ export function makePoolTokenApproveMessage( caller, ); } + +export function makeCreateIncentiveMessage( + poolPath: string, + rewardTokenPath: string, + rewardAmount: string, + startTime: number, + endTime: number, + caller: string, +) { + return makeTransactionMessage({ + send: "", + func: "CreateExternalIncentive", + packagePath: PACKAGE_STAKER_PATH, + args: [ + poolPath, + rewardTokenPath, + rewardAmount, + `${startTime}`, + `${endTime}`, + ], + caller, + }); +} + +export function makeRemoveIncentiveMessage( + poolPath: string, + rewardTokenPath: string, + caller: string, +) { + return makeTransactionMessage({ + send: "", + func: "EndExternalIncentive", + packagePath: PACKAGE_STAKER_PATH, + args: [caller, poolPath, rewardTokenPath], + caller, + }); +} + +export function makeStakerApproveMessage( + tokenPath: string, + amount: string, + caller: string, +) { + return makeApproveMessage( + tokenPath, + [PACKAGE_STAKER_ADDRESS, amount], + caller, + ); +} diff --git a/packages/web/src/common/errors/pool/pool-error.ts b/packages/web/src/common/errors/pool/pool-error.ts index c62fd19d2..39b5c48d0 100644 --- a/packages/web/src/common/errors/pool/pool-error.ts +++ b/packages/web/src/common/errors/pool/pool-error.ts @@ -5,6 +5,14 @@ const ERROR_VALUE = { status: 404, type: "Not found pool", }, + FAILED_TO_CREATE_INCENTIVE: { + status: 500, + type: "Failed to create incentive", + }, + FAILED_TO_REMOVE_INCENTIVE: { + status: 500, + type: "Failed to remove incentive", + }, }; type ErrorType = keyof typeof ERROR_VALUE; diff --git a/packages/web/src/containers/incentivize-pool-modal-container/IncentivizePoolModalContainer.tsx b/packages/web/src/containers/incentivize-pool-modal-container/IncentivizePoolModalContainer.tsx index 3fd824c83..0e9ee0151 100644 --- a/packages/web/src/containers/incentivize-pool-modal-container/IncentivizePoolModalContainer.tsx +++ b/packages/web/src/containers/incentivize-pool-modal-container/IncentivizePoolModalContainer.tsx @@ -3,21 +3,44 @@ import { useClearModal } from "@hooks/common/use-clear-modal"; import React, { useCallback } from "react"; import { useAtom } from "jotai"; import { EarnState } from "@states/index"; +import { useGnoswapContext } from "@hooks/common/use-gnoswap-context"; +import { useRouter } from "next/router"; + +const DAY_TIME = 24 * 60 * 60; const IncentivizePoolModalContainer = () => { + const router = useRouter(); const clearModal = useClearModal(); + const { poolRepository } = useGnoswapContext(); const [period] = useAtom(EarnState.period); const [startDate] = useAtom(EarnState.date); const [dataModal] = useAtom(EarnState.dataModal); const [pool] = useAtom(EarnState.pool); - + const close = useCallback(() => { clearModal(); }, [clearModal]); const onSubmit = useCallback(() => { - clearModal(); - }, [clearModal]); + if (!pool || !dataModal?.token) { + return null; + } + const startUTCDate = Date.UTC(startDate.year, startDate.month - 1, startDate.date, 0, 0, 0, 0); + const startTime = new Date(startUTCDate).getTime(); + const endTime = startTime + period * DAY_TIME; + + return poolRepository.createExternalIncentive({ + poolPath: pool.path, + rewardToken: dataModal.token, + rewardAmount: dataModal.amount || "0", + startTime, + endTime + }).then(response => { + clearModal(); + router.back(); + return response; + }); + }, [clearModal, dataModal, period, pool, poolRepository, router, startDate.date, startDate.month, startDate.year]); return ; }; diff --git a/packages/web/src/containers/pool-incentivize-container/PoolIncentivizeContainer.tsx b/packages/web/src/containers/pool-incentivize-container/PoolIncentivizeContainer.tsx index 24e10ddfd..2bb0efaf2 100644 --- a/packages/web/src/containers/pool-incentivize-container/PoolIncentivizeContainer.tsx +++ b/packages/web/src/containers/pool-incentivize-container/PoolIncentivizeContainer.tsx @@ -32,7 +32,7 @@ const PoolIncentivizeContainer: React.FC = () => { const tokenAmountInput = useTokenAmountInput(token); const { updateTokenPrices } = useTokenData(); const { data: pools = [] } = useGetPoolList({ enabled: false }); - + useEffect(() => { setDataModal(tokenAmountInput); }, [tokenAmountInput.amount, token]); @@ -57,7 +57,7 @@ const PoolIncentivizeContainer: React.FC = () => { if (pool) { setCurrentPool(pool); } - }, [setCurrentPool]); + }, [pools, setCurrentPool]); const selectToken = useCallback((path: string) => { const token = tokenBalances.find(token => token.path === path); diff --git a/packages/web/src/repositories/pool/pool-repository-impl.ts b/packages/web/src/repositories/pool/pool-repository-impl.ts index 7267c4bcc..e337af6d7 100644 --- a/packages/web/src/repositories/pool/pool-repository-impl.ts +++ b/packages/web/src/repositories/pool/pool-repository-impl.ts @@ -32,6 +32,9 @@ import { makeRawTokenAmount } from "@utils/token-utils"; import { tickToSqrtPriceX96 } from "@gnoswap-labs/swap-router"; import { PoolDetailModel } from "@models/pool/pool-detail-model"; import { makeDepositMessage } from "@common/clients/wallet-client/transaction-messages/token"; +import { CreateExternalIncentiveRequest } from "./request/create-external-incentive-request"; +import { RemoveExternalIncentiveRequest } from "./request/remove-external-incentive-request"; +import { makeCreateIncentiveMessage, makeRemoveIncentiveMessage, makeStakerApproveMessage } from "@common/clients/wallet-client/transaction-messages/pool"; const POOL_PATH = process.env.NEXT_PUBLIC_PACKAGE_POOL_PATH || ""; const POSITION_PATH = process.env.NEXT_PUBLIC_PACKAGE_POSITION_PATH || ""; @@ -264,6 +267,92 @@ export class PoolRepositoryImpl implements PoolRepository { return response.hash; }; + getPoolDetailByPath = async ( + poolPath: string, + ): Promise => { + const response = await this.networkClient.get({ + url: "/pool_details/" + poolPath, + }); + return response.data; + }; + + createExternalIncentive = async (request: CreateExternalIncentiveRequest): Promise => { + if (this.walletClient === null) { + throw new CommonError("FAILED_INITIALIZE_WALLET"); + } + const account = await this.walletClient.getAccount(); + if (!account.data ) { + throw new CommonError("FAILED_INITIALIZE_PROVIDER"); + } + const { address } = account.data; + const { + poolPath, + rewardToken, + rewardAmount, + startTime, + endTime + } = request; + + const rewardAmountRaw = makeRawTokenAmount(rewardToken, rewardAmount) || "0"; + + const messages = []; + let tokenPath = rewardToken.path; + if (isNativeToken(rewardToken)) { + tokenPath = rewardToken.wrappedPath; + messages.push( + makeDepositMessage(tokenPath, rewardAmountRaw, "ugnot", address), + ); + } + messages.push(makeStakerApproveMessage(tokenPath, rewardAmountRaw, address)); + messages.push(makeCreateIncentiveMessage(poolPath, tokenPath, rewardAmountRaw, startTime, endTime, address)); + + const response = await this.walletClient.sendTransaction({ + messages, + gasWanted: 2000000, + gasFee: 1, + memo: "", + }); + if (response.code !== 0 || !response.data) { + throw new PoolError("FAILED_TO_CREATE_INCENTIVE"); + } + const data = response?.data as SendTransactionSuccessResponse; + return data?.hash || null; + }; + + removeExternalIncentive = async (request: RemoveExternalIncentiveRequest): Promise => { + if (this.walletClient === null) { + throw new CommonError("FAILED_INITIALIZE_WALLET"); + } + const account = await this.walletClient.getAccount(); + if (!account.data ) { + throw new CommonError("FAILED_INITIALIZE_PROVIDER"); + } + const { address } = account.data; + const { + poolPath, + rewardToken + } = request; + + const messages = []; + let tokenPath = rewardToken.path; + if (isNativeToken(rewardToken)) { + tokenPath = rewardToken.wrappedPath; + } + messages.push(makeRemoveIncentiveMessage(poolPath, tokenPath, address)); + + const response = await this.walletClient.sendTransaction({ + messages, + gasWanted: 2000000, + gasFee: 1, + memo: "", + }); + if (response.code !== 0 || !response.data) { + throw new PoolError("FAILED_TO_CREATE_INCENTIVE"); + } + const data = response?.data as SendTransactionSuccessResponse; + return data?.hash || null; + }; + private static makeCreatePoolMessage( tokenA: TokenModel, tokenB: TokenModel, @@ -271,8 +360,14 @@ export class PoolRepositoryImpl implements PoolRepository { startPrice: string, caller: string, ) { - const tokenAPath = tokenA.priceId; - const tokenBPath = tokenB.priceId; + let tokenAPath = tokenA.path; + let tokenBPath = tokenB.path; + if (isNativeToken(tokenA) ) { + tokenAPath = tokenA.wrappedPath; + } + if (isNativeToken(tokenB) ) { + tokenBPath = tokenB.wrappedPath; + } const fee = `${SwapFeeTierInfoMap[feeTier].fee}`; const startPriceSqrt = tickToSqrtPriceX96(priceToNearTick(Number(startPrice), SwapFeeTierInfoMap[feeTier].tickSpacing)); @@ -314,19 +409,18 @@ export class PoolRepositoryImpl implements PoolRepository { slippage: string, caller: string, ) { - const tokenAPath = tokenA.priceId; - const tokenBPath = tokenB.priceId; const fee = `${SwapFeeTierInfoMap[feeTier].fee}`; const slippageRatio = 0; const deadline = "7282571140"; - const sendItems = []; - if (tokenA.type === "native" && BigNumber(tokenAAmount).isGreaterThan(0) ) { - sendItems.push(`${tokenAAmount}ugnot`); + let tokenAPath = tokenA.path; + let tokenBPath = tokenB.path; + if (isNativeToken(tokenA) ) { + tokenAPath = tokenA.wrappedPath; } - if (tokenB.type === "native" && BigNumber(tokenAAmount).isGreaterThan(0)) { - sendItems.push(`${tokenBAmount}ugnot`); + if (isNativeToken(tokenB) ) { + tokenBPath = tokenB.wrappedPath; } - const sendAmount = sendItems.join(","); + const sendAmount = ""; return { caller, send: sendAmount, @@ -346,13 +440,4 @@ export class PoolRepositoryImpl implements PoolRepository { ], }; } - - getPoolDetailByPath = async ( - poolPath: string, - ): Promise => { - const response = await this.networkClient.get({ - url: "/pool_details/" + poolPath, - }); - return response.data; - }; } diff --git a/packages/web/src/repositories/pool/pool-repository-mock.ts b/packages/web/src/repositories/pool/pool-repository-mock.ts index bea555a0b..24c58ad2d 100644 --- a/packages/web/src/repositories/pool/pool-repository-mock.ts +++ b/packages/web/src/repositories/pool/pool-repository-mock.ts @@ -34,9 +34,19 @@ export class PoolRepositoryMock implements PoolRepository { return "hash"; }; - getPoolDetailByPath = async (poolPath: string): Promise => { + getPoolDetailByPath = async ( + poolPath: string, + ): Promise => { console.log(poolPath); - + return PoolDetailDataByPath as IPoolDetailResponse; }; + + createExternalIncentive = async (): Promise => { + return "hash"; + }; + + removeExternalIncentive = async (): Promise => { + return "hash"; + }; } diff --git a/packages/web/src/repositories/pool/pool-repository.ts b/packages/web/src/repositories/pool/pool-repository.ts index 0111ffad0..a5e40bdfe 100644 --- a/packages/web/src/repositories/pool/pool-repository.ts +++ b/packages/web/src/repositories/pool/pool-repository.ts @@ -4,6 +4,8 @@ import { IPoolDetailResponse, PoolModel } from "@models/pool/pool-model"; import { PoolRPCModel } from "@models/pool/pool-rpc-model"; import { AddLiquidityRequest } from "./request/add-liquidity-request"; import { CreatePoolRequest } from "./request/create-pool-request"; +import { CreateExternalIncentiveRequest } from "./request/create-external-incentive-request"; +import { RemoveExternalIncentiveRequest } from "./request/remove-external-incentive-request"; export interface PoolRepository { getPools: () => Promise; @@ -20,4 +22,11 @@ export interface PoolRepository { getPoolDetailByPath: (poolPath: string) => Promise; + createExternalIncentive: ( + request: CreateExternalIncentiveRequest, + ) => Promise; + + removeExternalIncentive: ( + request: RemoveExternalIncentiveRequest, + ) => Promise; } diff --git a/packages/web/src/repositories/pool/request/create-external-incentive-request.ts b/packages/web/src/repositories/pool/request/create-external-incentive-request.ts new file mode 100644 index 000000000..2c960375b --- /dev/null +++ b/packages/web/src/repositories/pool/request/create-external-incentive-request.ts @@ -0,0 +1,13 @@ +import { TokenModel } from "@models/token/token-model"; + +export interface CreateExternalIncentiveRequest { + poolPath: string; + + rewardToken: TokenModel; + + rewardAmount: string; + + startTime: number; + + endTime: number; +} diff --git a/packages/web/src/repositories/pool/request/remove-external-incentive-request.ts b/packages/web/src/repositories/pool/request/remove-external-incentive-request.ts new file mode 100644 index 000000000..6dee84286 --- /dev/null +++ b/packages/web/src/repositories/pool/request/remove-external-incentive-request.ts @@ -0,0 +1,7 @@ +import { TokenModel } from "@models/token/token-model"; + +export interface RemoveExternalIncentiveRequest { + poolPath: string; + + rewardToken: TokenModel; +} From e780f4ca37a7e2a6648a3589e1b5bf2af6ec4ddc Mon Sep 17 00:00:00 2001 From: jinoosss Date: Fri, 15 Dec 2023 19:15:59 +0900 Subject: [PATCH 2/2] feat: [GSW-695] Integrate Create Incentive --- .../IncentivizePoolModalContainer.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/web/src/containers/incentivize-pool-modal-container/IncentivizePoolModalContainer.tsx b/packages/web/src/containers/incentivize-pool-modal-container/IncentivizePoolModalContainer.tsx index 0e9ee0151..f2bbd99fd 100644 --- a/packages/web/src/containers/incentivize-pool-modal-container/IncentivizePoolModalContainer.tsx +++ b/packages/web/src/containers/incentivize-pool-modal-container/IncentivizePoolModalContainer.tsx @@ -39,6 +39,9 @@ const IncentivizePoolModalContainer = () => { clearModal(); router.back(); return response; + }).catch(e => { + console.log(e); + return null; }); }, [clearModal, dataModal, period, pool, poolRepository, router, startDate.date, startDate.month, startDate.year]);