diff --git a/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.js b/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.js new file mode 100644 index 00000000..95b6eb00 --- /dev/null +++ b/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.js @@ -0,0 +1,64 @@ +import AddressIdentityProvider from './AddressIdentityProvider' +import { first, map } from 'rxjs/operators' +import { getCacheKey } from '../utils/index' + +const addressBookAppIds = [ + '0x32ec8cc9f3136797e0ae30e7bf3740905b0417b81ff6d4a74f6100f9037425de', + // TODO Add in App Ids for rinkeby and mainnet appIds +] +/** + * An identity provider for Address Book Entries + * + * @class AddressIdentityProvider + */ +export default class AddressBookIdentityProvider extends AddressIdentityProvider { + constructor (apps, cache) { + super() + this.apps = apps + this.cache = cache + } + /** + * Optional initialization, if required by the provider + */ + async init () { + // no-op + } + + /** + * Resolve the identity metadata for an address + * Should resolve to null if an identity does not exist + * Will return the first successful resolution tity could not be found + * + * @param {string} address Address to resolve + * @return {Promise} Resolved metadata or rejected error + */ + async resolve (address) { + address = address.toLowerCase() + const addressBookApps = await this.apps.pipe( + first(), + map(apps => apps.filter(app => addressBookAppIds.includes(app.appId))) + ).toPromise() + + return await addressBookApps.reduce(async (identity, app) => { + if (identity) { + return identity + } + const cacheKey = getCacheKey(app.proxyAddress, 'state') + const { entries = [] } = await this.cache.get(cacheKey) + const { data: entryData } = entries + .find(entry => entry.addr.toLowerCase() === address) || {} + return entryData || null + }, null) + } + + /** + * Modify the identity metadata of an address + * + * @param {string} address Address to resolve + * @param {Object} metadata Metadata to modify + * @return {Promise} Resolved success action or rejected error + */ + async modify (address, metadata) { + throw new Error('Use the Address Book to change this label, or create your own local label') + } +} diff --git a/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.test.js b/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.test.js new file mode 100644 index 00000000..90f0801c --- /dev/null +++ b/packages/aragon-wrapper/src/identity/AddressBookIdentityProvider.test.js @@ -0,0 +1,49 @@ +import test from 'ava' +import Cache from '../cache' +import { of } from 'rxjs' + +import { AddressBookIdentityProvider } from './index' +let apps, cache +test.before(async t => { + apps = of([ + { + appId: '0x32ec8cc9f3136797e0ae30e7bf3740905b0417b81ff6d4a74f6100f9037425de', + proxyAddress: '0x0' + }, + { + appId: '0x123', + proxyAddress: '0x1' + }, + { + appId: '0x32ec8cc9f3136797e0ae30e7bf3740905b0417b81ff6d4a74f6100f9037425de', + proxyAddress: '0x11' + } + ]) + + cache = new Cache('stubbedAddressBook') + await cache.init() + cache.set('0x0.state', { entries: [{addr: '0x3', data: { name: 'testEntity'}}] }) + cache.set('0x11.state', { entries: [{addr: '0x3', data: { name: 'testEntity2'}}] }) +}) + +test.beforeEach(async t => { + t.context.addressBookIdentityProvider = new AddressBookIdentityProvider(apps, cache) + await t.context.addressBookIdentityProvider.init() +}) + +test('should resolve identity from first address book in app array', async t => { + const provider = t.context.addressBookIdentityProvider + const identityMetadata = await provider.resolve('0x3') + t.is(identityMetadata.name, 'testEntity') +}) + +test('should resolve to null for non-existent identity', async t => { + const provider = t.context.addressBookIdentityProvider + const identityMetadata = await provider.resolve('0x9') + t.is(identityMetadata, null) +}) + +test('should throw error on any modify attempt', async t => { + const provider = t.context.addressBookIdentityProvider + await t.throwsAsync(() => provider.modify('0x9', { name: 'newEntity' })) +}) diff --git a/packages/aragon-wrapper/src/identity/index.js b/packages/aragon-wrapper/src/identity/index.js index 395d1bc4..8e7cc154 100644 --- a/packages/aragon-wrapper/src/identity/index.js +++ b/packages/aragon-wrapper/src/identity/index.js @@ -1,2 +1,3 @@ export { default as AddressIdentityProvider } from './AddressIdentityProvider' export { default as LocalIdentityProvider } from './LocalIdentityProvider' +export { default as AddressBookIdentityProvider } from './AddressBookIdentityProvider' diff --git a/packages/aragon-wrapper/src/index.js b/packages/aragon-wrapper/src/index.js index 258a35be..8c92d996 100644 --- a/packages/aragon-wrapper/src/index.js +++ b/packages/aragon-wrapper/src/index.js @@ -37,7 +37,7 @@ import { isKernelAppCodeNamespace } from './core/aragonOS/kernel' import { setConfiguration } from './configuration' import * as configurationKeys from './configuration/keys' import ens from './ens' -import { LocalIdentityProvider } from './identity' +import { LocalIdentityProvider, AddressBookIdentityProvider } from './identity' import { getAbi } from './interfaces' import { postprocessRadspecDescription, @@ -184,8 +184,8 @@ export default class Aragon { await this.kernelProxy.updateInitializationBlock() await this.initAccounts(options.accounts) await this.initAcl(Object.assign({ aclAddress }, options.acl)) - await this.initIdentityProviders() this.initApps() + await this.initIdentityProviders() this.initForwarders() this.initAppIdentifiers() this.initNetwork() @@ -879,6 +879,10 @@ export default class Aragon { const defaultIdentityProviders = [{ name: 'local', provider: new LocalIdentityProvider() + }, + { + name: 'addressBook', + provider: new AddressBookIdentityProvider(this.apps, this.cache) }] // TODO: detect other installed providers const detectedIdentityProviders = [] @@ -923,12 +927,17 @@ export default class Aragon { * @return {Promise} Resolves with the identity or null if not found */ resolveAddressIdentity (address) { - const providerName = 'local' // TODO - get provider - const provider = this.identityProviderRegistrar.get(providerName) - if (provider && typeof provider.resolve === 'function') { - return provider.resolve(address) - } - return Promise.reject(new Error(`Provider (${providerName}) not installed`)) + const providerNames = [ 'local', 'addressBook' ] // TODO - get provider + return providerNames.reduce(async (identity, providerName, index) => { + if (await identity) { + return identity + } + const provider = this.identityProviderRegistrar.get(providerName) + if (provider && typeof provider.resolve === 'function') { + return await provider.resolve(address) + } + return Promise.reject(new Error(`Provider (${providerName}) not installed`)) + }, Promise.resolve(null)) } /** diff --git a/packages/aragon-wrapper/src/index.test.js b/packages/aragon-wrapper/src/index.test.js index 4887109e..bbe57b41 100644 --- a/packages/aragon-wrapper/src/index.test.js +++ b/packages/aragon-wrapper/src/index.test.js @@ -805,7 +805,7 @@ test('should init the identity providers correctly', async (t) => { // assert t.truthy(instance.identityProviderRegistrar) t.true(instance.identityProviderRegistrar instanceof Map) - t.is(instance.identityProviderRegistrar.size, 1, 'Should have only one provider') + t.is(instance.identityProviderRegistrar.size, 2, 'Should have only two providers') }) test('should emit an intent when requesting address identity modification', async (t) => {