Skip to content

Commit

Permalink
Flow config API (#204)
Browse files Browse the repository at this point in the history
* add contracts/accounts api

* show account name in details

* run format
  • Loading branch information
bartolomej committed Nov 7, 2023
1 parent db459e3 commit 8ff0d1f
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 77 deletions.
4 changes: 4 additions & 0 deletions apps/electron/src/main/ipc/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export enum FlowserIpcEvent {
INTERACTIONS_LIST_TEMPLATES = 'INTERACTIONS_LIST_TEMPLATES',

FLOW_CLI_GET_INFO = 'FLOW_CLI_GET_INFO',

FLOW_CONFIG_GET_CONTRACTS = 'FLOW_CONFIG_GET_CONTRACTS',
FLOW_CONFIG_GET_ACCOUNTS = 'FLOW_CONFIG_GET_ACCOUNTS',

FLOW_TRANSACTION_SEND = 'FLOW_TRANSACTION_SEND',
FLOW_SCRIPT_EXECUTE = 'FLOW_SCRIPT_EXECUTE',
FLOW_ACCOUNT_GET_INDEX = 'FLOW_ACCOUNT_GET_INDEX',
Expand Down
3 changes: 3 additions & 0 deletions apps/electron/src/main/ipc/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function registerHandlers(appService: FlowserAppService) {
blockchainIndexService,
flowInteractionsService,
flowCliService,
flowConfigService,
goBindingsService,
flowGatewayService,
walletService,
Expand Down Expand Up @@ -91,6 +92,8 @@ export function registerHandlers(appService: FlowserAppService) {
chainId: 'flow-emulator',
}),
FLOW_CLI_GET_INFO: () => flowCliService.getVersion(),
FLOW_CONFIG_GET_CONTRACTS: () => flowConfigService.getContracts(),
FLOW_CONFIG_GET_ACCOUNTS: () => flowConfigService.getAccounts(),
FLOW_TRANSACTION_SEND: (
event: IpcMainInvokeEvent,
request: SendTransactionRequest,
Expand Down
9 changes: 9 additions & 0 deletions apps/electron/src/main/ipc/invokers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ipcRenderer } from 'electron';
import { FlowserWorkspace } from '@onflowser/api';
import {
ExecuteScriptRequest,
IFlowConfigService,
IFlowService,
IInteractionService,
IProcessManagerService,
Expand Down Expand Up @@ -66,6 +67,13 @@ const snapshots: ISnapshotService = {
ipcRenderer.invoke(FlowserIpcEvent.SNAPSHOTS_ROLLBACK_TO_HEIGHT, height),
};

const flowConfigService: IFlowConfigService = {
getContracts: () =>
ipcRenderer.invoke(FlowserIpcEvent.FLOW_CONFIG_GET_CONTRACTS),
getAccounts: () =>
ipcRenderer.invoke(FlowserIpcEvent.FLOW_CONFIG_GET_ACCOUNTS),
};

const processManagerService: IProcessManagerService = {
findAllLogsByProcessId: (processId: string) =>
ipcRenderer.invoke(FlowserIpcEvent.PROCESS_LOGS_LIST, processId),
Expand All @@ -92,6 +100,7 @@ export const electronInvokers = {
callback(value as unknown as number),
),
},
flowConfigService,
interactions,
workspaces,
snapshots,
Expand Down
1 change: 1 addition & 0 deletions apps/electron/src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export function App() {
>
<ServiceRegistryProvider
services={{
flowConfigService: window.electron.flowConfigService,
walletService: window.electron.wallet,
flowService: window.electron.flow,
interactionsService: window.electron.interactions,
Expand Down
6 changes: 0 additions & 6 deletions packages/api/src/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,6 @@ export interface FlowContract
address: string;
name: string;
code: string;
localConfig: FlowContractLocalConfig | undefined;
}

export interface FlowContractLocalConfig {
relativePath: string;
absolutePath: string;
}

// TODO(restructure-followup): Refactor FlowAccountStorage
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/flow-indexer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,9 +601,6 @@ export class FlowIndexerService {
return {
id: `${account.address}.${name}`,
address: ensurePrefixedAddress(account.address),
// TODO(restructure-followup): Populate local config if applicable
// Or even better, create a separate "workspace contracts" API and remove this field.
localConfig: undefined,
name: name,
code: account.contracts[name],
};
Expand Down
35 changes: 6 additions & 29 deletions packages/nodejs/src/flow-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { readFile, writeFile, watch } from "fs/promises";
import * as path from "path";
import { AbortController } from "node-abort-controller";
import * as fs from "fs";
import { IFlowserLogger, isObject } from "@onflowser/core";
import {
ensurePrefixedAddress,
IFlowserLogger,
isObject,
} from "@onflowser/core";
import { EventEmitter } from "node:events";

type FlowAddress = string;
Expand Down Expand Up @@ -68,7 +72,6 @@ type FlowJSON = {
// of multiple configuration formats.
export type FlowAbstractAccountConfig = {
name: string;
// Possibly without the '0x' prefix.
address: string;
privateKey: string | undefined;
};
Expand Down Expand Up @@ -138,7 +141,7 @@ export class FlowConfigService extends EventEmitter {
}
return {
name,
address: accountConfig.address,
address: ensurePrefixedAddress(accountConfig.address),
privateKey: this.getPrivateKey(accountConfig.key),
};
},
Expand Down Expand Up @@ -176,21 +179,6 @@ export class FlowConfigService extends EventEmitter {
return isSimpleFormat ? contractConfig : contractConfig?.source;
}

public async updateAccounts(
newOrUpdatedAccounts: FlowAbstractAccountConfig[],
): Promise<void> {
if (!this.config?.accounts) {
throw new Error("Accounts config not loaded");
}
for (const newOrUpdatedAccount of newOrUpdatedAccounts) {
this.config.accounts[newOrUpdatedAccount.name] = {
address: newOrUpdatedAccount.address,
key: newOrUpdatedAccount.privateKey,
};
}
await this.save();
}

private getPrivateKey(keyConfig: FlowAccountKeyConfig): string | undefined {
// Private keys can also be defined in external files or env variables,
// but for now just ignore those, since those are likely very sensitive credentials,
Expand Down Expand Up @@ -232,13 +220,6 @@ export class FlowConfigService extends EventEmitter {
}
}

private async save() {
await this.writeProjectFile(
this.configFileName,
JSON.stringify(this.config, null, 4),
);
}

private getConfigPath() {
return this.buildProjectPath(this.configFileName);
}
Expand All @@ -248,10 +229,6 @@ export class FlowConfigService extends EventEmitter {
return data.toString();
}

private async writeProjectFile(pathPostfix: string, data: string) {
return writeFile(this.buildProjectPath(pathPostfix), data);
}

private buildProjectPath(pathPostfix: string) {
if (!pathPostfix) {
throw new Error("Postfix path not provided");
Expand Down
67 changes: 40 additions & 27 deletions packages/ui/src/accounts/AccountDetails/AccountDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { DateDisplay } from "../../common/time/DateDisplay/DateDisplay";
import {
useGetAccount,
useGetContractsByAccount,
useGetFlowConfigAccounts,
useGetKeysByAccount,
useGetStoragesByAccount,
useGetTransactionsByAccount,
Expand All @@ -37,39 +38,51 @@ export const AccountDetails: FunctionComponent<AccountDetailsProps> = (
const { data: contracts } = useGetContractsByAccount(accountId);
const { data: keys } = useGetKeysByAccount(accountId);
const { data: storageItems } = useGetStoragesByAccount(accountId);
const { data: flowConfigAccounts } = useGetFlowConfigAccounts();

if (isLoading || !account) {
return <FullScreenLoading />;
}

const detailsColumns: DetailsCardColumn[] = [
[
{
label: "Address",
value: (
<>
<AccountAvatar address={account.address} />
<SizedBox width={10} />
<AccountName address={account.address} />
</>
),
},
{
label: "Balance",
value: (
<>
{account.balance}
<span className={classes.flowCurrency}>FLOW</span>
</>
),
},
{
label: "Created date",
value: <DateDisplay date={account.createdAt.toISOString()} />,
},
],
const column: DetailsCardColumn = [
{
label: "Address",
value: (
<>
<AccountAvatar address={account.address} />
<SizedBox width={10} />
<AccountName address={account.address} />
</>
),
},
];

const flowConfigAccount = flowConfigAccounts?.find(
(e) => e.address === account.address,
);
if (flowConfigAccount) {
column.push({
label: "Name",
value: flowConfigAccount.name,
});
}

column.push(
{
label: "Balance",
value: (
<>
{account.balance}
<span className={classes.flowCurrency}>FLOW</span>
</>
),
},
{
label: "Created date",
value: <DateDisplay date={account.createdAt.toISOString()} />,
},
);

const tabs: BaseTabItem[] = [
{
id: "storage",
Expand Down Expand Up @@ -116,7 +129,7 @@ export const AccountDetails: FunctionComponent<AccountDetailsProps> = (
return (
<div className={classes.root}>
<div className={classes.header}>
<DetailsCard className={classes.detailsCard} columns={detailsColumns} />
<DetailsCard className={classes.detailsCard} columns={[column]} />
</div>
<SizedBox height={30} />
<StyledTabs tabs={tabs} contentClassName={classes.tabContent} />
Expand Down
12 changes: 12 additions & 0 deletions packages/ui/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,18 @@ export function useGetInteractionTemplates(): SWRResponse<
);
}

export function useGetFlowConfigContracts() {
const { flowConfigService } = useServiceRegistry();
return useSWR("flow-config/contracts", () =>
flowConfigService.getContracts(),
);
}

export function useGetFlowConfigAccounts() {
const { flowConfigService } = useServiceRegistry();
return useSWR("flow-config/accounts", () => flowConfigService.getAccounts());
}

export function useGetTokenMetadataList() {
return useSWR("token-list", async () => {
const tokenListContainer = await new TokenListProvider().resolve();
Expand Down
19 changes: 19 additions & 0 deletions packages/ui/src/contexts/service-registry.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ export interface IFlowService {
getFlowCliInfo(): Promise<FlowCliInfo>;
}

export type FlowConfigAccount = {
name: string;
address: string;
privateKey: string | undefined;
};

export type FlowConfigContract = {
name: string;
// Path relative to the project root dir.
relativePath: string;
absolutePath: string;
};

export interface IFlowConfigService {
getAccounts(): Promise<FlowConfigAccount[]>;
getContracts(): Promise<FlowConfigContract[]>;
}

export interface IWalletService {
createAccount(): Promise<void>;
listKeyPairs(): Promise<ManagedKeyPair[]>;
Expand All @@ -86,6 +104,7 @@ export interface IProcessManagerService {
type ServiceRegistry = {
interactionsService: IInteractionService;
flowService: IFlowService;
flowConfigService: IFlowConfigService;
walletService: IWalletService;
snapshotService: ISnapshotService;
workspaceService: IWorkspaceService;
Expand Down
27 changes: 17 additions & 10 deletions packages/ui/src/contracts/ContractDetails/ContractDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { CadenceEditor } from "../../common/code/CadenceEditor/CadenceEditor";
import { DateDisplay } from "../../common/time/DateDisplay/DateDisplay";
import { ProjectLink } from "../../common/links/ProjectLink";
import { IdeLink } from "../../common/links/IdeLink";
import { useGetContract, useGetTokenMetadataList } from "../../api";
import {
useGetContract,
useGetFlowConfigContracts,
useGetTokenMetadataList,
} from "../../api";
import { ContractName } from "../ContractName/ContractName";
import { ExternalLink } from "../../common/links/ExternalLink/ExternalLink";
import { TokenExtensions } from "flow-native-token-registry";
Expand All @@ -25,6 +29,7 @@ export const ContractDetails: FunctionComponent<ContractDetailsProps> = (
const { contractId } = props;
const { isLoading, data: contract } = useGetContract(contractId);
const { data: tokenMetadataList } = useGetTokenMetadataList();
const { data: flowConfigContracts } = useGetFlowConfigContracts();

if (isLoading || !contract) {
return <FullScreenLoading />;
Expand All @@ -49,10 +54,14 @@ export const ContractDetails: FunctionComponent<ContractDetailsProps> = (
},
];

if (contract.localConfig) {
const flowConfigContract = flowConfigContracts?.find(
(e) => e.name === contract.name,
);

if (flowConfigContract) {
primaryColumn.push({
label: "Project path",
value: <span>{contract.localConfig.relativePath}</span>,
label: "File path",
value: <span>{flowConfigContract.relativePath}</span>,
});
}

Expand All @@ -78,14 +87,12 @@ export const ContractDetails: FunctionComponent<ContractDetailsProps> = (
<DetailsCard columns={columns} />
<SizedBox height={30} />
<div className={classes.codeWrapper}>
{contract.localConfig && (
{flowConfigContract && (
<div className={classes.actionButtons}>
Open in:
<IdeLink.VsCode filePath={contract.localConfig.absolutePath} />
<IdeLink.WebStorm filePath={contract.localConfig.absolutePath} />
<IdeLink.IntellijIdea
filePath={contract.localConfig.absolutePath}
/>
<IdeLink.VsCode filePath={flowConfigContract.absolutePath} />
<IdeLink.WebStorm filePath={flowConfigContract.absolutePath} />
<IdeLink.IntellijIdea filePath={flowConfigContract.absolutePath} />
</div>
)}
<CadenceEditor value={contract.code} editable={false} />
Expand Down
Loading

0 comments on commit 8ff0d1f

Please sign in to comment.