diff --git a/packages/cli/src.ts/bin/ethers-ens.ts b/packages/cli/src.ts/bin/ethers-ens.ts index 4ab690106..85a199053 100644 --- a/packages/cli/src.ts/bin/ethers-ens.ts +++ b/packages/cli/src.ts/bin/ethers-ens.ts @@ -42,7 +42,8 @@ const ethControllerAbi = [ const ethRegistrarAbi = [ "function ownerOf(uint256 tokenId) view returns (address)", "function reclaim(uint256 id, address owner) @500000", - "function safeTransferFrom(address from, address to, uint256 tokenId) @500000" + "function safeTransferFrom(address from, address to, uint256 tokenId) @500000", + "function nameExpires(uint256 id) external view returns(uint)" ]; const resolverAbi = [ @@ -867,6 +868,140 @@ class ReclaimPlugin extends AddressAccountPlugin { } cli.addPlugin("reclaim", ReclaimPlugin); +function zpad(value: number, length: number): string { + let v = String(value); + while (v.length < length) { v = "0" + v; } + return v; +} + +function formatDate(date: Date): string { + const count = Math.round((date.getTime() - (new Date()).getTime()) / (24 * 60 * 60 * 1000)) + return [ + date.getFullYear(), + zpad(date.getMonth() + 1, 2), + zpad(date.getDate(), 2) + ].join("-") + ` (${ count } days from now)`; +} + +class RenewPlugin extends EnsPlugin { + labels: ReadonlyArray; + + // Only one of these two will be set + duration: number; + until: string; + + static getHelp(): Help { + return { + name: "renew NAME [ NAME ... ]", + help: "Reset the controller by the registrant" + } + } + + static getOptionHelp(): Array { + return [ + { + name: "[ --duration DAYS ]", + help: "Register duration (default: 365 days)" + }, + { + name: "[ --until YYYY-MM-DD ]", + help: "Register until date" + }, + ]; + } + + getDuration(startDate: number, until: string): number { + const match = until.match(/^(\d\d\d\d)-(\d\d)-(\d\d)$/); + if (!match) { + this.throwError("invalid date format; use YYYY-MM-DD"); + } + const year = parseInt(match[1]); + const month = parseInt(match[2]); + const day = parseInt(match[3]); + // Not perfect; allow February 30 or April 31 @TODO? + if (month < 1 || month > 12 || day < 1 || day > 31) { + this.throwError("date out of range"); + } + + const endDate = (new Date(year, month - 1, day)).getTime() / 1000; + + return Math.ceil(endDate - startDate); + } + + async prepareOptions(argParser: ArgParser): Promise { + await super.prepareOptions(argParser); + + if (this.accounts.length !== 1) { + this.throwError("new requires ONE account"); + } + + const timespans = argParser.consumeMultiOptions([ "duration", "until" ]); + + if (timespans.length === 1) { + const timespan = timespans.pop(); + if (timespan.name === "duration") { + this.duration = parseInt(timespan.value) * 60 * 60 * 24; + } else if (timespan.name === "until") { + this.until = timespan.value; + } + } else if (timespans.length > 1) { + this.throwError("renew requires at most ONE of --duration or --until"); + } else { + this.duration = 365 * 60 * 60 * 24; + } + } + + async prepareArgs(args: Array): Promise { + await super.prepareArgs(args); + const labels: Array = [ ]; + args.forEach((arg) => { + const comps = arg.split("."); + if (comps.length !== 2 || comps[1] !== "eth") { + this.throwError(`name not supported ${ JSON.stringify(arg) }`); + } + labels.push(comps[0]); + }); + + this.labels = Object.freeze(labels); + } + + async run(): Promise { + await super.run(); + + const ethController = await this.getEthController(); + const ethRegistrar = await this.getEthRegistrar(); + + for (let i = 0; i < this.labels.length; i++) { + const label = this.labels[i]; + console.log(label); + + const expiration = (await ethRegistrar.nameExpires(ethers.utils.id(label))).toNumber(); + if (expiration === 0) { + this.throwError(`not registered: ${ label }`); + } + + const duration = this.duration ? this.duration: this.getDuration(expiration, this.until); + if (duration < 0) { + this.throwError(`bad duration: ${ duration }`); + } + + const fee = (await ethController.rentPrice(label, duration)).mul(11).div(10); + + this.dump(`Renew: ${ label }.eth`, { + "Current Expiry": formatDate(new Date(expiration * 1000)), + "Duration": `${ (duration / (24 * 60 * 60)) } days`, + "Until": formatDate(new Date((expiration + duration) * 1000)), + "Fee": `${ ethers.utils.formatEther(fee) } (+10% buffer)`, + }); + + await ethController.renew(label, duration, { + value: fee + }); + } + } +} +cli.addPlugin("renew", RenewPlugin); + /** * To Do: * register NAME --registrar