UNPKG

ens-did-resolver

Version:
341 lines (281 loc) 10.6 kB
import { BigNumber } from '@ethersproject/bignumber'; import { InfuraProvider, JsonRpcProvider } from '@ethersproject/providers'; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } const identifierMatcher = /^(.*:)?(.*\.eth)$/; const knownInfuraNetworks = { mainnet: '0x1', goerli: '0x5' }; const knownNetworks = _extends({}, 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 InfuraProvider('homestead', projectId) }, { name: 'goerli', chainId: '0x5', provider: new 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.from(chainIdRaw).toNumber() : chainIdRaw; const networkName = knownInfuraNetworks[conf.name || ''] ? (_conf$name = conf.name) == null ? void 0 : _conf$name.replace('mainnet', 'homestead') : 'any'; provider = new 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 _extends({}, configureNetwork(conf), (_conf$networks = conf.networks) == null ? void 0 : _conf$networks.reduce((networks, net) => { return _extends({}, 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 = _extends({}, 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 getResolver(config) { async function resolve(did, parsed) { var _didDocument; const networks = configureResolverWithNetworks(config); // check if identifier(parsed.id) contains a network code const fullId = parsed.id.match(identifierMatcher); if (!fullId) { return { 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 { 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 }; } const ensResolver = await provider.getResolver(ensName); 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; try { address = await ensResolver.getAddress(); } catch (error) { err = `resolver_error: Cannot resolve ENS name: ${error}`; } const didDocumentMetadata = {}; let didDocument = null; if (address) { const chainId = (await 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}`] }; } const getEnsRecord = async (ensResolver, name) => { let parsedEntry = null; const entry = await ensResolver.getText(name); if (entry) { try { parsedEntry = JSON.parse(unescape(entry)); } catch (e) { return null; } } return parsedEntry; }; 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]; }; const services = (await getEnsRecord(ensResolver, 'org.w3c.did.service')) || []; if (services) { if (didDocument) { didDocument.service = [...(didDocument.service || []), ...services].filter(isDefined); } } const verificationMethods = (await getEnsRecord(ensResolver, 'org.w3c.did.verificationMethod')) || []; 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']; await relationships.reduce(async (memo, relationship) => { await memo; try { const verificationMethod = (await getEnsRecord(ensResolver, `org.w3c.did.${relationship}`)) || []; if (verificationMethod) { if (didDocument) { didDocument[relationship] = [...(didDocument[relationship] || []), ...filterValidVerificationMethods(did, verificationMethod, verificationMethods)]; } } } catch (e) {// nop } }, Promise.resolve()); 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 } }; } } return { ens: resolve }; } export { getResolver }; //# sourceMappingURL=index.modern.mjs.map