Skip to content

Commit

Permalink
feat: 馃幐 allow disabling IU when creating a token
Browse files Browse the repository at this point in the history
The `TickerReservation.create` endpoint now accepts an optional
`requireInvestorUniqueness` parameter (defaults to true). This value is
now also returned from `SecurityTokenDetails`. Also fetching the token's
name from the new 3.3.0 query

BREAKING CHANGE: 馃Ж the `SecurityTokenDetails` interface now has a new
`requireInvestorUniqueness: boolean` property. This affects the return
value of `SecurityToken.details`
  • Loading branch information
monitz87 committed Aug 30, 2021
1 parent 3a122c6 commit 3c8be1b
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 15 deletions.
21 changes: 18 additions & 3 deletions src/api/entities/SecurityToken/__tests__/index.ts
@@ -1,8 +1,9 @@
import { bool, u64 } from '@polkadot/types';
import { Balance } from '@polkadot/types/interfaces';
import { bool, u64 } from '@polkadot/types/primitive';
import BigNumber from 'bignumber.js';
import {
AssetIdentifier,
AssetName,
FundingRoundName,
SecurityToken as MeshSecurityToken,
} from 'polymesh-types/types';
Expand Down Expand Up @@ -76,20 +77,24 @@ describe('SecurityToken class', () => {
let isDivisible: boolean;
let owner: string;
let assetType: 'EquityCommon';
let iuDisabled: boolean;
let did: string;

let rawToken: MeshSecurityToken;
let rawName: AssetName;
let rawIuDisabled: bool;

let context: Context;
let securityToken: SecurityToken;

beforeAll(() => {
ticker = 'FAKETICKER';
name = 'placeholder';
name = 'tokenName';
totalSupply = 1000;
isDivisible = true;
owner = '0x0wn3r';
assetType = 'EquityCommon';
iuDisabled = false;
did = 'someDid';
});

Expand All @@ -102,6 +107,8 @@ describe('SecurityToken class', () => {
total_supply: dsMockUtils.createMockBalance(totalSupply),
/* eslint-enable @typescript-eslint/naming-convention */
});
rawIuDisabled = dsMockUtils.createMockBool(iuDisabled);
rawName = dsMockUtils.createMockAssetName(name);
context = dsMockUtils.getContextInstance();
securityToken = new SecurityToken({ ticker }, context);

Expand All @@ -121,6 +128,12 @@ describe('SecurityToken class', () => {
),
],
});
dsMockUtils.createQueryStub('asset', 'assetNames', {
returnValue: rawName,
});
dsMockUtils.createQueryStub('asset', 'disableInvestorUniqueness', {
returnValue: rawIuDisabled,
});
});

test('should return details for a security token', async () => {
Expand All @@ -139,6 +152,7 @@ describe('SecurityToken class', () => {
expect(details.assetType).toBe(assetType);
expect(details.primaryIssuanceAgents).toEqual([entityMockUtils.getIdentityInstance({ did })]);
expect(details.fullAgents).toEqual([entityMockUtils.getIdentityInstance({ did: owner })]);
expect(details.requiresInvestorUniqueness).toBe(true);

dsMockUtils.createQueryStub('externalAgents', 'groupOfAgent', {
entries: [
Expand Down Expand Up @@ -194,11 +208,12 @@ describe('SecurityToken class', () => {
sinon.match({
assetType,
isDivisible,
name: 'placeholder',
name,
owner: sinon.match({ did: owner }),
totalSupply: new BigNumber(totalSupply).div(Math.pow(10, 6)),
primaryIssuanceAgents: [entityMockUtils.getIdentityInstance({ did })],
fullAgents: [entityMockUtils.getIdentityInstance({ did: owner })],
requiresInvestorUniqueness: true,
})
);
});
Expand Down
30 changes: 22 additions & 8 deletions src/api/entities/SecurityToken/index.ts
@@ -1,6 +1,7 @@
import { Option, StorageKey } from '@polkadot/types';
import { bool, Option, StorageKey } from '@polkadot/types';
import {
AgentGroup,
AssetName,
Counter,
IdentityId,
SecurityToken as MeshSecurityToken,
Expand Down Expand Up @@ -46,6 +47,7 @@ import {
middlewareEventToEventIdentifier,
numberToU32,
stringToTicker,
textToString,
tickerToDid,
u64ToBigNumber,
} from '~/utils/conversion';
Expand Down Expand Up @@ -183,7 +185,7 @@ export class SecurityToken extends Entity<UniqueIdentifiers, string> {
public modify: ProcedureMethod<ModifyTokenParams, SecurityToken>;

/**
* Retrieve the Security Token's name, total supply, whether it is divisible or not and the Identity of the owner
* Retrieve the Security Token's data
*
* @note can be subscribed to
*/
Expand All @@ -207,7 +209,9 @@ export class SecurityToken extends Entity<UniqueIdentifiers, string> {
/* eslint-disable @typescript-eslint/naming-convention */
const assembleResult = async (
{ total_supply, divisible, owner_did, asset_type }: MeshSecurityToken,
agentGroups: [StorageKey<[Ticker, IdentityId]>, Option<AgentGroup>][]
agentGroups: [StorageKey<[Ticker, IdentityId]>, Option<AgentGroup>][],
assetName: AssetName,
iuDisabled: bool
): Promise<SecurityTokenDetails> => {
const primaryIssuanceAgents: Identity[] = [];
const fullAgents: Identity[] = [];
Expand Down Expand Up @@ -237,31 +241,41 @@ export class SecurityToken extends Entity<UniqueIdentifiers, string> {
return {
assetType,
isDivisible: boolToBoolean(divisible),
name: 'placeholder',
name: textToString(assetName),
owner,
totalSupply: balanceToBigNumber(total_supply),
primaryIssuanceAgents,
fullAgents,
requiresInvestorUniqueness: !boolToBoolean(iuDisabled),
};
};
/* eslint-enable @typescript-eslint/naming-convention */

const rawTicker = stringToTicker(ticker, context);

const groupOfAgentPromise = externalAgents.groupOfAgent.entries(rawTicker);
const namePromise = asset.assetNames(rawTicker);
const disabledIuPromise = asset.disableInvestorUniqueness(rawTicker);

if (callback) {
const groupOfAgents = await groupOfAgentPromise;
const groups = await groupOfAgentPromise;
const name = await namePromise;
const disabledIu = await disabledIuPromise;

return asset.tokens(rawTicker, async securityToken => {
const result = await assembleResult(securityToken, groupOfAgents);
const result = await assembleResult(securityToken, groups, name, disabledIu);
callback(result);
});
}

const [token, groupOfAgent] = await Promise.all([asset.tokens(rawTicker), groupOfAgentPromise]);
const [token, groups, name, disabledIu] = await Promise.all([
asset.tokens(rawTicker),
groupOfAgentPromise,
namePromise,
disabledIuPromise,
]);

return assembleResult(token, groupOfAgent);
return assembleResult(token, groups, name, disabledIu);
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/api/entities/SecurityToken/types.ts
Expand Up @@ -9,8 +9,12 @@ export interface SecurityTokenDetails {
name: string;
owner: Identity;
totalSupply: BigNumber;
/**
* @deprecated
*/
primaryIssuanceAgents: Identity[];
fullAgents: Identity[];
requiresInvestorUniqueness: boolean;
}

/**
Expand Down
9 changes: 6 additions & 3 deletions src/api/procedures/__tests__/createSecurityToken.ts
Expand Up @@ -67,6 +67,7 @@ describe('createSecurityToken procedure', () => {
let tokenType: string;
let tokenIdentifiers: TokenIdentifier[];
let fundingRound: string;
let requireInvestorUniqueness: boolean;
let documents: TokenDocument[];
let rawTicker: Ticker;
let rawName: AssetName;
Expand Down Expand Up @@ -117,6 +118,7 @@ describe('createSecurityToken procedure', () => {
},
];
fundingRound = 'Series A';
requireInvestorUniqueness = true;
documents = [
{
name: 'someDocument',
Expand Down Expand Up @@ -152,7 +154,7 @@ describe('createSecurityToken procedure', () => {
})
);
rawFundingRound = dsMockUtils.createMockFundingRoundName(fundingRound);
rawDisableIu = dsMockUtils.createMockBool(false);
rawDisableIu = dsMockUtils.createMockBool(!requireInvestorUniqueness);
args = {
ticker,
name,
Expand Down Expand Up @@ -187,7 +189,7 @@ describe('createSecurityToken procedure', () => {
numberToBalanceStub.withArgs(totalSupply, mockContext, isDivisible).returns(rawTotalSupply);
stringToAssetNameStub.withArgs(name, mockContext).returns(rawName);
booleanToBoolStub.withArgs(isDivisible, mockContext).returns(rawIsDivisible);
booleanToBoolStub.withArgs(false, mockContext).returns(rawDisableIu);
booleanToBoolStub.withArgs(!requireInvestorUniqueness, mockContext).returns(rawDisableIu);
internalTokenTypeToAssetTypeStub
.withArgs(tokenType as KnownTokenType, mockContext)
.returns(rawType);
Expand Down Expand Up @@ -271,6 +273,7 @@ describe('createSecurityToken procedure', () => {
totalSupply: new BigNumber(0),
tokenIdentifiers: undefined,
fundingRound: undefined,
requireInvestorUniqueness: false,
});

sinon.assert.calledWith(
Expand All @@ -283,7 +286,7 @@ describe('createSecurityToken procedure', () => {
rawType,
[],
null,
rawDisableIu
rawIsDivisible // disable IU = true
);

const issueTransaction = dsMockUtils.createTxStub('asset', 'issue');
Expand Down
9 changes: 8 additions & 1 deletion src/api/procedures/createSecurityToken.ts
Expand Up @@ -64,6 +64,11 @@ export interface CreateSecurityTokenParams {
*/
fundingRound?: string;
documents?: TokenDocument[];
/**
* whether this asset requires investors to have a Investor Uniqueness Claim in order
* to hold it. Optional, defaults to true. More information about Investor Uniqueness and PUIS [here](https://developers.polymesh.live/introduction/identity#polymesh-unique-identity-system-puis)
*/
requireInvestorUniqueness?: boolean;
}

/**
Expand Down Expand Up @@ -113,6 +118,7 @@ export async function prepareCreateSecurityToken(
tokenIdentifiers = [],
fundingRound,
documents,
requireInvestorUniqueness = true,
} = args;

const reservation = new TickerReservation({ ticker }, context);
Expand Down Expand Up @@ -163,6 +169,7 @@ export async function prepareCreateSecurityToken(
tokenIdentifierToAssetIdentifier(identifier, context)
);
const rawFundingRound = fundingRound ? stringToFundingRoundName(fundingRound, context) : null;
const rawDisableIu = booleanToBool(!requireInvestorUniqueness, context);

let fee: undefined | BigNumber;

Expand All @@ -184,7 +191,7 @@ export async function prepareCreateSecurityToken(
rawType,
rawIdentifiers,
rawFundingRound,
booleanToBool(false, context)
rawDisableIu
);

if (totalSupply && totalSupply.gt(0)) {
Expand Down

0 comments on commit 3c8be1b

Please sign in to comment.