Skip to content

Commit

Permalink
feat: added withdraw to marketplace
Browse files Browse the repository at this point in the history
  • Loading branch information
veeso committed Dec 6, 2024
1 parent e4a8a71 commit 964bdf8
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 1 deletion.
13 changes: 13 additions & 0 deletions ethereum/app/src/js/components/App/pages/Marketplace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import { ChainId } from '../../MetamaskConnect';
import Container from '../../reusable/Container';
import AdminSetRewardPool from './Marketplace/AdminSetRewardPool';
import TokenPrice from './Marketplace/TokenPrice';
import { convertToHumanReadable } from '../../../utils/format';
import AdminWithdraw from './Marketplace/AdminWithdraw';

const Marketplace = () => {
const { account, ethereum, chainId } = useConnectedMetaMask();

const [usdErc20, setUsdErc20] = React.useState<string>();
const [rewardPool, setRewardPool] = React.useState<string>();
const [interestRate, setInterestRate] = React.useState<string>();
const [liquidityWithdrawable, setLiquidityWithdrawable] =
React.useState<string>();

React.useEffect(() => {
const client = new MarketplaceClient(account, ethereum, chainId as ChainId);
Expand All @@ -22,6 +26,11 @@ const Marketplace = () => {
client.interestRate().then((rate) => {
setInterestRate(rate.toString());
});
client
.liquidityWithdrawable()
.then((value) =>
setLiquidityWithdrawable(convertToHumanReadable(value, 6)),
);
}, []);

return (
Expand All @@ -30,9 +39,13 @@ const Marketplace = () => {
<span className="block">USD ERC20: {usdErc20}</span>
<span className="block">Reward Pool: {rewardPool}</span>
<span className="block">Interest Rate: {interestRate}</span>
<span className="block">
Liquidity Withdrawable: {liquidityWithdrawable} USDT
</span>
</Container.Container>
<Container.FlexCols className="gap-8 w-3/6">
<AdminSetRewardPool />
<AdminWithdraw />
<TokenPrice />
</Container.FlexCols>
</Container.FlexCols>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as React from 'react';
import { useConnectedMetaMask } from 'metamask-react';

import MarketplaceClient from '../../../../web3/MarketplaceClient';
import { ChainId } from '../../../MetamaskConnect';
import Container from '../../../reusable/Container';
import Input from '../../../reusable/Input';
import Button from '../../../reusable/Button';
import Heading from '../../../reusable/Heading';

const AdminWithdraw = () => {
const { account, ethereum, chainId } = useConnectedMetaMask();
const [pendingTx, setPendingTx] = React.useState<boolean>(false);
const [amount, setAmount] = React.useState<string>('');

const onAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setAmount(event.target.value);
};

const onSubmit = () => {
const client = new MarketplaceClient(account, ethereum, chainId as ChainId);

if (!amount) {
alert('Address is required');
return;
}

setPendingTx(true);

client
.adminWithdraw(BigInt(amount))
.then(() => {
alert(`Withdrawn ${amount} from contract`);
setAmount('');
setPendingTx(false);
})
.catch((error) => {
alert(`Error: ${error.message}`);
setPendingTx(false);
});
};

return (
<Container.FlexCols>
<Heading.H2>Withdraw liquidity from marketplace</Heading.H2>
<Input.Input
id="withdraw-amount"
value={amount}
onChange={onAmountChange}
label="Withdraw amount"
type="number"
min={1}
/>
<Button.Primary disabled={pendingTx} onClick={onSubmit}>
Withdraw liquidity
</Button.Primary>
</Container.FlexCols>
);
};

export default AdminWithdraw;
10 changes: 10 additions & 0 deletions ethereum/app/src/js/web3/MarketplaceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ export default class MarketplaceClient {
.send({ from: this.address });
}

async adminWithdraw(amount: bigint) {
const contract = this.getContract();
return contract.methods.adminWithdraw(amount).send({ from: this.address });
}

async liquidityWithdrawable(): Promise<bigint> {
const contract = this.getContract();
return contract.methods.liquidityWithdrawable().call();
}

async adminSetRewardPool(newAddress: string) {
const contract = this.getContract();
return contract.methods
Expand Down
54 changes: 53 additions & 1 deletion ethereum/app/src/js/web3/contracts/Marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ export const ABI = [
name: 'OwnableUnauthorizedAccount',
type: 'error',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'LiquidityWithdrawn',
type: 'event',
},
{
anonymous: false,
inputs: [
Expand Down Expand Up @@ -137,6 +150,19 @@ export const ABI = [
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: '_amount',
type: 'uint256',
},
],
name: 'adminWithdraw',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
Expand Down Expand Up @@ -195,6 +221,32 @@ export const ABI = [
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'lastWithdrawalBlock',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'liquidityWithdrawable',
outputs: [
{
internalType: 'uint256',
name: '_withdrawable',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'owner',
Expand Down Expand Up @@ -291,6 +343,6 @@ export const CONTRACT_ADDRESS: ContractAddress = {
[ChainId.Mainnet]: '',
[ChainId.Rinkeby]: '',
[ChainId.Ropsten]: '',
[ChainId.Sepolia]: '0x9BDF7DdB6b24e554e2b58D6f32241b9b1C000674',
[ChainId.Sepolia]: '0x09BE22BBB90E5A0752B859d863cB8BC40898D234',
[ChainId.Hardhat]: '',
};
69 changes: 69 additions & 0 deletions ethereum/contracts/Marketplace.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ contract Marketplace is Ownable {
/// @notice tokens that has been sold
mapping(uint256 => bool) private soldTokens;

/// @notice The number of blocks per day
uint256 private constant BLOCKS_PER_DAY = 7200;

/// @notice The number of USDT that can be withdrawn per day
uint256 private constant WITHDRAWABLE_USDT_PER_DAY = 100;

/// @notice The maximum amount of USDT that can be withdrawn
uint256 private constant MAX_WITHDRAWABLE_USDT = 10_000;

/// @notice Last withdrawal block number
uint256 public lastWithdrawalBlock;

/// @notice Event emitted when a token is bought
event TokenBought(
address indexed buyer,
Expand All @@ -40,6 +52,9 @@ contract Marketplace is Ownable {
uint256 paidAmount
);

/// @notice Event emitted when liquidity is withdrawn
event LiquidityWithdrawn(uint256 amount);

constructor(
address _owner,
address _usdErc20,
Expand All @@ -60,6 +75,9 @@ contract Marketplace is Ownable {

// get decimals of the USD ERC20 token
usdErc20Decimals = ERC20(usdErc20).decimals();

// set the last withdrawal block
lastWithdrawalBlock = block.number;
}

/// @notice Set the address of the reward pool
Expand All @@ -82,6 +100,57 @@ contract Marketplace is Ownable {
interestRate = _interestRate;
}

/// @notice Get the liquidity that can be withdrawn
/// @return _withdrawable The amount of USDT that can be withdrawn
function liquidityWithdrawable()
public
view
returns (uint256 _withdrawable)
{
// calculate the number of blocks since the last withdrawal
uint256 blocksSinceLastWithdrawal = block.number - lastWithdrawalBlock;
// calculate the number of days since the last withdrawal
uint256 daysSinceLastWithdrawal = blocksSinceLastWithdrawal /
BLOCKS_PER_DAY;
// calculate the withdrawable amount
uint256 withdrawableAmount = daysSinceLastWithdrawal *
WITHDRAWABLE_USDT_PER_DAY;

// if the withdrawable amount is greater than the maximum withdrawable amount, set it to the maximum withdrawable amount
if (withdrawableAmount > MAX_WITHDRAWABLE_USDT) {
withdrawableAmount = MAX_WITHDRAWABLE_USDT;
}

// set withdrawable to the balance if the balance is less than the withdrawable amount
ERC20 currency = ERC20(usdErc20);
uint256 balance = currency.balanceOf(address(this));

if (balance < withdrawableAmount) {
withdrawableAmount = balance;
}

return withdrawableAmount;
}

/// @notice Withdraw USDT from the marketplace
/// @dev Only the owner can call this function
/// @param _amount The amount of USDT to withdraw
function adminWithdraw(uint256 _amount) external onlyOwner {
uint256 withdrawableAmount = liquidityWithdrawable();

require(
withdrawableAmount >= _amount,
"Marketplace: Not enough withdrawal funds"
);

ERC20 currency = ERC20(usdErc20);
currency.transfer(owner(), _amount);

lastWithdrawalBlock = block.number;

emit LiquidityWithdrawn(_amount);
}

/// @notice Buy the next token for a deferred contractt with the configured USD ERC20 token
/// @param _contractId The ID of the contract to buy the token for
/// @return tokenId The ID of the bought token
Expand Down

0 comments on commit 964bdf8

Please sign in to comment.