From ea81a25f0cc722e1229df813b7577471249f173b Mon Sep 17 00:00:00 2001 From: the-masthead <116779271+the-masthead@users.noreply.github.com> Date: Thu, 27 Oct 2022 22:07:42 -0700 Subject: [PATCH 1/4] Add content publish command --- README.md | 18 ++++-- abi/staging/ArmadaProjects.json | 60 ++++++++++++++++++++ abi/testnet/ArmadaProjects.json | 60 ++++++++++++++++++++ src/checksum.ts | 4 -- src/cli.ts | 26 +++++++-- src/commands/key/import.ts | 23 +++----- src/commands/node/list.ts | 37 +++++-------- src/commands/project/content.ts | 33 +++++++++++ src/commands/project/create.ts | 92 ++++++------------------------- src/commands/project/index.ts | 5 +- src/helpers.ts | 98 +++++++++++++++++++++++++++++---- src/keystore.ts | 49 ++++++++--------- src/ledger.ts | 2 +- 13 files changed, 339 insertions(+), 168 deletions(-) create mode 100644 src/commands/project/content.ts diff --git a/README.md b/README.md index 9ede14d..f4f6654 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,30 @@ A command line tool for working with the Armada Network. +> Note: For now, projects must be whitelisted to run on Armada testnet. Once a project is admitted, it gets a `project-id` that can be used in the commands below. + ## Installation ```sh npm install armada-cli ``` -## Usage - -### Creating a Bundle +## Creating a Bundle -Once a site has been built and is ready for bundling, run the following command to generate an Armada-compatible archive: +Once a site has been built and is ready for bundling, run the following command to generate an Armada-compatible archive. This can be done manually, or by a CI system: ```sh npx armada bundle ``` Example: `npx armada bundle my-site-v1.0.0 ./dist` + +## Publishing a Bundle + +Once the bundle has been made available on a publicly accessible URL, such as Github Releases or S3, it can be published on the Armada Network. This can be done by a DAO vote, for example through Tally, or manually like this: + +```sh +npx armada project-content +``` + +Example: `npx armada publish 0x0123... https://.../my-site-v1.0.0.tgz 0xabcd...` diff --git a/abi/staging/ArmadaProjects.json b/abi/staging/ArmadaProjects.json index eabcb6e..b9efbe8 100644 --- a/abi/staging/ArmadaProjects.json +++ b/abi/staging/ArmadaProjects.json @@ -1,6 +1,43 @@ { "address": "0xef8eF5F616Bc14515A9f2724628a4b7C8bA12E3F", "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "projectId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "oldContent", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "oldChecksum", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "newContent", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "newChecksum", + "type": "bytes32" + } + ], + "name": "ProjectContentChanged", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -89,6 +126,29 @@ ], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "projectId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "content", + "type": "string" + }, + { + "internalType": "bytes32", + "name": "checksum", + "type": "bytes32" + } + ], + "name": "setProjectContent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ] } \ No newline at end of file diff --git a/abi/testnet/ArmadaProjects.json b/abi/testnet/ArmadaProjects.json index d788cb3..31940e4 100644 --- a/abi/testnet/ArmadaProjects.json +++ b/abi/testnet/ArmadaProjects.json @@ -1,6 +1,43 @@ { "address": "0x2622d2ac086820d4E5372d0804A14A79aFCabBa9", "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "projectId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "oldContent", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "oldChecksum", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "newContent", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "newChecksum", + "type": "bytes32" + } + ], + "name": "ProjectContentChanged", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -89,6 +126,29 @@ ], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "projectId", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "content", + "type": "string" + }, + { + "internalType": "bytes32", + "name": "checksum", + "type": "bytes32" + } + ], + "name": "setProjectContent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" } ] } \ No newline at end of file diff --git a/src/checksum.ts b/src/checksum.ts index d3ca098..537b1b6 100644 --- a/src/checksum.ts +++ b/src/checksum.ts @@ -1,10 +1,6 @@ import { createHash } from "crypto"; import { createReadStream, PathLike } from "fs"; -export async function sha1File(path: any): Promise { - return hashFile("sha1", path); -} - export async function sha256File(path: string): Promise { return hashFile("sha256", path); } diff --git a/src/cli.ts b/src/cli.ts index 1d53375..d552643 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,3 +1,5 @@ +#!/usr/bin/env node + import path from "path"; import tar from "tar"; import yargs, { CommandModule } from "yargs"; @@ -7,6 +9,7 @@ import { commands as keyCommands } from "./commands/key"; import { commands as nodeCommands } from "./commands/node"; import { commands as projectCommands } from "./commands/project"; import { generateManifest } from "./manifest"; +import { defaultNetworks } from "./networks"; yargs(hideBin(process.argv)) .command(keyCommands as unknown as CommandModule<{}, unknown>) @@ -17,11 +20,11 @@ yargs(hideBin(process.argv)) "Bundle an application for use on the Armada Network", (yargs) => { return yargs - .positional('name', { - describe: 'The name of the bundle to create (e.g. my-site-v1.0.0)', + .positional("name", { + describe: "The name of the bundle to create (e.g. my-site-v1.0.0)", }) .positional("build-dir", { - describe: 'Relative path to the application\'s build directory (e.g. ./dist)', + describe: "Relative path to the application's build directory (e.g. ./dist)", }); }, async (argv) => { @@ -56,14 +59,25 @@ yargs(hideBin(process.argv)) console.log(manifestPath); } ) + .option("network", { + describe: "The network to use.", + type: "string", + choices: Object.keys(defaultNetworks), + default: "testnet", + }) + .option("ledger", { + describe: "Use Ledger hardware wallet", + type: "boolean", + default: false, + }) .demandCommand(1) .parse(); function normalizeBundleExtension(name: string) { - if (name.endsWith('.tar.gz') || name.endsWith('.tgz')) { - return name; + if (name.endsWith(".tar.gz") || name.endsWith(".tgz")) { + return name; } - return name + '.tgz'; + return name + ".tgz"; } async function compress(archive: string, buildDir: string) { diff --git a/src/commands/key/import.ts b/src/commands/key/import.ts index 84ff5a1..14594da 100644 --- a/src/commands/key/import.ts +++ b/src/commands/key/import.ts @@ -1,9 +1,8 @@ import inquirer from "inquirer"; -import { generateKeyStore, openKeyStoreFile } from "../../keystore"; +import { loadWallet, saveWallet } from "../../keystore"; export const command = "key-import"; -export const desc = - "Imports a wallet into armada cli for signing txs. An encrypted keystore is generated from private key and stored on your local computer. The private key itself is not stored."; +export const desc = "Save a private key for signing transactions"; export const builder = function (yargs: any) { return yargs; @@ -13,21 +12,17 @@ export const handler = async function () { let responses = await inquirer.prompt([ { name: "privateKey", - message: "Enter the privateKey of the wallet to import", - type: "string", + message: "Enter the wallet private key to import", + type: "password", }, { name: "password", - message: "Enter the password to encrypt the keystore", - type: "string", + message: "Enter the password to encrypt the key", + type: "password", }, ]); - console.log(`Importing wallet`); - console.log(`Keystore encryption password: ${responses.password}`); - - const filename = await generateKeyStore(responses.privateKey, responses.password); - const wallet = await openKeyStoreFile(filename, responses.password); - - console.log(`Keystore generated for: ${wallet.address}`); + const filename = await saveWallet(responses.privateKey, responses.password); + const wallet = await loadWallet(filename, responses.password); + console.log(`Account ${wallet.address} imported`); }; diff --git a/src/commands/node/list.ts b/src/commands/node/list.ts index 140dbdc..0843896 100644 --- a/src/commands/node/list.ts +++ b/src/commands/node/list.ts @@ -1,47 +1,38 @@ -import { ethers } from "ethers"; import yargs, { Arguments } from "yargs"; -import { defaultNetworks, getArmadaAbi, getNetworkRpcUrl, supportedNetworks } from "../../networks"; +import { getContract, getProvider, normalizeHex } from "../../helpers"; export const command = "node-list"; -export const desc = "List all armada nodes"; +export const desc = "List network content nodes"; export const builder = function (yargs: yargs.Argv) { return yargs .option("skip", { - describe: "The number of nodes to skip. Example: 0", + describe: "The number of results to skip", default: 0, type: "number", }) .option("size", { - describe: "The number of nodes to return. Example: 100", + describe: "The number of results to list", default: 100, type: "number", }) .option("operator", { - describe: "The operator id to filter by.", - default: "0x0000000000000000000000000000000000000000000000000000000000000000", + describe: "The operator ID to filter by", + default: "", type: "string", }) .option("topology", { - describe: "Return topology nodes.", + describe: "List topology nodes instead", type: "boolean", default: false, - }) - .option("network", { - describe: `The network to use. Default is testnet. Options: ${Object.keys(defaultNetworks).join(", ")}`, - type: "string", - default: false, - }) + }); }; export const handler = async function (argv: Arguments) { - console.log(`Listing all nodes.`); - - const url = getNetworkRpcUrl(argv.network as supportedNetworks); - const provider = new ethers.providers.JsonRpcProvider(url); - let { address: armadaNodesAddress, abi: armadaNodesAbi } = getArmadaAbi(argv.network as supportedNetworks, "nodes"); - - const contract = new ethers.Contract(armadaNodesAddress, armadaNodesAbi, provider); - const nodes = await contract.getNodes(argv.operator, argv.topology, argv.skip, argv.size); - console.log(nodes); + const provider = await getProvider(argv); + const nodes = await getContract(argv, "nodes", provider); + const operator = normalizeHex(argv.operator as string); + const data = await nodes.getNodes(operator, argv.topology, argv.skip, argv.size); + console.log(data); + console.log("OK"); }; diff --git a/src/commands/project/content.ts b/src/commands/project/content.ts new file mode 100644 index 0000000..27b5795 --- /dev/null +++ b/src/commands/project/content.ts @@ -0,0 +1,33 @@ +import yargs, { Arguments } from "yargs"; +import { decodeEvent, getContract, getSigner, normalizeHex } from "../../helpers"; + +export const command = ["project-content ", "publish"]; +export const desc = "Publish content on the network"; + +export const builder = function (yargs: yargs.Argv) { + return yargs + .positional("project-id", { + describe: "The ID of the project", + type: "string", + }) + .positional("bundle-url", { + describe: "The public URL to fetch the bundle", + type: "string", + }) + .positional("bundle-sha", { + describe: "The SHA-256 checksum of the bundle", + type: "string", + }); +}; + +export const handler = async function (argv: Arguments) { + const signer = await getSigner(argv); + const projects = await getContract(argv, "projects", signer); + const projectId = normalizeHex(argv.projectId as string); + const bundleSha = normalizeHex(argv.bundleSha as string); + const receipt = await projects.setProjectContent(projectId, argv.bundleUrl, bundleSha); + const response = await receipt.wait(); + const events = await decodeEvent(response, projects, "ProjectContentChanged"); + console.log(events); + console.log("OK"); +}; diff --git a/src/commands/project/create.ts b/src/commands/project/create.ts index 056a1bd..89bf22d 100644 --- a/src/commands/project/create.ts +++ b/src/commands/project/create.ts @@ -1,14 +1,8 @@ -import { ethers } from "ethers"; -import inquirer from "inquirer"; -import keytar from "keytar"; import yargs, { Arguments } from "yargs"; -import { decodeEvent, waitTx } from "../../helpers"; -import { getWallets, openKeyStoreFile } from "../../keystore"; -import { defaultNetworks, getArmadaAbi, getNetworkRpcUrl, supportedNetworks } from "../../networks"; -import { LedgerSigner } from "../../ledger"; +import { decodeEvent, getContract, getSigner, normalizeHex } from "../../helpers"; -export const command = "project-create "; -export const desc = "Create a new project"; +export const command = "project-create [bundle-url] [bundle-sha]"; +export const desc = "Register a new project"; export const builder = function (yargs: yargs.Argv) { return yargs @@ -20,80 +14,26 @@ export const builder = function (yargs: yargs.Argv) { describe: "The email of the project", type: "string", }) - .option("network", { - describe: `The network to use. Default is testnet. Options: ${Object.keys(defaultNetworks).join(", ")}`, - type: "string", - default: false, - }) - .option("content", { - describe: "The content of the project", + .positional("bundle-url", { + describe: "The public URL to fetch the bundle", type: "string", default: "", }) - .option("checksum", { - describe: "The checksum of the project", + .positional("bundle-sha", { + describe: "The SHA-256 checksum of the bundle", type: "string", - default: "0x0000000000000000000000000000000000000000000000000000000000000000", - }) - .option("ledger", { - describe: "Use a ledger wallet", - type: "boolean", - default: false, + default: "", }); }; export const handler = async function (argv: Arguments) { - const { address: armadaProjectsAddress, abi: armadaProjectsAbi } = getArmadaAbi(argv.network as supportedNetworks, "projects"); - const url = getNetworkRpcUrl(argv.network as supportedNetworks); - const provider = new ethers.providers.JsonRpcProvider(url); - - let wallet; - let address; - if (argv.ledger) { - console.log( - "Creating project using Ledger wallet. Make sure Ledger walet is unlocked and the ethereum application is open" - ); - - wallet = new LedgerSigner(provider); - address = await wallet.getAddress(); - - console.log("Using Ledger wallet. Wallet address: ", address); - } else { - let res = await inquirer.prompt([ - { - name: "address", - message: "Which wallet do you want to use to sign the transaction?", - type: "list", - choices: await getWallets(), - }, - ]); - address = res.address; - - let password = await keytar.getPassword("armada-cli", address); - if (!password) { - let res = await inquirer.prompt([ - { - name: "password", - message: "Enter the password of the wallet.", - type: "string", - }, - ]); - password = res.password; - } - - wallet = await openKeyStoreFile(`keystore_${address}.json`, password); - wallet = wallet.connect(provider); - } - - console.log(`Creating project: ${argv.name}. May take up to a minute to complete...`); - - const projectContract = new ethers.Contract(armadaProjectsAddress, armadaProjectsAbi, provider); - const projectContractWithSigner = projectContract.connect(wallet); - const createProject = await waitTx( - projectContractWithSigner.createProject([address, argv.name, argv.email, argv.content, argv.checksum]) - ); - const events = await decodeEvent(createProject, projectContract, "ProjectCreated"); - - console.log("Project created! Details: "); + const signer = await getSigner(argv); + const projects = await getContract(argv, "projects", signer); + const address = await signer.getAddress(); + const bundleSha = normalizeHex(argv.bundleSha as string); + const receipt = await projects.createProject([address, argv.name, argv.email, argv.bundleUrl, bundleSha]); + const response = await receipt.wait(); + const events = await decodeEvent(response, projects, "ProjectCreated"); console.log(events); + console.log("OK"); }; diff --git a/src/commands/project/index.ts b/src/commands/project/index.ts index 94eb6fa..ae9e2f8 100644 --- a/src/commands/project/index.ts +++ b/src/commands/project/index.ts @@ -1,3 +1,4 @@ -import * as a from "./create"; +import * as content from "./content"; +import * as create from "./create"; -export const commands = [a]; +export const commands = [create, content]; diff --git a/src/helpers.ts b/src/helpers.ts index 10e28cd..a526b25 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,15 +1,95 @@ -import { Contract } from "ethers"; +import { Contract, ethers, Signer } from "ethers"; +import inquirer from "inquirer"; +import keytar from "keytar"; +import { Arguments } from "yargs"; +import { listWallets, loadWallet } from "./keystore"; +import { LedgerSigner } from "./ledger"; +import { getArmadaAbi, getNetworkRpcUrl, supportedContracts, supportedNetworks } from "./networks"; -export async function decodeEvent(receipt: { logs: string | any[]; }, contract: Contract, event: string) { +export function normalizeHex(s: string): string { + if (!s.length) { + return "0x0000000000000000000000000000000000000000000000000000000000000000"; + } else { + return s.startsWith("0x") ? s : "0x" + s; + } +} + +export async function getProvider(argv: Arguments) { + const url = getNetworkRpcUrl(argv.network as supportedNetworks); + const provider = new ethers.providers.JsonRpcProvider(url); + return provider; +} + +export async function getSigner(argv: Arguments): Promise { + const url = getNetworkRpcUrl(argv.network as supportedNetworks); + const provider = new ethers.providers.JsonRpcProvider(url); + + let signer: Signer; + if (argv.ledger) { + console.log("Make sure the Ledger wallet is unlocked and the Ethereum application is open"); + signer = new LedgerSigner(provider); + const address = await signer.getAddress(); + console.log("Using Ledger wallet. Wallet address: ", address); + } else { + const addresses = await listWallets(); + if (!addresses.length) { + console.error("ERROR: No private keys found. Use npx armada key-import."); + process.exit(1); + } + + let res = await inquirer.prompt([ + { + name: "address", + message: "Pick the wallet to sign the transaction:", + type: "list", + choices: addresses, + }, + ]); + + const address = res.address; + let password = await keytar.getPassword("armada-cli", address); + if (!password) { + let res = await inquirer.prompt([ + { + name: "password", + message: "Enter the wallet encryption password:", + type: "password", + }, + ]); + password = res.password as string; + } + + signer = await loadWallet(`keystore_${address}.json`, password); + signer = signer.connect(provider); + } + + return signer; +} + +export async function getContract( + argv: Arguments, + name: supportedContracts, + signerOrProvider: Signer | ethers.providers.Provider +): Promise { + const abi = getArmadaAbi(argv.network as supportedNetworks, name); + if (signerOrProvider instanceof Signer) { + const signer = signerOrProvider; + const contract = new Contract(abi.address, abi.abi, signer.provider); + const contractWithSigner = contract.connect(signer); + return contractWithSigner; + } else { + const provider = signerOrProvider; + const contract = new Contract(abi.address, abi.abi, provider); + return contract; + } +} + +export async function decodeEvent(receipt: { logs: string | any[] }, contract: Contract, event: string) { const results = []; for (let i = 0; i < receipt.logs.length; i++) { const log = receipt.logs[i]; try { - const args = contract.interface.decodeEventLog( - event, - log.data, - log.topics - ); + const args = contract.interface.decodeEventLog(event, log.data, log.topics); if (args) { results.push(args); } @@ -19,7 +99,3 @@ export async function decodeEvent(receipt: { logs: string | any[]; }, contract: } return results.length === 1 ? results[0] : results; } - -export async function waitTx(txPromise: any) { - return await (await txPromise).wait(); -} diff --git a/src/keystore.ts b/src/keystore.ts index c93793c..a2d91f3 100644 --- a/src/keystore.ts +++ b/src/keystore.ts @@ -1,55 +1,50 @@ import { encryptKeystore } from "@ethersproject/json-wallets"; import fs from "fs"; -import { ethers } from "ethers"; +import { Wallet } from "ethers"; import path from "path"; import os from "os"; import keytar from "keytar"; +import { normalizeHex } from "./helpers"; const homedir = os.homedir(); const keyStoreFolderPath = path.join(homedir, ".armada"); -export async function generateKeyStore(privateKey: string, passw: string) { - privateKey = privateKey.startsWith("0x") ? privateKey : "0x" + privateKey; - const address = new ethers.Wallet(privateKey).address; - const wallet = { address, privateKey }; - - const keyStore = await encryptKeystore(wallet, passw); - const filename = `keystore_${wallet.address}.json`; +export async function saveWallet(privateKey: string, password: string): Promise { + privateKey = normalizeHex(privateKey); + const wallet = new Wallet(privateKey); + const address = wallet.address; + const json = await encryptKeystore({ address, privateKey }, password); + const filename = `keystore_${address}.json`; if (!fs.existsSync(keyStoreFolderPath)) { fs.mkdirSync(keyStoreFolderPath); } - fs.writeFileSync(path.join(keyStoreFolderPath, filename), keyStore); - keytar.setPassword("armada-cli", wallet.address, passw); + fs.writeFileSync(path.join(keyStoreFolderPath, filename), json); + keytar.setPassword("armada-cli", address, password); return filename; } -export async function openKeyStoreFile(filename: string, passw: any) { - const keyStore = fs.readFileSync(path.join(keyStoreFolderPath, filename), "utf8"); - const wallet = await openKeyStore(keyStore, passw); +export async function loadWallet(filename: string, password: string): Promise { + const json = fs.readFileSync(path.join(keyStoreFolderPath, filename), "utf8"); + const wallet = await Wallet.fromEncryptedJson(json, password); return wallet; } -export async function openKeyStore(keyStore: string, passw: string) { - return await ethers.Wallet.fromEncryptedJson(keyStore, passw); -} - -function getWalletAddressFromKeystoreFilename(filename: string) { - return filename.split("_")[1].split(".")[0]; -} +export async function listWallets(): Promise { + if (!fs.existsSync(keyStoreFolderPath)) { + return []; + } -export async function getWallets() { - const wallets = []; + const wallets: string[] = []; const files = fs.readdirSync(keyStoreFolderPath); - for (const file of files) { - if (file.includes(".DS_Store")) { + for (const filename of files) { + if (!filename.match(/keystore_.*\.json/)) { continue; } - wallets.push({ - name: getWalletAddressFromKeystoreFilename(file), - }); + wallets.push(filename.split("_")[1].split(".")[0]); } + return wallets; } diff --git a/src/ledger.ts b/src/ledger.ts index 0dde83f..8d6e3c7 100644 --- a/src/ledger.ts +++ b/src/ledger.ts @@ -151,7 +151,7 @@ export class LedgerSigner extends ethers.Signer { chainId: tx.chainId || undefined, data: tx.data || undefined, gasLimit: tx.gasLimit || undefined, - gasPrice: (tx.gasPrice || tx.maxFeePerGas || undefined), + gasPrice: tx.gasPrice || tx.maxFeePerGas || undefined, nonce: tx.nonce ? ethers.BigNumber.from(tx.nonce).toNumber() : undefined, to: tx.to || undefined, value: tx.value || undefined, From 066aeaca4b5ec20aed679e73fb380a9bc2f8d63a Mon Sep 17 00:00:00 2001 From: the-masthead <116779271+the-masthead@users.noreply.github.com> Date: Fri, 28 Oct 2022 17:17:13 -0700 Subject: [PATCH 2/4] Print tx hash --- src/commands/project/content.ts | 7 ++++--- src/commands/project/create.ts | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/commands/project/content.ts b/src/commands/project/content.ts index 27b5795..2fd6d64 100644 --- a/src/commands/project/content.ts +++ b/src/commands/project/content.ts @@ -25,9 +25,10 @@ export const handler = async function (argv: Arguments) { const projects = await getContract(argv, "projects", signer); const projectId = normalizeHex(argv.projectId as string); const bundleSha = normalizeHex(argv.bundleSha as string); - const receipt = await projects.setProjectContent(projectId, argv.bundleUrl, bundleSha); - const response = await receipt.wait(); - const events = await decodeEvent(response, projects, "ProjectContentChanged"); + const tx = await projects.setProjectContent(projectId, argv.bundleUrl, bundleSha); + console.log(`Transaction ${tx.hash}...`); + const receipt = await tx.wait(); + const events = await decodeEvent(receipt, projects, "ProjectContentChanged"); console.log(events); console.log("OK"); }; diff --git a/src/commands/project/create.ts b/src/commands/project/create.ts index 89bf22d..1326a0a 100644 --- a/src/commands/project/create.ts +++ b/src/commands/project/create.ts @@ -31,9 +31,10 @@ export const handler = async function (argv: Arguments) { const projects = await getContract(argv, "projects", signer); const address = await signer.getAddress(); const bundleSha = normalizeHex(argv.bundleSha as string); - const receipt = await projects.createProject([address, argv.name, argv.email, argv.bundleUrl, bundleSha]); - const response = await receipt.wait(); - const events = await decodeEvent(response, projects, "ProjectCreated"); + const tx = await projects.createProject([address, argv.name, argv.email, argv.bundleUrl, bundleSha]); + console.log(`Transaction ${tx.hash}...`); + const receipt = await tx.wait(); + const events = await decodeEvent(receipt, projects, "ProjectCreated"); console.log(events); console.log("OK"); }; From 42a3626a6060bee12cc99098a5357118ce5b0506 Mon Sep 17 00:00:00 2001 From: the-masthead <116779271+the-masthead@users.noreply.github.com> Date: Fri, 28 Oct 2022 18:09:52 -0700 Subject: [PATCH 3/4] Code review --- README.md | 2 +- src/commands/project/content.ts | 4 ++++ src/commands/project/create.ts | 4 ++++ src/helpers.ts | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f4f6654..98905e3 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Example: `npx armada bundle my-site-v1.0.0 ./dist` Once the bundle has been made available on a publicly accessible URL, such as Github Releases or S3, it can be published on the Armada Network. This can be done by a DAO vote, for example through Tally, or manually like this: ```sh -npx armada project-content +npx armada publish ``` Example: `npx armada publish 0x0123... https://.../my-site-v1.0.0.tgz 0xabcd...` diff --git a/src/commands/project/content.ts b/src/commands/project/content.ts index 2fd6d64..4e3e7f6 100644 --- a/src/commands/project/content.ts +++ b/src/commands/project/content.ts @@ -21,6 +21,10 @@ export const builder = function (yargs: yargs.Argv) { }; export const handler = async function (argv: Arguments) { + if ((argv.bundleUrl != "") ^ (argv.bundleSha != "")) { + console.error("Error: bundleUrl and bundleSha must be specified together"); + process.exit(1); + } const signer = await getSigner(argv); const projects = await getContract(argv, "projects", signer); const projectId = normalizeHex(argv.projectId as string); diff --git a/src/commands/project/create.ts b/src/commands/project/create.ts index 1326a0a..9806e07 100644 --- a/src/commands/project/create.ts +++ b/src/commands/project/create.ts @@ -27,6 +27,10 @@ export const builder = function (yargs: yargs.Argv) { }; export const handler = async function (argv: Arguments) { + if ((argv.bundleUrl != "") ^ (argv.bundleSha != "")) { + console.error("Error: bundleUrl and bundleSha must be specified together"); + process.exit(1); + } const signer = await getSigner(argv); const projects = await getContract(argv, "projects", signer); const address = await signer.getAddress(); diff --git a/src/helpers.ts b/src/helpers.ts index a526b25..7fa2972 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -33,7 +33,7 @@ export async function getSigner(argv: Arguments): Promise { } else { const addresses = await listWallets(); if (!addresses.length) { - console.error("ERROR: No private keys found. Use npx armada key-import."); + console.error("Error: No private keys found. Use npx armada key-import."); process.exit(1); } From 1b44b6012dd99ce02715f1debe9759df62cd353c Mon Sep 17 00:00:00 2001 From: the-masthead <116779271+the-masthead@users.noreply.github.com> Date: Fri, 28 Oct 2022 18:12:13 -0700 Subject: [PATCH 4/4] Simplify code --- src/commands/project/content.ts | 2 +- src/commands/project/create.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/project/content.ts b/src/commands/project/content.ts index 4e3e7f6..9f2e012 100644 --- a/src/commands/project/content.ts +++ b/src/commands/project/content.ts @@ -21,7 +21,7 @@ export const builder = function (yargs: yargs.Argv) { }; export const handler = async function (argv: Arguments) { - if ((argv.bundleUrl != "") ^ (argv.bundleSha != "")) { + if ((argv.bundleUrl != "") !== (argv.bundleSha != "")) { console.error("Error: bundleUrl and bundleSha must be specified together"); process.exit(1); } diff --git a/src/commands/project/create.ts b/src/commands/project/create.ts index 9806e07..a42be6a 100644 --- a/src/commands/project/create.ts +++ b/src/commands/project/create.ts @@ -27,7 +27,7 @@ export const builder = function (yargs: yargs.Argv) { }; export const handler = async function (argv: Arguments) { - if ((argv.bundleUrl != "") ^ (argv.bundleSha != "")) { + if ((argv.bundleUrl != "") !== (argv.bundleSha != "")) { console.error("Error: bundleUrl and bundleSha must be specified together"); process.exit(1); }