index.js
1 const fs = require('../../core/fs.js'); 2 const utils = require('../../utils/utils.js'); 3 const namehash = require('eth-ens-namehash'); 4 const async = require('async'); 5 const embarkJsUtils = require('embarkjs').Utils; 6 const reverseAddrSuffix = '.addr.reverse'; 7 const ENSFunctions = require('./ENSFunctions'); 8 9 const MAINNET_ID = '1'; 10 const ROPSTEN_ID = '3'; 11 const RINKEBY_ID = '4'; 12 // Price of ENS registration contract functions 13 const ENS_GAS_PRICE = 700000; 14 15 const ENS_CONTRACTS_CONFIG = { 16 [MAINNET_ID]: { 17 "ENSRegistry": { 18 "address": "0x314159265dd8dbb310642f98f50c066173c1259b", 19 "silent": true 20 }, 21 "Resolver": { 22 "deploy": false 23 }, 24 "FIFSRegistrar": { 25 "deploy": false 26 } 27 }, 28 [ROPSTEN_ID]: { 29 "ENSRegistry": { 30 "address": "0x112234455c3a32fd11230c42e7bccd4a84e02010", 31 "silent": true 32 }, 33 "Resolver": { 34 "deploy": false 35 }, 36 "FIFSRegistrar": { 37 "deploy": false 38 } 39 }, 40 [RINKEBY_ID]: { 41 "ENSRegistry": { 42 "address": "0xe7410170f87102DF0055eB195163A03B7F2Bff4A", 43 "silent": true 44 }, 45 "Resolver": { 46 "deploy": false 47 }, 48 "FIFSRegistrar": { 49 "deploy": false 50 } 51 } 52 }; 53 54 class ENS { 55 constructor(embark, _options) { 56 this.env = embark.env; 57 this.logger = embark.logger; 58 this.events = embark.events; 59 this.namesConfig = embark.config.namesystemConfig; 60 this.enabled = false; 61 this.registration = this.namesConfig.register || {}; 62 this.embark = embark; 63 this.ensConfig = require('./ensContractConfigs'); 64 this.configured = false; 65 66 this.events.setCommandHandler("ens:resolve", this.ensResolve.bind(this)); 67 68 if (this.namesConfig === {} || 69 this.namesConfig.enabled !== true || 70 this.namesConfig.available_providers.indexOf('ens') < 0) { 71 return; 72 } 73 this.enabled = true; 74 this.doSetENSProvider = this.namesConfig.provider === 'ens'; 75 76 this.addENSToEmbarkJS(); 77 this.registerEvents(); 78 this.registerConsoleCommands(); 79 } 80 81 reset() { 82 this.configured = false; 83 } 84 85 registerConsoleCommands() { 86 this.embark.registerConsoleCommand((cmd, _options) => { 87 let [cmdName, domain] = cmd.split(' '); 88 return { 89 match: () => cmdName === 'resolve', 90 process: (cb) => global.EmbarkJS.Names.resolve(domain, cb) 91 }; 92 }); 93 94 this.embark.registerConsoleCommand((cmd, _options) => { 95 let [cmdName, address] = cmd.split(' '); 96 return { 97 match: () => cmdName === 'lookup', 98 process: (cb) => global.EmbarkJS.Names.lookup(address, cb) 99 }; 100 }); 101 102 this.embark.registerConsoleCommand((cmd, _options) => { 103 let [cmdName, name, address] = cmd.split(' '); 104 return { 105 match: () => cmdName === 'registerSubDomain', 106 process: (cb) => global.EmbarkJS.Names.registerSubDomain(name, address, cb) 107 }; 108 }); 109 } 110 111 registerEvents() { 112 this.embark.registerActionForEvent("deploy:beforeAll", this.configureContractsAndRegister.bind(this)); 113 this.events.on('blockchain:reseted', this.reset.bind(this)); 114 this.events.setCommandHandler("storage:ens:associate", this.associateStorageToEns.bind(this)); 115 } 116 117 setProviderAndRegisterDomains(cb = (() => {})) { 118 const self = this; 119 let config = { 120 env: self.env, 121 registration: self.registration, 122 registryAbi: self.ensConfig.ENSRegistry.abiDefinition, 123 registryAddress: self.ensConfig.ENSRegistry.deployedAddress, 124 registrarAbi: self.ensConfig.FIFSRegistrar.abiDefinition, 125 registrarAddress: self.ensConfig.FIFSRegistrar.deployedAddress, 126 resolverAbi: self.ensConfig.Resolver.abiDefinition, 127 resolverAddress: self.ensConfig.Resolver.deployedAddress 128 }; 129 130 if (self.doSetENSProvider) { 131 self.addSetProvider(config); 132 } 133 134 self.events.request('blockchain:networkId', (networkId) => { 135 const isKnownNetwork = Boolean(ENS_CONTRACTS_CONFIG[networkId]); 136 const shouldRegisterSubdomain = self.registration && self.registration.subdomains && Object.keys(self.registration.subdomains).length; 137 if (isKnownNetwork || !shouldRegisterSubdomain) { 138 return cb(); 139 } 140 141 self.registerConfigDomains(config, cb); 142 }); 143 } 144 145 associateStorageToEns(options, cb) { 146 const self = this; 147 // Code inspired by https://github.com/monkybrain/ipfs-to-ens 148 const {name, storageHash} = options; 149 150 if (!utils.isValidEthDomain(name)) { 151 return cb('Invalid domain name ' + name); 152 } 153 154 let hashedName = namehash.hash(name); 155 let contentHash; 156 try { 157 contentHash = utils.hashTo32ByteHexString(storageHash); 158 } catch (e) { 159 return cb('Invalid IPFS hash'); 160 } 161 // Set content 162 async.waterfall([ 163 function getRegistryABI(next) { 164 self.events.request('contracts:contract', "ENSRegistry", (contract) => { 165 next(null, contract); 166 }); 167 }, 168 function createRegistryContract(contract, next) { 169 self.events.request("blockchain:contract:create", 170 {abi: contract.abiDefinition, address: contract.deployedAddress}, 171 (resolver) => { 172 next(null, resolver); 173 }); 174 }, 175 function getResolverForName(registry, next) { 176 registry.methods.resolver(hashedName).call((err, resolverAddress) => { 177 if (err) { 178 return cb(err); 179 } 180 if (resolverAddress === '0x0000000000000000000000000000000000000000') { 181 return cb('Name not yet registered'); 182 } 183 next(null, resolverAddress); 184 }); 185 }, 186 function getResolverABI(resolverAddress, next) { 187 self.events.request('contracts:contract', "Resolver", (contract) => { 188 next(null, resolverAddress, contract); 189 }); 190 }, 191 function createResolverContract(resolverAddress, contract, next) { 192 self.events.request("blockchain:contract:create", 193 {abi: contract.abiDefinition, address: resolverAddress}, 194 (resolver) => { 195 next(null, resolver); 196 }); 197 }, 198 function getDefaultAccount(resolver, next) { 199 self.events.request("blockchain:defaultAccount:get", (defaultAccount) => { 200 next(null, resolver, defaultAccount); 201 }); 202 }, 203 function setContent(resolver, defaultAccount, next) { 204 resolver.methods.setContent(hashedName, contentHash).send({from: defaultAccount}).then((transaction) => { 205 if (transaction.status !== "0x1" && transaction.status !== "0x01" && transaction.status !== true) { 206 return next('Association failed. Status: ' + transaction.status); 207 } 208 next(); 209 }).catch(next); 210 } 211 ], cb); 212 } 213 214 registerConfigDomains(config, cb) { 215 const self = this; 216 const secureSend = embarkJsUtils.secureSend; 217 218 self.events.request("blockchain:defaultAccount:get", (defaultAccount) => { 219 async.each(Object.keys(self.registration.subdomains), (subDomainName, eachCb) => { 220 const address = self.registration.subdomains[subDomainName]; 221 const reverseNode = utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix); 222 ENSFunctions.registerSubDomain(self.ensContract, self.registrarContract, self.resolverContract, defaultAccount, 223 subDomainName, self.registration.rootDomain, reverseNode, address, self.logger, secureSend, eachCb); 224 }, cb); 225 }); 226 } 227 228 createResolverContract(config, callback) { 229 this.events.request("blockchain:contract:create", { 230 abi: config.resolverAbi, 231 address: config.resolverAddress 232 }, (resolver) => { 233 callback(null, resolver); 234 }); 235 } 236 237 registerAPI() { 238 let self = this; 239 240 const createInternalResolverContract = function(resolverAddress, callback) { 241 self.createResolverContract({resolverAbi: self.ensConfig.Resolver.abiDefinition, resolverAddress}, callback); 242 }; 243 244 self.embark.registerAPICall( 245 'get', 246 '/embark-api/ens/resolve', 247 (req, res) => { 248 async.waterfall([ 249 function(callback) { 250 ENSFunctions.resolveName(req.query.name, self.ensContract, createInternalResolverContract.bind(self), callback); 251 } 252 ], function(error, address) { 253 if (error) { 254 return res.send({error: error.message}); 255 } 256 res.send({address}); 257 }); 258 } 259 ); 260 261 self.embark.registerAPICall( 262 'get', 263 '/embark-api/ens/lookup', 264 (req, res) => { 265 async.waterfall([ 266 function(callback) { 267 ENSFunctions.lookupAddress(req.query.address, self.ensContract, utils, createInternalResolverContract.bind(self), callback); 268 } 269 ], function(error, name) { 270 if (error) { 271 return res.send({error: error || error.message}); 272 } 273 res.send({name}); 274 }); 275 } 276 ); 277 278 self.embark.registerAPICall( 279 'post', 280 '/embark-api/ens/register', 281 (req, res) => { 282 self.events.request("blockchain:defaultAccount:get", (defaultAccount) => { 283 const secureSend = embarkJsUtils.secureSend; 284 const {subdomain, address} = req.body; 285 const reverseNode = utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix); 286 ENSFunctions.registerSubDomain(self.ensContract, self.registrarContract, self.resolverContract, defaultAccount, 287 subdomain, self.registration.rootDomain, reverseNode, address, self.logger, secureSend, (error) => { 288 if (error) { 289 return res.send({error: error || error.message}); 290 } 291 res.send({name: `${req.body.subdomain}.${self.registration.rootDomain}`, address: req.body.address}); 292 }); 293 }); 294 } 295 ); 296 } 297 298 addENSToEmbarkJS() { 299 const self = this; 300 301 // get namehash, import it into file 302 self.events.request("version:get:eth-ens-namehash", function(EnsNamehashVersion) { 303 let currentEnsNamehashVersion = require('../../../package.json').dependencies["eth-ens-namehash"]; 304 if (EnsNamehashVersion !== currentEnsNamehashVersion) { 305 self.events.request("version:getPackageLocation", "eth-ens-namehash", EnsNamehashVersion, function(err, location) { 306 self.embark.registerImportFile("eth-ens-namehash", fs.dappPath(location)); 307 }); 308 } 309 }); 310 311 let code = fs.readFileSync(utils.joinPath(__dirname, 'ENSFunctions.js')).toString(); 312 code += "\n" + fs.readFileSync(utils.joinPath(__dirname, 'embarkjs.js')).toString(); 313 code += "\nEmbarkJS.Names.registerProvider('ens', __embarkENS);"; 314 315 this.embark.addCodeToEmbarkJS(code); 316 } 317 318 configureContractsAndRegister(cb) { 319 const NO_REGISTRATION = 'NO_REGISTRATION'; 320 const self = this; 321 if (self.configured) { 322 return cb(); 323 } 324 self.events.request('blockchain:networkId', (networkId) => { 325 if (ENS_CONTRACTS_CONFIG[networkId]) { 326 self.ensConfig = utils.recursiveMerge(self.ensConfig, ENS_CONTRACTS_CONFIG[networkId]); 327 } 328 329 async.waterfall([ 330 function registry(next) { 331 self.events.request('deploy:contract', self.ensConfig.ENSRegistry, (err, _receipt) => { 332 return next(err); 333 }); 334 }, 335 function resolver(next) { 336 self.ensConfig.Resolver.args = [self.ensConfig.ENSRegistry.deployedAddress]; 337 self.events.request('deploy:contract', self.ensConfig.Resolver, (err, _receipt) => { 338 return next(err); 339 }); 340 }, 341 function registrar(next) { 342 if (!self.registration || !self.registration.rootDomain) { 343 return next(NO_REGISTRATION); 344 } 345 const registryAddress = self.ensConfig.ENSRegistry.deployedAddress; 346 const rootNode = namehash.hash(self.registration.rootDomain); 347 const contract = self.ensConfig.FIFSRegistrar; 348 contract.args = [registryAddress, rootNode]; 349 350 self.events.request('deploy:contract', contract, (err, _receipt) => { 351 return next(err); 352 }); 353 }, 354 function registerRoot(next) { 355 let config = { 356 registryAbi: self.ensConfig.ENSRegistry.abiDefinition, 357 registryAddress: self.ensConfig.ENSRegistry.deployedAddress, 358 registrarAbi: self.ensConfig.FIFSRegistrar.abiDefinition, 359 registrarAddress: self.ensConfig.FIFSRegistrar.deployedAddress, 360 resolverAbi: self.ensConfig.Resolver.abiDefinition, 361 resolverAddress: self.ensConfig.Resolver.deployedAddress 362 }; 363 async.parallel([ 364 function createRegistryContract(paraCb) { 365 self.events.request("blockchain:contract:create", 366 {abi: config.registryAbi, address: config.registryAddress}, 367 (registry) => { 368 paraCb(null, registry); 369 }); 370 }, 371 function createRegistrarContract(paraCb) { 372 self.events.request("blockchain:contract:create", 373 {abi: config.registrarAbi, address: config.registrarAddress}, 374 (registrar) => { 375 paraCb(null, registrar); 376 }); 377 }, 378 function createResolverContract(paraCb) { 379 self.events.request("blockchain:contract:create", 380 {abi: config.resolverAbi, address: config.resolverAddress}, 381 (resolver) => { 382 paraCb(null, resolver); 383 }); 384 }, 385 function getWeb3(paraCb) { 386 self.events.request("blockchain:get", 387 (web3) => { 388 paraCb(null, web3); 389 }); 390 } 391 ], (err, result) => { 392 self.ensContract = result[0]; 393 self.registrarContract = result[1]; 394 self.resolverContract = result[2]; 395 const web3 = result[3]; 396 397 const rootNode = namehash.hash(self.registration.rootDomain); 398 var reverseNode = web3.utils.soliditySha3(web3.eth.defaultAccount.toLowerCase().substr(2) + reverseAddrSuffix); 399 self.ensContract.methods.setOwner(rootNode, web3.eth.defaultAccount).send({from: web3.eth.defaultAccount, gas: ENS_GAS_PRICE}).then(() => { 400 return self.ensContract.methods.setResolver(rootNode, config.resolverAddress).send({from: web3.eth.defaultAccount, gas: ENS_GAS_PRICE}); 401 }).then(() => { 402 return self.ensContract.methods.setResolver(reverseNode, config.resolverAddress).send({from: web3.eth.defaultAccount, gas: ENS_GAS_PRICE}); 403 }).then(() => { 404 return self.resolverContract.methods.setAddr(rootNode, web3.eth.defaultAccount).send({from: web3.eth.defaultAccount, gas: ENS_GAS_PRICE}); 405 }).then(() => { 406 return self.resolverContract.methods.setName(reverseNode, self.registration.rootDomain).send({from: web3.eth.defaultAccount, gas: ENS_GAS_PRICE}); 407 }).then((_result) => { 408 next(); 409 }) 410 .catch(err => { 411 self.logger.error('Error while registering the root domain'); 412 if (err.message.indexOf('Transaction has been reverted by the EVM') > -1) { 413 return next(__('Registration was rejected. Did you change the deployment account? If so, delete chains.json')); 414 } 415 next(err); 416 }); 417 }); 418 } 419 ], (err) => { 420 self.configured = true; 421 if (err && err !== NO_REGISTRATION) { 422 self.logger.error('Error while deploying ENS contracts'); 423 self.logger.error(err.message || err); 424 return cb(err); 425 } 426 self.registerAPI(); 427 self.setProviderAndRegisterDomains(cb); 428 }); 429 }); 430 } 431 432 addSetProvider(config) { 433 let code = "\nEmbarkJS.Names.setProvider('ens'," + JSON.stringify(config) + ");"; 434 435 let shouldInit = (namesConfig) => { 436 return (namesConfig.provider === 'ens' && namesConfig.enabled === true); 437 }; 438 439 this.embark.addProviderInit('names', code, shouldInit); 440 this.embark.addConsoleProviderInit('names', code, shouldInit); 441 } 442 443 ensResolve(name, cb) { 444 const self = this; 445 if (!self.enabled) { 446 return cb('ENS not enabled'); 447 } 448 if(!self.configured) { 449 return cb('ENS not configured'); 450 } 451 const hashedName = namehash.hash(name); 452 async.waterfall([ 453 function getResolverAddress(next) { 454 self.ensContract.methods.resolver(hashedName).call((err, resolverAddress) => { 455 if (err) { 456 return next(err); 457 } 458 if(resolverAddress === '0x0000000000000000000000000000000000000000') { 459 return next('Name not yet registered'); 460 } 461 next(null, resolverAddress); 462 }); 463 }, 464 function createResolverContract(resolverAddress, next) { 465 self.events.request("blockchain:contract:create", 466 {abi: self.ensConfig.Resolver.abiDefinition, address: resolverAddress}, 467 (resolver) => { 468 next(null, resolver); 469 }); 470 }, 471 function resolveName(resolverContract, next) { 472 resolverContract.methods.addr(hashedName).call(next); 473 } 474 ], (err, result) => { 475 if (err) { 476 self.logger.error(__('Failed to resolve the ENS name: %s', name)); 477 return cb(err); 478 } 479 cb(null, result); 480 }); 481 } 482 } 483 484 module.exports = ENS; 485 486