Skip to content

Commit

Permalink
feat: implement airdrop and unknown transaction status
Browse files Browse the repository at this point in the history
  • Loading branch information
sohrab- committed Aug 2, 2022
1 parent b9ce3ea commit 7311f70
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 59 deletions.
16 changes: 4 additions & 12 deletions src/components/client/accounts/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useWallet } from "@solana/wallet-adapter-react";
import { Keypair } from "@solana/web3.js";
import { WritableDraft } from "immer/dist/internal";
import React, { useContext } from "react";
import { FaKey, FaParachuteBox, FaPenNib, FaWallet } from "react-icons/fa";
import { FaKey, FaPenNib, FaWallet } from "react-icons/fa";
import { useInstruction } from "../../../hooks/useInstruction";
import { usePersistentStore } from "../../../hooks/usePersistentStore";
import { useSessionStoreWithUndo } from "../../../hooks/useSessionStore";
Expand All @@ -26,6 +26,7 @@ import { ExplorerButton } from "../../common/ExplorerButton";
import { Numbering } from "../../common/Numbering";
import { SortableItemContext } from "../../common/Sortable";
import { ToggleIconButton } from "../../common/ToggleIconButton";
import { AirdropButton } from "./AirdropButton";

export const Account: React.FC<{ data: IAccount; index: number }> = ({
data,
Expand Down Expand Up @@ -129,19 +130,10 @@ export const Account: React.FC<{ data: IAccount; index: number }> = ({
});
}}
></Input>
<InputRightElement w="65px">
<InputRightElement w="60px">
{isValid ? (
<>
<Tooltip label="Airdrop SOL">
<IconButton
ml="1"
size="xs"
variant="ghost"
aria-label="Airdrop SOL"
icon={<FaParachuteBox />}
isDisabled={rpcEndpoint.network === "mainnet-beta"}
/>
</Tooltip>
<AirdropButton accountPubkey={data.pubkey} />
<ExplorerButton
size="xs"
valueType="account"
Expand Down
170 changes: 170 additions & 0 deletions src/components/client/accounts/AirdropButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { CheckCircleIcon, WarningIcon } from "@chakra-ui/icons";
import {
Button,
Center,
Heading,
HStack,
Icon,
IconButton,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Popover,
PopoverArrow,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
Spinner,
Text,
VStack,
} from "@chakra-ui/react";
import { useConnection } from "@solana/wallet-adapter-react";
import { PublicKey } from "@solana/web3.js";
import React, { useState } from "react";
import { FaParachuteBox } from "react-icons/fa";
import { useGetWeb3Transaction } from "../../../hooks/useGetWeb3Transaction";
import { usePersistentStore } from "../../../hooks/usePersistentStore";
import { IPubKey } from "../../../models/internal-types";
import { toLamports } from "../../../models/web3js-mappers";

export const AirdropButton: React.FC<{ accountPubkey: IPubKey }> = ({
accountPubkey,
}) => {
const [value, setValue] = useState("1.000000000");
const [message, setMessage] = useState("");
const [status, setStatus] = useState<"idle" | "running" | "success" | "fail">(
"idle"
);
const rpcEndpoint = usePersistentStore(
(state) => state.transactionOptions.rpcEndpoint
);

const { connection } = useConnection();
const {
// TODO once we have link explorer, set it in
// signature,
start: startConfirmation,
cancel,
} = useGetWeb3Transaction({
onStatus: (status) => {
setMessage(`Confirmed by ${status.confirmations || 0}`);
},
onFinalised: () => {
setMessage("Airdop has been successful");
setStatus("success");
},
onError: (error) => {
setMessage(`Error: ${error.message}`);
setStatus("fail");
},
onTimeout: () => {
setMessage("Error: Time out");
setStatus("fail");
},
});

const airdop = () => {
setStatus("running");
setMessage("Sending transcation...");
connection
.requestAirdrop(new PublicKey(accountPubkey), parseInt(value))
.then((signature) => {
startConfirmation(signature);
});
};

return (
<Popover placement="left">
<PopoverTrigger>
{/* TODO tooltips don't play nice with Popovers */}
<IconButton
size="xs"
variant="ghost"
aria-label="Airdrop SOL"
icon={<FaParachuteBox />}
isDisabled={rpcEndpoint.network === "mainnet-beta"}
/>
</PopoverTrigger>

<PopoverContent>
<PopoverArrow />
<PopoverCloseButton />
<PopoverHeader>
<Heading size="sm">Airdrop SOL</Heading>
</PopoverHeader>
<PopoverBody>
<VStack mt="2" mb="3">
<Text mb="2">Add more SOL to this account.</Text>

<NumberInput
size="sm"
min={0}
precision={9}
allowMouseWheel
value={value.toLocaleString()}
onChange={setValue}
>
<NumberInputField fontFamily="mono" textAlign="center" />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>

<Text as="i">{`${(toLamports(value) || 0).toFormat(
0
)} Lamports`}</Text>
</VStack>
</PopoverBody>
<PopoverFooter>
{message && (
<HStack mb="3" justifyContent="center">
{status === "running" && <Spinner color="blue.400" size="sm" />}
{status === "success" && <CheckCircleIcon color="green.400" />}
{status === "fail" && <WarningIcon color="red.400" />}
<Text
textColor={
status === "running"
? "blue.400"
: status === "success"
? "green.400"
: status === "fail"
? "red.400"
: undefined
}
>
{message}
</Text>
</HStack>
)}
<Center mt="1" mb="1">
{status === "running" ? (
<Button
size="sm"
colorScheme="red"
leftIcon={<Icon as={FaParachuteBox} />}
onClick={cancel}
>
Cancel
</Button>
) : (
<Button
size="sm"
colorScheme="teal"
leftIcon={<Icon as={FaParachuteBox} />}
onClick={airdop}
>
Airdop
</Button>
)}
</Center>
</PopoverFooter>
</PopoverContent>
</Popover>
);
};
71 changes: 43 additions & 28 deletions src/components/client/results/Results.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { CheckCircleIcon, WarningIcon } from "@chakra-ui/icons";
import {
CheckCircleIcon,
QuestionIcon,
RepeatIcon,
WarningIcon,
} from "@chakra-ui/icons";
import {
Button,
Flex,
Grid,
Heading,
IconButton,
Spacer,
Tab,
TabList,
Expand All @@ -28,8 +34,6 @@ import { ProgramLogs } from "./ProgramLogs";
import { Signature } from "./Signature";

type State = {
startedAt?: number;
finalisedAt?: number;
slot?: number;
confirmations?: number;
confirmationStatus?: TransactionConfirmationStatus;
Expand All @@ -54,6 +58,7 @@ export const Results: React.FC = () => {
const {
signature,
inProgress,
finalisedAt,
start: startWeb3Transaction,
cancel: cancelWeb3Transaction,
} = useGetWeb3Transaction({
Expand All @@ -79,11 +84,6 @@ export const Results: React.FC = () => {
setInProgress(false);
},
onTimeout: () => {
// TODO should be reflected in the badge instead
setResults({
...results,
error: "Transcation faield to confirm",
});
setInProgress(false);
},
onError: (error) => {
Expand Down Expand Up @@ -121,24 +121,23 @@ export const Results: React.FC = () => {
<Heading mb="6" mr="3" size="md">
Results
</Heading>
{results.confirmationStatus === "finalized" &&
(results.error ? (
<Tooltip label="Transaction returned a failure">
{results.confirmationStatus === "finalized" ? (
results.error ? (
<Tooltip label="Failed Transaction">
<WarningIcon mt="0.5" mr="1" color="red.400" />
</Tooltip>
) : (
<Tooltip label="Transaction returned a success">
<Tooltip label="Successful Transction">
<CheckCircleIcon mt="1" mr="1" color="green.400" />
</Tooltip>
))}
{results.finalisedAt && (
<Tooltip label={new Date(results.finalisedAt).toLocaleString()}>
<Tag height="20px">
{`Finalised @ ${new Date(
results.finalisedAt
).toLocaleTimeString()}`}
</Tag>
</Tooltip>
)
) : (
signature &&
!inProgress && (
<Tooltip label="Unknown Transaction Status">
<QuestionIcon mt="1" mr="1" color="yellow.400" />
</Tooltip>
)
)}

<Spacer />
Expand All @@ -162,11 +161,33 @@ export const Results: React.FC = () => {
: `Finalised by max confirmations`}
</Tag>
)}
{finalisedAt && (
<Tooltip
label={`Last fetched @ ${new Date(finalisedAt).toLocaleString()}`}
>
<Tag height="20px" variant="outline">
{new Date(finalisedAt).toLocaleTimeString()}
</Tag>
</Tooltip>
)}
{inProgress && (
<Button color="red.600" variant="outline" size="xs" onClick={cancel}>
Cancel
</Button>
)}
<Tooltip label="Refresh">
<IconButton
mt="-1"
aria-label="Refresh"
icon={<RepeatIcon />}
variant="ghost"
size="sm"
isDisabled={!signature || inProgress}
onClick={() => {
start(signature);
}}
/>
</Tooltip>
</Flex>

<ErrorAlert
Expand All @@ -176,13 +197,7 @@ export const Results: React.FC = () => {
}}
/>

<Signature
signature={signature}
inProgress={inProgress}
refresh={start}
slot={results.slot}
fee={results.fee}
/>
<Signature signature={signature} slot={results.slot} fee={results.fee} />

<Tabs colorScheme="main" variant="enclosed">
<TabList>
Expand Down
20 changes: 1 addition & 19 deletions src/components/client/results/Signature.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { RepeatIcon } from "@chakra-ui/icons";
import {
Box,
Flex,
FormLabel,
IconButton,
Input,
InputGroup,
InputRightElement,
Tag,
Tooltip,
} from "@chakra-ui/react";
import React from "react";
import { usePersistentStore } from "../../../hooks/usePersistentStore";
Expand All @@ -18,11 +15,9 @@ import { ExplorerButton } from "../../common/ExplorerButton";

export const Signature: React.FC<{
signature: string;
inProgress: boolean;
refresh: (signature: string) => void;
slot?: number;
fee?: number;
}> = ({ signature, inProgress, refresh, slot, fee }) => {
}> = ({ signature, slot, fee }) => {
const rpcEndpoint = usePersistentStore(
(state) => state.transactionOptions.rpcEndpoint
);
Expand Down Expand Up @@ -59,19 +54,6 @@ export const Signature: React.FC<{
/>
</InputRightElement>
</InputGroup>

<Tooltip label="Refresh">
<IconButton
aria-label="Refresh"
icon={<RepeatIcon />}
variant="ghost"
size="sm"
isDisabled={!signature || inProgress}
onClick={() => {
refresh(signature);
}}
/>
</Tooltip>
</Flex>

<Flex mb="4">
Expand Down
3 changes: 3 additions & 0 deletions src/models/web3js-mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { toSortedArray } from "./sortable";
export const toSol = (x: number): BigNumber =>
new BigNumber(x).div(new BigNumber(LAMPORTS_PER_SOL));

export const toLamports = (x: number | string): BigNumber =>
new BigNumber(x).multipliedBy(new BigNumber(LAMPORTS_PER_SOL));

const SHORT_TRUNC_TO = 7;
/** Shortens public keys to a truncated format */
export const short = (pubkey: string) =>
Expand Down

0 comments on commit 7311f70

Please sign in to comment.