Skip to content

Commit

Permalink
feat: added ft tools,cleaned a styled code
Browse files Browse the repository at this point in the history
  • Loading branch information
matiasbenary committed Sep 20, 2024
2 parents 1d0de77 + 05204d7 commit 90f0d17
Show file tree
Hide file tree
Showing 15 changed files with 1,833 additions and 1,519 deletions.
2 changes: 1 addition & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const nextConfig = {
compiler: { styledComponents: true },
reactStrictMode: true,
images: {
domains: ['ipfs.near.social'],
domains: ['ipfs.near.social','ipfs.io'],
},
experimental: {
optimizePackageImports: ['@phosphor-icons/react'],
Expand Down
22 changes: 20 additions & 2 deletions src/components/NTFImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,33 @@ interface NftImageProps {

const DEFAULT_IMAGE = 'https://ipfs.near.social/ipfs/bafkreibmiy4ozblcgv3fm3gc6q62s55em33vconbavfd2ekkuliznaq3zm';

const getImage = (key: string) => {
const imgUrl = localStorage.getItem(`keysImage:${key}`);
return imgUrl || null;
};

const setImage = (key: string, url: string) => {
localStorage.setItem(`keysImage:${key}`, url);
};

export const NftImage: React.FC<NftImageProps> = ({ nft, ipfs_cid, alt }) => {
const { wallet } = useContext(NearContext);
const [imageUrl, setImageUrl] = useState<string>(DEFAULT_IMAGE);

const fetchNftData = useCallback(async () => {
if (!wallet || !nft || !nft.contractId || !nft.tokenId || ipfs_cid) return;

const imgCache = getImage(nft.tokenId);
if (imgCache) {
setImageUrl(imgCache);
return;
}
const [nftMetadata, tokenData] = await Promise.all([
wallet.viewMethod({ contractId: nft.contractId, method: 'nft_metadata' }),
wallet.viewMethod({ contractId: nft.contractId, method: 'nft_token', args: { token_id: nft.tokenId } }),
]);

const tokenMetadata = tokenData.metadata;
const tokenMedia = tokenMetadata?.media || '';
const tokenMedia = tokenData?.metadata?.media || '';

if (tokenMedia.startsWith('https://') || tokenMedia.startsWith('http://') || tokenMedia.startsWith('data:image')) {
setImageUrl(tokenMedia);
Expand All @@ -54,5 +67,10 @@ export const NftImage: React.FC<NftImageProps> = ({ nft, ipfs_cid, alt }) => {
}
}, [ipfs_cid, fetchNftData]);

useEffect(() => {
if (!wallet || !nft || !nft.contractId || !nft.tokenId || ipfs_cid || DEFAULT_IMAGE === imageUrl) return;
setImage(nft.tokenId, imageUrl);
}, [imageUrl, wallet, nft, ipfs_cid]);

return <RoundedImage width={43} height={43} src={imageUrl} alt={alt} />;
};
134 changes: 68 additions & 66 deletions src/components/tools/FungibleToken/CreateTokenForm.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { NearContext } from '@/components/WalletSelector';
import { Button, FileInput, Flex, Form, Input, openToast, Text } from '@near-pagoda/ui';
import { Button, FileInput, Flex, Form, Grid, Input, openToast, Text } from '@near-pagoda/ui';
import React, { useContext } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { Controller, useForm } from 'react-hook-form';

import { NearContext } from '@/components/WalletSelector';

type FormData = {
owner_id: string;
total_supply: string;
name: string;
symbol: string;
Expand All @@ -19,7 +19,12 @@ const MAX_FILE_SIZE = 10 * 1024;
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'];

const CreateTokenForm: React.FC = () => {
const { control, register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>();
const {
control,
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>();

const { wallet, signedAccountId } = useContext(NearContext);

Expand All @@ -45,40 +50,44 @@ const CreateTokenForm: React.FC = () => {
if (data.icon[0]) {
base64Image = await convertToBase64(data.icon[0]);
}
const total_supply = BigInt(data.total_supply) * BigInt(10) ** BigInt(data.decimals)

const total_supply = BigInt(data.total_supply) * BigInt(Math.pow(10, Number(data.decimals)));

const args = {
args: {
owner_id: data.owner_id,
owner_id: signedAccountId,
total_supply: total_supply.toString(),
metadata: {
spec: "ft-1.0.0",
spec: 'ft-1.0.0',
name: data.name,
symbol: data.symbol,
icon: base64Image,
decimals: data.decimals,
},
},
account_id: data.owner_id,
account_id: signedAccountId,
};

const requiredDeposit = await wallet?.viewMethod({ contractId: FACTORY_CONTRACT, method: 'get_required', args });

try {
const result = await wallet?.signAndSendTransactions({
transactions: [{
receiverId: FACTORY_CONTRACT,
actions: [
{
type: 'FunctionCall',
params: {
methodName: 'create_token',
args,
gas: "300000000000000",
deposit: requiredDeposit
transactions: [
{
receiverId: FACTORY_CONTRACT,
actions: [
{
type: 'FunctionCall',
params: {
methodName: 'create_token',
args,
gas: '300000000000000',
deposit: requiredDeposit,
},
},
},
],
}]
],
},
],
});

if (result) {
Expand Down Expand Up @@ -110,30 +119,40 @@ const CreateTokenForm: React.FC = () => {
</Text>
<Form onSubmit={handleSubmit(onSubmit)}>
<Flex stack gap="l">
<Input
label="Owner ID"
placeholder="e.g., bob.near"
error={errors.owner_id?.message}
{...register('owner_id', { required: 'Owner ID is required', value: signedAccountId })}
/>
<Input
label="Total Supply"
placeholder="e.g., 1000"
error={errors.total_supply?.message}
{...register('total_supply', { required: 'Total supply is required' })}
/>
<Input
label="Token Name"
placeholder="e.g., Test Token"
error={errors.name?.message}
{...register('name', { required: 'Token name is required' })}
/>
<Input
label="Token Symbol"
placeholder="e.g., TEST"
error={errors.symbol?.message}
{...register('symbol', {})}
/>
<Grid columns="1fr 1fr" columnsTablet="1fr" columnsPhone="1fr">
<Input
label="Total Supply"
placeholder="e.g., 1000"
error={errors.total_supply?.message}
{...register('total_supply', { required: 'Total supply is required' })}
/>
<Input
label="Decimals"
type="number"
placeholder="e.g., 6"
error={errors.decimals?.message}
{...register('decimals', {
required: 'Decimals is required',
valueAsNumber: true,
min: { value: 0, message: 'Decimals must be non-negative' },
max: { value: 24, message: 'Decimals must be 24 or less' },
})}
/>
</Grid>
<Grid columns="1fr 1fr" columnsTablet="1fr" columnsPhone="1fr">
<Input
label="Token Name"
placeholder="e.g., Test Token"
error={errors.name?.message}
{...register('name', { required: 'Token name is required' })}
/>
<Input
label="Token Symbol"
placeholder="e.g., TEST"
error={errors.symbol?.message}
{...register('symbol', { required: 'Token symbol is required' })}
/>
</Grid>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Controller
control={control}
Expand All @@ -160,29 +179,12 @@ const CreateTokenForm: React.FC = () => {
Accepted Formats: PNG, JPEG, GIF, SVG | Ideal dimension: 1:1 | Max size: 10kb
</span>
</div>
<Input
label="Decimals"
type="number"
placeholder="e.g., 6"
error={errors.decimals?.message}
{...register('decimals', {
required: 'Decimals is required',
valueAsNumber: true,
min: { value: 0, message: 'Decimals must be non-negative' },
max: { value: 24, message: 'Decimals must be 24 or less' }
})}
/>
<Button
label="Create Token"
variant="affirmative"
type="submit"
loading={isSubmitting}
/>

<Button label="Create Token" variant="affirmative" type="submit" loading={isSubmitting} />
</Flex>
</Form>
</>
);
};


export default CreateTokenForm;
export default CreateTokenForm;
35 changes: 18 additions & 17 deletions src/components/tools/FungibleToken/ListToken.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import { Accordion, Flex, Text } from "@near-pagoda/ui";
import Image from "next/image";
import { Accordion, Flex, Text } from '@near-pagoda/ui';
import Image from 'next/image';

const ListToken = ({ tokens }) => {

import type { FT } from '@/pages/tools';

const ListToken = ({ tokens }: { tokens: FT[] }) => {
return (
<Accordion.Root type="multiple">
<Accordion.Item value="one">
<Accordion.Item value="one">
<Accordion.Trigger>Tokens you minted</Accordion.Trigger>
<Accordion.Content>
{tokens.map((token) => {
return (
<Flex justify="space-between" align="center">
<Text>{token.name}</Text>
<Text>{token.symbol}</Text>
<Text>{token.total_supply/ 10 ** token.decimals}</Text>
<Image src={token.icon} alt={token.name} width={50} height={50}/>
</Flex>
);
})}
{tokens.map((token) => {
return (
<Flex justify="space-between" align="center" key={`ft-${token.symbol}`}>
<Text>{token.name}</Text>
<Text>{token.symbol}</Text>
<Text>{BigInt(token.total_supply) / BigInt(Math.pow(10, Number(token.decimals)))}</Text>
<Image src={token.icon} alt={token.name} width={50} height={50} />
</Flex>
);
})}
</Accordion.Content>
</Accordion.Item>
</Accordion.Item>
</Accordion.Root>
);
};

export default ListToken;
export default ListToken;
49 changes: 3 additions & 46 deletions src/components/tools/FungibleToken/index.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,15 @@
// https://dev.near.org/contractwizard.near/widget/ContractWizardUI
// curl https://api.fastnear.com/v1/account/here.tg/ft
// https://github.com/fastnear/fastnear-api-server-rs?tab=readme-ov-file#api-v1
// near call tkn.near create_token '{"args":{"owner_id": "maguila.near","total_supply": "1000000000","metadata":{"spec": "ft-1.0.0","name": "Test Token","symbol": "TTTEST","icon": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7","decimals": 18}},"account_id": "maguila.near"}' --gas 300000000000000 --depositYocto 2234830000000000000000000 --accountId maguila.near --networkId mainnet
// https://docs.near.org/build/primitives/ft
import type { FT } from '@/pages/tools';

import { Card, Flex, SvgIcon, Text, Title, Tooltip } from '@near-pagoda/ui';
import { CheckFat, ListNumbers, PlusCircle } from '@phosphor-icons/react';
import { useState } from 'react';
import CreateTokenForm from './CreateTokenForm';
import ListToken from './ListToken';


const formattedBalance = (balance: string, decimals = 24) => {
const numericBalance = Number(balance);
if (isNaN(numericBalance) || isNaN(decimals)) {
return '0';
}
const result = numericBalance / Math.pow(10, decimals);
return result % 1 === 0 ? result.toString() : result.toFixed(5).replace(/\.?0+$/, '');
};

const FungibleToken = ({ tokens }) => {

const FungibleToken = ({ tokens }: { tokens: FT[] }) => {
return (
<>
<CreateTokenForm />
<hr />
<ListToken tokens={tokens}/>
<ListToken tokens={tokens} />
</>
);
};
export default FungibleToken;


/*
<hr />
<Title> </Title>
{tokens.map((token, index) => (
<Card key={index} style={{ marginBottom: '8px' }}>
<Flex align="center" justify="space-between">
<Flex align="center" style={{ flex: "1" }} >
<Text>{token.icon && <img width={25} height={25} alt={token.symbol} src={token.icon} />}</Text>
</Flex>
<Text style={{ flex: "1" }} size="text-l">{formattedBalance(token.balance, token.decimals)}</Text>
<Flex justify="end" align='center' style={{ flex: "1" }}>
<Text>{token.symbol}</Text>
{/* {token.verified && ( *\/}
<Tooltip content="It is verified">
<SvgIcon icon={<CheckFat /> /*<SealCheck />*\/} size="m" color="violet8" />
</Tooltip>
{/* )} \/}
</Flex>
</Flex>
</Card>
))}
*/
Loading

0 comments on commit 90f0d17

Please sign in to comment.