-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1309 from matiasbenary/feat/add-ft-tools
added ft tools
- Loading branch information
Showing
12 changed files
with
1,729 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,7 +15,7 @@ interface Nft { | |
} | ||
|
||
interface NftImageProps { | ||
nft: Nft; | ||
nft?: Nft; | ||
ipfs_cid?: string; | ||
alt: string; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
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 = { | ||
total_supply: string; | ||
name: string; | ||
symbol: string; | ||
icon: FileList; | ||
decimals: number; | ||
}; | ||
|
||
const FACTORY_CONTRACT = 'tkn.primitives.near'; | ||
|
||
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 { wallet, signedAccountId } = useContext(NearContext); | ||
|
||
const validateImage = (files: FileList) => { | ||
if (files.length === 0) return 'Image is required'; | ||
const file = files[0]; | ||
if (file.size > MAX_FILE_SIZE) return 'Image size should be less than 10KB'; | ||
if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) return 'Not a valid image format'; | ||
return true; | ||
}; | ||
|
||
const convertToBase64 = (file: File): Promise<string> => { | ||
return new Promise((resolve, reject) => { | ||
const reader = new FileReader(); | ||
reader.readAsDataURL(file); | ||
reader.onload = () => resolve(reader.result as string); | ||
reader.onerror = (error) => reject(error); | ||
}); | ||
}; | ||
|
||
const onSubmit: SubmitHandler<FormData> = async (data) => { | ||
let base64Image = ''; | ||
if (data.icon[0]) { | ||
base64Image = await convertToBase64(data.icon[0]); | ||
} | ||
|
||
const total_supply = BigInt(data.total_supply) * BigInt(Math.pow(10, Number(data.decimals))); | ||
|
||
const args = { | ||
args: { | ||
owner_id: signedAccountId, | ||
total_supply: total_supply.toString(), | ||
metadata: { | ||
spec: 'ft-1.0.0', | ||
name: data.name, | ||
symbol: data.symbol, | ||
icon: base64Image, | ||
decimals: data.decimals, | ||
}, | ||
}, | ||
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, | ||
}, | ||
}, | ||
], | ||
}, | ||
], | ||
}); | ||
|
||
if (result) { | ||
const transactionId = result[0].transaction_outcome.id; | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
window.open(`https://nearblocks.io/txns/${transactionId}`, '_blank')!.focus(); | ||
} | ||
|
||
openToast({ | ||
type: 'success', | ||
title: 'Token Created', | ||
description: `Token ${data.name} (${data.symbol}) created successfully`, | ||
duration: 5000, | ||
}); | ||
} catch (error) { | ||
openToast({ | ||
type: 'error', | ||
title: 'Error', | ||
description: 'Failed to create token', | ||
duration: 5000, | ||
}); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<Text size="text-l" style={{ marginBottom: '12px' }}> | ||
Mint a Fungible Token | ||
</Text> | ||
<Form onSubmit={handleSubmit(onSubmit)}> | ||
<Flex stack gap="l"> | ||
<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} | ||
name="icon" | ||
rules={{ | ||
required: 'Image is required', | ||
validate: validateImage, | ||
}} | ||
render={({ field, fieldState }) => ( | ||
<FileInput | ||
label="Image Upload" | ||
accept={ACCEPTED_IMAGE_TYPES.join(',')} | ||
error={fieldState.error?.message} | ||
{...field} | ||
value={field.value ? Array.from(field.value) : []} | ||
onChange={(value: File[] | null) => { | ||
const files = value; | ||
field.onChange(files); | ||
}} | ||
/> | ||
)} | ||
/> | ||
<span style={{ fontSize: '0.8rem', color: 'gray' }}> | ||
Accepted Formats: PNG, JPEG, GIF, SVG | Ideal dimension: 1:1 | Max size: 10kb | ||
</span> | ||
</div> | ||
|
||
<Button label="Create Token" variant="affirmative" type="submit" loading={isSubmitting} /> | ||
</Flex> | ||
</Form> | ||
</> | ||
); | ||
}; | ||
|
||
export default CreateTokenForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Accordion, Flex, Text } from '@near-pagoda/ui'; | ||
import Image from 'next/image'; | ||
|
||
import type { FT } from '@/pages/tools'; | ||
|
||
const ListToken = ({ tokens }: { tokens: FT[] }) => { | ||
return ( | ||
<Accordion.Root type="multiple"> | ||
<Accordion.Item value="one"> | ||
<Accordion.Trigger>Tokens you minted</Accordion.Trigger> | ||
<Accordion.Content> | ||
{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.Root> | ||
); | ||
}; | ||
|
||
export default ListToken; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type { FT } from '@/pages/tools'; | ||
|
||
import CreateTokenForm from './CreateTokenForm'; | ||
import ListToken from './ListToken'; | ||
|
||
const FungibleToken = ({ tokens }: { tokens: FT[] }) => { | ||
return ( | ||
<> | ||
<CreateTokenForm /> | ||
<hr /> | ||
<ListToken tokens={tokens} /> | ||
</> | ||
); | ||
}; | ||
export default FungibleToken; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { Text } from '@near-pagoda/ui'; | ||
import Image from 'next/image'; | ||
import styled from 'styled-components'; | ||
|
||
import MintBase from '@/assets/images/mintbase.svg'; | ||
import Paras from '@/assets/images/paras.svg'; | ||
|
||
const StyledButton = styled.a` | ||
background-color: #1e2030; | ||
color: #fff; | ||
border: none; | ||
border-radius: 25px; | ||
padding: 10px 20px; | ||
font-size: 16px; | ||
cursor: pointer; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
transition: background-color 0.3s; | ||
&:hover { | ||
background-color: #101124; | ||
} | ||
`; | ||
|
||
const MintbaseButton = styled(StyledButton)` | ||
background-color: #1e2030; | ||
&:hover { | ||
background-color: #282b3b; | ||
} | ||
`; | ||
|
||
const ParasButton = styled(StyledButton)` | ||
background-color: #050330; | ||
&:hover { | ||
background-color: #101438; | ||
} | ||
`; | ||
|
||
const CommunityTools = () => { | ||
return ( | ||
<> | ||
<Text size="text-l" style={{ margin: '12px 0 0 0' }}> | ||
Community tools | ||
</Text> | ||
<Text>For more advanced options use community tools:</Text> | ||
<MintbaseButton href="https://www.mintbase.xyz/" target="_blank"> | ||
<Image alt="Mintbase Logo" src={MintBase} width={85} />{' '} | ||
</MintbaseButton> | ||
<ParasButton href="https://paras.id/" target="_blank"> | ||
<Image alt="Paras Logo" src={Paras} width={85} />{' '} | ||
</ParasButton> | ||
</> | ||
); | ||
}; | ||
|
||
export default CommunityTools; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Accordion, Text } from '@near-pagoda/ui'; | ||
import Image from 'next/image'; | ||
import styled from 'styled-components'; | ||
|
||
import type { NFT } from '@/pages/tools'; | ||
|
||
const RoundedImage = styled(Image)` | ||
border-radius: 50%; | ||
`; | ||
|
||
const CarouselContainer = styled.div` | ||
display: flex; | ||
overflow-x: auto; | ||
width: 100%; | ||
scrollbar-width: thin; | ||
&::-webkit-scrollbar { | ||
height: 8px; | ||
} | ||
&::-webkit-scrollbar-thumb { | ||
background-color: rgba(0, 0, 0, 0.3); | ||
border-radius: 4px; | ||
} | ||
`; | ||
|
||
const ImgCard = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
padding: 8px; | ||
margin: 4px; | ||
border-radius: 6px; | ||
cursor: pointer; | ||
`; | ||
|
||
const ListToken = ({ tokens }: { tokens: NFT[] }) => { | ||
return ( | ||
<Accordion.Root type="multiple"> | ||
<Accordion.Item value="one"> | ||
<Accordion.Trigger>NFT you minted</Accordion.Trigger> | ||
<Accordion.Content> | ||
<CarouselContainer> | ||
{tokens.map((token) => { | ||
return ( | ||
<ImgCard key={`Carousel-${token.token_id}`}> | ||
<RoundedImage width={43} height={43} src={token.media} alt={token.title} /> | ||
<Text>{token.title}</Text> | ||
</ImgCard> | ||
); | ||
})} | ||
</CarouselContainer> | ||
</Accordion.Content> | ||
</Accordion.Item> | ||
</Accordion.Root> | ||
); | ||
}; | ||
|
||
export default ListToken; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.