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)" "function resolver(bytes32 node) external view returns (address)"
]; ];
const States = Object.freeze([ "Open", "Auction", "Owned", "Forbidden", "Reveal", "NotAvailable" ]);
const ethLegacyRegistrarAbi = [ 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 state(bytes32 _hash) public view returns (uint8)",
"function transferRegistrars(bytes32 _hash) @500000",
]; ];
/*
const ethRegistrarAbi = [
"function available(uint256 id) public view returns(bool)",
];
*/
const ethControllerAbi = [ 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 rentPrice(string memory name, uint duration) view public returns(uint)",
"function available(string memory label) public view returns(bool)", "function available(string memory label) public view returns(bool)",
"function makeCommitment(string memory name, address owner, bytes32 secret) pure public returns(bytes32)", "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", "function renew(string calldata name, uint duration) payable @500000",
]; ];
const ethRegistrarAbi = [
"function transferFrom(address from, address to, uint256 tokenId)"
];
const resolverAbi = [ const resolverAbi = [
"function interfaceImplementer(bytes32 nodehash, bytes4 interfaceId) view returns (address)", "function interfaceImplementer(bytes32 nodehash, bytes4 interfaceId) view returns (address)",
"function addr(bytes32 nodehash) 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", "function setText(bytes32 nodehash, string key, string value) @500000",
]; ];
//const InterfaceID_ERC721 = "0x6ccb2df4"; const InterfaceID_ERC721 = "0x6ccb2df4";
const InterfaceID_Controller = "0x018fac06"; const InterfaceID_Controller = "0x018fac06";
const InterfaceID_Legacy = "0x7ba18ba1"; const InterfaceID_Legacy = "0x7ba18ba1";
@ -67,10 +64,55 @@ function listify(words: Array<string>): string {
} }
let cli = new CLI(); 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>; names: Array<string>;
@ -89,31 +131,8 @@ class LookupPlugin extends Plugin {
async run(): Promise<void> { async run(): Promise<void> {
await super.run(); await super.run();
let ens = new ethers.Contract(this.network.ensAddress, ensAbi, this.provider);
let ethNodehash = ethers.utils.namehash("eth"); let ens = await this.getEns();
/*
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);
});
});
for (let i = 0; i < this.names.length; i++) { for (let i = 0; i < this.names.length; i++) {
let name = this.names[i]; let name = this.names[i];
@ -129,22 +148,33 @@ class LookupPlugin extends Plugin {
if (comps.length === 2 && comps[1] === "eth") { if (comps.length === 2 && comps[1] === "eth") {
let labelhash = ethers.utils.id(comps[0].toLowerCase()); // @TODO: nameprep 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]); return ethController.available(comps[0]);
}); });
details.Available = available; details.Available = available;
details.Registrar = Promise.all([ let legacyRegistrarPromise = this.getEthLegacyRegistrar();
details._Registrar = Promise.all([
available, available,
ethLegacyRegistrarPromise.then((legacyRegistrar) => { legacyRegistrarPromise.then((legacyRegistrar) => {
return legacyRegistrar.state(labelhash); return legacyRegistrar.state(labelhash);
}) })
]).then((results) => { ]).then((results) => {
let States = [ "Open", "Auction", "Owned", "Forbidden", "Reveal", "NotAvailable" ];
let available = results[0]; let available = results[0];
let state = States[results[1]]; let state = States[results[1]];
console.log(available, state); if (!available && state === "Owned") {
return "FOO"; 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) { if (details.Resolver !== ethers.constants.AddressZero) {
let resolver = new ethers.Contract(details.Resolver, resolverAbi, this.provider); let resolver = new ethers.Contract(details.Resolver, resolverAbi, this.provider);
details.address = resolver.addr(nodehash); details.address = resolver.addr(nodehash);
details.email = resolver.text(nodehash, "email"); details.email = resolver.text(nodehash, "email").catch((error: any) => (""));
details.website = resolver.text(nodehash, "website"); details.website = resolver.text(nodehash, "website").catch((error: any) => (""));
details = await ethers.utils.resolveProperties(details);
} }
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); this.dump("Name: " + this.names[i], details);
} }
} }
} }
cli.addPlugin("lookup", LookupPlugin); cli.addPlugin("lookup", LookupPlugin);
abstract class AccountPlugin extends Plugin { abstract class AccountPlugin extends EnsPlugin {
ens: ethers.Contract;
name: string; name: string;
nodehash: string; nodehash: string;
_wait: boolean; _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> { async wait(tx: ethers.providers.TransactionResponse): Promise<void> {
if (!this._wait) { return; } if (!this._wait) { return; }
try { try {
@ -226,7 +248,6 @@ abstract class AccountPlugin extends Plugin {
await super.prepareOptions(argParser); await super.prepareOptions(argParser);
ethers.utils.defineReadOnly(this, "_wait", argParser.consumeFlag("wait")); 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> { async prepareArgs(args: Array<string>): Promise<void> {
@ -263,11 +284,11 @@ abstract class ControllerPlugin extends AccountPlugin {
[ [
{ {
name: "[ --duration DAYS ]", name: "[ --duration DAYS ]",
help: "The duration to register for (default: 365 days)" help: "Register duration (default: 365 days)"
}, },
{ {
name: "[ --salt SALT ]", name: "[ --salt SALT ]",
help: "Use SALT to blind the commit" help: "SALT to blind the commit with"
}, },
{ {
name: "[ --secret SECRET ]", name: "[ --secret SECRET ]",
@ -275,7 +296,7 @@ abstract class ControllerPlugin extends AccountPlugin {
}, },
{ {
name: "[ --owner OWNER ]", name: "[ --owner OWNER ]",
help: "Set the OWNER (default: current account)" help: "The target owner (default: current account)"
} }
].forEach((help) => { ].forEach((help) => {
result.push(help); result.push(help);
@ -476,7 +497,9 @@ class SetOwnerPlugin extends AddressAccountPlugin {
async run(): Promise<void> { async run(): Promise<void> {
await super.run(); 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); this.wait(tx);
} }
} }
@ -498,7 +521,6 @@ class SetSubnodePlugin extends AddressAccountPlugin {
let comps = value.toLowerCase().split("."); let comps = value.toLowerCase().split(".");
await super._setValue("label", comps[0]); await super._setValue("label", comps[0]);
await super._setValue("node", comps.slice(1).join(".")); await super._setValue("node", comps.slice(1).join("."));
} else {
} }
await super._setValue(key, value); await super._setValue(key, value);
} }
@ -511,7 +533,8 @@ class SetSubnodePlugin extends AddressAccountPlugin {
Node: this.node 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); this.wait(tx);
} }
} }
@ -537,7 +560,8 @@ class SetResolverPlugin extends AddressAccountPlugin {
Resolver: this.address 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); this.wait(tx);
} }
@ -561,7 +585,7 @@ class SetAddrPlugin extends AddressAccountPlugin {
Address: this.address Address: this.address
}); });
let resolver = await this.getResolver(); let resolver = await this.getResolver(this.nodehash);
let tx = await resolver.setAddr(this.nodehash, this.address); let tx = await resolver.setAddr(this.nodehash, this.address);
this.wait(tx); this.wait(tx);
} }
@ -585,7 +609,7 @@ abstract class TextAccountPlugin extends AccountPlugin {
Value: value Value: value
}); });
let resolver = await this.getResolver(); let resolver = await this.getResolver(this.nodehash);
let tx = await resolver.setText(this.nodehash, key, value); let tx = await resolver.setText(this.nodehash, key, value);
this.wait(tx); this.wait(tx);
} }
@ -641,6 +665,8 @@ class SetWebsitePlugin extends TextAccountPlugin {
cli.addPlugin("set-website", SetWebsitePlugin); cli.addPlugin("set-website", SetWebsitePlugin);
/*
// @TODO:
class SetContentHashPlugin extends AccountPlugin { class SetContentHashPlugin extends AccountPlugin {
hash: string; hash: string;
@ -660,13 +686,106 @@ class SetContentHashPlugin extends AccountPlugin {
} }
} }
cli.addPlugin("set-content", SetContentHashPlugin); 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);
/** /**
* To Do:
* register NAME --registrar
* set-reverse NAME
*
* Done:
* migrate-registrar NAME * migrate-registrar NAME
* transfer NAME OWNER * transfer NAME OWNER
* register NAME --registrar
* set-subnode LABEL.NAME * set-subnode LABEL.NAME
*
* set-owner NAME OWNER * set-owner NAME OWNER
* set-resolver NAME RESOLVER * set-resolver NAME RESOLVER
* set-addr NAME ADDRESS * set-addr NAME ADDRESS
@ -675,7 +794,6 @@ cli.addPlugin("set-content", SetContentHashPlugin);
* set-webstie NAME WEBSITE * set-webstie NAME WEBSITE
* set-text NAME KEY VALUE * set-text NAME KEY VALUE
* set-content NAME HASH * set-content NAME HASH
* Duration??
*/ */
cli.run(process.argv.slice(2)) cli.run(process.argv.slice(2))