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#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/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)
|
||||
ERC20("PAD Token", "PAD")
|
||||
Ownable(initialOwner)
|
||||
ERC20Permit("PAD Token")
|
||||
{}
|
||||
|
||||
function mint(address to, uint256 amount) public onlyOwner {
|
||||
_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]);
|
||||
|
||||
return { messageBox, padToken };
|
||||
const padTokenStake = m.contract("PADTokenStake", [padToken]);
|
||||
|
||||
return { messageBox, padToken, padTokenStake };
|
||||
});
|
||||
|
||||
export default MainModule;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ export function Header() {
|
|||
<Link to="/pad-token" className="[&.active]:font-bold">
|
||||
PAD Token
|
||||
</Link>
|
||||
<Link to="/pad-token-stake" className="[&.active]:font-bold">
|
||||
PAD Token Stake
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<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#PADToken": "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
|
||||
"MainModule#MessageBox": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F",
|
||||
"MainModule#PADToken": "0x09635F643e140090A9A8Dcd712eD6285858ceBef",
|
||||
"MainModule#PADTokenStake": "0xc5a5C42992dECbae36851359345FE25997F5C42d"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { Route as rootRoute } from './routes/__root'
|
|||
|
||||
// Create Virtual Routes
|
||||
|
||||
const PadTokenStakeLazyImport = createFileRoute('/pad-token-stake')()
|
||||
const PadTokenLazyImport = createFileRoute('/pad-token')()
|
||||
const MessageBoxLazyImport = createFileRoute('/message-box')()
|
||||
const AboutLazyImport = createFileRoute('/about')()
|
||||
|
|
@ -23,6 +24,13 @@ const IndexLazyImport = createFileRoute('/')()
|
|||
|
||||
// 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({
|
||||
path: '/pad-token',
|
||||
getParentRoute: () => rootRoute,
|
||||
|
|
@ -63,6 +71,10 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof PadTokenLazyImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/pad-token-stake': {
|
||||
preLoaderRoute: typeof PadTokenStakeLazyImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +85,7 @@ export const routeTree = rootRoute.addChildren([
|
|||
AboutLazyRoute,
|
||||
MessageBoxLazyRoute,
|
||||
PadTokenLazyRoute,
|
||||
PadTokenStakeLazyRoute,
|
||||
])
|
||||
|
||||
/* 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) {
|
||||
if (Number(amount) === Math.pow(2, 256)) {
|
||||
return "∞ PAD";
|
||||
}
|
||||
return `${web3.utils.fromWei(amount, "ether").replace(/\.*$/, "")} PAD`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Web3 } from "web3";
|
||||
import messageBoxArtifacts from "./evm-output/MessageBox.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";
|
||||
|
||||
export let web3: Web3;
|
||||
|
|
@ -23,3 +24,8 @@ export const padToken = new web3!.eth.Contract(
|
|||
padTokenArtifacts.abi,
|
||||
deployedAddresses["MainModule#PADToken"]
|
||||
);
|
||||
|
||||
export const padTokenStake = new web3!.eth.Contract(
|
||||
padTokenStakeArtifacts.abi,
|
||||
deployedAddresses["MainModule#PADTokenStake"]
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue