diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..6442effca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+node_modules/
+obsolete/
+.DS_Store
+.tmp/
+dist/types/shims/
+shims/*.d.ts
+**/*.swp
+
+# Weird intermediate files tsc generates for references
+**/src.ts/*.js
+
+# Weird file Browserify sometimes leaves lying around.
+**/*.tmp-browserify-*
+
+lerna-debug.log
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..f1103395a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+Changelog
+=========
+
+This change log is managed by scripts/index.js but may
+be manually updated.
+
+Coming Soon...
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 684e27f7d..d0ef66b5f 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,85 @@
-Coming soon...
+The Ethers Project
+==================
+
+**EXPERIMENTAL!!!**
+
+This is just a development version to experiment with lerna.
+
+**Do NOT use**
+
+
+Installing
+----------
+
+**node.js**
+
+```
+/home/ricmoo/some_project> npm install --save ethers
+```
+
+**browser**
+
+```
+
+```
+
+
+Ancillary Packages
+------------------
+
+These are a number of packages not included in the umbrella `ethers ` npm package, and
+additional packages are always being added. Often these packages are for specific
+use-cases, so rather than adding them to the umbrella package, they are added as
+ancillary packaged, which can be included by those who need them, while not bloating
+everyone else with packages they do not need.
+
+We will keep a list of useful pacakges here.
+
+- `@ethersproject/experimental`
+- `@ethersproject/cli`
+- `@ethersproject/ens`
+- `@ethersproject/ledger`
+- `@ethersproject/trezor`
+
+
+Hacking
+-------
+
+This project uses a combination of Lerna and the ./admin scripts to manage
+itself as a package of packages.
+
+The umbrella package can be found in `packages/ethers`, and all packages in general
+can be found in the `packages/` folder.
+
+If you add new dependencies to any package (incuding internal dependencies), you will
+need to re-create the internal links and re-build teh dependency graph::
+
+```
+/home/ethers> npm run bootstrap
+```
+
+To run a continuous build (with incremental TypeScript compilation):
+
+```
+/home/ethers> npm run auto-build
+```
+
+Finally, once you have made all your changes, you will need to bump the version
+of packages that changed their NPM tarballs, as well as update the _version.*
+and distribution builds (which is what we host on the CDN for browser-based
+apps). To do this, run:
+
+
+```
+/home/ethers> npm run update-version
+```
+
+Which will also list all packages that have changed along with the specifc files.
+
+
+License
+-------
+
+MIT License (including **all** dependencies).
+
diff --git a/admin/README.md b/admin/README.md
new file mode 100644
index 000000000..3e24c14fd
--- /dev/null
+++ b/admin/README.md
@@ -0,0 +1,80 @@
+Admin Tool
+==========
+
+This tool is meant for admin taks related to ethers.js.
+
+
+Workflow
+--------
+
+After a new change is made and `npm run build` has been used to compile the
+TypeScript, the following steps should be completed to publish it.
+
+1. Run `admin status` or `admin diff` to update the package.json (tarballHash and version) and audit changes
+2. Run `admin add-source` to stage changed source files
+3. Run `git commit -S -m "message..."`
+4. Run `npm dist` to create the changelog and all dist files
+5. Update the changelog as needed
+6. Run `admin add-dist` to stage the dist files
+7. Run test cases
+8. Run `git commit -S -m "Updated dist files."`
+9. Run `git push`
+10. Wait for TravisCI to complete running test cases
+11. Run `admin publish` to publish changed packages to NPM and tag GitHub
+
+
+Status: admin status
+--------------------
+
+- Updates all package.json `tarballHash`
+- List all files that are different from the most recent published files.
+
+Diff: admin diff
+----------------
+
+- Updates all package.json `tarballHash`
+- List all diffs between the local files and the most recent published files.
+
+Add Source: admin add-source
+----------------------------
+
+- Stages all changed files which are library source files
+
+Add Dist: admin add-dist
+------------------------
+
+- Stages all changed files which are dist files
+
+
+Update Dependency Graph: admin/cmds/update-depgraph
+---------------------------------------------------
+
+This is run as part of `npm run bootstrap` before running lerna bootstrap.
+It recomputes the dependency graph and writes out the ordered
+**tsconfig.project.json**
+
+
+Update Versions: admin/cmds/update-versions
+-------------------------------------------
+
+Run using the `npm run update-versions`, which also cleans, bootstraps and
+rebuilds the project.
+
+For each package that has changed from the version in NPM (the published
+tarballs are compared):
+
+- Update the `version` in the **package.json**
+- Update the **src.ts/_version.js** (matches the **package.json**)
+- Updates the `tarballHash` in the **package.json**
+- Compiles the TypeScript (which updates the **_version.js** and **_version.d.js**)
+- Lists all changed files
+
+
+Publish: admin/cmds/publish
+----------------------------
+
+Run using `node admin/cmds/publish`.
+
+- Publish (in dependency order) changed files to NPM
+- The `gitHead` is updated in **only** the NPM **package.json**
+
diff --git a/admin/build.js b/admin/build.js
new file mode 100644
index 000000000..9e912a0fa
--- /dev/null
+++ b/admin/build.js
@@ -0,0 +1,61 @@
+"use strict";
+
+const fs = require("fs");
+const resolve = require("path").resolve;
+const spawn = require("child_process").spawn;
+
+const local = require("./local");
+
+function run(progname, args, ignoreErrorStream) {
+ return new Promise((resolve, reject) => {
+ const proc = spawn(progname, args);
+
+ let stdout = Buffer.from([]);
+ let stderr = Buffer.from([]);
+
+ proc.stdout.on("data", (data) => {
+ stdout = Buffer.concat([ stdout, data ]);
+ });
+
+ proc.stderr.on("data", (data) => {
+ stderr = Buffer.concat([ stdout, data ]);
+ });
+
+ proc.on("error", (error) => {
+ console.log("ERROR");
+ console.log(stderr.toString());
+ error.stderr = stderr.toString();
+ error.stdout = stdout.toString();
+ reject(error);
+ });
+
+ proc.on("close", (code) => {
+ if ((stderr.length && !ignoreErrorStream) || code !== 0) {
+ console.log("ERROR");
+ console.log(stderr.toString());
+
+ let error = new Error("stderr not empty");
+ error.stderr = stderr.toString();
+ error.stdout = stdout.toString();
+ error.statusCode = code;
+ reject(error);
+ } else {
+ resolve(stdout.toString());
+ }
+ });
+ });
+}
+
+function runBuild() {
+ return run("npx", [ "tsc", "--build", resolve(__dirname, "../tsconfig.project.json") ]);
+}
+
+function runDist() {
+ return run("npx", [ "lerna", "run", "dist" ], true);
+}
+
+module.exports = {
+ run: run,
+ runDist: runDist,
+ runBuild: runBuild
+};
diff --git a/admin/changelog.js b/admin/changelog.js
new file mode 100644
index 000000000..ec6d7bd68
--- /dev/null
+++ b/admin/changelog.js
@@ -0,0 +1,101 @@
+"use strict";
+
+const fs = require("fs");
+const resolve = require("path").resolve;
+
+const git = require("./git");
+const local = require("./local");
+const utils = require("./utils");
+
+async function generate() {
+
+ let version = local.loadPackage("ethers").version;
+ let latest = await git.getLatestTag();
+ let date = utils.today();
+ let ethersVersion = "ethers/" + version;
+ let log = await git.run([ "log", "--format=%B", (latest + "..") ])
+
+ let existing = fs.readFileSync(resolve(__dirname, "../CHANGELOG.md")).toString().split("\n");
+
+ let sections = [ [ ] ];
+
+ for (let i = 0; i < existing.length; i++) {
+ let line = existing[i];
+ let lastSection = sections[sections.length - 1];
+ if (line.substring(0, 3) === "---" || line.substring(0, 3) === "===") {
+ sections.push([ lastSection.pop(), line ])
+ } else {
+ lastSection.push(line);
+ }
+ }
+
+ // Snip off the dummy first section
+ sections.shift();
+
+ let output = [ ];
+
+ let addSection = (section) => {
+
+ // Add the header with the same underline style
+ let header = section[0];
+ output.push(header);
+ output.push(utils.repeat(section[1], header.length));
+
+ // Add gap before body
+ output.push("");
+
+ // For each line, properly indent it (the root body does not get indented)
+ section.slice(2).forEach((line) => {
+ line = line.trim();
+ if (line === "") { return; }
+ if (header.trim().toLowerCase() !== "changelog") {
+ if (line.substring(0, 1) !== "*") {
+ line = " " + line;
+ }
+ line = " " + line;
+ }
+ output.push(line);
+ });
+
+ // Add gap after body
+ output.push("");
+ }
+
+ let newSection = [];
+ {
+ let header = "ethers/" + version + " (" + date + ")";
+ newSection.push(header);
+ newSection.push(utils.repeat("-", header.length));
+ log.split("\n").forEach((line) => {
+ line = line.trim();
+ if (line === "") { return; }
+ newSection.push(" * " + line);
+ });
+ }
+
+ sections.forEach((section) => {
+ let header = section[0].split(" ");
+
+ // Check if this is the current version, we may need to update
+ if (header.length === 2) {
+ // This new section obsoletes the old new section...
+ if (header[0] === ethersVersion) {
+ addSection(newSection);
+ newSection = null;
+ return;
+
+ // Put the new section before any old sections
+ } else if (newSection) {
+ addSection(newSection);
+ }
+ }
+
+ addSection(section);
+ });
+
+ return output.join("\n") + "\n";
+}
+
+module.exports = {
+ generate: generate
+}
diff --git a/admin/cmds/publish.js b/admin/cmds/publish.js
new file mode 100644
index 000000000..8589c2568
--- /dev/null
+++ b/admin/cmds/publish.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const config = require("../config");
+
+const { getOrdered, loadPackage } = require("../depgraph");
+const { getPackageVersion, publish } = require("../npm");
+const { log } = require("../log");
+
+const USER_AGENT = "ethers-dist@0.0.0";
+const TAG = "next";
+
+let dirnames = getOrdered();
+
+// Only publish specific packages
+if (process.argv.length > 2) {
+ let filter = process.argv.slice(2);
+
+ // Verify all named packages exist
+ filter.forEach((dirname) => {
+ try {
+ loadPackage(dirname);
+ } catch (error) {
+ console.log("Package not found: " + dirname);
+ process.exit(1);
+ }
+ });
+
+ // Filter out pacakges we don't care about
+ dirnames = dirnames.filter((dirname) => (filter.indexOf(dirname) >= 0));
+}
+
+(async function() {
+ let token = null;
+
+ // @TODO: Fail if there are any untracked files or unchecked in files
+
+ // Load the token from the encrypted store
+ try {
+ token = await config.get("token");
+ } catch (error) {
+ switch (error.message) {
+ case "wrong password":
+ log("");
+ break;
+ case "cancelled":
+ break;
+ default:
+ console.log(error);
+ }
+
+ log("");
+
+ return;
+ }
+
+ token = token.trim().split("=");
+
+ let options = {
+ npmVersion: USER_AGENT,
+ tag: TAG
+ };
+
+ // Set the authentication token
+ options[token[0]] = token[1];
+
+ for (let i = 0; i < dirnames.length; i++) {
+ let dirname = dirnames[i];
+
+ if (dirname === "ethers") {
+ options.tag = "next";
+ } else {
+ options.tag = "latest";
+ }
+
+ let info = loadPackage(dirname);
+ let npmInfo = await getPackageVersion(info.name);
+ if (!npmInfo) { npmInfo = { version: "NEW" }; }
+
+ if (info.tarballHash === npmInfo.tarballHash) { continue; }
+
+ log(` ${info.name}...`);
+ log(` ${npmInfo.version} > ${info.version}`);
+
+ let success = await publish(dirname, options);
+ if (!success) {
+ log(" ");
+ return;
+ }
+ log(" ");
+ }
+
+})();
diff --git a/admin/cmds/update-depgraph.js b/admin/cmds/update-depgraph.js
new file mode 100644
index 000000000..618fbe38f
--- /dev/null
+++ b/admin/cmds/update-depgraph.js
@@ -0,0 +1,16 @@
+"use stricT";
+
+const depgraph = require("../depgraph");
+const { log } = require("../log");
+const { loadJson, resolve, saveJson } = require("../utils");
+
+(async function() {
+ log(``);
+ let ordered = depgraph.getOrdered(true);
+
+ let path = resolve("tsconfig.project.json")
+
+ let projectConfig = loadJson(path);
+ projectConfig.references = ordered.map((name) => ({ path: ("./packages/" + name) }));
+ saveJson(path, projectConfig);
+})();
diff --git a/admin/cmds/update-versions.js b/admin/cmds/update-versions.js
new file mode 100644
index 000000000..6b3c935fa
--- /dev/null
+++ b/admin/cmds/update-versions.js
@@ -0,0 +1,131 @@
+"use strict";
+
+// Expected this to be run after
+// - npm run clean
+// - npm run bootstrap
+// - npm run build
+
+const fs = require("fs");
+
+const semver = require("semver");
+
+const { runBuild, runDist } = require("../build");
+const { getOrdered, loadPackage } = require("../depgraph");
+const { getDiff, getGitTag } = require("../git");
+const { updatePackage } = require("../local");
+const { getPackageVersion } = require("../npm");
+const { resolve } = require("../utils");
+const { colorify, log } = require("../log");
+
+const { getProgressBar } = require("../../packages/cli/prompt");
+
+let dirnames = getOrdered();
+
+// Only publish specific packages
+if (process.argv.length > 2) {
+ let filter = process.argv.slice(2);
+
+ // Verify all named packages exist
+ filter.forEach((dirname) => {
+ try {
+ loadPackage(dirname);
+ } catch (error) {
+ console.log("Package not found: " + dirname);
+ process.exit(1);
+ }
+ });
+
+ // Filter out pacakges we don't care about
+ dirnames = dirnames.filter((dirname) => (filter.indexOf(dirname) >= 0));
+}
+
+(async function() {
+ let progress = getProgressBar(colorify("Updating versions", "bold"));
+
+ for (let i = 0; i < dirnames.length; i++) {
+ progress(i / dirnames.length);
+
+ let dirname = dirnames[i];
+ let path = resolve("packages", dirname);
+
+ // Get local package.json (update the tarballHash)
+ let info = await updatePackage(dirname);
+
+ // Get the remote package.json (or sub in a placeholder for new pacakges)
+ let npmInfo = await getPackageVersion(info.name);
+ if (!npmInfo) { npmInfo = { version: "NEW" }; }
+
+ if (info.tarballHash === npmInfo.tarballHash) { continue; }
+
+ // Bump the version if necessary
+ if (info.version === npmInfo.version) {
+ let newVersion = semver.inc(info.version, "prerelease", "beta");
+
+ // Write out the _version.ts
+ if (!info._ethers_nobuild) {
+ let code = "export const version = " + JSON.stringify(newVersion) + ";\n";
+ fs.writeFileSync(resolve(path, "src.ts/_version.ts"), code);
+ }
+
+ // Update the package.json (we do this after _version, so if we fail,
+ // this remains old; which is what triggers the version bump)
+ info = await updatePackage(dirname, { version: newVersion });
+ }
+ }
+ progress(1);
+
+ try {
+ log("");
+ await runBuild();
+ log("");
+ let content = await runDist();
+ console.log(content);
+ } catch (error) {
+ console.log(error);
+ log("");
+ return;
+ }
+
+ // Update the tarball hash now that _version and package.json may have changed.
+ progress = getProgressBar(colorify("Updating tarballHash", "bold"));
+ for (let i = 0; i < dirnames.length; i++) {
+ progress(i / dirnames.length);
+ await updatePackage(dirnames[i]);
+ }
+ progress(1);
+
+ // Show the changed files (compared to npm)
+ for (let i = 0; i < dirnames.length; i++) {
+ let dirname = dirnames[i];
+
+ // Get local package.json
+ let info = await loadPackage(dirname);
+ let path = resolve("packages/", dirname);
+
+ // Get the remote package.json (or sub in a placeholder for new pacakges)
+ let npmInfo = await getPackageVersion(info.name);
+ if (!npmInfo) { npmInfo = { version: "NEW" }; }
+
+ // No change
+ if (info.tarballHash === npmInfo.tarballHash) { continue; }
+
+ let gitHead = await getGitTag(path);
+
+ log(`: ${info.name}`);
+ log(` (bumping version)`);
+ log(` ${npmInfo.version} => ${info.version}`)
+ log(` `);
+ let filenames = await getDiff(path, npmInfo.gitHead, true);
+ filenames.forEach((filename) => {
+ let short = filename.split("/").slice(1).join("/");
+ if (short.indexOf("/src.ts/") >= 0 || short.indexOf("/dist/") >= 0) {
+ log(` `);
+ } else {
+ log(` ${short}`);
+ }
+ });
+ log("");
+ }
+
+
+})();
diff --git a/admin/config.js b/admin/config.js
new file mode 100644
index 000000000..5d894c76c
--- /dev/null
+++ b/admin/config.js
@@ -0,0 +1,87 @@
+"use strict";
+
+const fs = require("fs");
+const os = require("os");
+const resolve = require("path").resolve;
+
+const AES = require("aes-js");
+const scrypt = require("scrypt-js");
+
+const prompt = require("../packages/cli/prompt");
+const randomBytes = require("../packages/random").randomBytes;
+const computeHmac = require("../packages/sha2").computeHmac;
+
+const colorify = require("./log").colorify;
+
+function getConfigFilename() {
+ return resolve(os.homedir(), ".ethers-dist");
+}
+
+function getScrypt(message, password, salt) {
+ let progressBar = prompt.getProgressBar(message);
+ return new Promise((resolve, reject) => {
+ scrypt(Buffer.from(password), Buffer.from(salt), (1 << 17), 8, 1, 64, (error, progress, key) => {
+ if (error) { return reject(error); }
+ progressBar(progress);
+ if (key) { resolve(key); }
+ });
+ });
+}
+
+async function loadConfig(dkey) {
+ let config = { };
+
+ let filename = getConfigFilename();
+ if (fs.existsSync(filename)) {
+ let data = JSON.parse(fs.readFileSync(filename));
+ let ciphertext = Buffer.from(data.ciphertext, "base64");
+ let iv = Buffer.from(data.iv, "base64");
+ let aes = new AES.ModeOfOperation.ctr(dkey.slice(0, 32), new AES.Counter(iv));
+ let plaintext = aes.decrypt(ciphertext);
+ let hmac = computeHmac("sha512", dkey.slice(32, 64), plaintext);
+ if (hmac !== data.hmac) {
+ throw new Error("wrong password");
+ }
+ config = JSON.parse(Buffer.from(plaintext).toString());
+ }
+
+ return config;
+}
+
+async function getConfig(key) {
+ let password = await prompt.getPassword(colorify("Password (seesion-store): ", "bold"));
+ let dkey = await getScrypt(colorify("Decrypting", "bold"), password, key);
+
+ let config = await loadConfig(dkey);
+ return config[key];
+}
+
+async function setConfig(key, value) {
+ let password = await prompt.getPassword(colorify("Password (seesion-store): ", "bold"));
+ let dkey = await getScrypt("Encrypting", password, key);
+
+ let config = await loadConfig(dkey);
+ config[key] = value;
+ config._junk = Buffer.from(randomBytes(16 + parseInt(Math.random() * 48))).toString("base64")
+
+ let plaintext = Buffer.from(JSON.stringify(config));
+
+ let iv = Buffer.from(randomBytes(16));
+ let hmac = computeHmac("sha512", dkey.slice(32, 64), plaintext);
+
+ let aes = new AES.ModeOfOperation.ctr(dkey.slice(0, 32), new AES.Counter(iv));
+ let ciphertext = Buffer.from(aes.encrypt(plaintext));
+
+ let data = {
+ ciphertext: ciphertext.toString("base64"),
+ iv: iv.toString("base64"),
+ hmac: hmac
+ };
+
+ fs.writeFileSync(getConfigFilename(), JSON.stringify(data, null, 2));
+}
+
+module.exports = {
+ get: getConfig,
+ set: setConfig
+}
diff --git a/admin/depgraph.js b/admin/depgraph.js
new file mode 100644
index 000000000..42b887f60
--- /dev/null
+++ b/admin/depgraph.js
@@ -0,0 +1,94 @@
+"use strict";
+
+const fs = require("fs");
+
+const { loadJson, resolve } = require("./utils");
+
+const ROOT = resolve("packages");
+
+const dirnames = fs.readdirSync(ROOT);
+
+function loadPackage(dirname) {
+ return loadJson(resolve("packages", dirname, "package.json"));
+}
+
+function getOrdered(skipNobuild) {
+ let packages = { };
+ let filenames = { };
+
+ let addDeps = (name, depends) => {
+ Object.keys(depends).forEach((dep) => {
+ // Not a package we manage
+ if (packages[dep] == null) { return; }
+ deps[name][dep] = true;
+ });
+ }
+
+ for (let i = 0; i < dirnames.length; i++) {
+ let dirname = dirnames[i];
+ let info = loadPackage(dirname);
+ if (skipNobuild && info._ethers_nobuild) { continue; }
+ packages[info.name] = info;
+ filenames[info.name] = dirname;
+ }
+
+ // Maps names to list of dependencies; { [ name:string]: Array }
+ let deps = { };
+ let depGraph = { };
+
+ Object.keys(packages).forEach((name) => {
+ let info = packages[name];
+ deps[info.name] = { };
+ addDeps(info.name, info.dependencies || { });
+ addDeps(info.name, info.devDependencies || { });
+ deps[info.name] = Object.keys(deps[info.name]);
+ deps[info.name].sort();
+ });
+
+ let ordered = [ ];
+ let remaining = Object.keys(deps);
+
+ let isSatisfied = (name) => {
+ for (let i = 0; i < deps[name].length; i++) {
+ if (ordered.indexOf(deps[name][i]) === -1) { return false; }
+ }
+ return true;
+ }
+
+ while (remaining.length) {
+ let bail = true;
+ for (let i = 0; i < remaining.length; i++) {
+ if (!isSatisfied(remaining[i])) { continue; }
+ bail = false;
+ ordered.push(remaining[i]);
+ remaining.splice(i, 1);
+ break;
+ }
+
+ if (bail) {
+ throw new Error("Nothing processed; circular dependencies...");
+ }
+ }
+
+ return ordered.map((name) => filenames[name]);
+}
+
+function sort(dirnames) {
+ let ordered = getOrdered();
+ dirnames.sort((a, b) => {
+ let ai = ordered.indexOf(local.loadPackage(a).name);
+ let bi = ordered.indexOf(local.loadPackage(b).name);
+ if (ai === -1 || bi === -1) {
+ throw new Error("unknown dirname - " + [a, b].join(", "));
+ }
+ return ai - bi;
+ });
+}
+
+module.exports = {
+ dirnames: dirnames,
+ getOrdered: getOrdered,
+ loadPackage: loadPackage,
+ ROOT: ROOT,
+ sort: sort
+}
diff --git a/admin/git.js b/admin/git.js
new file mode 100644
index 000000000..0582ad16b
--- /dev/null
+++ b/admin/git.js
@@ -0,0 +1,173 @@
+"use strict";
+
+const resolve = require("path").resolve;
+const spawn = require("child_process").spawn;
+
+const semver = require("semver");
+
+const { run } = require("./build");
+const { loadPackage } = require("./local");
+
+function git(args) {
+ return run("git", args);
+}
+
+function getStatus(filename) {
+ return git([ "status", "-s", resolve(__dirname, "..", filename) ]).then((result) => {
+ result = result.trim();
+ if (result === "") { return "unmodified"; }
+ switch (result.substring(0, 2)) {
+ case 'M ': return "modified";
+ case 'A ': return "added";
+ case 'D ': return "deleted";
+ case 'R ': return "renamed";
+ case 'C ': return "copied";
+ case 'U ': return "updated";
+ case '??': return "untracked";
+ }
+ console.log(result);
+ return "unknown";
+ });
+}
+
+async function getChanges(latest) {
+ let diff = await git(["diff", "--name-only", latest ]);
+
+ // Map dirname => { dist: [ ], src: [ ] }
+ let changes = { "_": { filename: "_", dist: [], src: [] } };
+
+ diff.split("\n").forEach((line) => {
+ // e.g. packages/constants/index.d.ts
+ let comps = line.trim().split("/");
+
+ // Track non-packages as dist
+ if (comps.length < 2 || comps[0] !== "packages") {
+ let filename = comps.join("/").trim();
+ if (filename === "") { return; }
+ changes._.dist.push(filename);
+ return;
+ }
+
+ let name = loadPackage(comps[1]).name;
+
+ let change = changes[name];
+ if (!change) {
+ change = { filename: comps[1], dist: [ ], src: [ ] }
+ changes[name] = change;
+ }
+
+ // Split changes into source changes (src.ts/) or dist changes (output of TypeScript)
+ if (comps[2] === "src.ts") {
+ change.src.push(comps.join("/"));
+ } else {
+ change.dist.push(comps.join("/"));
+ }
+ });
+
+ return changes;
+}
+
+function getLatestTag() {
+ let seq = Promise.resolve();
+
+ // @TODO: Pull
+ if (false) {
+ seq = seq.then(() => {
+ console.log("Pulling remote changes...");
+ return git([ "pull" ]);
+ });
+ }
+
+ seq = seq.then(() => {
+ return git([ "tag" ]).then((tags) => {
+ tags = tags.split("\n").filter(tag => (tag.match(/^v[0-9]+\.[0-9]+\.[0-9]+\-/)));
+ tags.sort(semver.compare)
+ return tags.pop();
+ });
+ });
+
+ return seq;
+}
+
+function findChanges(latest) {
+ let seq = Promise.resolve();
+
+ seq = seq.then(() => {
+ return git(["diff", "--name-only", latest, "HEAD" ]).then((result) => {
+ let filenames = { };
+ result.split("\n").forEach((line) => {
+ // e.g. packages/constants/index.d.ts
+ let comps = line.trim().split("/");
+ if (comps.length < 2) { return; }
+ filenames[comps[1]] = true;
+ });
+ return Object.keys(filenames);
+ });
+ });
+
+ seq = seq.then((filenames) => {
+ return filenames.map((filename) => {
+ let name = packages[filename].name;
+ return {
+ filename: filename,
+ name: name,
+ localVersion: getLocalVersion(name),
+ }
+ });
+ });
+
+ seq = seq.then((packages) => {
+ let seq = Promise.resolve();
+ packages.forEach((p) => {
+ seq = seq.then(() => {
+ return getNpmVersion(p.name).then((version) => {
+ p.npmVersion = version;
+ });
+ });
+ });
+ return seq.then(() => packages);
+ });
+ return seq;
+}
+
+async function getGitTag(filename) {
+ let result = await git([ "log", "-n", "1", "--", filename ]);
+ result = result.trim();
+ if (!result) { return null; }
+ result = result.match(/^commit\s+([0-9a-f]{40})\n/i);
+ if (!result) { return null; }
+ return result[1];
+}
+
+async function getDiff(filename, tag, nameOnly) {
+ if (tag == null) { tag = "HEAD"; }
+ let cmd = [ "diff", "--name-only", tag, "--", filename ]
+ if (!nameOnly) { cmd.splice(1, 1); }
+ let result = await git(cmd);
+ result = result.trim();
+ if (result === "") { return [ ]; }
+ return result.split("\n");
+}
+
+async function getUntracked(filename) {
+ let cmd = [ "ls-files", "-o", "--exclude-standard"];
+ if (filename) {
+ cmd.push("--");
+ cmd.push(filename);
+ }
+ let result = await git(cmd);
+ result = result.trim();
+ if (result === "") { return [ ]; }
+ return result.split("\n");
+}
+
+module.exports = {
+ findChanges: findChanges,
+ getChanges: getChanges,
+ getDiff: getDiff,
+ getGitTag: getGitTag,
+ getLatestTag: getLatestTag,
+ getStatus: getStatus,
+ getUntracked: getUntracked,
+ run: git,
+}
diff --git a/admin/index.js b/admin/index.js
new file mode 100644
index 000000000..3da0db121
--- /dev/null
+++ b/admin/index.js
@@ -0,0 +1,401 @@
+"use strict";
+
+const fs = require("fs");
+const resolve = require("path").resolve;
+
+const diff = require("diff");
+const semver = require("semver");
+
+const { getProgressBar, prompt } = require("../packages/cli/prompt");
+
+const build = require("./build");
+const changelog = require("./changelog");
+const depgraph = require("./depgraph");
+const { colorify, colorifyStatus, log } = require("./log");
+const config = require("./config")
+const git = require("./git");
+const local = require("./local");
+const npm = require("./npm");
+const utils = require("./utils");
+
+/*
+async function runChanged(dirnames, callback) {
+ try {
+ await callback(dirname, info, npmInfo);
+ } catch (error) {
+ console.log(error);
+ console.log(colorify("Aborting! " + error.message));
+ return;
+ }
+ }
+ }
+}
+*/
+/*
+ if (diff) {
+ } else {
+*/
+
+async function runDiff(dirnames) {
+ // Default to all packages
+ if (dirnames == null || dirnames.length === 0) { dirnames = local.dirnames; }
+
+ for (let i = 0; i < dirnames.length; i++) {
+ let dirname = dirnames[i];
+
+ // Get local (update the tarballHash) and remote package.json
+ let info = await local.loadPackage(dirname);
+ let npmInfo = await npm.getPackageVersion(info.name);
+ if (!npmInfo) { npmInfo = { gitHead: "HEAD", version: "NEW" }; }
+
+ let delta = await git.getDiff(resolve(__dirname, "../packages", dirname), npmInfo.gitHead);
+
+ if (delta.length === 0) { continue; }
+
+ // Bump the version if necessary
+ if (info.version === npmInfo.version) {
+ info.version = semver.inc(info.version, "prerelease", "beta");
+ }
+
+ console.log(colorify(": ") + info.name);
+ console.log(colorify(" (run update to bump version)"));
+ console.log(" " + npmInfo.gitHead)
+ console.log(" " + npmInfo.version + colorify(" => ", "bold") + info.version)
+
+ console.log(colorify(" Diff", "bold"));
+ delta.forEach((line) => {
+ let color = "blue";
+ switch (line.substring(0, 1)) {
+ case '+':
+ color = "green";
+ break;
+ case '-':
+ color = "red";
+ break;
+ case ' ':
+ color = "normal";
+ break;
+ }
+ console.log(" " + colorify(line, color));
+ });
+
+ console.log("");
+ }
+
+ console.log("");
+}
+
+async function updateChangelog() {
+ let filename = resolve(local.ROOT, "../CHANGELOG.md");
+
+ let lastVersion = await git.getLatestTag();
+ let newVersion = "v" + local.getVersion("ethers");
+
+ let current = fs.readFileSync(filename).toString();
+ let log = await changelog.generate();
+ if (log === current) { return; }
+
+ let changes = diff.createTwoFilesPatch("CHANGELOG-old.md", "CHANGELOG.md", current, log, lastVersion, newVersion);
+ console.log(changes);
+
+ try {
+ let response = await prompt.getChoice(colorify("Accept changes?", "bold"), "yn", "n");
+ if (response === "n") { throw new Error("Not changing."); }
+ } catch (error) {
+ console.log("Abort: " + error.message);
+ return;
+ }
+
+ fs.writeFileSync(filename, log);
+}
+
+// Updates the dependency-graph (tsconfig.project.json) so the build order is correct
+async function runUpdateDepgraph() {
+ log(``);
+ let ordered = depgraph.getOrdered();
+
+ let path = resolve(local.ROOT, "../tsconfig.project.json")
+
+ let projectConfig = local.loadJson(path);
+ projectConfig.references = ordered.map((name) => ({ path: ("./packages/" + name) }));
+ local.saveJson(path, projectConfig);
+}
+
+async function runUpdate(dirnames) {
+
+ // Check for untracked files...
+ let untracked = [ ];
+ if (dirnames == null || dirnames.length === 0) {
+ dirnames = local.dirnames;
+ let filenames = await git.getUntracked(resolve(__dirname, ".."));
+ for (let i = 0; i < filenames.length; i++) {
+ untracked.push(filenames[i]);
+ }
+ } else {
+ for (let i = 0; i < dirnames.length; i++) {
+ let filenames = await git.getUntracked(resolve(local.ROOT, dirnames[i]));
+ for (let j = 0; j < filenames.length; j++) {
+ untracked.push(filenames[j]);
+ }
+ }
+ }
+
+ // Untracked files! Abort.
+ if (untracked.length) {
+ log("");
+ untracked.forEach((filename) => {
+ console.log(" " + filename);
+ });
+ log("");
+ return;
+ }
+
+ log(``);
+ await build.runBuild()
+
+ log("");
+
+ // @TODO: Root
+
+ // Update all the package.json and _version.ts
+ let progress = getProgressBar(colorify("Updating versions", "bold"));
+ for (let i = 0; i < dirnames.length; i++) {
+ progress(i / dirnames.length);
+
+ let dirname = dirnames[i];
+ let path = resolve(__dirname, "../packages/", dirname);
+
+ // Get local package.json (update the tarballHash)
+ let info = await local.updatePackage(dirname);
+
+ // Get the remote package.json (or sub in a placeholder for new pacakges)
+ let npmInfo = await npm.getPackageVersion(info.name);
+ if (!npmInfo) { npmInfo = { version: "NEW" }; }
+
+ if (info.tarballHash === npmInfo.tarballHash) { continue; }
+
+ // Bump the version if necessary
+ if (info.version === npmInfo.version) {
+ let newVersion = semver.inc(info.version, "prerelease", "beta");
+
+ // Write out the _version.ts
+ if (!info._ethers_skipPrepare) {
+ let code = "export const version = " + JSON.stringify(newVersion) + ";\n";
+ fs.writeFileSync(resolve(path, "src.ts/_version.ts"), code);
+ }
+
+ // Update the package.json (we do this after _version, so if we fail,
+ // this remains old; which is what triggers the version bump)
+ info = await local.updatePackage(dirname, { version: newVersion });
+ }
+ }
+ progress(1);
+
+ // Build the TypeScript sources
+ log("");
+ try {
+ await build.runTsc();
+ } catch (error) {
+ console.log(error);
+ log("");
+ return;
+ }
+
+ // Run the dist
+ // @TODO:
+
+ // Update the tarball hash now that _version and package.json may have changed.
+ progress = getProgressBar(colorify("Updating tarballHash", "bold"));
+ for (let i = 0; i < dirnames.length; i++) {
+ progress(i / dirnames.length);
+ await local.updatePackage(dirnames[i]);
+ }
+ progress(1);
+
+ // Show the changed files (compared to npm)
+ for (let i = 0; i < dirnames.length; i++) {
+ let dirname = dirnames[i];
+
+ // Get local package.json
+ let info = await local.loadPackage(dirname);
+ let path = resolve(__dirname, "../packages/", dirname);
+
+ // Get the remote package.json (or sub in a placeholder for new pacakges)
+ let npmInfo = await npm.getPackageVersion(info.name);
+ if (!npmInfo) { npmInfo = { version: "NEW" }; }
+
+ // No change
+ if (info.tarballHash === npmInfo.tarballHash) { continue; }
+
+ let gitHead = await git.getGitTag(path);
+
+ log(`: ${info.name}`);
+ log(` (bumping version)`);
+ log(` ${npmInfo.version} => ${info.version}`)
+ log(` `);
+ let filenames = await git.getDiff(resolve(__dirname, "../packages", dirname), npmInfo.gitHead, true);
+ filenames.forEach((filename) => {
+ let short = filename.split("/").slice(1).join("/");
+ if (short.indexOf("/src.ts/") >= 0) {
+ log(` `);
+ } else {
+ log(` ${short}`);
+ }
+ });
+ log("");
+ }
+
+ // @TODO: Changelog
+ await updateChangelog();
+}
+
+async function runAdd(type, names) {
+ let latest = await git.getLatestTag();
+ console.log("");
+ console.log(colorify(": ") + latest);
+ console.log("");
+
+ let changes = await git.getChanges("HEAD");
+
+ if (!names || names.length === 0) {
+ names = Object.keys(changes);
+ }
+
+ let filenames = [ ];
+ for (let i = 0; i < names.length; i++) {
+ let name = names[i];
+ let change = changes[name] || changes[(packages[name] || {}).name];
+ if (!change) { return; }
+ change[type].forEach((filename) => {
+ filenames.push(filename);
+ });
+ }
+
+ if (filenames.length === 0) {
+ console.log(colorify(""));
+ console.log("");
+ return;
+ }
+
+ for (let i = 0; i < filenames.length; i++) {
+ let filename = filenames[i];
+ let status = await git.getStatus(filename);
+ console.log(" " + colorifyStatus(status) + ": " + utils.repeat(" ", 10 - status.length) + filename);
+ }
+
+ console.log("");
+
+ try {
+ let response = await prompt.getChoice(colorify("Add these files?", "bold"), "yn", "n");
+ if (response === "n") { throw new Error("Not adding."); }
+ } catch (error) {
+ console.log("Abort: " + error.message);
+ return;
+ }
+
+ let params = filenames.map((f) => f); //resolve(ROOT, f));
+ params.unshift("--");
+ params.unshift("add");
+
+ console.log("git " + params.join(" "));
+
+ try {
+ await git.run(params);
+ } catch (error) {
+ console.log("Error: (status: " + error.code + ")");
+ console.log(" " + error.stderr);
+ return;
+ }
+
+ console.log("Added.");
+}
+
+function runDist() {
+ // Run npm dist
+ // Generate changelog
+ // run status to update all the package
+ // add dist files?
+}
+
+async function runPublish(dirnames) {
+
+ // @TODO: Make sure there are no staged files
+
+ // @TODO: Make sure the repo has been pushed
+
+ // @TODO: Run the publish in the correct order
+
+ // Get the authentication token from our encrypted store
+ let token = await config.get("token");
+ token = token.trim().split("=");
+
+ let options = {
+ npmVersion: "ethers-dist@0.0.0",
+ tag: "next"
+ };
+
+ // Set the authentication token
+ options[token[0]] = token[1];
+
+ if (dirnames == null || dirnames.length === 0) { dirnames = local.dirnames; }
+ depgraph.sort(dirnames);
+
+ await runChanged(dirnames, async (dirname, info, npmInfo) => {
+ console.log(colorify(" ") + info.name + "...")
+ console.log(colorify(" Version: ", "blue") + npmInfo.version + colorify(" => ", "bold") + info.version);
+
+ let success = await npm.publish(dirname, options);
+ if (!success) {
+ console.log(colorify(" "));
+ throw new Error("");
+ }
+ console.log(colorify(" "));
+ });
+}
+
+async function runTest() {
+ let r = await git([ "tag", "--porcelain", "-a", "-m", "Title of Release\n\nHello\n-----\n\nTesting 4 **bold** #1\nHello World", "test6", "HEAD" ]);
+ console.log(r);
+ try {
+ r = await git([ "push", "--tags" ])
+ } catch(e) { console.log(e); }
+ console.log(r);
+}
+
+(function() {
+ let args = process.argv.slice(2);
+ switch (args[0]) {
+
+ // Compare published to current stage
+ case "diff":
+ return runDiff(args.slice(1));
+
+ // Add unchecked-in source files
+ case "add-source":
+ return runAdd("src", args.slice(1));
+
+ // Update all package.json. the changelog and dist files
+ case "update":
+ return runUpdate(args.slice(1));
+
+ // Update dependency graph (./tsconfig-project.json)
+ case "update-depgraph":
+ return runUpdateDepgraph();
+
+ // Add unchecked-in dist files
+ case "add-dist":
+ return runAdd("dist", args.slice(1));
+
+
+ // Add unchecked-in source files
+ case "changelog":
+ return updateChangelog();
+
+ // Add unchecked-in source files
+ case "publish":
+ return runPublish(args.slice(1));
+
+ case "test":
+ return runTest();
+ }
+})();
diff --git a/admin/local.js b/admin/local.js
new file mode 100644
index 000000000..415951cf2
--- /dev/null
+++ b/admin/local.js
@@ -0,0 +1,80 @@
+"use strict";
+
+const packlist = require("npm-packlist");
+const tar = require("tar");
+const keccak256 = require("../packages/keccak256").keccak256;
+const { dirnames, loadPackage, ROOT } = require("./depgraph");
+const { resolve, saveJson } = require("./utils");
+
+function savePackage(dirname, info) {
+ return saveJson(resolve(ROOT, dirname, "package.json"), info);
+}
+
+async function createTarball(dirname) {
+ let base = resolve(ROOT, dirname);
+
+ // From NPM publish, create the packed version
+ let files = await packlist({ path: base });
+ files = files.map((f) => ("./" + f));
+
+ let options = {
+ cwd: base,
+ prefix: 'package/',
+ portable: true,
+ sync: true,
+ // Provide a specific date in the 1980s for the benefit of zip,
+ // which is confounded by files dated at the Unix epoch 0.
+ mtime: new Date('1985-10-26T08:15:00.000Z'),
+ gzip: true
+ };
+
+ // Take the hash of the package sans
+ return tar.create(options, files).read();
+}
+
+async function updatePackage(dirname, values) {
+ let info = loadPackage(dirname);
+
+ if (values) {
+ for (let key in values) {
+ info[key] = values[key];
+ }
+ }
+ /*
+ ["dependencies", "devDependencies"].forEach((key) => {
+ let deps = info[key] || [];
+ for (let name in deps) {
+ if (name.substring(0, "@ethersproject".length) === "@ethersproject" || name === "ethers") {
+ deps[name] = ">5.0.0-beta.0";
+ }
+ }
+ });
+ */
+
+ //if (dirname !== "ethers") {
+ // delete info.publishConfig.tag;
+ //}
+
+ // Create a normalized version sans tarballHash to compute the tarballHash
+ delete info.tarballHash;
+ savePackage(dirname, info);
+
+ // Compute the tarballHash
+ let tarball = await createTarball(dirname);
+ info.tarballHash = keccak256(tarball);
+
+ // Save the updated package.json to disk
+ savePackage(dirname, info);
+
+ return info;
+}
+
+module.exports = {
+ ROOT: ROOT,
+ createTarball: createTarball,
+ dirnames: dirnames,
+ getVersion: function(dirname) { return ((loadPackage(dirname) || {}).version || null); },
+ loadPackage: loadPackage,
+ savePackage: savePackage,
+ updatePackage: updatePackage,
+}
diff --git a/admin/log.js b/admin/log.js
new file mode 100644
index 000000000..c740f26ff
--- /dev/null
+++ b/admin/log.js
@@ -0,0 +1,53 @@
+"use strict";
+
+function getColor(color) {
+ if (!color || color === "normal") { return "\x1b[0m"; }
+ return "\x1b[1m" + ({
+ blue: "\x1b[34m",
+ cyan: "\x1b[36m",
+ green: "\x1b[32m",
+ magenta: "\x1b[35m",
+ red: "\x1b[31m",
+ yellow: "\x1b[33m",
+ bold: ""
+ })[color];
+}
+
+// See: https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
+let disableColor = !(process.stdout.isTTY);
+function colorify(message, color) {
+ if (color) {
+ if (disableColor) { return message; }
+ return getColor(color) + message + getColor();
+ }
+
+ return message.replace(/<([^:]*):((?:[^<>\\]|\\.)*)>/g, (all, color, message) => {
+ message = message.replace("\\>", ">");
+ if (disableColor) { return message; }
+ return getColor(color) + message + getColor();
+ });
+}
+
+function colorifyStatus(status) {
+ switch (status) {
+ case "modified": return colorify("");
+ case "added": return colorify("");
+ case "deleted": return colorify("");
+ case "unmodified": return colorify("");
+ }
+ return status;
+}
+
+function log(message, color) {
+ if (color) {
+ console.log(colorify(message, color));
+ } else {
+ console.log(colorify(message));
+ }
+}
+
+module.exports = {
+ colorify: colorify,
+ colorifyStatus: colorifyStatus,
+ log: log
+}
diff --git a/admin/npm.js b/admin/npm.js
new file mode 100644
index 000000000..06824ddb4
--- /dev/null
+++ b/admin/npm.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const resolve = require("path").resolve;
+
+const npm = require("libnpm");
+const semver = require("semver");
+
+const local = require("./local");
+
+const keccak256 = require("../packages/keccak256").keccak256;
+const fetchJson = require("../packages/web").fetchJson;
+const prompt = require("../packages/cli/prompt");
+
+const colorify = require("./log").colorify;
+const git = require("./git");
+
+
+let cache = { };
+
+async function getPackage(name) {
+ if (cache[name]) { return cache[name]; }
+
+ return fetchJson("http:/" + "/registry.npmjs.org/" + name).then((result) => {
+ cache[name] = result;
+ return result;
+ }, (error) => {
+ if (error.statusCode === 404) {
+ return null;
+ }
+ throw error;
+ });
+}
+
+async function getVersion(name) {
+ return getPackage(name).then((result) => {
+ if (!result) { return null; }
+ let versions = Object.keys(result.versions);
+ versions.sort(semver.compare)
+ return versions.pop();
+ });
+}
+
+async function getPackageVersion(name, version) {
+ let info = await getPackage(name)
+ if (!info) { return null; }
+
+ if (version == null) {
+ let versions = Object.keys(info.versions);
+ versions.sort(semver.compare);
+ version = versions.pop();
+ }
+
+ return info.versions[version] || null;
+}
+
+async function getTarballHash(name, version) {
+ let info = await getPackageVersion(name, version);
+ return (info || {}).tarballHash;
+}
+
+async function _publish(info, tarball, options) {
+ try {
+ let result = await npm.publish(info, tarball, options);
+ return result;
+ } catch (error) {
+
+ // We need an OTP
+ if (error.code === "EOTP") {
+ try {
+ let otp = await prompt.getMessage(colorify("Enter OTP: ", "bold"));
+ options.otp = otp.replace(" ", "");
+ } catch (error) {
+
+ // CTRL-C
+ if (error.message === "cancelled") {
+ return false;
+ }
+
+ // Something unexpected...
+ throw error;
+ }
+
+ // Retry with the new OTP
+ return _publish(info, tarball, options);
+ }
+ throw error;
+ }
+}
+
+async function publish(dirname, options) {
+ let info = local.loadPackage(dirname);
+ info.gitHead = await git.getGitTag(resolve(__dirname, "../packages/", dirname));
+ if (info.gitHead == null) { throw new Error("no git tag found - " + dirname); }
+ let tarball = await local.createTarball(dirname);
+ return _publish(info, tarball, options);
+}
+
+module.exports = {
+ getPackage: getPackage,
+ getPackageVersion: getPackageVersion,
+ getTarballHash: getTarballHash,
+ getVersion: getVersion,
+ publish: publish,
+};
diff --git a/admin/utils.js b/admin/utils.js
new file mode 100644
index 000000000..9979cfcca
--- /dev/null
+++ b/admin/utils.js
@@ -0,0 +1,47 @@
+"use strict";
+
+const fs = require("fs");
+const _resolve = require("path").resolve;
+
+function repeat(chr, length) {
+ let result = chr;
+ while (result.length < length) { result += chr; }
+ return result;
+}
+
+function zpad(value) {
+ value = String(value);
+ while (value.length < 2) { value = "0" + value; }
+ return value;
+}
+
+function today() {
+ let now = new Date();
+ return [ now.getFullYear(), zpad(now.getMonth() + 1), zpad(now.getDate()) ].join("-");
+}
+
+function loadJson(filename) {
+ return JSON.parse(fs.readFileSync(filename).toString());
+}
+
+// @TODO: atomic write
+function saveJson(filename, json) {
+ fs.writeFileSync(filename, JSON.stringify(json, null, 2) + "\n");
+}
+
+function resolve(...args) {
+ args = args.slice();
+ args.unshift("..");
+ args.unshift(__dirname);
+ return _resolve.apply(null, args);
+}
+
+module.exports = {
+ resolve: resolve,
+
+ loadJson: loadJson,
+ saveJson: saveJson,
+
+ repeat: repeat,
+ today: today
+}
diff --git a/lerna.json b/lerna.json
new file mode 100644
index 000000000..a2bb50ba7
--- /dev/null
+++ b/lerna.json
@@ -0,0 +1,6 @@
+{
+ "packages": [
+ "packages/*"
+ ],
+ "version": "independent"
+}
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..c4b533ac7
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "root",
+ "private": true,
+ "scripts": {
+ "auto-build": "npm run build -- -w",
+ "bootstrap": "node ./admin/cmds/update-depgraph && lerna bootstrap --hoist",
+ "build": "tsc --build ./tsconfig.project.json",
+ "clean": "tsc --build --clean ./tsconfig.project.json",
+ "_dist": "npm run clean && npm run bootstrap && npm run build && lerna run dist",
+ "test": "npm run _dist && npm run test-check",
+ "test-check": "if [ \"$RUN_PHANTOMJS\" = \"1\" ]; then npm run-script test-phantomjs; else npm run-script test-node; fi",
+ "test-node": "cd packages/tests && mocha --no-colors --reporter ./tests/reporter ./tests/test-*.js",
+ "test-phantomjs": "cd packages/tests && npm run dist-phantomjs && phantomjs --web-security=false ../../node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js ./test.html ./tests/reporter.js",
+ "test-aion": "npm run dist && npm run test-aion-node",
+ "test-aion-node": "cd packages/aion-tests && mocha --no-colors --reporter ../tests/tests/reporter ./tests/test-*.js",
+ "update-versions": "npm run clean && npm run bootstrap && npm run build && node ./admin/cmds/update-versions",
+ "publish-all": "node ./admin/cmds/publish"
+ },
+ "devDependencies": {
+ "@types/assert": "^1.4.1",
+ "@types/mocha": "^5.2.0",
+ "aes-js": "3.0.0",
+ "browserify": "16.2.3",
+ "diff": "4.0.1",
+ "npm-packlist": "1.4.1",
+ "lerna": "^3.13.0",
+ "libnpm": "2.0.1",
+ "mocha": "^5.2.0",
+ "mocha-phantomjs-core": "2.1.2",
+ "scrypt-js": "2.0.4",
+ "semver": "^5.6.0",
+ "tar": "4.4.8",
+ "typescript": "^3.3.3"
+ }
+}
diff --git a/packages/abi/.npmignore b/packages/abi/.npmignore
new file mode 100644
index 000000000..684f719ca
--- /dev/null
+++ b/packages/abi/.npmignore
@@ -0,0 +1,2 @@
+tsconfig.json
+src.ts/
diff --git a/packages/abi/LICENSE.md b/packages/abi/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/packages/abi/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/abi/README.md b/packages/abi/README.md
new file mode 100644
index 000000000..c3c158183
--- /dev/null
+++ b/packages/abi/README.md
@@ -0,0 +1,17 @@
+Ethereum ABI Coder
+==================
+
+**EXPERIMENTAL**
+
+Please see the [ethers](https://github.com/ethers-io/ethers.js) repository
+for more informations.
+
+API
+---
+
+`@TODO`
+
+License
+-------
+
+MIT License
diff --git a/packages/abi/package.json b/packages/abi/package.json
new file mode 100644
index 000000000..9a874dfcc
--- /dev/null
+++ b/packages/abi/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@ethersproject/abi",
+ "version": "5.0.0-beta.128",
+ "description": "Error utility functions for ethers.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@ethersproject/address": ">5.0.0-beta.0",
+ "@ethersproject/bignumber": ">5.0.0-beta.0",
+ "@ethersproject/bytes": ">5.0.0-beta.0",
+ "@ethersproject/constants": ">5.0.0-beta.0",
+ "@ethersproject/errors": ">5.0.0-beta.0",
+ "@ethersproject/hash": ">5.0.0-beta.0",
+ "@ethersproject/keccak256": ">5.0.0-beta.0",
+ "@ethersproject/properties": ">5.0.0-beta.0",
+ "@ethersproject/strings": ">5.0.0-beta.0"
+ },
+ "keywords": [
+ "Ethereum",
+ "ethers"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0xd6298eed7cb50f08154591d43a324a68220ce97b92ab6f44d2015437688def09"
+}
diff --git a/packages/abi/src.ts/_version.ts b/packages/abi/src.ts/_version.ts
new file mode 100644
index 000000000..d25e399bf
--- /dev/null
+++ b/packages/abi/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.128";
diff --git a/packages/abi/src.ts/abi-coder.ts b/packages/abi/src.ts/abi-coder.ts
new file mode 100644
index 000000000..24d9d99a9
--- /dev/null
+++ b/packages/abi/src.ts/abi-coder.ts
@@ -0,0 +1,124 @@
+"use strict";
+
+// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
+
+import { arrayify, BytesLike } from "@ethersproject/bytes";
+import * as errors from "@ethersproject/errors";
+import { defineReadOnly } from "@ethersproject/properties";
+
+import { Coder, Reader, Writer } from "./coders/abstract-coder";
+import { AddressCoder } from "./coders/address";
+import { ArrayCoder } from "./coders/array";
+import { BooleanCoder } from "./coders/boolean";
+import { BytesCoder } from "./coders/bytes";
+import { FixedBytesCoder } from "./coders/fixed-bytes";
+import { NullCoder } from "./coders/null";
+import { NumberCoder } from "./coders/number";
+import { StringCoder } from "./coders/string";
+import { TupleCoder } from "./coders/tuple";
+
+import { ParamType } from "./fragments";
+
+
+const paramTypeBytes = new RegExp(/^bytes([0-9]*)$/);
+const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);
+
+
+export type CoerceFunc = (type: string, value: any) => any;
+
+export class AbiCoder {
+ readonly coerceFunc: CoerceFunc;
+
+ constructor(coerceFunc?: CoerceFunc) {
+ errors.checkNew(new.target, AbiCoder);
+ defineReadOnly(this, "coerceFunc", coerceFunc || null);
+ }
+
+ _getCoder(param: ParamType): Coder {
+
+ switch (param.baseType) {
+ case "address":
+ return new AddressCoder(param.name);
+ case "bool":
+ return new BooleanCoder(param.name);
+ case "string":
+ return new StringCoder(param.name);
+ case "bytes":
+ return new BytesCoder(param.name);
+ case "array":
+ return new ArrayCoder(this._getCoder(param.arrayChildren), param.arrayLength, param.name);
+ case "tuple":
+ return new TupleCoder((param.components || []).map((component) => {
+ return this._getCoder(component);
+ }), param.name);
+ case "":
+ return new NullCoder(param.name);
+ }
+
+ // u?int[0-9]*
+ let match = param.type.match(paramTypeNumber);
+ if (match) {
+ let size = parseInt(match[2] || "256");
+ if (size === 0 || size > 256 || (size % 8) !== 0) {
+ errors.throwError("invalid " + match[1] + " bit length", errors.INVALID_ARGUMENT, {
+ arg: "param",
+ value: param
+ });
+ }
+ return new NumberCoder(size / 8, (match[1] === "int"), param.name);
+ }
+
+ // bytes[0-9]+
+ match = param.type.match(paramTypeBytes);
+ if (match) {
+ let size = parseInt(match[1]);
+ if (size === 0 || size > 32) {
+ errors.throwError("invalid bytes length", errors.INVALID_ARGUMENT, {
+ arg: "param",
+ value: param
+ });
+ }
+ return new FixedBytesCoder(size, param.name);
+ }
+
+ return errors.throwError("invalid type", errors.INVALID_ARGUMENT, {
+ arg: "type",
+ value: param.type
+ });
+ }
+
+ _getWordSize(): number { return 32; }
+
+ _getReader(data: Uint8Array): Reader {
+ return new Reader(data, this._getWordSize(), this.coerceFunc);
+ }
+
+ _getWriter(): Writer {
+ return new Writer(this._getWordSize());
+ }
+
+ encode(types: Array, values: Array): string {
+ if (types.length !== values.length) {
+ errors.throwError("types/values length mismatch", errors.INVALID_ARGUMENT, {
+ count: { types: types.length, values: values.length },
+ value: { types: types, values: values }
+ });
+ }
+
+ let coders = types.map((type) => this._getCoder(ParamType.from(type)));
+ let coder = (new TupleCoder(coders, "_"));
+
+ let writer = this._getWriter();
+ coder.encode(writer, values);
+ return writer.data;
+ }
+
+ decode(types: Array, data: BytesLike): any {
+ let coders: Array = types.map((type) => this._getCoder(ParamType.from(type)));
+ let coder = new TupleCoder(coders, "_");
+ return coder.decode(this._getReader(arrayify(data)));
+ }
+}
+
+export const defaultAbiCoder: AbiCoder = new AbiCoder();
+
diff --git a/packages/abi/src.ts/coders/abstract-coder.ts b/packages/abi/src.ts/coders/abstract-coder.ts
new file mode 100644
index 000000000..00e5a332c
--- /dev/null
+++ b/packages/abi/src.ts/coders/abstract-coder.ts
@@ -0,0 +1,161 @@
+"use trict";
+
+import { arrayify, BytesLike, concat, hexlify } from "@ethersproject/bytes";
+import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
+import * as errors from "@ethersproject/errors";
+import { defineReadOnly } from "@ethersproject/properties";
+
+export type CoerceFunc = (type: string, value: any) => any;
+
+export abstract class Coder {
+
+ // The coder name:
+ // - address, uint256, tuple, array, etc.
+ readonly name: string;
+
+ // The fully expanded type, including composite types:
+ // - address, uint256, tuple(address,bytes), uint256[3][4][], etc.
+ readonly type: string;
+
+ // The localName bound in the signature, in this example it is "baz":
+ // - tuple(address foo, uint bar) baz
+ readonly localName: string;
+
+ // Whether this type is dynamic:
+ // - Dynamic: bytes, string, address[], tuple(boolean[]), etc.
+ // - Not Dynamic: address, uint256, boolean[3], tuple(address, uint8)
+ readonly dynamic: boolean;
+
+ constructor(name: string, type: string, localName: string, dynamic: boolean) {
+ this.name = name;
+ this.type = type;
+ this.localName = localName;
+ this.dynamic = dynamic;
+ }
+
+ _throwError(message: string, value: any): void {
+ errors.throwError(message, errors.INVALID_ARGUMENT, {
+ argument: this.localName,
+ coder: this,
+ value: value
+ });
+ }
+
+ abstract encode(writer: Writer, value: any): number;
+ abstract decode(reader: Reader): any;
+}
+
+export class Writer {
+ readonly wordSize: number;
+
+ _data: Uint8Array;
+ _padding: Uint8Array;
+
+ constructor(wordSize?: number) {
+ defineReadOnly(this, "wordSize", wordSize || 32);
+ this._data = arrayify([ ]);
+ this._padding = new Uint8Array(wordSize);
+ }
+
+ get data(): string { return hexlify(this._data); }
+ get length(): number { return this._data.length; }
+
+ _writeData(data: Uint8Array): number {
+ this._data = concat([ this._data, data ]);
+ return data.length;
+ }
+
+ // Arrayish items; padded on the right to wordSize
+ writeBytes(value: BytesLike): number {
+ let bytes = arrayify(value);
+ if (bytes.length % this.wordSize) {
+ bytes = concat([ bytes, this._padding.slice(bytes.length % this.wordSize) ])
+ }
+ return this._writeData(bytes);
+ }
+
+ _getValue(value: BigNumberish): Uint8Array {
+ let bytes = arrayify(BigNumber.from(value));
+ if (bytes.length > this.wordSize) {
+ errors.throwError("value out-of-bounds", errors.BUFFER_OVERRUN, {
+ length: this.wordSize,
+ offset: bytes.length
+ });
+ }
+ if (bytes.length % this.wordSize) {
+ bytes = concat([ this._padding.slice(bytes.length % this.wordSize), bytes ]);
+ }
+ return bytes;
+ }
+
+ // BigNumberish items; padded on the left to wordSize
+ writeValue(value: BigNumberish): number {
+ return this._writeData(this._getValue(value));
+ }
+
+ writeUpdatableValue(): (value: BigNumberish) => void {
+ let offset = this.length;
+ this.writeValue(0);
+ return (value: BigNumberish) => {
+ this._data.set(this._getValue(value), offset);
+ };
+ }
+}
+
+export class Reader {
+ readonly wordSize: number;
+
+ readonly _data: Uint8Array;
+ readonly _coerceFunc: CoerceFunc;
+
+ _offset: number;
+
+ constructor(data: BytesLike, wordSize?: number, coerceFunc?: CoerceFunc) {
+ defineReadOnly(this, "_data", arrayify(data));
+ defineReadOnly(this, "wordSize", wordSize || 32);
+ defineReadOnly(this, "_coerceFunc", coerceFunc);
+
+ this._offset = 0;
+ }
+
+ get data(): string { return hexlify(this._data); }
+ get consumed(): number { return this._offset; }
+
+ // The default Coerce function
+ static coerce(name: string, value: any): any {
+ let match = name.match("^u?int([0-9]+)$");
+ if (match && parseInt(match[1]) <= 48) { value = value.toNumber(); }
+ return value;
+ }
+
+ coerce(name: string, value: any): any {
+ if (this._coerceFunc) { return this._coerceFunc(name, value); }
+ return Reader.coerce(name, value);
+ }
+
+ _peekBytes(offset: number, length: number): Uint8Array {
+ let alignedLength = Math.ceil(length / this.wordSize) * this.wordSize;
+ if (this._offset + alignedLength > this._data.length) {
+ errors.throwError("data out-of-bounds", errors.BUFFER_OVERRUN, {
+ length: this._data.length,
+ offset: this._offset + alignedLength
+ });
+ }
+ return this._data.slice(this._offset, this._offset + alignedLength)
+ }
+
+ subReader(offset: number): Reader {
+ return new Reader(this._data.slice(this._offset + offset), this.wordSize, this._coerceFunc);
+ }
+
+ readBytes(length: number): Uint8Array {
+ let bytes = this._peekBytes(0, length);
+ this._offset += bytes.length;
+ // @TODO: Make sure the length..end bytes are all 0?
+ return bytes.slice(0, length);
+ }
+
+ readValue(): BigNumber {
+ return BigNumber.from(this.readBytes(this.wordSize));
+ }
+}
diff --git a/packages/abi/src.ts/coders/address.ts b/packages/abi/src.ts/coders/address.ts
new file mode 100644
index 000000000..efaf93a54
--- /dev/null
+++ b/packages/abi/src.ts/coders/address.ts
@@ -0,0 +1,27 @@
+"use strict";
+
+import { getAddress } from "@ethersproject/address";
+import { hexZeroPad } from "@ethersproject/bytes";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+
+export class AddressCoder extends Coder {
+
+ constructor(localName: string) {
+ super("address", "address", localName, false);
+ }
+
+ encode(writer: Writer, value: string): number {
+ try {
+ getAddress(value);
+ } catch (error) {
+ this._throwError(error.message, value);
+ }
+ return writer.writeValue(value);
+ }
+
+ decode(reader: Reader): any {
+ return getAddress(hexZeroPad(reader.readValue().toHexString(), 20));
+ }
+}
+
diff --git a/packages/abi/src.ts/coders/anonymous.ts b/packages/abi/src.ts/coders/anonymous.ts
new file mode 100644
index 000000000..59c99f6a9
--- /dev/null
+++ b/packages/abi/src.ts/coders/anonymous.ts
@@ -0,0 +1,21 @@
+"use strict";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+
+// Clones the functionality of an existing Coder, but without a localName
+export class AnonymousCoder extends Coder {
+ private coder: Coder;
+
+ constructor(coder: Coder) {
+ super(coder.name, coder.type, undefined, coder.dynamic);
+ this.coder = coder;
+ }
+
+ encode(writer: Writer, value: any): number {
+ return this.coder.encode(writer, value);
+ }
+
+ decode(reader: Reader): any {
+ return this.coder.decode(reader);
+ }
+}
diff --git a/packages/abi/src.ts/coders/array.ts b/packages/abi/src.ts/coders/array.ts
new file mode 100644
index 000000000..6b066a222
--- /dev/null
+++ b/packages/abi/src.ts/coders/array.ts
@@ -0,0 +1,159 @@
+"use strict";
+
+import * as errors from "@ethersproject/errors";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+import { AnonymousCoder } from "./anonymous";
+
+export function pack(writer: Writer, coders: Array, values: Array): number {
+
+ if (Array.isArray(values)) {
+ // do nothing
+
+ } else if (values && typeof(values) === "object") {
+ let arrayValues: Array = [];
+ coders.forEach(function(coder) {
+ arrayValues.push((values)[coder.localName]);
+ });
+ values = arrayValues;
+
+ } else {
+ errors.throwError("invalid tuple value", errors.INVALID_ARGUMENT, {
+ coderType: "tuple",
+ value: values
+ });
+ }
+
+ if (coders.length !== values.length) {
+ errors.throwError("types/value length mismatch", errors.INVALID_ARGUMENT, {
+ coderType: "tuple",
+ value: values
+ });
+ }
+
+ let staticWriter = new Writer(writer.wordSize);
+ let dynamicWriter = new Writer(writer.wordSize);
+
+ let updateFuncs: Array<(baseOffset: number) => void> = [];
+ coders.forEach((coder, index) => {
+ let value = values[index];
+
+ if (coder.dynamic) {
+ // Get current dynamic offset (for the future pointer)
+ let dynamicOffset = dynamicWriter.length;
+
+ // Encode the dynamic value into the dynamicWriter
+ coder.encode(dynamicWriter, value);
+
+ // Prepare to populate the correct offset once we are done
+ let updateFunc = staticWriter.writeUpdatableValue();
+ updateFuncs.push((baseOffset: number) => {
+ updateFunc(baseOffset + dynamicOffset);
+ });
+
+ } else {
+ coder.encode(staticWriter, value);
+ }
+ });
+
+ // Backfill all the dynamic offsets, now that we know the static length
+ updateFuncs.forEach((func) => { func(staticWriter.length); });
+
+ let length = writer.writeBytes(staticWriter.data);
+ length += writer.writeBytes(dynamicWriter.data);
+ return length;
+}
+
+export function unpack(reader: Reader, coders: Array): Array {
+ let values: any = [];
+
+ // A reader anchored to this base
+ let baseReader = reader.subReader(0);
+
+ // The amount of dynamic data read; to consume later to synchronize
+ let dynamicLength = 0;
+
+ coders.forEach((coder) => {
+ let value: any = null;
+
+ if (coder.dynamic) {
+ let offset = reader.readValue();
+ let offsetReader = baseReader.subReader(offset.toNumber());
+ value = coder.decode(offsetReader);
+ dynamicLength += offsetReader.consumed;
+ } else {
+ value = coder.decode(reader);
+ }
+
+ if (value != undefined) {
+ values.push(value);
+ }
+ });
+
+// @TODO: get rid of this an see if it still works?
+ // Consume the dynamic components in the main reader
+ reader.readBytes(dynamicLength);
+
+ // Add any named parameters (i.e. tuples)
+ coders.forEach((coder: Coder, index: number) => {
+ let name: string = coder.localName;
+ if (!name) { return; }
+
+ if (name === "length") { name = "_length"; }
+
+ if (values[name] != null) { return; }
+
+ values[name] = values[index];
+ });
+
+ return values;
+}
+
+
+export class ArrayCoder extends Coder {
+ readonly coder: Coder;
+ readonly length: number;
+
+ constructor(coder: Coder, length: number, localName: string) {
+ const type = (coder.type + "[" + (length >= 0 ? length: "") + "]");
+ const dynamic = (length === -1 || coder.dynamic);
+ super("array", type, localName, dynamic);
+
+ this.coder = coder;
+ this.length = length;
+ }
+
+ encode(writer: Writer, value: Array): number {
+ if (!Array.isArray(value)) {
+ this._throwError("expected array value", value);
+ }
+
+ let count = this.length;
+
+ //let result = new Uint8Array(0);
+ if (count === -1) {
+ count = value.length;
+ writer.writeValue(value.length);
+ }
+
+ errors.checkArgumentCount(count, value.length, " in coder array" + (this.localName? (" "+ this.localName): ""));
+
+ let coders = [];
+ for (let i = 0; i < value.length; i++) { coders.push(this.coder); }
+
+ return pack(writer, coders, value);
+ }
+
+ decode(reader: Reader): any {
+ let count = this.length;
+ if (count === -1) {
+ count = reader.readValue().toNumber();
+ }
+
+ let coders = [];
+ for (let i = 0; i < count; i++) { coders.push(new AnonymousCoder(this.coder)); }
+
+ return reader.coerce(this.name, unpack(reader, coders));
+ }
+}
+
diff --git a/packages/abi/src.ts/coders/boolean.ts b/packages/abi/src.ts/coders/boolean.ts
new file mode 100644
index 000000000..ebd7384ab
--- /dev/null
+++ b/packages/abi/src.ts/coders/boolean.ts
@@ -0,0 +1,19 @@
+"use strict";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+
+export class BooleanCoder extends Coder {
+
+ constructor(localName: string) {
+ super("bool", "bool", localName, false);
+ }
+
+ encode(writer: Writer, value: boolean): number {
+ return writer.writeValue(value ? 1: 0);
+ }
+
+ decode(reader: Reader): any {
+ return reader.coerce(this.type, !reader.readValue().isZero());
+ }
+}
+
diff --git a/packages/abi/src.ts/coders/bytes.ts b/packages/abi/src.ts/coders/bytes.ts
new file mode 100644
index 000000000..a2d8ce43c
--- /dev/null
+++ b/packages/abi/src.ts/coders/bytes.ts
@@ -0,0 +1,34 @@
+"use strict";
+
+import { arrayify, hexlify } from "@ethersproject/bytes";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+
+export class DynamicBytesCoder extends Coder {
+ constructor(type: string, localName: string) {
+ super(type, type, localName, true);
+ }
+
+ encode(writer: Writer, value: any): number {
+ value = arrayify(value);
+ let length = writer.writeValue(value.length);
+ length += writer.writeBytes(value);
+ return length;
+ }
+
+ decode(reader: Reader): any {
+ return reader.readBytes(reader.readValue().toNumber());
+ }
+}
+
+export class BytesCoder extends DynamicBytesCoder {
+ constructor(localName: string) {
+ super("bytes", localName);
+ }
+
+ decode(reader: Reader): any {
+ return reader.coerce(this.name, hexlify(super.decode(reader)));
+ }
+}
+
+
diff --git a/packages/abi/src.ts/coders/fixed-bytes.ts b/packages/abi/src.ts/coders/fixed-bytes.ts
new file mode 100644
index 000000000..45cadeedf
--- /dev/null
+++ b/packages/abi/src.ts/coders/fixed-bytes.ts
@@ -0,0 +1,26 @@
+"use strict";
+
+import { arrayify, BytesLike, hexlify } from "@ethersproject/bytes";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+
+// @TODO: Merge this with bytes
+export class FixedBytesCoder extends Coder {
+ readonly size: number;
+
+ constructor(size: number, localName: string) {
+ let name = "bytes" + String(size);
+ super(name, name, localName, false);
+ this.size = size;
+ }
+
+ encode(writer: Writer, value: BytesLike): number {
+ let data = arrayify(value);
+ if (data.length !== this.size) { this._throwError("incorrect data length", value); }
+ return writer.writeBytes(data);
+ }
+
+ decode(reader: Reader): any {
+ return reader.coerce(this.name, hexlify(reader.readBytes(this.size)));
+ }
+}
diff --git a/packages/abi/src.ts/coders/null.ts b/packages/abi/src.ts/coders/null.ts
new file mode 100644
index 000000000..e0ad58870
--- /dev/null
+++ b/packages/abi/src.ts/coders/null.ts
@@ -0,0 +1,20 @@
+"use strict";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+
+export class NullCoder extends Coder {
+
+ constructor(localName: string) {
+ super("null", "", localName, false);
+ }
+
+ encode(writer: Writer, value: any): number {
+ if (value != null) { this._throwError("not null", value); }
+ return writer.writeBytes([ ]);
+ }
+
+ decode(reader: Reader): any {
+ reader.readBytes(0);
+ return reader.coerce(this.name, null);
+ }
+}
diff --git a/packages/abi/src.ts/coders/number.ts b/packages/abi/src.ts/coders/number.ts
new file mode 100644
index 000000000..19f237d19
--- /dev/null
+++ b/packages/abi/src.ts/coders/number.ts
@@ -0,0 +1,53 @@
+"use strict";
+
+import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
+import { MaxUint256, NegativeOne, One, Zero } from "@ethersproject/constants";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+
+export class NumberCoder extends Coder {
+ readonly size: number;
+ readonly signed: boolean;
+
+ constructor(size: number, signed: boolean, localName: string) {
+ const name = ((signed ? "int": "uint") + (size * 8));
+ super(name, name, localName, false);
+
+ this.size = size;
+ this.signed = signed;
+ }
+
+ encode(writer: Writer, value: BigNumberish): number {
+ let v = BigNumber.from(value);
+
+ // Check bounds are safe for encoding
+ let maxUintValue = MaxUint256.maskn(writer.wordSize * 8);
+ if (this.signed) {
+ let bounds = maxUintValue.maskn(this.size * 8 - 1);
+ if (v.gt(bounds) || v.lt(bounds.add(One).mul(NegativeOne))) {
+ this._throwError("value out-of-bounds", value);
+ }
+ } else if (v.lt(Zero) || v.gt(maxUintValue.maskn(this.size * 8))) {
+ this._throwError("value out-of-bounds", value);
+ }
+
+ v = v.toTwos(this.size * 8).maskn(this.size * 8);
+
+ if (this.signed) {
+ v = v.fromTwos(this.size * 8).toTwos(8 * writer.wordSize);
+ }
+
+ return writer.writeValue(v);
+ }
+
+ decode(reader: Reader): any {
+ let value = reader.readValue().maskn(this.size * 8);
+
+ if (this.signed) {
+ value = value.fromTwos(this.size * 8);
+ }
+
+ return reader.coerce(this.name, value);
+ }
+}
+
diff --git a/packages/abi/src.ts/coders/string.ts b/packages/abi/src.ts/coders/string.ts
new file mode 100644
index 000000000..14f13b1a6
--- /dev/null
+++ b/packages/abi/src.ts/coders/string.ts
@@ -0,0 +1,21 @@
+"use strict";
+
+import { toUtf8Bytes, toUtf8String } from "@ethersproject/strings";
+
+import { Reader, Writer } from "./abstract-coder";
+import { DynamicBytesCoder } from "./bytes";
+
+export class StringCoder extends DynamicBytesCoder {
+
+ constructor(localName: string) {
+ super("string", localName);
+ }
+
+ encode(writer: Writer, value: any): number {
+ return super.encode(writer, toUtf8Bytes(value));
+ }
+
+ decode(reader: Reader): any {
+ return toUtf8String(super.decode(reader));
+ }
+}
diff --git a/packages/abi/src.ts/coders/tuple.ts b/packages/abi/src.ts/coders/tuple.ts
new file mode 100644
index 000000000..257fec7f7
--- /dev/null
+++ b/packages/abi/src.ts/coders/tuple.ts
@@ -0,0 +1,30 @@
+"use strict";
+
+import { Coder, Reader, Writer } from "./abstract-coder";
+import { pack, unpack } from "./array";
+
+export class TupleCoder extends Coder {
+ readonly coders: Array;
+
+ constructor(coders: Array, localName: string) {
+ let dynamic = false;
+ let types: Array = [];
+ coders.forEach((coder) => {
+ if (coder.dynamic) { dynamic = true; }
+ types.push(coder.type);
+ });
+ let type = ("tuple(" + types.join(",") + ")");
+
+ super("tuple", type, localName, dynamic);
+ this.coders = coders;
+ }
+
+ encode(writer: Writer, value: Array): number {
+ return pack(writer, this.coders, value);
+ }
+
+ decode(reader: Reader): any {
+ return reader.coerce(this.name, unpack(reader, this.coders));
+ }
+}
+
diff --git a/packages/abi/src.ts/fragments.ts b/packages/abi/src.ts/fragments.ts
new file mode 100644
index 000000000..4602e363d
--- /dev/null
+++ b/packages/abi/src.ts/fragments.ts
@@ -0,0 +1,676 @@
+"use strict";
+
+import { BigNumber } from "@ethersproject/bignumber";
+import * as errors from "@ethersproject/errors";
+import { defineReadOnly, isNamedInstance } from "@ethersproject/properties";
+
+
+export interface JsonFragmentType {
+ name?: string;
+ indexed?: boolean;
+ type?: string;
+ components?: Array;
+}
+
+export interface JsonFragment {
+ name?: string;
+ type?: string;
+
+ anonymous?: boolean;
+
+ payable?: boolean;
+ constant?: boolean;
+ stateMutability?: string;
+
+ inputs?: Array;
+ outputs?: Array;
+
+ gas?: string;
+};
+
+
+const _constructorGuard = { };
+
+// AST Node parser state
+type ParseState = {
+ allowArray?: boolean,
+ allowName?: boolean,
+ allowParams?: boolean,
+ allowType?: boolean,
+ readArray?: boolean,
+};
+
+// AST Node
+type ParseNode = {
+ parent?: any,
+ type?: string,
+ name?: string,
+ state?: ParseState,
+ indexed?: boolean,
+ components?: Array
+};
+
+
+// @TODO: Make sure that children of an indexed tuple are marked with a null indexed
+function parseParamType(param: string, allowIndexed: boolean): ParseNode {
+
+ let originalParam = param;
+ function throwError(i: number) {
+ throw new Error("unexpected character '" + originalParam[i] + "' at position " + i + " in '" + originalParam + "'");
+ }
+ param = param.replace(/\s/g, " ");
+
+ function newNode(parent: ParseNode): ParseNode {
+ let node: ParseNode = { type: "", name: "", parent: parent, state: { allowType: true } };
+ if (allowIndexed) { node.indexed = false; }
+ return node
+ }
+
+ let parent: ParseNode = { type: "", name: "", state: { allowType: true } };
+ let node = parent;
+
+ for (let i = 0; i < param.length; i++) {
+ let c = param[i];
+ switch (c) {
+ case "(":
+ if (!node.state.allowParams) { throwError(i); }
+ node.state.allowType = false;
+ node.type = verifyType(node.type);
+ node.components = [ newNode(node) ];
+ node = node.components[0];
+ break;
+
+ case ")":
+ delete node.state;
+ if (allowIndexed) {
+ if (node.name === "indexed") {
+ node.indexed = true;
+ node.name = "";
+ }
+ }
+ node.type = verifyType(node.type);
+
+ let child = node;
+ node = node.parent;
+ if (!node) { throwError(i); }
+ delete child.parent;
+ node.state.allowParams = false;
+ node.state.allowName = true;
+ node.state.allowArray = true;
+ break;
+
+ case ",":
+ delete node.state;
+ if (allowIndexed) {
+ if (node.name === "indexed") {
+ node.indexed = true;
+ node.name = "";
+ }
+ }
+ node.type = verifyType(node.type);
+
+ let sibling: ParseNode = newNode(node.parent);
+ //{ type: "", name: "", parent: node.parent, state: { allowType: true } };
+ node.parent.components.push(sibling);
+ delete node.parent;
+ node = sibling;
+ break;
+
+ // Hit a space...
+ case " ":
+
+ // If reading type, the type is done and may read a param or name
+ if (node.state.allowType) {
+ if (node.type !== "") {
+ node.type = verifyType(node.type);
+ delete node.state.allowType;
+ node.state.allowName = true;
+ node.state.allowParams = true;
+ }
+ }
+
+ // If reading name, the name is done
+ if (node.state.allowName) {
+ if (node.name !== "") {
+ if (allowIndexed) {
+ if (node.name === "indexed") {
+ if (node.indexed) { throwError(i); }
+ node.indexed = true;
+ node.name = "";
+ }
+ } else {
+ node.state.allowName = false;
+ }
+ }
+ }
+
+ break;
+
+ case "[":
+ if (!node.state.allowArray) { throwError(i); }
+
+ node.type += c;
+
+ node.state.allowArray = false;
+ node.state.allowName = false;
+ node.state.readArray = true;
+ break;
+
+ case "]":
+ if (!node.state.readArray) { throwError(i); }
+
+ node.type += c;
+
+ node.state.readArray = false;
+ node.state.allowArray = true;
+ node.state.allowName = true;
+ break;
+
+ default:
+ if (node.state.allowType) {
+ node.type += c;
+ node.state.allowParams = true;
+ node.state.allowArray = true;
+ } else if (node.state.allowName) {
+ node.name += c;
+ delete node.state.allowArray;
+ } else if (node.state.readArray) {
+ node.type += c;
+ } else {
+ throwError(i);
+ }
+ }
+ }
+
+ if (node.parent) { throw new Error("unexpected eof"); }
+
+ delete parent.state;
+
+ if (allowIndexed) {
+ if (node.name === "indexed") {
+ if (node.indexed) { throwError(originalParam.length - 7); }
+ node.indexed = true;
+ node.name = "";
+ }
+ }
+ parent.type = verifyType(parent.type);
+
+ return parent;
+}
+
+function populate(object: any, params: any) {
+ for (let key in params) { defineReadOnly(object, key, params[key]); }
+}
+
+const paramTypeArray = new RegExp(/^(.*)\[([0-9]*)\]$/);
+
+export class ParamType {
+
+ // The local name of the parameter (of null if unbound)
+ readonly name: string;
+
+ // The fully qualified type (e.g. "address", "tuple(address)", "uint256[3][]"
+ readonly type: string;
+
+ // The base type (e.g. "address", "tuple", "array")
+ readonly baseType: string;
+
+ // Indexable Paramters ONLY (otherwise null)
+ readonly indexed: boolean;
+
+ // Tuples ONLY: (otherwise null)
+ // - sub-components
+ readonly components: Array;
+
+ // Arrays ONLY: (otherwise null)
+ // - length of the array (-1 for dynamic length)
+ // - child type
+ readonly arrayLength: number;
+ readonly arrayChildren: ParamType;
+
+ constructor(constructorGuard: any, params: any) {
+ if (constructorGuard !== _constructorGuard) { throw new Error("use fromString"); }
+ populate(this, params);
+
+ let match = this.type.match(paramTypeArray);
+ if (match) {
+ populate(this, {
+ arrayLength: parseInt(match[2] || "-1"),
+ arrayChildren: ParamType.fromObject({
+ type: match[1],
+ components: this.components
+ }),
+ baseType: "array"
+ });
+ } else {
+ populate(this, {
+ arrayLength: null,
+ arrayChildren: null,
+ baseType: ((this.components != null) ? "tuple": this.type)
+ });
+ }
+ }
+
+ // Format the parameter fragment
+ // - non-expanded: "(uint256,address)"
+ // - expanded: "tuple(uint256 foo, addres bar) indexed baz"
+ format(expanded?: boolean): string {
+ let result = "";
+
+ // Array
+ if (this.baseType === "array") {
+ result += this.arrayChildren.format(expanded);
+ result += "[" + (this.arrayLength < 0 ? "": String(this.arrayLength)) + "]";
+ } else {
+ if (this.baseType === "tuple") {
+ if (expanded) {
+ result += this.type;
+ }
+ result += "(" + this.components.map((c) => c.format(expanded)).join(expanded ? ", ": ",") + ")";
+ } else {
+ result += this.type;
+ }
+ }
+
+ if (expanded) {
+ if (this.indexed === true) { result += " indexed"; }
+ if (this.name) { result += " " + this.name; }
+ }
+
+ return result;
+ }
+
+ static from(value: string | JsonFragmentType | ParamType, allowIndexed?: boolean): ParamType {
+ if (typeof(value) === "string") {
+ return ParamType.fromString(value, allowIndexed);
+ }
+ return ParamType.fromObject(value);
+ }
+
+ static fromObject(value: JsonFragmentType | ParamType): ParamType {
+ if (isNamedInstance(ParamType, value)) { return value; }
+
+ return new ParamType(_constructorGuard, {
+ name: (value.name || null),
+ type: verifyType(value.type),
+ indexed: ((value.indexed == null) ? null: !!value.indexed),
+ components: (value.components ? value.components.map(ParamType.fromObject): null)
+ });
+ }
+
+ static fromString(value: string, allowIndexed?: boolean): ParamType {
+ function ParamTypify(node: ParseNode): ParamType {
+ return ParamType.fromObject({
+ name: node.name,
+ type: node.type,
+ indexed: node.indexed,
+ components: node.components
+ });
+ }
+
+ return ParamTypify(parseParamType(value, !!allowIndexed));
+ }
+};
+
+function parseParams(value: string, allowIndex: boolean): Array {
+ return splitNesting(value).map((param) => ParamType.fromString(param, allowIndex));
+}
+
+export abstract class Fragment {
+
+ readonly type: string;
+ readonly name: string;
+ readonly inputs: Array;
+
+ constructor(constructorGuard: any, params: any) {
+ if (constructorGuard !== _constructorGuard) { throw new Error("use a static from method"); }
+ populate(this, params);
+ }
+
+ // @TOOD: move logic to sub-classes; make this abstract
+ format(expanded?: boolean): string {
+ let result = "";
+
+ if (this.type === "constructor") {
+ result += "constructor";
+ } else {
+ if (expanded) {
+ result += this.type + " ";
+ }
+ result += this.name;
+ }
+
+ result += "(" + this.inputs.map((i) => i.format(expanded)).join(expanded ? ", ": ",") + ") ";
+
+ // @TODO: Handle returns, modifiers, etc.
+ if (expanded) {
+ result += "public ";
+ if ((this).mutabilityState) {
+ result += (this).mutabilityState + " ";
+ } else if ((this).constant) {
+ result += "view ";
+ }
+
+ if ((this).outputs && (this).outputs.length) {
+ result += "(" + (this).outputs.map((i: ParamType) => i.format(expanded)).join(", ") + ") ";
+ }
+ }
+
+ return result.trim();
+ }
+
+ static from(value: Fragment | JsonFragment | string): Fragment {
+ if (typeof(value) === "string") {
+ return Fragment.fromString(value);
+ }
+ return Fragment.fromObject(value);
+ }
+
+ static fromObject(value: Fragment | JsonFragment): Fragment {
+ if (isNamedInstance(Fragment, value)) { return value; }
+
+ if (value.type === "function") {
+ return FunctionFragment.fromObject(value);
+ } else if (value.type === "event") {
+ return EventFragment.fromObject(value);
+ } else if (value.type === "constructor") {
+ return ConstructorFragment.fromObject(value);
+ } else if (value.type === "fallback") {
+ // @TODO:
+ return null;
+ }
+
+ return errors.throwError("invalid fragment object", errors.INVALID_ARGUMENT, {
+ argument: "value",
+ value: value
+ });
+ }
+
+ static fromString(value: string): Fragment {
+ // Make sure the "returns" is surrounded by a space and all whitespace is exactly one space
+ value = value.replace(/\s/g, " ");
+ value = value.replace(/\(/g, " (").replace(/\)/g, ") ").replace(/\s+/g, " ");
+ value = value.trim();
+
+ if (value.split(" ")[0] === "event") {
+ return EventFragment.fromString(value.substring(5).trim());
+ } else if (value.split(" ")[0] === "function") {
+ return FunctionFragment.fromString(value.substring(8).trim());
+ } else if (value.split("(")[0].trim() === "constructor") {
+ return ConstructorFragment.fromString(value.trim());
+ }
+
+ throw new Error("unknown fragment");
+ }
+}
+
+export class EventFragment extends Fragment {
+ readonly anonymous: boolean;
+
+ static from(value: EventFragment | JsonFragment | string): EventFragment {
+ if (typeof(value) === "string") {
+ return EventFragment.fromString(value);
+ }
+ return EventFragment.fromObject(value);
+ }
+
+ static fromObject(value: JsonFragment | EventFragment): EventFragment {
+ if (isNamedInstance(EventFragment, value)) { return value; }
+
+ if (value.type !== "event") { throw new Error("invalid event object - " + value.type); }
+
+ return new EventFragment(_constructorGuard, {
+ name: verifyIdentifier(value.name),
+ anonymous: value.anonymous,
+ inputs: (value.inputs ? value.inputs.map(ParamType.fromObject) : []),
+ type: "event"
+ });
+ }
+
+ static fromString(value: string): EventFragment {
+
+ let match = value.match(regexParen);
+ if (!match) { throw new Error("invalid event: " + value); }
+
+ let anonymous = false;
+ match[3].split(" ").forEach((modifier) => {
+ switch(modifier.trim()) {
+ case "anonymous":
+ anonymous = true;
+ break;
+ case "":
+ break;
+ default:
+ errors.warn("unknown modifier: " + modifier);
+ }
+ });
+
+ return EventFragment.fromObject({
+ name: match[1].trim(),
+ anonymous: anonymous,
+ inputs: parseParams(match[2], true),
+ type: "event"
+ });
+ }
+}
+
+function parseGas(value: string, params: any): string {
+ params.gas = null;
+
+ let comps = value.split("@");
+ if (comps.length !== 1) {
+ if (comps.length > 2) {
+ throw new Error("invalid signature");
+ }
+ if (!comps[1].match(/^[0-9]+$/)) {
+ throw new Error("invalid signature gas");
+ }
+ params.gas = BigNumber.from(comps[1]);
+ return comps[0];
+ }
+
+ return value;
+}
+
+function parseModifiers(value: string, params: any): void {
+ params.constant = false;
+ params.payable = false;
+ // @TODO: Should this be initialized to "nonpayable"?
+ params.stateMutability = null;
+
+ value.split(" ").forEach((modifier) => {
+ switch (modifier.trim()) {
+ case "constant":
+ params.constant = true;
+ break;
+ case "payable":
+ params.payable = true;
+ params.stateMutability = "payable";
+ break;
+ case "pure":
+ params.constant = true;
+ params.stateMutability = "pure";
+ break;
+ case "view":
+ params.constant = true;
+ params.stateMutability = "view";
+ break;
+ case "external":
+ case "public":
+ case "":
+ break;
+ default:
+ console.log("unknown modifier: " + modifier);
+ }
+ });
+}
+
+export class ConstructorFragment extends Fragment {
+ stateMutability: string;
+ payable: boolean;
+ gas?: BigNumber;
+
+ static from(value: ConstructorFragment | JsonFragment | string): ConstructorFragment {
+ if (typeof(value) === "string") {
+ return ConstructorFragment.fromString(value);
+ }
+ return ConstructorFragment.fromObject(value);
+ }
+
+ static fromObject(value: ConstructorFragment | JsonFragment): ConstructorFragment {
+ if (isNamedInstance(ConstructorFragment, value)) { return value; }
+
+ if (value.type !== "constructor") { throw new Error("invalid constructor object - " + value.type); }
+
+ return new ConstructorFragment(_constructorGuard, {
+ type: value.type,
+ inputs: (value.inputs ? value.inputs.map(ParamType.fromObject): []),
+ payable: ((value.payable == null) ? true: !!value.payable),
+ gas: (value.gas ? BigNumber.from(value.gas): null)
+ });
+ }
+
+ static fromString(value: string): ConstructorFragment {
+ let params: any = { type: "constructor" };
+
+ value = parseGas(value, params);
+
+ let parens = value.match(regexParen);
+ if (!parens) { throw new Error("invalid constructor: " + value); }
+
+ if (parens[1].trim() !== "constructor") { throw new Error("invalid constructor"); }
+
+ params.inputs = parseParams(parens[2].trim(), false);
+
+ parseModifiers(parens[3].trim(), params);
+
+ return ConstructorFragment.fromObject(params);
+ }
+
+}
+
+export class FunctionFragment extends ConstructorFragment {
+ constant: boolean;
+ outputs?: Array;
+
+ static from(value: FunctionFragment | JsonFragment | string): FunctionFragment {
+ if (typeof(value) === "string") {
+ return FunctionFragment.fromString(value);
+ }
+ return FunctionFragment.fromObject(value);
+ }
+
+ static fromObject(value: FunctionFragment | JsonFragment): FunctionFragment {
+ if (isNamedInstance(FunctionFragment, value)) { return value; }
+
+ if (value.type !== "function") { throw new Error("invalid function object - " + value.type); }
+
+ return new FunctionFragment(_constructorGuard, {
+ type: value.type,
+ name: verifyIdentifier(value.name),
+ constant: !!value.constant,
+ inputs: (value.inputs ? value.inputs.map(ParamType.fromObject): []),
+ outputs: (value.outputs ? value.outputs.map(ParamType.fromObject): [ ]),
+ payable: ((value.payable == null) ? true: !!value.payable),
+ stateMutability: ((value.stateMutability != null) ?verifyString(value.stateMutability): null),
+ gas: (value.gas ? BigNumber.from(value.gas): null)
+ });
+ }
+
+ static fromString(value: string): FunctionFragment {
+ let params: any = { type: "function" };
+ value = parseGas(value, params);
+
+ let comps = value.split(" returns ");
+ if (comps.length > 2) { throw new Error("invalid function"); }
+
+ let parens = comps[0].match(regexParen);
+ if (!parens) { throw new Error("invalid signature"); }
+
+ params.name = parens[1].trim();
+ if (!params.name.match(regexIdentifier)) {
+ throw new Error("invalid identifier: '" + params.name + "'");
+ }
+
+ params.inputs = parseParams(parens[2], false);
+
+ parseModifiers(parens[3].trim(), params);
+
+ // We have outputs
+ if (comps.length > 1) {
+ let returns = comps[1].match(regexParen);
+ if (returns[1].trim() != "" || returns[3].trim() != "") {
+ throw new Error("unexpected tokens");
+ }
+ params.outputs = parseParams(returns[2], false);
+ } else {
+ params.outputs = [ ];
+ }
+
+ return FunctionFragment.fromObject(params);
+ }
+}
+
+//export class ErrorFragment extends Fragment {
+//}
+
+//export class StructFragment extends Fragment {
+//}
+
+function verifyString(value: string): string {
+ if (typeof(value) !== "string") { throw new Error("requires a string"); }
+ return value;
+}
+
+function verifyType(type: string): string {
+
+ // These need to be transformed to their full description
+ if (type.match(/^uint($|[^1-9])/)) {
+ type = "uint256" + type.substring(4);
+ } else if (type.match(/^int($|[^1-9])/)) {
+ type = "int256" + type.substring(3);
+ }
+
+ // @TODO: more verification
+
+ return type;
+}
+
+const regexIdentifier = new RegExp("^[A-Za-z_][A-Za-z0-9_]*$");
+function verifyIdentifier(value: string): string {
+ if (!value || !value.match(regexIdentifier)) {
+ throw new Error("invalid identifier: '" + value + "'");
+ }
+ return value;
+}
+
+const regexParen = new RegExp("^([^)(]*)\\((.*)\\)([^)(]*)$");
+
+function splitNesting(value: string): Array {
+ value = value.trim();
+
+ let result = [];
+ let accum = "";
+ let depth = 0;
+ for (let offset = 0; offset < value.length; offset++) {
+ let c = value[offset];
+ if (c === "," && depth === 0) {
+ result.push(accum);
+ accum = "";
+ } else {
+ accum += c;
+ if (c === "(") {
+ depth++;
+ } else if (c === ")") {
+ depth--;
+ if (depth === -1) {
+ throw new Error("unbalanced parenthsis");
+ }
+ }
+ }
+ }
+ if (accum) { result.push(accum); }
+
+ return result;
+}
+
diff --git a/packages/abi/src.ts/index.ts b/packages/abi/src.ts/index.ts
new file mode 100644
index 000000000..33ce6c1d4
--- /dev/null
+++ b/packages/abi/src.ts/index.ts
@@ -0,0 +1,26 @@
+"use strict";
+
+import { ConstructorFragment, EventFragment, Fragment, FunctionFragment, JsonFragment, JsonFragmentType, ParamType } from "./fragments";
+import { AbiCoder, CoerceFunc, defaultAbiCoder } from "./abi-coder";
+import { Indexed, Interface } from "./interface";
+
+export {
+ ConstructorFragment,
+ EventFragment,
+ Fragment,
+ FunctionFragment,
+ ParamType,
+
+ AbiCoder,
+ defaultAbiCoder,
+
+ Interface,
+ Indexed,
+
+ /////////////////////////
+ // Types
+
+ CoerceFunc,
+ JsonFragment,
+ JsonFragmentType
+};
diff --git a/packages/abi/src.ts/interface.ts b/packages/abi/src.ts/interface.ts
new file mode 100644
index 000000000..80b7f3251
--- /dev/null
+++ b/packages/abi/src.ts/interface.ts
@@ -0,0 +1,406 @@
+"use strict";
+
+import { getAddress } from "@ethersproject/address";
+import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
+import { arrayify, BytesLike, concat, hexDataSlice, hexlify, hexZeroPad, isHexString } from "@ethersproject/bytes";
+import { id } from "@ethersproject/hash";
+import { keccak256 } from "@ethersproject/keccak256"
+import * as errors from "@ethersproject/errors";
+import { defineReadOnly, Description, isNamedInstance } from "@ethersproject/properties";
+
+import { AbiCoder, defaultAbiCoder } from "./abi-coder";
+import { ConstructorFragment, EventFragment, Fragment, FunctionFragment, JsonFragment, ParamType } from "./fragments";
+
+
+export class LogDescription extends Description {
+ readonly eventFragment: EventFragment;
+ readonly name: string;
+ readonly signature: string;
+ readonly topic: string;
+ readonly values: any
+}
+
+export class TransactionDescription extends Description {
+ readonly functionFragment: FunctionFragment;
+ readonly name: string;
+ readonly args: Array;
+ readonly signature: string;
+ readonly sighash: string;
+ readonly value: BigNumber;
+}
+
+export class Indexed extends Description {
+ readonly hash: string;
+}
+
+export class Result {
+ [key: string]: any;
+ [key: number]: any;
+}
+
+
+export class Interface {
+ readonly fragments: Array;
+
+ readonly errors: { [ name: string ]: any };
+ readonly events: { [ name: string ]: EventFragment };
+ readonly functions: { [ name: string ]: FunctionFragment };
+ readonly structs: { [ name: string ]: any };
+
+ readonly deploy: ConstructorFragment;
+
+ readonly _abiCoder: AbiCoder;
+
+ constructor(fragments: string | Array) {
+ errors.checkNew(new.target, Interface);
+
+ let abi: Array = [ ];
+ if (typeof(fragments) === "string") {
+ abi = JSON.parse(fragments);
+ } else {
+ abi = fragments;
+ }
+
+ defineReadOnly(this, "fragments", abi.map((fragment) => {
+ if (isNamedInstance(Fragment, fragment)) {
+ return fragment
+ }
+ return Fragment.from(fragment);
+ }).filter((fragment) => (fragment != null)));
+
+ defineReadOnly(this, "_abiCoder", new.target.getAbiCoder());
+
+ defineReadOnly(this, "functions", { });
+ defineReadOnly(this, "errors", { });
+ defineReadOnly(this, "events", { });
+ defineReadOnly(this, "structs", { });
+
+ // Add all fragments by their signature
+ this.fragments.forEach((fragment) => {
+ let bucket: { [ name: string ]: Fragment } = null;
+ switch (fragment.type) {
+ case "constructor":
+ if (this.deploy) {
+ errors.warn("duplicate definition - constructor");
+ return;
+ }
+ defineReadOnly(this, "deploy", fragment);
+ return;
+ case "function":
+ bucket = this.functions;
+ break;
+ case "event":
+ bucket = this.events;
+ break;
+ default:
+ return;
+ }
+
+ let signature = fragment.format();
+ if (bucket[signature]) {
+ errors.warn("duplicate definition - " + signature);
+ return;
+ }
+
+ bucket[signature] = fragment;
+ });
+
+ // Add any fragments with a unique name by its name (sans signature parameters)
+ [this.events, this.functions].forEach((bucket) => {
+ let count = getNameCount(bucket);
+ Object.keys(bucket).forEach((signature) => {
+ let fragment = bucket[signature];
+ if (count[fragment.name] !== 1) {
+ errors.warn("duplicate definition - " + fragment.name);
+ return;
+ }
+ bucket[fragment.name] = fragment;
+ });
+ });
+
+ // If we do not have a constructor use the default "constructor() payable"
+ if (!this.deploy) {
+ defineReadOnly(this, "deploy", ConstructorFragment.from( { type: "constructor" } ));
+ }
+ }
+
+ static getAbiCoder(): AbiCoder {
+ return defaultAbiCoder;
+ }
+
+ static getAddress(address: string): string {
+ return getAddress(address);
+ }
+
+ _sighashify(functionFragment: FunctionFragment): string {
+ return hexDataSlice(id(functionFragment.format()), 0, 4);
+ }
+
+ _topicify(eventFragment: EventFragment): string {
+ return id(eventFragment.format());
+ }
+
+ getFunction(nameOrSignatureOrSighash: string): FunctionFragment {
+ if (isHexString(nameOrSignatureOrSighash)) {
+ return getFragment(nameOrSignatureOrSighash, this.getSighash.bind(this), this.functions);
+ }
+
+ // It is a bare name, look up the function (will return null if ambiguous)
+ if (nameOrSignatureOrSighash.indexOf("(") === -1) {
+ return (this.functions[nameOrSignatureOrSighash.trim()] || null);
+ }
+
+ // Normlize the signature and lookup the function
+ return this.functions[FunctionFragment.fromString(nameOrSignatureOrSighash).format()];
+ }
+
+ getEvent(nameOrSignatureOrTopic: string): EventFragment {
+ if (isHexString(nameOrSignatureOrTopic)) {
+ return getFragment(nameOrSignatureOrTopic, this.getEventTopic.bind(this), this.events);
+ }
+
+ // It is a bare name, look up the function (will return null if ambiguous)
+ if (nameOrSignatureOrTopic.indexOf("(") === -1) {
+ return this.events[nameOrSignatureOrTopic];
+ }
+
+ return this.events[EventFragment.fromString(nameOrSignatureOrTopic).format()];
+ }
+
+
+ getSighash(functionFragment: FunctionFragment | string): string {
+ if (typeof(functionFragment) === "string") {
+ functionFragment = this.getFunction(functionFragment);
+ }
+
+ return this._sighashify(functionFragment);
+ }
+
+ getEventTopic(eventFragment: EventFragment | string): string {
+ if (typeof(eventFragment) === "string") {
+ eventFragment = this.getEvent(eventFragment);
+ }
+
+ return this._topicify(eventFragment);
+ }
+
+
+ _encodeParams(params: Array, values: Array): string {
+ return this._abiCoder.encode(params, values)
+ }
+
+ encodeDeploy(values?: Array): string {
+ return this._encodeParams(this.deploy.inputs, values || [ ]);
+ }
+
+ encodeFunctionData(functionFragment: FunctionFragment | string, values?: Array): string {
+ if (typeof(functionFragment) === "string") {
+ functionFragment = this.getFunction(functionFragment);
+ }
+
+ return hexlify(concat([
+ this.getSighash(functionFragment),
+ this._encodeParams(functionFragment.inputs, values || [ ])
+ ]));
+ }
+
+ decodeFunctionResult(functionFragment: FunctionFragment | string, data: BytesLike): Array {
+ if (typeof(functionFragment) === "string") {
+ functionFragment = this.getFunction(functionFragment);
+ }
+
+ let bytes = arrayify(data);
+
+ let reason: string = null;
+ let errorSignature: string = null;
+ switch (bytes.length % this._abiCoder._getWordSize()) {
+ case 0:
+ try {
+ return this._abiCoder.decode(functionFragment.outputs, bytes);
+ } catch (error) { }
+ break;
+
+ case 4:
+ if (hexlify(bytes.slice(0, 4)) === "0x08c379a0") {
+ errorSignature = "Error(string)";
+ reason = this._abiCoder.decode([ "string" ], bytes.slice(4));
+ }
+ break;
+ }
+
+ return errors.throwError("call revert exception", errors.CALL_EXCEPTION, {
+ method: functionFragment.format(),
+ errorSignature: errorSignature,
+ errorArgs: [ reason ],
+ reason: reason
+ });
+ }
+
+ encodeFilterTopics(eventFragment: EventFragment, values: Array): Array> {
+ if (typeof(eventFragment) === "string") {
+ eventFragment = this.getEvent(eventFragment);
+ }
+
+ if (values.length > eventFragment.inputs.length) {
+ errors.throwError("too many arguments for " + eventFragment.format(), errors.UNEXPECTED_ARGUMENT, {
+ argument: "values",
+ value: values
+ })
+ }
+
+ let topics: Array = [];
+ if (!eventFragment.anonymous) { topics.push(this.getEventTopic(eventFragment)); }
+
+ values.forEach((value, index) => {
+
+ let param = eventFragment.inputs[index];
+
+ if (!param.indexed) {
+ if (value != null) {
+ errors.throwArgumentError("cannot filter non-indexed parameters; must be null", ("contract." + param.name), value);
+ }
+ return;
+ }
+
+ if (value == null) {
+ topics.push(null);
+ } else if (param.type === "string") {
+ topics.push(id(value));
+ } else if (param.type === "bytes") {
+ topics.push(keccak256(hexlify(value)));
+ } else if (param.type.indexOf("[") !== -1 || param.type.substring(0, 5) === "tuple") {
+ errors.throwArgumentError("filtering with tuples or arrays not supported", ("contract." + param.name), value);
+ } else {
+ // Check addresses are valid
+ if (param.type === "address") { this._abiCoder.encode( [ "address" ], [ value ]); }
+ topics.push(hexZeroPad(hexlify(value), 32));
+ }
+ });
+
+ // Trim off trailing nulls
+ while (topics.length && topics[topics.length - 1] === null) {
+ topics.pop();
+ }
+
+ return topics;
+ }
+
+ decodeEventLog(eventFragment: EventFragment | string, data: BytesLike, topics?: Array): Array {
+ if (typeof(eventFragment) === "string") {
+ eventFragment = this.getEvent(eventFragment);
+ }
+
+ if (topics != null && !eventFragment.anonymous) { topics = topics.slice(1); }
+
+ let indexed: Array = [];
+ let nonIndexed: Array = [];
+ let dynamic: Array = [];
+
+ eventFragment.inputs.forEach((param, index) => {
+ if (param.indexed) {
+ if (param.type === "string" || param.type === "bytes" || param.baseType === "tuple" || param.baseType === "array") {
+ indexed.push(ParamType.fromObject({ type: "bytes32", name: param.name }));
+ dynamic.push(true);
+ } else {
+ indexed.push(param);
+ dynamic.push(false);
+ }
+ } else {
+ nonIndexed.push(param);
+ dynamic.push(false);
+ }
+ });
+
+ let resultIndexed = (topics != null) ? this._abiCoder.decode(indexed, concat(topics)): null;
+ let resultNonIndexed = this._abiCoder.decode(nonIndexed, data);
+
+ let result: Array = [ ];
+ let nonIndexedIndex = 0, indexedIndex = 0;
+ eventFragment.inputs.forEach((param, index) => {
+ if (param.indexed) {
+ if (resultIndexed == null) {
+ result[index] = new Indexed({ hash: null });
+
+ } else if (dynamic[index]) {
+ result[index] = new Indexed({ hash: resultIndexed[indexedIndex++] });
+
+ } else {
+ result[index] = resultIndexed[indexedIndex++];
+ }
+ } else {
+ result[index] = resultNonIndexed[nonIndexedIndex++];
+ }
+ //if (param.name && result[param.name] == null) { result[param.name] = result[index]; }
+ });
+
+ return result;
+ }
+
+
+ parseTransaction(tx: { data: string, value?: BigNumberish }): TransactionDescription {
+ let fragment = this.getFunction(tx.data.substring(0, 10).toLowerCase())
+
+ if (!fragment) { return null; }
+
+ return new TransactionDescription({
+ args: this._abiCoder.decode(fragment.inputs, "0x" + tx.data.substring(10)),
+ functionFragment: fragment,
+ name: fragment.name,
+ signature: fragment.format(),
+ sighash: this.getSighash(fragment),
+ value: BigNumber.from(tx.value || "0"),
+ });
+ }
+
+ parseLog(log: { topics: Array, data: string}): LogDescription {
+ let fragment = this.getEvent(log.topics[0]);
+
+ if (!fragment || fragment.anonymous) { return null; }
+
+ // @TODO: If anonymous, and the only method, and the input count matches, should we parse?
+
+
+ return new LogDescription({
+ eventFragment: fragment,
+ name: fragment.name,
+ signature: fragment.format(),
+ topic: this.getEventTopic(fragment),
+ values: this.decodeEventLog(fragment, log.data, log.topics)
+ });
+ }
+
+
+ /*
+ static from(value: Array | string | Interface) {
+ if (Interface.isInterface(value)) {
+ return value;
+ }
+ if (typeof(value) === "string") {
+ return new Interface(JSON.parse(value));
+ }
+ return new Interface(value);
+ }
+ */
+}
+
+function getFragment(hash: string, calcFunc: (f: Fragment) => string, items: { [ sig: string ]: Fragment } ) {
+ for (let signature in items) {
+ if (signature.indexOf("(") === -1) { continue; }
+ let fragment = items[signature];
+ if (calcFunc(fragment) === hash) { return fragment; }
+ }
+ return null;
+}
+
+function getNameCount(fragments: { [ signature: string ]: Fragment }): { [ name: string ]: number } {
+ let unique: { [ name: string ]: number } = { };
+
+ // Count each name
+ for (let signature in fragments) {
+ let name = fragments[signature].name;
+ if (!unique[name]) { unique[name] = 0; }
+ unique[name]++;
+ }
+
+ return unique;
+}
diff --git a/packages/abi/test-coder.js b/packages/abi/test-coder.js
new file mode 100644
index 000000000..6969951d2
--- /dev/null
+++ b/packages/abi/test-coder.js
@@ -0,0 +1,4 @@
+let { defaultAbiCoder } = require(".");
+
+console.log(defaultAbiCoder);
+console.log(defaultAbiCoder.encode([ "uint256", "bytes" ], [ 42, "0x1234" ]));
diff --git a/packages/abi/tsconfig.json b/packages/abi/tsconfig.json
new file mode 100644
index 000000000..bbf8508be
--- /dev/null
+++ b/packages/abi/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "rootDir": "./src.ts",
+ "outDir": "./"
+ },
+ "include": [
+ "./src.ts/*.ts",
+ "./src.ts/coders/*.ts"
+ ],
+ "exclude": [ ]
+}
+
diff --git a/packages/abstract-provider/.npmignore b/packages/abstract-provider/.npmignore
new file mode 100644
index 000000000..684f719ca
--- /dev/null
+++ b/packages/abstract-provider/.npmignore
@@ -0,0 +1,2 @@
+tsconfig.json
+src.ts/
diff --git a/packages/abstract-provider/LICENSE.md b/packages/abstract-provider/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/packages/abstract-provider/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/abstract-provider/README.md b/packages/abstract-provider/README.md
new file mode 100644
index 000000000..8010615a2
--- /dev/null
+++ b/packages/abstract-provider/README.md
@@ -0,0 +1,17 @@
+Abstract Provider
+=================
+
+**EXPERIMENTAL**
+
+Please see the [ethers](https://github.com/ethers-io/ethers.js) repository
+for more informations.
+
+API
+---
+
+`@TODO`
+
+License
+-------
+
+MIT License
diff --git a/packages/abstract-provider/package.json b/packages/abstract-provider/package.json
new file mode 100644
index 000000000..81a7f51b3
--- /dev/null
+++ b/packages/abstract-provider/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@ethersproject/abstract-provider",
+ "version": "5.0.0-beta.126",
+ "description": "Error utility functions for ethers.",
+ "main": "index.js",
+ "browser": {
+ "net": "./browser-net.js",
+ "./ipc-provider": "./browser-ipc-provider"
+ },
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@ethersproject/bignumber": ">5.0.0-beta.0",
+ "@ethersproject/bytes": ">5.0.0-beta.0",
+ "@ethersproject/errors": ">5.0.0-beta.0",
+ "@ethersproject/networks": ">5.0.0-beta.0",
+ "@ethersproject/properties": ">5.0.0-beta.0",
+ "@ethersproject/transactions": ">5.0.0-beta.0",
+ "@ethersproject/web": ">5.0.0-beta.0"
+ },
+ "keywords": [
+ "Ethereum",
+ "ethers"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0x4424dfecaacd3e4d135d01136d92bcee22bb7a5d0ce5d2c5272bd02422135db7"
+}
diff --git a/packages/abstract-provider/src.ts/_version.ts b/packages/abstract-provider/src.ts/_version.ts
new file mode 100644
index 000000000..10730de6c
--- /dev/null
+++ b/packages/abstract-provider/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.126";
diff --git a/packages/abstract-provider/src.ts/index.ts b/packages/abstract-provider/src.ts/index.ts
new file mode 100644
index 000000000..a17efc4ea
--- /dev/null
+++ b/packages/abstract-provider/src.ts/index.ts
@@ -0,0 +1,283 @@
+"use strict";
+
+import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
+import { BytesLike, isHexString } from "@ethersproject/bytes";
+import * as errors from "@ethersproject/errors";
+import { checkAbstract } from "@ethersproject/errors";
+import { Network } from "@ethersproject/networks";
+import { defineReadOnly } from "@ethersproject/properties";
+import { Transaction } from "@ethersproject/transactions";
+import { OnceBlockable } from "@ethersproject/web";
+
+
+///////////////////////////////
+// Exported Types
+
+
+export type TransactionRequest = {
+ to?: string | Promise,
+ from?: string | Promise,
+ nonce?: BigNumberish | Promise,
+
+ gasLimit?: BigNumberish | Promise,
+ gasPrice?: BigNumberish | Promise,
+
+ data?: BytesLike | Promise,
+ value?: BigNumberish | Promise,
+ chainId?: number | Promise,
+}
+
+export interface TransactionResponse extends Transaction {
+ // Only if a transaction has been mined
+ blockNumber?: number,
+ blockHash?: string,
+ timestamp?: number,
+
+ confirmations: number,
+
+ // Not optional (as it is in Transaction)
+ from: string;
+
+ // The raw transaction
+ raw?: string,
+
+ // This function waits until the transaction has been mined
+ wait: (confirmations?: number) => Promise
+};
+
+export type BlockTag = string | number;
+
+interface _Block {
+ hash: string;
+ parentHash: string;
+ number: number;
+
+ timestamp: number;
+ nonce: string;
+ difficulty: number;
+
+ gasLimit: BigNumber;
+ gasUsed: BigNumber;
+
+ miner: string;
+ extraData: string;
+}
+
+export interface Block extends _Block {
+ transactions: Array;
+}
+
+export interface BlockWithTransactions extends _Block {
+ transactions: Array;
+}
+
+
+export interface Log {
+ blockNumber?: number;
+ blockHash?: string;
+ transactionIndex?: number;
+
+ removed?: boolean;
+
+ transactionLogIndex?: number,
+
+ address: string;
+ data: string;
+
+ topics: Array;
+
+ transactionHash?: string;
+ logIndex?: number;
+}
+
+export interface TransactionReceipt {
+ to?: string;
+ from?: string;
+ contractAddress?: string,
+ transactionIndex?: number,
+ root?: string,
+ gasUsed?: BigNumber,
+ logsBloom?: string,
+ blockHash?: string,
+ transactionHash?: string,
+ logs?: Array,
+ blockNumber?: number,
+ confirmations?: number,
+ cumulativeGasUsed?: BigNumber,
+ byzantium: boolean,
+ status?: number
+};
+
+export interface EventFilter {
+ address?: string;
+ topics?: Array>;
+}
+
+export interface Filter extends EventFilter {
+ fromBlock?: BlockTag,
+ toBlock?: BlockTag,
+}
+
+export interface FilterByBlockHash extends EventFilter {
+ blockhash?: string;
+}
+
+//export type CallTransactionable = {
+// call(transaction: TransactionRequest): Promise;
+//};
+
+export class ForkEvent {
+ readonly expiry: number;
+
+ constructor(expiry?: number) {
+ defineReadOnly(this, "expiry", expiry || 0);
+ }
+}
+
+export class BlockForkEvent extends ForkEvent {
+ readonly blockhash: string;
+
+ constructor(blockhash: string, expiry?: number) {
+ if (!isHexString(blockhash, 32)) {
+ errors.throwArgumentError("invalid blockhash", "blockhash", blockhash);
+ }
+ super(expiry);
+ defineReadOnly(this, "blockhash", blockhash);
+ }
+}
+
+export class TransactionForkEvent extends ForkEvent {
+ readonly hash: string;
+
+ constructor(hash: string, expiry?: number) {
+ if (!isHexString(hash, 32)) {
+ errors.throwArgumentError("invalid transaction hash", "hash", hash);
+ }
+ super(expiry);
+ defineReadOnly(this, "hash", hash);
+ }
+}
+
+export class TransactionOrderForkEvent extends ForkEvent {
+ readonly beforeHash: string;
+ readonly afterHash: string;
+
+ constructor(beforeHash: string, afterHash: string, expiry?: number) {
+ if (!isHexString(beforeHash, 32)) {
+ errors.throwArgumentError("invalid transaction hash", "beforeHash", beforeHash);
+ }
+ if (!isHexString(afterHash, 32)) {
+ errors.throwArgumentError("invalid transaction hash", "afterHash", afterHash);
+ }
+ super(expiry);
+ defineReadOnly(this, "beforeHash", beforeHash);
+ defineReadOnly(this, "afterHash", afterHash);
+ }
+}
+
+export type EventType = string | Array> | EventFilter | ForkEvent;
+
+export type Listener = (...args: Array) => void;
+
+///////////////////////////////
+// Exported Abstracts
+
+export abstract class Provider implements OnceBlockable {
+
+ // Network
+ abstract getNetwork(): Promise;
+
+ // Latest State
+ abstract getBlockNumber(): Promise;
+ abstract getGasPrice(): Promise;
+
+ // Account
+ abstract getBalance(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise;
+ abstract getTransactionCount(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise;
+ abstract getCode(addressOrName: string | Promise, blockTag?: BlockTag | Promise): Promise ;
+ abstract getStorageAt(addressOrName: string | Promise, position: BigNumberish | Promise, blockTag?: BlockTag | Promise): Promise;
+
+ // Execution
+ abstract sendTransaction(signedTransaction: string | Promise): Promise;
+ abstract call(transaction: TransactionRequest, blockTag?: BlockTag | Promise): Promise;
+ abstract estimateGas(transaction: TransactionRequest): Promise;
+
+ // Queries
+ abstract getBlock(blockHashOrBlockTag: BlockTag | string | Promise): Promise;
+ abstract getBlockWithTransactions(blockHashOrBlockTag: BlockTag | string | Promise): Promise;
+ abstract getTransaction(transactionHash: string): Promise;
+ abstract getTransactionReceipt(transactionHash: string): Promise;
+
+ // Bloom-filter Queries
+ abstract getLogs(filter: Filter): Promise>;
+
+ // ENS
+ abstract resolveName(name: string | Promise): Promise;
+ abstract lookupAddress(address: string | Promise): Promise;
+
+ // Event Emitter (ish)
+ abstract on(eventName: EventType, listener: Listener): Provider;
+ abstract once(eventName: EventType, listener: Listener): Provider;
+ abstract emit(eventName: EventType, ...args: Array): boolean
+ abstract listenerCount(eventName?: EventType): number;
+ abstract listeners(eventName?: EventType): Array;
+ abstract off(eventName: EventType, listener?: Listener): Provider;
+ abstract removeAllListeners(eventName?: EventType): Provider;
+
+ // Alias for "on"
+ addListener(eventName: EventType, listener: Listener): Provider {
+ return this.on(eventName, listener);
+ }
+
+ // Alias for "off"
+ removeListener(eventName: EventType, listener: Listener): Provider {
+ return this.off(eventName, listener);
+ }
+
+ // @TODO: This *could* be implemented here, but would pull in events...
+ abstract waitForTransaction(transactionHash: string, timeout?: number): Promise;
+
+ constructor() {
+ checkAbstract(new.target, Provider);
+ }
+
+/*
+ static getResolver(network: Network, callable: CallTransactionable, namehash: string): string {
+ // No ENS...
+ if (!network.ensAddress) {
+ errors.throwError(
+ "network does support ENS",
+ errors.UNSUPPORTED_OPERATION,
+ { operation: "ENS", network: network.name }
+ );
+ }
+
+ // Not a namehash
+ if (!isHexString(namehash, 32)) {
+ errors.throwArgumentError("invalid name hash", "namehash", namehash);
+ }
+
+ // keccak256("resolver(bytes32)")
+ let data = "0x0178b8bf" + namehash.substring(2);
+ let transaction = { to: network.ensAddress, data: data };
+
+ return provider.call(transaction).then((data) => {
+ return provider.formatter.callAddress(data);
+ });
+ }
+
+ static resolveNamehash(network: Network, callable: CallTransactionable, namehash: string): string {
+ return this.getResolver(network, callable, namehash).then((resolverAddress) => {
+ if (!resolverAddress) { return null; }
+
+ // keccak256("addr(bytes32)")
+ let data = "0x3b3b57de" + namehash(name).substring(2);
+ let transaction = { to: resolverAddress, data: data };
+ return callable.call(transaction).then((data) => {
+ return this.formatter.callAddress(data);
+ });
+
+ })
+ }
+*/
+}
diff --git a/packages/abstract-provider/tsconfig.json b/packages/abstract-provider/tsconfig.json
new file mode 100644
index 000000000..f8b22b29e
--- /dev/null
+++ b/packages/abstract-provider/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "rootDir": "./src.ts",
+ "outDir": "./"
+ },
+ "include": [
+ "./src.ts/*"
+ ],
+ "exclude": [ ]
+}
+
diff --git a/packages/abstract-signer/.npmignore b/packages/abstract-signer/.npmignore
new file mode 100644
index 000000000..684f719ca
--- /dev/null
+++ b/packages/abstract-signer/.npmignore
@@ -0,0 +1,2 @@
+tsconfig.json
+src.ts/
diff --git a/packages/abstract-signer/LICENSE.md b/packages/abstract-signer/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/packages/abstract-signer/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/abstract-signer/README.md b/packages/abstract-signer/README.md
new file mode 100644
index 000000000..bf4277022
--- /dev/null
+++ b/packages/abstract-signer/README.md
@@ -0,0 +1,17 @@
+Abstract Signer
+===============
+
+**EXPERIMENTAL**
+
+Please see the [ethers](https://github.com/ethers-io/ethers.js) repository
+for more informations.
+
+API
+---
+
+`@TODO`
+
+License
+-------
+
+MIT License
diff --git a/packages/abstract-signer/package.json b/packages/abstract-signer/package.json
new file mode 100644
index 000000000..c036fd260
--- /dev/null
+++ b/packages/abstract-signer/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@ethersproject/abstract-signer",
+ "version": "5.0.0-beta.126",
+ "description": "Error utility functions for ethers.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@ethersproject/abstract-provider": ">5.0.0-beta.0",
+ "@ethersproject/bignumber": ">5.0.0-beta.0",
+ "@ethersproject/bytes": ">5.0.0-beta.0",
+ "@ethersproject/errors": ">5.0.0-beta.0",
+ "@ethersproject/properties": ">5.0.0-beta.0"
+ },
+ "keywords": [
+ "Ethereum",
+ "ethers"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0x3bc5ce92bedb99f4c15e3c399e4367eaa001bb72bf25a82f52e09c74f09f492f"
+}
diff --git a/packages/abstract-signer/src.ts/_version.ts b/packages/abstract-signer/src.ts/_version.ts
new file mode 100644
index 000000000..10730de6c
--- /dev/null
+++ b/packages/abstract-signer/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.126";
diff --git a/packages/abstract-signer/src.ts/index.ts b/packages/abstract-signer/src.ts/index.ts
new file mode 100644
index 000000000..1af05aaee
--- /dev/null
+++ b/packages/abstract-signer/src.ts/index.ts
@@ -0,0 +1,216 @@
+"use strict";
+
+import { BlockTag, Provider, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
+import { BigNumber } from "@ethersproject/bignumber";
+import { Bytes } from "@ethersproject/bytes";
+import * as errors from "@ethersproject/errors";
+import { defineReadOnly, resolveProperties, shallowCopy } from "@ethersproject/properties";
+
+const allowedTransactionKeys: Array = [
+ "chainId", "data", "from", "gasLimit", "gasPrice", "nonce", "to", "value"
+];
+
+// Sub-classes of Signer may optionally extend this interface to indicate
+// they have a private key available synchronously
+export interface ExternallyOwnedAccount {
+ readonly address: string;
+ readonly privateKey: string;
+ readonly mnemonic?: string;
+ readonly path?: string;
+}
+
+// Sub-Class Notes:
+// - A Signer MUST always make sure, that if present, the "from" field
+// matches the Signer, before sending or signing a transaction
+// - A Signer SHOULD always wrap private information (such as a private
+// key or mnemonic) in a function, so that console.log does not leak
+// the data
+
+export abstract class Signer {
+ readonly provider?: Provider;
+
+ ///////////////////
+ // Sub-classes MUST implement these
+
+ // Returns the checksum address
+ abstract getAddress(): Promise
+
+ // Returns the signed prefixed-message. This MUST treat:
+ // - Bytes as a binary message
+ // - string as a UTF8-message
+ // i.e. "0x1234" is a SIX (6) byte string, NOT 2 bytes of data
+ abstract signMessage(message: Bytes | string): Promise;
+
+ // Signs a transaxction and returns the fully serialized, signed transaction.
+ // The EXACT transaction MUST be signed, and NO additional properties to be added.
+ // - This MAY throw if signing transactions is not supports, but if
+ // it does, sentTransaction MUST be overridden.
+ abstract signTransaction(transaction: TransactionRequest): Promise;
+
+ // Returns a new instance of the Signer, connected to provider.
+ // This MAY throw if changing providers is not supported.
+ abstract connect(provider: Provider): Signer;
+
+
+ ///////////////////
+ // Sub-classes MUST call super
+ constructor() {
+ errors.checkAbstract(new.target, Signer);
+ }
+
+
+ ///////////////////
+ // Sub-classes MAY override these
+
+ getBalance(blockTag?: BlockTag): Promise {
+ this._checkProvider("getBalance");
+ return this.provider.getBalance(this.getAddress(), blockTag);
+ }
+
+ getTransactionCount(blockTag?: BlockTag): Promise {
+ this._checkProvider("getTransactionCount");
+ return this.provider.getTransactionCount(this.getAddress(), blockTag);
+ }
+
+ // Populates "from" if unspecified, and estimates the gas for the transation
+ estimateGas(transaction: TransactionRequest): Promise {
+ this._checkProvider("estimateGas");
+ return resolveProperties(this.checkTransaction(transaction)).then((tx) => {
+ return this.provider.estimateGas(tx);
+ });
+ }
+
+ // Populates "from" if unspecified, and calls with the transation
+ call(transaction: TransactionRequest, blockTag?: BlockTag): Promise {
+ this._checkProvider("call");
+ return resolveProperties(this.checkTransaction(transaction)).then((tx) => {
+ return this.provider.call(tx);
+ });
+ }
+
+ // Populates all fields in a transaction, signs it and sends it to the network
+ sendTransaction(transaction: TransactionRequest): Promise {
+ this._checkProvider("sendTransaction");
+ return this.populateTransaction(transaction).then((tx) => {
+ return this.signTransaction(tx).then((signedTx) => {
+ return this.provider.sendTransaction(signedTx);
+ });
+ });
+ }
+
+ getChainId(): Promise {
+ this._checkProvider("getChainId");
+ return this.provider.getNetwork().then((network) => network.chainId);
+ }
+
+ getGasPrice(): Promise {
+ this._checkProvider("getGasPrice");
+ return this.provider.getGasPrice();
+ }
+
+ resolveName(name: string): Promise {
+ this._checkProvider("resolveName");
+ return this.provider.resolveName(name);
+ }
+
+
+
+
+ // Checks a transaction does not contain invalid keys and if
+ // no "from" is provided, populates it.
+ // - does NOT require a provider
+ // - adds "from" is not present
+ // - returns a COPY (safe to mutate the result)
+ // By default called from: (overriding these prevents it)
+ // - call
+ // - estimateGas
+ // - populateTransaction (and therefor sendTransaction)
+ checkTransaction(transaction: TransactionRequest): TransactionRequest {
+ for (let key in transaction) {
+ if (allowedTransactionKeys.indexOf(key) === -1) {
+ errors.throwArgumentError("invalid transaction key: " + key, "transaction", transaction);
+ }
+ }
+
+ let tx = shallowCopy(transaction);
+ if (tx.from == null) { tx.from = this.getAddress(); }
+ return tx;
+ }
+
+ // Populates ALL keys for a transaction and checks that "from" matches
+ // this Signer. Should be used by sendTransaction but NOT by signTransaction.
+ // By default called from: (overriding these prevents it)
+ // - sendTransaction
+ populateTransaction(transaction: TransactionRequest): Promise {
+ return resolveProperties(this.checkTransaction(transaction)).then((tx) => {
+
+ if (tx.to != null) { tx.to = Promise.resolve(tx.to).then((to) => this.resolveName(to)); }
+ if (tx.gasPrice == null) { tx.gasPrice = this.getGasPrice(); }
+ if (tx.nonce == null) { tx.nonce = this.getTransactionCount("pending"); }
+
+ // Make sure any provided address matches this signer
+ if (tx.from == null) {
+ tx.from = this.getAddress();
+ } else {
+ tx.from = Promise.all([
+ this.getAddress(),
+ this.provider.resolveName(tx.from)
+ ]).then((results) => {
+ if (results[0] !== results[1]) {
+ errors.throwArgumentError("from address mismatch", "transaction", transaction);
+ }
+ return results[0];
+ });
+ }
+
+ if (tx.gasLimit == null) { tx.gasLimit = this.estimateGas(tx); }
+ if (tx.chainId == null) { tx.chainId = this.getChainId(); }
+
+ return resolveProperties(tx);
+ });
+ }
+
+
+ ///////////////////
+ // Sub-classes SHOULD leave these alone
+
+ _checkProvider(operation?: string): void {
+ if (!this.provider) { errors.throwError("missing provider", errors.UNSUPPORTED_OPERATION, {
+ operation: (operation || "_checkProvider") });
+ }
+ }
+}
+
+export class VoidSigner extends Signer {
+ readonly address: string;
+
+ constructor(address: string, provider?: Provider) {
+ errors.checkNew(new.target, VoidSigner);
+ super();
+ defineReadOnly(this, "address", address);
+ defineReadOnly(this, "provider", provider || null);
+ }
+
+ getAddress(): Promise {
+ return Promise.resolve(this.address);
+ }
+
+ _fail(message: string, operation: string): Promise {
+ return Promise.resolve().then(() => {
+ errors.throwError(message, errors.UNSUPPORTED_OPERATION, { operation: operation });
+ });
+ }
+
+ signMessage(message: Bytes | string): Promise {
+ return this._fail("VoidSigner cannot sign messages", "signMessage");
+ }
+
+ signTransaction(transaction: TransactionRequest): Promise {
+ return this._fail("VoidSigner cannot sign transactions", "signTransaction");
+ }
+
+ connect(provider: Provider): VoidSigner {
+ return new VoidSigner(this.address, provider);
+ }
+}
+
diff --git a/packages/abstract-signer/tsconfig.json b/packages/abstract-signer/tsconfig.json
new file mode 100644
index 000000000..f8b22b29e
--- /dev/null
+++ b/packages/abstract-signer/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "rootDir": "./src.ts",
+ "outDir": "./"
+ },
+ "include": [
+ "./src.ts/*"
+ ],
+ "exclude": [ ]
+}
+
diff --git a/packages/address/.npmignore b/packages/address/.npmignore
new file mode 100644
index 000000000..684f719ca
--- /dev/null
+++ b/packages/address/.npmignore
@@ -0,0 +1,2 @@
+tsconfig.json
+src.ts/
diff --git a/packages/address/LICENSE.md b/packages/address/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/packages/address/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/address/README.md b/packages/address/README.md
new file mode 100644
index 000000000..dbea4f2da
--- /dev/null
+++ b/packages/address/README.md
@@ -0,0 +1,17 @@
+Ethereum Address Utilities
+==========================
+
+**EXPERIMENTAL**
+
+Please see the [ethers](https://github.com/ethers-io/ethers.js) repository
+for more informations.
+
+API
+---
+
+`@TODO`
+
+License
+-------
+
+MIT License
diff --git a/packages/address/package.json b/packages/address/package.json
new file mode 100644
index 000000000..3b88052ee
--- /dev/null
+++ b/packages/address/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "@ethersproject/address",
+ "version": "5.0.0-beta.125",
+ "description": "Error utility functions for ethers.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@ethersproject/bignumber": ">5.0.0-beta.0",
+ "@ethersproject/bytes": ">5.0.0-beta.0",
+ "@ethersproject/errors": ">5.0.0-beta.0",
+ "@ethersproject/keccak256": ">5.0.0-beta.0",
+ "@ethersproject/rlp": ">5.0.0-beta.0",
+ "bn.js": "^4.4.0"
+ },
+ "keywords": [
+ "Ethereum",
+ "ethers"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0x962ac58ce384cd1b220fd2d7fbd66e914f4c55f38440d75afd12ba560cffc7f1"
+}
diff --git a/packages/address/src.ts/_version.ts b/packages/address/src.ts/_version.ts
new file mode 100644
index 000000000..08381ee67
--- /dev/null
+++ b/packages/address/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.125";
diff --git a/packages/address/src.ts/index.ts b/packages/address/src.ts/index.ts
new file mode 100644
index 000000000..40acf766f
--- /dev/null
+++ b/packages/address/src.ts/index.ts
@@ -0,0 +1,146 @@
+"use strict";
+
+// We use this for base 36 maths
+import * as BN from "bn.js";
+
+import * as errors from "@ethersproject/errors";
+
+import { arrayify, hexDataSlice, isHexString, stripZeros } from "@ethersproject/bytes";
+import { BigNumberish } from "@ethersproject/bignumber";
+import { keccak256 } from "@ethersproject/keccak256";
+import { encode } from "@ethersproject/rlp";
+
+
+function getChecksumAddress(address: string): string {
+ if (!isHexString(address, 20)) {
+ errors.throwError("invalid address", errors.INVALID_ARGUMENT, { arg: "address", value: address });
+ }
+
+ address = address.toLowerCase();
+
+ let chars = address.substring(2).split("");
+
+ let hashed = new Uint8Array(40);
+ for (let i = 0; i < 40; i++) {
+ hashed[i] = chars[i].charCodeAt(0);
+ }
+ hashed = arrayify(keccak256(hashed));
+
+ for (let i = 0; i < 40; i += 2) {
+ if ((hashed[i >> 1] >> 4) >= 8) {
+ chars[i] = chars[i].toUpperCase();
+ }
+ if ((hashed[i >> 1] & 0x0f) >= 8) {
+ chars[i + 1] = chars[i + 1].toUpperCase();
+ }
+ }
+
+ return "0x" + chars.join("");
+}
+
+// Shims for environments that are missing some required constants and functions
+const MAX_SAFE_INTEGER: number = 0x1fffffffffffff;
+
+function log10(x: number): number {
+ if (Math.log10) { return Math.log10(x); }
+ return Math.log(x) / Math.LN10;
+}
+
+
+// See: https://en.wikipedia.org/wiki/International_Bank_Account_Number
+
+// Create lookup table
+let ibanLookup: { [character: string]: string } = {};
+for (let i = 0; i < 10; i++) { ibanLookup[String(i)] = String(i); }
+for (let i = 0; i < 26; i++) { ibanLookup[String.fromCharCode(65 + i)] = String(10 + i); }
+
+// How many decimal digits can we process? (for 64-bit float, this is 15)
+let safeDigits = Math.floor(log10(MAX_SAFE_INTEGER));
+
+function ibanChecksum(address: string): string {
+ address = address.toUpperCase();
+ address = address.substring(4) + address.substring(0, 2) + "00";
+
+ let expanded = "";
+ address.split("").forEach(function(c) {
+ expanded += ibanLookup[c];
+ });
+
+ // Javascript can handle integers safely up to 15 (decimal) digits
+ while (expanded.length >= safeDigits){
+ let block = expanded.substring(0, safeDigits);
+ expanded = parseInt(block, 10) % 97 + expanded.substring(block.length);
+ }
+
+ let checksum = String(98 - (parseInt(expanded, 10) % 97));
+ while (checksum.length < 2) { checksum = "0" + checksum; }
+
+ return checksum;
+};
+
+export function getAddress(address: string): string {
+ let result = null;
+
+ if (typeof(address) !== "string") {
+ errors.throwArgumentError("invalid address", "address", address);
+ }
+
+ if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) {
+
+ // Missing the 0x prefix
+ if (address.substring(0, 2) !== "0x") { address = "0x" + address; }
+
+ result = getChecksumAddress(address);
+
+ // It is a checksummed address with a bad checksum
+ if (address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) && result !== address) {
+ errors.throwArgumentError("bad address checksum", "address", address);
+ }
+
+ // Maybe ICAP? (we only support direct mode)
+ } else if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) {
+
+ // It is an ICAP address with a bad checksum
+ if (address.substring(2, 4) !== ibanChecksum(address)) {
+ errors.throwArgumentError("bad icap checksum", "address", address);
+ }
+
+ result = (new BN.BN(address.substring(4), 36)).toString(16);
+ while (result.length < 40) { result = "0" + result; }
+ result = getChecksumAddress("0x" + result);
+
+ } else {
+ errors.throwArgumentError("invalid address", "address", address);
+ }
+
+ return result;
+}
+
+export function isAddress(address: string): boolean {
+ try {
+ getAddress(address);
+ return true;
+ } catch (error) { }
+ return false;
+}
+
+export function getIcapAddress(address: string): string {
+ let base36 = (new BN.BN(getAddress(address).substring(2), 16)).toString(36).toUpperCase();
+ while (base36.length < 30) { base36 = "0" + base36; }
+ return "XE" + ibanChecksum("XE00" + base36) + base36;
+}
+
+// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
+export function getContractAddress(transaction: { from: string, nonce: BigNumberish }) {
+ let from: string = null;
+ try {
+ from = getAddress(transaction.from);
+ } catch (error) {
+ errors.throwArgumentError("missing from address", "transaction", transaction);
+ }
+
+ let nonce = stripZeros(arrayify(transaction.nonce));
+
+ return getAddress(hexDataSlice(keccak256(encode([ from, nonce ])), 12));
+}
+
diff --git a/packages/address/thirdparty.d.ts b/packages/address/thirdparty.d.ts
new file mode 100644
index 000000000..8c07d6bfb
--- /dev/null
+++ b/packages/address/thirdparty.d.ts
@@ -0,0 +1,30 @@
+declare module "bn.js" {
+ export class BN {
+ constructor(value: string | number, radix?: number);
+
+ add(other: BN): BN;
+ sub(other: BN): BN;
+ div(other: BN): BN;
+ mod(other: BN): BN;
+ mul(other: BN): BN;
+
+ pow(other: BN): BN;
+ maskn(other: number): BN;
+
+ eq(other: BN): boolean;
+ lt(other: BN): boolean;
+ lte(other: BN): boolean;
+ gt(other: BN): boolean;
+ gte(other: BN): boolean;
+
+ isZero(): boolean;
+
+ toTwos(other: number): BN;
+ fromTwos(other: number): BN;
+
+ toString(radix: number): string;
+ toNumber(): number;
+ toArray(endian: string, width: number): Uint8Array;
+ encode(encoding: string, compact: boolean): Uint8Array;
+ }
+}
diff --git a/packages/address/tsconfig.json b/packages/address/tsconfig.json
new file mode 100644
index 000000000..f7c84c603
--- /dev/null
+++ b/packages/address/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "rootDir": "./src.ts",
+ "outDir": "./"
+ },
+ "include": [
+ "./thirdparty.d.ts",
+ "./src.ts/*"
+ ],
+ "exclude": [ ]
+}
+
diff --git a/packages/base64/.gitignore b/packages/base64/.gitignore
new file mode 100644
index 000000000..aa5c62527
--- /dev/null
+++ b/packages/base64/.gitignore
@@ -0,0 +1 @@
+browser.d.ts
diff --git a/packages/base64/.npmignore b/packages/base64/.npmignore
new file mode 100644
index 000000000..684f719ca
--- /dev/null
+++ b/packages/base64/.npmignore
@@ -0,0 +1,2 @@
+tsconfig.json
+src.ts/
diff --git a/packages/base64/LICENSE.md b/packages/base64/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/packages/base64/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/base64/README.md b/packages/base64/README.md
new file mode 100644
index 000000000..7e4a07fdd
--- /dev/null
+++ b/packages/base64/README.md
@@ -0,0 +1,36 @@
+Base64 Coder
+============
+
+function decode(textData: string): Uint8Array
+---------------------------------------------
+
+Decodes a base64 encoded string into the binary data.
+
+```javascript
+import * as base64 from "@ethersproject/base64";
+
+let encodedData = "...";
+let data = base64.decode(encodedData);
+console.log(data);
+// { Uint8Array: [] }
+```
+
+function encode(data: Arrayish): string
+---------------------------------------
+
+Decodes a base64 encoded string into the binary data.
+
+```javascript
+import * as base64 from "@ethersproject/base64";
+
+let data = [ ];
+let encodedData = base64.encode(data);
+console.log(encodedData);
+// "..."
+```
+
+
+License
+=======
+
+MIT License
diff --git a/packages/base64/package.json b/packages/base64/package.json
new file mode 100644
index 000000000..2d36b08ce
--- /dev/null
+++ b/packages/base64/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@ethersproject/base64",
+ "version": "5.0.0-beta.124",
+ "description": "Base64 coder.",
+ "main": "index.js",
+ "browser": "browser.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@ethersproject/bytes": ">5.0.0-beta.0"
+ },
+ "keywords": [
+ "Ethereum",
+ "ethers"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0xe5da4b9df9188216fed902efb45ee9af1cdb9a47c1212d9a674a93c5adb716bd"
+}
diff --git a/packages/base64/src.ts/_version.ts b/packages/base64/src.ts/_version.ts
new file mode 100644
index 000000000..4c7b329a3
--- /dev/null
+++ b/packages/base64/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.124";
diff --git a/packages/base64/src.ts/browser.ts b/packages/base64/src.ts/browser.ts
new file mode 100644
index 000000000..a252395a6
--- /dev/null
+++ b/packages/base64/src.ts/browser.ts
@@ -0,0 +1,23 @@
+"use strict";
+
+import { arrayify, BytesLike } from "@ethersproject/bytes";
+
+export function decode(textData: string): Uint8Array {
+ textData = atob(textData);
+ let data = [];
+ for (let i = 0; i < textData.length; i++) {
+ data.push(textData.charCodeAt(i));
+ }
+ return arrayify(data);
+}
+
+export function encode(data: BytesLike): string {
+ data = arrayify(data);
+ let textData = "";
+ for (let i = 0; i < data.length; i++) {
+ textData += String.fromCharCode(data[i]);
+ }
+ return btoa(textData);
+}
+
+
diff --git a/packages/base64/src.ts/index.ts b/packages/base64/src.ts/index.ts
new file mode 100644
index 000000000..c6f84a0cc
--- /dev/null
+++ b/packages/base64/src.ts/index.ts
@@ -0,0 +1,12 @@
+"use strict";
+
+import { arrayify, BytesLike } from "@ethersproject/bytes";
+
+
+export function decode(textData: string): Uint8Array {
+ return arrayify(new Uint8Array(Buffer.from(textData, "base64")));
+};
+
+export function encode(data: BytesLike): string {
+ return Buffer.from(arrayify(data)).toString("base64");
+}
diff --git a/packages/base64/tsconfig.json b/packages/base64/tsconfig.json
new file mode 100644
index 000000000..f8b22b29e
--- /dev/null
+++ b/packages/base64/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "rootDir": "./src.ts",
+ "outDir": "./"
+ },
+ "include": [
+ "./src.ts/*"
+ ],
+ "exclude": [ ]
+}
+
diff --git a/packages/basex/.npmignore b/packages/basex/.npmignore
new file mode 100644
index 000000000..684f719ca
--- /dev/null
+++ b/packages/basex/.npmignore
@@ -0,0 +1,2 @@
+tsconfig.json
+src.ts/
diff --git a/packages/basex/LICENSE.md b/packages/basex/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/packages/basex/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/basex/README.md b/packages/basex/README.md
new file mode 100644
index 000000000..db64b85c6
--- /dev/null
+++ b/packages/basex/README.md
@@ -0,0 +1,5 @@
+Base X
+======
+
+**EXPERIMENTAL**
+
diff --git a/packages/basex/package.json b/packages/basex/package.json
new file mode 100644
index 000000000..abc2f2b3f
--- /dev/null
+++ b/packages/basex/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@ethersproject/basex",
+ "version": "5.0.0-beta.124",
+ "description": "Base-X without Buffer.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@ethersproject/bytes": ">5.0.0-beta.0",
+ "@ethersproject/properties": ">5.0.0-beta.0"
+ },
+ "keywords": [
+ "Ethereum",
+ "ethers"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0x8243e502fd9c51780351423f21ff95978c66005a9a9691cabd98e805ca69fe40"
+}
diff --git a/packages/basex/src.ts/_version.ts b/packages/basex/src.ts/_version.ts
new file mode 100644
index 000000000..4c7b329a3
--- /dev/null
+++ b/packages/basex/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.124";
diff --git a/packages/basex/src.ts/index.ts b/packages/basex/src.ts/index.ts
new file mode 100644
index 000000000..a99e9905b
--- /dev/null
+++ b/packages/basex/src.ts/index.ts
@@ -0,0 +1,143 @@
+/**
+ * var basex = require("base-x");
+ *
+ * This implementation is heavily based on base-x. The main reason to
+ * deviate was to prevent the dependency of Buffer.
+ *
+ * Contributors:
+ *
+ * base-x encoding
+ * Forked from https://github.com/cryptocoinjs/bs58
+ * Originally written by Mike Hearn for BitcoinJ
+ * Copyright (c) 2011 Google Inc
+ * Ported to JavaScript by Stefan Thomas
+ * Merged Buffer refactorings from base58-native by Stephen Pair
+ * Copyright (c) 2013 BitPay Inc
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright base-x contributors (c) 2016
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ */
+
+import { arrayify, BytesLike } from "@ethersproject/bytes";
+import { defineReadOnly } from "@ethersproject/properties";
+
+export class BaseX {
+ readonly alphabet: string;
+ readonly base: number;
+
+ private _alphabetMap: { [ character: string ]: number };
+ private _leader: string;
+
+ constructor(alphabet: string) {
+ defineReadOnly(this, "alphabet", alphabet);
+ defineReadOnly(this, "base", alphabet.length);
+
+ defineReadOnly(this, "_alphabetMap", { });
+ defineReadOnly(this, "_leader", alphabet.charAt(0));
+
+ // pre-compute lookup table
+ for (let i = 0; i < alphabet.length; i++) {
+ this._alphabetMap[alphabet.charAt(i)] = i;
+ }
+ }
+
+ encode(value: BytesLike): string {
+ let source = arrayify(value);
+
+ if (source.length === 0) { return ""; }
+
+ let digits = [ 0 ]
+ for (let i = 0; i < source.length; ++i) {
+ let carry = source[i];
+ for (let j = 0; j < digits.length; ++j) {
+ carry += digits[j] << 8;
+ digits[j] = carry % this.base;
+ carry = (carry / this.base) | 0;
+ }
+
+ while (carry > 0) {
+ digits.push(carry % this.base);
+ carry = (carry / this.base) | 0;
+ }
+ }
+
+ let string = ""
+
+ // deal with leading zeros
+ for (let k = 0; source[k] === 0 && k < source.length - 1; ++k) {
+ string += this._leader;
+ }
+
+ // convert digits to a string
+ for (let q = digits.length - 1; q >= 0; --q) {
+ string += this.alphabet[digits[q]];
+ }
+
+ return string;
+ }
+
+ decode(value: string): Uint8Array {
+ if (typeof(value) !== "string") {
+ throw new TypeError("Expected String");
+ }
+
+ let bytes: Array = [];
+ if (value.length === 0) { return new Uint8Array(bytes); }
+
+ bytes.push(0);
+ for (let i = 0; i < value.length; i++) {
+ let byte = this._alphabetMap[value[i]];
+
+ if (byte === undefined) {
+ throw new Error("Non-base" + this.base + " character");
+ }
+
+ let carry = byte;
+ for (let j = 0; j < bytes.length; ++j) {
+ carry += bytes[j] * this.base;
+ bytes[j] = carry & 0xff;
+ carry >>= 8;
+ }
+
+ while (carry > 0) {
+ bytes.push(carry & 0xff);
+ carry >>= 8;
+ }
+ }
+
+ // deal with leading zeros
+ for (let k = 0; value[k] === this._leader && k < value.length - 1; ++k) {
+ bytes.push(0)
+ }
+
+ return arrayify(new Uint8Array(bytes.reverse()))
+ }
+}
+
+const Base32 = new BaseX("abcdefghijklmnopqrstuvwxyz234567");
+const Base58 = new BaseX("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
+
+export { Base32, Base58 };
+
+//console.log(Base58.decode("Qmd2V777o5XvJbYMeMb8k2nU5f8d3ciUQ5YpYuWhzv8iDj"))
+//console.log(Base58.encode(Base58.decode("Qmd2V777o5XvJbYMeMb8k2nU5f8d3ciUQ5YpYuWhzv8iDj")))
diff --git a/packages/basex/tsconfig.json b/packages/basex/tsconfig.json
new file mode 100644
index 000000000..f8b22b29e
--- /dev/null
+++ b/packages/basex/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "rootDir": "./src.ts",
+ "outDir": "./"
+ },
+ "include": [
+ "./src.ts/*"
+ ],
+ "exclude": [ ]
+}
+
diff --git a/packages/bignumber/.npmignore b/packages/bignumber/.npmignore
new file mode 100644
index 000000000..684f719ca
--- /dev/null
+++ b/packages/bignumber/.npmignore
@@ -0,0 +1,2 @@
+tsconfig.json
+src.ts/
diff --git a/packages/bignumber/LICENSE.md b/packages/bignumber/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/packages/bignumber/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/bignumber/README.md b/packages/bignumber/README.md
new file mode 100644
index 000000000..1d1b8bb2d
--- /dev/null
+++ b/packages/bignumber/README.md
@@ -0,0 +1,17 @@
+Big Numbers
+===========
+
+**EXPERIMENTAL**
+
+Please see the [ethers](https://github.com/ethers-io/ethers.js) repository
+for more informations.
+
+API
+---
+
+`@TODO`
+
+License
+-------
+
+MIT License
diff --git a/packages/bignumber/package.json b/packages/bignumber/package.json
new file mode 100644
index 000000000..9de8fa8d9
--- /dev/null
+++ b/packages/bignumber/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@ethersproject/bignumber",
+ "version": "5.0.0-beta.126",
+ "description": "BigNumber library used in ethers.js.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@ethersproject/bytes": ">5.0.0-beta.0",
+ "@ethersproject/errors": ">5.0.0-beta.0",
+ "@ethersproject/properties": ">5.0.0-beta.0",
+ "bn.js": "^4.4.0"
+ },
+ "keywords": [
+ "Ethereum",
+ "bignumber",
+ "bn"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0xbb5426f664ba919d5668ddde9f13255ef77278a2e556f4d64f9be1089f5947c0"
+}
diff --git a/packages/bignumber/src.ts/_version.ts b/packages/bignumber/src.ts/_version.ts
new file mode 100644
index 000000000..10730de6c
--- /dev/null
+++ b/packages/bignumber/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.126";
diff --git a/packages/bignumber/src.ts/bignumber.ts b/packages/bignumber/src.ts/bignumber.ts
new file mode 100644
index 000000000..990e8e606
--- /dev/null
+++ b/packages/bignumber/src.ts/bignumber.ts
@@ -0,0 +1,350 @@
+"use strict";
+
+/**
+ * BigNumber
+ *
+ * A wrapper around the BN.js object. We use the BN.js library
+ * because it is used by elliptic, so it is required regardles.
+ *
+ */
+
+import * as BN from "bn.js";
+
+import { Bytes, Hexable, hexlify, isBytes, isHexString } from "@ethersproject/bytes";
+import { defineReadOnly, isNamedInstance } from "@ethersproject/properties";
+
+import * as errors from "@ethersproject/errors";
+
+const _constructorGuard = { };
+
+const MAX_SAFE = 0x1fffffffffffff;
+
+
+export type BigNumberish = BigNumber | Bytes | string | number;
+
+/*
+export function isBigNumberLike(value: any): value is BigNumberish {
+ return (BigNumber.isBigNumber(value) ||
+ (!!((value).toHexString)) ||
+ isBytes(value) ||
+ value.match(/^-?([0-9]+|0x[0-9a-f]+)$/i) ||
+ typeof(value) === "number");
+}
+*/
+
+export class BigNumber implements Hexable {
+ readonly _hex: string;
+
+ constructor(constructorGuard: any, hex: string) {
+ errors.checkNew(new.target, BigNumber);
+
+ if (constructorGuard !== _constructorGuard) {
+ errors.throwError("cannot call consturtor directly; use BigNumber.from", errors.UNSUPPORTED_OPERATION, {
+ operation: "new (BigNumber)"
+ });
+ }
+
+ defineReadOnly(this, "_hex", hex);
+ }
+
+ fromTwos(value: number): BigNumber {
+ return toBigNumber(toBN(this).fromTwos(value));
+ }
+
+ toTwos(value: number): BigNumber {
+ return toBigNumber(toBN(this).toTwos(value));
+ }
+
+ abs(): BigNumber {
+ if (this._hex[0] === "-") {
+ return BigNumber.from(this._hex.substring(1));
+ }
+ return this;
+ }
+
+ add(other: BigNumberish): BigNumber {
+ return toBigNumber(toBN(this).add(toBN(other)));
+ }
+
+ sub(other: BigNumberish): BigNumber {
+ return toBigNumber(toBN(this).sub(toBN(other)));
+ }
+
+ div(other: BigNumberish): BigNumber {
+ let o = BigNumber.from(other);
+ if (o.isZero()) {
+ throwFault("division by zero", "div");
+ }
+ return toBigNumber(toBN(this).div(toBN(other)));
+ }
+
+ mul(other: BigNumberish): BigNumber {
+ return toBigNumber(toBN(this).mul(toBN(other)));
+ }
+
+ mod(other: BigNumberish): BigNumber {
+ return toBigNumber(toBN(this).mod(toBN(other)));
+ }
+
+ pow(other: BigNumberish): BigNumber {
+ return toBigNumber(toBN(this).pow(toBN(other)));
+ }
+
+ maskn(value: number): BigNumber {
+ return toBigNumber(toBN(this).maskn(value));
+ }
+
+ eq(other: BigNumberish): boolean {
+ return toBN(this).eq(toBN(other));
+ }
+
+ lt(other: BigNumberish): boolean {
+ return toBN(this).lt(toBN(other));
+ }
+
+ lte(other: BigNumberish): boolean {
+ return toBN(this).lte(toBN(other));
+ }
+
+ gt(other: BigNumberish): boolean {
+ return toBN(this).gt(toBN(other));
+ }
+
+ gte(other: BigNumberish): boolean {
+ return toBN(this).gte(toBN(other));
+ }
+
+ isZero(): boolean {
+ return toBN(this).isZero();
+ }
+
+ toNumber(): number {
+ try {
+ return toBN(this).toNumber();
+ } catch (error) {
+ throwFault("overflow", "toNumber", this.toString());
+ }
+ return null;
+ }
+
+ toString(): string {
+ // Lots of people expect this, which we do not support, so check
+ if (arguments.length !== 0) {
+ errors.throwError("bigNumber.toString does not accept parameters", errors.UNEXPECTED_ARGUMENT, { });
+ }
+ return toBN(this).toString(10);
+ }
+
+ toHexString(): string {
+ return this._hex;
+ }
+
+ static from(value: any): BigNumber {
+ if (value instanceof BigNumber) { return value; }
+
+ if (typeof(value) === "string") {
+ if (value.match(/-?0x[0-9a-f]+/i)) {
+ return new BigNumber(_constructorGuard, toHex(value));
+ }
+
+ if (value.match(/^-?[0-9]+$/)) {
+ return new BigNumber(_constructorGuard, toHex(new BN.BN(value)));
+ }
+
+ return errors.throwArgumentError("invalid BigNumber string", "value", value);
+ }
+
+ if (typeof(value) === "number") {
+ if (value % 1) {
+ throwFault("underflow", "BigNumber.from", value);
+ }
+
+ if (value >= MAX_SAFE || value <= -MAX_SAFE) {
+ throwFault("overflow", "BigNumber.from", value);
+ }
+
+ return BigNumber.from(String(value));
+ }
+
+ if (typeof(value) === "bigint") {
+ return BigNumber.from((value).toString());
+ }
+
+ if (isBytes(value)) {
+ return BigNumber.from(hexlify(value));
+ }
+
+ if ((value)._hex && isHexString((value)._hex)) {
+ return BigNumber.from((value)._hex);
+ }
+
+ if ((value).toHexString) {
+ value = (value).toHexString();
+ if (typeof(value) === "string") {
+ return BigNumber.from(value);
+ }
+ }
+
+ return errors.throwArgumentError("invalid BigNumber value", "value", value);
+ }
+
+ static isBigNumber(value: any): value is BigNumber {
+ return isNamedInstance(this, value);
+ }
+}
+
+/*
+export function bigNumberify(value: BigNumberish): BigNumber {
+ if (BigNumber.isBigNumber(value)) { return value; }
+ return new BigNumber(value);
+}
+*/
+
+/*
+function zeros(length) {
+ let result = "";
+ while (result.length < length) { tens += "0"; }
+ return result;
+}
+export class FixedNumber {
+ readonly value: BigNumber;
+ readonly decimalPlaces: number;
+
+ constructor(value: BigNumberish, decimalPlaces: number) {
+ defineReadOnly(this, "value", bigNumberify(value));
+ defineReadOnly(this, "decimalPlaces", decimalPlaces);
+ }
+
+ toString(): string {
+ return formatUnits(this.value, this.decimalPlaces);
+ }
+
+ static fromString(value: string): FixedNumber {
+ let comps = value.split(".");
+ let decimalPlaces = 0;
+ if (comps.length === 2) { decimalPlaces = comps[1].length; }
+ return new FixedNumber(parseUnits(value, decimalPlaces), decimalPlaces);
+ }
+*/
+/*
+
+ readonly negative: boolean;
+ readonly whole: BigNumber;
+ readonly fraction: BigNumber;
+ constructor(whole: BigNumberish, fraction: BigNumberish, negative?: boolean) {
+ if (whole.lt(constants.Zero)) {
+ errors.throwError("whole component must be positive", errors.INVALID_ARGUMENT, {
+ argument: whole,
+ value: whole
+ });
+ }
+ defineReadOnly(this, "whole", bigNumberify(whole));
+ defineReadOnly(this, "fraction", bigNumberify(fraction));
+ defineReadOnly(this, "negative", !!boolean);
+ }
+*/
+/*
+ toHexString(bitWidth?: number, decimalPlaces?: number, signed?: boolean): string {
+ if (bitWidth == null) { bitWidth = 128; }
+ if (decimalPlaces == null) { decimalPlaces = 18; }
+ if (signed == null) { signed = true; }
+ return null;
+ }
+ static fromValue(value: BigNumberish, decimalPlaces: number): FixedNumber {
+ let negative = false;
+ if (value.lt(constants.Zero)) {
+ negative = true;
+ value = value.abs();
+ }
+ let tens = bigNumberify("1" + zeros(decimalPlaces));
+ return new FixedNumber(value.divide(tens), value.mod(tens), negative);
+ }
+ let negative = false;
+ if (value.substring(0, 1) === "-") {
+ negative = true;
+ value = value.substring(1);
+ }
+
+ if (value !== "." && value !== "") {
+ let comps = value.split(".");
+ if (comps.length === 1) {
+ return new FixedNumber(comps[0], 0, negative);
+ } else if (comps.length === 2) {
+ if (comps[0] === "") { comps[0] = "0"; }
+ if (comps[1] === "") { comps[1] = "0"; }
+ return new FixedNumber(comps[0], comps[1], negative);
+ }
+ }
+
+ errors.throwError("invalid fixed-point value", errors.INVALID_ARGUMENT, {
+ argument: "value",
+ value: value
+ });
+
+ return null;
+*/
+
+//}
+
+
+// Normalize the hex string
+function toHex(value: string | BN.BN): string {
+
+ // For BN, call on the hex string
+ if (typeof(value) !== "string") {
+ return toHex(value.toString(16));
+ }
+
+ // If negative, prepend the negative sign to the normalized positive value
+ if (value[0] === "-") {
+ // Strip off the negative sign
+ value = value.substring(1);
+
+ // Cannot have mulitple negative signs (e.g. "--0x04")
+ if (value[0] === "-") { errors.throwArgumentError("invalid hex", "value", value); }
+
+ // Call toHex on the positive component
+ value = toHex(value);
+
+ // Do not allow "-0x00"
+ if (value === "0x00") { return value; }
+
+ // Negate the value
+ return "-" + value;
+ }
+
+ // Add a "0x" prefix if missing
+ if (value.substring(0, 2) !== "0x") { value = "0x" + value; }
+
+ // Normalize zero
+ if (value === "0x") { return "0x00"; }
+
+ // Make the string even length
+ if (value.length % 2) { value = "0x0" + value.substring(2); }
+
+ // Trim to smallest even-length string
+ while (value.length > 4 && value.substring(0, 4) === "0x00") {
+ value = "0x" + value.substring(4);
+ }
+
+ return value;
+}
+
+function toBigNumber(value: BN.BN): BigNumber {
+ return BigNumber.from(toHex(value));
+}
+
+function toBN(value: BigNumberish): BN.BN {
+ let hex = BigNumber.from(value).toHexString();
+ if (hex[0] === "-") {
+ return (new BN.BN("-" + hex.substring(3), 16));
+ }
+ return new BN.BN(hex.substring(2), 16);
+}
+
+function throwFault(fault: string, operation: string, value?: any): never {
+ let params: any = { fault: fault, operation: operation };
+ if (value != null) { params.value = value; }
+
+ return errors.throwError(fault, errors.NUMERIC_FAULT, params);
+}
diff --git a/packages/bignumber/src.ts/fixednumber.ts b/packages/bignumber/src.ts/fixednumber.ts
new file mode 100644
index 000000000..482f1ab7a
--- /dev/null
+++ b/packages/bignumber/src.ts/fixednumber.ts
@@ -0,0 +1,343 @@
+"use strict";
+
+import { arrayify, BytesLike, hexZeroPad, isBytes } from "@ethersproject/bytes";
+import * as errors from "@ethersproject/errors";
+import { defineReadOnly, isNamedInstance } from "@ethersproject/properties";
+
+import { BigNumber, BigNumberish } from "./bignumber";
+
+const _constructorGuard = { };
+
+const Zero = BigNumber.from(0);
+const NegativeOne = BigNumber.from(-1);
+
+function throwFault(message: string, fault: string, operation: string, value?: any): never {
+ let params: any = { fault: fault, operation: operation };
+ if (value !== undefined) { params.value = value; }
+ return errors.throwError(message, errors.NUMERIC_FAULT, params);
+}
+
+// Constant to pull zeros from for multipliers
+let zeros = "0";
+while (zeros.length < 256) { zeros += zeros; }
+
+// Returns a string "1" followed by decimal "0"s
+function getMultiplier(decimals: BigNumberish): string {
+
+ if (typeof(decimals) !== "number") {
+ try {
+ decimals = BigNumber.from(decimals).toNumber();
+ } catch (e) { }
+ }
+
+ if (typeof(decimals) === "number" && decimals >= 0 && decimals <= 256 && !(decimals % 1)) {
+ return ("1" + zeros.substring(0, decimals));
+ }
+
+ return errors.throwArgumentError("invalid decimal size", "decimals", decimals);
+}
+
+export function formatFixed(value: BigNumberish, decimals?: string | BigNumberish): string {
+ if (decimals == null) { decimals = 0; }
+ let multiplier = getMultiplier(decimals);
+
+ // Make sure wei is a big number (convert as necessary)
+ value = BigNumber.from(value);
+
+ let negative = value.lt(Zero);
+ if (negative) { value = value.mul(NegativeOne); }
+
+ let fraction = value.mod(multiplier).toString();
+ while (fraction.length < multiplier.length - 1) { fraction = "0" + fraction; }
+
+ // Strip training 0
+ fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1];
+
+ let whole = value.div(multiplier).toString();
+
+ value = whole + "." + fraction;
+
+ if (negative) { value = "-" + value; }
+
+ return value;
+}
+
+export function parseFixed(value: string, decimals?: BigNumberish): BigNumber {
+ if (decimals == null) { decimals = 0; }
+ let multiplier = getMultiplier(decimals);
+
+ if (typeof(value) !== "string" || !value.match(/^-?[0-9.,]+$/)) {
+ errors.throwArgumentError("invalid decimal value", "value", value);
+ }
+
+ if (multiplier.length - 1 === 0) {
+ return BigNumber.from(value);
+ }
+
+ // Is it negative?
+ let negative = (value.substring(0, 1) === "-");
+ if (negative) { value = value.substring(1); }
+
+ if (value === ".") {
+ errors.throwArgumentError("missing value", "value", value);
+ }
+
+ // Split it into a whole and fractional part
+ let comps = value.split(".");
+ if (comps.length > 2) {
+ errors.throwArgumentError("too many decimal points", "value", value);
+ }
+
+ let whole = comps[0], fraction = comps[1];
+ if (!whole) { whole = "0"; }
+ if (!fraction) { fraction = "0"; }
+
+ // Prevent underflow
+ if (fraction.length > multiplier.length - 1) {
+ throwFault("fractional component exceeds decimals", "underflow", "parseFixed");
+ }
+
+ // Fully pad the string with zeros to get to wei
+ while (fraction.length < multiplier.length - 1) { fraction += "0"; }
+
+ let wholeValue = BigNumber.from(whole);
+ let fractionValue = BigNumber.from(fraction);
+
+ let wei = (wholeValue.mul(multiplier)).add(fractionValue);
+
+ if (negative) { wei = wei.mul(NegativeOne); }
+
+ return wei;
+}
+
+export class FixedFormat {
+ readonly signed: boolean;
+ readonly width: number;
+ readonly decimals: number;
+ readonly name: string;
+ readonly _multiplier: BigNumber;
+
+ constructor(constructorGuard: any, signed: boolean, width: number, decimals: number) {
+ defineReadOnly(this, "signed", signed);
+ defineReadOnly(this, "width", width);
+ defineReadOnly(this, "decimals", decimals);
+
+ let name = (signed ? "": "u") + "fixed" + String(width) + "x" + String(decimals);
+ defineReadOnly(this, "name", name);
+
+ defineReadOnly(this, "_multiplier", getMultiplier(decimals));
+ }
+
+ static from(value: any): FixedFormat {
+ if (value instanceof FixedFormat) { return value; }
+
+ let signed = true;
+ let width = 128;
+ let decimals = 18;
+
+ if (typeof(value) === "string") {
+ if (value === "fixed") {
+ // defaults...
+ } else if (value === "ufixed") {
+ signed = false;
+ } else if (value != null) {
+ let match = value.match(/^(u?)fixed([0-9]+)x([0-9]+)$/);
+ if (!match) { errors.throwArgumentError("invalid fixed format", "format", value); }
+ signed = (match[1] !== "u");
+ width = parseInt(match[2]);
+ decimals = parseInt(match[3]);
+ }
+ } else if (value) {
+ let check = (key: string, type: string, defaultValue: any): any => {
+ if (value[key] == null) { return defaultValue; }
+ if (typeof(value[key]) !== type) {
+ errors.throwArgumentError("invalid fixed format (" + key + " not " + type +")", "format." + key, value[key]);
+ }
+ return value[key];
+ }
+ signed = check("signed", "boolean", signed);
+ width = check("width", "number", width);
+ decimals = check("decimals", "number", decimals);
+ }
+
+ if (width % 8) {
+ errors.throwArgumentError("invalid fixed format width (not byte aligned)", "format.width", width);
+ }
+
+ if (decimals > 80) {
+ errors.throwArgumentError("invalid fixed format (decimals too large)", "format.decimals", decimals);
+ }
+
+ return new FixedFormat(_constructorGuard, signed, width, decimals);
+ }
+
+ static isInstance(value: any): value is FixedFormat {
+ return isNamedInstance(this, value);
+ }
+}
+
+export class FixedNumber {
+ readonly format: FixedFormat;
+ readonly _hex: string;
+ readonly _value: string;
+
+ constructor(constructorGuard: any, hex: string, value: string, format?: FixedFormat) {
+ errors.checkNew(new.target, FixedNumber);
+ defineReadOnly(this, 'format', format);
+ defineReadOnly(this, '_hex', hex);
+ defineReadOnly(this, '_value', value);
+ }
+
+
+ _checkFormat(other: FixedNumber): void {
+ if (this.format.name !== other.format.name) {
+ errors.throwArgumentError("incompatible format; use fixedNumber.toFormat", "other", other);
+ }
+ }
+
+ addUnsafe(other: FixedNumber): FixedNumber {
+ this._checkFormat(other);
+ let a = parseFixed(this._value, this.format.decimals);
+ let b = parseFixed(other._value, other.format.decimals);
+ return FixedNumber.fromValue(a.add(b), this.format.decimals, this.format);
+ }
+
+ subUnsafe(other: FixedNumber): FixedNumber {
+ this._checkFormat(other);
+ let a = parseFixed(this._value, this.format.decimals);
+ let b = parseFixed(other._value, other.format.decimals);
+ return FixedNumber.fromValue(a.sub(b), this.format.decimals, this.format);
+ }
+
+ mulUnsafe(other: FixedNumber): FixedNumber {
+ this._checkFormat(other);
+ let a = parseFixed(this._value, this.format.decimals);
+ let b = parseFixed(other._value, other.format.decimals);
+ return FixedNumber.fromValue(a.mul(b).div(this.format._multiplier), this.format.decimals, this.format);
+ }
+
+ divUnsafe(other: FixedNumber): FixedNumber {
+ this._checkFormat(other);
+ let a = parseFixed(this._value, this.format.decimals);
+ let b = parseFixed(other._value, other.format.decimals);
+ return FixedNumber.fromValue(a.mul(this.format._multiplier).div(b), this.format.decimals, this.format);
+ }
+
+ // @TODO: Support other rounding algorithms
+ round(decimals?: number): FixedNumber {
+ if (decimals == null) { decimals = 0; }
+ if (decimals < 0 || decimals > 80 || (decimals % 1)) {
+ errors.throwArgumentError("invalid decimal cound", "decimals", decimals);
+ }
+
+ // If we are already in range, we're done
+ let comps = this.toString().split(".");
+ if (comps[1].length <= decimals) { return this; }
+
+ // Bump the value up by the 0.00...0005
+ let bump = "0." + zeros.substring(0, decimals) + "5";
+ comps = this.addUnsafe(FixedNumber.fromString(bump, this.format))._value.split(".");
+
+ // Now it is safe to truncate
+ return FixedNumber.fromString(comps[0] + "." + comps[1].substring(0, decimals));
+ }
+
+
+ toString(): string { return this._value; }
+
+ toHexString(width?: number): string {
+ if (width == null) { return this._hex; }
+ if (width % 8) { errors.throwArgumentError("invalid byte width", "width", width); }
+ let hex = BigNumber.from(this._hex).fromTwos(this.format.width).toTwos(width).toHexString();
+ return hexZeroPad(hex, width / 8);
+ }
+
+ toUnsafeFloat(): number { return parseFloat(this.toString()); }
+
+ toFormat(format: FixedFormat | string): FixedNumber {
+ return FixedNumber.fromString(this._value, format);
+ }
+
+
+ static fromValue(value: BigNumber, decimals?: BigNumberish, format?: FixedFormat | string): FixedNumber {
+ // If decimals looks more like a format, and there is no format, shift the parameters
+ if (format == null && decimals != null && (FixedFormat.isInstance(decimals) || typeof(decimals) === "string")) {
+ format = decimals;
+ decimals = null;
+ }
+
+ if (decimals == null) { decimals = 0; }
+ if (format == null) { format = "fixed"; }
+
+ let fixedFormat = (FixedFormat.isInstance(format) ? format: FixedFormat.from(format));
+ return FixedNumber.fromString(formatFixed(value, decimals), fixedFormat);
+ }
+
+
+ static fromString(value: string, format?: FixedFormat | string): FixedNumber {
+ if (format == null) { format = "fixed"; }
+
+ let fixedFormat = (FixedFormat.isInstance(format) ? format: FixedFormat.from(format));
+
+ let numeric = parseFixed(value, fixedFormat.decimals);
+
+ if (!fixedFormat.signed && numeric.lt(Zero)) {
+ throwFault("unsigned value cannot be negative", "overflow", "value", value);
+ }
+
+ let hex: string = null;
+ if (fixedFormat.signed) {
+ hex = numeric.toTwos(fixedFormat.width).toHexString();
+ } else {
+ hex = numeric.toHexString();
+ hex = hexZeroPad(hex, fixedFormat.width / 8);
+ }
+
+ let decimal = formatFixed(numeric, fixedFormat.decimals);
+
+ return new FixedNumber(_constructorGuard, hex, decimal, fixedFormat);
+ }
+
+ static fromBytes(value: BytesLike, format?: FixedFormat | string): FixedNumber {
+ if (format == null) { format = "fixed"; }
+
+ let fixedFormat = (FixedFormat.isInstance(format) ? format: FixedFormat.from(format));
+
+ if (arrayify(value).length > fixedFormat.width / 8) {
+ throw new Error("overflow");
+ }
+
+ let numeric = BigNumber.from(value);
+ if (fixedFormat.signed) { numeric = numeric.fromTwos(fixedFormat.width); }
+
+ let hex = numeric.toTwos((fixedFormat.signed ? 0: 1) + fixedFormat.width).toHexString();
+ let decimal = formatFixed(numeric, fixedFormat.decimals);
+
+ return new FixedNumber(_constructorGuard, hex, decimal, fixedFormat);
+ }
+
+ static from(value: any, format?: FixedFormat | string) {
+ if (typeof(value) === "string") {
+ return FixedNumber.fromString(value, format);
+ }
+
+ if (isBytes(value)) {
+ return FixedNumber.fromBytes(value, format);
+ }
+
+ try {
+ return FixedNumber.fromValue(value, 0, format);
+ } catch (error) {
+ // Allow NUMERIC_FAULT to bubble up
+ if (error.code !== errors.INVALID_ARGUMENT) {
+ throw error;
+ }
+ }
+
+ return errors.throwArgumentError("invalid FixedNumber value", "value", value);
+ }
+
+ static isFixedNumber(value: any): value is FixedNumber {
+ return isNamedInstance(this, value);
+ }
+}
diff --git a/packages/bignumber/src.ts/index.ts b/packages/bignumber/src.ts/index.ts
new file mode 100644
index 000000000..a80c3ba53
--- /dev/null
+++ b/packages/bignumber/src.ts/index.ts
@@ -0,0 +1,2 @@
+export { BigNumber, BigNumberish } from "./bignumber";
+export { FixedNumber } from "./fixednumber";
diff --git a/packages/bignumber/thirdparty.d.ts b/packages/bignumber/thirdparty.d.ts
new file mode 100644
index 000000000..8c07d6bfb
--- /dev/null
+++ b/packages/bignumber/thirdparty.d.ts
@@ -0,0 +1,30 @@
+declare module "bn.js" {
+ export class BN {
+ constructor(value: string | number, radix?: number);
+
+ add(other: BN): BN;
+ sub(other: BN): BN;
+ div(other: BN): BN;
+ mod(other: BN): BN;
+ mul(other: BN): BN;
+
+ pow(other: BN): BN;
+ maskn(other: number): BN;
+
+ eq(other: BN): boolean;
+ lt(other: BN): boolean;
+ lte(other: BN): boolean;
+ gt(other: BN): boolean;
+ gte(other: BN): boolean;
+
+ isZero(): boolean;
+
+ toTwos(other: number): BN;
+ fromTwos(other: number): BN;
+
+ toString(radix: number): string;
+ toNumber(): number;
+ toArray(endian: string, width: number): Uint8Array;
+ encode(encoding: string, compact: boolean): Uint8Array;
+ }
+}
diff --git a/packages/bignumber/tsconfig.json b/packages/bignumber/tsconfig.json
new file mode 100644
index 000000000..f09aa6ba8
--- /dev/null
+++ b/packages/bignumber/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "rootDir": "./src.ts",
+ "outDir": "./"
+ },
+ "include": [
+ "./src.ts/*.ts",
+ "./thirdparty.d.ts"
+ ],
+ "exclude": [ ]
+}
+
diff --git a/packages/bytes/.npmignore b/packages/bytes/.npmignore
new file mode 100644
index 000000000..684f719ca
--- /dev/null
+++ b/packages/bytes/.npmignore
@@ -0,0 +1,2 @@
+tsconfig.json
+src.ts/
diff --git a/packages/bytes/LICENSE.md b/packages/bytes/LICENSE.md
new file mode 100644
index 000000000..989e34a72
--- /dev/null
+++ b/packages/bytes/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Richard Moore
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/bytes/README.md b/packages/bytes/README.md
new file mode 100644
index 000000000..0616e29a6
--- /dev/null
+++ b/packages/bytes/README.md
@@ -0,0 +1,17 @@
+Byte Manipulation Libraries
+===========================
+
+**EXPERIMENTAL**
+
+Please see the [ethers](https://github.com/ethers-io/ethers.js) repository
+for more informations.
+
+API
+---
+
+`@TODO`
+
+License
+-------
+
+MIT License
diff --git a/packages/bytes/package.json b/packages/bytes/package.json
new file mode 100644
index 000000000..f0b473aa7
--- /dev/null
+++ b/packages/bytes/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@ethersproject/bytes",
+ "version": "5.0.0-beta.126",
+ "description": "Bytes utility functions for ethers.",
+ "main": "index.js",
+ "scripts": {
+ "build": "tsc -p ./tsconfig.json",
+ "auto-build": "npm run build -- -w",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "@ethersproject/errors": ">5.0.0-beta.0"
+ },
+ "keywords": [
+ "Ethereum",
+ "ethers"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0xb1a6e6e8b38d5c28de285c858a6a7fe0a505a0de9cb2baa0d07a7c0e3af167d1"
+}
diff --git a/packages/bytes/src.ts/_version.ts b/packages/bytes/src.ts/_version.ts
new file mode 100644
index 000000000..10730de6c
--- /dev/null
+++ b/packages/bytes/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.126";
diff --git a/packages/bytes/src.ts/index.ts b/packages/bytes/src.ts/index.ts
new file mode 100644
index 000000000..8cf1e1d4f
--- /dev/null
+++ b/packages/bytes/src.ts/index.ts
@@ -0,0 +1,445 @@
+"use strict";
+
+
+import * as errors from "@ethersproject/errors";
+
+
+///////////////////////////////
+// Exported Types
+
+export type Bytes = ArrayLike;
+
+export type BytesLike = Bytes | string;
+
+export type DataOptions = {
+ allowMissingPrefix?: boolean;
+ allowOddLength?: boolean;
+};
+
+export interface Hexable {
+ toHexString(): string;
+}
+
+
+/*
+export interface HexString {
+ length: number;
+ substring: (start: number, end?: number) => string;
+
+ [index: number]: string;
+}
+*/
+
+export type SignatureLike = {
+ r: string;
+ s?: string;
+ _vs?: string,
+ recoveryParam?: number;
+ v?: number;
+} | BytesLike;
+
+export interface Signature {
+ r: string;
+
+ s: string;
+ _vs: string,
+
+ recoveryParam: number;
+ v: number;
+}
+
+///////////////////////////////
+
+
+function isHexable(value: any): value is Hexable {
+ return !!(value.toHexString);
+}
+
+function addSlice(array: Uint8Array): Uint8Array {
+ if (array.slice) { return array; }
+
+ array.slice = function() {
+ let args = Array.prototype.slice.call(arguments);
+ return addSlice(new Uint8Array(Array.prototype.slice.apply(array, args)));
+ }
+
+ return array;
+}
+
+export function isBytesLike(value: any): value is BytesLike {
+ return ((isHexString(value) && !(value.length % 2)) || isBytes(value));
+}
+
+export function isBytes(value: any): value is Bytes {
+ if (value == null) { return false; }
+
+ if (value.constructor === Uint8Array) { return true; }
+ if (typeof(value) === "string") { return false; }
+ if (value.length == null) { return false; }
+
+ for (let i = 0; i < value.length; i++) {
+ let v = value[i];
+ if (v < 0 || v >= 256 || (v % 1)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+export function arrayify(value: BytesLike | Hexable | number, options?: DataOptions): Uint8Array {
+ if (!options) { options = { }; }
+
+ if (typeof(value) === "number") {
+ errors.checkSafeUint53(value, "invalid arrayify value");
+
+ let result = [];
+ while (value) {
+ result.unshift(value & 0xff);
+ value /= 256;
+ }
+ if (result.length === 0) { result.push(0); }
+
+ return addSlice(new Uint8Array(result));
+ }
+
+ if (options.allowMissingPrefix && typeof(value) === "string" && value.substring(0, 2) !== "0x") {
+ value = "0x" + value;
+ }
+
+ if (isHexable(value)) { value = value.toHexString(); }
+
+ if (isHexString(value)) {
+ let hex = (value).substring(2);
+ if (!options.allowOddLength && hex.length % 2) {
+ errors.throwArgumentError("hex data is odd-length", "value", value);
+ }
+
+ let result = [];
+ for (let i = 0; i < hex.length; i += 2) {
+ result.push(parseInt(hex.substring(i, i + 2), 16));
+ }
+
+ return addSlice(new Uint8Array(result));
+ }
+
+ if (isBytes(value)) {
+ return addSlice(new Uint8Array(value));
+ }
+
+ return errors.throwArgumentError("invalid arrayify value", "value", value);
+}
+
+export function concat(items: Array): Uint8Array {
+ let objects = items.map(item => arrayify(item));
+ let length = objects.reduce((accum, item) => (accum + item.length), 0);
+
+ let result = new Uint8Array(length);
+
+ objects.reduce((offset, object) => {
+ result.set(object, offset);
+ return offset + object.length;
+ }, 0);
+
+ return addSlice(result);
+}
+
+export function stripZeros(value: BytesLike): Uint8Array {
+ let result: Uint8Array = arrayify(value);
+
+ if (result.length === 0) { return result; }
+
+ // Find the first non-zero entry
+ let start = 0;
+ while (start < result.length && result[start] === 0) { start++ }
+
+ // If we started with zeros, strip them
+ if (start) {
+ result = result.slice(start);
+ }
+
+ return result;
+}
+
+export function zeroPad(value: BytesLike, length: number): Uint8Array {
+ value = arrayify(value);
+
+ if (value.length > length) {
+ errors.throwArgumentError("value out of range", "value", arguments[0]);
+ }
+
+ let result = new Uint8Array(length);
+ result.set(value, length - value.length);
+ return addSlice(result);
+}
+
+
+export function isHexString(value: any, length?: number): boolean {
+ if (typeof(value) !== "string" || !value.match(/^0x[0-9A-Fa-f]*$/)) {
+ return false
+ }
+ if (length && value.length !== 2 + 2 * length) { return false; }
+ return true;
+}
+
+const HexCharacters: string = "0123456789abcdef";
+
+export function hexlify(value: BytesLike | Hexable | number, options?: DataOptions): string {
+ if (!options) { options = { }; }
+
+ if (typeof(value) === "number") {
+ errors.checkSafeUint53(value, "invalid hexlify value");
+
+ let hex = "";
+ while (value) {
+ hex = HexCharacters[value & 0x0f] + hex;
+ value = Math.floor(value / 16);
+ }
+
+ if (hex.length) {
+ if (hex.length % 2) { hex = "0" + hex; }
+ return "0x" + hex;
+ }
+
+ return "0x00";
+ }
+
+ if (options.allowMissingPrefix && typeof(value) === "string" && value.substring(0, 2) !== "0x") {
+ value = "0x" + value;
+ }
+
+ if (isHexable(value)) { return value.toHexString(); }
+
+ if (isHexString(value)) {
+ if (!options.allowOddLength && (value).length % 2) {
+ errors.throwArgumentError("hex data is odd-length", "value", value);
+ }
+ return (value).toLowerCase();
+ }
+
+ if (isBytes(value)) {
+ let result = "0x";
+ for (let i = 0; i < value.length; i++) {
+ let v = value[i];
+ result += HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f];
+ }
+ return result;
+ }
+
+ return errors.throwArgumentError("invalid hexlify value", "value", value);
+}
+
+/*
+function unoddify(value: BytesLike | Hexable | number): BytesLike | Hexable | number {
+ if (typeof(value) === "string" && value.length % 2 && value.substring(0, 2) === "0x") {
+ return "0x0" + value.substring(2);
+ }
+ return value;
+}
+*/
+export function hexDataLength(data: BytesLike) {
+ if (typeof(data) !== "string") {
+ data = hexlify(data);
+ } else if (!isHexString(data) || (data.length % 2)) {
+ return null;
+ }
+
+ return (data.length - 2) / 2;
+}
+
+export function hexDataSlice(data: BytesLike, offset: number, endOffset?: number): string {
+ if (typeof(data) !== "string") {
+ data = hexlify(data);
+ } else if (!isHexString(data) || (data.length % 2)) {
+ errors.throwArgumentError("invalid hexData", "value", data );
+ }
+
+ offset = 2 + 2 * offset;
+
+ if (endOffset != null) {
+ return "0x" + data.substring(offset, 2 + 2 * endOffset);
+ }
+
+ return "0x" + data.substring(offset);
+}
+
+export function hexConcat(items: Array): string {
+ let result = "0x";
+ items.forEach((item) => {
+ result += hexlify(item).substring(2);
+ });
+ return result;
+}
+
+export function hexValue(value: BytesLike | Hexable | number): string {
+ let trimmed = hexStripZeros(hexlify(value, { allowOddLength: true }));
+ if (trimmed === "0x") { return "0x0"; }
+ return trimmed;
+}
+
+export function hexStripZeros(value: BytesLike): string {
+ if (typeof(value) !== "string") { value = hexlify(value); }
+
+ if (!isHexString(value)) {
+ errors.throwArgumentError("invalid hex string", "value", value);
+ }
+ value = value.substring(2);
+ let offset = 0;
+ while (offset < value.length && value[offset] === "0") { offset++; }
+ return "0x" + value.substring(offset);
+}
+
+export function hexZeroPad(value: BytesLike, length: number): string {
+ if (typeof(value) !== "string") {
+ value = hexlify(value);
+ } else if (!isHexString(value)) {
+ errors.throwArgumentError("invalid hex string", "value", value);
+ }
+
+ if (value.length > 2 * length + 2) {
+ errors.throwArgumentError("value out of range", "value", arguments[1]);
+ }
+
+ while (value.length < 2 * length + 2) {
+ value = "0x0" + value.substring(2);
+ }
+
+ return value;
+}
+
+export function splitSignature(signature: SignatureLike): Signature {
+ let result = {
+ r: "0x",
+ s: "0x",
+ _vs: "0x",
+ recoveryParam: 0,
+ v: 0
+ };
+
+ if (isBytesLike(signature)) {
+ let bytes: Uint8Array = arrayify(signature);
+ if (bytes.length !== 65) {
+ errors.throwArgumentError("invalid signature string; must be 65 bytes", "signature", signature);
+ }
+
+ // Get the r and s
+ result.r = hexlify(bytes.slice(0, 32));
+ result.s = hexlify(bytes.slice(32, 64));
+
+ // Reduce v to the canonical 27 or 28
+ result.v = bytes[64];
+ if (result.v !== 27 && result.v !== 28) {
+ result.v = 27 + (result.v % 2);
+ }
+
+ // Compute recoveryParam from v
+ result.recoveryParam = (result.v - 27);
+
+ // Compute _vs from recoveryParam and s
+ if (result.recoveryParam) { bytes[32] |= 0x80; }
+ result._vs = hexlify(bytes.slice(32, 64))
+
+ } else {
+ result.r = signature.r;
+ result.s = signature.s;
+ result.v = signature.v;
+ result.recoveryParam = signature.recoveryParam;
+ result._vs = signature._vs;
+
+ // Normalize v into a canonical 27 or 28
+ if (result.v != null && !(result.v == 27 || result.v == 28)) {
+ result.v = 27 + (result.v % 2);
+ }
+
+ // Populate a missing v or recoveryParam if possible
+ if (result.recoveryParam == null && result.v != null) {
+ result.recoveryParam = 1 - (result.v % 2);
+ } else if (result.recoveryParam != null && result.v == null) {
+ result.v = 27 + result.recoveryParam;
+ } else if (result.recoveryParam != null && result.v != null) {
+ if (result.v !== 27 + result.recoveryParam) {
+ errors.throwArgumentError("signature v mismatch recoveryParam", "signature", signature);
+ }
+ }
+
+ // Make sure r and s are padded properly
+ if (result.r != null) { result.r = hexZeroPad(result.r, 32); }
+ if (result.s != null) { result.s = hexZeroPad(result.s, 32); }
+
+ // If the _vs is available, use it to populate missing s, v and recoveryParam
+ // and verify non-missing s, v and recoveryParam
+ if (result._vs != null) {
+ result._vs = hexZeroPad(result._vs, 32);
+ if (result._vs.length > 66) {
+ errors.throwArgumentError("signature _vs overflow", "signature", signature);
+ }
+
+ let vs = arrayify(result._vs);
+
+ let recoveryParam = ((vs[0] >= 128) ? 1: 0);
+ let v = 27 + result.recoveryParam;
+
+ // Use _vs to compute s
+ vs[0] &= 0x7f;
+ let s = hexlify(vs);
+
+ // Check _vs aggress with other parameters
+
+ if (result.s == null) {
+ result.s = s;
+ } else if (result.s !== s) {
+ errors.throwArgumentError("signature v mismatch _vs", "signature", signature);
+ }
+
+ if (result.v == null) {
+ result.v = v;
+ } else if (result.v !== v) {
+ errors.throwArgumentError("signature v mismatch _vs", "signature", signature);
+ }
+
+ if (recoveryParam == null) {
+ result.recoveryParam = recoveryParam;
+ } else if (result.recoveryParam !== recoveryParam) {
+ errors.throwArgumentError("signature recoveryParam mismatch _vs", "signature", signature);
+ }
+ }
+
+ // After all populating, both v and recoveryParam are still missing...
+ if (result.v == null && result.recoveryParam == null) {
+ errors.throwArgumentError("signature requires at least one of recoveryParam, v or _vs", "signature", signature);
+ }
+
+ // Check for canonical v
+ if (result.v !== 27 && result.v !== 28) {
+ errors.throwArgumentError("signature v not canonical", "signature", signature);
+ }
+
+ // Check that r and s are in range
+ if (result.r.length > 66 || result.s.length > 66) {
+ errors.throwArgumentError("signature overflow r or s", "signature", signature);
+ }
+
+ if (result._vs == null) {
+ let vs = arrayify(result.s);
+ if (vs[0] >= 128) {
+ errors.throwArgumentError("signature s out of range", "signature", signature);
+ }
+ if (result.recoveryParam) { vs[0] |= 0x80; }
+ result._vs = hexlify(vs);
+ }
+ }
+
+ return result;
+}
+
+export function joinSignature(signature: Signature): string {
+ signature = splitSignature(signature);
+
+ return hexlify(concat([
+ signature.r,
+ signature.s,
+ (signature.recoveryParam ? "0x1c": "0x1b")
+ ]));
+}
+
diff --git a/packages/bytes/tsconfig.json b/packages/bytes/tsconfig.json
new file mode 100644
index 000000000..f8b22b29e
--- /dev/null
+++ b/packages/bytes/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "rootDir": "./src.ts",
+ "outDir": "./"
+ },
+ "include": [
+ "./src.ts/*"
+ ],
+ "exclude": [ ]
+}
+
diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore
new file mode 100644
index 000000000..13cc592f7
--- /dev/null
+++ b/packages/cli/.gitignore
@@ -0,0 +1 @@
+bin/*.ts
diff --git a/packages/cli/package.json b/packages/cli/package.json
new file mode 100644
index 000000000..1afee4cba
--- /dev/null
+++ b/packages/cli/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@ethersproject/cli",
+ "version": "5.0.0-beta.129",
+ "description": "Command-Line Interface scripts and releated utilities.",
+ "main": "index.js",
+ "scripts": {
+ "test": "exit 1"
+ },
+ "bin": {
+ "ethers": "./bin/ethers.js",
+ "ethers-ts": "./bin/ethers-ts.js"
+ },
+ "dependencies": {
+ "@types/node": "^10.3.2",
+ "ethers": ">5.0.0-beta.0",
+ "mime-types": "2.1.11",
+ "solc": "^0.5.5",
+ "solidity-parser-antlr": "^0.3.2"
+ },
+ "keywords": [
+ "Ethereum",
+ "ethers",
+ "cli"
+ ],
+ "author": "Richard Moore ",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "tarballHash": "0xf1056c4f8e2c3d6896510821ac3042b3e73bee3312c9c046518115c9fe9df45b"
+}
diff --git a/packages/cli/src.ts/_version.ts b/packages/cli/src.ts/_version.ts
new file mode 100644
index 000000000..cc0f13f68
--- /dev/null
+++ b/packages/cli/src.ts/_version.ts
@@ -0,0 +1 @@
+export const version = "5.0.0-beta.129";
diff --git a/packages/cli/src.ts/bin/ethers-ts.ts b/packages/cli/src.ts/bin/ethers-ts.ts
new file mode 100644
index 000000000..e36b256cd
--- /dev/null
+++ b/packages/cli/src.ts/bin/ethers-ts.ts
@@ -0,0 +1,126 @@
+#!/usr/bin/env node
+
+'use strict';
+
+import fs from 'fs';
+import { join as pathJoin } from "path";
+
+import { ethers } from 'ethers';
+
+import { ArgParser, CLI, Plugin } from '../cli';
+import { header as Header, generate as generateTypeScript } from "../typescript";
+import { compile, ContractCode } from "../solc";
+
+function computeHash(content: string): string {
+ let bareContent = content.replace(/\/\*\* Content Hash: 0x[0-9A-F]{64} \*\//i, '/** Content Hash: */');
+ return ethers.utils.id(bareContent);
+}
+
+function checkHash(content: string): boolean {
+ let match = content.match(/\/\*\* Content Hash: (0x[0-9A-F]{64}) \*\//i);
+ return (match && match[1] === computeHash(content));
+}
+
+function addContentHash(content: string): string {
+ let contentHash = computeHash("/** Content Hash: */\n" + content);
+ return "/** Content Hash: " + contentHash + " */\n" + content;
+}
+
+function save(path: string, content: string, force?: boolean): boolean {
+ if (fs.existsSync(path) && !force) {
+ let oldContent = fs.readFileSync(path).toString();
+ if (!checkHash(oldContent)) { return false; }
+ }
+ fs.writeFileSync(path, content);
+ return true;
+}
+
+function walkFilenames(filenames: Array): Array {
+ let result: Array = [];
+ filenames.forEach((filename) => {
+ let stat = fs.statSync(filename);
+ if (stat.isDirectory()) {
+ walkFilenames(fs.readdirSync(filename).map((x: string) => pathJoin(filename, x))).forEach((filename) => {
+ result.push(filename);
+ });
+ } else if (stat.isFile()) {
+ result.push(filename);
+ }
+ });
+ return result;
+}
+
+let cli = new CLI("generate");
+
+class GeneratePlugin extends Plugin {
+
+ filenames: Array;
+ output: string;
+ force: boolean;
+ optimize: boolean;
+ noBytecode: boolean;
+
+ async prepareOptions(argParser: ArgParser): Promise {
+ await super.prepareOptions(argParser);
+
+ this.output = argParser.consumeOption("output");
+ this.force = argParser.consumeFlag("force");
+ this.optimize = argParser.consumeFlag("no-optimize");
+ this.noBytecode = argParser.consumeFlag("no-bytecode");
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length === 0) {
+ this.throwError("generate requires at least one FILENAME");
+ }
+
+ this.filenames = args;
+ }
+
+
+ async run(): Promise {
+ let output = Header;
+
+ walkFilenames(this.filenames).forEach((filename) => {
+ if (!filename.match(/\.sol$/)) { return; }
+ let contracts: Array = null;
+ let content = fs.readFileSync(filename).toString();
+
+ try {
+ contracts = compile(content, { filename: filename, optimize: this.optimize });
+ } catch (error) {
+ console.log(error);
+ if ((error).errors) {
+ (error).errors.forEach((error: string) => {
+ console.log(error);
+ });
+ }
+
+ throw new Error("errors during compilation");
+ }
+
+ contracts.forEach((contract) => {
+ output += generateTypeScript(contract, (this.noBytecode ? null: contract.bytecode));
+ output += "\n";
+ });
+ });
+
+ output = addContentHash(output.trim());
+
+ if (this.output) {
+ let success = save(this.output, output, this.force);
+ if (!success) {
+ return Promise.reject(new Error("File has been modified; use --force"));
+ }
+ } else {
+ console.log(output);
+ }
+
+ return Promise.resolve(null);
+ }
+}
+cli.addPlugin("generate", GeneratePlugin);
+
+cli.run(process.argv.slice(2))
diff --git a/packages/cli/src.ts/bin/ethers.ts b/packages/cli/src.ts/bin/ethers.ts
new file mode 100644
index 000000000..9364d531a
--- /dev/null
+++ b/packages/cli/src.ts/bin/ethers.ts
@@ -0,0 +1,718 @@
+#!/usr/bin/env node
+
+"use strict";
+
+import fs from "fs";
+import REPL from "repl";
+import util from "util";
+import vm from "vm";
+
+import { ethers } from "ethers";
+
+import { ArgParser, CLI, dump, Help, Plugin } from "../cli";
+import { getPassword, getProgressBar } from "../prompt";
+import { compile } from "../solc";
+
+function setupContext(context: any, plugin: Plugin) {
+
+ context.provider = plugin.provider;
+ context.accounts = plugin.accounts;
+
+ if (!context.console) { context.console = console; }
+ if (!context.require) { context.require = require; }
+ if (!context.process) { context.process = process; }
+
+ context.ethers = ethers;
+ context.version = ethers.version;
+
+ context.Contract = ethers.Contract;
+ context.ContractFactory = ethers.ContractFactory;
+ context.Wallet = ethers.Wallet;
+
+ context.providers = ethers.providers;
+ context.utils = ethers.utils;
+
+ context.abiCoder = ethers.utils.defaultAbiCoder;
+
+ context.BN = ethers.BigNumber;
+ context.BigNumber = ethers.BigNumber;
+ context.FixedNumber = ethers.FixedNumber;
+
+ context.getAddress = ethers.utils.getAddress;
+ context.getContractAddress = ethers.utils.getContractAddress;
+ context.getIcapAddress = ethers.utils.getIcapAddress;
+
+ context.arrayify = ethers.utils.arrayify;
+ context.hexlify = ethers.utils.hexlify;
+
+ context.joinSignature = ethers.utils.joinSignature;
+ context.splitSignature = ethers.utils.splitSignature;
+
+ context.id = ethers.utils.id;
+ context.keccak256 = ethers.utils.keccak256;
+ context.namehash = ethers.utils.namehash;
+ context.sha256 = ethers.utils.sha256;
+
+ context.parseEther = ethers.utils.parseEther;
+ context.parseUnits = ethers.utils.parseUnits;
+ context.formatEther = ethers.utils.formatEther;
+ context.formatUnits = ethers.utils.formatUnits;
+
+ context.randomBytes = ethers.utils.randomBytes;
+ context.constants = ethers.constants;
+
+ context.parseTransaction = ethers.utils.parseTransaction;
+ context.serializeTransaction = ethers.utils.serializeTransaction;
+
+ context.toUtf8Bytes = ethers.utils.toUtf8Bytes;
+ context.toUtf8String = ethers.utils.toUtf8String;
+}
+
+
+const cli = new CLI("sandbox");
+
+class SandboxPlugin extends Plugin {
+ static getHelp(): Help {
+ return {
+ name: "sandbox",
+ help: "Run a REPL VM environment with ethers"
+ }
+ }
+
+ async prepareOptions(argParser: ArgParser): Promise {
+ await super.prepareOptions(argParser);
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 0) {
+ this.throwUsageError("Unexpected argument - " + JSON.stringify(args[0]));
+ }
+
+ for (let i = 0; i < this.accounts.length; i++) {
+ await this.accounts[i].unlock();
+ }
+ }
+
+ run(): Promise {
+ console.log("network: " + this.network.name + " (chainId: " + this.network.chainId + ")");
+
+ let nextPromiseId = 0;
+ function promiseWriter(output: any): string {
+ if (output instanceof Promise) {
+ repl.context._p = output;
+ let promiseId = nextPromiseId++;
+ output.then((result) => {
+ console.log(`\n`);
+ console.log(util.inspect(result));
+ repl.context._r = result;
+ repl.displayPrompt(true)
+ }, (error) => {
+ console.log(`\n`);
+ console.log(util.inspect(error));
+ repl.displayPrompt(true)
+ });
+ return ``;
+ }
+ return util.inspect(output);
+ }
+
+ let repl = REPL.start({
+ input: process.stdin,
+ output: process.stdout,
+ prompt: (this.provider ? this.network.name: "no-network") + "> ",
+ writer: promiseWriter
+ });
+ setupContext(repl.context, this);
+
+ return new Promise((resolve) => {
+ repl.on("exit", function() {
+ console.log("");
+ resolve(null);
+ });
+ });
+ }
+}
+cli.addPlugin("sandbox", SandboxPlugin);
+
+
+class InitPlugin extends Plugin {
+ filename: string;
+ force: boolean;
+
+ static getHelp(): Help {
+ return {
+ name: "init FILENAME",
+ help: "Create a new JSON wallet"
+ }
+ }
+
+ static getOptionHelp(): Array {
+ return [
+ {
+ name: "[ --force ]",
+ help: "Overwrite any existing files"
+ }
+ ];
+ }
+
+ async prepareOptions(argParser: ArgParser): Promise {
+ await super.prepareOptions(argParser);
+ this.force = argParser.consumeFlag("force");
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args)
+
+ if (args.length !== 1) {
+ this.throwUsageError("init requires FILENAME");
+ }
+
+ this.filename = args[0];
+ }
+
+ async run(): Promise {
+ if (!this.force && fs.existsSync(this.filename)) {
+ this.throwError('File already exists (use --force to overwrite)');
+ }
+
+ console.log("Creating a new JSON Wallet - " + this.filename);
+ console.log('Keep this password and file SAFE!! If lost or forgotten');
+ console.log('it CANNOT be recovered, by ANYone, EVER.');
+
+ let password = await getPassword("Choose a password: ");
+ let confirm = await getPassword("Confirm password: ");
+ if (password !== confirm) {
+ this.throwError("Passwords do not match");
+ }
+
+ let wallet = ethers.Wallet.createRandom();
+
+ let progressBar = await getProgressBar("Encrypting");
+ let json = await wallet.encrypt(password, { }, progressBar);
+
+ try {
+ if (this.force) {
+ fs.writeFileSync(this.filename, json);
+ } else {
+ fs.writeFileSync(this.filename, json, { flag: 'wx' });
+ }
+ console.log('New account address: ' + wallet.address);
+ console.log('Saved: ' + this.filename);
+ } catch (error) {
+ if (error.code === 'EEXIST') {
+ this.throwError('File already exists (use --force to overwrite)');
+ }
+ this.throwError('Unknown Error: ' + error.message);
+ }
+ }
+}
+cli.addPlugin("init", InitPlugin);
+
+
+class FundPlugin extends Plugin {
+ toAddress: string;
+
+ static getHelp(): Help {
+ return {
+ name: "fund TARGET",
+ help: "Fund TARGET with testnet ether"
+ }
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (this.network.name !== "ropsten") {
+ this.throwError("Funding requires --network ropsten");
+ }
+
+ if (args.length !== 1) {
+ this.throwUsageError("fund requires ADDRESS");
+ }
+
+ this.toAddress = await this.getAddress(args[0], "Cannot fund ZERO address", false);
+ }
+
+ async run(): Promise {
+ let url = "https:/" + "/api.ethers.io/api/v1/?action=fundAccount&address=" + this.toAddress.toLowerCase();
+ return ethers.utils.fetchJson(url).then((data) => {
+ console.log("Transaction Hash: " + data.hash);
+ });
+ }
+}
+cli.addPlugin("fund", FundPlugin);
+
+
+class InfoPlugin extends Plugin {
+ queries: Array;
+ addresses: Array;
+
+ static getHelp(): Help {
+ return {
+ name: "info [ TARGET ... ]",
+ help: "Dump info for accounts, addresses and ENS names"
+ }
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ this.queries = [ ];
+ let runners: Array> = [];
+
+ this.accounts.forEach((account, index) => {
+ this.queries.push(`Account #${index}`);
+ runners.push(account.getAddress());
+ });
+
+ args.forEach((arg) => {
+ if (ethers.utils.isAddress(arg)) {
+ this.queries.push(`Address: ${arg}`);
+ } else {
+ this.queries.push(`ENS Name: ${arg}`);
+ }
+ runners.push(this.provider.resolveName(arg));
+ })
+
+ this.addresses = await Promise.all(runners);
+ }
+
+ async run(): Promise {
+ for (let i = 0; i < this.addresses.length; i++) {
+ let address = this.addresses[i];
+ let { balance, nonce, code, reverse } = await ethers.utils.resolveProperties({
+ balance: this.provider.getBalance(address),
+ nonce: this.provider.getTransactionCount(address),
+ code: this.provider.getCode(address),
+ reverse: this.provider.lookupAddress(address)
+ });
+
+ let info: any = {
+ "Address": address,
+ "Balance": (ethers.utils.formatEther(balance) + " ether"),
+ "Transaction Count": nonce
+ }
+
+ if (code != "0x") {
+ info["Code"] = code;
+ }
+
+ if (reverse) {
+ info["Reverse Lookup"] = reverse;
+ }
+
+ dump(this.queries[i], info);
+ }
+ }
+}
+cli.addPlugin("info", InfoPlugin);
+
+
+class SendPlugin extends Plugin {
+ toAddress: string;
+ value: ethers.BigNumber;
+ allowZero: boolean;
+
+ static getHelp(): Help {
+ return {
+ name: "send TARGET ETHER",
+ help: "Send ETHER ether to TARGET form accounts[0]"
+ }
+ }
+
+ static getOptionHelp(): Array {
+ return [
+ {
+ name: "[ --allow-zero ]",
+ help: "Allow sending to the address zero"
+ }
+ ];
+ }
+
+ async prepareOptions(argParser: ArgParser): Promise {
+ await super.prepareOptions(argParser);
+
+ if (this.accounts.length !== 1) {
+ this.throwUsageError("send requires exacly one account");
+ }
+
+ this.allowZero = argParser.consumeFlag("allow-zero");
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 2) {
+ this.throwUsageError("send requires exactly ADDRESS and AMOUNT");
+ }
+
+ this.toAddress = await this.getAddress(args[0], "Cannot send to the zero address (use --allow-zero to override)", this.allowZero);
+ this.value = ethers.utils.parseEther(args[1]);
+ }
+
+ async run(): Promise {
+ await this.accounts[0].sendTransaction({
+ to: this.toAddress,
+ value: this.value
+ });;
+ }
+}
+cli.addPlugin("send", SendPlugin);
+
+
+class SweepPlugin extends Plugin {
+ toAddress: string;
+
+ static getHelp(): Help {
+ return {
+ name: "sweep TARGET",
+ help: "Send all ether from accounts[0] to TARGET"
+ }
+ }
+
+ async prepareOptions(argParser: ArgParser): Promise {
+ await super.prepareOptions(argParser);
+
+ if (this.accounts.length !== 1) {
+ this.throwUsageError("sweep requires exacly one account");
+ }
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 1) {
+ this.throwUsageError("sweep requires exactly ADDRESS");
+ }
+
+ this.toAddress = await this.getAddress(args[0]);;
+ }
+
+ async run(): Promise {
+
+ let { balance, gasPrice, code } = await ethers.utils.resolveProperties({
+ balance: this.provider.getBalance(this.accounts[0].getAddress()),
+ gasPrice: (this.gasPrice || this.provider.getGasPrice()),
+ code: this.provider.getCode(this.toAddress)
+ });
+
+ if (code !== "0x") {
+ this.throwError("Cannot sweep to a contract address");
+ }
+
+ let maxSpendable = balance.sub(gasPrice.mul(21000));
+ if (maxSpendable.lte(0)) {
+ this.throwError("Insufficient funds to sweep");
+ }
+
+ await this.accounts[0].sendTransaction({
+ to: this.toAddress,
+ gasLimit: 21000,
+ gasPrice: gasPrice,
+ value: maxSpendable
+ });
+ }
+}
+cli.addPlugin("sweep", SweepPlugin);
+
+
+class SignMessagePlugin extends Plugin {
+ message: string;
+ hex: boolean;
+
+ static getHelp(): Help {
+ return {
+ name: "sign-message MESSAGE",
+ help: "Sign a MESSAGE with accounts[0]"
+ }
+ }
+
+ static getOptionHelp(): Array {
+ return [
+ {
+ name: "[ --hex ]",
+ help: "The message content is hex encoded"
+ }
+ ];
+ }
+
+ async prepareOptions(argParser: ArgParser): Promise {
+ await super.prepareOptions(argParser);
+ if (this.accounts.length !== 1) {
+ this.throwError("sign-message requires exacly one account");
+ }
+ this.hex = argParser.consumeFlag("hex");
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 1) {
+ this.throwError("send requires exactly MESSAGE");
+ }
+
+ this.message = args[0];
+ }
+
+ async run(): Promise {
+ await this.accounts[0].signMessage(this.message);
+ }
+}
+cli.addPlugin("sign-message", SignMessagePlugin);
+
+
+class EvalPlugin extends Plugin {
+ code: string;
+
+ static getHelp(): Help {
+ return {
+ name: "eval CODE",
+ help: "Run CODE in a VM with ethers"
+ }
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 1) {
+ this.throwError("eval requires exactly CODE");
+ }
+
+ this.code = args[0];
+ }
+
+ async run(): Promise {
+ let contextObject = { };
+ setupContext(contextObject, this);
+
+ let context = vm.createContext(contextObject);
+ let script = new vm.Script(this.code, { filename: "-" });
+
+ let result = script.runInContext(context);
+ if (result instanceof Promise) {
+ result = await result;
+ }
+
+ console.log(result);
+ }
+}
+cli.addPlugin("eval", EvalPlugin);
+
+
+class RunPlugin extends Plugin {
+ filename: string;
+
+ static getHelp(): Help {
+ return {
+ name: "run FILENAME",
+ help: "Run FILENAME in a VM with ethers"
+ }
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 1) {
+ this.throwError("run requires exactly FILENAME");
+ }
+
+ this.filename = args[0];
+ }
+
+ async run(): Promise {
+ let contextObject = { };
+ setupContext(contextObject, this);
+
+ let context = vm.createContext(contextObject);
+ let script = new vm.Script(fs.readFileSync(this.filename).toString(), { filename: this.filename });
+
+ let result = script.runInContext(context);
+ if (result instanceof Promise) {
+ result = await result;
+ }
+
+ console.log(result);
+ }
+}
+cli.addPlugin("run", RunPlugin);
+
+
+class WaitPlugin extends Plugin {
+ hash: string;
+
+ static getHelp(): Help {
+ return {
+ name: "wait HASH",
+ help: "Wait for a transaction HASH to be mined"
+ }
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 1) {
+ this.throwError("wait requires exactly HASH");
+ }
+
+ this.hash = args[0];
+ }
+
+ async run(): Promise {
+ console.log("Waiting for Transaction:", this.hash);
+
+ let receipt = await this.provider.waitForTransaction(this.hash);
+ dump("Response:", {
+ "Block": receipt.blockNumber,
+ "Block Hash": receipt.blockHash,
+ "Status": (receipt.status ? "ok": "failed")
+ });
+ }
+}
+cli.addPlugin("wait", WaitPlugin);
+
+
+class CompilePlugin extends Plugin {
+ filename: string;
+ noOptimize: boolean;
+ warnings: boolean;
+
+ static getHelp(): Help {
+ return {
+ name: "compile FILENAME",
+ help: "Compiles a Solidity contract"
+ }
+ }
+
+ static getOptionHelp(): Array {
+ return [
+ {
+ name: "[ --no-optimize ]",
+ help: "Do not optimize the compiled output"
+ },
+ {
+ name: "[ --warnings ]",
+ help: "Error on any warning"
+ }
+ ];
+ }
+
+ async prepareOptions(argParser: ArgParser): Promise {
+ await super.prepareOptions(argParser);
+
+ this.noOptimize = argParser.consumeFlag("no-optimize");
+ this.warnings = argParser.consumeFlag("warnings");
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 1) {
+ this.throwError("compile requires exactly FILENAME");
+ }
+
+ this.filename = args[0];
+ }
+
+ async run(): Promise {
+ let source = fs.readFileSync(this.filename).toString();
+ let result = compile(source, {
+ filename: this.filename,
+ optimize: (!this.noOptimize)
+ });
+
+ let output: any = { };
+ result.forEach((contract, index) => {
+ output[contract.name] = {
+ bytecode: contract.bytecode,
+ runtime: contract.runtime,
+ interface: contract.interface.fragments.map((f) => f.format(true))
+ };
+ });
+
+ console.log(JSON.stringify(output, null, 4));
+ }
+}
+cli.addPlugin("compile", CompilePlugin);
+
+
+class DeployPlugin extends Plugin {
+ filename: string;
+ contractName: string;
+ noOptimize: boolean;
+
+ static getHelp(): Help {
+ return {
+ name: "deploy FILENAME",
+ help: "Compile and deploy a Solidity contract"
+ }
+ }
+
+ static getOptionHelp(): Array {
+ return [
+ {
+ name: "[ --no-optimize ]",
+ help: "Do not optimize the compiled output"
+ },
+ {
+ name: "[ --contract NAME ]",
+ help: "Specify the contract to deploy"
+ }
+ ];
+ }
+
+ async prepareOptions(argParser: ArgParser): Promise {
+ await super.prepareOptions(argParser);
+
+ if (this.accounts.length !== 1) {
+ this.throwError("deploy requires exactly one account");
+ }
+
+ this.noOptimize = argParser.consumeFlag("no-optimize");
+ this.contractName = argParser.consumeOption("contract");
+ }
+
+ async prepareArgs(args: Array): Promise {
+ await super.prepareArgs(args);
+
+ if (args.length !== 1) {
+ this.throwError("deploy requires exactly FILENAME");
+ }
+
+ this.filename = args[0];
+ }
+
+ async run(): Promise {
+ let source = fs.readFileSync(this.filename).toString();
+ let result = compile(source, {
+ filename: this.filename,
+ optimize: (!this.noOptimize)
+ });
+
+ let codes = result.filter((c) => (c.bytecode !== "0x" && (this.contractName == null || this.contractName == c.name)));
+
+ if (codes.length > 1) {
+ this.throwError("Please specify a contract with --contract NAME");
+ }
+
+ if (codes.length === 0) {
+ this.throwError("No contract found");
+ }
+
+ let factory = new ethers.ContractFactory(codes[0].interface, codes[0].bytecode, this.accounts[0]);
+
+ let contract = await factory.deploy();
+
+ dump("Deployed:", {
+ Contract: codes[0].name,
+ Address: contract.address,
+ Bytecode: codes[0].bytecode,
+ Interface: codes[0].interface.fragments.map((f) => f.format(true))
+ });
+ }
+}
+cli.addPlugin("deploy", DeployPlugin);
+
+
+cli.run(process.argv.slice(2));
diff --git a/packages/cli/src.ts/cli.ts b/packages/cli/src.ts/cli.ts
new file mode 100644
index 000000000..46a78e4e4
--- /dev/null
+++ b/packages/cli/src.ts/cli.ts
@@ -0,0 +1,754 @@
+"use strict";
+
+import fs from "fs";
+
+import { ethers } from "ethers";
+
+import { getChoice, getPassword, getProgressBar } from "./prompt";
+
+class UsageError extends Error { }
+
+
+/////////////////////////////
+// Signer
+
+const signerFuncs = new WeakMap();
+const signers = new WeakMap();
+const alwaysAllow = new WeakMap();
+
+// Gets a signer or lazily request it if needed, possibly asking for a password
+// to decrypt a JSON wallet
+async function getSigner(wrapper: WrappedSigner): Promise