Initial v5 branch commit.
This commit is contained in:
parent
d06e7d0fd6
commit
2777f5cb4c
15
.gitignore
vendored
Normal file
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
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
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.
|
86
README.md
86
README.md
@ -1 +1,85 @@
|
||||
Coming soon...
|
||||
The Ethers Project
|
||||
==================
|
||||
|
||||
**EXPERIMENTAL!!!**
|
||||
|
||||
This is just a development version to experiment with lerna.
|
||||
|
||||
**Do NOT use**
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
**node.js**
|
||||
|
||||
```
|
||||
/home/ricmoo/some_project> npm install --save ethers
|
||||
```
|
||||
|
||||
**browser**
|
||||
|
||||
```
|
||||
<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
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
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
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
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.>");
|
||||
}
|
||||
|
||||
})();
|
16
admin/cmds/update-depgraph.js
Normal file
16
admin/cmds/update-depgraph.js
Normal file
@ -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);
|
||||
})();
|
131
admin/cmds/update-versions.js
Normal file
131
admin/cmds/update-versions.js
Normal file
@ -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
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
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
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
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
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
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
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
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
6
lerna.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent"
|
||||
}
|
35
package.json
Normal file
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
2
packages/abi/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
tsconfig.json
|
||||
src.ts/
|
21
packages/abi/LICENSE.md
Normal file
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
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
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"
|
||||
}
|
1
packages/abi/src.ts/_version.ts
Normal file
1
packages/abi/src.ts/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export const version = "5.0.0-beta.128";
|
124
packages/abi/src.ts/abi-coder.ts
Normal file
124
packages/abi/src.ts/abi-coder.ts
Normal file
@ -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();
|
||||
|
161
packages/abi/src.ts/coders/abstract-coder.ts
Normal file
161
packages/abi/src.ts/coders/abstract-coder.ts
Normal file
@ -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));
|
||||
}
|
||||
}
|
27
packages/abi/src.ts/coders/address.ts
Normal file
27
packages/abi/src.ts/coders/address.ts
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
|
21
packages/abi/src.ts/coders/anonymous.ts
Normal file
21
packages/abi/src.ts/coders/anonymous.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
159
packages/abi/src.ts/coders/array.ts
Normal file
159
packages/abi/src.ts/coders/array.ts
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
|
19
packages/abi/src.ts/coders/boolean.ts
Normal file
19
packages/abi/src.ts/coders/boolean.ts
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
|
34
packages/abi/src.ts/coders/bytes.ts
Normal file
34
packages/abi/src.ts/coders/bytes.ts
Normal file
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
26
packages/abi/src.ts/coders/fixed-bytes.ts
Normal file
26
packages/abi/src.ts/coders/fixed-bytes.ts
Normal file
@ -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)));
|
||||
}
|
||||
}
|
20
packages/abi/src.ts/coders/null.ts
Normal file
20
packages/abi/src.ts/coders/null.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
53
packages/abi/src.ts/coders/number.ts
Normal file
53
packages/abi/src.ts/coders/number.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
21
packages/abi/src.ts/coders/string.ts
Normal file
21
packages/abi/src.ts/coders/string.ts
Normal file
@ -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));
|
||||
}
|
||||
}
|
30
packages/abi/src.ts/coders/tuple.ts
Normal file
30
packages/abi/src.ts/coders/tuple.ts
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
|
676
packages/abi/src.ts/fragments.ts
Normal file
676
packages/abi/src.ts/fragments.ts
Normal file
@ -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;
|
||||
}
|
||||
|
26
packages/abi/src.ts/index.ts
Normal file
26
packages/abi/src.ts/index.ts
Normal file
@ -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
|
||||
};
|
406
packages/abi/src.ts/interface.ts
Normal file
406
packages/abi/src.ts/interface.ts
Normal file
@ -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;
|
||||
}
|
4
packages/abi/test-coder.js
Normal file
4
packages/abi/test-coder.js
Normal file
@ -0,0 +1,4 @@
|
||||
let { defaultAbiCoder } = require(".");
|
||||
|
||||
console.log(defaultAbiCoder);
|
||||
console.log(defaultAbiCoder.encode([ "uint256", "bytes" ], [ 42, "0x1234" ]));
|
13
packages/abi/tsconfig.json
Normal file
13
packages/abi/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.package.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src.ts",
|
||||
"outDir": "./"
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/*.ts",
|
||||
"./src.ts/coders/*.ts"
|
||||
],
|
||||
"exclude": [ ]
|
||||
}
|
||||
|
2
packages/abstract-provider/.npmignore
Normal file
2
packages/abstract-provider/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
tsconfig.json
|
||||
src.ts/
|
21
packages/abstract-provider/LICENSE.md
Normal file
21
packages/abstract-provider/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/abstract-provider/README.md
Normal file
17
packages/abstract-provider/README.md
Normal file
@ -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
|
32
packages/abstract-provider/package.json
Normal file
32
packages/abstract-provider/package.json
Normal file
@ -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"
|
||||
}
|
1
packages/abstract-provider/src.ts/_version.ts
Normal file
1
packages/abstract-provider/src.ts/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export const version = "5.0.0-beta.126";
|
283
packages/abstract-provider/src.ts/index.ts
Normal file
283
packages/abstract-provider/src.ts/index.ts
Normal file
@ -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);
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
*/
|
||||
}
|
12
packages/abstract-provider/tsconfig.json
Normal file
12
packages/abstract-provider/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.package.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src.ts",
|
||||
"outDir": "./"
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/*"
|
||||
],
|
||||
"exclude": [ ]
|
||||
}
|
||||
|
2
packages/abstract-signer/.npmignore
Normal file
2
packages/abstract-signer/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
tsconfig.json
|
||||
src.ts/
|
21
packages/abstract-signer/LICENSE.md
Normal file
21
packages/abstract-signer/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/abstract-signer/README.md
Normal file
17
packages/abstract-signer/README.md
Normal file
@ -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
|
26
packages/abstract-signer/package.json
Normal file
26
packages/abstract-signer/package.json
Normal file
@ -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"
|
||||
}
|
1
packages/abstract-signer/src.ts/_version.ts
Normal file
1
packages/abstract-signer/src.ts/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export const version = "5.0.0-beta.126";
|
216
packages/abstract-signer/src.ts/index.ts
Normal file
216
packages/abstract-signer/src.ts/index.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
12
packages/abstract-signer/tsconfig.json
Normal file
12
packages/abstract-signer/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.package.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src.ts",
|
||||
"outDir": "./"
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/*"
|
||||
],
|
||||
"exclude": [ ]
|
||||
}
|
||||
|
2
packages/address/.npmignore
Normal file
2
packages/address/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
tsconfig.json
|
||||
src.ts/
|
21
packages/address/LICENSE.md
Normal file
21
packages/address/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/address/README.md
Normal file
17
packages/address/README.md
Normal file
@ -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
|
27
packages/address/package.json
Normal file
27
packages/address/package.json
Normal file
@ -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"
|
||||
}
|
1
packages/address/src.ts/_version.ts
Normal file
1
packages/address/src.ts/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export const version = "5.0.0-beta.125";
|
146
packages/address/src.ts/index.ts
Normal file
146
packages/address/src.ts/index.ts
Normal file
@ -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
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;
|
||||
}
|
||||
}
|
13
packages/address/tsconfig.json
Normal file
13
packages/address/tsconfig.json
Normal file
@ -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
1
packages/base64/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
browser.d.ts
|
2
packages/base64/.npmignore
Normal file
2
packages/base64/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
tsconfig.json
|
||||
src.ts/
|
21
packages/base64/LICENSE.md
Normal file
21
packages/base64/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.
|
36
packages/base64/README.md
Normal file
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
|
23
packages/base64/package.json
Normal file
23
packages/base64/package.json
Normal file
@ -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"
|
||||
}
|
1
packages/base64/src.ts/_version.ts
Normal file
1
packages/base64/src.ts/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export const version = "5.0.0-beta.124";
|
23
packages/base64/src.ts/browser.ts
Normal file
23
packages/base64/src.ts/browser.ts
Normal file
@ -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);
|
||||
}
|
||||
|
||||
|
12
packages/base64/src.ts/index.ts
Normal file
12
packages/base64/src.ts/index.ts
Normal file
@ -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");
|
||||
}
|
12
packages/base64/tsconfig.json
Normal file
12
packages/base64/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.package.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src.ts",
|
||||
"outDir": "./"
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/*"
|
||||
],
|
||||
"exclude": [ ]
|
||||
}
|
||||
|
2
packages/basex/.npmignore
Normal file
2
packages/basex/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
tsconfig.json
|
||||
src.ts/
|
21
packages/basex/LICENSE.md
Normal file
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
5
packages/basex/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
Base X
|
||||
======
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
23
packages/basex/package.json
Normal file
23
packages/basex/package.json
Normal file
@ -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"
|
||||
}
|
1
packages/basex/src.ts/_version.ts
Normal file
1
packages/basex/src.ts/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export const version = "5.0.0-beta.124";
|
143
packages/basex/src.ts/index.ts
Normal file
143
packages/basex/src.ts/index.ts
Normal file
@ -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")))
|
12
packages/basex/tsconfig.json
Normal file
12
packages/basex/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.package.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src.ts",
|
||||
"outDir": "./"
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/*"
|
||||
],
|
||||
"exclude": [ ]
|
||||
}
|
||||
|
2
packages/bignumber/.npmignore
Normal file
2
packages/bignumber/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
tsconfig.json
|
||||
src.ts/
|
21
packages/bignumber/LICENSE.md
Normal file
21
packages/bignumber/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/bignumber/README.md
Normal file
17
packages/bignumber/README.md
Normal file
@ -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
|
26
packages/bignumber/package.json
Normal file
26
packages/bignumber/package.json
Normal file
@ -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"
|
||||
}
|
1
packages/bignumber/src.ts/_version.ts
Normal file
1
packages/bignumber/src.ts/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export const version = "5.0.0-beta.126";
|
350
packages/bignumber/src.ts/bignumber.ts
Normal file
350
packages/bignumber/src.ts/bignumber.ts
Normal file
@ -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);
|
||||
}
|
343
packages/bignumber/src.ts/fixednumber.ts
Normal file
343
packages/bignumber/src.ts/fixednumber.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
2
packages/bignumber/src.ts/index.ts
Normal file
2
packages/bignumber/src.ts/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { BigNumber, BigNumberish } from "./bignumber";
|
||||
export { FixedNumber } from "./fixednumber";
|
30
packages/bignumber/thirdparty.d.ts
vendored
Normal file
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;
|
||||
}
|
||||
}
|
13
packages/bignumber/tsconfig.json
Normal file
13
packages/bignumber/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.package.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src.ts",
|
||||
"outDir": "./"
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/*.ts",
|
||||
"./thirdparty.d.ts"
|
||||
],
|
||||
"exclude": [ ]
|
||||
}
|
||||
|
2
packages/bytes/.npmignore
Normal file
2
packages/bytes/.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
tsconfig.json
|
||||
src.ts/
|
21
packages/bytes/LICENSE.md
Normal file
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
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
|
24
packages/bytes/package.json
Normal file
24
packages/bytes/package.json
Normal file
@ -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"
|
||||
}
|
1
packages/bytes/src.ts/_version.ts
Normal file
1
packages/bytes/src.ts/_version.ts
Normal file
@ -0,0 +1 @@
|
||||
export const version = "5.0.0-beta.126";
|
445
packages/bytes/src.ts/index.ts
Normal file
445
packages/bytes/src.ts/index.ts
Normal file
@ -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")
|
||||
]));
|
||||
}
|
||||
|
12
packages/bytes/tsconfig.json
Normal file
12
packages/bytes/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.package.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src.ts",
|
||||
"outDir": "./"
|
||||
},
|
||||
"include": [
|
||||
"./src.ts/*"
|
||||
],
|
||||
"exclude": [ ]
|
||||
}
|
||||
|
1
packages/cli/.gitignore
vendored
Normal file
1
packages/cli/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
bin/*.ts
|
31
packages/cli/package.json
Normal file
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"
|
||||
}
|
1
packages/cli/src.ts/_version.ts
Normal file
1
packages/cli/src.ts/_version.ts
Normal file
@ -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
Loading…
Reference in New Issue
Block a user