diff --git a/.eslintrc.js b/.eslintrc.js index 2bd6a71..d1a1f07 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,7 +26,7 @@ module.exports = { "import/order": ["error", { alphabetize: { order: "asc", caseInsensitive: true } }], // Disabled to avoid duplicate diagnostics with node/no-missing-import rule "import/no-unresolved": "off", - // Disabled because typescript adds support for module import/export to node - "node/no-unsupported-features/es-syntax": ["error", { ignores: ["modules"] }], + // Disabled because typescript adds support for much of this syntax to node + "node/no-unsupported-features/es-syntax": "off", }, }; diff --git a/src/base.ts b/src/base.ts index a6600cd..ef1d872 100644 --- a/src/base.ts +++ b/src/base.ts @@ -13,6 +13,16 @@ export abstract class BlockchainCommand extends Command { options: NetworkNames, default: "testnet", }), + abi: Flags.string({ + helpGroup: "BASE", + description: "The ABI base directory.", + helpValue: "DIR", + }), + rpc: Flags.string({ + helpGroup: "BASE", + description: "Ethereum node endpoint.", + helpValue: "URL", + }), }; static enableJsonFlag = true; diff --git a/src/commands/node/list.ts b/src/commands/node/list.ts index dc981d3..6b981fe 100644 --- a/src/commands/node/list.ts +++ b/src/commands/node/list.ts @@ -17,16 +17,15 @@ export default class NodeList extends BlockchainCommand { public async run(): Promise[]> { const { flags } = await this.parse(NodeList); - - const provider = await getProvider(flags.network); - const nodes = await getContract(flags.network, "nodes", provider); + const provider = await getProvider(flags.network, flags.rpc); + const nodes = await getContract(flags.network, flags.abi, "ArmadaNodes", provider); const operatorId = normalizeHex(flags.operator); const blockTag = await provider.getBlockNumber(); const results: Result[] = await getAll(flags.page, async (i, n) => { return await nodes.getNodes(operatorId, flags.topology, i, n, { blockTag }); }); - const records = results.slice(flags.skip, flags.skip + flags.size); + const records = results.slice(flags.skip, flags.skip + flags.size); const output = normalizeRecords(records); if (!flags.json) console.log(output); return output; diff --git a/src/commands/node/show.ts b/src/commands/node/show.ts index 1aeb74d..85632d0 100644 --- a/src/commands/node/show.ts +++ b/src/commands/node/show.ts @@ -10,12 +10,10 @@ export default class NodeShow extends BlockchainCommand { public async run(): Promise> { const { args, flags } = await this.parse(NodeShow); - - const provider = await getProvider(flags.network); - const nodes = await getContract(flags.network, "nodes", provider); + const provider = await getProvider(flags.network, flags.rpc); + const nodes = await getContract(flags.network, flags.abi, "ArmadaNodes", provider); const nodeId = normalizeHex(args.ID); const record = await nodes.getNode(nodeId); - const output = normalizeRecord(record); if (!flags.json) console.log(output); return output; diff --git a/src/commands/project/content.ts b/src/commands/project/content.ts index 645686d..797d951 100644 --- a/src/commands/project/content.ts +++ b/src/commands/project/content.ts @@ -23,8 +23,8 @@ export default class ProjectContent extends TransactionCommand { this.error("URL and SHA must be specified"); } - const signer = await getSigner(flags.network, flags.address, flags.signer); - const projects = await getContract(flags.network, "projects", signer); + const signer = await getSigner(flags.network, flags.rpc, flags.address, flags.signer); + const projects = await getContract(flags.network, flags.abi, "ArmadaProjects", signer); const projectId = normalizeHex(args.ID); const bundleSha = normalizeHex(args.SHA); CliUx.ux.action.start("- Submitting transaction"); @@ -35,7 +35,6 @@ export default class ProjectContent extends TransactionCommand { const receipt = await tx.wait(); CliUx.ux.action.stop("done"); const event = await decodeEvent(receipt, projects, "ProjectContentChanged"); - const output = normalizeRecord(event); if (!flags.json) console.log(output); return output; diff --git a/src/commands/project/create.ts b/src/commands/project/create.ts index 97d8768..86d89e8 100644 --- a/src/commands/project/create.ts +++ b/src/commands/project/create.ts @@ -23,8 +23,8 @@ export default class ProjectCreate extends TransactionCommand { this.error("URL and SHA must be specified together"); } - const signer = await getSigner(flags.network, flags.address, flags.signer); - const projects = await getContract(flags.network, "projects", signer); + const signer = await getSigner(flags.network, flags.rpc, flags.address, flags.signer); + const projects = await getContract(flags.network, flags.abi, "ArmadaProjects", signer); const owner = flags.owner ? normalizeHex(flags.owner) : await signer.getAddress(); const bundleSha = normalizeHex(args.SHA); CliUx.ux.action.start("- Submitting transaction"); @@ -35,7 +35,6 @@ export default class ProjectCreate extends TransactionCommand { const receipt = await tx.wait(); CliUx.ux.action.stop("done"); const event = await decodeEvent(receipt, projects, "ProjectCreated"); - const output = normalizeRecord(event); if (!flags.json) console.log(output); return output; diff --git a/src/commands/project/delete.ts b/src/commands/project/delete.ts index d58916e..d66f195 100644 --- a/src/commands/project/delete.ts +++ b/src/commands/project/delete.ts @@ -11,9 +11,8 @@ export default class ProjectDelete extends TransactionCommand { public async run(): Promise> { const { args, flags } = await this.parse(ProjectDelete); - - const signer = await getSigner(flags.network, flags.address, flags.signer); - const projects = await getContract(flags.network, "projects", signer); + const signer = await getSigner(flags.network, flags.rpc, flags.address, flags.signer); + const projects = await getContract(flags.network, flags.abi, "ArmadaProjects", signer); const projectId = normalizeHex(args.ID); CliUx.ux.action.start("- Submitting transaction"); const tx = await projects.deleteProject(projectId); @@ -23,7 +22,6 @@ export default class ProjectDelete extends TransactionCommand { const receipt = await tx.wait(); CliUx.ux.action.stop("done"); const event = await decodeEvent(receipt, projects, "ProjectDeleted"); - const output = normalizeRecord(event); if (!flags.json) console.log(output); return output; diff --git a/src/commands/project/list.ts b/src/commands/project/list.ts index 67fd40d..8c828ff 100644 --- a/src/commands/project/list.ts +++ b/src/commands/project/list.ts @@ -16,9 +16,8 @@ export default class ProjectList extends BlockchainCommand { public async run(): Promise[]> { const { flags } = await this.parse(ProjectList); - - const provider = await getProvider(flags.network); - const projects = await getContract(flags.network, "projects", provider); + const provider = await getProvider(flags.network, flags.rpc); + const projects = await getContract(flags.network, flags.abi, "ArmadaProjects", provider); const owner = normalizeHex(flags.owner); const blockTag = await provider.getBlockNumber(); let results: Result[] = await getAll(flags.page, async (i, n) => { diff --git a/src/commands/project/owner.ts b/src/commands/project/owner.ts index 912d912..631bdad 100644 --- a/src/commands/project/owner.ts +++ b/src/commands/project/owner.ts @@ -15,9 +15,8 @@ export default class ProjectOwner extends TransactionCommand { public async run(): Promise> { const { args, flags } = await this.parse(ProjectOwner); - - const signer = await getSigner(flags.network, flags.address, flags.signer); - const projects = await getContract(flags.network, "projects", signer); + const signer = await getSigner(flags.network, flags.rpc, flags.address, flags.signer); + const projects = await getContract(flags.network, flags.abi, "ArmadaProjects", signer); const projectId = normalizeHex(args.ID); CliUx.ux.action.start("- Submitting transaction"); const tx = await projects.setProjectOwner(projectId, args.ADDR); @@ -27,7 +26,6 @@ export default class ProjectOwner extends TransactionCommand { const receipt = await tx.wait(); CliUx.ux.action.stop("done"); const event = await decodeEvent(receipt, projects, "ProjectOwnerChanged"); - const output = normalizeRecord(event); if (!flags.json) console.log(output); return output; diff --git a/src/commands/project/props.ts b/src/commands/project/props.ts index 762a490..50e71f2 100644 --- a/src/commands/project/props.ts +++ b/src/commands/project/props.ts @@ -15,9 +15,8 @@ export default class ProjectProps extends TransactionCommand { public async run(): Promise> { const { args, flags } = await this.parse(ProjectProps); - - const signer = await getSigner(flags.network, flags.address, flags.signer); - const projects = await getContract(flags.network, "projects", signer); + const signer = await getSigner(flags.network, flags.rpc, flags.address, flags.signer); + const projects = await getContract(flags.network, flags.abi, "ArmadaProjects", signer); const projectId = normalizeHex(args.ID); CliUx.ux.action.start("- Submitting transaction"); const tx = await projects.setProjectProps(projectId, args.NAME, args.EMAIL); @@ -27,7 +26,6 @@ export default class ProjectProps extends TransactionCommand { const receipt = await tx.wait(); CliUx.ux.action.stop("done"); const event = await decodeEvent(receipt, projects, "ProjectPropsChanged"); - const output = normalizeRecord(event); if (!flags.json) console.log(output); return output; diff --git a/src/commands/project/show.ts b/src/commands/project/show.ts index 23ae663..65443dc 100644 --- a/src/commands/project/show.ts +++ b/src/commands/project/show.ts @@ -10,12 +10,10 @@ export default class ProjectShow extends BlockchainCommand { public async run(): Promise> { const { args, flags } = await this.parse(ProjectShow); - - const provider = await getProvider(flags.network); - const projects = await getContract(flags.network, "projects", provider); + const provider = await getProvider(flags.network, flags.rpc); + const projects = await getContract(flags.network, flags.abi, "ArmadaProjects", provider); const projectId = normalizeHex(args.ID); const record = await projects.getProject(projectId); - const output = normalizeRecord(record); if (!flags.json) console.log(output); return output; diff --git a/src/commands/reservation/create.ts b/src/commands/reservation/create.ts index 707a1e3..416ebc5 100644 --- a/src/commands/reservation/create.ts +++ b/src/commands/reservation/create.ts @@ -31,8 +31,8 @@ export default class ReservationCreate extends TransactionCommand { .slice(1) // Skip ID .map((id) => normalizeHex(id)); - const signer = await getSigner(flags.network, flags.address, flags.signer); - const reservations = await getContract(flags.network, "reservations", signer); + const signer = await getSigner(flags.network, flags.rpc, flags.address, flags.signer); + const reservations = await getContract(flags.network, flags.abi, "ArmadaReservations", signer); const projectId = normalizeHex(args.ID); const prices = nodeIds.map(() => parseUnits("1", 18)); CliUx.ux.action.start("- Submitting transaction"); @@ -44,7 +44,6 @@ export default class ReservationCreate extends TransactionCommand { const receipt = await tx.wait(); CliUx.ux.action.stop("done"); const events = await decodeEvents(receipt, reservations, "ReservationCreated"); - const output = normalizeRecords(events); if (!flags.json) console.log(output); return output; diff --git a/src/commands/reservation/delete.ts b/src/commands/reservation/delete.ts index ebfd373..b90bd8d 100644 --- a/src/commands/reservation/delete.ts +++ b/src/commands/reservation/delete.ts @@ -23,8 +23,8 @@ export default class ReservationDelete extends TransactionCommand { .slice(1) // Skip ID .map((id) => normalizeHex(id)); - const signer = await getSigner(flags.network, flags.address, flags.signer); - const reservations = await getContract(flags.network, "reservations", signer); + const signer = await getSigner(flags.network, flags.rpc, flags.address, flags.signer); + const reservations = await getContract(flags.network, flags.abi, "ArmadaReservations", signer); const projectId = normalizeHex(args.ID); CliUx.ux.action.start("- Submitting transaction"); const slot = { last: false, next: true }; @@ -35,7 +35,6 @@ export default class ReservationDelete extends TransactionCommand { const receipt = await tx.wait(); CliUx.ux.action.stop("done"); const events = await decodeEvents(receipt, reservations, "ReservationDeleted"); - const output = normalizeRecords(events); if (!flags.json) console.log(output); return output; diff --git a/src/commands/reservation/list.ts b/src/commands/reservation/list.ts index 4d36765..f1b3fed 100644 --- a/src/commands/reservation/list.ts +++ b/src/commands/reservation/list.ts @@ -17,9 +17,8 @@ export default class ReservationList extends BlockchainCommand { public async run(): Promise[]> { const { args, flags } = await this.parse(ReservationList); - - const provider = await getProvider(flags.network); - const reservations = await getContract(flags.network, "reservations", provider); + const provider = await getProvider(flags.network, flags.rpc); + const reservations = await getContract(flags.network, flags.abi, "ArmadaReservations", provider); const projectId = normalizeHex(args.ID); const blockTag = await provider.getBlockNumber(); const results: Result[] = await getAll(flags.page, async (i, n) => { diff --git a/src/contracts.ts b/src/contracts.ts new file mode 100644 index 0000000..a6208a3 --- /dev/null +++ b/src/contracts.ts @@ -0,0 +1,26 @@ +import fs from "fs"; +import type { ContractInterface } from "ethers"; + +// These imports are necessary to pull these files into dist/ +import "../abi/staging/ArmadaNodes.json"; +import "../abi/staging/ArmadaProjects.json"; +import "../abi/staging/ArmadaReservations.json"; +import "../abi/testnet/ArmadaNodes.json"; +import "../abi/testnet/ArmadaProjects.json"; +import "../abi/testnet/ArmadaReservations.json"; + +export type ContractName = "ArmadaNodes" | "ArmadaProjects" | "ArmadaReservations"; + +export interface ContractInfo { + address: string; + abi: ContractInterface; +} + +export async function loadAbi(path: string): Promise { + const importPrefix = "import://"; + if (path.startsWith(importPrefix)) { + return await import(path.slice(importPrefix.length)); + } else { + return JSON.parse(fs.readFileSync(path).toString()); + } +} diff --git a/src/helpers.ts b/src/helpers.ts index 17d0b4e..0292164 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,11 +1,13 @@ +import path from "path"; import type { Provider, TransactionReceipt } from "@ethersproject/abstract-provider"; import { BigNumber, Contract, ethers, Signer, type Transaction } from "ethers"; import { formatUnits, Result } from "ethers/lib/utils"; import inquirer from "inquirer"; import keytar from "keytar"; +import { ContractName, loadAbi } from "./contracts"; import { listWallets, loadWallet } from "./keystore"; import { LedgerSigner } from "./ledger"; -import { ContractName, Contracts, NetworkName, Networks } from "./networks"; +import { NetworkName, Networks } from "./networks"; export type SignerType = "keystore" | "ledger"; export const SignerTypes: SignerType[] = ["keystore", "ledger"]; @@ -63,18 +65,19 @@ function normalizeBigNumber(n: BigNumber): string { } } -export async function getProvider(network: NetworkName): Promise { - const url = Networks[network].url; +export async function getProvider(network: NetworkName, rpcUrl: string | undefined): Promise { + const url = rpcUrl ?? Networks[network].url; const provider = new ethers.providers.JsonRpcProvider(url); return provider; } export async function getSigner( network: NetworkName, + rpcUrl: string | undefined, address: string | undefined, signer: SignerType ): Promise { - const url = Networks[network].url; + const url = rpcUrl ?? Networks[network].url; const provider = new ethers.providers.JsonRpcProvider(url); let wallet: Signer; @@ -124,10 +127,13 @@ export async function getSigner( export async function getContract( network: NetworkName, + abiDir: string | undefined, contract: ContractName, signerOrProvider: Signer | ethers.providers.Provider ): Promise { - const abi = Contracts[network][contract]; + const dir = abiDir ?? Networks[network].abi; + const file = path.join(dir, network, contract + ".json"); + const abi = await loadAbi(file); if (signerOrProvider instanceof Signer) { const signer = signerOrProvider; const contract = new Contract(abi.address, abi.abi, signer.provider); diff --git a/src/networks.ts b/src/networks.ts index e90d698..64165ea 100644 --- a/src/networks.ts +++ b/src/networks.ts @@ -1,44 +1,22 @@ -import type { ContractInterface } from "ethers"; - -import StagingNodesDeployment from "../abi/staging/ArmadaNodes.json"; -import StagingProjectsDeployment from "../abi/staging/ArmadaProjects.json"; -import StagingReservationsDeployment from "../abi/staging/ArmadaReservations.json"; - -import TestnetNodesDeployment from "../abi/testnet/ArmadaNodes.json"; -import TestnetProjectsDeployment from "../abi/testnet/ArmadaProjects.json"; -import TestnetReservationsDeployment from "../abi/testnet/ArmadaReservations.json"; - -export type NetworkName = "testnet" | "staging"; -export type ContractName = "nodes" | "projects" | "reservations"; +export type NetworkName = "testnet" | "staging" | "localhost"; export interface NetworkInfo { url: string; -} - -export interface ContractInfo { - address: string; - abi: ContractInterface; + abi: string; } export const Networks: Record = { testnet: { url: "https://rpc.ankr.com/eth_goerli", + abi: "import://../abi", }, staging: { url: "https://rpc.ankr.com/eth_goerli", + abi: "import://../abi", }, -}; - -export const Contracts: Record> = { - testnet: { - nodes: TestnetNodesDeployment, - projects: TestnetProjectsDeployment, - reservations: TestnetReservationsDeployment, - }, - staging: { - nodes: StagingNodesDeployment, - projects: StagingProjectsDeployment, - reservations: StagingReservationsDeployment, + localhost: { + url: "http://localhost:8545", + abi: "../armada-contracts/deployments", }, };