Skip to content

Commit

Permalink
Creat and Fund Account Page (#788)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeesunikim authored Mar 20, 2024
1 parent 0f350cd commit 88401a6
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18
v18
2 changes: 1 addition & 1 deletion src/app/(sidebar)/account/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default function CreateAccount() {
</Button>
</div>
</div>
<ExpandBox isExpanded={Boolean(account.publicKey && secretKey)}>
<ExpandBox isExpanded={Boolean(account.publicKey)}>
<GenerateKeypair
publicKey={account.publicKey}
secretKey={secretKey}
Expand Down
118 changes: 117 additions & 1 deletion src/app/(sidebar)/account/fund/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,121 @@
"use client";

import { useEffect, useState } from "react";
import { Alert, Card, Input, Text, Button } from "@stellar/design-system";

import { shortenStellarAddress } from "@/helpers/shortenStellarAddress";
import { validatePublicKey } from "@/helpers/validatePublicKey";
import { useFriendBot } from "@/query/useFriendBot";
import { useStore } from "@/store/useStore";

import "../styles.scss";

export default function FundAccount() {
return <div>Fund Account</div>;
const { account, network } = useStore();

const [showAlert, setShowAlert] = useState<boolean>(false);
const [generatedPublicKey, setGeneratedPublicKey] = useState<string>("");
const [inlineErrorMessage, setInlineErrorMessage] = useState<string>("");

const { error, isError, isLoading, isSuccess, refetch, isFetchedAfterMount } =
useFriendBot({
network: network.id,
publicKey: generatedPublicKey,
});

useEffect(() => {
if (isError || isSuccess) {
setShowAlert(true);
}
}, [isError, isSuccess]);

return (
<div className="Account">
<Card>
<div className="Account__card">
<div className="CardText">
<Text size="lg" as="h1" weight="medium">
Friendbot: fund a {network.id} network account
</Text>

<Text size="sm" as="p">
The friendbot is a horizon API endpoint that will fund an account
with 10,000 lumens on the {network.id} network.
</Text>
</div>

<Input
id="generate-keypair-publickey"
fieldSize="md"
label="Public Key"
value={generatedPublicKey}
onChange={(e) => {
setGeneratedPublicKey(e.target.value);

const error = validatePublicKey(e.target.value);
setInlineErrorMessage(error);
}}
placeholder="Ex: GCEXAMPLE5HWNK4AYSTEQ4UWDKHTCKADVS2AHF3UI2ZMO3DPUSM6Q4UG"
error={inlineErrorMessage}
/>

<div className="Account__CTA">
<Button
disabled={!generatedPublicKey || Boolean(inlineErrorMessage)}
size="md"
variant={isFetchedAfterMount && isError ? "error" : "secondary"}
isLoading={isLoading}
onClick={() => {
if (!inlineErrorMessage) {
refetch();
}
}}
>
Get lumens
</Button>

<Button
disabled={!account.publicKey || isLoading}
size="md"
variant="tertiary"
onClick={() => {
setInlineErrorMessage("");
setGeneratedPublicKey(account.publicKey);
}}
>
Fill in with generated key
</Button>
</div>
</div>
</Card>

{showAlert && isFetchedAfterMount && isSuccess && (
<Alert
placement="inline"
variant="primary"
actionLabel="View on stellar.expert"
actionLink={`https://stellar.expert/explorer/${network.id}/account/${account.publicKey}`}
onClose={() => {
setShowAlert(false);
}}
title={`Successfully funded ${shortenStellarAddress(account.publicKey)} on
${network.id}`}
>
{""}
</Alert>
)}
{showAlert && isFetchedAfterMount && isError && (
<Alert
placement="inline"
variant="error"
onClose={() => {
setShowAlert(false);
}}
title={error?.message}
>
{""}
</Alert>
)}
</div>
);
}
5 changes: 5 additions & 0 deletions src/app/(sidebar)/account/styles.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
@use "../../../styles/utils.scss" as *;

.Account {
display: flex;
flex-direction: column;
gap: pxToRem(12px);

&__card {
display: flex;
flex-direction: column;
Expand All @@ -15,6 +19,7 @@
display: flex;
gap: pxToRem(18px) pxToRem(8px);
}

&__keypair {
display: flex;
align-items: flex-start;
Expand Down
60 changes: 21 additions & 39 deletions src/components/FormElements/PubKeyPicker.tsx
Original file line number Diff line number Diff line change
@@ -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<InputProps, "fieldSize"> {
id: string;
Expand All @@ -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 (
<Input
id={id}
fieldSize={fieldSize}
label={label}
labelSuffix={labelSuffix}
placeholder={placeholder}
value={value}
onChange={(e) => {
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) => (
<Input
id={id}
fieldSize={fieldSize}
label={label}
labelSuffix={labelSuffix}
placeholder={placeholder}
value={value}
onChange={(e) => {
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}
/>
);
7 changes: 4 additions & 3 deletions src/components/NetworkSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions src/helpers/shortenStellarAddress.ts
Original file line number Diff line number Diff line change
@@ -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,
)}`;
};
17 changes: 17 additions & 0 deletions src/helpers/validatePublicKey.ts
Original file line number Diff line number Diff line change
@@ -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 "";
};
43 changes: 43 additions & 0 deletions src/query/useFriendBot.ts
Original file line number Diff line number Diff line change
@@ -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;
};

0 comments on commit 88401a6

Please sign in to comment.