Skip to content

Commit

Permalink
Merge pull request #30 from PolymathNetwork/feat/MSDK-34-fetch-tickers
Browse files Browse the repository at this point in the history
feat: fetch all the token symbols registered to my DID
  • Loading branch information
monitz87 authored Mar 12, 2020
2 parents e84c8dc + a3adbe3 commit a90df35
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 66 deletions.
82 changes: 82 additions & 0 deletions src/Polymesh.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { u8aToString } from '@polkadot/util';
import { ApiPromise, Keyring, WsProvider } from '@polymathnetwork/polkadot/api';
import { Option } from '@polymathnetwork/polkadot/types';
import { Link } from '@polymathnetwork/polkadot/types/interfaces';
import { BigNumber } from 'bignumber.js';

import { TickerReservation } from '~/api/entities';
Expand Down Expand Up @@ -113,4 +116,83 @@ export class Polymesh {
): Promise<TransactionQueue<TickerReservation>> {
return reserveTicker.prepare(args, this.context);
}

/**
* Retrieve all the ticker reservations currently owned by an identity. This includes
* Security Tokens that have already been launched
*
* @param args.did - identity ID as stored in the blockchain
*/
public async getTickerReservations(args?: { did: string }): Promise<TickerReservation[]> {
const {
context: {
polymeshApi: {
query: {
identity: { links },
},
},
},
context: { currentIdentity },
context,
} = this;

let identity: string;

if (args) {
identity = args.did;
} else if (currentIdentity) {
identity = currentIdentity.did;
} else {
throw new PolymeshError({
code: ErrorCode.FatalError,
message: 'The current account does not have an associated identity',
});
}

const tickers = await links.entries({ identity });

/*
NOTE: we have cast to Option<Link> to get access of link_data properties despite what the types say.
*/
const tickerReservations = tickers
.filter(([, data]) => ((data as unknown) as Option<Link>).unwrap().link_data.isTickerOwned)
.map(([, data]) => {
const ticker = ((data as unknown) as Option<Link>).unwrap().link_data.asTickerOwned;
return new TickerReservation(
// eslint-disable-next-line no-control-regex
{ ticker: u8aToString(ticker).replace(/\u0000/g, '') },
context
);
});

return tickerReservations;
}

/**
* Retrieve a Ticker Reservation
*
* @param ticker - Security Token ticker
*/
public async getTickerReservation(args: { ticker: string }): Promise<TickerReservation> {
const { ticker } = args;
const {
context: {
polymeshApi: {
query: { asset },
},
},
context,
} = this;

const tickerReservation = await asset.tickers(ticker);

if (!tickerReservation.owner.isEmpty) {
return new TickerReservation({ ticker }, context);
}

throw new PolymeshError({
code: ErrorCode.FatalError,
message: `There is no reservation for ${ticker} ticker`,
});
}
}
132 changes: 131 additions & 1 deletion src/__tests__/Polymesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ import { TickerReservation } from '~/api/entities';
import { reserveTicker } from '~/api/procedures';
import { TransactionQueue } from '~/base';
import { Polymesh } from '~/Polymesh';
import { PolkadotMockFactory } from '~/testUtils/mocks';
import {
createMockIdentityId,
createMockLink,
createMockLinkData,
createMockOption,
createMockTicker,
createMockTickerRegistration,
createMockU64,
PolkadotMockFactory,
} from '~/testUtils/mocks';

describe('Polymesh Class', () => {
const polkadotMockFactory = new PolkadotMockFactory();
Expand Down Expand Up @@ -174,4 +183,125 @@ describe('Polymesh Class', () => {
expect(queue).toBe(expectedQueue);
});
});

describe('method: getTickerReservations', () => {
test('should throw if identity was not instantiated', async () => {
polkadotMockFactory.initMocks({ mockContext: { withSeed: false } });

polkadotMockFactory.createQueryStub('identity', 'links');

const polymesh = await Polymesh.connect({
nodeUrl: 'wss://some.url',
});

return expect(polymesh.getTickerReservations()).rejects.toThrow(
'The current account does not have an associated identity'
);
});

test('should return a list of ticker reservations if did parameter is set', async () => {
const fakeTicker = 'TEST';

polkadotMockFactory.initMocks({ mockContext: { withSeed: true } });

polkadotMockFactory.createQueryStub('identity', 'links', {
entries: [
createMockOption(
createMockLink({
// eslint-disable-next-line @typescript-eslint/camelcase
link_data: createMockLinkData({
tickerOwned: createMockTicker(fakeTicker),
}),
expiry: createMockOption(),
// eslint-disable-next-line @typescript-eslint/camelcase
link_id: createMockU64(),
})
),
],
});

const polymesh = await Polymesh.connect({
nodeUrl: 'wss://some.url',
accountUri: '//uri',
});

const tickerReservations = await polymesh.getTickerReservations({ did: 'someDid' });

expect(tickerReservations).toHaveLength(1);
expect(tickerReservations[0].ticker).toBe(fakeTicker);
});

test('should return a list of ticker reservations owned by the identity', async () => {
const fakeTicker = 'TEST';

polkadotMockFactory.initMocks({ mockContext: { withSeed: true } });

polkadotMockFactory.createQueryStub('identity', 'links', {
entries: [
createMockOption(
createMockLink({
// eslint-disable-next-line @typescript-eslint/camelcase
link_data: createMockLinkData({
tickerOwned: createMockTicker(fakeTicker),
}),
expiry: createMockOption(),
// eslint-disable-next-line @typescript-eslint/camelcase
link_id: createMockU64(),
})
),
],
});

const polymesh = await Polymesh.connect({
nodeUrl: 'wss://some.url',
accountUri: '//uri',
});

const tickerReservations = await polymesh.getTickerReservations();

expect(tickerReservations).toHaveLength(1);
expect(tickerReservations[0].ticker).toBe(fakeTicker);
});
});

describe('method: getTickerReservation', () => {
test('should return a specific ticker reservation owned by the identity', async () => {
const ticker = 'TEST';

polkadotMockFactory.createQueryStub('asset', 'tickers', {
returnValue: createMockTickerRegistration({
owner: createMockIdentityId('someDid'),
expiry: createMockOption(),
}),
});

const polymesh = await Polymesh.connect({
nodeUrl: 'wss://some.url',
accountUri: '//uri',
});

const tickerReservation = await polymesh.getTickerReservation({ ticker });
expect(tickerReservation.ticker).toBe(ticker);
});

test('should throw if ticker reservation does not exist', async () => {
const ticker = 'TEST';

polkadotMockFactory.createQueryStub('asset', 'tickers', {
returnValue: createMockTickerRegistration({
owner: createMockIdentityId(),
expiry: createMockOption(),
}),
});

const polymesh = await Polymesh.connect({
nodeUrl: 'wss://some.url',
accountUri: '//uri',
});

return expect(polymesh.getTickerReservation({ ticker })).rejects.toThrow(
`There is no reservation for ${ticker} ticker`
);
});
});
});
16 changes: 6 additions & 10 deletions src/api/entities/TickerReservation/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,12 @@ describe('TickerReservation class', () => {
let tokensStub: sinon.SinonStub<[string | Uint8Array | Ticker, Callback<Codec | Codec[]>]>;

beforeEach(() => {
tickersStub = polkadotMockFactory.createQueryStub(
'asset',
'tickers',
createMockTickerRegistration()
);
tokensStub = polkadotMockFactory.createQueryStub(
'asset',
'tokens',
createMockSecurityToken()
);
tickersStub = polkadotMockFactory.createQueryStub('asset', 'tickers', {
returnValue: createMockTickerRegistration(),
});
tokensStub = polkadotMockFactory.createQueryStub('asset', 'tokens', {
returnValue: createMockSecurityToken(),
});
});

test('should return details for a free ticker', async () => {
Expand Down
43 changes: 25 additions & 18 deletions src/api/procedures/__tests__/reserveTicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ import {
import { Mocked } from '~/testUtils/types';
import { TickerReservationStatus } from '~/types';
import { PolymeshTx } from '~/types/internal';
/* eslint-disable import/no-duplicates */
import { tickerToString } from '~/utils';
import * as utilsModule from '~/utils';
/* eslint-enable import/no-duplicates */

describe('reserveTicker procedure', () => {
let mockProcedure: MockManager<procedureModule.Procedure<ReserveTickerParams, TickerReservation>>;
Expand Down Expand Up @@ -73,14 +76,16 @@ describe('reserveTicker procedure', () => {
status: TickerReservationStatus.Free,
});

mockFactory.createQueryStub(
'asset',
'tickerRegistrationFee',
createMockBalance(fee * Math.pow(10, 6))
);
mockFactory.createQueryStub('asset', 'tickers', createMockTickerRegistration());
mockFactory.createQueryStub('asset', 'tokens', createMockSecurityToken());
mockFactory.createQueryStub('asset', 'tickerConfig', createMockTickerRegistrationConfig());
mockFactory.createQueryStub('asset', 'tickerRegistrationFee', {
returnValue: createMockBalance(fee * Math.pow(10, 6)),
});
mockFactory.createQueryStub('asset', 'tickers', {
returnValue: createMockTickerRegistration(),
});
mockFactory.createQueryStub('asset', 'tokens', { returnValue: createMockSecurityToken() });
mockFactory.createQueryStub('asset', 'tickerConfig', {
returnValue: createMockTickerRegistrationConfig(),
});

transaction = mockFactory.createTxStub('asset', 'registerTicker');

Expand Down Expand Up @@ -145,16 +150,14 @@ describe('reserveTicker procedure', () => {

test('should throw an error if the ticker length exceeds the maximum', () => {
const maxTickerLength = 3;
mockFactory.createQueryStub(
'asset',
'tickerConfig',
createMockTickerRegistrationConfig({
mockFactory.createQueryStub('asset', 'tickerConfig', {
returnValue: createMockTickerRegistrationConfig({
/* eslint-disable @typescript-eslint/camelcase */
max_ticker_length: createMockU8(maxTickerLength),
registration_length: createMockOption(createMockMoment(10000)),
/* eslint-enable @typescript-eslint/camelcase */
})
);
}),
});
const proc = mockProcedure.getMockInstance();
proc.context = mockContext;

Expand All @@ -164,7 +167,9 @@ describe('reserveTicker procedure', () => {
});

test("should throw an error if the signing account doesn't have enough balance", () => {
mockFactory.createQueryStub('asset', 'tickerRegistrationFee', createMockBalance(600000000));
mockFactory.createQueryStub('asset', 'tickerRegistrationFee', {
returnValue: createMockBalance(600000000),
});
const proc = mockProcedure.getMockInstance();
proc.context = mockContext;

Expand Down Expand Up @@ -194,7 +199,9 @@ describe('reserveTicker procedure', () => {
ownerDid: 'someDid',
expiryDate,
});
mockFactory.createQueryStub('asset', 'tickerRegistrationFee', createMockBalance(600000000));
mockFactory.createQueryStub('asset', 'tickerRegistrationFee', {
returnValue: createMockBalance(600000000),
});
const proc = mockProcedure.getMockInstance();
proc.context = mockContext;

Expand Down Expand Up @@ -240,7 +247,7 @@ describe('reserveTicker procedure', () => {

describe('tickerReservationResolver', () => {
const findEventRecordStub = sinon.stub(utilsModule, 'findEventRecord');
const ticker = 'someTicker';
const ticker = createMockTicker('someTicker');

beforeEach(() => {
findEventRecordStub.returns(createMockEventRecord([ticker]));
Expand All @@ -255,6 +262,6 @@ describe('tickerReservationResolver', () => {

const result = createTickerReservationResolver(fakeContext)({} as ISubmittableResult);

expect(result.ticker).toBe(ticker);
expect(result.ticker).toBe(tickerToString(ticker));
});
});
Loading

0 comments on commit a90df35

Please sign in to comment.