diff --git a/docs/web3-eth-ens.rst b/docs/web3-eth-ens.rst index 5292340213d..9ff6c459e0c 100644 --- a/docs/web3-eth-ens.rst +++ b/docs/web3-eth-ens.rst @@ -938,6 +938,134 @@ For further information on the handling of contract events please see :ref:`here ------------------------------------------------------------------------------ +getContenthash +===================== + +.. code-block:: javascript + + web3.eth.ens.getContenthash(ENSName [, callback]); + +Returns the content hash object associated with an ENS node. + +---------- +Parameters +---------- + +1. ``ENSName`` - ``String``: The ENS name. +2. ``callback`` - ``Function``: (optional) Optional callback + +------- +Returns +------- + +``Promise`` - The content hash object associated with an ENS node. + +------- +Example +------- + +.. code-block:: javascript + + web3.eth.ens.getContenthash('ethereum.eth').then(function (result) { + console.log(result); + }); + > { + "protocolType": "ipfs", + "decoded": "QmaEBknbGT4bTQiQoe2VNgBJbRfygQGktnaW5TbuKixjYL" + } + +------------------------------------------------------------------------------ + +setContenthash +===================== + +.. code-block:: javascript + + web3.eth.ens.setContenthash(ENSName, hash [, txConfig ] [, callback]); + +Sets the content hash associated with an ENS node. + +---------- +Parameters +---------- + +1. ``ENSName`` - ``String``: The ENS name. +2. ``hash`` - ``String``: The content hash to set. +3. ``txConfig`` - ``Object``: (optional) The transaction options as described ::ref::`here ` +4. ``callback`` - ``Function``: (optional) Optional callback + +Emits a ``ContenthashChanged`` event. + +Supports the following protocols as valid ``hash`` inputs: + +1. ``ipfs://`` - example: ipfs://QmaEBknbGT4bTQiQoe2VNgBJbRfygQGktnaW5TbuKixjYL +2. ``/ipfs/`` - example: /ipfs/QmaEBknbGT4bTQiQoe2VNgBJbRfygQGktnaW5TbuKixjYL +3. ``bzz://`` - example: bzz://d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162 +4. ``onion://`` - example: onion://3g2upl4pq6kufc4m +5. ``onion3://`` - exmaple: onion3://p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd + +------- +Returns +------- + +``PromiEvent`` + +------- +Example +------- + +.. code-block:: javascript + + web3.eth.ens.setContenthash( + 'ethereum.eth', + 'ipfs://QmaEBknbGT4bTQiQoe2VNgBJbRfygQGktnaW5TbuKixjYL', + { + from: '0x9CC9a2c777605Af16872E0997b3Aeb91d96D5D8c' + } + ).then(function (result) { + console.log(result.events); + }); + > ContenthashChanged(...) + + // Or using the event emitter + + web3.eth.ens.setContenthash( + 'ethereum.eth', + 'ipfs://QmaEBknbGT4bTQiQoe2VNgBJbRfygQGktnaW5TbuKixjYL', + { + from: '0x9CC9a2c777605Af16872E0997b3Aeb91d96D5D8c' + } + ) + .on('transactionHash', function(hash){ + ... + }) + .on('confirmation', function(confirmationNumber, receipt){ + ... + }) + .on('receipt', function(receipt){ + ... + }) + .on('error', console.error); + + // Or listen to the ContenthashChanged event on the resolver + + web3.eth.ens.resolver('ethereum.eth').then(function (resolver) { + resolver.events.ContenthashChanged({fromBlock: 0}, function(error, event) { + console.log(event); + }) + .on('data', function(event){ + console.log(event); + }) + .on('changed', function(event){ + // remove event from local database + }) + .on('error', console.error); + }); + + +For further information on the handling of contract events please see :ref:`here `. + + getMultihash ===================== diff --git a/package-lock.json b/package-lock.json index 708f6450d00..4ed2bf65b0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -837,9 +837,9 @@ } }, "@ensdomains/resolver": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@ensdomains/resolver/-/resolver-0.1.13.tgz", - "integrity": "sha512-VcMygGO/b0H4AXkN4CRAHw0CZd5XvTJW8YdIdZEmpJGs/O3eMzBzxpgJJ7UerD7U098rbBDSJmj0zjGudV0/aQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@ensdomains/resolver/-/resolver-0.2.4.tgz", + "integrity": "sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA==", "dev": true }, "@evocateur/libnpmaccess": { diff --git a/package.json b/package.json index c54e9e1d5e5..f01b08ebca8 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "@babel/core": "^7.6.4", "@babel/preset-env": "^7.6.3", "@ensdomains/ens": "^0.4.0", - "@ensdomains/resolver": "^0.1.13", + "@ensdomains/resolver": "^0.2.4", "@types/bignumber.js": "^4.0.2", "@types/bn.js": "^4.11.5", "@types/node": "^12.6.1", diff --git a/packages/web3-core-helpers/src/errors.js b/packages/web3-core-helpers/src/errors.js index d6bb1d22c4d..a093a61a662 100644 --- a/packages/web3-core-helpers/src/errors.js +++ b/packages/web3-core-helpers/src/errors.js @@ -106,5 +106,8 @@ module.exports = { }, TransactionOutOfGasError: function(receipt) { return this.TransactionError('Transaction ran out of gas. Please provide more gas:\n' + JSON.stringify(receipt, null, 2), receipt); + }, + ResolverMethodMissingError: function(address, name) { + return new Error('The resolver at ' + address + 'does not implement requested method: "' + name + '".'); } }; diff --git a/packages/web3-core-helpers/types/index.d.ts b/packages/web3-core-helpers/types/index.d.ts index 914c5d2e465..2995efba578 100644 --- a/packages/web3-core-helpers/types/index.d.ts +++ b/packages/web3-core-helpers/types/index.d.ts @@ -80,6 +80,7 @@ export class errors { static ContractCodeNotStoredError(receipt: object): TransactionError static TransactionRevertedWithoutReasonError(receipt: object): TransactionError static TransactionOutOfGasError(receipt: object): TransactionError + static ResolverMethodMissingError(address: string, name: string): Error } export class WebsocketProviderBase { diff --git a/packages/web3-core-helpers/types/tests/errors-test.ts b/packages/web3-core-helpers/types/tests/errors-test.ts index 5f00b754684..01dab2938a4 100644 --- a/packages/web3-core-helpers/types/tests/errors-test.ts +++ b/packages/web3-core-helpers/types/tests/errors-test.ts @@ -76,3 +76,6 @@ errors.TransactionRevertedWithoutReasonError({}); // $ExpectType TransactionError errors.TransactionOutOfGasError({}); + +// $ExpectType Error +errors.ResolverMethodMissingError('0x0000000000000000000000000000000000000001', 'content'); diff --git a/packages/web3-eth-ens/package-lock.json b/packages/web3-eth-ens/package-lock.json index fb2edd50069..c9152aa8eb2 100644 --- a/packages/web3-eth-ens/package-lock.json +++ b/packages/web3-eth-ens/package-lock.json @@ -84,6 +84,19 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -94,6 +107,15 @@ "concat-map": "0.0.1" } }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -137,6 +159,34 @@ } } }, + "cids": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", + "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", + "requires": { + "buffer": "^5.5.0", + "class-is": "^1.1.0", + "multibase": "~0.6.0", + "multicodec": "^1.0.0", + "multihashes": "~0.4.15" + }, + "dependencies": { + "multicodec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.1.tgz", + "integrity": "sha512-yrrU/K8zHyAH2B0slNVeq3AiwluflHpgQ3TAzwNJcuO2AoPyXgBT2EDkdbP1D8B/yFOY+S2hDYmFlI1vhVFkQw==", + "requires": { + "buffer": "^5.5.0", + "varint": "^5.0.0" + } + } + } + }, + "class-is": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", + "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==" + }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -170,9 +220,9 @@ "dev": true }, "command-exists": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.8.tgz", - "integrity": "sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", "dev": true }, "commander": { @@ -187,6 +237,16 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "content-hash": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", + "integrity": "sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==", + "requires": { + "cids": "^0.7.1", + "multicodec": "^0.5.5", + "multihashes": "^0.4.15" + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -214,6 +274,16 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "definitelytyped-header-parser": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/definitelytyped-header-parser/-/definitelytyped-header-parser-3.9.0.tgz", + "integrity": "sha512-slbwZ5h5lasB12t+9EAGYr060aCMqEXp6cwD7CoTriK40HNDYU56/XQ6S4sbjBK8ReGRMnB/uDx0elKkb4kuQA==", + "dev": true, + "requires": { + "@types/parsimmon": "^1.3.0", + "parsimmon": "^1.2.0" + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -221,34 +291,19 @@ "dev": true }, "dts-critic": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-3.0.1.tgz", - "integrity": "sha512-3y34qsytqwEgfoUcYwxVm9Lv54Q+MPEXCOtZpwhl4TNM1SN/yjolWXz7Xw2U0BQv/rGhIdM2ONNTaAxRfQdJ6g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/dts-critic/-/dts-critic-3.0.2.tgz", + "integrity": "sha512-wkRb9FOBXNwpB14nxHbevbyGm42KhA2YGtYsIrfDMvVWDaRMQVYodYtUbuNAH7WUPFMd0ywaqnmzGJl+E6yjsg==", "dev": true, "requires": { "command-exists": "^1.2.8", "definitelytyped-header-parser": "^3.8.2", + "rimraf": "^3.0.2", "semver": "^6.2.0", "typescript": "^3.7.5", "yargs": "^12.0.5" }, "dependencies": { - "definitelytyped-header-parser": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/definitelytyped-header-parser/-/definitelytyped-header-parser-3.9.0.tgz", - "integrity": "sha512-slbwZ5h5lasB12t+9EAGYr060aCMqEXp6cwD7CoTriK40HNDYU56/XQ6S4sbjBK8ReGRMnB/uDx0elKkb4kuQA==", - "dev": true, - "requires": { - "@types/parsimmon": "^1.3.0", - "parsimmon": "^1.2.0" - } - }, - "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", - "dev": true - }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", @@ -272,37 +327,19 @@ } }, "dtslint": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-3.4.1.tgz", - "integrity": "sha512-gIFYwlAO8vY17zGMqdJ7x2DA2swrQsKCwrtX0TUP4A36dlXjdFpj6NWMWc1HW5mYkWOkQFHwTWMOdkP6DLsrfA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/dtslint/-/dtslint-3.4.2.tgz", + "integrity": "sha512-qvZN5jI849zG8cjirBzyetK2ZGRa3rO8ExhcirqDlkas251Wt8TlZFKvW8wDGQ++fHLA8omINNxWvPBCb8AXhA==", "dev": true, "requires": { "definitelytyped-header-parser": "3.9.0", - "dts-critic": "^3.0.0", + "dts-critic": "^3.0.2", "fs-extra": "^6.0.1", "json-stable-stringify": "^1.0.1", "strip-json-comments": "^2.0.1", "tslint": "5.14.0", - "typescript": "^3.9.0-dev.20200424", + "typescript": "^3.8.3", "yargs": "^15.1.0" - }, - "dependencies": { - "definitelytyped-header-parser": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/definitelytyped-header-parser/-/definitelytyped-header-parser-3.9.0.tgz", - "integrity": "sha512-slbwZ5h5lasB12t+9EAGYr060aCMqEXp6cwD7CoTriK40HNDYU56/XQ6S4sbjBK8ReGRMnB/uDx0elKkb4kuQA==", - "dev": true, - "requires": { - "@types/parsimmon": "^1.3.0", - "parsimmon": "^1.2.0" - } - }, - "typescript": { - "version": "3.9.0-dev.20200424", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.0-dev.20200424.tgz", - "integrity": "sha512-dlfK6Mtv78aQhWs8Umtq+twEP051mnXStu+TwOYlvkqMF/hdG626I5PMonLx11gLL2NmOftLTjM/ebAiAZqx7g==", - "dev": true - } } }, "emoji-regex": { @@ -418,9 +455,9 @@ } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "has-ansi": { @@ -454,6 +491,11 @@ "punycode": "2.1.0" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -608,6 +650,44 @@ "minimist": "^1.2.5" } }, + "multibase": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", + "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", + "requires": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + }, + "multicodec": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", + "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", + "requires": { + "varint": "^5.0.0" + } + }, + "multihashes": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.19.tgz", + "integrity": "sha512-ej74GAfA20imjj00RO5h34aY3pGUFyzn9FJZFWwdeUHlHTkKmv90FrNpvYT4jYf1XXCy5O/5EjVnxTaESgOM6A==", + "requires": { + "buffer": "^5.5.0", + "multibase": "^0.7.0", + "varint": "^5.0.0" + }, + "dependencies": { + "multibase": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", + "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", + "requires": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + } + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -749,14 +829,28 @@ "dev": true }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -894,6 +988,11 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, + "varint": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz", + "integrity": "sha1-2Ca4n3SQcy+rwMDtaT7Uddyynr8=" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -1113,9 +1212,9 @@ } }, "yargs-parser": { - "version": "18.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.2.tgz", - "integrity": "sha512-hlIPNR3IzC1YuL1c2UwwDKpXlNFBqD1Fswwh1khz5+d8Cq/8yc/Mn0i+rQXduu8hcrFKvO7Eryk+09NecTQAAQ==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/packages/web3-eth-ens/package.json b/packages/web3-eth-ens/package.json index 028fa81045c..eb824f1cf9b 100644 --- a/packages/web3-eth-ens/package.json +++ b/packages/web3-eth-ens/package.json @@ -13,6 +13,7 @@ }, "main": "src/index.js", "dependencies": { + "content-hash": "^2.5.2", "eth-ens-namehash": "2.0.8", "underscore": "1.9.1", "web3-core": "1.2.7", diff --git a/packages/web3-eth-ens/src/ENS.js b/packages/web3-eth-ens/src/ENS.js index 7eaf0330d52..604df14329d 100644 --- a/packages/web3-eth-ens/src/ENS.js +++ b/packages/web3-eth-ens/src/ENS.js @@ -26,6 +26,7 @@ var formatters = require('web3-core-helpers').formatters; var utils = require('web3-utils'); var Registry = require('./contracts/Registry'); var ResolverMethodHandler = require('./lib/ResolverMethodHandler'); +var contenthash = require('./lib/contentHash'); /** * Constructs a new instance of ENS @@ -363,7 +364,7 @@ ENS.prototype.setAddress = function (name, address, txConfig, callback) { * @returns {PromiEvent} */ ENS.prototype.getPubkey = function (name, callback) { - return this.resolverMethodHandler.method(name, 'pubkey', [], callback).call(callback); + return this.resolverMethodHandler.method(name, 'pubkey', [], null, callback).call(callback); }; /** @@ -416,6 +417,52 @@ ENS.prototype.setContent = function (name, hash, txConfig, callback) { return this.resolverMethodHandler.method(name, 'setContent', [hash]).send(txConfig, callback); }; +/** + * Returns the contenthash + * + * @method getContenthash + * + * @param {string} name + * @param {function} callback + * + * @callback callback callback(error, result) + * @returns {PromiEvent} + */ +ENS.prototype.getContenthash = function (name, callback) { + return this.resolverMethodHandler.method(name, 'contenthash', [], contenthash.decode).call(callback); +}; + +/** + * Set the contenthash + * + * @method setContent + * + * @param {string} name + * @param {string} hash + * @param {function} callback + * @param {TransactionConfig} txConfig + * + * @callback callback callback(error, result) + * @returns {PromiEvent} + */ +ENS.prototype.setContenthash = function (name, hash, txConfig, callback) { + var encoded; + try { + encoded = contenthash.encode(hash); + } catch(err){ + var error = new Error('Could not encode ' + hash + '. See docs for supported hash protocols.'); + + if (_.isFunction(callback)) { + callback(error, null); + + return; + } + + throw error; + } + return this.resolverMethodHandler.method(name, 'setContenthash', [encoded]).send(txConfig, callback); +}; + /** * Get the multihash * diff --git a/packages/web3-eth-ens/src/config.js b/packages/web3-eth-ens/src/config.js index b4075241114..b12e5f5b717 100644 --- a/packages/web3-eth-ens/src/config.js +++ b/packages/web3-eth-ens/src/config.js @@ -32,6 +32,18 @@ var config = { rinkeby: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", goerli: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" }, + // These ids obtained at ensdomains docs: + // https://docs.ens.domains/contract-developer-guide/writing-a-resolver + interfaceIds: { + addr: "0x3b3b57de", + setAddr: "0x3b3b57de", + pubkey: "0xc8690233", + setPubkey: "0xc8690233", + contenthash: "0xbc1c58d1", + setContenthash: "0xbc1c58d1", + content: "0xd8389dc5", + setContent: "0xd8389dc5" + } }; module.exports = config; diff --git a/packages/web3-eth-ens/src/lib/ResolverMethodHandler.js b/packages/web3-eth-ens/src/lib/ResolverMethodHandler.js index 069e82474cb..247b3215f85 100644 --- a/packages/web3-eth-ens/src/lib/ResolverMethodHandler.js +++ b/packages/web3-eth-ens/src/lib/ResolverMethodHandler.js @@ -22,7 +22,9 @@ var PromiEvent = require('web3-core-promievent'); var namehash = require('eth-ens-namehash'); +var errors = require('web3-core-helpers').errors; var _ = require('underscore'); +var interfaceIds = require('../config').interfaceIds; /** * @param {Registry} registry @@ -41,14 +43,15 @@ function ResolverMethodHandler(registry) { * @param {function} callback * @returns {Object} */ -ResolverMethodHandler.prototype.method = function (ensName, methodName, methodArguments, callback) { +ResolverMethodHandler.prototype.method = function (ensName, methodName, methodArguments, outputFormatter, callback) { return { call: this.call.bind({ ensName: ensName, methodName: methodName, methodArguments: methodArguments, callback: callback, - parent: this + parent: this, + outputFormatter: outputFormatter }), send: this.send.bind({ ensName: ensName, @@ -69,9 +72,11 @@ ResolverMethodHandler.prototype.call = function (callback) { var self = this; var promiEvent = new PromiEvent(); var preparedArguments = this.parent.prepareArguments(this.ensName, this.methodArguments); + var outputFormatter = this.outputFormatter || null; - this.parent.registry.getResolver(this.ensName).then(function (resolver) { - self.parent.handleCall(promiEvent, resolver.methods[self.methodName], preparedArguments, callback); + this.parent.registry.getResolver(this.ensName).then(async function (resolver) { + await self.parent.checkInterfaceSupport(resolver, self.methodName); + self.parent.handleCall(promiEvent, resolver.methods[self.methodName], preparedArguments, outputFormatter, callback); }).catch(function(error) { if (_.isFunction(callback)) { callback(error, null); @@ -98,7 +103,8 @@ ResolverMethodHandler.prototype.send = function (sendOptions, callback) { var promiEvent = new PromiEvent(); var preparedArguments = this.parent.prepareArguments(this.ensName, this.methodArguments); - this.parent.registry.getResolver(this.ensName).then(function (resolver) { + this.parent.registry.getResolver(this.ensName).then(async function (resolver) { + await self.parent.checkInterfaceSupport(resolver, self.methodName); self.parent.handleSend(promiEvent, resolver.methods[self.methodName], preparedArguments, sendOptions, callback); }).catch(function(error) { if (_.isFunction(callback)) { @@ -122,9 +128,13 @@ ResolverMethodHandler.prototype.send = function (sendOptions, callback) { * @param {function} callback * @returns {eventifiedPromise} */ -ResolverMethodHandler.prototype.handleCall = function (promiEvent, method, preparedArguments, callback) { +ResolverMethodHandler.prototype.handleCall = function (promiEvent, method, preparedArguments, outputFormatter, callback) { method.apply(this, preparedArguments).call() .then(function (result) { + if (outputFormatter){ + result = outputFormatter(result); + } + if (_.isFunction(callback)) { // It's required to pass the receipt to the second argument to be backwards compatible and to have the required consistency callback(result, result); @@ -208,4 +218,31 @@ ResolverMethodHandler.prototype.prepareArguments = function (name, methodArgumen return [node]; }; +/** + * + * + * @param {Contract} resolver + * @param {string} methodName + * + * @returns {Promise} + */ +ResolverMethodHandler.prototype.checkInterfaceSupport = async function (resolver, methodName) { + // Skip validation for undocumented interface ids (ex: multihash) + if (!interfaceIds[methodName]) return; + + var supported = false; + try { + supported = await resolver + .methods + .supportsInterface(interfaceIds[methodName]) + .call(); + } catch(err) { + console.warn('Could not verify interface of resolver contract at "' + resolver.options.address + '". '); + } + + if (!supported){ + throw errors.ResolverMethodMissingError(resolver.options.address, methodName); + } +}; + module.exports = ResolverMethodHandler; diff --git a/packages/web3-eth-ens/src/lib/contentHash.js b/packages/web3-eth-ens/src/lib/contentHash.js new file mode 100644 index 00000000000..bd0cbc8486d --- /dev/null +++ b/packages/web3-eth-ens/src/lib/contentHash.js @@ -0,0 +1,111 @@ +/* +Adapted from ensdomains/ui +https://github.com/ensdomains/ui/blob/3e62e440b53466eeec9dd1c63d73924eefbd88c1/src/utils/contents.js#L1-L85 + +BSD 2-Clause License + +Copyright (c) 2019, Ethereum Name Service +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +var contentHash = require('content-hash'); + +function decode(encoded) { + var decoded = null; + var protocolType = null; + var error = null; + + if (encoded && encoded.error) { + return { + protocolType: null, + decoded: encoded.error + }; + } + if (encoded) { + try { + decoded = contentHash.decode(encoded); + var codec = contentHash.getCodec(encoded); + if (codec === 'ipfs-ns') { + protocolType = 'ipfs'; + } else if (codec === 'swarm-ns') { + protocolType = 'bzz'; + } else if (codec === 'onion') { + protocolType = 'onion'; + } else if (codec === 'onion3') { + protocolType = 'onion3'; + } else { + decoded = encoded; + } + } catch (e) { + error = e.message; + } + } + return { + protocolType: protocolType, + decoded: decoded, + error: error + }; +} + +function encode(text) { + var content, contentType; + var encoded = false; + if (!!text) { + var matched = text.match(/^(ipfs|bzz|onion|onion3):\/\/(.*)/) || text.match(/\/(ipfs)\/(.*)/); + if (matched) { + contentType = matched[1]; + content = matched[2]; + } + + try { + if (contentType === 'ipfs') { + if(content.length >= 4) { + encoded = '0x' + contentHash.fromIpfs(content); + } + } else if (contentType === 'bzz') { + if(content.length >= 4) { + encoded = '0x' + contentHash.fromSwarm(content); + } + } else if (contentType === 'onion') { + if(content.length === 16) { + encoded = '0x' + contentHash.encode('onion', content); + } + } else if (contentType === 'onion3') { + if(content.length === 56) { + encoded = '0x' + contentHash.encode('onion3', content); + } + } else { + throw new Error('Could not encode content hash: unsupported content type'); + } + } catch (err) { + throw err; + } + } + return encoded; +} + +module.exports = { + decode: decode, + encode: encode +}; diff --git a/packages/web3-eth-ens/src/ressources/ABI/Resolver.js b/packages/web3-eth-ens/src/ressources/ABI/Resolver.js index 9bd8da24603..1d897391299 100644 --- a/packages/web3-eth-ens/src/ressources/ABI/Resolver.js +++ b/packages/web3-eth-ens/src/ressources/ABI/Resolver.js @@ -350,7 +350,61 @@ var RESOLVER = [ ], "name": "PubkeyChanged", "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "name": "hash", + "type": "bytes" + } + ], + "name": "ContenthashChanged", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "name": "contenthash", + "outputs": [ + { + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "hash", + "type": "bytes" + } + ], + "name": "setContenthash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" } ]; -module.exports = RESOLVER; +module.exports = RESOLVER; \ No newline at end of file diff --git a/packages/web3-eth-ens/types/index.d.ts b/packages/web3-eth-ens/types/index.d.ts index 5c50d7b8976..c47f21fac03 100644 --- a/packages/web3-eth-ens/types/index.d.ts +++ b/packages/web3-eth-ens/types/index.d.ts @@ -22,6 +22,12 @@ import { TransactionRevertInstructionError } from 'web3-core-helpers'; import { Eth } from 'web3-eth'; import { Contract } from 'web3-eth-contract'; +export interface ContentHash { + protocolType: 'ipfs' | 'bzz' | 'onion' | 'onion3' | null, + decoded: string | null, + error?: Error | null +} + // TODO: Define as soon as implemented the generic contract export class Ens { constructor(eth: Eth); @@ -279,12 +285,12 @@ export class Ens { */ getContenthash( name: string, - callback?: (value: any) => void - ): Promise; + callback?: (value: ContentHash) => void + ): Promise; getContenthash( name: string, - callback?: (error: Error, contenthash: string) => void - ): Promise; + callback?: (error: Error, contenthash: ContentHash) => void + ): Promise; setContenthash( name: string, diff --git a/packages/web3-eth-ens/types/tests/ens-test.ts b/packages/web3-eth-ens/types/tests/ens-test.ts index 1606779da8a..17d0af5bcdd 100644 --- a/packages/web3-eth-ens/types/tests/ens-test.ts +++ b/packages/web3-eth-ens/types/tests/ens-test.ts @@ -20,7 +20,7 @@ import { TransactionRevertInstructionError } from 'web3-core-helpers'; import { TransactionReceipt } from 'web3-core'; import { Contract } from 'web3-eth-contract'; -import { Ens } from 'web3-eth-ens'; +import { Ens, ContentHash } from 'web3-eth-ens'; import { Eth } from 'web3-eth'; const ens = new Ens(new Eth('http://localhost:8545')); @@ -210,12 +210,12 @@ ens.setMultihash('name', 'hash', {}, (error: Error, result: any) => {}); // $ExpectType PromiEvent ens.setMultihash('name', 'hash', {}); -// $ExpectType Promise +// $ExpectType Promise ens.getContenthash('name'); -// $ExpectType Promise -ens.getContenthash('name', (error: Error, contenthash: string) => {}); -// $ExpectType Promise -ens.getContenthash('name', (value: any) => {}); +// $ExpectType Promise +ens.getContenthash('name', (error: Error, contenthash: ContentHash) => {}); +// $ExpectType Promise +ens.getContenthash('name', (value: ContentHash) => {}); // $ExpectType PromiEvent ens.setContenthash('name', 'hash'); diff --git a/test/e2e.ens.js b/test/e2e.ens.js index bb77f32fcd9..13b3cc4145d 100644 --- a/test/e2e.ens.js +++ b/test/e2e.ens.js @@ -12,6 +12,7 @@ describe('ENS [ @E2E ]', function () { let addresses; let registryAddr; let resolverAddr; + let options; before(async function(){ web3 = new Web3('http://localhost:8545'); @@ -22,6 +23,12 @@ describe('ENS [ @E2E ]', function () { registryAddr = addresses.registry; resolverAddr = addresses.resolver; web3.eth.ens.registryAddress = registryAddr; + + options = { + from: account, + gas: 4000000, + gasPrice: 1 + } }); it('custom registry got defined in the ENS module', function () { @@ -50,5 +57,126 @@ describe('ENS [ @E2E ]', function () { '0x0000000000000000000000000000000000000001' ); }); + + it('should get/set a publickey', async function(){ + const x = "0x3078303030303030303030303030303030303030303030303030303030303030"; + const y = "0x3030303030303030303030303030303030303030303030303030303030303030"; + + await web3.eth.ens.setPubkey('resolver', x, y, options); + const coords = await web3.eth.ens.getPubkey('resolver'); + + assert.equal(coords.x, x); + assert.equal(coords.y, y); + }); + + it('should error when calling "getContent" if resolver does not support it', async function () { + try { + await web3.eth.ens.getContent('resolver'); + assert.fail(); + } catch(err){ + assert(err.message.includes(resolverAddr)); + assert(err.message.includes('does not implement requested method: "content"')) + } + }) + + it('should error when calling "setContent" if resolver does not support it', async function () { + try { + await web3.eth.ens.setContent('resolver', web3.utils.sha3('test')); + assert.fail(); + } catch(err){ + assert(err.message.includes(resolverAddr)); + assert(err.message.includes('does not implement requested method: "setContent"')) + } + }); + + // This test must be run before any contentHashes are set + it('getContenthash return object keys are null if no contentHash is set', async function(){ + const val = await web3.eth.ens.getContenthash('resolver'); + + assert.equal(val.protocolType, null); + assert.equal(val.decoded, null); + assert.equal(val.error, null); + }); + + /** + * NB: hash values for these tests are borrowed from unit tests at @ensdomains/ui + * Link: https://github.com/ensdomains/ui/blob/3e62e440b53466eeec9dd1c63d73924eefbd88c1/src/utils/contents.test.js#L1-L151 + */ + it('should get/set an IPFS contenthash (ipfs://)', async function(){ + const prefix = "ipfs://" + const hash = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"; + + await web3.eth.ens.setContenthash('resolver', prefix + hash, options); + const val = await web3.eth.ens.getContenthash('resolver'); + + assert.equal(val.protocolType, 'ipfs'); + assert.equal(val.decoded, hash); + }); + + it('should get/set an IPFS contenthash (/ipfs/)', async function(){ + const prefix = "/ipfs/" + const hash = "QmaEBknbGT4bTQiQoe2VNgBJbRfygQGktnaW5TbuKixjYL"; + + await web3.eth.ens.setContenthash('resolver', prefix + hash, options); + const val = await web3.eth.ens.getContenthash('resolver'); + + assert.equal(val.protocolType, 'ipfs'); + assert.equal(val.decoded, hash); + }); + + it('should get/set a bzz contenthash', async function(){ + const prefix = "bzz://"; + const hash = "d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162"; + + await web3.eth.ens.setContenthash('resolver', prefix + hash, options); + const val = await web3.eth.ens.getContenthash('resolver'); + + assert.equal(val.protocolType, 'bzz'); + assert.equal(val.decoded, hash); + }); + + it('should get/set an onion contenthash', async function(){ + const prefix = "onion://" + const hash = "3g2upl4pq6kufc4m"; + + await web3.eth.ens.setContenthash('resolver', prefix + hash, options); + const val = await web3.eth.ens.getContenthash('resolver'); + + assert.equal(val.protocolType, 'onion'); + assert.equal(val.decoded, hash); + }); + + it('should get/set an onion3 contenthash', async function(){ + const prefix = "onion3://" + const hash = "p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd"; + + await web3.eth.ens.setContenthash('resolver', prefix + hash, options); + const val = await web3.eth.ens.getContenthash('resolver'); + + assert.equal(val.protocolType, 'onion3'); + assert.equal(val.decoded, hash); + }); + + it('setContenthash errors when encoding an invalid contenthash (promise)', async function(){ + // Missing required protocol prefix + const hash = "p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd"; + + try { + await web3.eth.ens.setContenthash('resolver', hash, options); + assert.fail(); + } catch(err) { + assert(err.message.includes(`Could not encode ${hash}`)); + } + }); + + it('setContentHash errors when encoding an invalid contenthash (callback)', function(done){ + // Missing required protocol prefix + const hash = "p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd"; + + web3.eth.ens.setContenthash('resolver', hash, options, function(err, result){ + assert(err.message.includes(`Could not encode ${hash}`)); + done(); + }); + }); }); diff --git a/test/eth.ens.js b/test/eth.ens.js index 792cfe61f00..fb79d62c7a1 100644 --- a/test/eth.ens.js +++ b/test/eth.ens.js @@ -1613,6 +1613,7 @@ describe('ens', function () { it('should call getAddress and return the expected address (promise)', async function () { const resolverSig = 'resolver(bytes32)'; const addrSig = 'addr(bytes32)'; + const addrInterfaceId = "3b3b57de"; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1624,6 +1625,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + addrInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1642,6 +1653,7 @@ describe('ens', function () { it('should call getAddress and return the expected address (callback)', function (done) { const resolverSig = 'resolver(bytes32)'; const addrSig = 'addr(bytes32)'; + const addrInterfaceId = "3b3b57de"; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1653,6 +1665,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + addrInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1674,6 +1696,7 @@ describe('ens', function () { it('should call getAddress and throw the expected error (promise)', async function () { const resolverSig = 'resolver(bytes32)'; const addrSig = 'addr(bytes32)'; + const addrInterfaceId = "3b3b57de"; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1685,6 +1708,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + addrInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1698,6 +1731,7 @@ describe('ens', function () { provider.error.push(null); provider.error.push(null); provider.error.push(null); + provider.error.push(null); provider.injectError({ code: 1234, @@ -1718,6 +1752,7 @@ describe('ens', function () { it('should call getAddress and throw the expected error (callback)', function (done) { const resolverSig = 'resolver(bytes32)'; const addrSig = 'addr(bytes32)'; + const addrInterfaceId = "3b3b57de"; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1729,6 +1764,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + addrInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1742,6 +1787,7 @@ describe('ens', function () { provider.error.push(null); provider.error.push(null); provider.error.push(null); + provider.error.push(null); provider.injectError({ code: 1234, @@ -1760,6 +1806,7 @@ describe('ens', function () { it('should call getPubkey and return the expected X and Y value (promise)', async function () { const resolverSignature = 'resolver(bytes32)'; const pubkeySignature = 'pubkey(bytes32)'; + const pubkeyInterfaceId = "c8690233"; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1771,6 +1818,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + pubkeyInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1795,6 +1852,7 @@ describe('ens', function () { it('should call getPubkey and return the expected X and Y value (callback)', function (done) { const resolverSignature = 'resolver(bytes32)'; const pubkeySignature = 'pubkey(bytes32)'; + const pubkeyInterfaceId = "c8690233"; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1806,6 +1864,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + pubkeyInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1838,6 +1906,7 @@ describe('ens', function () { it('should call getPubkey and throw the expected error (callback)', function (done) { const resolverSignature = 'resolver(bytes32)'; const pubkeySignature = 'pubkey(bytes32)'; + const pubkeyInterfaceId = "c8690233"; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1849,6 +1918,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + pubkeyInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1868,6 +1947,7 @@ describe('ens', function () { provider.error.push(null); provider.error.push(null); provider.error.push(null); + provider.error.push(null); provider.injectError({ code: 1234, @@ -1889,6 +1969,7 @@ describe('ens', function () { it('should call getPubkey and throw the expected error (promise)', async function () { const resolverSignature = 'resolver(bytes32)'; const pubkeySignature = 'pubkey(bytes32)'; + const pubkeyInterfaceId = "c8690233"; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1900,6 +1981,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + pubkeyInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1919,6 +2010,7 @@ describe('ens', function () { provider.error.push(null); provider.error.push(null); provider.error.push(null); + provider.error.push(null); provider.injectError({ code: 1234, @@ -1938,6 +2030,7 @@ describe('ens', function () { it('should call getContent and return the expected content of the resolver (promise)', async function () { const resolverSignature = 'resolver(bytes32)'; const contentSignature = 'content(bytes32)'; + const contentInterfaceId = 'd8389dc5'; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1949,6 +2042,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + contentInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -1968,6 +2071,7 @@ describe('ens', function () { it('should call getContent and return the expected content of the resolver (callback)', function () { const resolverSignature = 'resolver(bytes32)'; const contentSignature = 'content(bytes32)'; + const contentInterfaceId = 'd8389dc5'; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -1979,6 +2083,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + contentInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -2003,6 +2117,7 @@ describe('ens', function () { it('should call getContent and throw the expected error (promise)', async function () { const resolverSignature = 'resolver(bytes32)'; const contentSignature = 'content(bytes32)'; + const contentInterfaceId = 'd8389dc5'; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -2014,6 +2129,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + contentInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -2027,6 +2152,7 @@ describe('ens', function () { provider.error.push(null); provider.error.push(null); provider.error.push(null); + provider.error.push(null); provider.injectError({ code: 1234, @@ -2046,6 +2172,7 @@ describe('ens', function () { it('should call getContent and throw the expected error (callback)', function () { const resolverSignature = 'resolver(bytes32)'; const contentSignature = 'content(bytes32)'; + const contentInterfaceId = 'd8389dc5'; provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); @@ -2057,6 +2184,16 @@ describe('ens', function () { }); provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + contentInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000001'); + provider.injectValidation(function (payload) { assert.equal(payload.jsonrpc, '2.0'); assert.equal(payload.method, 'eth_call'); @@ -2072,6 +2209,7 @@ describe('ens', function () { provider.error.push(null); provider.error.push(null); provider.error.push(null); + provider.error.push(null); provider.injectError({ code: 1234, @@ -2087,6 +2225,153 @@ describe('ens', function () { } ); }); + + it('should error if resolver ABI does not support contenthash (promise)', async function () { + const resolverSignature = 'resolver(bytes32)'; + const contentSignature = 'contenthash(bytes32)'; + const contenthashInterfaceId = 'bc1c58d1'; + + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3(resolverSignature).slice(0, 10) + '1757b5941987904c18c7594de32c1726cda093fdddacb738cfbc4a7cd1ef4370', + to: '0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + contenthashInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000000'); + + try { + await web3.eth.ens.getContenthash('foobar.eth'); + + assert.fail(); + } catch (error) { + assert(error.message.includes('does not implement requested method: "contenthash"')); + } + }); + + it('should error if resolver ABI does not support contenthash (callback)', function (done) { + const resolverSignature = 'resolver(bytes32)'; + const contentSignature = 'contenthash(bytes32)'; + const contenthashInterfaceId = 'bc1c58d1'; + + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3(resolverSignature).slice(0, 10) + '1757b5941987904c18c7594de32c1726cda093fdddacb738cfbc4a7cd1ef4370', + to: '0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + contenthashInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000000'); + + web3.eth.ens.getContenthash( + 'foobar.eth', + function (error, result) { + assert(error.message.includes('does not implement requested method: "contenthash"')); + assert.equal(result, null); + done(); + } + ); + }); + + it('should error if resolver ABI does not support setContenthash (promise)', async function () { + const resolverSignature = 'resolver(bytes32)'; + const contentSignature = 'setContenthash(bytes32)'; + const contenthashInterfaceId = 'bc1c58d1'; + + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3(resolverSignature).slice(0, 10) + '1757b5941987904c18c7594de32c1726cda093fdddacb738cfbc4a7cd1ef4370', + to: '0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + contenthashInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000000'); + + try { + await web3.eth.ens.setContenthash( + 'foobar.eth', + 'ipfs://QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' + ); + + assert.fail(); + } catch (error) { + assert(error.message.includes('does not implement requested method: "setContenthash"')); + } + }); + + it('should error if resolver ABI does not support setContenthash (callback)', function (done) { + const resolverSignature = 'resolver(bytes32)'; + const contentSignature = 'setContenthash(bytes32)'; + const contenthashInterfaceId = 'bc1c58d1'; + + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3(resolverSignature).slice(0, 10) + '1757b5941987904c18c7594de32c1726cda093fdddacb738cfbc4a7cd1ef4370', + to: '0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000123456701234567012345670123456701234567'); + + provider.injectValidation(function (payload) { + assert.equal(payload.jsonrpc, '2.0'); + assert.equal(payload.method, 'eth_call'); + assert.deepEqual(payload.params, [{ + data: sha3('supportsInterface(bytes4)').slice(0, 10) + contenthashInterfaceId + '00000000000000000000000000000000000000000000000000000000', + to: '0x0123456701234567012345670123456701234567', + }, 'latest']); + }); + provider.injectResult('0x0000000000000000000000000000000000000000000000000000000000000000'); + + web3.eth.ens.setContenthash( + 'foobar.eth', + 'ipfs://QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + { + from: '0x0123456701234567012345670123456701234567', + gas: 4000000, + gasPrice: 1 + }, + function (error, result) { + assert(error.message.includes('does not implement requested method: "setContenthash"')); + assert.equal(result, null); + done(); + } + ); + }); }); describe('checkNetwork', function () {