diff --git a/lib/cmd.js b/lib/cmd.js index 3500f3b13..b75448262 100644 --- a/lib/cmd.js +++ b/lib/cmd.js @@ -219,6 +219,7 @@ class Cmd { upload() { program .command('upload [environment]') + .option('--ens [ensDomain]', __('ENS domain to associate to')) .option('--logfile [logfile]', __('filename to output logs (default: %s)', 'none')) .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'debug') .option('--locale [locale]', __('language to use (default: en)')) @@ -231,6 +232,7 @@ class Cmd { console.warn("In embark 3.1 forwards, the correct command is embark upload and the provider is configured in config/storage.js"); } _options.env = env || 'development'; + _options.ensDomain = _options.ens; _options.logFile = _options.logfile; // fix casing _options.logLevel = _options.loglevel; // fix casing _options.client = _options.client || 'geth'; diff --git a/lib/modules/ens/index.js b/lib/modules/ens/index.js index 77b759fda..42cf4c3a0 100644 --- a/lib/modules/ens/index.js +++ b/lib/modules/ens/index.js @@ -20,46 +20,132 @@ class ENS { } registerEvents() { - const self = this; - self.embark.registerActionForEvent("contracts:deploy:afterAll", (cb) => { - async.parallel([ - function getENSRegistry(paraCb) { - self.events.request('contracts:contract', "ENSRegistry", (contract) => { - paraCb(null, contract); - }); - }, - function getRegistrar(paraCb) { - self.events.request('contracts:contract', "FIFSRegistrar", (contract) => { - paraCb(null, contract); - }); - }, - function getResolver(paraCb) { - self.events.request('contracts:contract', "Resolver", (contract) => { - paraCb(null, contract); - }); - } - ], (err, results) => { - // result[0] => ENSRegistry; result[1] => FIFSRegistrar; result[2] => FIFSRegistrar - let config = { - env: self.env, - registration: self.registration, - registryAbi: results[0].abiDefinition, - registryAddress: results[0].deployedAddress, - registrarAbi: results[1].abiDefinition, - registrarAddress: results[1].deployedAddress, - resolverAbi: results[2].abiDefinition, - resolverAddress: results[2].deployedAddress - }; - self.addSetProvider(config); + this.embark.registerActionForEvent("contracts:deploy:afterAll", this.setProviderAndRegisterDomains.bind(this)); - if (!self.env === 'development' || !self.registration || !self.registration.subdomains || !Object.keys(self.registration.subdomains).length) { - return cb(); - } - self.registerConfigDomains(config, cb); - }); + this.embark.registerActionForEvent("storage:ens:associate", this.associateStorageToEns.bind(this)); + } + + setProviderAndRegisterDomains(cb) { + const self = this; + async.parallel([ + function getENSRegistry(paraCb) { + self.events.request('contracts:contract', "ENSRegistry", (contract) => { + paraCb(null, contract); + }); + }, + function getRegistrar(paraCb) { + self.events.request('contracts:contract', "FIFSRegistrar", (contract) => { + paraCb(null, contract); + }); + }, + function getResolver(paraCb) { + self.events.request('contracts:contract', "Resolver", (contract) => { + paraCb(null, contract); + }); + } + ], (err, results) => { + // result[0] => ENSRegistry; result[1] => FIFSRegistrar; result[2] => FIFSRegistrar + let config = { + env: self.env, + registration: self.registration, + registryAbi: results[0].abiDefinition, + registryAddress: results[0].deployedAddress, + registrarAbi: results[1].abiDefinition, + registrarAddress: results[1].deployedAddress, + resolverAbi: results[2].abiDefinition, + resolverAddress: results[2].deployedAddress + }; + self.addSetProvider(config); + + if (!self.env === 'development' || !self.registration || !self.registration.subdomains || !Object.keys(self.registration.subdomains).length) { + return cb(); + } + self.registerConfigDomains(config, cb); }); } + associateStorageToEns(options, cb) { + // Code inspired by https://github.com/monkybrain/ipfs-to-ens + const {name, ipfsHash} = options; + + if (!ENS.isValidDomain(name)) { + return cb('Invalid domain name ' + name); + } + + let namehash = namehash.hash(name); + + // Convert IPFS multihash to 32 byte hex string + let contentHash; + try { + contentHash = ENS.hashTo32ByteHexString(ipfsHash); + } catch(e) { + return cb('Invalid IPFS hash'); + } + + // Set content + async.waterfall([ + function getRegistryABI(next) { + self.events.request('contracts:contract', "ENSRegistry", (contract) => { + next(null, contract); + }); + }, + function createRegistryContract(contract, next) { + self.events.request("blockchain:contract:create", + {abi: contract.abiDefinition, address: contract.deployedAddress}, + (resolver) => { + next(null, resolver); + }); + }, + function getResolverForName(registry, next) { + this.ens.methods.resolver(namehash).call((err, resolverAddress) => { + if (err) { + return cb(err); + } + if (resolverAddress === '0x0000000000000000000000000000000000000000') { + return cb('Name not yet registered'); + } + next(null, resolverAddress); + }); + }, + function getResolverABI(resolverAddress, next) { + self.events.request('contracts:contract', "Resolver", (contract) => { + next(null, resolverAddress, contract); + }); + }, + function createResolverContract(resolverAddress, contract, next) { + self.events.request("blockchain:contract:create", + {abi: contract.abiDefinition, address: resolverAddress}, + (resolver) => { + next(null, resolver); + }); + }, + function getDefaultAccount(resolver, next) { + self.events.request("blockchain:defaultAccount:get", (defaultAccount) => { + next(null, resolver, defaultAccount); + }); + }, + function setContent(resolver, defaultAccount, next) { + resolver.methods.setContent(namehash, contentHash).send({from: defaultAccount}).then(next); + } + ], cb); + } + + static isValidDomain(domain) { + const isValidDomain = require('is-valid-domain'); + if (!isValidDomain(domain)) { + return false; + } else { + return domain.substring(domain.lastIndexOf('.'), domain.length) === '.eth'; + } + } + + static hashTo32ByteHexString(ipfsHash) { + const multihash = require('multihashes'); + let buf = multihash.fromB58String(ipfsHash); + let digest = multihash.decode(buf).digest; + return '0x' + multihash.toHexString(digest); + } + registerConfigDomains(config, cb) { const self = this; const register = require('./register'); diff --git a/package-lock.json b/package-lock.json index 6a9208275..609bb0e49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6476,6 +6476,11 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "is-valid-domain": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/is-valid-domain/-/is-valid-domain-0.0.5.tgz", + "integrity": "sha1-SOcDGfy0MAkjbpazf5hDiJzntRM=" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", diff --git a/package.json b/package.json index c610f4e4e..050feac4e 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,11 @@ "http-shutdown": "^1.2.0", "i18n": "^0.8.3", "ipfs-api": "17.2.4", + "is-valid-domain": "0.0.5", "live-plugin-manager-git-fix": "^0.12.1", "merge": "^1.2.0", "mocha": "^5.2.0", + "multihashes": "^0.4.13", "neo-blessed": "^0.2.0", "netcat": "^1.3.5", "node-ipc": "^9.1.1",