Skip to content

Commit

Permalink
refactor - Faucet: clean up faucet API & pow utils (#627)
Browse files Browse the repository at this point in the history
* fix: clean up faucet utils and return types

* fix: organize utils
  • Loading branch information
jurevans authored Feb 13, 2024
1 parent 4a362a1 commit 265900a
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 231 deletions.
13 changes: 9 additions & 4 deletions apps/faucet/src/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { FaucetForm } from "App/Faucet";
import { chains } from "@namada/chains";
import { useUntil } from "@namada/hooks";
import { Account, AccountType } from "@namada/types";
import { requestSettings } from "utils";
import { API } from "utils";
import dotsBackground from "../../public/bg-dots.svg";
import { CallToActionCard } from "./CallToActionCard";
import { CardsContainer } from "./Card.components";
Expand All @@ -42,6 +42,7 @@ const {

const apiUrl = isProxied ? `http://localhost:${proxyPort}/proxy` : faucetApiUrl;
const url = `${apiUrl}${faucetApiEndpoint}`;
const api = new API(url);
const limit = parseInt(faucetLimit);
const runFullNodeUrl = "https://docs.namada.net/operators/ledger";
const becomeBuilderUrl = "https://docs.namada.net/integrating-with-namada";
Expand All @@ -57,6 +58,7 @@ type AppContext = Settings & {
limit: number;
url: string;
settingsError?: string;
api: API;
};

const START_TIME_UTC = 1702918800;
Expand All @@ -81,6 +83,7 @@ export const AppContext = createContext<AppContext>({
...defaults,
limit,
url,
api,
});

enum ExtensionAttachStatus {
Expand Down Expand Up @@ -138,8 +141,9 @@ export const App: React.FC = () => {
// Fetch settings from faucet API
(async () => {
try {
const { difficulty, tokens_alias_to_address: tokens } =
await requestSettings(url).catch((e) => {
const { difficulty, tokens_alias_to_address: tokens } = await api
.settings()
.catch((e) => {
const message = e.errors?.message;
setSettingsError(
`Error requesting settings: ${message?.join(" ")}`
Expand Down Expand Up @@ -190,6 +194,7 @@ export const App: React.FC = () => {
settingsError,
limit,
url,
api,
...settings,
}}
>
Expand Down Expand Up @@ -225,9 +230,9 @@ export const App: React.FC = () => {
)}
{isExtensionConnected && (
<FaucetForm
isTestnetLive={isTestnetLive}
accounts={accounts}
integration={integration}
isTestnetLive={isTestnetLive}
/>
)}
{extensionAttachStatus === ExtensionAttachStatus.Installed &&
Expand Down
32 changes: 9 additions & 23 deletions apps/faucet/src/App/Faucet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@ import { Namada } from "@namada/integrations";
import { Account } from "@namada/types";
import { bech32mValidation, shortenAddress } from "@namada/utils";

import {
TransferResponse,
computePowSolution,
requestChallenge,
requestTransfer,
} from "../utils";
import { TransferResponse, computePowSolution } from "../utils";
import { AppContext } from "./App";
import { InfoContainer } from "./App.components";
import {
Expand Down Expand Up @@ -48,7 +43,7 @@ export const FaucetForm: React.FC<Props> = ({
integration,
isTestnetLive,
}) => {
const { difficulty, settingsError, limit, tokens, url } =
const { api, difficulty, settingsError, limit, tokens } =
useContext(AppContext);

const accountLookup = accounts.reduce(
Expand All @@ -58,7 +53,6 @@ export const FaucetForm: React.FC<Props> = ({
},
{} as Record<string, Account>
);

const [account, setAccount] = useState<Account>(accounts[0]);
const [tokenAddress, setTokenAddress] = useState<string>();
const [amount, setAmount] = useState<number | undefined>(undefined);
Expand Down Expand Up @@ -126,22 +120,14 @@ export const FaucetForm: React.FC<Props> = ({
throw new Error("Account does not have a public key!");
}

const { challenge, tag } =
(await requestChallenge(url, account.publicKey).catch(
({ message, code }) => {
throw new Error(`${code} - ${message}`);
}
)) || {};
if (!tag || !challenge) {
throw new Error("Request challenge did not return a valid response");
}
const { challenge, tag } = await api
.challenge(account.publicKey)
.catch(({ message, code }) => {
throw new Error(`Unable to request challenge: ${code} - ${message}`);
});

const solution = computePowSolution(challenge, difficulty || 0);

if (!solution) {
throw new Error("A solution was not computed!");
}

const signer = integration.signer();
if (!signer) {
throw new Error("signer not defined");
Expand All @@ -165,10 +151,10 @@ export const FaucetForm: React.FC<Props> = ({
},
};

const response = await requestTransfer(url, submitData).catch((e) => {
const response = await api.submitTransfer(submitData).catch((e) => {
console.info(e);
const { code, message } = e;
throw new Error(`Unable to request transfer: ${code} ${message}`);
throw new Error(`Unable to submit transfer: ${code} ${message}`);
});

if (response.sent) {
Expand Down
89 changes: 89 additions & 0 deletions apps/faucet/src/utils/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
ChallengeResponse,
Data,
ErrorResponse,
SettingsResponse,
TransferResponse,
} from "./types";

enum Endpoint {
Settings = "/setting",
Challenge = "/challenge",
Transfer = "",
}

export class API {
constructor(protected readonly url: string) {}

/**
* Wrapper for fetch requests to handle ReadableStream response when errors are received from API
*
* @param {string} endpoint
* @param {RequestInit} options
*
* @returns Object
*/
async request<T = unknown>(
endpoint: string,
options: RequestInit = { method: "GET" }
): Promise<T> {
return await fetch(new URL(`${this.url}${endpoint}`), {
...options,
})
.then((response) => {
if (response.ok) {
return response.json();
}
const reader = response?.body?.getReader();
const errors = reader
?.read()
.then(
(data): Promise<ErrorResponse> =>
Promise.reject(JSON.parse(new TextDecoder().decode(data.value)))
);
if (!errors) {
throw new Error("Unable to parse error response");
}
return errors;
})
.catch((e) => {
console.error(e);
return Promise.reject(e);
});
}

/**
* Request faucet settings
*
* @returns Object
*/
async settings(): Promise<SettingsResponse> {
return this.request(Endpoint.Settings);
}

/**
* Request challenge from endpoint url
*
* @param {string} publicKey
* @returns Object
*/
async challenge(publicKey: string): Promise<ChallengeResponse> {
return this.request(`${Endpoint.Challenge}/${publicKey}`);
}

/**
* Submit a transfer request
*
* @param {Data} data
* @returns {Object}
*/
async submitTransfer(data: Data): Promise<TransferResponse> {
return this.request(Endpoint.Transfer, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
});
}
}
Loading

0 comments on commit 265900a

Please sign in to comment.