-
Notifications
You must be signed in to change notification settings - Fork 72
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 #1307 from matiasbenary/feat/add-linkdrop
Feat/add linkdrop
- Loading branch information
Showing
9 changed files
with
374 additions
and
3 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 |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { Button, Flex, Form, Input, openToast, Text } from '@near-pagoda/ui'; | ||
import { parseNearAmount } from 'near-api-js/lib/utils/format'; | ||
import { useContext, useEffect, useState } from 'react'; | ||
import type { SubmitHandler } from 'react-hook-form'; | ||
import { useForm } from 'react-hook-form'; | ||
|
||
import generateAndStore from '@/utils/linkdrops'; | ||
|
||
import { NearContext } from '../../WalletSelector'; | ||
|
||
type FormData = { | ||
dropName: string; | ||
numberLinks: number; | ||
amountPerLink: number; | ||
}; | ||
|
||
function displayBalance(balance: number) { | ||
let display = Math.floor(balance * 100) / 100; | ||
|
||
if (balance < 1) { | ||
display = Math.floor(balance * 100000) / 100000; | ||
if (balance && !display) return '< 0.00001'; | ||
return display; | ||
} | ||
|
||
return display; | ||
} | ||
|
||
const getDeposit = (amountPerLink: number, numberLinks: number) => | ||
parseNearAmount(((0.0426 + amountPerLink) * numberLinks).toString()); | ||
|
||
const CreateTokenDrop = () => { | ||
const { | ||
register, | ||
handleSubmit, | ||
formState: { errors, isSubmitting }, | ||
} = useForm<FormData>({ | ||
defaultValues: { | ||
numberLinks: 1, | ||
amountPerLink: 0, | ||
}, | ||
}); | ||
|
||
const { wallet, signedAccountId } = useContext(NearContext); | ||
const [currentNearAmount, setCurrentNearAmount] = useState(0); | ||
|
||
useEffect(() => { | ||
if (!wallet || !signedAccountId) return; | ||
|
||
const loadBalance = async () => { | ||
try { | ||
const balance = await wallet.getBalance(signedAccountId); | ||
const requiredGas = 0.00005; | ||
const cost = 0.0426; | ||
setCurrentNearAmount(balance - requiredGas - cost); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
}; | ||
|
||
loadBalance(); | ||
}, [wallet, signedAccountId]); | ||
|
||
const onSubmit: SubmitHandler<FormData> = async (data) => { | ||
if (!wallet) throw new Error('Wallet has not initialized yet'); | ||
|
||
try { | ||
const args = { | ||
deposit_per_use: parseNearAmount(data.amountPerLink.toString()), | ||
metadata: JSON.stringify({ | ||
dropName: data.dropName, | ||
}), | ||
public_keys: generateAndStore(data.dropName, data.numberLinks), | ||
}; | ||
|
||
await wallet.signAndSendTransactions({ | ||
transactions: [ | ||
{ | ||
receiverId: 'v2.keypom.near', | ||
actions: [ | ||
{ | ||
type: 'FunctionCall', | ||
params: { | ||
methodName: 'create_drop', | ||
args, | ||
gas: '300000000000000', | ||
deposit: getDeposit(data.amountPerLink, data.numberLinks), | ||
}, | ||
}, | ||
], | ||
}, | ||
], | ||
}); | ||
|
||
openToast({ | ||
type: 'success', | ||
title: 'Form Submitted', | ||
description: 'Your form has been submitted successfully', | ||
duration: 5000, | ||
}); | ||
} catch (error) { | ||
console.log(error); | ||
|
||
openToast({ | ||
type: 'error', | ||
title: 'Error', | ||
description: 'Failed to submit form', | ||
duration: 5000, | ||
}); | ||
} | ||
}; | ||
return ( | ||
<> | ||
<Text size="text-l" style={{ marginBottom: '12px' }}> | ||
Token Drop | ||
</Text> | ||
<Form onSubmit={handleSubmit(onSubmit)}> | ||
<Flex stack gap="l"> | ||
<Input | ||
label="Token Drop name" | ||
placeholder="NEARCon Token Giveaway" | ||
error={errors.dropName?.message} | ||
{...register('dropName', { required: 'Token Drop name is required' })} | ||
/> | ||
<Input | ||
label="Number of links" | ||
number={{ allowDecimal: false, allowNegative: false }} | ||
placeholder="1 - 50" | ||
error={errors.numberLinks?.message} | ||
{...register('numberLinks', { | ||
min: { | ||
message: 'Must be greater than 0', | ||
value: 1, | ||
}, | ||
max: { | ||
message: `Must be equal to or less than 50`, | ||
value: 50, | ||
}, | ||
valueAsNumber: true, | ||
required: 'Number of links is required', | ||
})} | ||
/> | ||
<Input | ||
label="Amount per link" | ||
number={{ | ||
allowNegative: false, | ||
allowDecimal: true, | ||
}} | ||
assistive={`${displayBalance(currentNearAmount)} available`} | ||
placeholder="Enter an amount" | ||
error={errors.amountPerLink?.message} | ||
{...register('amountPerLink', { | ||
min: { | ||
message: 'Must be greater than 0', | ||
value: 0.0000000001, | ||
}, | ||
max: { | ||
message: `Must be equal to or less than ${currentNearAmount}`, | ||
value: currentNearAmount, | ||
}, | ||
valueAsNumber: true, | ||
required: 'Amount per link is required', | ||
})} | ||
/> | ||
<Button label="Create links" variant="affirmative" type="submit" loading={isSubmitting} /> | ||
</Flex> | ||
</Form> | ||
</> | ||
); | ||
}; | ||
|
||
export default CreateTokenDrop; |
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,55 @@ | ||
import { Accordion, Badge, Button, copyTextToClipboard, Flex, Text, Tooltip } from '@near-pagoda/ui'; | ||
import { Copy } from '@phosphor-icons/react'; | ||
|
||
import type { Drops } from '@/utils/types'; | ||
|
||
const getDropName = (drop: Drops) => { | ||
return drop ? JSON.parse(drop.metadata).dropName : ''; | ||
}; | ||
|
||
const ListTokenDrop = ({ drops }: { drops: Drops[] }) => { | ||
return ( | ||
<Accordion.Root type="multiple"> | ||
{drops.map((drop) => { | ||
return ( | ||
<Accordion.Item key={drop.drop_id} value={drop.drop_id}> | ||
<Accordion.Trigger> | ||
{getDropName(drop)} - Claimed {drop.next_key_id - drop.registered_uses}/{drop.next_key_id} | ||
</Accordion.Trigger> | ||
<Accordion.Content> | ||
{drop.keys && | ||
drop.keys.map((key) => { | ||
const wasClaimed = !drop.information.some((info) => info.pk == key.public); | ||
return ( | ||
<Flex align="center" justify="space-between" key={key.private}> | ||
<Text style={{ maxWidth: '10rem' }} clampLines={1}> | ||
{key.private} | ||
</Text> | ||
<Badge | ||
label={wasClaimed ? 'Claimed' : 'Unclaimed'} | ||
variant={wasClaimed ? 'success' : 'neutral'} | ||
/> | ||
<Tooltip content="Copy Account ID"> | ||
<Button | ||
label="copy" | ||
onClick={() => { | ||
const url = 'https://app.mynearwallet.com' + '/linkdrop/v2.keypom.near/' + key.private; | ||
copyTextToClipboard(url, url); | ||
}} | ||
size="small" | ||
fill="outline" | ||
icon={<Copy />} | ||
/> | ||
</Tooltip> | ||
</Flex> | ||
); | ||
})} | ||
</Accordion.Content> | ||
</Accordion.Item> | ||
); | ||
})} | ||
</Accordion.Root> | ||
); | ||
}; | ||
|
||
export default ListTokenDrop; |
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 { Drops } from '@/utils/types'; | ||
|
||
import CreateTokenDrop from './CreateTokenDrop'; | ||
import ListTokenDrop from './ListTokenDrop'; | ||
|
||
const Linkdrops = ({ drops }: { drops: Drops[] }) => { | ||
return ( | ||
<> | ||
<CreateTokenDrop /> | ||
<ListTokenDrop drops={drops} /> | ||
</> | ||
); | ||
}; | ||
|
||
export default Linkdrops; |
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
File renamed without changes.
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,54 @@ | ||
import { useContext, useEffect, useState } from 'react'; | ||
|
||
import { NearContext } from '@/components/WalletSelector'; | ||
import { getKeypomKeys } from '@/utils/linkdrops'; | ||
import type { Drops } from '@/utils/types'; | ||
|
||
const useLinkdrops = () => { | ||
const { signedAccountId } = useContext(NearContext); | ||
const [drops, setDrops] = useState<Drops[]>([]); | ||
|
||
const { wallet } = useContext(NearContext); | ||
|
||
useEffect(() => { | ||
const fetchDropData = async () => { | ||
if (!wallet || !signedAccountId) return; | ||
const fetchedDrops: Drops[] = await wallet.viewMethod({ | ||
contractId: 'v2.keypom.near', | ||
method: 'get_drops_for_owner', | ||
args: { account_id: signedAccountId }, | ||
}); | ||
|
||
const filteredDrops = fetchedDrops.filter( | ||
(drop) => | ||
drop.metadata && | ||
JSON.parse(drop.metadata).dropName && | ||
getKeypomKeys(JSON.parse(drop.metadata).dropName).length, | ||
); | ||
|
||
const fetchedInformationDrops = await Promise.all( | ||
filteredDrops.map(async (drop) => { | ||
const information = await wallet.viewMethod({ | ||
contractId: 'v2.keypom.near', | ||
method: 'get_keys_for_drop', | ||
args: { drop_id: drop.drop_id }, | ||
}); | ||
return { ...drop, information }; | ||
}), | ||
); | ||
|
||
const localDataDrops = fetchedInformationDrops.map((drop) => ({ | ||
...drop, | ||
keys: getKeypomKeys(JSON.parse(drop.metadata).dropName), | ||
})); | ||
|
||
setDrops(localDataDrops); | ||
}; | ||
|
||
fetchDropData(); | ||
}, [wallet, signedAccountId]); | ||
|
||
return drops; | ||
}; | ||
|
||
export default useLinkdrops; |
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,40 @@ | ||
import { KeyPair } from 'near-api-js'; | ||
|
||
export interface Keys { | ||
publicKey: PublicKey; | ||
secretKey: string; | ||
extendedSecretKey: string; | ||
} | ||
|
||
export interface PublicKey { | ||
keyType: number; | ||
data: { [key: string]: number }; | ||
} | ||
|
||
export const getKeypomKeys = (dropName: string) => { | ||
const keys = localStorage.getItem(`keysPom:${dropName}`); | ||
if (keys) { | ||
return JSON.parse(keys); | ||
} | ||
return []; | ||
}; | ||
|
||
const setKeypomKeys = (dropName: string, keys: Keys[]) => { | ||
localStorage.setItem(`keysPom:${dropName}`, JSON.stringify(keys)); | ||
}; | ||
|
||
const generateAndStore = (dropName: string, dropsNumber: number) => { | ||
const keys = []; | ||
const keysLocalStorage = getKeypomKeys(dropName); | ||
for (let index = 0; index < dropsNumber; index++) { | ||
const newKeyPair = KeyPair.fromRandom('ed25519'); | ||
const publicKey = newKeyPair.getPublicKey().toString(); | ||
keys.push(publicKey); | ||
keysLocalStorage.push({ private: newKeyPair.toString(), public: publicKey }); | ||
} | ||
setKeypomKeys(dropName, keysLocalStorage); | ||
|
||
return keys; | ||
}; | ||
|
||
export default generateAndStore; |
Oops, something went wrong.