Skip to content

Commit b9342cc

Browse files
authored
switch networks POC (#176)
* Adds ability to switch networks to node app - Adds network selector to onboarding flow and main layout - Updates styling of selectors - Changes user setting to store network and remove storing data dir - Bug fix in SendAsset when no accounts exist * comment explaining usequery
1 parent bd3f937 commit b9342cc

File tree

15 files changed

+494
-100
lines changed

15 files changed

+494
-100
lines changed

main/api/ironfish/Ironfish.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@ import fsAsync from "fs/promises";
22

33
import {
44
ALL_API_NAMESPACES,
5+
DEFAULT_DATA_DIR,
6+
DatabaseIsLockedError,
57
FullNode,
6-
PEER_STORE_FILE_NAME,
78
IronfishSdk,
9+
NodeFileProvider,
10+
PEER_STORE_FILE_NAME,
811
RpcClient,
912
RpcMemoryClient,
1013
getPackageFrom,
11-
DatabaseIsLockedError,
1214
} from "@ironfish/sdk";
1315
import log from "electron-log";
1416
import { v4 as uuid } from "uuid";
1517

1618
import { logger } from "./logger";
1719
import packageJson from "../../../package.json";
1820
import { SnapshotManager } from "../snapshot/snapshotManager";
21+
import { getUserSettings } from "../user-settings/userSettings";
1922
import { SplitPromise, splitPromise } from "../utils";
2023

2124
export class Ironfish {
@@ -26,27 +29,40 @@ export class Ironfish {
2629
private _started: boolean = false;
2730
private _fullNode: FullNode | null = null;
2831
private _initialized: boolean = false;
29-
private _dataDir: string;
32+
private _networkId: number;
3033

31-
constructor(dataDir: string) {
32-
this._dataDir = dataDir;
34+
constructor({ networkId }: { networkId: number }) {
35+
this._networkId = networkId;
3336
}
3437

35-
private constructSdk() {
36-
return IronfishSdk.init({
37-
dataDir: this._dataDir,
38+
private async constructSdk() {
39+
const dataDir = await this.getDataDir();
40+
return await IronfishSdk.init({
41+
dataDir,
3842
logger: logger,
3943
pkg: getPackageFrom(packageJson),
4044
configOverrides: {
4145
databaseMigrate: true,
4246
},
47+
internalOverrides: {
48+
networkId: this._networkId,
49+
},
4350
});
4451
}
4552

53+
private async getDataDir() {
54+
const fileSystem = new NodeFileProvider();
55+
await fileSystem.init();
56+
const path = fileSystem.resolve(DEFAULT_DATA_DIR);
57+
58+
return this._networkId === 0 ? path + "-testnet" : path;
59+
}
60+
4661
private constructNode(sdk: IronfishSdk) {
4762
return sdk.node({
4863
privateIdentity: sdk.getPrivateIdentity(),
4964
autoSeed: true,
65+
networkId: this._networkId,
5066
});
5167
}
5268

@@ -160,6 +176,23 @@ export class Ironfish {
160176
this.sdkPromise = splitPromise();
161177
}
162178

179+
async changeNetwork(networkId: number) {
180+
if (this._networkId === networkId) {
181+
return;
182+
}
183+
184+
await this.stop();
185+
186+
this._networkId = networkId;
187+
188+
const settingsStore = await getUserSettings();
189+
settingsStore.set({
190+
networkId,
191+
});
192+
193+
await this.init();
194+
}
195+
163196
async restart() {
164197
await this.stop();
165198
await this.init();

main/api/ironfish/index.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,21 @@ export const ironfishRouter = t.router({
7777
const response = await rpcClient.node.getStatus();
7878
return response.content;
7979
}),
80-
getInitialState: t.procedure.query(async () => {
81-
return manager.getInitialState();
82-
}),
80+
getInitialState: t.procedure
81+
.input(
82+
z
83+
.object({
84+
seed: z.string().optional(),
85+
})
86+
.optional(),
87+
)
88+
.query(
89+
async ({
90+
input: _, // Hack to skip the cache. Trpc doesn't support queryKey yet.
91+
}) => {
92+
return await manager.getInitialState();
93+
},
94+
),
8395
shouldDownloadSnapshot: t.procedure.query(async () => {
8496
return manager.shouldDownloadSnapshot();
8597
}),
@@ -113,4 +125,14 @@ export const ironfishRouter = t.router({
113125
const ironfish = await manager.getIronfish();
114126
await ironfish.start();
115127
}),
128+
changeNetwork: t.procedure
129+
.input(
130+
z.object({
131+
network: z.enum(["MAINNET", "TESTNET"]),
132+
}),
133+
)
134+
.mutation(async (opts) => {
135+
const ironfish = await manager.getIronfish();
136+
await ironfish.changeNetwork(opts.input.network === "MAINNET" ? 1 : 0);
137+
}),
116138
});

main/api/manager.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ export type InitialState =
1010

1111
export class Manager {
1212
private _ironfish: Ironfish | null = null;
13-
private _initialState: InitialState | null = null;
1413

1514
async getIronfish(): Promise<Ironfish> {
1615
if (this._ironfish) return this._ironfish;
1716

1817
const userSettings = await getUserSettings();
19-
const dataDir = userSettings.get("dataDir");
20-
this._ironfish = new Ironfish(dataDir);
18+
const networkId = userSettings.get("networkId");
19+
this._ironfish = new Ironfish({
20+
networkId,
21+
});
2122
return this._ironfish;
2223
}
2324

@@ -36,8 +37,6 @@ export class Manager {
3637
};
3738

3839
async getInitialState(): Promise<InitialState> {
39-
if (this._initialState) return this._initialState;
40-
4140
const ironfish = await this.getIronfish();
4241

4342
if (!ironfish.isStarted()) {

main/api/snapshot/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export const snapshotRouter = t.router({
3636
}),
3737
downloadSnapshot: t.procedure.mutation(async () => {
3838
const ironfish = await manager.getIronfish();
39+
if (ironfish.isStarted()) {
40+
await ironfish.stop();
41+
await ironfish.init();
42+
}
3943
ironfish.downloadSnapshot();
4044
}),
4145
});

main/api/user-settings/userSettings.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import { DEFAULT_DATA_DIR, NodeFileProvider } from "@ironfish/sdk";
1+
import { DEFAULT_NETWORK_ID, NodeFileProvider } from "@ironfish/sdk";
22
import Store, { Schema } from "electron-store";
33
import { z } from "zod";
44

55
const STORE_NAME = "user-settings";
66

77
export const settingsZodSchema = z.object({
8-
dataDir: z.string(),
8+
networkId: z.number(),
99
theme: z.enum(["light", "dark", "system"]),
1010
locale: z.string().nullable(),
1111
});
1212

1313
export type SchemaDefinition = z.infer<typeof settingsZodSchema>;
1414

1515
export const settingsSchema: Schema<SchemaDefinition> = {
16-
dataDir: {
17-
type: "string",
16+
networkId: {
17+
type: "number",
1818
},
1919
theme: {
2020
enum: ["light", "dark", "system"],
@@ -36,7 +36,7 @@ export async function getUserSettings() {
3636
schema: settingsSchema,
3737
name: STORE_NAME,
3838
defaults: {
39-
dataDir: fileSystem.resolve(DEFAULT_DATA_DIR),
39+
networkId: DEFAULT_NETWORK_ID,
4040
theme: "system",
4141
locale: null,
4242
},

renderer/components/LanguageSelector/LanguageSelector.tsx

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import {
2-
Text,
2+
Box,
3+
Flex,
4+
FlexProps,
5+
Heading,
36
Modal,
4-
ModalOverlay,
5-
ModalContent,
67
ModalBody,
7-
Heading,
8+
ModalContent,
89
ModalFooter,
10+
ModalOverlay,
11+
Text,
912
useDisclosure,
10-
Flex,
11-
Box,
12-
FlexProps,
1313
} from "@chakra-ui/react";
1414
import { useForm } from "react-hook-form";
15-
import { FaChevronDown } from "react-icons/fa";
15+
import { FaChevronRight } from "react-icons/fa";
1616
import { MdOutlineLanguage } from "react-icons/md";
1717
import { defineMessages, useIntl } from "react-intl";
1818
import { z } from "zod";
1919

20-
import { useSelectedLocaleContext, Locales } from "@/intl/IntlProvider";
20+
import { Locales, useSelectedLocaleContext } from "@/intl/IntlProvider";
2121
import { COLORS } from "@/ui/colors";
2222
import { Select } from "@/ui/Forms/Select/Select";
2323
import { PillButton } from "@/ui/PillButton/PillButton";
@@ -111,9 +111,16 @@ export function LanguageSelector({ buttonContainerProps }: Props) {
111111
borderRadius="5px"
112112
bg={COLORS.GRAY_LIGHT}
113113
color={COLORS.GRAY_MEDIUM}
114-
justifyContent="center"
114+
justifyContent={{
115+
base: "center",
116+
md: "space-between",
117+
}}
115118
alignItems="center"
116119
py="6px"
120+
px={{
121+
base: 0,
122+
md: "18px",
123+
}}
117124
_dark={{
118125
bg: COLORS.DARK_MODE.GRAY_MEDIUM,
119126
color: COLORS.DARK_MODE.GRAY_LIGHT,
@@ -129,25 +136,28 @@ export function LanguageSelector({ buttonContainerProps }: Props) {
129136
onClick={onOpen}
130137
{...buttonContainerProps}
131138
>
132-
<MdOutlineLanguage />
133-
<Text
134-
ml={2}
135-
mr={3}
136-
as="span"
137-
display={{
138-
base: "none",
139-
md: "block",
140-
}}
141-
>
142-
{formatMessage(selectedLanguage.message)}
143-
</Text>
139+
<Flex alignItems="center" justifyContent="center">
140+
<MdOutlineLanguage />
141+
<Text
142+
ml={18}
143+
mr={3}
144+
as="span"
145+
display={{
146+
base: "none",
147+
md: "block",
148+
}}
149+
>
150+
{formatMessage(selectedLanguage.message)}
151+
</Text>
152+
</Flex>
153+
144154
<Box
145155
display={{
146156
base: "none",
147157
md: "block",
148158
}}
149159
>
150-
<FaChevronDown fontSize="0.6em" />
160+
<FaChevronRight fontSize="0.6em" />
151161
</Box>
152162
</Flex>
153163
<LanguageSelectorModal isOpen={isOpen} onClose={onClose} />

0 commit comments

Comments
 (0)