Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/compareApiSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function compareSnapshots(base: APISnapshot, current: APISnapshot) {

const currMethods = new Map<string, MethodSignature>(currExport.map(m => [m.name, m]));

for (const baseMethod of baseExport) {
for (const baseMethod of baseExport || []) {
const currMethod = currMethods.get(baseMethod.name);

if (!currMethod) {
Expand Down
34 changes: 33 additions & 1 deletion src/api/entities/Asset/NonFungible/NftCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { assetQuery, assetTransactionQuery } from '~/middleware/queries/assets';
import { Query } from '~/middleware/types';
import {
AssetDetails,
BatchIssueNftParams,
CollectionKey,
ErrorCode,
EventIdentifier,
Expand Down Expand Up @@ -54,6 +55,19 @@ const sumNftIssuance = (
return numberIssued;
};

/**
* @hidden
*/
export function issueNftTransformer([nft]: Nft[]): Nft {
if (!nft) {
throw new PolymeshError({
code: ErrorCode.DataUnavailable,
message: 'Expected at least one Nft',
});
}
return nft;
}

/**
* Class used to manage NFT functionality
*/
Expand All @@ -65,7 +79,14 @@ export class NftCollection extends BaseAsset {
*
* @note Each NFT requires metadata for each value returned by `collectionKeys`. The SDK and chain only validate the presence of these fields. Additional validation may be needed to ensure each value complies with the specification.
*/
public issue: ProcedureMethod<IssueNftParams, Nft>;
public issue: ProcedureMethod<IssueNftParams, Nft[], Nft>;

/**
* Issues mulitple NFTs for the collection
*
* @note Each NFT requires metadata for each value returned by `collectionKeys`. The SDK and chain only validate the presence of these fields. Additional validation may be needed to ensure each value complies with the specification.
*/
public batchIssue: ProcedureMethod<BatchIssueNftParams, Nft[]>;

/**
* Force a transfer from the origin portfolio to one of the caller's portfolios
Expand All @@ -89,6 +110,17 @@ export class NftCollection extends BaseAsset {
this.settlements = new NonFungibleSettlements(this, context);

this.issue = createProcedureMethod(
{
getProcedureAndArgs: ({ metadata, portfolioId }) => [
issueNft,
{ collection: this, metadataList: [metadata], portfolioId },
],
transformer: issueNftTransformer,
},
context
);

this.batchIssue = createProcedureMethod(
{
getProcedureAndArgs: args => [issueNft, { collection: this, ...args }],
},
Expand Down
57 changes: 56 additions & 1 deletion src/api/entities/Asset/__tests__/NonFungible/NftCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Context,
DefaultPortfolio,
Entity,
issueNftTransformer,
NftCollection,
PolymeshError,
PolymeshTransaction,
Expand Down Expand Up @@ -513,7 +514,14 @@ describe('NftCollection class', () => {
'someTransaction' as unknown as PolymeshTransaction<NftCollection>;

when(procedureMockUtils.getPrepareMock())
.calledWith({ args: { collection, ...args }, transformer: undefined }, context, {})
.calledWith(
{
args: { collection, metadataList: [args.metadata], portfolioId: args.portfolioId },
transformer: issueNftTransformer,
},
context,
{}
)
.mockResolvedValue(expectedTransaction);

const tx = await collection.issue(args);
Expand All @@ -522,6 +530,37 @@ describe('NftCollection class', () => {
});
});

describe('method: batchIssue', () => {
it('should prepare the procedure with the correct arguments and context, and return the resulting transaction', async () => {
const assetId = '12341234-1234-1234-1234-123412341234';
const context = dsMockUtils.getContextInstance();
const collection = new NftCollection({ assetId }, context);

const args = {
metadataList: [[]],
portfolioId: new BigNumber(1),
};

const expectedTransaction =
'someTransaction' as unknown as PolymeshTransaction<NftCollection>;

when(procedureMockUtils.getPrepareMock())
.calledWith(
{
args: { collection, ...args },
transformer: undefined,
},
context,
{}
)
.mockResolvedValue(expectedTransaction);

const tx = await collection.batchIssue(args);

expect(tx).toBe(expectedTransaction);
});
});

describe('method: controllerTransfer', () => {
it('should prepare the procedure with the correct arguments and context, and return the resulting transaction', async () => {
const assetId = '12341234-1234-1234-1234-123412341234';
Expand Down Expand Up @@ -702,4 +741,20 @@ describe('NftCollection class', () => {
expect(nftCollection.toHuman()).toBe('12341234-1234-1234-1234123412341234');
});
});

describe('issueNftTransformer', () => {
it('should return a single Nft', () => {
const id = new BigNumber(1);
const assetId = '12341234-1234-1234-1234-123412341234';

const result = issueNftTransformer([entityMockUtils.getNftInstance({ id, assetId })]);

expect(result.id).toEqual(id);
expect(result.collection.id).toBe(assetId);
});

it('should throw if no Nft is provided', () => {
expect(() => issueNftTransformer([])).toThrow('Expected at least one Nft');
});
});
});
76 changes: 47 additions & 29 deletions src/api/procedures/__tests__/issueNft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { when } from 'jest-when';
import { Nft } from '~/api/entities/Asset/NonFungible/Nft';
import {
getAuthorization,
issueNftResolver,
issuedNftsResolver,
Params,
prepareIssueNft,
} from '~/api/procedures/issueNft';
Expand Down Expand Up @@ -105,46 +105,54 @@ describe('issueNft procedure', () => {

it('should return an issueNft transaction spec', async () => {
const args = {
metadata: [],
metadataList: [[]],
collection,
};
nftInputToMetadataValueSpy.mockReturnValue([]);
mockContext.getSigningIdentity.mockResolvedValue(entityMockUtils.getIdentityInstance());

const transaction = dsMockUtils.createTxMock('nft', 'issueNft');
const proc = procedureMockUtils.getInstance<Params, Nft>(mockContext);
const proc = procedureMockUtils.getInstance<Params, Nft[]>(mockContext);

const result = await prepareIssueNft.call(proc, args);
expect(result).toEqual({
transaction,
args: [rawAssetId, [], defaultPortfolioKind],
transactions: [
{
transaction,
args: [rawAssetId, [], defaultPortfolioKind],
},
],
resolver: expect.any(Function),
});
});

it('should issue tokens to Default portfolio if portfolioId is not specified', async () => {
const args = {
metadata: [],
metadataList: [[]],
collection,
};
mockContext.getSigningIdentity.mockResolvedValue(
entityMockUtils.getIdentityInstance({ portfoliosGetPortfolio: getPortfolio })
);
nftInputToMetadataValueSpy.mockReturnValue([]);
const transaction = dsMockUtils.createTxMock('nft', 'issueNft');
const proc = procedureMockUtils.getInstance<Params, Nft>(mockContext);
const proc = procedureMockUtils.getInstance<Params, Nft[]>(mockContext);
const result = await prepareIssueNft.call(proc, args);

expect(result).toEqual({
transaction,
args: [rawAssetId, [], defaultPortfolioKind],
transactions: [
{
transaction,
args: [rawAssetId, [], defaultPortfolioKind],
},
],
resolver: expect.any(Function),
});
});

it('should issue tokens to Default portfolio if default portfolioId is provided', async () => {
const args = {
metadata: [],
metadataList: [[]],
collection,
portfolioId: defaultPortfolioId,
};
Expand All @@ -153,19 +161,23 @@ describe('issueNft procedure', () => {
);
nftInputToMetadataValueSpy.mockReturnValue([]);
const transaction = dsMockUtils.createTxMock('nft', 'issueNft');
const proc = procedureMockUtils.getInstance<Params, Nft>(mockContext);
const proc = procedureMockUtils.getInstance<Params, Nft[]>(mockContext);
const result = await prepareIssueNft.call(proc, args);

expect(result).toEqual({
transaction,
args: [rawAssetId, [], defaultPortfolioKind],
transactions: [
{
transaction,
args: [rawAssetId, [], defaultPortfolioKind],
},
],
resolver: expect.any(Function),
});
});

it('should issue the Nft to the Numbered portfolio that is specified', async () => {
const args = {
metadata: [],
metadataList: [[]],
collection,
portfolioId: numberedPortfolioId,
};
Expand All @@ -176,27 +188,31 @@ describe('issueNft procedure', () => {
nftInputToMetadataValueSpy.mockReturnValue([]);

const transaction = dsMockUtils.createTxMock('nft', 'issueNft');
const proc = procedureMockUtils.getInstance<Params, Nft>(mockContext);
const proc = procedureMockUtils.getInstance<Params, Nft[]>(mockContext);
const result = await prepareIssueNft.call(proc, args);

expect(result).toEqual({
transaction,
args: [rawAssetId, [], numberedPortfolioKind],
transactions: [
{
transaction,
args: [rawAssetId, [], numberedPortfolioKind],
},
],
resolver: expect.any(Function),
});
});

it('should throw if unneeded metadata is provided', () => {
const args = {
metadata: [{ type: MetadataType.Local, id: new BigNumber(1), value: 'test' }],
metadataList: [[{ type: MetadataType.Local, id: new BigNumber(1), value: 'test' }]],
collection,
};
nftInputToMetadataValueSpy.mockReturnValue([]);
mockContext.getSigningIdentity.mockResolvedValue(entityMockUtils.getIdentityInstance());

dsMockUtils.createTxMock('nft', 'issueNft');

const proc = procedureMockUtils.getInstance<Params, Nft>(mockContext);
const proc = procedureMockUtils.getInstance<Params, Nft[]>(mockContext);

const expectedError = new PolymeshError({
code: ErrorCode.ValidationError,
Expand All @@ -208,7 +224,7 @@ describe('issueNft procedure', () => {

it('should throw if not all needed metadata is given', () => {
const args = {
metadata: [],
metadataList: [[]],
collection: entityMockUtils.getNftCollectionInstance({
collectionKeys: [
{ type: MetadataType.Global, id: new BigNumber(1), specs: {}, name: 'Example Global' },
Expand All @@ -221,7 +237,7 @@ describe('issueNft procedure', () => {

dsMockUtils.createTxMock('nft', 'issueNft');

const proc = procedureMockUtils.getInstance<Params, Nft>(mockContext);
const proc = procedureMockUtils.getInstance<Params, Nft[]>(mockContext);

const expectedError = new PolymeshError({
code: ErrorCode.ValidationError,
Expand All @@ -233,9 +249,11 @@ describe('issueNft procedure', () => {

it('should not throw when all required metadata is provided', () => {
const args = {
metadata: [
{ type: MetadataType.Local, id: new BigNumber(1), value: 'local' },
{ type: MetadataType.Global, id: new BigNumber(2), value: 'global' },
metadataList: [
[
{ type: MetadataType.Local, id: new BigNumber(1), value: 'local' },
{ type: MetadataType.Global, id: new BigNumber(2), value: 'global' },
],
],
collection: entityMockUtils.getNftCollectionInstance({
collectionKeys: [
Expand All @@ -256,15 +274,15 @@ describe('issueNft procedure', () => {

dsMockUtils.createTxMock('nft', 'issueNft');

const proc = procedureMockUtils.getInstance<Params, Nft>(mockContext);
const proc = procedureMockUtils.getInstance<Params, Nft[]>(mockContext);

return expect(prepareIssueNft.call(proc, args)).resolves.not.toThrow();
});
});

describe('getAuthorization', () => {
it('should return the appropriate roles and permissions', () => {
const proc = procedureMockUtils.getInstance<Params, Nft>(mockContext);
const proc = procedureMockUtils.getInstance<Params, Nft[]>(mockContext);
const boundFunc = getAuthorization.bind(proc);

expect(boundFunc({ collection } as unknown as Params)).toEqual({
Expand Down Expand Up @@ -295,10 +313,10 @@ describe('issueNft procedure', () => {

it('should create an NFT entity', () => {
const context = dsMockUtils.getContextInstance();
const result = issueNftResolver(context)({} as ISubmittableResult);
const result = issuedNftsResolver(context)({} as ISubmittableResult);

expect(result.collection).toEqual(expect.objectContaining({ id: assetId }));
expect(result.id).toEqual(id);
expect(result[0]!.collection).toEqual(expect.objectContaining({ id: assetId }));
expect(result[0]!.id).toEqual(id);
});
});
});
Loading
Loading