From 88401a6358c4bed51724c1ffb9aab46a2e086b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jeesun=20=EC=A7=80=EC=84=A0?= Date: Wed, 20 Mar 2024 21:10:01 +0100 Subject: [PATCH] Creat and Fund Account Page (#788) --- .nvmrc | 2 +- src/app/(sidebar)/account/create/page.tsx | 2 +- src/app/(sidebar)/account/fund/page.tsx | 118 ++++++++++++++++++- src/app/(sidebar)/account/styles.scss | 5 + src/components/FormElements/PubKeyPicker.tsx | 60 ++++------ src/components/NetworkSelector/index.tsx | 7 +- src/helpers/shortenStellarAddress.ts | 7 ++ src/helpers/validatePublicKey.ts | 17 +++ src/query/useFriendBot.ts | 43 +++++++ 9 files changed, 216 insertions(+), 45 deletions(-) create mode 100644 src/helpers/shortenStellarAddress.ts create mode 100644 src/helpers/validatePublicKey.ts create mode 100644 src/query/useFriendBot.ts diff --git a/.nvmrc b/.nvmrc index 3f430af8..0828ab79 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18 +v18 \ No newline at end of file diff --git a/src/app/(sidebar)/account/create/page.tsx b/src/app/(sidebar)/account/create/page.tsx index 110e1c74..ace07355 100644 --- a/src/app/(sidebar)/account/create/page.tsx +++ b/src/app/(sidebar)/account/create/page.tsx @@ -53,7 +53,7 @@ export default function CreateAccount() { - + Fund Account; + const { account, network } = useStore(); + + const [showAlert, setShowAlert] = useState(false); + const [generatedPublicKey, setGeneratedPublicKey] = useState(""); + const [inlineErrorMessage, setInlineErrorMessage] = useState(""); + + const { error, isError, isLoading, isSuccess, refetch, isFetchedAfterMount } = + useFriendBot({ + network: network.id, + publicKey: generatedPublicKey, + }); + + useEffect(() => { + if (isError || isSuccess) { + setShowAlert(true); + } + }, [isError, isSuccess]); + + return ( +
+ +
+
+ + Friendbot: fund a {network.id} network account + + + + The friendbot is a horizon API endpoint that will fund an account + with 10,000 lumens on the {network.id} network. + +
+ + { + setGeneratedPublicKey(e.target.value); + + const error = validatePublicKey(e.target.value); + setInlineErrorMessage(error); + }} + placeholder="Ex: GCEXAMPLE5HWNK4AYSTEQ4UWDKHTCKADVS2AHF3UI2ZMO3DPUSM6Q4UG" + error={inlineErrorMessage} + /> + +
+ + + +
+
+
+ + {showAlert && isFetchedAfterMount && isSuccess && ( + { + setShowAlert(false); + }} + title={`Successfully funded ${shortenStellarAddress(account.publicKey)} on + ${network.id}`} + > + {""} + + )} + {showAlert && isFetchedAfterMount && isError && ( + { + setShowAlert(false); + }} + title={error?.message} + > + {""} + + )} +
+ ); } diff --git a/src/app/(sidebar)/account/styles.scss b/src/app/(sidebar)/account/styles.scss index 1115f112..6bc7c7e4 100644 --- a/src/app/(sidebar)/account/styles.scss +++ b/src/app/(sidebar)/account/styles.scss @@ -1,6 +1,10 @@ @use "../../../styles/utils.scss" as *; .Account { + display: flex; + flex-direction: column; + gap: pxToRem(12px); + &__card { display: flex; flex-direction: column; @@ -15,6 +19,7 @@ display: flex; gap: pxToRem(18px) pxToRem(8px); } + &__keypair { display: flex; align-items: flex-start; diff --git a/src/components/FormElements/PubKeyPicker.tsx b/src/components/FormElements/PubKeyPicker.tsx index aecb34c2..456dfbb5 100644 --- a/src/components/FormElements/PubKeyPicker.tsx +++ b/src/components/FormElements/PubKeyPicker.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { StrKey } from "stellar-sdk"; import { Input, InputProps } from "@stellar/design-system"; +import { validatePublicKey } from "@/helpers/validatePublicKey"; interface PubKeyPickerProps extends Omit { id: string; @@ -27,41 +27,23 @@ export const PubKeyPicker = ({ onChange, onBlur, ...props -}: PubKeyPickerProps) => { - const validatePublicKey = (issuer: string) => { - if (!issuer) { - return "Asset issuer is required."; - } - - if (issuer.startsWith("M")) { - if (!StrKey.isValidMed25519PublicKey(issuer)) { - return "Muxed account address is invalid."; - } - } else if (!StrKey.isValidEd25519PublicKey(issuer)) { - return "Public key is invalid."; - } - - return ""; - }; - - return ( - { - const error = validatePublicKey(e.target.value); - onChange(e.target.value, error); - }} - onBlur={(e) => { - const error = validatePublicKey(e.target.value); - onBlur(e.target.value, error); - }} - error={error} - {...props} - /> - ); -}; +}: PubKeyPickerProps) => ( + { + const error = validatePublicKey(e.target.value); + onChange(e.target.value, error); + }} + onBlur={(e) => { + const error = validatePublicKey(e.target.value); + onBlur(e.target.value, error); + }} + error={error} + {...props} + /> +); diff --git a/src/components/NetworkSelector/index.tsx b/src/components/NetworkSelector/index.tsx index 624fd707..08b43b9d 100644 --- a/src/components/NetworkSelector/index.tsx +++ b/src/components/NetworkSelector/index.tsx @@ -6,6 +6,7 @@ import React, { useState, } from "react"; import { Button, Icon, Input, Notification } from "@stellar/design-system"; +import { Networks } from "stellar-sdk"; import { NetworkIndicator } from "@/components/NetworkIndicator"; import { localStorageSavedNetwork } from "@/helpers/localStorageSavedNetwork"; @@ -20,21 +21,21 @@ const NetworkOptions: Network[] = [ label: "Futurenet", horizonUrl: "https://horizon-futurenet.stellar.org", rpcUrl: "https://rpc-futurenet.stellar.org", - passphrase: "Test SDF Future Network ; October 2022", + passphrase: Networks.FUTURENET, }, { id: "testnet", label: "Testnet", horizonUrl: "https://horizon-testnet.stellar.org", rpcUrl: "https://soroban-testnet.stellar.org", - passphrase: "Test SDF Network ; September 2015", + passphrase: Networks.TESTNET, }, { id: "mainnet", label: "Mainnet", horizonUrl: "https://horizon.stellar.org", rpcUrl: "", - passphrase: "Public Global Stellar Network ; September 2015", + passphrase: Networks.PUBLIC, }, { id: "custom", diff --git a/src/helpers/shortenStellarAddress.ts b/src/helpers/shortenStellarAddress.ts new file mode 100644 index 00000000..7f1cf488 --- /dev/null +++ b/src/helpers/shortenStellarAddress.ts @@ -0,0 +1,7 @@ +export const shortenStellarAddress = (address: string, size: number = 8) => { + const slice = Math.ceil(size / 2); + + return `${address.substring(0, slice)}…${address.substring( + address.length - slice, + )}`; +}; diff --git a/src/helpers/validatePublicKey.ts b/src/helpers/validatePublicKey.ts new file mode 100644 index 00000000..5fb16a69 --- /dev/null +++ b/src/helpers/validatePublicKey.ts @@ -0,0 +1,17 @@ +import { StrKey } from "stellar-sdk"; + +export const validatePublicKey = (issuer: string) => { + if (!issuer) { + return "Asset issuer is required."; + } + + if (issuer.startsWith("M")) { + if (!StrKey.isValidMed25519PublicKey(issuer)) { + return "Muxed account address is invalid."; + } + } else if (!StrKey.isValidEd25519PublicKey(issuer)) { + return "Public key is invalid."; + } + + return ""; +}; diff --git a/src/query/useFriendBot.ts b/src/query/useFriendBot.ts new file mode 100644 index 00000000..6f3d6123 --- /dev/null +++ b/src/query/useFriendBot.ts @@ -0,0 +1,43 @@ +import { useQuery } from "@tanstack/react-query"; + +import { shortenStellarAddress } from "@/helpers/shortenStellarAddress"; + +export const useFriendBot = ({ + network, + publicKey, +}: { + network: string; + publicKey: string; +}) => { + const friendbotURL = + network === "futurenet" + ? "https://friendbot-futurenet.stellar.org" + : "https://friendbot.stellar.org"; + + const query = useQuery({ + queryKey: ["friendBot"], + queryFn: async () => { + try { + const response = await fetch(`${friendbotURL}/?addr=${publicKey}`); + + if (!response.ok) { + const errorBody = await response.json(); + + throw new Error(errorBody.status); + } + return response; + } catch (e: any) { + if (e.status === 0) { + throw new Error(`Unable to reach Friendbot server at ${network}`); + } else { + throw new Error( + `Unable to fund ${shortenStellarAddress(publicKey)} on the test network`, + ); + } + } + }, + enabled: false, + }); + + return query; +};