ens-did-resolver
Version:
Resolve DID documents for ENS
367 lines (311 loc) • 12.8 kB
JavaScript
var bignumber = require('@ethersproject/bignumber');
var providers = require('@ethersproject/providers');
const identifierMatcher = /^(.*:)?(.*\.eth)$/;
const knownInfuraNetworks = {
mainnet: '0x1',
goerli: '0x5'
};
const knownNetworks = { ...knownInfuraNetworks,
rsk: '0x1e',
'rsk:testnet': '0x1f',
artis_t1: '0x03c401',
artis_s1: '0x03c301',
matic: '0x89',
maticmum: '0x13881'
};
var Errors;
(function (Errors) {
/**
* The resolver has failed to construct the DID document.
* This can be caused by a network issue, a wrong registry address or malformed logs while parsing the registry
* history. Please inspect the `DIDResolutionMetadata.message` to debug further.
*/
Errors["notFound"] = "notFound";
/**
* The resolver does not know how to resolve the given DID. Most likely it is not a `did:ens`.
*/
Errors["invalidDid"] = "invalidDid";
/**
* The resolver is misconfigured or is being asked to resolve a DID anchored on an unknown network
*/
Errors["unknownNetwork"] = "unknownNetwork";
/**
* The resolver is being asked to resolve a DID anchored on a network without a known ENS resolver.
*/
Errors["unknownEnsResolver"] = "unknownEnsResolver";
})(Errors || (Errors = {}));
function isDefined(arg) {
return arg && typeof arg !== 'undefined';
}
function configureNetworksWithInfura(projectId) {
if (!projectId) {
return {};
}
const networks = [{
name: 'mainnet',
chainId: '0x1',
provider: new providers.InfuraProvider('homestead', projectId)
}, {
name: 'goerli',
chainId: '0x5',
provider: new providers.InfuraProvider('goerli', projectId)
}];
return configureNetworks({
networks
});
}
function getProviderForNetwork(conf) {
var _conf$web;
let provider = conf.provider || ((_conf$web = conf.web3) == null ? void 0 : _conf$web.currentProvider);
if (!provider) {
if (conf.rpcUrl) {
var _conf$name;
const chainIdRaw = conf.chainId ? conf.chainId : knownNetworks[conf.name || ''];
const chainId = chainIdRaw ? bignumber.BigNumber.from(chainIdRaw).toNumber() : chainIdRaw;
const networkName = knownInfuraNetworks[conf.name || ''] ? (_conf$name = conf.name) == null ? void 0 : _conf$name.replace('mainnet', 'homestead') : 'any';
provider = new providers.JsonRpcProvider(conf.rpcUrl, chainId || networkName);
} else {
throw new Error(`invalid_config: No web3 provider could be determined for network ${conf.name || conf.chainId}`);
}
}
return provider;
}
function configureNetwork(net) {
const networks = {};
const chainId = net.chainId || knownNetworks[net.name || ''];
if (chainId) {
if (net.name) {
networks[net.name] = getProviderForNetwork(net);
}
const id = typeof chainId === 'number' ? `0x${chainId.toString(16)}` : chainId;
networks[id] = getProviderForNetwork(net);
} else if (net.provider || net.web3 || net.rpcUrl) {
networks[net.name || ''] = getProviderForNetwork(net);
}
return networks;
}
function configureNetworks(conf) {
var _conf$networks;
return { ...configureNetwork(conf),
...((_conf$networks = conf.networks) == null ? void 0 : _conf$networks.reduce((networks, net) => {
return { ...networks,
...configureNetwork(net)
};
}, {}))
};
}
/**
* Generates a configuration that maps ethereum network names and chainIDs to the respective web3 providers.
* @returns a record of providers
* @param conf configuration options for the resolver. An array of network details.
* Each network entry should contain at least one of `name` or `chainId` AND one of `provider`, `web3`, or `rpcUrl`
* For convenience, you can also specify an `infuraProjectId` which will create a mapping for all the networks
* supported by https://infura.io.
* @example ```js
* [
* { name: 'development', rpcUrl: 'http://127.0.0.1:8545/' },
* { name: 'goerli', chainId: 5, provider: new InfuraProvider('goerli') },
* { name: 'rinkeby', provider: new AlchemyProvider('rinkeby') },
* { name: 'rsk:testnet', chainId: '0x1f', rpcUrl: 'https://public-node.testnet.rsk.co' },
* ]
* ```
*/
function configureResolverWithNetworks(conf = {}) {
const networks = { ...configureNetworksWithInfura(conf.infuraProjectId),
...configureNetworks(conf)
};
if (Object.keys(networks).length === 0) {
throw new Error('invalid_config: Please make sure to have at least one network');
}
return networks;
}
function _catch(body, recover) {
try {
var result = body();
} catch (e) {
return recover(e);
}
if (result && result.then) {
return result.then(void 0, recover);
}
return result;
}
function getResolver(config) {
const resolve = function (did, parsed) {
try {
const networks = configureResolverWithNetworks(config); // check if identifier(parsed.id) contains a network code
const fullId = parsed.id.match(identifierMatcher);
if (!fullId) {
return Promise.resolve({
didResolutionMetadata: {
error: Errors.invalidDid,
message: `Not a valid did:ens: ${parsed.id}`
},
didDocumentMetadata: {},
didDocument: null
});
}
const ensName = fullId[2];
const networkCode = typeof fullId[1] === 'string' ? fullId[1].slice(0, -1) : ''; // get provider for that network or the mainnet provider if none other is given
const provider = networks[networkCode];
if (!provider || typeof provider === 'undefined') {
return Promise.resolve({
didResolutionMetadata: {
error: Errors.unknownNetwork,
message: `This resolver is not configured for the ${networkCode} network required by ${parsed.id}. Networks: ${JSON.stringify(Object.keys(networks))}`
},
didDocumentMetadata: {},
didDocument: null
});
}
return Promise.resolve(provider.getResolver(ensName)).then(function (ensResolver) {
function _temp4() {
function _temp2() {
const getEnsRecord = function (ensResolver, name) {
try {
let parsedEntry = null;
return Promise.resolve(ensResolver.getText(name)).then(function (entry) {
if (entry) {
try {
parsedEntry = JSON.parse(unescape(entry));
} catch (e) {
return null;
}
}
return parsedEntry;
});
} catch (e) {
return Promise.reject(e);
}
};
const filterValidVerificationMethods = (did, current, all) => {
const methodLinks = current.filter(entry => typeof entry === 'string').map(entry => entry.startsWith('#') ? `${did}${entry}` : entry).filter(entry => all == null ? void 0 : all.some(b => b.id === entry));
const fullMethods = current.filter(entry => entry != null && typeof entry === 'object' && Object.keys(entry).includes('id') && Object.keys(entry).includes('type') && Object.keys(entry).some(k => k.startsWith('publicKey'))).map(entry => {
entry.controller = entry.controller || did;
if (entry.id.startsWith('#')) {
entry.id = `${did}${entry}`;
}
return entry;
});
return [...methodLinks, ...fullMethods];
};
return Promise.resolve(getEnsRecord(ensResolver, 'org.w3c.did.service')).then(function (services) {
if (services) {
if (didDocument) {
didDocument.service = [...(didDocument.service || []), ...services].filter(isDefined);
}
}
return Promise.resolve(getEnsRecord(ensResolver, 'org.w3c.did.verificationMethod')).then(function (verificationMethods) {
if (verificationMethods) {
verificationMethods.map(method => {
if (method.id.startsWith('#')) {
method.id = `${did}${method.id}`;
}
method.controller = method.controller || did;
return method;
});
if (didDocument) {
didDocument.verificationMethod = [...(didDocument.verificationMethod || []), ...verificationMethods].filter(isDefined);
}
}
const relationships = ['keyAgreement', 'assertionMethod', 'authentication', 'capabilityInvocation', 'capabilityDelegation'];
return Promise.resolve(relationships.reduce(function (memo, relationship) {
return Promise.resolve(memo).then(function () {
const _temp5 = _catch(function () {
return Promise.resolve(getEnsRecord(ensResolver, `org.w3c.did.${relationship}`)).then(function (verificationMethod) {
if (verificationMethod) {
if (didDocument) {
didDocument[relationship] = [...(didDocument[relationship] || []), ...filterValidVerificationMethods(did, verificationMethod, verificationMethods)];
}
}
});
}, function () {});
if (_temp5 && _temp5.then) return _temp5.then(function () {});
});
}, Promise.resolve())).then(function () {
var _didDocument;
const contentType = typeof ((_didDocument = didDocument) == null ? void 0 : _didDocument['@context']) !== 'undefined' ? 'application/did+ld+json' : 'application/did+json';
if (err) {
return {
didDocument,
didDocumentMetadata,
didResolutionMetadata: {
error: Errors.notFound,
message: err
}
};
} else {
return {
didDocument,
didDocumentMetadata,
didResolutionMetadata: {
contentType
}
};
}
});
});
});
}
const didDocumentMetadata = {};
let didDocument = null;
const _temp = function () {
if (address) {
return Promise.resolve(provider.getNetwork()).then(function (_provider$getNetwork) {
const chainId = _provider$getNetwork.chainId;
const blockchainAccountId = `${address}@eip155:${chainId}`;
const postfix = address; // setup default did doc
didDocument = {
id: did,
service: [{
id: `${did}#Web3PublicProfile-${postfix}`,
type: 'Web3PublicProfile',
serviceEndpoint: ensName
}],
verificationMethod: [{
id: `${did}#${postfix}`,
type: 'EcdsaSecp256k1RecoveryMethod2020',
controller: did,
blockchainAccountId
}],
authentication: [`${did}#${postfix}`],
capabilityDelegation: [`${did}#${postfix}`],
capabilityInvocation: [`${did}#${postfix}`],
assertionMethod: [`${did}#${postfix}`]
};
});
}
}();
return _temp && _temp.then ? _temp.then(_temp2) : _temp2(_temp);
}
if (!ensResolver) {
return {
didResolutionMetadata: {
error: Errors.unknownEnsResolver,
message: `This network (${networkCode}), required by ${parsed.id}, does not have a known ENS resolver`
},
didDocumentMetadata: {},
didDocument: null
};
}
let err = null;
let address = null;
const _temp3 = _catch(function () {
return Promise.resolve(ensResolver.getAddress()).then(function (_ensResolver$getAddre) {
address = _ensResolver$getAddre;
});
}, function (error) {
err = `resolver_error: Cannot resolve ENS name: ${error}`;
});
return _temp3 && _temp3.then ? _temp3.then(_temp4) : _temp4(_temp3);
});
} catch (e) {
return Promise.reject(e);
}
};
return {
ens: resolve
};
}
exports.getResolver = getResolver;
//# sourceMappingURL=index.js.map