Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add content publish command #6

Merged
merged 4 commits into from
Oct 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name> <build-dir>
```

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 publish <project-id> <bundle-url> <bundle-sha>
```

Example: `npx armada publish 0x0123... https://.../my-site-v1.0.0.tgz 0xabcd...`
60 changes: 60 additions & 0 deletions abi/staging/ArmadaProjects.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down Expand Up @@ -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"
}
]
}
60 changes: 60 additions & 0 deletions abi/testnet/ArmadaProjects.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down Expand Up @@ -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"
}
]
}
4 changes: 0 additions & 4 deletions src/checksum.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { createHash } from "crypto";
import { createReadStream, PathLike } from "fs";

export async function sha1File(path: any): Promise<string> {
return hashFile("sha1", path);
}

export async function sha256File(path: string): Promise<string> {
return hashFile("sha256", path);
}
Expand Down
26 changes: 20 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env node

import path from "path";
import tar from "tar";
import yargs, { CommandModule } from "yargs";
Expand All @@ -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>)
Expand All @@ -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) => {
Expand Down Expand Up @@ -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) {
Expand Down
23 changes: 9 additions & 14 deletions src/commands/key/import.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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`);
};
37 changes: 14 additions & 23 deletions src/commands/node/list.ts
Original file line number Diff line number Diff line change
@@ -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");
};
38 changes: 38 additions & 0 deletions src/commands/project/content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import yargs, { Arguments } from "yargs";
import { decodeEvent, getContract, getSigner, normalizeHex } from "../../helpers";

export const command = ["project-content <project-id> <bundle-url> <bundle-sha>", "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) {
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);
const bundleSha = normalizeHex(argv.bundleSha as string);
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");
};
Loading