Added migrate-registrar and transfer to ENS CLI.

This commit is contained in:
Richard Moore 2019-08-04 19:22:04 -04:00
parent 53bd96a9f6
commit 31e8e1b052
No known key found for this signature in database
GPG Key ID: 665176BE8E9DC651

@ -18,22 +18,15 @@ const ensAbi = [
"function resolver(bytes32 node) external view returns (address)"
];
const States = Object.freeze([ "Open", "Auction", "Owned", "Forbidden", "Reveal", "NotAvailable" ]);
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",
];
/*
const ethRegistrarAbi = [
"function available(uint256 id) public view returns(bool)",
];
*/
const ethControllerAbi = [
/*
"function nameExpires(uint256 id) external view returns(uint)",
"function register(uint256 id, address owner, uint duration) external returns(uint)",
"renew(uint256 id, uint duration) external returns(uint)",
"reclaim(uint256 id, address owner) external",
"acceptRegistrarTransfer(bytes32 label, Deed deed, uint) external",
*/
"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)",
@ -42,6 +35,10 @@ const ethControllerAbi = [
"function renew(string calldata name, uint duration) payable @500000",
];
const ethRegistrarAbi = [
"function transferFrom(address from, address to, uint256 tokenId)"
];
const resolverAbi = [
"function interfaceImplementer(bytes32 nodehash, bytes4 interfaceId) view returns (address)",
"function addr(bytes32 nodehash) view returns (address)",
@ -50,7 +47,7 @@ const resolverAbi = [
"function setText(bytes32 nodehash, string key, string value) @500000",
];
//const InterfaceID_ERC721 = "0x6ccb2df4";
const InterfaceID_ERC721 = "0x6ccb2df4";
const InterfaceID_Controller = "0x018fac06";
const InterfaceID_Legacy = "0x7ba18ba1";
@ -67,10 +64,55 @@ function listify(words: Array<string>): string {
}
let cli = new CLI();
class LookupPlugin extends Plugin {
abstract class EnsPlugin extends Plugin {
_ethAddressCache: { [ addressOrInterfaceId: string ]: string };
constructor() {
super();
ethers.utils.defineReadOnly(this, "_ethAddressCache", { });
}
async getEns(): Promise<ethers.Contract> {
let network = await this.provider.getNetwork();
return new ethers.Contract(network.ensAddress, ensAbi, this.accounts[0] || this.provider);
}
async getResolver(nodehash: string): Promise<ethers.Contract> {
if (!this._ethAddressCache[nodehash]) {
let ens = await this.getEns();
this._ethAddressCache[nodehash] = await ens.resolver(nodehash);
}
return new ethers.Contract(this._ethAddressCache[nodehash], resolverAbi, this.accounts[0] || this.provider);
}
async getEthInterfaceAddress(interfaceId: string): Promise<string> {
let ethNodehash = ethers.utils.namehash("eth");
if (!this._ethAddressCache[interfaceId]) {
let resolver = await this.getResolver(ethNodehash);
this._ethAddressCache[interfaceId] = await resolver.interfaceImplementer(ethNodehash, interfaceId);
}
return this._ethAddressCache[interfaceId];
}
async getEthController(): Promise<ethers.Contract> {
let address = await this.getEthInterfaceAddress(InterfaceID_Controller);
return new ethers.Contract(address, ethControllerAbi, this.accounts[0] || this.provider);
}
async getEthLegacyRegistrar(): Promise<ethers.Contract> {
let address = await this.getEthInterfaceAddress(InterfaceID_Legacy);
return new ethers.Contract(address, ethLegacyRegistrarAbi, this.accounts[0] || this.provider);
}
async getEthRegistrar(): Promise<ethers.Contract> {
let address = await this.getEthInterfaceAddress(InterfaceID_ERC721);
return new ethers.Contract(address, ethRegistrarAbi, this.accounts[0] || this.provider);
}
}
class LookupPlugin extends EnsPlugin {
names: Array<string>;
@ -89,31 +131,8 @@ class LookupPlugin extends Plugin {
async run(): Promise<void> {
await super.run();
let ens = new ethers.Contract(this.network.ensAddress, ensAbi, this.provider);
let ethNodehash = ethers.utils.namehash("eth");
/*
let ethRegistrarPromise = ens.owner(ethNodehash).then((ethOwner) => {
return new ethers.Contract(ethOwner, );
});
*/
let ethResolverPromise: Promise<ethers.Contract> = ens.resolver(ethNodehash).then((address: string) => {
return new ethers.Contract(address, resolverAbi, this.provider);
});
let ethControllerPromise: Promise<ethers.Contract> = ethResolverPromise.then((ethResolver) => {
return ethResolver.interfaceImplementer(ethNodehash, InterfaceID_Controller).then((address: string) => {
return new ethers.Contract(address, ethControllerAbi, this.provider);
});
});
let ethLegacyRegistrarPromise: Promise<ethers.Contract> = ethResolverPromise.then((ethResolver) => {
return ethResolver.interfaceImplementer(ethNodehash, InterfaceID_Legacy).then((address: string) => {
return new ethers.Contract(address, ethLegacyRegistrarAbi, this.provider);
});
});
let ens = await this.getEns();
for (let i = 0; i < this.names.length; i++) {
let name = this.names[i];
@ -129,22 +148,33 @@ class LookupPlugin extends Plugin {
if (comps.length === 2 && comps[1] === "eth") {
let labelhash = ethers.utils.id(comps[0].toLowerCase()); // @TODO: nameprep
let available = ethControllerPromise.then((ethController) => {
let available = this.getEthController().then((ethController) => {
return ethController.available(comps[0]);
});
details.Available = available;
details.Registrar = Promise.all([
let legacyRegistrarPromise = this.getEthLegacyRegistrar();
details._Registrar = Promise.all([
available,
ethLegacyRegistrarPromise.then((legacyRegistrar) => {
legacyRegistrarPromise.then((legacyRegistrar) => {
return legacyRegistrar.state(labelhash);
})
]).then((results) => {
let States = [ "Open", "Auction", "Owned", "Forbidden", "Reveal", "NotAvailable" ];
let available = results[0];
let state = States[results[1]];
console.log(available, state);
return "FOO";
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"),
}
});
});
}
return { Registrar: "Permanent" };
});
}
@ -153,19 +183,24 @@ class LookupPlugin extends Plugin {
if (details.Resolver !== ethers.constants.AddressZero) {
let resolver = new ethers.Contract(details.Resolver, resolverAbi, this.provider);
details.address = resolver.addr(nodehash);
details.email = resolver.text(nodehash, "email");
details.website = resolver.text(nodehash, "website");
details = await ethers.utils.resolveProperties(details);
details.email = resolver.text(nodehash, "email").catch((error: any) => (""));
details.website = resolver.text(nodehash, "website").catch((error: any) => (""));
}
details = await ethers.utils.resolveProperties(details);
for (let key in details._Registrar) {
details[key] = details._Registrar[key];
}
delete details._Registrar;
this.dump("Name: " + this.names[i], details);
}
}
}
cli.addPlugin("lookup", LookupPlugin);
abstract class AccountPlugin extends Plugin {
ens: ethers.Contract;
abstract class AccountPlugin extends EnsPlugin {
name: string;
nodehash: string;
_wait: boolean;
@ -185,19 +220,6 @@ abstract class AccountPlugin extends Plugin {
];
}
async getResolver(nodehash?: string): Promise<ethers.Contract> {
if (!nodehash) { nodehash = this.nodehash; }
let resolverAddress = await this.ens.resolver(nodehash);
return new ethers.Contract(resolverAddress, resolverAbi, this.accounts[0]);
}
async getEthController(): Promise<ethers.Contract> {
let ethNodehash = ethers.utils.namehash("eth");
let resolver = await this.getResolver(ethNodehash);
let ethControllerAddress = await resolver.interfaceImplementer(ethNodehash, InterfaceID_Controller);
return new ethers.Contract(ethControllerAddress, ethControllerAbi, this.accounts[0]);
}
async wait(tx: ethers.providers.TransactionResponse): Promise<void> {
if (!this._wait) { return; }
try {
@ -226,7 +248,6 @@ abstract class AccountPlugin extends Plugin {
await super.prepareOptions(argParser);
ethers.utils.defineReadOnly(this, "_wait", argParser.consumeFlag("wait"));
ethers.utils.defineReadOnly(this, "ens", new ethers.Contract(this.network.ensAddress, ensAbi, this.accounts[0]));
}
async prepareArgs(args: Array<string>): Promise<void> {
@ -263,11 +284,11 @@ abstract class ControllerPlugin extends AccountPlugin {
[
{
name: "[ --duration DAYS ]",
help: "The duration to register for (default: 365 days)"
help: "Register duration (default: 365 days)"
},
{
name: "[ --salt SALT ]",
help: "Use SALT to blind the commit"
help: "SALT to blind the commit with"
},
{
name: "[ --secret SECRET ]",
@ -275,7 +296,7 @@ abstract class ControllerPlugin extends AccountPlugin {
},
{
name: "[ --owner OWNER ]",
help: "Set the OWNER (default: current account)"
help: "The target owner (default: current account)"
}
].forEach((help) => {
result.push(help);
@ -476,7 +497,9 @@ class SetOwnerPlugin extends AddressAccountPlugin {
async run(): Promise<void> {
await super.run();
let tx = this.ens.setOwner(this.nodehash, this.address);
const ens = await this.getEns();
let tx = ens.setOwner(this.nodehash, this.address);
this.wait(tx);
}
}
@ -498,7 +521,6 @@ class SetSubnodePlugin extends AddressAccountPlugin {
let comps = value.toLowerCase().split(".");
await super._setValue("label", comps[0]);
await super._setValue("node", comps.slice(1).join("."));
} else {
}
await super._setValue(key, value);
}
@ -511,7 +533,8 @@ class SetSubnodePlugin extends AddressAccountPlugin {
Node: this.node
});
let tx = await this.ens.setSubnodeOwner(ethers.utils.namehash(this.node), ethers.utils.id(this.label), this.address);
const ens = await this.getEns();
let tx = await ens.setSubnodeOwner(ethers.utils.namehash(this.node), ethers.utils.id(this.label), this.address);
this.wait(tx);
}
}
@ -532,12 +555,13 @@ class SetResolverPlugin extends AddressAccountPlugin {
async run(): Promise<void> {
await super.run();
this.dump("Set Resolver:" + this.name, {
this.dump("Set Resolver: " + this.name, {
Nodehash: this.nodehash,
Resolver: this.address
});
let tx = await this.ens.setResolver(this.nodehash, this.address);
const ens = await this.getEns();
let tx = await ens.setResolver(this.nodehash, this.address);
this.wait(tx);
}
@ -556,12 +580,12 @@ class SetAddrPlugin extends AddressAccountPlugin {
async run(): Promise<void> {
await super.run();
this.dump("Set Addr:" + this.name, {
this.dump("Set Addr: " + this.name, {
Nodehash: this.nodehash,
Address: this.address
});
let resolver = await this.getResolver();
let resolver = await this.getResolver(this.nodehash);
let tx = await resolver.setAddr(this.nodehash, this.address);
this.wait(tx);
}
@ -585,7 +609,7 @@ abstract class TextAccountPlugin extends AccountPlugin {
Value: value
});
let resolver = await this.getResolver();
let resolver = await this.getResolver(this.nodehash);
let tx = await resolver.setText(this.nodehash, key, value);
this.wait(tx);
}
@ -641,6 +665,8 @@ class SetWebsitePlugin extends TextAccountPlugin {
cli.addPlugin("set-website", SetWebsitePlugin);
/*
// @TODO:
class SetContentHashPlugin extends AccountPlugin {
hash: string;
@ -660,22 +686,114 @@ class SetContentHashPlugin extends AccountPlugin {
}
}
cli.addPlugin("set-content", SetContentHashPlugin);
*/
class MigrateRegistrarPlugin extends AccountPlugin {
readonly label: string;
static getHelp(): Help {
return {
name: "migrate-registrar NAME",
help: "Migrates NAME from the Legacy to Permanent Registrar"
}
}
async prepareArgs(args: Array<string>): Promise<void> {
await super.prepareArgs(args);
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
let ethLegacyRegistrar = await this.getEthLegacyRegistrar();
let state = await ethLegacyRegistrar.state(ethers.utils.id(comps[0]));
if (States[state] !== "Owned") {
this.throwError("Name not present in the Legacy registrar");
}
await super._setValue("label", comps[0]);
}
async run(): Promise<void> {
await super.run();
this.dump("Migrate Registrar: " + this.name, {
Nodehash: this.nodehash
});
let legacyRegistrar = await this.getEthLegacyRegistrar();
let tx = await legacyRegistrar.transferRegistrars(ethers.utils.id(this.label));
this.wait(tx);
}
}
cli.addPlugin("migrate-registrar", MigrateRegistrarPlugin);
class TransferPlugin extends AccountPlugin {
readonly name: string;
readonly new_owner: string;
readonly label: string;
static getHelp(): Help {
return {
name: "transfer NAME NEW_OWNER",
help: "Transfers NAME to NEW_OWNER (permanent regstrar only)"
}
}
async _setValue(key: string, value: string): Promise<void> {
if (key === "new_owner") {
let address = await this.getAddress(value);
await this._setValue(key, address);
} else if (key === "name") {
let comps = this.name.split(".");
if (comps.length !== 2 || comps[1] !== "eth") {
this.throwError("Not a top-level .eth name");
}
await super._setValue("label", comps[0]);
await super._setValue(key, value);
} else {
await super._setValue(key, value);
}
}
async run(): Promise<void> {
await super.run();
this.dump("Transfer: " + this.name, {
Nodehash: this.nodehash,
"New Owner": this.new_owner,
});
let registrar = await this.getEthRegistrar();
let tx = await registrar.transferFrom(this.accounts[0].getAddress(), this.new_owner, ethers.utils.id(this.label));
this.wait(tx);
}
}
cli.addPlugin("transfer", TransferPlugin);
/**
* migrate-registrar NAME
* transfer NAME OWNER
* register NAME --registrar
* set-subnode LABEL.NAME
* To Do:
* register NAME --registrar
* set-reverse NAME
*
* set-owner NAME OWNER
* set-resolver NAME RESOLVER
* set-addr NAME ADDRESS
* set-reverse-name ADDRESS NAME
* set-email NAME EMAIL
* set-webstie NAME WEBSITE
* set-text NAME KEY VALUE
* set-content NAME HASH
* Duration??
* Done:
* migrate-registrar NAME
* transfer NAME OWNER
* set-subnode LABEL.NAME
* set-owner NAME OWNER
* set-resolver NAME RESOLVER
* set-addr NAME ADDRESS
* set-reverse-name ADDRESS NAME
* set-email NAME EMAIL
* set-webstie NAME WEBSITE
* set-text NAME KEY VALUE
* set-content NAME HASH
*/
cli.run(process.argv.slice(2))