From 031c92c34bf38830d789f661145a903e0f4cd485 Mon Sep 17 00:00:00 2001 From: osmannyildiz Date: Sun, 26 May 2024 14:42:59 +0300 Subject: [PATCH] achieve a whole new level of insanity by imitating a blockchain on the db --- backend/chain_listener_entrypoint.ts | 0 backend/src/api/controllers/campaigns.ts | 18 +-- backend/src/api/controllers/investors.ts | 29 +++++ backend/src/api/controllers/test.ts | 105 +++++++++++++++++ backend/src/api/index.ts | 2 + backend/src/api/routes/investors.ts | 6 + backend/src/config.ts | 2 +- backend/src/db/admin.ts | 14 +++ backend/src/db/campaign.ts | 17 ++- backend/src/db/index.ts | 4 + backend/src/db/investor.ts | 19 +++ backend/src/db/token.ts | 30 +++++ backend/src/db/tokenBalance.ts | 29 +++++ backend/src/services/CampaignService.ts | 22 ++++ backend/src/services/InvestorService.ts | 50 ++++++++ backend/src/services/TokenBalanceService.ts | 78 +++++++++++++ backend/src/services/TokenService.ts | 27 +++++ backend/src/utils/db.ts | 7 ++ evm/contracts/PADTokenStake.sol | 67 +++++++---- frontend/.eslintrc.cjs | 1 + .../src/components/ConnectMetamaskButton.tsx | 9 +- frontend/src/contexts/MainContext.ts | 61 ++++++---- .../evm-output/PADTokenStake.artifacts.json | 82 +++++++++++-- .../src/evm-output/deployed_addresses.json | 6 +- frontend/src/query/fetchers/investors.ts | 21 ++++ frontend/src/routes/pad-token-stake.lazy.tsx | 109 +++++++++++++++++- frontend/src/utils/toast.ts | 11 +- frontend/tsconfig.json | 3 +- 28 files changed, 756 insertions(+), 73 deletions(-) create mode 100644 backend/chain_listener_entrypoint.ts create mode 100644 backend/src/api/controllers/investors.ts create mode 100644 backend/src/api/routes/investors.ts create mode 100644 backend/src/db/admin.ts create mode 100644 backend/src/db/investor.ts create mode 100644 backend/src/db/token.ts create mode 100644 backend/src/db/tokenBalance.ts create mode 100644 backend/src/services/CampaignService.ts create mode 100644 backend/src/services/InvestorService.ts create mode 100644 backend/src/services/TokenBalanceService.ts create mode 100644 backend/src/services/TokenService.ts create mode 100644 backend/src/utils/db.ts create mode 100644 frontend/src/query/fetchers/investors.ts diff --git a/backend/chain_listener_entrypoint.ts b/backend/chain_listener_entrypoint.ts new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/api/controllers/campaigns.ts b/backend/src/api/controllers/campaigns.ts index 9a01c8d..e9b0cfa 100644 --- a/backend/src/api/controllers/campaigns.ts +++ b/backend/src/api/controllers/campaigns.ts @@ -1,9 +1,10 @@ import { RequestHandler } from "express"; -import { Campaign } from "../../db/campaign"; +import { CampaignService } from "../../services/CampaignService"; export const getMany: RequestHandler = async (req, res, next) => { try { - const campaigns = await Campaign.find(); + const campaignService = new CampaignService(); + const campaigns = await campaignService.getAll(); return res.json({ ok: true, @@ -16,16 +17,17 @@ export const getMany: RequestHandler = async (req, res, next) => { export const create: RequestHandler = async (req, res, next) => { try { - const { name, contractAddress } = req.body; - if (!name || !contractAddress) { + const { name, tokenId, maxDistributionAmount } = req.body; + if (!name || !tokenId || !maxDistributionAmount) { throw new Error("Missing field(s)."); } - const campaign = new Campaign({ - name: req.body.name, - contractAddress: req.body.contractAddress, + const campaignService = new CampaignService(); + const campaign = await campaignService.create({ + name, + tokenId, + maxDistributionAmount, }); - await campaign.save(); return res.json({ ok: true, diff --git a/backend/src/api/controllers/investors.ts b/backend/src/api/controllers/investors.ts new file mode 100644 index 0000000..abeb080 --- /dev/null +++ b/backend/src/api/controllers/investors.ts @@ -0,0 +1,29 @@ +import { RequestHandler } from "express"; +import { InvestorService } from "../../services/InvestorService"; + +export const getBalances: RequestHandler = async (req, res, next) => { + try { + const { walletAddress } = req.params; + if (!walletAddress) { + throw new Error("Missing field(s)."); + } + + const investorService = new InvestorService(); + const investor = await investorService.getByWalletAddress( + walletAddress.toLowerCase() + ); + if (!investor) { + throw new Error("Investor not found."); + } + const balances = await investorService.getBalances( + investor._id.toHexString() + ); + + return res.json({ + ok: true, + data: balances, + }); + } catch (error) { + return next(error); + } +}; diff --git a/backend/src/api/controllers/test.ts b/backend/src/api/controllers/test.ts index 52503b4..0313f82 100644 --- a/backend/src/api/controllers/test.ts +++ b/backend/src/api/controllers/test.ts @@ -1,7 +1,112 @@ import { RequestHandler } from "express"; +import { TokenService } from "../../services/TokenService"; +import { InvestorService } from "./../../services/InvestorService"; +import { TokenBalanceService } from "./../../services/TokenBalanceService"; export const getIndex: RequestHandler = async (req, res) => { + // await initTokens(); + // await initInvestors(); + // await testBalances1(); + // await testBalances2(); + // await testBalances3(); + return res.json({ message: "It works!", }); }; + +async function initTokens() { + const tokenService = new TokenService(); + tokenService.create({ name: "PAD Token", symbol: "PAD" }); + tokenService.create({ name: "Kitty Token", symbol: "KITTY" }); + tokenService.create({ name: "Doggo Token", symbol: "DOGGO" }); + tokenService.create({ name: "Birb Token", symbol: "BIRB" }); +} + +async function initInvestors() { + const investorService = new InvestorService(); + await investorService.createIfNotExists({ + walletAddress: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".toLowerCase(), + }); + await investorService.createIfNotExists({ + walletAddress: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".toLowerCase(), + }); +} + +async function testBalances1() { + const investorService = new InvestorService(); + const tokenService = new TokenService(); + const tokenBalanceService = new TokenBalanceService(); + + const investor1 = await investorService.getByWalletAddress( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".toLowerCase() + ); + const investor2 = await investorService.getByWalletAddress( + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".toLowerCase() + ); + const padToken = await tokenService.getBySymbol("PAD"); + if (!investor1 || !investor2 || !padToken) { + throw new Error("error 1"); + } + + await tokenBalanceService.mint( + padToken._id.toHexString(), + investor1._id.toHexString(), + 100e18 + ); + await tokenBalanceService.mint( + padToken._id.toHexString(), + investor2._id.toHexString(), + 200e18 + ); + await tokenBalanceService.burn( + padToken._id.toHexString(), + investor2._id.toHexString(), + 50e18 + ); +} + +async function testBalances2() { + const investorService = new InvestorService(); + const tokenService = new TokenService(); + const tokenBalanceService = new TokenBalanceService(); + + const investor1 = await investorService.getByWalletAddress( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".toLowerCase() + ); + const investor2 = await investorService.getByWalletAddress( + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".toLowerCase() + ); + const kittyToken = await tokenService.getBySymbol("KITTY"); + if (!investor1 || !investor2 || !kittyToken) { + throw new Error("error 1"); + } + + await tokenBalanceService.mint( + kittyToken._id.toHexString(), + investor1._id.toHexString(), + 100e18 + ); + await tokenBalanceService.transfer( + kittyToken._id.toHexString(), + investor1._id.toHexString(), + investor2._id.toHexString(), + 6.66e18 + ); +} + +async function testBalances3() { + const investorService = new InvestorService(); + + const investor1 = await investorService.getByWalletAddress( + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".toLowerCase() + ); + if (!investor1) { + throw new Error("error 1"); + } + + const balances = await investorService.getBalances( + investor1._id.toHexString() + ); + console.log("==== hey", balances); +} diff --git a/backend/src/api/index.ts b/backend/src/api/index.ts index 15024a1..1a0cb23 100644 --- a/backend/src/api/index.ts +++ b/backend/src/api/index.ts @@ -1,6 +1,7 @@ import cors from "cors"; import express, { ErrorRequestHandler } from "express"; import { campaignsRouter } from "./routes/campaigns"; +import { investorsRouter } from "./routes/investors"; import { testRouter } from "./routes/test"; export const api = express(); @@ -12,6 +13,7 @@ api.use(express.json()); // Routes api.use("/test", testRouter); api.use("/campaigns", campaignsRouter); +api.use("/investors", investorsRouter); // Error Handler const errorHandler: ErrorRequestHandler = (error, req, res, next) => { diff --git a/backend/src/api/routes/investors.ts b/backend/src/api/routes/investors.ts new file mode 100644 index 0000000..d8860c9 --- /dev/null +++ b/backend/src/api/routes/investors.ts @@ -0,0 +1,6 @@ +import express from "express"; +import * as controllers from "../controllers/investors"; + +export const investorsRouter = express.Router(); + +investorsRouter.route("/:walletAddress/balances").get(controllers.getBalances); diff --git a/backend/src/config.ts b/backend/src/config.ts index b762b65..cbe7cb0 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -1,7 +1,7 @@ import { SECRETS } from "./secrets"; export const CONFIG = { + DEBUG: true, API_PORT: 7231, MONGODB_URI: SECRETS.MONGODB_URI, - MONGODB_DB_NAME: "osipad", }; diff --git a/backend/src/db/admin.ts b/backend/src/db/admin.ts new file mode 100644 index 0000000..f97df8c --- /dev/null +++ b/backend/src/db/admin.ts @@ -0,0 +1,14 @@ +import mongoose from "mongoose"; + +export interface IAdmin { + walletAddress: string; +} + +const adminSchema = new mongoose.Schema({ + walletAddress: { + type: String, + required: true, + }, +}); + +export const Admin = mongoose.model("Admin", adminSchema); diff --git a/backend/src/db/campaign.ts b/backend/src/db/campaign.ts index 1795c75..0ddb953 100644 --- a/backend/src/db/campaign.ts +++ b/backend/src/db/campaign.ts @@ -1,8 +1,10 @@ -import mongoose from "mongoose"; +import mongoose, { Schema } from "mongoose"; -interface ICampaign { +export interface ICampaign { name: string; - contractAddress: string; + tokenId: mongoose.Types.ObjectId; + maxDistributionAmount: number; + // contractAddress: string; } const campaignSchema = new mongoose.Schema({ @@ -10,8 +12,13 @@ const campaignSchema = new mongoose.Schema({ type: String, required: true, }, - contractAddress: { - type: String, + tokenId: { + type: Schema.Types.ObjectId, + ref: "Token", + required: true, + }, + maxDistributionAmount: { + type: Number, required: true, }, }); diff --git a/backend/src/db/index.ts b/backend/src/db/index.ts index 369d6d4..5ed168c 100644 --- a/backend/src/db/index.ts +++ b/backend/src/db/index.ts @@ -5,6 +5,10 @@ export async function connectToDb() { try { await mongoose.connect(CONFIG.MONGODB_URI); console.log("✅ Connected to MongoDB."); + + if (CONFIG.DEBUG) { + mongoose.set("debug", true); + } } catch (error) { console.error("❌ Couldn't connect to MongoDB.", error); } diff --git a/backend/src/db/investor.ts b/backend/src/db/investor.ts new file mode 100644 index 0000000..e797964 --- /dev/null +++ b/backend/src/db/investor.ts @@ -0,0 +1,19 @@ +import mongoose from "mongoose"; + +export interface IInvestor { + walletAddress: string; + ethBalance: number; +} + +const investorSchema = new mongoose.Schema({ + walletAddress: { + type: String, + required: true, + }, + ethBalance: { + type: Number, + required: true, + }, +}); + +export const Investor = mongoose.model("Investor", investorSchema); diff --git a/backend/src/db/token.ts b/backend/src/db/token.ts new file mode 100644 index 0000000..4321688 --- /dev/null +++ b/backend/src/db/token.ts @@ -0,0 +1,30 @@ +import mongoose from "mongoose"; + +export interface IToken { + name: string; + symbol: string; + // decimals: number; + totalSupply: number; + // contractAddress: string; +} + +const tokenSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + }, + symbol: { + type: String, + required: true, + }, + // decimals: { + // type: Number, + // required: true, + // }, + totalSupply: { + type: Number, + required: true, + }, +}); + +export const Token = mongoose.model("Token", tokenSchema); diff --git a/backend/src/db/tokenBalance.ts b/backend/src/db/tokenBalance.ts new file mode 100644 index 0000000..c39b092 --- /dev/null +++ b/backend/src/db/tokenBalance.ts @@ -0,0 +1,29 @@ +import mongoose, { Schema } from "mongoose"; + +export interface ITokenBalance { + investorId: mongoose.Types.ObjectId; + tokenId: mongoose.Types.ObjectId; + amount: number; +} + +const tokenBalanceSchema = new mongoose.Schema({ + investorId: { + type: Schema.Types.ObjectId, + ref: "Investor", + required: true, + }, + tokenId: { + type: Schema.Types.ObjectId, + ref: "Token", + required: true, + }, + amount: { + type: Number, + required: true, + }, +}); + +export const TokenBalance = mongoose.model( + "TokenBalance", + tokenBalanceSchema +); diff --git a/backend/src/services/CampaignService.ts b/backend/src/services/CampaignService.ts new file mode 100644 index 0000000..af6af40 --- /dev/null +++ b/backend/src/services/CampaignService.ts @@ -0,0 +1,22 @@ +import { Campaign, ICampaign } from "../db/campaign"; + +type ICreateCampaignPayload = Pick< + ICampaign, + "name" | "tokenId" | "maxDistributionAmount" +> & {}; + +export class CampaignService { + async getAll() { + return await Campaign.find(); + } + + async create(payload: ICreateCampaignPayload) { + const campaign = new Campaign({ + name: payload.name, + tokenId: payload.tokenId, + maxDistributionAmount: payload.maxDistributionAmount, + }); + await campaign.save(); + return campaign; + } +} diff --git a/backend/src/services/InvestorService.ts b/backend/src/services/InvestorService.ts new file mode 100644 index 0000000..22d1687 --- /dev/null +++ b/backend/src/services/InvestorService.ts @@ -0,0 +1,50 @@ +import { IInvestor, Investor } from "../db/investor"; +import { IToken } from "../db/token"; +import { TokenBalance } from "../db/tokenBalance"; + +type ICreateInvestorPayload = Pick & {}; + +export class InvestorService { + async getAll() { + return await Investor.find(); + } + + async getById(id: string) { + return await Investor.findOne({ _id: id }); + } + + async getByWalletAddress(walletAddress: string) { + return await Investor.findOne({ walletAddress }); + } + + async create(payload: ICreateInvestorPayload) { + const investor = new Investor({ + walletAddress: payload.walletAddress, + ethBalance: 0, + }); + await investor.save(); + return investor; + } + + async createIfNotExists(payload: ICreateInvestorPayload) { + const existingInvestor = await Investor.findOne({ + walletAddress: payload.walletAddress, + }); + if (existingInvestor) { + return { data: existingInvestor, created: false }; + } + + const investor = await this.create(payload); + return { data: investor, created: true }; + } + + async getBalances(investorId: string) { + const results = await TokenBalance.find({ investorId }).populate("tokenId"); + const balances: Record = {}; + for (const result of results) { + const symbol = (result.tokenId as unknown as IToken).symbol; + balances[symbol] = result.amount; + } + return balances; + } +} diff --git a/backend/src/services/TokenBalanceService.ts b/backend/src/services/TokenBalanceService.ts new file mode 100644 index 0000000..5b947ea --- /dev/null +++ b/backend/src/services/TokenBalanceService.ts @@ -0,0 +1,78 @@ +import { ITokenBalance, TokenBalance } from "../db/tokenBalance"; +import { hexStringToObjectId } from "../utils/db"; + +type ICreateTokenBalancePayload = Pick< + ITokenBalance, + "investorId" | "tokenId" +> & {}; + +export class TokenBalanceService { + async create(payload: ICreateTokenBalancePayload) { + const tokenBalance = new TokenBalance({ + investorId: payload.investorId, + tokenId: payload.tokenId, + amount: 0, + }); + await tokenBalance.save(); + return tokenBalance; + } + + async createIfNotExists(payload: ICreateTokenBalancePayload) { + const existingTokenBalance = await TokenBalance.findOne({ + investorId: payload.investorId, + tokenId: payload.tokenId, + }); + if (existingTokenBalance) { + return { data: existingTokenBalance, created: false }; + } + + const tokenBalance = await this.create(payload); + return { data: tokenBalance, created: true }; + } + + async mint(tokenId: string, toInvestorId: string, amount: number) { + const tokenBalance = ( + await this.createIfNotExists({ + investorId: hexStringToObjectId(toInvestorId), + tokenId: hexStringToObjectId(tokenId), + }) + ).data; + tokenBalance.amount += amount; + await tokenBalance.save(); + } + + async burn(tokenId: string, fromInvestorId: string, amount: number) { + const tokenBalance = ( + await this.createIfNotExists({ + investorId: hexStringToObjectId(fromInvestorId), + tokenId: hexStringToObjectId(tokenId), + }) + ).data; + tokenBalance.amount -= amount; + await tokenBalance.save(); + } + + async transfer( + tokenId: string, + fromInvestorId: string, + toInvestorId: string, + amount: number + ) { + const fromTokenBalance = ( + await this.createIfNotExists({ + investorId: hexStringToObjectId(fromInvestorId), + tokenId: hexStringToObjectId(tokenId), + }) + ).data; + const toTokenBalance = ( + await this.createIfNotExists({ + investorId: hexStringToObjectId(toInvestorId), + tokenId: hexStringToObjectId(tokenId), + }) + ).data; + fromTokenBalance.amount -= amount; + toTokenBalance.amount += amount; + await fromTokenBalance.save(); + await toTokenBalance.save(); + } +} diff --git a/backend/src/services/TokenService.ts b/backend/src/services/TokenService.ts new file mode 100644 index 0000000..7245744 --- /dev/null +++ b/backend/src/services/TokenService.ts @@ -0,0 +1,27 @@ +import { IToken, Token } from "../db/token"; + +type ICreateTokenPayload = Pick & {}; + +export class TokenService { + async getAll() { + return await Token.find(); + } + + async getById(id: string) { + return await Token.findOne({ _id: id }); + } + + async getBySymbol(symbol: string) { + return await Token.findOne({ symbol }); + } + + async create(payload: ICreateTokenPayload) { + const token = new Token({ + name: payload.name, + symbol: payload.symbol, + totalSupply: 0, + }); + await token.save(); + return token; + } +} diff --git a/backend/src/utils/db.ts b/backend/src/utils/db.ts new file mode 100644 index 0000000..ee71213 --- /dev/null +++ b/backend/src/utils/db.ts @@ -0,0 +1,7 @@ +import mongoose from "mongoose"; + +export function hexStringToObjectId( + hexString: string +): mongoose.Types.ObjectId { + return new mongoose.Types.ObjectId(hexString); +} diff --git a/evm/contracts/PADTokenStake.sol b/evm/contracts/PADTokenStake.sol index a033568..3f28ed6 100644 --- a/evm/contracts/PADTokenStake.sol +++ b/evm/contracts/PADTokenStake.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; +import "hardhat/console.sol"; import "./PADToken.sol"; contract PADTokenStake { @@ -16,6 +17,7 @@ contract PADTokenStake { uint id; uint lockdownDays; uint rewardPercentage; + uint lockedAmount; } struct Deposit { @@ -27,6 +29,8 @@ contract PADTokenStake { uint releasedAt; } + event Staked(address indexed staker, uint indexed poolId, uint amount, uint lockedAt); + constructor(PADToken _padToken) { padToken = _padToken; _initPools(); @@ -36,44 +40,65 @@ contract PADTokenStake { pools[0] = Pool({ id: nextPoolId, lockdownDays: 30, - rewardPercentage: 25 + rewardPercentage: 25, + lockedAmount: 0 }); nextPoolId++; pools[1] = Pool({ id: nextPoolId, lockdownDays: 60, - rewardPercentage: 50 + rewardPercentage: 50, + lockedAmount: 0 }); nextPoolId++; } - function stake(uint poolId, uint amount) external { - Pool memory pool = pools[poolId]; - Deposit memory newDeposit = Deposit({ - id: nextDepositId, - staker: msg.sender, - poolId: poolId, - amount: amount, - lockedAt: block.timestamp, - releasedAt: block.timestamp + (pool.lockdownDays * 1 days) - }); - deposits[msg.sender].push(newDeposit); + function getPools() public view returns (Pool[] memory _pools) { + _pools = new Pool[](nextPoolId); + for (uint i = 0; i < nextPoolId; i++) { + _pools[i] = pools[i]; + } } - function getDeposit(uint depositId) public view returns (Deposit memory) { + function stake(uint _poolId, uint _amount) external { + address staker = msg.sender; + uint lockedAt = block.timestamp; + Pool storage pool = pools[_poolId]; + + padToken.transferFrom(staker, address(this), _amount); + + Deposit memory newDeposit = Deposit({ + id: nextDepositId, + staker: staker, + poolId: _poolId, + amount: _amount, + lockedAt: lockedAt, + releasedAt: lockedAt + (pool.lockdownDays * 1 days) + }); + deposits[staker].push(newDeposit); + + console.log("hey1"); + pool.lockedAmount += _amount; + console.log("hey2"); + + emit Staked(staker, _poolId, _amount, lockedAt); + console.log(staker, _poolId, _amount, lockedAt); + } + + function getDeposit(uint _depositId) public view returns (Deposit memory) { Deposit memory deposit; for (uint i = 0; i < deposits[msg.sender].length; i++) { deposit = deposits[msg.sender][i]; - if (deposit.id == depositId) { + if (deposit.id == _depositId) { return deposit; } } revert("Deposit with the given ID not found."); } - function getWithdrawPenalty(uint depositId) public view returns (uint) { - Deposit memory deposit = getDeposit(depositId); + function getWithdrawPenalty(uint _depositId) public view returns (uint) { + Deposit memory deposit = getDeposit(_depositId); uint remainingMs = deposit.releasedAt - block.timestamp; if (remainingMs <= 0) { @@ -84,13 +109,13 @@ contract PADTokenStake { return 100; } - function withdraw(uint depositId, bool acceptPenaltyCut) external { - uint penalty = getWithdrawPenalty(depositId); + function withdraw(uint _depositId, bool _acceptPenaltyCut) external { + uint penalty = getWithdrawPenalty(_depositId); if (penalty > 0) { - require(acceptPenaltyCut, "You should accept the penalty cut."); + require(_acceptPenaltyCut, "You should accept the penalty cut."); } - Deposit memory deposit = getDeposit(depositId); + Deposit memory deposit = getDeposit(_depositId); Pool memory pool = pools[deposit.poolId]; uint amountToTransfer = (deposit.amount * pool.rewardPercentage / 100) - penalty; padToken.transfer(deposit.staker, amountToTransfer); diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index c5330af..6d58d0b 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -16,5 +16,6 @@ module.exports = { ], // My additions "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/no-unused-vars": "off", // TODO Remove later }, }; diff --git a/frontend/src/components/ConnectMetamaskButton.tsx b/frontend/src/components/ConnectMetamaskButton.tsx index d34e981..9c1e469 100644 --- a/frontend/src/components/ConnectMetamaskButton.tsx +++ b/frontend/src/components/ConnectMetamaskButton.tsx @@ -1,3 +1,5 @@ +import { useQuery } from "@tanstack/react-query"; +import { fetchBalances } from "../query/fetchers/investors"; import { useStore } from "../store"; import { tryWithToast } from "../utils/toast"; import { formatPadTokenAmount, shortenAddress } from "../utils/ui"; @@ -9,7 +11,12 @@ export function ConnectMetamaskButton() { const unsetConnectedAccount = useStore( (state) => state.unsetConnectedAccount ); - const padTokenBalance = useStore((state) => state.padTokenBalance); + // const padTokenBalance = useStore((state) => state.padTokenBalance); + const balancesQuery = useQuery({ + queryKey: ["userBalances", connectedAccount], + queryFn: () => fetchBalances(connectedAccount), + }); + const padTokenBalance = balancesQuery?.data?.PAD || null; // https://docs.web3js.org/guides/getting_started/metamask/#react-app async function connectMetamask() { diff --git a/frontend/src/contexts/MainContext.ts b/frontend/src/contexts/MainContext.ts index 927d5b5..6fe28a2 100644 --- a/frontend/src/contexts/MainContext.ts +++ b/frontend/src/contexts/MainContext.ts @@ -1,39 +1,58 @@ /* eslint-disable react-hooks/rules-of-hooks */ -import { useEffect } from "react"; +// import { useEffect } from "react"; import { useStore } from "../store"; import buildContext from "../utils/context"; -import { padToken } from "../web3"; +// import { padToken } from "../web3"; interface IMainContext { - fetchPadTokenBalance: () => Promise; + // fetchPadTokenBalance: () => Promise; } export const [MainContext, useMainContext] = buildContext(); export function buildMainContextValue(): IMainContext { const connectedAccount = useStore((state) => state.connectedAccount); - const setPadTokenBalance = useStore((state) => state.setPadTokenBalance); - const unsetPadTokenBalance = useStore((state) => state.unsetPadTokenBalance); + // const setPadTokenBalance = useStore((state) => state.setPadTokenBalance); + // const unsetPadTokenBalance = useStore((state) => state.unsetPadTokenBalance); - const fetchPadTokenBalance = async () => { - const balance: bigint = await padToken.methods - .balanceOf(connectedAccount) - .call(); - if (typeof balance === "bigint") { - setPadTokenBalance(balance); - } else { - unsetPadTokenBalance(); - } - }; + // const fetchPadTokenBalance = async () => { + // const balance: bigint = await padToken.methods + // .balanceOf(connectedAccount) + // .call(); + // if (typeof balance === "bigint") { + // setPadTokenBalance(balance); + // } else { + // unsetPadTokenBalance(); + // } + // }; - useEffect(() => { - if (connectedAccount) { - fetchPadTokenBalance(); - } - }, [connectedAccount]); + // useEffect(() => { + // if (connectedAccount) { + // fetchPadTokenBalance(); + + // const subscription1 = padToken.events.Transfer({ + // filter: { to: connectedAccount }, + // }); + // subscription1.on("data", (event) => { + // fetchPadTokenBalance(); + // }); + + // const subscription2 = padToken.events.Transfer({ + // filter: { from: connectedAccount }, + // }); + // subscription2.on("data", (event) => { + // fetchPadTokenBalance(); + // }); + + // return () => { + // subscription1.unsubscribe(); + // subscription2.unsubscribe(); + // }; + // } + // }, [connectedAccount]); return { - fetchPadTokenBalance, + // fetchPadTokenBalance, }; } diff --git a/frontend/src/evm-output/PADTokenStake.artifacts.json b/frontend/src/evm-output/PADTokenStake.artifacts.json index 0a528ad..7c38b1e 100644 --- a/frontend/src/evm-output/PADTokenStake.artifacts.json +++ b/frontend/src/evm-output/PADTokenStake.artifacts.json @@ -14,11 +14,42 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "staker", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "poolId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lockedAt", + "type": "uint256" + } + ], + "name": "Staked", + "type": "event" + }, { "inputs": [ { "internalType": "uint256", - "name": "depositId", + "name": "_depositId", "type": "uint256" } ], @@ -65,11 +96,46 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getPools", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lockdownDays", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardPercentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lockedAmount", + "type": "uint256" + } + ], + "internalType": "struct PADTokenStake.Pool[]", + "name": "_pools", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { "internalType": "uint256", - "name": "depositId", + "name": "_depositId", "type": "uint256" } ], @@ -88,12 +154,12 @@ "inputs": [ { "internalType": "uint256", - "name": "poolId", + "name": "_poolId", "type": "uint256" }, { "internalType": "uint256", - "name": "amount", + "name": "_amount", "type": "uint256" } ], @@ -106,12 +172,12 @@ "inputs": [ { "internalType": "uint256", - "name": "depositId", + "name": "_depositId", "type": "uint256" }, { "internalType": "bool", - "name": "acceptPenaltyCut", + "name": "_acceptPenaltyCut", "type": "bool" } ], @@ -121,8 +187,8 @@ "type": "function" } ], - "bytecode": "0x60a0604052600060025560006003553480156200001b57600080fd5b5060405162000e7038038062000e708339818101604052810190620000419190620001e5565b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050620000856200008c60201b60201c565b506200029d565b60405180606001604052806002548152602001601e8152602001601981525060008080815260200190815260200160002060008201518160000155602082015181600101556040820151816002015590505060026000815480929190620000f39062000250565b919050555060405180606001604052806002548152602001603c815260200160328152506000806001815260200190815260200160002060008201518160000155602082015181600101556040820151816002015590505060026000815480929190620001609062000250565b9190505550565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600062000199826200016c565b9050919050565b6000620001ad826200018c565b9050919050565b620001bf81620001a0565b8114620001cb57600080fd5b50565b600081519050620001df81620001b4565b92915050565b600060208284031215620001fe57620001fd62000167565b5b60006200020e84828501620001ce565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000819050919050565b60006200025d8262000246565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820362000292576200029162000217565b5b600182019050919050565b608051610bb7620002b960003960006101c10152610bb76000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c806338d07436146100515780633a140b9d1461006d5780637b0472f01461009d5780639f9fb968146100b9575b600080fd5b61006b600480360381019061006691906106ad565b6100e9565b005b610087600480360381019061008291906106ed565b61026a565b6040516100949190610729565b60405180910390f35b6100b760048036038101906100b29190610744565b6102ab565b005b6100d360048036038101906100ce91906106ed565b61043a565b6040516100e0919061084f565b60405180910390f35b60006100f48361026a565b90506000811115610140578161013f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610136906108ed565b60405180910390fd5b5b600061014b8461043a565b9050600080600083604001518152602001908152602001600020604051806060016040529081600082015481526020016001820154815260200160028201548152505090506000836064836040015185606001516101a9919061093c565b6101b391906109ad565b6101bd91906109de565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8460200151836040518363ffffffff1660e01b815260040161021e929190610a21565b6020604051808303816000875af115801561023d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102619190610a5f565b50505050505050565b6000806102768361043a565b90506000428260a0015161028a91906109de565b90506000811161029f576000925050506102a6565b6064925050505b919050565b60008060008481526020019081526020016000206040518060600160405290816000820154815260200160018201548152602001600282015481525050905060006040518060c0016040528060035481526020013373ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001428152602001620151808460200151610340919061093c565b4261034b9190610a8c565b8152509050600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190806001815401808255809150506001900390600052602060002090600602016000909190919091506000820151816000015560208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020155606082015181600301556080820151816004015560a08201518160050155505050505050565b6104426105ee565b61044a6105ee565b60005b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805490508110156105ad57600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081815481106104e8576104e7610ac0565b5b90600052602060002090600602016040518060c0016040529081600082015481526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016002820154815260200160038201548152602001600482015481526020016005820154815250509150838260000151036105a05781925050506105e9565b808060010191505061044d565b506040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105e090610b61565b60405180910390fd5b919050565b6040518060c0016040528060008152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081526020016000815260200160008152602001600081525090565b600080fd5b6000819050919050565b6106528161063f565b811461065d57600080fd5b50565b60008135905061066f81610649565b92915050565b60008115159050919050565b61068a81610675565b811461069557600080fd5b50565b6000813590506106a781610681565b92915050565b600080604083850312156106c4576106c361063a565b5b60006106d285828601610660565b92505060206106e385828601610698565b9150509250929050565b6000602082840312156107035761070261063a565b5b600061071184828501610660565b91505092915050565b6107238161063f565b82525050565b600060208201905061073e600083018461071a565b92915050565b6000806040838503121561075b5761075a61063a565b5b600061076985828601610660565b925050602061077a85828601610660565b9150509250929050565b61078d8161063f565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107be82610793565b9050919050565b6107ce816107b3565b82525050565b60c0820160008201516107ea6000850182610784565b5060208201516107fd60208501826107c5565b5060408201516108106040850182610784565b5060608201516108236060850182610784565b5060808201516108366080850182610784565b5060a082015161084960a0850182610784565b50505050565b600060c08201905061086460008301846107d4565b92915050565b600082825260208201905092915050565b7f596f752073686f756c6420616363657074207468652070656e616c747920637560008201527f742e000000000000000000000000000000000000000000000000000000000000602082015250565b60006108d760228361086a565b91506108e28261087b565b604082019050919050565b60006020820190508181036000830152610906816108ca565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006109478261063f565b91506109528361063f565b92508282026109608161063f565b915082820484148315176109775761097661090d565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60006109b88261063f565b91506109c38361063f565b9250826109d3576109d261097e565b5b828204905092915050565b60006109e98261063f565b91506109f48361063f565b9250828203905081811115610a0c57610a0b61090d565b5b92915050565b610a1b816107b3565b82525050565b6000604082019050610a366000830185610a12565b610a43602083018461071a565b9392505050565b600081519050610a5981610681565b92915050565b600060208284031215610a7557610a7461063a565b5b6000610a8384828501610a4a565b91505092915050565b6000610a978261063f565b9150610aa28361063f565b9250828201905080821115610aba57610ab961090d565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4465706f73697420776974682074686520676976656e204944206e6f7420666f60008201527f756e642e00000000000000000000000000000000000000000000000000000000602082015250565b6000610b4b60248361086a565b9150610b5682610aef565b604082019050919050565b60006020820190508181036000830152610b7a81610b3e565b905091905056fea2646970667358221220bb7d26cd403bb136957890911eef51151abf24d7b5fd5e3c365f650206acdb1564736f6c63430008180033", - "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c806338d07436146100515780633a140b9d1461006d5780637b0472f01461009d5780639f9fb968146100b9575b600080fd5b61006b600480360381019061006691906106ad565b6100e9565b005b610087600480360381019061008291906106ed565b61026a565b6040516100949190610729565b60405180910390f35b6100b760048036038101906100b29190610744565b6102ab565b005b6100d360048036038101906100ce91906106ed565b61043a565b6040516100e0919061084f565b60405180910390f35b60006100f48361026a565b90506000811115610140578161013f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610136906108ed565b60405180910390fd5b5b600061014b8461043a565b9050600080600083604001518152602001908152602001600020604051806060016040529081600082015481526020016001820154815260200160028201548152505090506000836064836040015185606001516101a9919061093c565b6101b391906109ad565b6101bd91906109de565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8460200151836040518363ffffffff1660e01b815260040161021e929190610a21565b6020604051808303816000875af115801561023d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102619190610a5f565b50505050505050565b6000806102768361043a565b90506000428260a0015161028a91906109de565b90506000811161029f576000925050506102a6565b6064925050505b919050565b60008060008481526020019081526020016000206040518060600160405290816000820154815260200160018201548152602001600282015481525050905060006040518060c0016040528060035481526020013373ffffffffffffffffffffffffffffffffffffffff168152602001858152602001848152602001428152602001620151808460200151610340919061093c565b4261034b9190610a8c565b8152509050600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190806001815401808255809150506001900390600052602060002090600602016000909190919091506000820151816000015560208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020155606082015181600301556080820151816004015560a08201518160050155505050505050565b6104426105ee565b61044a6105ee565b60005b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020805490508110156105ad57600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081815481106104e8576104e7610ac0565b5b90600052602060002090600602016040518060c0016040529081600082015481526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016002820154815260200160038201548152602001600482015481526020016005820154815250509150838260000151036105a05781925050506105e9565b808060010191505061044d565b506040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105e090610b61565b60405180910390fd5b919050565b6040518060c0016040528060008152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081526020016000815260200160008152602001600081525090565b600080fd5b6000819050919050565b6106528161063f565b811461065d57600080fd5b50565b60008135905061066f81610649565b92915050565b60008115159050919050565b61068a81610675565b811461069557600080fd5b50565b6000813590506106a781610681565b92915050565b600080604083850312156106c4576106c361063a565b5b60006106d285828601610660565b92505060206106e385828601610698565b9150509250929050565b6000602082840312156107035761070261063a565b5b600061071184828501610660565b91505092915050565b6107238161063f565b82525050565b600060208201905061073e600083018461071a565b92915050565b6000806040838503121561075b5761075a61063a565b5b600061076985828601610660565b925050602061077a85828601610660565b9150509250929050565b61078d8161063f565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006107be82610793565b9050919050565b6107ce816107b3565b82525050565b60c0820160008201516107ea6000850182610784565b5060208201516107fd60208501826107c5565b5060408201516108106040850182610784565b5060608201516108236060850182610784565b5060808201516108366080850182610784565b5060a082015161084960a0850182610784565b50505050565b600060c08201905061086460008301846107d4565b92915050565b600082825260208201905092915050565b7f596f752073686f756c6420616363657074207468652070656e616c747920637560008201527f742e000000000000000000000000000000000000000000000000000000000000602082015250565b60006108d760228361086a565b91506108e28261087b565b604082019050919050565b60006020820190508181036000830152610906816108ca565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006109478261063f565b91506109528361063f565b92508282026109608161063f565b915082820484148315176109775761097661090d565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60006109b88261063f565b91506109c38361063f565b9250826109d3576109d261097e565b5b828204905092915050565b60006109e98261063f565b91506109f48361063f565b9250828203905081811115610a0c57610a0b61090d565b5b92915050565b610a1b816107b3565b82525050565b6000604082019050610a366000830185610a12565b610a43602083018461071a565b9392505050565b600081519050610a5981610681565b92915050565b600060208284031215610a7557610a7461063a565b5b6000610a8384828501610a4a565b91505092915050565b6000610a978261063f565b9150610aa28361063f565b9250828201905080821115610aba57610ab961090d565b5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4465706f73697420776974682074686520676976656e204944206e6f7420666f60008201527f756e642e00000000000000000000000000000000000000000000000000000000602082015250565b6000610b4b60248361086a565b9150610b5682610aef565b604082019050919050565b60006020820190508181036000830152610b7a81610b3e565b905091905056fea2646970667358221220bb7d26cd403bb136957890911eef51151abf24d7b5fd5e3c365f650206acdb1564736f6c63430008180033", + "bytecode": "0x60a0604052600060025560006003553480156200001b57600080fd5b506040516200159c3803806200159c833981810160405281019062000041919062000207565b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050620000856200008c60201b60201c565b50620002bf565b60405180608001604052806002548152602001601e81526020016019815260200160008152506000808081526020019081526020016000206000820151816000015560208201518160010155604082015181600201556060820151816003015590505060026000815480929190620001049062000272565b919050555060405180608001604052806002548152602001603c8152602001603281526020016000815250600080600181526020019081526020016000206000820151816000015560208201518160010155604082015181600201556060820151816003015590505060026000815480929190620001829062000272565b9190505550565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620001bb826200018e565b9050919050565b6000620001cf82620001ae565b9050919050565b620001e181620001c2565b8114620001ed57600080fd5b50565b6000815190506200020181620001d6565b92915050565b60006020828403121562000220576200021f62000189565b5b60006200023084828501620001f0565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000819050919050565b60006200027f8262000268565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203620002b457620002b362000239565b5b600182019050919050565b6080516112ba620002e2600039600081816101f401526103db01526112ba6000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c806338d074361461005c5780633a140b9d14610078578063673a2a1f146100a85780637b0472f0146100c65780639f9fb968146100e2575b600080fd5b61007660048036038101906100719190610ae6565b610112565b005b610092600480360381019061008d9190610b26565b61029d565b60405161009f9190610b62565b60405180910390f35b6100b06102de565b6040516100bd9190610c90565b60405180910390f35b6100e060048036038101906100db9190610cb2565b6103b9565b005b6100fc60048036038101906100f79190610b26565b6106c0565b6040516101099190610dae565b60405180910390f35b600061011d8361029d565b905060008111156101695781610168576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161015f90610e4c565b60405180910390fd5b5b6000610174846106c0565b905060008060008360400151815260200190815260200160002060405180608001604052908160008201548152602001600182015481526020016002820154815260200160038201548152505090506000836064836040015185606001516101dc9190610e9b565b6101e69190610f0c565b6101f09190610f3d565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8460200151836040518363ffffffff1660e01b8152600401610251929190610f80565b6020604051808303816000875af1158015610270573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102949190610fbe565b50505050505050565b6000806102a9836106c0565b90506000428260a001516102bd9190610f3d565b9050600081116102d2576000925050506102d9565b6064925050505b919050565b606060025467ffffffffffffffff8111156102fc576102fb610feb565b5b60405190808252806020026020018201604052801561033557816020015b6103226109f5565b81526020019060019003908161031a5790505b50905060005b6002548110156103b55760008082815260200190815260200160002060405180608001604052908160008201548152602001600182015481526020016002820154815260200160038201548152505082828151811061039d5761039c61101a565b5b6020026020010181905250808060010191505061033b565b5090565b60003390506000429050600080600086815260200190815260200160002090507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166323b872dd8430876040518463ffffffff1660e01b815260040161043693929190611049565b6020604051808303816000875af1158015610455573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104799190610fbe565b5060006040518060c0016040528060035481526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018781526020018681526020018481526020016201518084600101546104d09190610e9b565b856104db9190611080565b8152509050600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190806001815401808255809150506001900390600052602060002090600602016000909190919091506000820151816000015560208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020155606082015181600301556080820151816004015560a0820151816005015550506106026040518060400160405280600481526020017f6865793100000000000000000000000000000000000000000000000000000000815250610874565b848260030160008282546106169190611080565b9250508190555061065b6040518060400160405280600481526020017f6865793200000000000000000000000000000000000000000000000000000000815250610874565b858473ffffffffffffffffffffffffffffffffffffffff167fb4caaf29adda3eefee3ad552a8e85058589bf834c7466cae4ee58787f70589ed87866040516106a49291906110b4565b60405180910390a36106b88487878661090d565b505050505050565b6106c8610a1d565b6106d0610a1d565b60005b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054905081101561083357600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020818154811061076e5761076d61101a565b5b90600052602060002090600602016040518060c0016040529081600082015481526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200160028201548152602001600382015481526020016004820154815260200160058201548152505091508382600001510361082657819250505061086f565b80806001019150506106d3565b506040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108669061114f565b60405180910390fd5b919050565b61090a8160405160240161088891906111ee565b6040516020818303038152906040527f41304fac000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506109af565b50565b6109a9848484846040516024016109279493929190611210565b6040516020818303038152906040527f34f0e636000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506109af565b50505050565b6109c6816109be6109c96109ea565b63ffffffff16565b50565b60006a636f6e736f6c652e6c6f679050600080835160208501845afa505050565b610a69819050919050565b6040518060800160405280600081526020016000815260200160008152602001600081525090565b6040518060c0016040528060008152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081526020016000815260200160008152602001600081525090565b610a71611255565b565b600080fd5b6000819050919050565b610a8b81610a78565b8114610a9657600080fd5b50565b600081359050610aa881610a82565b92915050565b60008115159050919050565b610ac381610aae565b8114610ace57600080fd5b50565b600081359050610ae081610aba565b92915050565b60008060408385031215610afd57610afc610a73565b5b6000610b0b85828601610a99565b9250506020610b1c85828601610ad1565b9150509250929050565b600060208284031215610b3c57610b3b610a73565b5b6000610b4a84828501610a99565b91505092915050565b610b5c81610a78565b82525050565b6000602082019050610b776000830184610b53565b92915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b610bb281610a78565b82525050565b608082016000820151610bce6000850182610ba9565b506020820151610be16020850182610ba9565b506040820151610bf46040850182610ba9565b506060820151610c076060850182610ba9565b50505050565b6000610c198383610bb8565b60808301905092915050565b6000602082019050919050565b6000610c3d82610b7d565b610c478185610b88565b9350610c5283610b99565b8060005b83811015610c83578151610c6a8882610c0d565b9750610c7583610c25565b925050600181019050610c56565b5085935050505092915050565b60006020820190508181036000830152610caa8184610c32565b905092915050565b60008060408385031215610cc957610cc8610a73565b5b6000610cd785828601610a99565b9250506020610ce885828601610a99565b9150509250929050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610d1d82610cf2565b9050919050565b610d2d81610d12565b82525050565b60c082016000820151610d496000850182610ba9565b506020820151610d5c6020850182610d24565b506040820151610d6f6040850182610ba9565b506060820151610d826060850182610ba9565b506080820151610d956080850182610ba9565b5060a0820151610da860a0850182610ba9565b50505050565b600060c082019050610dc36000830184610d33565b92915050565b600082825260208201905092915050565b7f596f752073686f756c6420616363657074207468652070656e616c747920637560008201527f742e000000000000000000000000000000000000000000000000000000000000602082015250565b6000610e36602283610dc9565b9150610e4182610dda565b604082019050919050565b60006020820190508181036000830152610e6581610e29565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610ea682610a78565b9150610eb183610a78565b9250828202610ebf81610a78565b91508282048414831517610ed657610ed5610e6c565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000610f1782610a78565b9150610f2283610a78565b925082610f3257610f31610edd565b5b828204905092915050565b6000610f4882610a78565b9150610f5383610a78565b9250828203905081811115610f6b57610f6a610e6c565b5b92915050565b610f7a81610d12565b82525050565b6000604082019050610f956000830185610f71565b610fa26020830184610b53565b9392505050565b600081519050610fb881610aba565b92915050565b600060208284031215610fd457610fd3610a73565b5b6000610fe284828501610fa9565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060608201905061105e6000830186610f71565b61106b6020830185610f71565b6110786040830184610b53565b949350505050565b600061108b82610a78565b915061109683610a78565b92508282019050808211156110ae576110ad610e6c565b5b92915050565b60006040820190506110c96000830185610b53565b6110d66020830184610b53565b9392505050565b7f4465706f73697420776974682074686520676976656e204944206e6f7420666f60008201527f756e642e00000000000000000000000000000000000000000000000000000000602082015250565b6000611139602483610dc9565b9150611144826110dd565b604082019050919050565b600060208201905081810360008301526111688161112c565b9050919050565b600081519050919050565b60005b8381101561119857808201518184015260208101905061117d565b60008484015250505050565b6000601f19601f8301169050919050565b60006111c08261116f565b6111ca8185610dc9565b93506111da81856020860161117a565b6111e3816111a4565b840191505092915050565b6000602082019050818103600083015261120881846111b5565b905092915050565b60006080820190506112256000830187610f71565b6112326020830186610b53565b61123f6040830185610b53565b61124c6060830184610b53565b95945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052605160045260246000fdfea26469706673582212203dfcaff98baa42de5ccb8494b46fdb1000377828096d294f49c7f2b630987a9d64736f6c63430008180033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100575760003560e01c806338d074361461005c5780633a140b9d14610078578063673a2a1f146100a85780637b0472f0146100c65780639f9fb968146100e2575b600080fd5b61007660048036038101906100719190610ae6565b610112565b005b610092600480360381019061008d9190610b26565b61029d565b60405161009f9190610b62565b60405180910390f35b6100b06102de565b6040516100bd9190610c90565b60405180910390f35b6100e060048036038101906100db9190610cb2565b6103b9565b005b6100fc60048036038101906100f79190610b26565b6106c0565b6040516101099190610dae565b60405180910390f35b600061011d8361029d565b905060008111156101695781610168576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161015f90610e4c565b60405180910390fd5b5b6000610174846106c0565b905060008060008360400151815260200190815260200160002060405180608001604052908160008201548152602001600182015481526020016002820154815260200160038201548152505090506000836064836040015185606001516101dc9190610e9b565b6101e69190610f0c565b6101f09190610f3d565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8460200151836040518363ffffffff1660e01b8152600401610251929190610f80565b6020604051808303816000875af1158015610270573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102949190610fbe565b50505050505050565b6000806102a9836106c0565b90506000428260a001516102bd9190610f3d565b9050600081116102d2576000925050506102d9565b6064925050505b919050565b606060025467ffffffffffffffff8111156102fc576102fb610feb565b5b60405190808252806020026020018201604052801561033557816020015b6103226109f5565b81526020019060019003908161031a5790505b50905060005b6002548110156103b55760008082815260200190815260200160002060405180608001604052908160008201548152602001600182015481526020016002820154815260200160038201548152505082828151811061039d5761039c61101a565b5b6020026020010181905250808060010191505061033b565b5090565b60003390506000429050600080600086815260200190815260200160002090507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166323b872dd8430876040518463ffffffff1660e01b815260040161043693929190611049565b6020604051808303816000875af1158015610455573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104799190610fbe565b5060006040518060c0016040528060035481526020018573ffffffffffffffffffffffffffffffffffffffff1681526020018781526020018681526020018481526020016201518084600101546104d09190610e9b565b856104db9190611080565b8152509050600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190806001815401808255809150506001900390600052602060002090600602016000909190919091506000820151816000015560208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160020155606082015181600301556080820151816004015560a0820151816005015550506106026040518060400160405280600481526020017f6865793100000000000000000000000000000000000000000000000000000000815250610874565b848260030160008282546106169190611080565b9250508190555061065b6040518060400160405280600481526020017f6865793200000000000000000000000000000000000000000000000000000000815250610874565b858473ffffffffffffffffffffffffffffffffffffffff167fb4caaf29adda3eefee3ad552a8e85058589bf834c7466cae4ee58787f70589ed87866040516106a49291906110b4565b60405180910390a36106b88487878661090d565b505050505050565b6106c8610a1d565b6106d0610a1d565b60005b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208054905081101561083357600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020818154811061076e5761076d61101a565b5b90600052602060002090600602016040518060c0016040529081600082015481526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200160028201548152602001600382015481526020016004820154815260200160058201548152505091508382600001510361082657819250505061086f565b80806001019150506106d3565b506040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108669061114f565b60405180910390fd5b919050565b61090a8160405160240161088891906111ee565b6040516020818303038152906040527f41304fac000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506109af565b50565b6109a9848484846040516024016109279493929190611210565b6040516020818303038152906040527f34f0e636000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506109af565b50505050565b6109c6816109be6109c96109ea565b63ffffffff16565b50565b60006a636f6e736f6c652e6c6f679050600080835160208501845afa505050565b610a69819050919050565b6040518060800160405280600081526020016000815260200160008152602001600081525090565b6040518060c0016040528060008152602001600073ffffffffffffffffffffffffffffffffffffffff168152602001600081526020016000815260200160008152602001600081525090565b610a71611255565b565b600080fd5b6000819050919050565b610a8b81610a78565b8114610a9657600080fd5b50565b600081359050610aa881610a82565b92915050565b60008115159050919050565b610ac381610aae565b8114610ace57600080fd5b50565b600081359050610ae081610aba565b92915050565b60008060408385031215610afd57610afc610a73565b5b6000610b0b85828601610a99565b9250506020610b1c85828601610ad1565b9150509250929050565b600060208284031215610b3c57610b3b610a73565b5b6000610b4a84828501610a99565b91505092915050565b610b5c81610a78565b82525050565b6000602082019050610b776000830184610b53565b92915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b610bb281610a78565b82525050565b608082016000820151610bce6000850182610ba9565b506020820151610be16020850182610ba9565b506040820151610bf46040850182610ba9565b506060820151610c076060850182610ba9565b50505050565b6000610c198383610bb8565b60808301905092915050565b6000602082019050919050565b6000610c3d82610b7d565b610c478185610b88565b9350610c5283610b99565b8060005b83811015610c83578151610c6a8882610c0d565b9750610c7583610c25565b925050600181019050610c56565b5085935050505092915050565b60006020820190508181036000830152610caa8184610c32565b905092915050565b60008060408385031215610cc957610cc8610a73565b5b6000610cd785828601610a99565b9250506020610ce885828601610a99565b9150509250929050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610d1d82610cf2565b9050919050565b610d2d81610d12565b82525050565b60c082016000820151610d496000850182610ba9565b506020820151610d5c6020850182610d24565b506040820151610d6f6040850182610ba9565b506060820151610d826060850182610ba9565b506080820151610d956080850182610ba9565b5060a0820151610da860a0850182610ba9565b50505050565b600060c082019050610dc36000830184610d33565b92915050565b600082825260208201905092915050565b7f596f752073686f756c6420616363657074207468652070656e616c747920637560008201527f742e000000000000000000000000000000000000000000000000000000000000602082015250565b6000610e36602283610dc9565b9150610e4182610dda565b604082019050919050565b60006020820190508181036000830152610e6581610e29565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610ea682610a78565b9150610eb183610a78565b9250828202610ebf81610a78565b91508282048414831517610ed657610ed5610e6c565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000610f1782610a78565b9150610f2283610a78565b925082610f3257610f31610edd565b5b828204905092915050565b6000610f4882610a78565b9150610f5383610a78565b9250828203905081811115610f6b57610f6a610e6c565b5b92915050565b610f7a81610d12565b82525050565b6000604082019050610f956000830185610f71565b610fa26020830184610b53565b9392505050565b600081519050610fb881610aba565b92915050565b600060208284031215610fd457610fd3610a73565b5b6000610fe284828501610fa9565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060608201905061105e6000830186610f71565b61106b6020830185610f71565b6110786040830184610b53565b949350505050565b600061108b82610a78565b915061109683610a78565b92508282019050808211156110ae576110ad610e6c565b5b92915050565b60006040820190506110c96000830185610b53565b6110d66020830184610b53565b9392505050565b7f4465706f73697420776974682074686520676976656e204944206e6f7420666f60008201527f756e642e00000000000000000000000000000000000000000000000000000000602082015250565b6000611139602483610dc9565b9150611144826110dd565b604082019050919050565b600060208201905081810360008301526111688161112c565b9050919050565b600081519050919050565b60005b8381101561119857808201518184015260208101905061117d565b60008484015250505050565b6000601f19601f8301169050919050565b60006111c08261116f565b6111ca8185610dc9565b93506111da81856020860161117a565b6111e3816111a4565b840191505092915050565b6000602082019050818103600083015261120881846111b5565b905092915050565b60006080820190506112256000830187610f71565b6112326020830186610b53565b61123f6040830185610b53565b61124c6060830184610b53565b95945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052605160045260246000fdfea26469706673582212203dfcaff98baa42de5ccb8494b46fdb1000377828096d294f49c7f2b630987a9d64736f6c63430008180033", "linkReferences": {}, "deployedLinkReferences": {} } \ No newline at end of file diff --git a/frontend/src/evm-output/deployed_addresses.json b/frontend/src/evm-output/deployed_addresses.json index a7cee76..2dd2c7f 100644 --- a/frontend/src/evm-output/deployed_addresses.json +++ b/frontend/src/evm-output/deployed_addresses.json @@ -1,5 +1,5 @@ { - "MainModule#MessageBox": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F", - "MainModule#PADToken": "0x09635F643e140090A9A8Dcd712eD6285858ceBef", - "MainModule#PADTokenStake": "0xc5a5C42992dECbae36851359345FE25997F5C42d" + "MainModule#MessageBox": "0x998abeb3E57409262aE5b751f60747921B33613E", + "MainModule#PADToken": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49", + "MainModule#PADTokenStake": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528" } diff --git a/frontend/src/query/fetchers/investors.ts b/frontend/src/query/fetchers/investors.ts new file mode 100644 index 0000000..e54d3a8 --- /dev/null +++ b/frontend/src/query/fetchers/investors.ts @@ -0,0 +1,21 @@ +import { CONFIG } from "../../config"; + +export const fetchBalances = async (walletAddress: string) => { + if (!walletAddress) { + return null; + } + + const resp = await fetch( + `${CONFIG.API_BASE_URL}/investors/${walletAddress}/balances` + ); + if (!resp.ok) { + throw new Error("Network error."); + } + + const respBody = await resp.json(); + if (!respBody.ok) { + throw new Error(respBody.message || "Something went wrong."); + } + + return respBody.data; +}; diff --git a/frontend/src/routes/pad-token-stake.lazy.tsx b/frontend/src/routes/pad-token-stake.lazy.tsx index 17e95aa..81f8960 100644 --- a/frontend/src/routes/pad-token-stake.lazy.tsx +++ b/frontend/src/routes/pad-token-stake.lazy.tsx @@ -1,9 +1,10 @@ import { createLazyFileRoute } from "@tanstack/react-router"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useStore } from "../store"; +import { getFormInputValue } from "../utils/form"; import { toastError, tryWithToast } from "../utils/toast"; import { formatPadTokenAmount } from "../utils/ui"; -import { padToken, padTokenStake } from "../web3"; +import { padToken, padTokenStake, web3 } from "../web3"; export const Route = createLazyFileRoute("/pad-token-stake")({ component: PadTokenStakePage, @@ -12,6 +13,7 @@ export const Route = createLazyFileRoute("/pad-token-stake")({ function PadTokenStakePage() { const connectedAccount = useStore((state) => state.connectedAccount); const [allowance, setAllowance] = useState("(nothingness)"); + const [pools, setPools] = useState([]); const getAllowanceAmount = async () => { if (!connectedAccount) { @@ -42,6 +44,71 @@ function PadTokenStakePage() { }); }; + const getPools = async () => { + await tryWithToast( + "Get Pools", + async () => { + const _pools: any[] = await padTokenStake.methods.getPools().call(); + setPools(_pools); + }, + { onError: () => setPools([]), successSilent: true } + ); + }; + + const onStakeFormSubmit: React.FormEventHandler = async ( + event + ) => { + event.preventDefault(); + + if (!connectedAccount) { + toastError("Stake", "Connect your wallet first."); + return; + } + + const poolId = getFormInputValue( + event.target as HTMLFormElement, + "poolId" + ).trim(); + if (!poolId) { + toastError("Stake", "Something went wrong."); + return; + } + + const amount = getFormInputValue( + event.target as HTMLFormElement, + "amount" + ).trim(); + if (!amount) { + toastError("Stake", "Enter an amount."); + return; + } + + await tryWithToast("Stake", async () => { + await padToken.methods + .approve( + padTokenStake.options.address, + web3.utils.toWei(amount, "ether") + ) + .send({ from: connectedAccount }); + await padTokenStake.methods + .stake(poolId, web3.utils.toWei(amount, "ether")) + .send({ from: connectedAccount }); + }); + }; + + useEffect(() => { + getPools(); + + const subscription = padTokenStake.events.Staked(); + subscription.on("data", (event) => { + getPools(); + }); + + return () => { + subscription.unsubscribe(); + }; + }, []); + return (
@@ -69,6 +136,44 @@ function PadTokenStakePage() {
+ +
+
+

Pools

+
+ {pools.map((pool) => ( +
+ {console.log(pool)} +
Pool #{Number(pool.id)}
+
    +
  • Lockdown period: {Number(pool.lockdownDays)} days
  • +
  • Reward: +{Number(pool.rewardPercentage)}%
  • +
  • + Total locked value:{" "} + {formatPadTokenAmount(pool.lockedAmount)} +
  • +
+
+ + + +
+
+ ))} +
+
+
); } diff --git a/frontend/src/utils/toast.ts b/frontend/src/utils/toast.ts index 57386cb..5a07419 100644 --- a/frontend/src/utils/toast.ts +++ b/frontend/src/utils/toast.ts @@ -15,11 +15,17 @@ export function toastError(taskName: string, errorMessage: string) { export async function tryWithToast( taskName: string, - taskFn: () => Promise + taskFn: () => Promise, + options?: { + onError?: (error: any) => void; + successSilent: boolean; + } ) { try { await taskFn(); - toastSuccess(taskName); + if (!options?.successSilent) { + toastSuccess(taskName); + } } catch (error: any) { console.error(error); toastError( @@ -27,5 +33,6 @@ export async function tryWithToast( error.message || "No error message. Check the DevTools console for details." ); + options?.onError?.(error); } } diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index a7fc6fb..fdb717d 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -15,7 +15,8 @@ "jsx": "react-jsx", /* Linting */ - "strict": true, + /* TODO Set strict to true later */ + "strict": false, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true