Skip to content

Commit

Permalink
feat: transfer polyx
Browse files Browse the repository at this point in the history
  • Loading branch information
shuffledex committed Apr 29, 2020
1 parent b548c71 commit a5d59c0
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 4 deletions.
17 changes: 16 additions & 1 deletion src/Polymesh.ts
Expand Up @@ -3,7 +3,12 @@ import { BigNumber } from 'bignumber.js';
import { polymesh } from 'polymesh-types/definitions';

import { Identity, SecurityToken, TickerReservation } from '~/api/entities';
import { reserveTicker, ReserveTickerParams } from '~/api/procedures';
import {
reserveTicker,
ReserveTickerParams,
transferPolyX,
TransferPolyXParams,
} from '~/api/procedures';
import { PolymeshError, TransactionQueue } from '~/base';
import { Context } from '~/context';
import { ErrorCode } from '~/types';
Expand Down Expand Up @@ -84,6 +89,16 @@ export class Polymesh {
}
}

/**
* Transfer an amount of POLYX to a specified account
*
* @param args.to - address or identity representation that will receive the polyx
* @param args.amount - amount of polyx to be transferred
*/
public transferPolyX(args: TransferPolyXParams): Promise<TransactionQueue<void>> {
return transferPolyX.prepare(args, this.context);
}

/**
* Get the POLYX balance of the current account
*/
Expand Down
29 changes: 28 additions & 1 deletion src/__tests__/Polymesh.ts
Expand Up @@ -3,7 +3,7 @@ import { BigNumber } from 'bignumber.js';
import sinon from 'sinon';

import { Identity, TickerReservation } from '~/api/entities';
import { reserveTicker } from '~/api/procedures';
import { reserveTicker, transferPolyX } from '~/api/procedures';
import { TransactionQueue } from '~/base';
import { Polymesh } from '~/Polymesh';
import { polkadotMockUtils } from '~/testUtils/mocks';
Expand Down Expand Up @@ -399,6 +399,33 @@ describe('Polymesh Class', () => {
});
});

describe('method: transferPolyX', () => {
test('should prepare the procedure with the correct arguments and context, and return the resulting transaction queue', async () => {
const context = polkadotMockUtils.getContextInstance();

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

const args = {
to: 'someAccount',
amount: new BigNumber(50),
};

const expectedQueue = ('' as unknown) as TransactionQueue<void>;

sinon
.stub(transferPolyX, 'prepare')
.withArgs(args, context)
.resolves(expectedQueue);

const queue = await polymesh.transferPolyX(args);

expect(queue).toBe(expectedQueue);
});
});

describe('method: getSecurityToken', () => {
test('should return a specific security token', async () => {
const ticker = 'TEST';
Expand Down
2 changes: 1 addition & 1 deletion src/api/entities/SecurityToken/Compliance/Rules.ts
Expand Up @@ -60,7 +60,7 @@ export class Rules extends Namespace<SecurityToken> {
return rule;
});
}

/**
* Detele all the current rules for the Security Token.
*/
Expand Down
Expand Up @@ -68,7 +68,7 @@ describe('Rules class', () => {
.resolves(expectedQueue);

const queue = await rules.set(args);

expect(queue).toBe(expectedQueue);
});
});
Expand Down
88 changes: 88 additions & 0 deletions src/api/procedures/__tests__/transferPolyX.ts
@@ -0,0 +1,88 @@
import BigNumber from 'bignumber.js';
import sinon from 'sinon';

import { prepareTransferPolyX, TransferPolyXParams } from '~/api/procedures/transferPolyX';
import { Context } from '~/context';
import { polkadotMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import * as utilsModule from '~/utils';

describe('transferPolyX procedure', () => {
let mockContext: Mocked<Context>;
const someDid = 'someDid';

beforeAll(() => {
polkadotMockUtils.initMocks();
procedureMockUtils.initMocks();
sinon.stub(utilsModule, 'valueToDid').returns(someDid);
sinon.stub(utilsModule, 'stringToAccountKey').returns(polkadotMockUtils.createMockAccountKey());
});

beforeEach(() => {
mockContext = polkadotMockUtils.getContextInstance();
});

afterEach(() => {
procedureMockUtils.reset();
polkadotMockUtils.reset();
});

afterAll(() => {
procedureMockUtils.cleanup();
polkadotMockUtils.cleanup();
});

test('should throw an error if the user has insufficient balance to transfer', () => {
const proc = procedureMockUtils.getInstance<TransferPolyXParams, void>();
proc.context = mockContext;

return expect(
prepareTransferPolyX.call(proc, {
to: 'someAccount',
amount: new BigNumber(101),
})
).rejects.toThrow('Insufficient balance to perform this action');
});

test('should throw an error if destination account has not an associated identity', () => {
polkadotMockUtils.createQueryStub('identity', 'keyToIdentityIds', { returnValue: {} });

const proc = procedureMockUtils.getInstance<TransferPolyXParams, void>();
proc.context = mockContext;

return expect(
prepareTransferPolyX.call(proc, { to: 'someAccount', amount: new BigNumber(99) })
).rejects.toThrow('The destination account has not an associated identity');
});

test('should add a balance transfer transaction to the queue', async () => {
const amount = new BigNumber(99);
const rawBalance = polkadotMockUtils.createMockBalance(amount.toNumber());

polkadotMockUtils.createQueryStub(
'identity',
'keyToIdentityIds',
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
{ returnValue: { unwrap: () => ({ asUnique: '012abc' }) } }
);

sinon.stub(utilsModule, 'numberToBalance').returns(rawBalance);

const tx = polkadotMockUtils.createTxStub('balances', 'transfer');
const proc = procedureMockUtils.getInstance<TransferPolyXParams, void>();
proc.context = mockContext;

await prepareTransferPolyX.call(proc, {
to: 'someAccount',
amount,
});

sinon.assert.calledWith(
procedureMockUtils.getAddTransactionStub(),
tx,
{},
someDid,
rawBalance
);
});
});
1 change: 1 addition & 0 deletions src/api/procedures/index.ts
Expand Up @@ -11,3 +11,4 @@ export {
SetTokenTrustedClaimIssuersParams,
} from './setTokenTrustedClaimIssuers';
export { setTokenRules, SetTokenRulesParams } from './setTokenRules';
export { transferPolyX, TransferPolyXParams } from './transferPolyX';
56 changes: 56 additions & 0 deletions src/api/procedures/transferPolyX.ts
@@ -0,0 +1,56 @@
import BigNumber from 'bignumber.js';

import { Identity } from '~/api/entities/Identity';
import { PolymeshError, Procedure } from '~/base';
import { ErrorCode } from '~/types';
import { numberToBalance, stringToAccountKey, valueToDid } from '~/utils';

export interface TransferPolyXParams {
to: string | Identity;
amount: BigNumber;
}

/**
* @hidden
*/
export async function prepareTransferPolyX(
this: Procedure<TransferPolyXParams>,
args: TransferPolyXParams
): Promise<void> {
const {
context: {
polymeshApi: {
query: { identity },
tx,
},
},
context,
} = this;

const { to: dest, amount: val } = args;
const to = valueToDid(dest);

const freeBalance = await context.accountBalance();

if (val.isGreaterThan(freeBalance)) {
throw new PolymeshError({
code: ErrorCode.ValidationError,
message: 'Insufficient balance to perform this action',
});
}

try {
const identityIds = await identity.keyToIdentityIds(stringToAccountKey(to, context));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const did = identityIds.unwrap().asUnique;

this.addTransaction(tx.balances.transfer, {}, to, numberToBalance(val, context));
} catch (err) {
throw new PolymeshError({
code: ErrorCode.FatalError,
message: 'The destination account has not an associated identity',
});
}
}

export const transferPolyX = new Procedure(prepareTransferPolyX);

0 comments on commit a5d59c0

Please sign in to comment.