Initial v5 branch commit.

This commit is contained in:
Richard Moore 2019-05-14 18:25:46 -04:00
parent d06e7d0fd6
commit 2777f5cb4c
No known key found for this signature in database
GPG Key ID: 525F70A6FCABC295
416 changed files with 65697 additions and 1 deletions

15
.gitignore vendored Normal file

@ -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

7
CHANGELOG.md Normal file

@ -0,0 +1,7 @@
Changelog
=========
This change log is managed by scripts/index.js but may
be manually updated.
Coming Soon...

21
LICENSE.md Normal file

@ -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.

@ -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**
```
<script src="@TODO" type="text/javasctipt">
</script>
```
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).

80
admin/README.md Normal file

@ -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**

61
admin/build.js Normal file

@ -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
};

101
admin/changelog.js Normal file

@ -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
}

92
admin/cmds/publish.js Normal file

@ -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("<bold:Wrong password>");
break;
case "cancelled":
break;
default:
console.log(error);
}
log("<red:Aborting.>");
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(`<bold:Publishing:> ${info.name}...`);
log(` <blue:Version:> ${npmInfo.version} <bold:=\\>> ${info.version}`);
let success = await publish(dirname, options);
if (!success) {
log(" <red:FAILED! Aborting.>");
return;
}
log(" <green:Done.>");
}
})();

@ -0,0 +1,16 @@
"use stricT";
const depgraph = require("../depgraph");
const { log } = require("../log");
const { loadJson, resolve, saveJson } = require("../utils");
(async function() {
log(`<bold:Updating dependency-graph build order (tsconfig.project.json)...>`);
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);
})();

@ -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("<bold:Building TypeScript source...>");
await runBuild();
log("<bold:Building distribution files...>");
let content = await runDist();
console.log(content);
} catch (error) {
console.log(error);
log("<red:Aborting.>");
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(`<bold:Package>: ${info.name}`);
log(` <green:Tarball Changed:> (bumping version)`);
log(` ${npmInfo.version} => ${info.version}`)
log(` <blue:Changed:>`);
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(` <bold:${short}>`);
} else {
log(` ${short}`);
}
});
log("");
}
})();

87
admin/config.js Normal file

@ -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
}

94
admin/depgraph.js Normal file

@ -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<name: string> }
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
}

173
admin/git.js Normal file

@ -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,
}

401
admin/index.js Normal file

@ -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("<bold:Package>: ") + info.name);
console.log(colorify(" <green:Git Head Changed:> (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(`<bold:Updating dependency-graph build order (tsconfig.project.json)...>`);
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("<bold:Untracked Files:>");
untracked.forEach((filename) => {
console.log(" " + filename);
});
log("<red:Aborting.>");
return;
}
log(`<bold:Run TypeScript build...>`);
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("<bold:Runing TypeScript build...>");
try {
await build.runTsc();
} catch (error) {
console.log(error);
log("<red:Aborting.>");
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(`<bold:Package>: ${info.name}`);
log(` <green:Tarball Changed:> (bumping version)`);
log(` ${npmInfo.version} => ${info.version}`)
log(` <blue:Changed:>`);
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(` <bold:${short}>`);
} else {
log(` ${short}`);
}
});
log("");
}
// @TODO: Changelog
await updateChangelog();
}
async function runAdd(type, names) {
let latest = await git.getLatestTag();
console.log("");
console.log(colorify("<bold:Latest Published>: ") + 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("<bold:Nothing to add.>"));
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("<bold:Publishing:> ") + 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(" <red:FAILED! Aborting.>"));
throw new Error("");
}
console.log(colorify(" <green:Done.>"));
});
}
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();
}
})();

80
admin/local.js Normal file

@ -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,
}

53
admin/log.js Normal file

@ -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("<blue:" + status + ">");
case "added": return colorify("<green:" + status + ">");
case "deleted": return colorify("<red:" + status + ">");
case "unmodified": return colorify("<magenta:" + status + ">");
}
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
}

104
admin/npm.js Normal file

@ -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,
};

47
admin/utils.js Normal file

@ -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
}

6
lerna.json Normal file

@ -0,0 +1,6 @@
{
"packages": [
"packages/*"
],
"version": "independent"
}

35
package.json Normal file

@ -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"
}
}

2
packages/abi/.npmignore Normal file

@ -0,0 +1,2 @@
tsconfig.json
src.ts/

21
packages/abi/LICENSE.md Normal file

@ -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.

17
packages/abi/README.md Normal file

@ -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

30
packages/abi/package.json Normal file

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0xd6298eed7cb50f08154591d43a324a68220ce97b92ab6f44d2015437688def09"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.128";

@ -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<string | ParamType>, values: Array<any>): 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<string | ParamType>, data: BytesLike): any {
let coders: Array<Coder> = 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();

@ -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));
}
}

@ -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));
}
}

@ -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);
}
}

@ -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<Coder>, values: Array<any>): number {
if (Array.isArray(values)) {
// do nothing
} else if (values && typeof(values) === "object") {
let arrayValues: Array<any> = [];
coders.forEach(function(coder) {
arrayValues.push((<any>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<Coder>): Array<any> {
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<any>): 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));
}
}

@ -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());
}
}

@ -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)));
}
}

@ -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)));
}
}

@ -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);
}
}

@ -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);
}
}

@ -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));
}
}

@ -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<Coder>;
constructor(coders: Array<Coder>, localName: string) {
let dynamic = false;
let types: Array<string> = [];
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<any>): number {
return pack(writer, this.coders, value);
}
decode(reader: Reader): any {
return reader.coerce(this.name, unpack(reader, this.coders));
}
}

@ -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<JsonFragmentType>;
}
export interface JsonFragment {
name?: string;
type?: string;
anonymous?: boolean;
payable?: boolean;
constant?: boolean;
stateMutability?: string;
inputs?: Array<JsonFragmentType>;
outputs?: Array<JsonFragmentType>;
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<ParseNode>
};
// @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<ParamType>;
// 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>(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<ParamType> {
return splitNesting(value).map((param) => ParamType.fromString(param, allowIndex));
}
export abstract class Fragment {
readonly type: string;
readonly name: string;
readonly inputs: Array<ParamType>;
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 ((<any>this).mutabilityState) {
result += (<any>this).mutabilityState + " ";
} else if ((<any>this).constant) {
result += "view ";
}
if ((<any>this).outputs && (<any>this).outputs.length) {
result += "(" + (<any>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>(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>(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>(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<ParamType>;
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>(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<any> {
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;
}

@ -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
};

@ -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<any>;
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<Fragment>;
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<Fragment | JsonFragment | string>) {
errors.checkNew(new.target, Interface);
let abi: Array<Fragment | JsonFragment | string> = [ ];
if (typeof(fragments) === "string") {
abi = JSON.parse(fragments);
} else {
abi = fragments;
}
defineReadOnly(this, "fragments", abi.map((fragment) => {
if (isNamedInstance<Fragment>(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 <FunctionFragment>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 <EventFragment>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<ParamType>, values: Array<any>): string {
return this._abiCoder.encode(params, values)
}
encodeDeploy(values?: Array<any>): string {
return this._encodeParams(this.deploy.inputs, values || [ ]);
}
encodeFunctionData(functionFragment: FunctionFragment | string, values?: Array<any>): 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<any> {
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<any>): Array<string | Array<string>> {
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<string> = [];
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<string>): Array<any> {
if (typeof(eventFragment) === "string") {
eventFragment = this.getEvent(eventFragment);
}
if (topics != null && !eventFragment.anonymous) { topics = topics.slice(1); }
let indexed: Array<ParamType> = [];
let nonIndexed: Array<ParamType> = [];
let dynamic: Array<boolean> = [];
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<any> = [ ];
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<string>, 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<Fragment | string | JsonAbi> | 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;
}

@ -0,0 +1,4 @@
let { defaultAbiCoder } = require(".");
console.log(defaultAbiCoder);
console.log(defaultAbiCoder.encode([ "uint256", "bytes" ], [ 42, "0x1234" ]));

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./"
},
"include": [
"./src.ts/*.ts",
"./src.ts/coders/*.ts"
],
"exclude": [ ]
}

@ -0,0 +1,2 @@
tsconfig.json
src.ts/

@ -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.

@ -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

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0x4424dfecaacd3e4d135d01136d92bcee22bb7a5d0ce5d2c5272bd02422135db7"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.126";

@ -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<string>,
from?: string | Promise<string>,
nonce?: BigNumberish | Promise<BigNumberish>,
gasLimit?: BigNumberish | Promise<BigNumberish>,
gasPrice?: BigNumberish | Promise<BigNumberish>,
data?: BytesLike | Promise<BytesLike>,
value?: BigNumberish | Promise<BigNumberish>,
chainId?: number | Promise<number>,
}
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<TransactionReceipt>
};
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<string>;
}
export interface BlockWithTransactions extends _Block {
transactions: Array<TransactionResponse>;
}
export interface Log {
blockNumber?: number;
blockHash?: string;
transactionIndex?: number;
removed?: boolean;
transactionLogIndex?: number,
address: string;
data: string;
topics: Array<string>;
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<Log>,
blockNumber?: number,
confirmations?: number,
cumulativeGasUsed?: BigNumber,
byzantium: boolean,
status?: number
};
export interface EventFilter {
address?: string;
topics?: Array<string | Array<string>>;
}
export interface Filter extends EventFilter {
fromBlock?: BlockTag,
toBlock?: BlockTag,
}
export interface FilterByBlockHash extends EventFilter {
blockhash?: string;
}
//export type CallTransactionable = {
// call(transaction: TransactionRequest): Promise<TransactionResponse>;
//};
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<string | Array<string>> | EventFilter | ForkEvent;
export type Listener = (...args: Array<any>) => void;
///////////////////////////////
// Exported Abstracts
export abstract class Provider implements OnceBlockable {
// Network
abstract getNetwork(): Promise<Network>;
// Latest State
abstract getBlockNumber(): Promise<number>;
abstract getGasPrice(): Promise<BigNumber>;
// Account
abstract getBalance(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<BigNumber>;
abstract getTransactionCount(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<number>;
abstract getCode(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> ;
abstract getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
// Execution
abstract sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse>;
abstract call(transaction: TransactionRequest, blockTag?: BlockTag | Promise<BlockTag>): Promise<string>;
abstract estimateGas(transaction: TransactionRequest): Promise<BigNumber>;
// Queries
abstract getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>): Promise<Block>;
abstract getBlockWithTransactions(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>): Promise<BlockWithTransactions>;
abstract getTransaction(transactionHash: string): Promise<TransactionResponse>;
abstract getTransactionReceipt(transactionHash: string): Promise<TransactionReceipt>;
// Bloom-filter Queries
abstract getLogs(filter: Filter): Promise<Array<Log>>;
// ENS
abstract resolveName(name: string | Promise<string>): Promise<string>;
abstract lookupAddress(address: string | Promise<string>): Promise<string>;
// Event Emitter (ish)
abstract on(eventName: EventType, listener: Listener): Provider;
abstract once(eventName: EventType, listener: Listener): Provider;
abstract emit(eventName: EventType, ...args: Array<any>): boolean
abstract listenerCount(eventName?: EventType): number;
abstract listeners(eventName?: EventType): Array<Listener>;
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<TransactionReceipt>;
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);
});
})
}
*/
}

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./"
},
"include": [
"./src.ts/*"
],
"exclude": [ ]
}

@ -0,0 +1,2 @@
tsconfig.json
src.ts/

@ -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.

@ -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

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0x3bc5ce92bedb99f4c15e3c399e4367eaa001bb72bf25a82f52e09c74f09f492f"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.126";

@ -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<string> = [
"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<string>
// 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<string>;
// 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<string>;
// 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<BigNumber> {
this._checkProvider("getBalance");
return this.provider.getBalance(this.getAddress(), blockTag);
}
getTransactionCount(blockTag?: BlockTag): Promise<number> {
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<BigNumber> {
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<string> {
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<TransactionResponse> {
this._checkProvider("sendTransaction");
return this.populateTransaction(transaction).then((tx) => {
return this.signTransaction(tx).then((signedTx) => {
return this.provider.sendTransaction(signedTx);
});
});
}
getChainId(): Promise<number> {
this._checkProvider("getChainId");
return this.provider.getNetwork().then((network) => network.chainId);
}
getGasPrice(): Promise<BigNumber> {
this._checkProvider("getGasPrice");
return this.provider.getGasPrice();
}
resolveName(name: string): Promise<string> {
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<TransactionRequest> {
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<string> {
return Promise.resolve(this.address);
}
_fail(message: string, operation: string): Promise<any> {
return Promise.resolve().then(() => {
errors.throwError(message, errors.UNSUPPORTED_OPERATION, { operation: operation });
});
}
signMessage(message: Bytes | string): Promise<string> {
return this._fail("VoidSigner cannot sign messages", "signMessage");
}
signTransaction(transaction: TransactionRequest): Promise<string> {
return this._fail("VoidSigner cannot sign transactions", "signTransaction");
}
connect(provider: Provider): VoidSigner {
return new VoidSigner(this.address, provider);
}
}

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./"
},
"include": [
"./src.ts/*"
],
"exclude": [ ]
}

@ -0,0 +1,2 @@
tsconfig.json
src.ts/

@ -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.

@ -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

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0x962ac58ce384cd1b220fd2d7fbd66e914f4c55f38440d75afd12ba560cffc7f1"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.125";

@ -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));
}

30
packages/address/thirdparty.d.ts vendored Normal file

@ -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;
}
}

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./"
},
"include": [
"./thirdparty.d.ts",
"./src.ts/*"
],
"exclude": [ ]
}

1
packages/base64/.gitignore vendored Normal file

@ -0,0 +1 @@
browser.d.ts

@ -0,0 +1,2 @@
tsconfig.json
src.ts/

@ -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.

36
packages/base64/README.md Normal file

@ -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

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0xe5da4b9df9188216fed902efb45ee9af1cdb9a47c1212d9a674a93c5adb716bd"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.124";

@ -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);
}

@ -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");
}

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./"
},
"include": [
"./src.ts/*"
],
"exclude": [ ]
}

@ -0,0 +1,2 @@
tsconfig.json
src.ts/

21
packages/basex/LICENSE.md Normal file

@ -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.

5
packages/basex/README.md Normal file

@ -0,0 +1,5 @@
Base X
======
**EXPERIMENTAL**

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0x8243e502fd9c51780351423f21ff95978c66005a9a9691cabd98e805ca69fe40"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.124";

@ -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<number> = [];
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")))

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./"
},
"include": [
"./src.ts/*"
],
"exclude": [ ]
}

@ -0,0 +1,2 @@
tsconfig.json
src.ts/

@ -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.

@ -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

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0xbb5426f664ba919d5668ddde9f13255ef77278a2e556f4d64f9be1089f5947c0"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.126";

@ -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) ||
(!!((<any>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((<any>value).toString());
}
if (isBytes(value)) {
return BigNumber.from(hexlify(value));
}
if ((<any>value)._hex && isHexString((<any>value)._hex)) {
return BigNumber.from((<any>value)._hex);
}
if ((<any>value).toHexString) {
value = (<any>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<BigNumber>(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);
}

@ -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<FixedNumber>(this, value);
}
}

@ -0,0 +1,2 @@
export { BigNumber, BigNumberish } from "./bignumber";
export { FixedNumber } from "./fixednumber";

30
packages/bignumber/thirdparty.d.ts vendored Normal file

@ -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;
}
}

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./"
},
"include": [
"./src.ts/*.ts",
"./thirdparty.d.ts"
],
"exclude": [ ]
}

@ -0,0 +1,2 @@
tsconfig.json
src.ts/

21
packages/bytes/LICENSE.md Normal file

@ -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.

17
packages/bytes/README.md Normal file

@ -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

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0xb1a6e6e8b38d5c28de285c858a6a7fe0a505a0de9cb2baa0d07a7c0e3af167d1"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.126";

@ -0,0 +1,445 @@
"use strict";
import * as errors from "@ethersproject/errors";
///////////////////////////////
// Exported Types
export type Bytes = ArrayLike<number>;
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 = (<string>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<BytesLike>): 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 && (<string>value).length % 2) {
errors.throwArgumentError("hex data is odd-length", "value", value);
}
return (<string>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<BytesLike>): 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")
]));
}

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.package.json",
"compilerOptions": {
"rootDir": "./src.ts",
"outDir": "./"
},
"include": [
"./src.ts/*"
],
"exclude": [ ]
}

1
packages/cli/.gitignore vendored Normal file

@ -0,0 +1 @@
bin/*.ts

31
packages/cli/package.json Normal file

@ -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 <me@ricmoo.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"tarballHash": "0xf1056c4f8e2c3d6896510821ac3042b3e73bee3312c9c046518115c9fe9df45b"
}

@ -0,0 +1 @@
export const version = "5.0.0-beta.129";

Some files were not shown because too many files have changed in this diff Show More