Skip to content

Commit

Permalink
feat: #27 check balance before minting or creating NFT
Browse files Browse the repository at this point in the history
  • Loading branch information
0xkynz committed Jun 14, 2023
1 parent ab1f913 commit e6ab7d6
Show file tree
Hide file tree
Showing 18 changed files with 230 additions and 22 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@usedapptesthello/usewallet": "0.0.12",
"@usedapptesthello/usewallet-core": "0.0.15",
"axios": "^1.4.0",
"big.js": "^6.2.1",
"casper-js-sdk": "^2.12.1",
"copy-to-clipboard": "^3.3.3",
"date-fns": "^2.29.3",
Expand Down Expand Up @@ -83,6 +84,7 @@
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.7",
"@types/axios": "^0.14.0",
"@types/big.js": "^6.1.6",
"@types/jest": "^26.0.23",
"@types/lodash": "^4.14.194",
"@types/node": "^14.6.0",
Expand Down
2 changes: 1 addition & 1 deletion src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const Config = {
apiBaseUrl: process.env.NEXT_PUBLIC_API_URL || 'https://api.melem.io/v1',
metadataBaseUrl: 'http://localhost:4000/v1/nfts',
nodeRPCUrl: '/rpc',
deployUrl: 'https://testnet-api.casperdash.io/deploy',
casperDashUrl: 'https://testnet-api.casperdash.io',
networkName: 'casper-test',
cep78: {
contractWASM:
Expand Down
1 change: 1 addition & 0 deletions src/enums/queryKeys.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export enum QueryKeys {
CAMPAIGNS = 'campaigns',
CHECK_PHONE_VERFIED = 'check-phone-verfied',
MY_PROFILE = 'my-profile',
ACCOUNT_BALANCE = 'account-balance',
}
48 changes: 48 additions & 0 deletions src/hooks/queries/useGetAccountBalance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import _get from 'lodash/get';
import { useQuery, UseQueryOptions } from 'react-query';

import { QueryKeys } from '@/enums/queryKeys.enum';
import { getAccounts } from '@/services/casperdash/user';
import { hexToNumber } from '@/utils/format';

type GetAccountBalanceResponse = {
balanace: number;
};

export const useGetAccountBalance = (
{ publicKey }: { publicKey?: string | null },
options?: Omit<
UseQueryOptions<
GetAccountBalanceResponse,
unknown,
GetAccountBalanceResponse,
[QueryKeys.ACCOUNT_BALANCE, string | null | undefined]
>,
'queryKey' | 'queryFn'
>
) => {
return useQuery(
[QueryKeys.ACCOUNT_BALANCE, publicKey],
async () => {
const accounts = await getAccounts({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
publicKeys: [publicKey!],
});
if (!accounts || accounts.length === 0) {
throw new Error('Can not get account');
}

const [account] = accounts;
const balanceHex = _get(account, 'balance.hex', '');

return {
balanace: hexToNumber(balanceHex),
};
},
{
...options,
enabled: !!publicKey,
refetchOnWindowFocus: true,
}
);
};
4 changes: 2 additions & 2 deletions src/modules/AdminNft/ButtonBulkCreateModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const ButtonBulkCreateModal = () => {
return (
<Box>
<Button variant="contained" onClick={handleOpen}>
Bulk Create Nft
Bulk Create NFT
</Button>
<Modal
open={open}
Expand All @@ -53,7 +53,7 @@ const ButtonBulkCreateModal = () => {
<Close sx={{ color: grey[500] }} />
</IconButton>
</Box>
<Typography id="modal-modal-title" variant="h6" component="h2">
<Typography variant="h6" component="h2">
Bulk Create NFT
</Typography>
<Box mt={2}>
Expand Down
25 changes: 24 additions & 1 deletion src/modules/AdminNft/ButtonCreateModal/NftForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';

import { LoadingButton } from '@mui/lab';
import { Divider } from '@mui/material';
import Box from '@mui/material/Box';
import { useAccount } from '@usedapptesthello/usewallet';
import {
Expand All @@ -17,6 +18,7 @@ import { CEP78ClientInstance } from '@/contracts/cep78';
import { QueryKeys } from '@/enums/queryKeys.enum';
import { useMutateCreateNft } from '@/hooks/mutations';
import { useGetBenefits, useGetAllNftCollections } from '@/hooks/queries';
import { useGetAccountBalance } from '@/hooks/queries/useGetAccountBalance';
import { CreateNftParams } from '@/services/admin/nft/types';
import { Benefit } from '@/types/benefit';
import { NftCollection } from '@/types/nft-collection';
Expand All @@ -25,11 +27,16 @@ type NftFormProps = {
onSuccess: () => void;
};

const ESTIMATE_FEE = 20;

const NftForm = ({ onSuccess }: NftFormProps) => {
const queryClient = useQueryClient();
const [isLoading, setIsLoading] = useState(false);
const [isDisabled, setIsDisabled] = useState(false);
const { publicKey } = useAccount();
const { data: { balanace = 0 } = { balanace: 0 } } = useGetAccountBalance({
publicKey,
});

const {
data: { items = [] } = { items: [], total: 0 },
Expand Down Expand Up @@ -96,6 +103,13 @@ const NftForm = ({ onSuccess }: NftFormProps) => {

return (
<FormContainer formContext={formContext} onSuccess={handleOnSubmitForm}>
<Box mb="1rem">
<Box display={'flex'} justifyContent={'space-between'}>
<Box>Your Balance:</Box>
<Box>{balanace} CSPR</Box>
</Box>
</Box>

<StyledTextFieldElement name="name" label="Name" required />
<Box mt="1rem">
<SelectElement
Expand Down Expand Up @@ -134,12 +148,21 @@ const NftForm = ({ onSuccess }: NftFormProps) => {
})}
/>
</Box>

<Box mt="1rem">
<Divider />
<Box display={'flex'} justifyContent={'space-between'} mt="1rem">
<Box>Estimate Fee:</Box>
<Box>{ESTIMATE_FEE} CSPR</Box>
</Box>
</Box>

<Box mt="1rem">
<LoadingButton
type={'submit'}
color={'primary'}
variant={'contained'}
disabled={isDisabled}
disabled={isDisabled || balanace < ESTIMATE_FEE}
loading={
isLoading ||
isLoadingCollections ||
Expand Down
3 changes: 2 additions & 1 deletion src/modules/AdminNft/ButtonCreateModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const ButtonCreateModal = () => {
<Close sx={{ color: grey[500] }} />
</IconButton>
</Box>
<Typography id="modal-modal-title" variant="h6" component="h2">

<Typography variant="h6" component="h2">
Create NFT
</Typography>
<Box mt={2}>
Expand Down
27 changes: 27 additions & 0 deletions src/modules/AdminNftCollection/ButtonCreateModal/NftForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { LoadingButton } from '@mui/lab';
import { Divider } from '@mui/material';
import Box from '@mui/material/Box';
import { useAccount } from '@usedapptesthello/usewallet';
import { FormContainer, SelectElement } from 'react-hook-form-mui';
import { useQueryClient } from 'react-query';

Expand All @@ -8,13 +10,16 @@ import ToastMessage from '@/components/Toast';
import { ContractType } from '@/enums/contractType.enum';
import { QueryKeys } from '@/enums/queryKeys.enum';
import { useMutateCreateNftCollection } from '@/hooks/mutations';
import { useGetAccountBalance } from '@/hooks/queries/useGetAccountBalance';
import SelectBenefitsField from '@/modules/core/SelectBenefitsField';
import { CreateNftCollectionParams } from '@/services/admin/nft-collection/types';

type NftFormProps = {
onSuccess?: () => void;
};

const ESTIMATE_FEE = 250;

const NftForm = ({ onSuccess }: NftFormProps) => {
const queryClient = useQueryClient();
const createNftCollectionMutation = useMutateCreateNftCollection({
Expand All @@ -29,6 +34,10 @@ const NftForm = ({ onSuccess }: NftFormProps) => {
onSuccess?.();
},
});
const { publicKey } = useAccount();
const { data: { balanace = 0 } = { balanace: 0 } } = useGetAccountBalance({
publicKey,
});

const handleOnSubmitForm = async (
createNftCollectionParams: CreateNftCollectionParams
Expand All @@ -46,6 +55,13 @@ const NftForm = ({ onSuccess }: NftFormProps) => {
}}
onSuccess={handleOnSubmitForm}
>
<Box mb="1rem">
<Box display={'flex'} justifyContent={'space-between'}>
<Box>Your Balance:</Box>
<Box>{balanace} CSPR</Box>
</Box>
</Box>

<StyledTextFieldElement name="name" label="Name" required />
<StyledTextFieldElement name="description" label="Description" />
<Box mt="1rem">
Expand All @@ -68,8 +84,19 @@ const NftForm = ({ onSuccess }: NftFormProps) => {
<SelectBenefitsField name="benefitIds" />
</Box>

<Box mt="1rem">
<Divider />
<Box display={'flex'} justifyContent={'space-between'} mt="1rem">
<Box>Estimate Fee:</Box>
<Box>{ESTIMATE_FEE} CSPR</Box>
</Box>
</Box>

<Box mt="1rem">
<LoadingButton
disabled={
createNftCollectionMutation.isLoading || balanace < ESTIMATE_FEE
}
loading={createNftCollectionMutation.isLoading}
type={'submit'}
color={'primary'}
Expand Down
13 changes: 4 additions & 9 deletions src/modules/AdminNftCollection/ButtonCreateModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,12 @@ const ButtonCreateModal = () => {
return (
<Box>
<Button variant="contained" onClick={handleOpen}>
Create Nft Collection
Create NFT Collection
</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Modal open={open} onClose={handleClose}>
<Box sx={style}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Create Nft Collection
<Typography variant="h6" component="h2">
Create NFT Collection
</Typography>
<Box mt={2}>
<NftForm onSuccess={handleOnSuccess} />
Expand Down
13 changes: 6 additions & 7 deletions src/services/casperdash/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import axios from 'axios';

import { Config } from '@/config';
import request from './request';

export const deploy = async (signedDeploy: unknown) => {
const {
data: { deployHash },
} = await axios.post(Config.deployUrl, signedDeploy);
const data = await request.post<{ deployHash: string }>(
'/deploy',
signedDeploy
);

return deployHash;
return (<{ deployHash: string }>(<unknown>data)).deployHash;
};
32 changes: 32 additions & 0 deletions src/services/casperdash/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import axios, { AxiosResponse } from 'axios';
import qs from 'qs';

import { Config } from '@/config';

const casperDashRequest = axios.create({
baseURL: Config.casperDashUrl,
timeout: 100 * 1000,
paramsSerializer: {
serialize: (params: Record<string, unknown>) =>
qs.stringify(params, { arrayFormat: 'repeat' }),
},
});

casperDashRequest.interceptors.response.use(
(response: AxiosResponse) => response.data,
(error) => {
const { status } = error.response;

if (status === 400) {
const {
data: { message },
} = error.response;

alert(message);
}

return Promise.reject(error);
}
);

export default casperDashRequest;
2 changes: 2 additions & 0 deletions src/services/casperdash/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './type';
export * from './user.service';
32 changes: 32 additions & 0 deletions src/services/casperdash/user/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export type Account = {
_accountHash: string;
namedKeys: {
name: string;
key: string;
}[];
mainPurse: string;
associatedKeys: {
accountHash: string;
weight: number;
}[];
actionThresholds: {
deployment: number;
keyManagement: number;
};
balance: {
hex: string;
type: string;
};
publicKey: string;
};

export type GetAccountsResponse = Account[];

export type GetAccountsParams = {
publicKeys: string[];
};

export type TBalance = {
hex: string;
type: string;
};
8 changes: 8 additions & 0 deletions src/services/casperdash/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { GetAccountsParams, GetAccountsResponse } from './type';
import request from '../request';

export const getAccounts = async ({
publicKeys,
}: GetAccountsParams): Promise<GetAccountsResponse> => {
return request.post('/users', { publicKeys });
};
7 changes: 7 additions & 0 deletions src/utils/casper/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ export const getAccountInfo = async (
return blockState.Account;
};

export const getAccount = async (publicKeyHex: string) => {
const cliPublicKey = CLPublicKey.fromHex(publicKeyHex);
const accountInfo = await getAccountInfo(cliPublicKey);

return accountInfo;
};

/**
* Returns a value under an on-chain account's storage.
* @param accountInfo - On-chain account's info.
Expand Down
2 changes: 1 addition & 1 deletion src/utils/casper/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const signDeployNft = async (
name,
tokenAddress,
tokenId,
paymentAmount = '30000000000',
paymentAmount = '20000000000',
}: SignDeployNftParams,
{ isWaiting = false }: { isWaiting: boolean } = { isWaiting: false }
) => {
Expand Down
Loading

0 comments on commit e6ab7d6

Please sign in to comment.