Skip to content

Commit bc20fe8

Browse files
committed
feat: make extension optional
1 parent e116d62 commit bc20fe8

File tree

2 files changed

+109
-93
lines changed

2 files changed

+109
-93
lines changed

apps/faucet/src/App/App.tsx

Lines changed: 4 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import React, { createContext, useCallback, useEffect, useState } from "react";
1+
import React, { createContext, useEffect, useState } from "react";
22
import { GoGear } from "react-icons/go";
33
import { ThemeProvider } from "styled-components";
44

5-
import { ActionButton, Alert, Modal } from "@namada/components";
6-
import { Namada } from "@namada/integrations";
5+
import { Alert, Modal } from "@namada/components";
76
import { ColorMode, getTheme } from "@namada/utils";
87

98
import {
@@ -20,9 +19,6 @@ import {
2019
} from "App/App.components";
2120
import { FaucetForm } from "App/Faucet";
2221

23-
import { chains } from "@namada/chains";
24-
import { useUntil } from "@namada/hooks";
25-
import { Account } from "@namada/types";
2622
import { API, toNam } from "utils";
2723
import dotsBackground from "../../public/bg-dots.svg";
2824
import {
@@ -82,21 +78,9 @@ const START_TIME_TEXT = new Date(START_TIME_UTC * 1000).toLocaleString(
8278

8379
export const AppContext = createContext<AppContext | null>(null);
8480

85-
enum ExtensionAttachStatus {
86-
PendingDetection,
87-
NotInstalled,
88-
Installed,
89-
}
90-
9181
export const App: React.FC = () => {
9282
const initialColorMode = "dark";
93-
const chain = chains.namada;
94-
const integration = new Namada(chain);
95-
const [extensionAttachStatus, setExtensionAttachStatus] = useState(
96-
ExtensionAttachStatus.PendingDetection
97-
);
98-
const [isExtensionConnected, setIsExtensionConnected] = useState(false);
99-
const [accounts, setAccounts] = useState<Account[]>([]);
83+
10084
const [colorMode, _] = useState<ColorMode>(initialColorMode);
10185
const [isTestnetLive, setIsTestnetLive] = useState(true);
10286
const [settings, setSettings] = useState<Settings>({
@@ -129,20 +113,6 @@ export const App: React.FC = () => {
129113
});
130114
};
131115

132-
useUntil(
133-
{
134-
predFn: async () => Promise.resolve(integration.detect()),
135-
onSuccess: () => {
136-
setExtensionAttachStatus(ExtensionAttachStatus.Installed);
137-
},
138-
onFail: () => {
139-
setExtensionAttachStatus(ExtensionAttachStatus.NotInstalled);
140-
},
141-
},
142-
{ tries: 5, ms: 300 },
143-
[integration]
144-
);
145-
146116
useEffect(() => {
147117
// Sync url to localStorage
148118
localStorage.setItem("baseUrl", url);
@@ -168,27 +138,6 @@ export const App: React.FC = () => {
168138
.catch((e) => setSettingsError(`Failed to load settings! ${e}`));
169139
}, [url]);
170140

171-
const handleConnectExtensionClick = useCallback(async (): Promise<void> => {
172-
if (integration) {
173-
try {
174-
const isIntegrationDetected = integration.detect();
175-
176-
if (!isIntegrationDetected) {
177-
throw new Error("Extension not installed!");
178-
}
179-
180-
await integration.connect();
181-
const accounts = await integration.accounts();
182-
if (accounts) {
183-
setAccounts(accounts.filter((account) => !account.isShielded));
184-
}
185-
setIsExtensionConnected(true);
186-
} catch (e) {
187-
console.error(e);
188-
}
189-
}
190-
}, [integration]);
191-
192141
return (
193142
<AppContext.Provider
194143
value={{
@@ -227,29 +176,7 @@ export const App: React.FC = () => {
227176
</InfoContainer>
228177
)}
229178

230-
{extensionAttachStatus ===
231-
ExtensionAttachStatus.PendingDetection && (
232-
<InfoContainer>
233-
<Alert type="info">Detecting extension...</Alert>
234-
</InfoContainer>
235-
)}
236-
{extensionAttachStatus === ExtensionAttachStatus.NotInstalled && (
237-
<InfoContainer>
238-
<Alert type="error">You must download the extension!</Alert>
239-
</InfoContainer>
240-
)}
241-
242-
{isExtensionConnected && (
243-
<FaucetForm accounts={accounts} isTestnetLive={isTestnetLive} />
244-
)}
245-
{extensionAttachStatus === ExtensionAttachStatus.Installed &&
246-
!isExtensionConnected && (
247-
<InfoContainer>
248-
<ActionButton onClick={handleConnectExtensionClick}>
249-
Connect to Namada Extension
250-
</ActionButton>
251-
</InfoContainer>
252-
)}
179+
<FaucetForm isTestnetLive={isTestnetLive} />
253180
</FaucetContainer>
254181
{isModalOpen && (
255182
<Modal onClose={() => setIsModalOpen(false)}>

apps/faucet/src/App/Faucet.tsx

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import {
77
Alert,
88
AmountInput,
99
Input,
10+
Option,
1011
Select,
1112
} from "@namada/components";
1213
import { Account } from "@namada/types";
1314
import { bech32mValidation, shortenAddress } from "@namada/utils";
1415

16+
import { chains } from "@namada/chains";
17+
import { useUntil } from "@namada/hooks";
18+
import { Namada } from "@namada/integrations";
1519
import { Data, PowChallenge, TransferResponse } from "../utils";
1620
import { AppContext } from "./App";
1721
import {
@@ -33,18 +37,28 @@ enum Status {
3337
}
3438

3539
type Props = {
36-
accounts: Account[];
3740
isTestnetLive: boolean;
3841
};
3942

4043
const bech32mPrefix = "tnam";
4144

42-
export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
45+
enum ExtensionAttachStatus {
46+
PendingDetection,
47+
NotInstalled,
48+
Installed,
49+
}
50+
51+
export const FaucetForm: React.FC<Props> = ({ isTestnetLive }) => {
4352
const {
4453
api,
4554
settings: { difficulty, tokens, withdrawLimit },
4655
} = useContext(AppContext)!;
56+
const [extensionAttachStatus, setExtensionAttachStatus] = useState(
57+
ExtensionAttachStatus.PendingDetection
58+
);
59+
const [isExtensionConnected, setIsExtensionConnected] = useState(false);
4760

61+
const [accounts, setAccounts] = useState<Account[]>([]);
4862
const accountLookup = accounts.reduce(
4963
(acc, account) => {
5064
acc[account.address] = account;
@@ -53,24 +67,37 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
5367
{} as Record<string, Account>
5468
);
5569

56-
const [account, setAccount] = useState<Account>(accounts[0]);
70+
const chain = chains.namada;
71+
const integration = new Namada(chain);
72+
const [account, setAccount] = useState<Account>();
73+
const [accountsSelectData, setAccountsSelectData] = useState<
74+
Option<string>[]
75+
>([]);
76+
const [target, setTarget] = useState<string>();
5777
const [tokenAddress, setTokenAddress] = useState<string>();
5878
const [amount, setAmount] = useState<number | undefined>(undefined);
5979
const [error, setError] = useState<string>();
6080
const [status, setStatus] = useState(Status.Completed);
6181
const [statusText, setStatusText] = useState<string>();
6282
const [responseDetails, setResponseDetails] = useState<TransferResponse>();
6383

64-
const accountsSelectData = accounts.map(({ alias, address }) => ({
65-
label: `${alias} - ${shortenAddress(address)}`,
66-
value: address,
67-
}));
68-
6984
const powSolver: Worker = useMemo(
7085
() => new Worker(new URL("../workers/powWorker.ts", import.meta.url)),
7186
[]
7287
);
7388

89+
useEffect(() => {
90+
if (accounts) {
91+
setAccountsSelectData(
92+
accounts.map(({ alias, address }) => ({
93+
label: `${alias} - ${shortenAddress(address)}`,
94+
value: address,
95+
}))
96+
);
97+
setAccount(accounts[0]);
98+
}
99+
}, [accounts]);
100+
74101
useEffect(() => {
75102
if (tokens?.NAM) {
76103
setTokenAddress(tokens.NAM);
@@ -81,7 +108,7 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
81108
Boolean(tokenAddress) &&
82109
Boolean(amount) &&
83110
(amount || 0) <= withdrawLimit &&
84-
Boolean(account) &&
111+
Boolean(target) &&
85112
status !== Status.PendingPowSolution &&
86113
status !== Status.PendingTransfer &&
87114
typeof difficulty !== "undefined" &&
@@ -127,7 +154,7 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
127154
async (e: React.MouseEvent<HTMLButtonElement>) => {
128155
e.preventDefault();
129156
if (
130-
!account ||
157+
!target ||
131158
!amount ||
132159
!tokenAddress ||
133160
typeof difficulty === "undefined"
@@ -145,9 +172,9 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
145172
return;
146173
}
147174

148-
if (!account) {
175+
if (!target) {
149176
setStatus(Status.Error);
150-
setError("No account found!");
177+
setError("No target specified!");
151178
return;
152179
}
153180

@@ -175,7 +202,7 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
175202
tag,
176203
challenge,
177204
transfer: {
178-
target: account.address,
205+
target,
179206
token: sanitizedToken,
180207
amount: amount * 1_000_000,
181208
},
@@ -190,14 +217,59 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
190217
[account, tokenAddress, amount]
191218
);
192219

220+
useUntil(
221+
{
222+
predFn: async () => Promise.resolve(integration.detect()),
223+
onSuccess: () => {
224+
setExtensionAttachStatus(ExtensionAttachStatus.Installed);
225+
},
226+
onFail: () => {
227+
setExtensionAttachStatus(ExtensionAttachStatus.NotInstalled);
228+
},
229+
},
230+
{ tries: 5, ms: 300 },
231+
[integration]
232+
);
233+
234+
const handleConnectExtensionClick = useCallback(
235+
async (e: React.MouseEvent<HTMLButtonElement>): Promise<void> => {
236+
e.preventDefault();
237+
if (integration) {
238+
try {
239+
const isIntegrationDetected = integration.detect();
240+
241+
if (!isIntegrationDetected) {
242+
throw new Error("Extension not installed!");
243+
}
244+
245+
await integration.connect();
246+
const accounts = await integration.accounts();
247+
if (accounts) {
248+
setAccounts(accounts.filter((account) => !account.isShielded));
249+
}
250+
setIsExtensionConnected(true);
251+
} catch (e) {
252+
console.error(e);
253+
}
254+
}
255+
},
256+
[integration]
257+
);
258+
259+
useEffect(() => {
260+
if (account) {
261+
setTarget(account.address);
262+
}
263+
}, [account]);
264+
193265
return (
194266
<FaucetFormContainer>
195267
<InputContainer>
196-
{accounts.length > 0 ?
268+
{account && accounts.length > 0 ?
197269
<Select
198270
data={accountsSelectData}
199271
value={account.address}
200-
label="Account"
272+
label="Target"
201273
onChange={(e) => setAccount(accountLookup[e.target.value])}
202274
/>
203275
: <div>
@@ -207,12 +279,29 @@ export const FaucetForm: React.FC<Props> = ({ accounts, isTestnetLive }) => {
207279
}
208280
</InputContainer>
209281

282+
<InputContainer>
283+
<Input
284+
label="Target Address"
285+
value={target}
286+
onChange={(e) => setTarget(e.target.value)}
287+
autoFocus={true}
288+
/>
289+
</InputContainer>
290+
291+
{extensionAttachStatus === ExtensionAttachStatus.Installed &&
292+
!isExtensionConnected && (
293+
<InputContainer>
294+
<ActionButton onClick={handleConnectExtensionClick}>
295+
Load Accounts from Extension
296+
</ActionButton>
297+
</InputContainer>
298+
)}
299+
210300
<InputContainer>
211301
<Input
212302
label="Token Address (defaults to NAM)"
213303
value={tokenAddress}
214304
onChange={(e) => setTokenAddress(e.target.value)}
215-
autoFocus={true}
216305
/>
217306
</InputContainer>
218307

0 commit comments

Comments
 (0)