diff --git a/src/Polymesh.ts b/src/Polymesh.ts index d9aba67c8b..33e094e27d 100644 --- a/src/Polymesh.ts +++ b/src/Polymesh.ts @@ -1,14 +1,9 @@ -import { ApiPromise, WsProvider } from '@polymathnetwork/polkadot/api'; +import { ApiPromise, Keyring, WsProvider } from '@polymathnetwork/polkadot/api'; import { BigNumber } from 'bignumber.js'; import { Context, PolymeshError } from '~/base'; import { ErrorCode } from '~/types'; -interface ConnectParams { - nodeUrl: string; - accountSeed?: string; -} - /** * Main entry point of the Polymesh SDK */ @@ -22,11 +17,24 @@ export class Polymesh { this.context = context; } + static async connect(params: { nodeUrl: string; accountSeed: string }): Promise; + + static async connect(params: { nodeUrl: string; keyring: Keyring }): Promise; + + static async connect(params: { nodeUrl: string; accountUri: string }): Promise; + + static async connect(params: { nodeUrl: string }): Promise; + /** * Create the instance and connect to the Polymesh node */ - static async connect(params: ConnectParams): Promise { - const { nodeUrl, accountSeed } = params; + static async connect(params: { + nodeUrl: string; + accountSeed?: string; + keyring?: Keyring; + accountUri?: string; + }): Promise { + const { nodeUrl, accountSeed, keyring, accountUri } = params; let polymeshApi: ApiPromise; try { @@ -34,10 +42,28 @@ export class Polymesh { provider: new WsProvider(nodeUrl), }); - const context = await Context.create({ - polymeshApi, - accountSeed, - }); + let context: Context = {} as Context; + + if (accountSeed) { + context = await Context.create({ + polymeshApi, + seed: accountSeed, + }); + } else if (keyring) { + context = await Context.create({ + polymeshApi, + keyring: keyring, + }); + } else if (accountUri) { + context = await Context.create({ + polymeshApi, + uri: accountUri, + }); + } else { + context = await Context.create({ + polymeshApi, + }); + } return new Polymesh(context); } catch (e) { diff --git a/src/__tests__/Polymesh.ts b/src/__tests__/Polymesh.ts index e4b10bca00..f34c65b301 100644 --- a/src/__tests__/Polymesh.ts +++ b/src/__tests__/Polymesh.ts @@ -33,6 +33,33 @@ describe('Polymesh Class', () => { sinon.assert.match(polymesh instanceof Polymesh, true); }); + test('should instantiate ApiPromise with a seed and return a Polymesh instance', async () => { + const polymesh = await Polymesh.connect({ + nodeUrl: '', + accountSeed: 'Alice'.padEnd(32, ' '), + }); + + sinon.assert.match(polymesh instanceof Polymesh, true); + }); + + test('should instantiate ApiPromise with a keyring and return a Polymesh instance', async () => { + const polymesh = await Polymesh.connect({ + nodeUrl: '', + keyring: {} as polkadotModule.Keyring, + }); + + sinon.assert.match(polymesh instanceof Polymesh, true); + }); + + test('should instantiate ApiPromise with a uri and return a Polymesh instance', async () => { + const polymesh = await Polymesh.connect({ + nodeUrl: '', + accountUri: '//uri', + }); + + sinon.assert.match(polymesh instanceof Polymesh, true); + }); + test('should throw if ApiPromise fails in the connection process', async () => { polkadotMockFactory.throwOnApiCreation(); const polymeshApiPromise = Polymesh.connect({ diff --git a/src/base/Context.ts b/src/base/Context.ts index 228123947f..702cdae58f 100644 --- a/src/base/Context.ts +++ b/src/base/Context.ts @@ -7,11 +7,6 @@ import { Identity } from '~/api/entities'; import { PolymeshError } from '~/base'; import { ErrorCode } from '~/types'; -interface BuildParams { - polymeshApi: ApiPromise; - accountSeed?: string | undefined; -} - interface SignerData { currentPair: IKeyringPair; did: IdentityId; @@ -59,26 +54,53 @@ export class Context { } } + static async create(params: { polymeshApi: ApiPromise; seed: string }): Promise; + + static async create(params: { polymeshApi: ApiPromise; keyring: Keyring }): Promise; + + static async create(params: { polymeshApi: ApiPromise; uri: string }): Promise; + + static async create(params: { polymeshApi: ApiPromise }): Promise; + /** * Create the Context instance */ - static async create(params: BuildParams): Promise { - const { polymeshApi, accountSeed } = params; - - const keyring = new Keyring({ type: 'sr25519' }); - - if (accountSeed) { - if (accountSeed.length !== 32) { - throw new PolymeshError({ - code: ErrorCode.ValidationError, - message: 'Seed must be 32 characters in length', - }); + static async create(params: { + polymeshApi: ApiPromise; + seed?: string; + keyring?: Keyring; + uri?: string; + }): Promise { + const { polymeshApi, seed, keyring, uri } = params; + + let keyringManagement = new Keyring({ type: 'sr25519' }); + let currentPair = {} as IKeyringPair; + let keyToIdentityIds; + + if (seed || keyring || uri) { + if (keyring) { + keyringManagement = keyring; + + keyToIdentityIds = await polymeshApi.query.identity.keyToIdentityIds( + keyring.getPairs()[0].publicKey + ); + } else { + if (seed) { + if (seed.length !== 32) { + throw new PolymeshError({ + code: ErrorCode.ValidationError, + message: 'Seed must be 32 characters in length', + }); + } + + currentPair = keyringManagement.addFromSeed(stringToU8a(seed)); + } else { + currentPair = keyringManagement.addFromUri(uri as string); + } + + keyToIdentityIds = await polymeshApi.query.identity.keyToIdentityIds(currentPair.publicKey); } - const currentPair = keyring.addFromSeed(stringToU8a(accountSeed)); - const keyToIdentityIds = await polymeshApi.query.identity.keyToIdentityIds( - currentPair.publicKey - ); let did: IdentityId; try { did = keyToIdentityIds.unwrap().asUnique; @@ -89,9 +111,9 @@ export class Context { }); } - return new Context({ polymeshApi, keyring, pair: { currentPair, did } }); + return new Context({ polymeshApi, keyring: keyringManagement, pair: { currentPair, did } }); } - return new Context({ polymeshApi, keyring }); + return new Context({ polymeshApi, keyring: keyringManagement }); } /** diff --git a/src/base/__tests__/Context.ts b/src/base/__tests__/Context.ts index 4bdf2da179..adde300299 100644 --- a/src/base/__tests__/Context.ts +++ b/src/base/__tests__/Context.ts @@ -26,16 +26,16 @@ describe('Context class', () => { }); describe('method: create', () => { - test('should throw if accountSeed parameter is not a 32 length string', async () => { + test('should throw if seed parameter is not a 32 length string', async () => { const context = Context.create({ polymeshApi: polkadotMockFactory.getApiInstance(), - accountSeed: 'abc', + seed: 'abc', }); await expect(context).rejects.toThrow(new Error('Seed must be 32 characters in length')); }); - test('should create a Context class with Pair and Identity attached', async () => { + test('should create a Context class from a seed with Pair and Identity attached', async () => { const keyToIdentityIdsStub = polkadotMockFactory.createQueryStub( 'identity', 'keyToIdentityIds', @@ -45,7 +45,7 @@ describe('Context class', () => { const context = await Context.create({ polymeshApi: polkadotMockFactory.getApiInstance(), - accountSeed: 'Alice'.padEnd(32, ' '), + seed: 'Alice'.padEnd(32, ' '), }); sinon.assert.calledOnce(keyringAddFromSeedStub); @@ -54,6 +54,43 @@ describe('Context class', () => { sinon.assert.match(context.currentIdentity instanceof identityModule.Identity, true); }); + test('should create a Context class from a keyring with Pair and Identity attached', async () => { + const keyToIdentityIdsStub = polkadotMockFactory.createQueryStub( + 'identity', + 'keyToIdentityIds', + { unwrap: () => ({ asUnique: '012abc' }) } + ); + const keyringGetPairsStub = mockKeyring.mock('getPairs', [{ publicKey: 'address' }]); + + const context = await Context.create({ + polymeshApi: polkadotMockFactory.getApiInstance(), + keyring: mockKeyring.getMockInstance(), + }); + + sinon.assert.calledOnce(keyringGetPairsStub); + sinon.assert.calledOnce(keyToIdentityIdsStub); + sinon.assert.match(context.currentIdentity instanceof identityModule.Identity, true); + }); + + test('should create a Context class from a uri with Pair and Identity attached', async () => { + const keyToIdentityIdsStub = polkadotMockFactory.createQueryStub( + 'identity', + 'keyToIdentityIds', + { unwrap: () => ({ asUnique: '012abc' }) } + ); + const keyringAddFromUriStub = mockKeyring.mock('addFromUri', 'currentPair'); + + const context = await Context.create({ + polymeshApi: polkadotMockFactory.getApiInstance(), + uri: '//Alice', + }); + + sinon.assert.calledOnce(keyringAddFromUriStub); + sinon.assert.calledOnce(keyToIdentityIdsStub); + expect(context.currentPair).toEqual('currentPair'); + sinon.assert.match(context.currentIdentity instanceof identityModule.Identity, true); + }); + test('should create a Context class without Pair and Identity attached', async () => { const keyToIdentityIdsStub = polkadotMockFactory.createQueryStub( 'identity', @@ -78,7 +115,7 @@ describe('Context class', () => { const context = Context.create({ polymeshApi: polkadotMockFactory.getApiInstance(), - accountSeed: 'Alice'.padEnd(32, ' '), + seed: 'Alice'.padEnd(32, ' '), }); await expect(context).rejects.toThrow(new Error('Identity ID does not exist'));