Additional sanity checks in ethers-ens.
This commit is contained in:
parent
9977c9f66a
commit
de4b2a449c
220
packages/cli/README.md
Normal file
220
packages/cli/README.md
Normal file
@ -0,0 +1,220 @@
|
||||
Command-Line Interface (CLI)
|
||||
============================
|
||||
|
||||
The command-line interface provides several simple tools to manage
|
||||
and debug Ethereum-related tasks using the ethers.js library.
|
||||
|
||||
**To install:**
|
||||
|
||||
```
|
||||
/home/ricmoo> npm install -g @ethersproject/cli
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
Sandbox Utility
|
||||
===============
|
||||
|
||||
The sandbox utility run on its own will run a REPL environment similar
|
||||
to running `node`, with many features from the ethers.js library
|
||||
already imported and permits loading accounts and setting up a provider.
|
||||
|
||||
It also provides a simple interface to common tasks, such as sweeping
|
||||
accounts, signing messages and compiling Solidity.
|
||||
|
||||
**Example:** Create and fund testnet account
|
||||
|
||||
```
|
||||
/home/ricmoo> ethers init ropsten.json
|
||||
Creating a new JSON Wallet - ropsten.json
|
||||
Keep this password and file SAFE!! If lost or forgotten
|
||||
it CANNOT be recovered, by ANYone, EVER.
|
||||
Choose a password: ****
|
||||
Confirm password: ****
|
||||
Encrypting... 100%
|
||||
New account address: 0xe923a7f82860C30442a1A541C14bE4251bd71A34
|
||||
Saved: ropsten.json
|
||||
/home/ricmoo> ethers --wait --network ropsten fund 0xe923a7f82860C30442a1A541C14bE4251bd71A34
|
||||
Transaction Hash: 0x457c1d8b58170c73a02afa2816e877de41d6337a483d4af9cbd674d2b478473d
|
||||
/home/ethers>
|
||||
```
|
||||
|
||||
**Example:** Simple evaluations
|
||||
|
||||
```
|
||||
/home/ricmoo> ethers eval 'namehash("ricmoose.eth")'
|
||||
0xb52c4744695ed3be701ccef35d5901de3aaf7294245966ef16617c30aab7b626
|
||||
|
||||
/home/ricmoo> ethers eval 'id("Hello...")'
|
||||
0x9cd41c139084dafa62261ce045f504e3c697fa303c87a78b241a9f8ae65bae88
|
||||
|
||||
/home/ricmoo> ethers --network ropsten eval '(new Contract(provider.network.ensAddress, [ "function owner(bytes32) view returns (address)" ], provider)).owner(namehash("eth"))'
|
||||
0x227Fcb6Ddf14880413EF4f1A3dF2Bbb32bcb29d7
|
||||
```
|
||||
|
||||
**Example:** REPL
|
||||
|
||||
```
|
||||
/home/ricmoo> ethers --network ropsten --account mnemonic.txt
|
||||
network: ropsten (chainId: 3)
|
||||
ropsten> provider.getGasPrice()
|
||||
BigNumber { _hex: '0xb2d05e00', _isBigNumber: true }
|
||||
ropsten> accounts[0].signMessage("Hello...");
|
||||
Message:
|
||||
Message: "Hello..."
|
||||
Message (hex): 0x48656c6c6f2e2e2e
|
||||
Sign Message? (y/N/a) yy
|
||||
Signature
|
||||
Flat: 0x37e9add966fe86d50bc5d816f9cb8213107d428551e64f118bc087fe1e4031f61685c7123d14f47e673ada87049cbbecab0c4e9ef5e8ded073e0be9980d14e761b
|
||||
r: 0x37e9add966fe86d50bc5d816f9cb8213107d428551e64f118bc087fe1e4031f6
|
||||
s: 0x1685c7123d14f47e673ada87049cbbecab0c4e9ef5e8ded073e0be9980d14e76
|
||||
vs: 0x1685c7123d14f47e673ada87049cbbecab0c4e9ef5e8ded073e0be9980d14e76
|
||||
v: 27
|
||||
recid: 0
|
||||
'0x37e9add966fe86d50bc5d816f9cb8213107d428551e64f118bc087fe1e4031f61685c7123d14f47e673ada87049cbbecab0c4e9ef5e8ded073e0be9980d14e761b'
|
||||
```
|
||||
|
||||
Help (--help)
|
||||
-------------
|
||||
|
||||
```
|
||||
Usage:
|
||||
ethers [ COMMAND ] [ ARGS ] [ OPTIONS ]
|
||||
|
||||
COMMANDS (default: sandbox)
|
||||
sandbox Run a REPL VM environment with ethers
|
||||
init FILENAME Create a new JSON wallet
|
||||
[ --force ] Overwrite any existing files
|
||||
fund TARGET Fund TARGET with testnet ether
|
||||
info [ TARGET ... ] Dump info for accounts, addresses and ENS names
|
||||
send TARGET ETHER Send ETHER ether to TARGET form accounts[0]
|
||||
[ --allow-zero ] Allow sending to the address zero
|
||||
[ --data DATA ] Include data in the transaction
|
||||
sweep TARGET Send all ether from accounts[0] to TARGET
|
||||
sign-message MESSAGE Sign a MESSAGE with accounts[0]
|
||||
[ --hex ] The message content is hex encoded
|
||||
eval CODE Run CODE in a VM with ethers
|
||||
run FILENAME Run FILENAME in a VM with ethers
|
||||
wait HASH Wait for a transaction HASH to be mined
|
||||
compile FILENAME Compiles a Solidity contract
|
||||
[ --no-optimize ] Do not optimize the compiled output
|
||||
[ --warnings ] Error on any warning
|
||||
deploy FILENAME Compile and deploy a Solidity contract
|
||||
[ --no-optimize ] Do not optimize the compiled output
|
||||
[ --contract NAME ] Specify the contract to deploy
|
||||
|
||||
ACCOUNT OPTIONS
|
||||
--account FILENAME Load from a file (JSON, RAW or mnemonic)
|
||||
--account RAW_KEY Use a private key (insecure *)
|
||||
--account 'MNEMONIC' Use a mnemonic (insecure *)
|
||||
--account - Use secure entry for a raw key or mnemonic
|
||||
--account-void ADDRESS Use an address as a void signer
|
||||
--account-void ENS_NAME Add the resolved address as a void signer
|
||||
--account-rpc ADDRESS Add the address from a JSON-RPC provider
|
||||
--account-rpc INDEX Add the index from a JSON-RPC provider
|
||||
--mnemonic-password Prompt for a password for mnemonics
|
||||
--xxx-mnemonic-password Prompt for a (experimental) hard password
|
||||
|
||||
PROVIDER OPTIONS (default: all + homestead)
|
||||
--alchemy Include Alchemy
|
||||
--etherscan Include Etherscan
|
||||
--infura Include INFURA
|
||||
--nodesmith Include nodesmith
|
||||
--rpc URL Include a custom JSON-RPC
|
||||
--offline Dump signed transactions (no send)
|
||||
--network NETWORK Network to connect to (default: homestead)
|
||||
|
||||
TRANSACTION OPTIONS (default: query network)
|
||||
--gasPrice GWEI Default gas price for transactions(in wei)
|
||||
--gasLimit GAS Default gas limit for transactions
|
||||
--nonce NONCE Initial nonce for the first transaction
|
||||
--yes Always accept Siging and Sending
|
||||
|
||||
OTHER OPTIONS
|
||||
--wait Wait until transactions are mined
|
||||
--debug Show stack traces for errors
|
||||
--help Show this usage and exit
|
||||
--version Show this version and exit
|
||||
|
||||
(*) By including mnemonics or private keys on the command line they are
|
||||
possibly readable by other users on your system and may get stored in
|
||||
your bash history file. This is NOT recommended.
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
Ethereum Naming Service (ENS)
|
||||
=============================
|
||||
|
||||
These tools help manage ENS names.
|
||||
|
||||
Help (--help)
|
||||
-------------
|
||||
|
||||
```
|
||||
Usage:
|
||||
ethers-ens COMMAND [ ARGS ] [ OPTIONS ]
|
||||
|
||||
COMMANDS
|
||||
lookup [ NAME | ADDRESS [ ... ] ]
|
||||
Lookup a name or address
|
||||
commit NAME Submit a pre-commitment
|
||||
[ --duration DAYS ] Register duration (default: 365 days)
|
||||
[ --salt SALT ] SALT to blind the commit with
|
||||
[ --secret SECRET ] Use id(SECRET) as the salt
|
||||
[ --owner OWNER ] The target owner (default: current account)
|
||||
reveal NAME Reveal a previous pre-commitment
|
||||
[ --duration DAYS ] Register duration (default: 365 days)
|
||||
[ --salt SALT ] SALT to blind the commit with
|
||||
[ --secret SECRET ] Use id(SECRET) as the salt
|
||||
[ --owner OWNER ] The target owner (default: current account)
|
||||
set-controller NAME Set the controller (default: current account)
|
||||
[ --address ADDRESS ] Specify another address
|
||||
set-subnode NAME Set a subnode owner (default: current account)
|
||||
[ --address ADDRESS ] Specify another address
|
||||
set-resolver NAME Set the resolver (default: resolver.eth)
|
||||
[ --address ADDRESS ] Specify another address
|
||||
set-addr NAME Set the addr record (default: current account)
|
||||
[ --address ADDRESS ] Specify another address
|
||||
set-text NAME KEY VALUE Set a text record
|
||||
set-email NAME EMAIL Set the email text record
|
||||
set-website NAME URL Set the website text record
|
||||
set-content NAME HASH Set the IPFS Content Hash
|
||||
migrate-registrar NAME Migrate from the Legacy to the Permanent Registrar
|
||||
transfer NAME NEW_OWNER Transfer registrant ownership
|
||||
reclaim NAME Reset the controller by the registrant
|
||||
[ --address ADDRESS ] Specify another address
|
||||
```
|
||||
|
||||
See above for the `ACCOUNT`, `PROVIDER`, `TRANSACTION`, and `OTHER`
|
||||
options.
|
||||
|
||||
-----
|
||||
|
||||
TypeScript Utility
|
||||
==================
|
||||
|
||||
The TypeScript utility compiles Solidity contracts into a single file
|
||||
with each contract sub-classing Contract, with all typing information
|
||||
added.
|
||||
|
||||
Help (--help)
|
||||
-------------
|
||||
|
||||
```
|
||||
Usage:
|
||||
ethers-ts FILENAME [ ... ] [ OPTIONS ]
|
||||
|
||||
OPTIONS
|
||||
--output FILENAME Write the output to FILENAME (default: stdout)
|
||||
--force Overwrite files if they already exist
|
||||
--no-optimize Do not run the solc optimizer
|
||||
--no-bytecode Do not include bytecode and Factory methods
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
MIT License
|
@ -21,9 +21,12 @@ const ensAbi = [
|
||||
|
||||
const States = Object.freeze([ "Open", "Auction", "Owned", "Forbidden", "Reveal", "NotAvailable" ]);
|
||||
|
||||
const deedAbi = [
|
||||
"function owner() view returns (address)"
|
||||
];
|
||||
|
||||
const ethLegacyRegistrarAbi = [
|
||||
"function entries(bytes32 _hash) view returns (uint8 state, address owner, uint registrationDate, uint value, uint highestBid)",
|
||||
"function state(bytes32 _hash) public view returns (uint8)",
|
||||
"function transferRegistrars(bytes32 _hash) @500000",
|
||||
];
|
||||
|
||||
@ -31,13 +34,15 @@ const ethControllerAbi = [
|
||||
"function rentPrice(string memory name, uint duration) view public returns(uint)",
|
||||
"function available(string memory label) public view returns(bool)",
|
||||
"function makeCommitment(string memory name, address owner, bytes32 secret) pure public returns(bytes32)",
|
||||
"function commit(bytes32 commitment) public",
|
||||
"function commit(bytes32 commitment) public @500000",
|
||||
"function register(string calldata name, address owner, uint duration, bytes32 secret) payable @500000",
|
||||
"function renew(string calldata name, uint duration) payable @500000",
|
||||
];
|
||||
|
||||
const ethRegistrarAbi = [
|
||||
"function transferFrom(address from, address to, uint256 tokenId)"
|
||||
"function ownerOf(uint256 tokenId) view returns (address)",
|
||||
"function reclaim(uint256 id, address owner) @500000",
|
||||
"function transferFrom(address from, address to, uint256 tokenId) @500000"
|
||||
];
|
||||
|
||||
const resolverAbi = [
|
||||
@ -50,7 +55,7 @@ const resolverAbi = [
|
||||
"function setContenthash(bytes32 nodehash, bytes contenthash) @500000",
|
||||
];
|
||||
|
||||
const InterfaceID_ERC721 = "0x6ccb2df4";
|
||||
//const InterfaceID_ERC721 = "0x6ccb2df4";
|
||||
const InterfaceID_Controller = "0x018fac06";
|
||||
const InterfaceID_Legacy = "0x7ba18ba1";
|
||||
|
||||
@ -108,7 +113,8 @@ abstract class EnsPlugin extends Plugin {
|
||||
}
|
||||
|
||||
async getEthRegistrar(): Promise<ethers.Contract> {
|
||||
let address = await this.getEthInterfaceAddress(InterfaceID_ERC721);
|
||||
//let address = await this.getEthInterfaceAddress(InterfaceID_ERC721);
|
||||
let address = await this.getEns().owner(ethers.utils.namehash("eth"));
|
||||
return new ethers.Contract(address, ethRegistrarAbi, this.accounts[0] || this.provider);
|
||||
}
|
||||
}
|
||||
@ -135,74 +141,90 @@ class LookupPlugin extends EnsPlugin {
|
||||
|
||||
let ens = this.getEns();
|
||||
|
||||
let controller = await this.getEthController();
|
||||
let registrar = await this.getEthRegistrar();
|
||||
let legacyRegistrar = await this.getEthLegacyRegistrar();
|
||||
|
||||
for (let i = 0; i < this.names.length; i++) {
|
||||
let name = this.names[i];
|
||||
|
||||
let nodehash = ethers.utils.namehash(name);
|
||||
|
||||
let details: any = {
|
||||
Owner: ens.owner(nodehash),
|
||||
Resolver: ens.resolver(nodehash)
|
||||
let details: { [ key: string]: string } = {
|
||||
Nodehash: nodehash
|
||||
};
|
||||
|
||||
let owner = await ens.owner(nodehash);
|
||||
let resolverAddress: string = null;
|
||||
if (owner === ethers.constants.AddressZero) {
|
||||
owner = null;
|
||||
} else {
|
||||
details.Controller = owner;
|
||||
details.Resolver = await ens.resolver(nodehash).then((address: string) => {
|
||||
if (address === ethers.constants.AddressZero) {
|
||||
return "(not configured)";
|
||||
}
|
||||
resolverAddress = address;
|
||||
return address;
|
||||
});
|
||||
}
|
||||
|
||||
let comps = name.split(".");
|
||||
if (comps.length === 2 && comps[1] === "eth") {
|
||||
let labelhash = ethers.utils.id(comps[0].toLowerCase()); // @TODO: nameprep
|
||||
details.Labelhash = ethers.utils.id(comps[0].toLowerCase()); // @TODO: nameprep
|
||||
|
||||
let available = this.getEthController().then((ethController) => {
|
||||
return ethController.available(comps[0]);
|
||||
});
|
||||
details.Available = available;
|
||||
details.Available = await controller.available(comps[0]);
|
||||
|
||||
let legacyRegistrarPromise = this.getEthLegacyRegistrar();
|
||||
|
||||
details._Registrar = Promise.all([
|
||||
available,
|
||||
legacyRegistrarPromise.then((legacyRegistrar) => {
|
||||
return legacyRegistrar.state(labelhash);
|
||||
})
|
||||
]).then((results) => {
|
||||
let available = results[0];
|
||||
let state = States[results[1]];
|
||||
if (!available && state === "Owned") {
|
||||
return legacyRegistrarPromise.then((legacyRegistrar) => {
|
||||
return legacyRegistrar.entries(labelhash).then((entries: any) => {
|
||||
return {
|
||||
Registrar: "Legacy",
|
||||
"Deed Value": (ethers.utils.formatEther(entries.value) + " ether"),
|
||||
"Highest Bid": (ethers.utils.formatEther(entries.highestBid) + " ether"),
|
||||
if (!details.Available) {
|
||||
try {
|
||||
let ownerOf = await registrar.ownerOf(details.Labelhash);
|
||||
if (ownerOf !== ethers.constants.AddressZero) {
|
||||
details.Registrant = ownerOf;
|
||||
details.Registrar = "Permanent";
|
||||
}
|
||||
} catch (error) {
|
||||
let entry = await legacyRegistrar.entries(details.Labelhash);
|
||||
let deed = new ethers.Contract(entry.owner, deedAbi, this.provider);
|
||||
|
||||
details.Registrant = await deed.owner();
|
||||
details.Registrar = "Legacy";
|
||||
details["Deed Value"] = (ethers.utils.formatEther(entry.value) + " ether");
|
||||
details["Highest Bid"] = (ethers.utils.formatEther(entry.highestBid) + " ether");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return { Registrar: "Permanent" };
|
||||
});
|
||||
}
|
||||
|
||||
details = await ethers.utils.resolveProperties(details);
|
||||
if (resolverAddress) {
|
||||
let resolver = new ethers.Contract(resolverAddress, resolverAbi, this.provider);
|
||||
details["Address"] = await resolver.addr(nodehash);
|
||||
|
||||
if (details.Resolver !== ethers.constants.AddressZero) {
|
||||
let resolver = new ethers.Contract(details.Resolver, resolverAbi, this.provider);
|
||||
details["Address"] = resolver.addr(nodehash);
|
||||
details["E-mail"] = resolver.text(nodehash, "email").catch((error: any) => (""));
|
||||
details["Website"] = resolver.text(nodehash, "website").catch((error: any) => (""));
|
||||
details["Content Hash"] = resolver.contenthash(nodehash).then((hash: string) => {
|
||||
if (hash === "0x") { return "0x"; }
|
||||
let email = await resolver.text(nodehash, "email").catch((error: any) => (""));
|
||||
if (email) { details["E-mail"] = email; }
|
||||
|
||||
let website = await resolver.text(nodehash, "website").catch((error: any) => (""));
|
||||
if (website) { details["Website"] = website; }
|
||||
|
||||
let content = await resolver.contenthash(nodehash).then((hash: string) => {
|
||||
if (hash === "0x") { return ""; }
|
||||
if (hash.substring(0, 10) === "0xe3010170" && ethers.utils.isHexString(hash, 38)) {
|
||||
return Base58.encode(ethers.utils.hexDataSlice(hash, 4)) + " (IPFS)";
|
||||
}
|
||||
return hash + " (unknown format)";
|
||||
}, (error: any) => (""));
|
||||
if (content) { details["Content Hash"] = content; }
|
||||
}
|
||||
|
||||
details = await ethers.utils.resolveProperties(details);
|
||||
|
||||
for (let key in details._Registrar) {
|
||||
details[key] = details._Registrar[key];
|
||||
let ordered: { [ key: string]: string } = { };
|
||||
"Nodehash,Labelhash,Available,Registrant,Controller,Resolver,Address,Registrar,Deed Value,Highest Bid,E-mail,Website,Content Hash".split(",").forEach((key) => {
|
||||
if (!details[key]) { return; }
|
||||
ordered[key] = details[key];
|
||||
});
|
||||
for (let key in details) {
|
||||
if (ordered[key]) { continue; }
|
||||
ordered[key] = details[key];
|
||||
}
|
||||
delete details._Registrar;
|
||||
|
||||
this.dump("Name: " + this.names[i], details);
|
||||
this.dump("Name: " + this.names[i], ordered);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -335,7 +357,7 @@ class CommitPlugin extends ControllerPlugin {
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "commit NAME",
|
||||
help: "Commit to NAME"
|
||||
help: "Submit a pre-commitment"
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,8 +386,8 @@ class RevealPlugin extends ControllerPlugin {
|
||||
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "reveal LABEL",
|
||||
help: "Reveal a previously committed name"
|
||||
name: "reveal NAME",
|
||||
help: "Reveal a previous pre-commitment"
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,7 +460,7 @@ abstract class AddressAccountPlugin extends AccountPlugin {
|
||||
return [
|
||||
{
|
||||
name: "[ --address ADDRESS ]",
|
||||
help: "Override the address"
|
||||
help: "Specify another address"
|
||||
}
|
||||
];
|
||||
}
|
||||
@ -459,12 +481,12 @@ abstract class AddressAccountPlugin extends AccountPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
class SetOwnerPlugin extends AddressAccountPlugin {
|
||||
class SetControllerPlugin extends AddressAccountPlugin {
|
||||
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "set-owner NAME",
|
||||
help: "Set the owner of NAME (default: current account)"
|
||||
name: "set-controller NAME",
|
||||
help: "Set the controller (default: current account)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,7 +496,7 @@ class SetOwnerPlugin extends AddressAccountPlugin {
|
||||
this.getEns().setOwner(this.nodehash, this.address);
|
||||
}
|
||||
}
|
||||
cli.addPlugin("set-owner", SetOwnerPlugin);
|
||||
cli.addPlugin("set-controller", SetControllerPlugin);
|
||||
|
||||
class SetSubnodePlugin extends AddressAccountPlugin {
|
||||
label: string;
|
||||
@ -483,7 +505,7 @@ class SetSubnodePlugin extends AddressAccountPlugin {
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "set-subnode NAME",
|
||||
help: "Set the subnode owner"
|
||||
help: "Set a subnode owner (default: current account)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,7 +535,7 @@ class SetResolverPlugin extends AddressAccountPlugin {
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "set-resolver NAME",
|
||||
help: "Set the resolver for NAME (default: resolver.eth)"
|
||||
help: "Set the resolver (default: resolver.eth)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -586,7 +608,7 @@ class SetTextPlugin extends TextAccountPlugin {
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "set-text NAME KEY VALUE",
|
||||
help: "Set the KEY text record to VALUE"
|
||||
help: "Set a text record"
|
||||
}
|
||||
}
|
||||
|
||||
@ -602,7 +624,7 @@ class SetEmailPlugin extends TextAccountPlugin {
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "set-email NAME EMAIL",
|
||||
help: "Set the email text record to EMAIL"
|
||||
help: "Set the email text record"
|
||||
}
|
||||
}
|
||||
|
||||
@ -618,7 +640,7 @@ class SetWebsitePlugin extends TextAccountPlugin {
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "set-website NAME URL",
|
||||
help: "Set the website text record to URL"
|
||||
help: "Set the website text record"
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,7 +658,7 @@ class SetContentPlugin extends AccountPlugin {
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "set-content NAME HASH",
|
||||
help: "Set the IPFS HASH for NAME"
|
||||
help: "Set the IPFS Content Hash"
|
||||
}
|
||||
}
|
||||
|
||||
@ -669,40 +691,54 @@ cli.addPlugin("set-content", SetContentPlugin);
|
||||
|
||||
class MigrateRegistrarPlugin extends AccountPlugin {
|
||||
readonly label: string;
|
||||
readonly deedValue: ethers.BigNumber;
|
||||
readonly highestBid: ethers.BigNumber;
|
||||
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "migrate-registrar NAME",
|
||||
help: "Migrates NAME from the Legacy to Permanent Registrar"
|
||||
help: "Migrate from the Legacy to the Permanent Registrar"
|
||||
}
|
||||
}
|
||||
|
||||
async prepareArgs(args: Array<string>): Promise<void> {
|
||||
await super.prepareArgs(args);
|
||||
|
||||
// Only Top-Level names can be migrated
|
||||
let comps = this.name.split(".");
|
||||
if (comps.length !== 2 || comps[1] !== "eth") {
|
||||
this.throwError("Not a top-level .eth name");
|
||||
}
|
||||
|
||||
// @TODO: Should probably check that accounts[0].getAddress() matches
|
||||
// the owner in the legacy registrar
|
||||
await super._setValue("label", comps[0]);
|
||||
|
||||
let ethLegacyRegistrar = await this.getEthLegacyRegistrar();
|
||||
let state = await ethLegacyRegistrar.state(ethers.utils.id(comps[0]));
|
||||
let entry: any = await ethLegacyRegistrar.entries(ethers.utils.id(comps[0]));
|
||||
|
||||
if (States[state] !== "Owned") {
|
||||
// Only owned names can be migrated
|
||||
if (States[entry.state] !== "Owned") {
|
||||
this.throwError("Name not present in the Legacy registrar");
|
||||
}
|
||||
|
||||
await super._setValue("label", comps[0]);
|
||||
let deed = new ethers.Contract(entry.owner, deedAbi, this.provider);
|
||||
let owner = await deed.owner();
|
||||
let address = await this.accounts[0].getAddress();
|
||||
|
||||
// Only the deed owner (registrant) may migrate a name
|
||||
if (owner !== address) {
|
||||
this.throwError("Only the registrant can migrate");
|
||||
}
|
||||
|
||||
await super._setValue("deedValue", entry.value);
|
||||
await super._setValue("highestBid", entry.highestBid);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
await super.run();
|
||||
|
||||
this.dump("Migrate Registrar: " + this.name, {
|
||||
Nodehash: this.nodehash
|
||||
"Nodehash": this.nodehash,
|
||||
"Highest Bid": (ethers.utils.formatEther(this.highestBid) + " ether"),
|
||||
"Deed Value": (ethers.utils.formatEther(this.deedValue) + " ether"),
|
||||
});
|
||||
|
||||
let legacyRegistrar = await this.getEthLegacyRegistrar();
|
||||
@ -720,16 +756,16 @@ class TransferPlugin extends AccountPlugin {
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "transfer NAME NEW_OWNER",
|
||||
help: "Transfers NAME to NEW_OWNER (permanent regstrar only)"
|
||||
help: "Transfer registrant ownership"
|
||||
}
|
||||
}
|
||||
|
||||
async _setValue(key: string, value: string): Promise<void> {
|
||||
if (key === "new_owner") {
|
||||
let address = await this.getAddress(value);
|
||||
await this._setValue(key, address);
|
||||
await super._setValue(key, address);
|
||||
} else if (key === "name") {
|
||||
let comps = this.name.split(".");
|
||||
let comps = value.split(".");
|
||||
if (comps.length !== 2 || comps[1] !== "eth") {
|
||||
this.throwError("Not a top-level .eth name");
|
||||
}
|
||||
@ -754,12 +790,61 @@ class TransferPlugin extends AccountPlugin {
|
||||
}
|
||||
cli.addPlugin("transfer", TransferPlugin);
|
||||
|
||||
class ReclaimPlugin extends AddressAccountPlugin {
|
||||
readonly label: string;
|
||||
|
||||
static getHelp(): Help {
|
||||
return {
|
||||
name: "reclaim NAME",
|
||||
help: "Reset the controller by the registrant"
|
||||
}
|
||||
}
|
||||
|
||||
async _setValue(key: string, value: string): Promise<void> {
|
||||
if (key === "name") {
|
||||
let comps = value.split(".");
|
||||
if (comps.length !== 2 || comps[1] !== "eth") {
|
||||
this.throwError("Not a top-level .eth name");
|
||||
}
|
||||
|
||||
let account = await this.accounts[0].getAddress();
|
||||
|
||||
let registrar = await this.getEthRegistrar();
|
||||
let ownerOf: string = null;
|
||||
try {
|
||||
ownerOf = await registrar.ownerOf(ethers.utils.id(comps[0]));
|
||||
} catch (error) {
|
||||
this.throwError("Name not present in Permantent Registrar");
|
||||
}
|
||||
|
||||
if (account !== ownerOf) {
|
||||
this.throwError("Only the registrant can call reclaim");
|
||||
}
|
||||
|
||||
await super._setValue("label", comps[0]);
|
||||
}
|
||||
await super._setValue(key, value);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
await super.run();
|
||||
|
||||
this.dump("Reclaim: " + this.name, {
|
||||
Nodehash: this.nodehash,
|
||||
"Address": this.address,
|
||||
});
|
||||
|
||||
let registrar = await this.getEthRegistrar();
|
||||
await registrar.reclaim(ethers.utils.id(this.label), this.address);
|
||||
}
|
||||
}
|
||||
cli.addPlugin("reclaim", ReclaimPlugin);
|
||||
|
||||
/**
|
||||
* To Do:
|
||||
* register NAME --registrar
|
||||
* set-reverse NAME
|
||||
* renew NAME --duration DAYS
|
||||
* reclaim NAME --address OWNER
|
||||
*
|
||||
* Done:
|
||||
* migrate-registrar NAME
|
||||
@ -773,6 +858,7 @@ cli.addPlugin("transfer", TransferPlugin);
|
||||
* set-webstie NAME WEBSITE
|
||||
* set-text NAME KEY VALUE
|
||||
* set-content NAME HASH
|
||||
* reclaim NAME --address OWNER
|
||||
*/
|
||||
|
||||
cli.run(process.argv.slice(2))
|
||||
|
Loading…
x
Reference in New Issue
Block a user