add PADTokenStake and PADToken.approveMax
This commit is contained in:
parent
29ca4239ee
commit
3240620034
|
|
@ -4,3 +4,4 @@ cp "./evm/ignition/deployments/chain-31337/deployed_addresses.json" "./frontend/
|
||||||
|
|
||||||
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#MessageBox.json" "./frontend/src/evm-output/MessageBox.artifacts.json"
|
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#MessageBox.json" "./frontend/src/evm-output/MessageBox.artifacts.json"
|
||||||
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#PADToken.json" "./frontend/src/evm-output/PADToken.artifacts.json"
|
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#PADToken.json" "./frontend/src/evm-output/PADToken.artifacts.json"
|
||||||
|
cp "./evm/ignition/deployments/chain-31337/artifacts/MainModule#PADTokenStake.json" "./frontend/src/evm-output/PADTokenStake.artifacts.json"
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,19 @@ pragma solidity ^0.8.20;
|
||||||
|
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
|
||||||
|
|
||||||
contract PADToken is ERC20, Ownable, ERC20Permit {
|
contract PADToken is ERC20, Ownable {
|
||||||
constructor(address initialOwner)
|
constructor(address initialOwner)
|
||||||
ERC20("PAD Token", "PAD")
|
ERC20("PAD Token", "PAD")
|
||||||
Ownable(initialOwner)
|
Ownable(initialOwner)
|
||||||
ERC20Permit("PAD Token")
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
function mint(address to, uint256 amount) public onlyOwner {
|
function mint(address to, uint256 amount) public onlyOwner {
|
||||||
_mint(to, amount);
|
_mint(to, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function approveMax(address spender) public returns (bool) {
|
||||||
|
uint value = type(uint256).max;
|
||||||
|
return approve(spender, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.24;
|
||||||
|
|
||||||
|
import "./PADToken.sol";
|
||||||
|
|
||||||
|
contract PADTokenStake {
|
||||||
|
// pool id => pool info
|
||||||
|
mapping (uint => Pool) pools;
|
||||||
|
// staker => deposits
|
||||||
|
mapping(address => Deposit[]) deposits;
|
||||||
|
uint nextPoolId = 0;
|
||||||
|
uint nextDepositId = 0;
|
||||||
|
PADToken immutable padToken;
|
||||||
|
|
||||||
|
struct Pool {
|
||||||
|
uint id;
|
||||||
|
uint lockdownDays;
|
||||||
|
uint rewardPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Deposit {
|
||||||
|
uint id;
|
||||||
|
address staker;
|
||||||
|
uint poolId;
|
||||||
|
uint amount;
|
||||||
|
uint lockedAt;
|
||||||
|
uint releasedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(PADToken _padToken) {
|
||||||
|
padToken = _padToken;
|
||||||
|
_initPools();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _initPools() internal {
|
||||||
|
pools[0] = Pool({
|
||||||
|
id: nextPoolId,
|
||||||
|
lockdownDays: 30,
|
||||||
|
rewardPercentage: 25
|
||||||
|
});
|
||||||
|
nextPoolId++;
|
||||||
|
|
||||||
|
pools[1] = Pool({
|
||||||
|
id: nextPoolId,
|
||||||
|
lockdownDays: 60,
|
||||||
|
rewardPercentage: 50
|
||||||
|
});
|
||||||
|
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 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) {
|
||||||
|
return deposit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
revert("Deposit with the given ID not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWithdrawPenalty(uint depositId) public view returns (uint) {
|
||||||
|
Deposit memory deposit = getDeposit(depositId);
|
||||||
|
uint remainingMs = deposit.releasedAt - block.timestamp;
|
||||||
|
|
||||||
|
if (remainingMs <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Find a meaningful formula
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withdraw(uint depositId, bool acceptPenaltyCut) external {
|
||||||
|
uint penalty = getWithdrawPenalty(depositId);
|
||||||
|
if (penalty > 0) {
|
||||||
|
require(acceptPenaltyCut, "You should accept the penalty cut.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Deposit memory deposit = getDeposit(depositId);
|
||||||
|
Pool memory pool = pools[deposit.poolId];
|
||||||
|
uint amountToTransfer = (deposit.amount * pool.rewardPercentage / 100) - penalty;
|
||||||
|
padToken.transfer(deposit.staker, amountToTransfer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,9 @@ const MainModule = buildModule("MainModule", (m) => {
|
||||||
|
|
||||||
const padToken = m.contract("PADToken", [initialOwner]);
|
const padToken = m.contract("PADToken", [initialOwner]);
|
||||||
|
|
||||||
return { messageBox, padToken };
|
const padTokenStake = m.contract("PADTokenStake", [padToken]);
|
||||||
|
|
||||||
|
return { messageBox, padToken, padTokenStake };
|
||||||
});
|
});
|
||||||
|
|
||||||
export default MainModule;
|
export default MainModule;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ export function Header() {
|
||||||
<Link to="/pad-token" className="[&.active]:font-bold">
|
<Link to="/pad-token" className="[&.active]:font-bold">
|
||||||
PAD Token
|
PAD Token
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link to="/pad-token-stake" className="[&.active]:font-bold">
|
||||||
|
PAD Token Stake
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ConnectMetamaskButton />
|
<ConnectMetamaskButton />
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"MainModule#MessageBox": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
|
"MainModule#MessageBox": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
|
||||||
"MainModule#PADToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
|
"MainModule#PADToken": "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
|
||||||
|
"MainModule#PADTokenStake": "0xc5a5C42992dECbae36851359345FE25997F5C42d"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { Route as rootRoute } from './routes/__root'
|
||||||
|
|
||||||
// Create Virtual Routes
|
// Create Virtual Routes
|
||||||
|
|
||||||
|
const PadTokenStakeLazyImport = createFileRoute('/pad-token-stake')()
|
||||||
const PadTokenLazyImport = createFileRoute('/pad-token')()
|
const PadTokenLazyImport = createFileRoute('/pad-token')()
|
||||||
const MessageBoxLazyImport = createFileRoute('/message-box')()
|
const MessageBoxLazyImport = createFileRoute('/message-box')()
|
||||||
const AboutLazyImport = createFileRoute('/about')()
|
const AboutLazyImport = createFileRoute('/about')()
|
||||||
|
|
@ -23,6 +24,13 @@ const IndexLazyImport = createFileRoute('/')()
|
||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
|
const PadTokenStakeLazyRoute = PadTokenStakeLazyImport.update({
|
||||||
|
path: '/pad-token-stake',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any).lazy(() =>
|
||||||
|
import('./routes/pad-token-stake.lazy').then((d) => d.Route),
|
||||||
|
)
|
||||||
|
|
||||||
const PadTokenLazyRoute = PadTokenLazyImport.update({
|
const PadTokenLazyRoute = PadTokenLazyImport.update({
|
||||||
path: '/pad-token',
|
path: '/pad-token',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
|
|
@ -63,6 +71,10 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof PadTokenLazyImport
|
preLoaderRoute: typeof PadTokenLazyImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/pad-token-stake': {
|
||||||
|
preLoaderRoute: typeof PadTokenStakeLazyImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +85,7 @@ export const routeTree = rootRoute.addChildren([
|
||||||
AboutLazyRoute,
|
AboutLazyRoute,
|
||||||
MessageBoxLazyRoute,
|
MessageBoxLazyRoute,
|
||||||
PadTokenLazyRoute,
|
PadTokenLazyRoute,
|
||||||
|
PadTokenStakeLazyRoute,
|
||||||
])
|
])
|
||||||
|
|
||||||
/* prettier-ignore-end */
|
/* prettier-ignore-end */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useStore } from "../store";
|
||||||
|
import { toastError, tryWithToast } from "../utils/toast";
|
||||||
|
import { formatPadTokenAmount } from "../utils/ui";
|
||||||
|
import { padToken, padTokenStake } from "../web3";
|
||||||
|
|
||||||
|
export const Route = createLazyFileRoute("/pad-token-stake")({
|
||||||
|
component: PadTokenStakePage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PadTokenStakePage() {
|
||||||
|
const connectedAccount = useStore((state) => state.connectedAccount);
|
||||||
|
const [allowance, setAllowance] = useState("(nothingness)");
|
||||||
|
|
||||||
|
const getAllowanceAmount = async () => {
|
||||||
|
if (!connectedAccount) {
|
||||||
|
toastError("Get Current Allowance", "Connect your wallet first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tryWithToast("Get Current Allowance", async () => {
|
||||||
|
const owner = connectedAccount;
|
||||||
|
const spender = padTokenStake.options.address;
|
||||||
|
const allowance: bigint = await padToken.methods
|
||||||
|
.allowance(owner, spender)
|
||||||
|
.call();
|
||||||
|
setAllowance(formatPadTokenAmount(allowance));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const approveMax = async () => {
|
||||||
|
if (!connectedAccount) {
|
||||||
|
toastError("Approve Max", "Connect your wallet first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tryWithToast("Approve Max", async () => {
|
||||||
|
const owner = connectedAccount;
|
||||||
|
const spender = padTokenStake.options.address;
|
||||||
|
await padToken.methods.approveMax(spender).send({ from: owner });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-4 grid lg:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
|
<div className="card bg-base-200">
|
||||||
|
<div className="card-body items-start">
|
||||||
|
<h2 className="text-2xl font-bold">Allowance</h2>
|
||||||
|
<div>
|
||||||
|
<span className="font-bold">
|
||||||
|
Allowance of PADTokenStake contract, for my PAD tokens:
|
||||||
|
</span>{" "}
|
||||||
|
{allowance}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => getAllowanceAmount()}
|
||||||
|
>
|
||||||
|
Get
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => approveMax()}
|
||||||
|
>
|
||||||
|
Approve Max
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -5,5 +5,8 @@ export function shortenAddress(address: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatPadTokenAmount(amount: bigint) {
|
export function formatPadTokenAmount(amount: bigint) {
|
||||||
|
if (Number(amount) === Math.pow(2, 256)) {
|
||||||
|
return "∞ PAD";
|
||||||
|
}
|
||||||
return `${web3.utils.fromWei(amount, "ether").replace(/\.*$/, "")} PAD`;
|
return `${web3.utils.fromWei(amount, "ether").replace(/\.*$/, "")} PAD`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Web3 } from "web3";
|
import { Web3 } from "web3";
|
||||||
import messageBoxArtifacts from "./evm-output/MessageBox.artifacts.json";
|
import messageBoxArtifacts from "./evm-output/MessageBox.artifacts.json";
|
||||||
import padTokenArtifacts from "./evm-output/PADToken.artifacts.json";
|
import padTokenArtifacts from "./evm-output/PADToken.artifacts.json";
|
||||||
|
import padTokenStakeArtifacts from "./evm-output/PADTokenStake.artifacts.json";
|
||||||
import deployedAddresses from "./evm-output/deployed_addresses.json";
|
import deployedAddresses from "./evm-output/deployed_addresses.json";
|
||||||
|
|
||||||
export let web3: Web3;
|
export let web3: Web3;
|
||||||
|
|
@ -23,3 +24,8 @@ export const padToken = new web3!.eth.Contract(
|
||||||
padTokenArtifacts.abi,
|
padTokenArtifacts.abi,
|
||||||
deployedAddresses["MainModule#PADToken"]
|
deployedAddresses["MainModule#PADToken"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const padTokenStake = new web3!.eth.Contract(
|
||||||
|
padTokenStakeArtifacts.abi,
|
||||||
|
deployedAddresses["MainModule#PADTokenStake"]
|
||||||
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue