1234567891011121314151617import '../interfaces/augment-api-rpc';18import '../interfaces/augment-api-query';19import {ApiPromise, Keyring} from '@polkadot/api';20import type {AccountId, EventRecord, Event} from '@polkadot/types/interfaces';21import {AnyTuple, IEvent, IKeyringPair} from '@polkadot/types/types';22import {evmToAddress} from '@polkadot/util-crypto';23import BN from 'bn.js';24import chai from 'chai';25import chaiAsPromised from 'chai-as-promised';26import {alicesPublicKey} from '../accounts';27import privateKey from '../substrate/privateKey';28import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api';29import {hexToStr, strToUTF16, utf16ToStr} from './util';30import {UpDataStructsRpcCollection, UpDataStructsCreateItemData} from '@polkadot/types/lookup';3132chai.use(chaiAsPromised);33const expect = chai.expect;3435export type CrossAccountId = {36 Substrate: string,37} | {38 Ethereum: string,39};4041export function normalizeAccountId(input: string | AccountId | CrossAccountId | IKeyringPair): CrossAccountId {42 if (typeof input === 'string') {43 if (input.length === 48 || input.length === 47) {44 return {Substrate: input};45 } else if (input.length === 42 && input.startsWith('0x')) {46 return {Ethereum: input.toLowerCase()};47 } else if (input.length === 40 && !input.startsWith('0x')) {48 return {Ethereum: '0x' + input.toLowerCase()};49 } else {50 throw new Error(`Unknown address format: "${input}"`);51 }52 }53 if ('address' in input) {54 return {Substrate: input.address};55 }56 if ('Ethereum' in input) {57 return {58 Ethereum: input.Ethereum.toLowerCase(),59 };60 } else if ('ethereum' in input) {61 return {62 Ethereum: (input as any).ethereum.toLowerCase(),63 };64 } else if ('Substrate' in input) {65 return input;66 } else if ('substrate' in input) {67 return {68 Substrate: (input as any).substrate,69 };70 }7172 73 return {Substrate: input.toString()};74}75export function toSubstrateAddress(input: string | CrossAccountId | IKeyringPair): string {76 input = normalizeAccountId(input);77 if ('Substrate' in input) {78 return input.Substrate;79 } else {80 return evmToAddress(input.Ethereum);81 }82}8384export const U128_MAX = (1n << 128n) - 1n;8586const MICROUNIQUE = 1_000_000_000_000n;87const MILLIUNIQUE = 1_000n * MICROUNIQUE;88const CENTIUNIQUE = 10n * MILLIUNIQUE;89export const UNIQUE = 100n * CENTIUNIQUE;9091type GenericResult = {92 success: boolean,93};9495interface CreateCollectionResult {96 success: boolean;97 collectionId: number;98}99100interface CreateItemResult {101 success: boolean;102 collectionId: number;103 itemId: number;104 recipient?: CrossAccountId;105}106107interface TransferResult {108 collectionId: number;109 itemId: number;110 sender?: CrossAccountId;111 recipient?: CrossAccountId;112 value: bigint;113}114115interface IReFungibleOwner {116 fraction: BN;117 owner: number[];118}119120interface IGetMessage {121 checkMsgUnqMethod: string;122 checkMsgTrsMethod: string;123 checkMsgSysMethod: string;124}125126export interface IFungibleTokenDataType {127 value: number;128}129130export interface IChainLimits {131 collectionNumbersLimit: number;132 accountTokenOwnershipLimit: number;133 collectionsAdminsLimit: number;134 customDataLimit: number;135 nftSponsorTransferTimeout: number;136 fungibleSponsorTransferTimeout: number;137 refungibleSponsorTransferTimeout: number;138 offchainSchemaLimit: number;139 constOnChainSchemaLimit: number;140}141142export interface IReFungibleTokenDataType {143 owner: IReFungibleOwner[];144 constData: number[];145}146147export function uniqueEventMessage(events: EventRecord[]): IGetMessage {148 let checkMsgUnqMethod = '';149 let checkMsgTrsMethod = '';150 let checkMsgSysMethod = '';151 events.forEach(({event: {method, section}}) => {152 if (section === 'common') {153 checkMsgUnqMethod = method;154 } else if (section === 'treasury') {155 checkMsgTrsMethod = method;156 } else if (section === 'system') {157 checkMsgSysMethod = method;158 } else { return null; }159 });160 const result: IGetMessage = {161 checkMsgUnqMethod,162 checkMsgTrsMethod,163 checkMsgSysMethod,164 };165 return result;166}167168export function getEvent<T extends Event>(events: EventRecord[], check: (event: IEvent<AnyTuple>) => event is T): T | undefined {169 const event = events.find(r => check(r.event));170 if (!event) return;171 return event.event as T;172}173174export function getGenericResult(events: EventRecord[]): GenericResult {175 const result: GenericResult = {176 success: false,177 };178 events.forEach(({event: {method}}) => {179 180 if (method === 'ExtrinsicSuccess') {181 result.success = true;182 }183 });184 return result;185}186187188189export function getCreateCollectionResult(events: EventRecord[]): CreateCollectionResult {190 let success = false;191 let collectionId = 0;192 events.forEach(({event: {data, method, section}}) => {193 194 if (method == 'ExtrinsicSuccess') {195 success = true;196 } else if ((section == 'common') && (method == 'CollectionCreated')) {197 collectionId = parseInt(data[0].toString(), 10);198 }199 });200 const result: CreateCollectionResult = {201 success,202 collectionId,203 };204 return result;205}206207export function getCreateItemsResult(events: EventRecord[]): CreateItemResult[] {208 let success = false;209 let collectionId = 0;210 let itemId = 0;211 let recipient;212213 const results : CreateItemResult[] = [];214215 events.forEach(({event: {data, method, section}}) => {216 217 if (method == 'ExtrinsicSuccess') {218 success = true;219 } else if ((section == 'common') && (method == 'ItemCreated')) {220 collectionId = parseInt(data[0].toString(), 10);221 itemId = parseInt(data[1].toString(), 10);222 recipient = normalizeAccountId(data[2].toJSON() as any);223224 const itemRes: CreateItemResult = {225 success,226 collectionId,227 itemId,228 recipient,229 };230231 results.push(itemRes);232 }233 });234235 return results;236}237238export function getCreateItemResult(events: EventRecord[]): CreateItemResult {239 let success = false;240 let collectionId = 0;241 let itemId = 0;242 let recipient;243 events.forEach(({event: {data, method, section}}) => {244 245 if (method == 'ExtrinsicSuccess') {246 success = true;247 } else if ((section == 'common') && (method == 'ItemCreated')) {248 collectionId = parseInt(data[0].toString(), 10);249 itemId = parseInt(data[1].toString(), 10);250 recipient = normalizeAccountId(data[2].toJSON() as any);251 }252 });253 const result: CreateItemResult = {254 success,255 collectionId,256 itemId,257 recipient,258 };259 return result;260}261262export function getTransferResult(api: ApiPromise, events: EventRecord[]): TransferResult {263 for (const {event} of events) {264 if (api.events.common.Transfer.is(event)) {265 const [collection, token, sender, recipient, value] = event.data;266 return {267 collectionId: collection.toNumber(),268 itemId: token.toNumber(),269 sender: normalizeAccountId(sender.toJSON() as any),270 recipient: normalizeAccountId(recipient.toJSON() as any),271 value: value.toBigInt(),272 };273 }274 }275 throw new Error('no transfer event');276}277278interface Nft {279 type: 'NFT';280}281282interface Fungible {283 type: 'Fungible';284 decimalPoints: number;285}286287interface ReFungible {288 type: 'ReFungible';289}290291type CollectionMode = Nft | Fungible | ReFungible;292293export type Property = {294 key: any,295 value: any,296};297298type Permission = {299 mutable: boolean;300 collectionAdmin: boolean;301 tokenOwner: boolean;302}303304type PropertyPermission = {305 key: any;306 permission: Permission;307}308309export type CreateCollectionParams = {310 mode: CollectionMode,311 name: string,312 description: string,313 tokenPrefix: string,314 schemaVersion: string,315 properties?: Array<Property>,316 propPerm?: Array<PropertyPermission>317};318319const defaultCreateCollectionParams: CreateCollectionParams = {320 description: 'description',321 mode: {type: 'NFT'},322 name: 'name',323 tokenPrefix: 'prefix',324 schemaVersion: 'ImageURL',325};326327export async function createCollectionExpectSuccess(params: Partial<CreateCollectionParams> = {}): Promise<number> {328 const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params};329330 let collectionId = 0;331 await usingApi(async (api) => {332 333 const collectionCountBefore = await getCreatedCollectionCount(api);334335 336 const alicePrivateKey = privateKey('//Alice');337338 let modeprm = {};339 if (mode.type === 'NFT') {340 modeprm = {nft: null};341 } else if (mode.type === 'Fungible') {342 modeprm = {fungible: mode.decimalPoints};343 } else if (mode.type === 'ReFungible') {344 modeprm = {refungible: null};345 }346347 const tx = api.tx.unique.createCollectionEx({348 name: strToUTF16(name),349 description: strToUTF16(description),350 tokenPrefix: strToUTF16(tokenPrefix),351 mode: modeprm as any352 });353 const events = await submitTransactionAsync(alicePrivateKey, tx);354 const result = getCreateCollectionResult(events);355356 357 const collectionCountAfter = await getCreatedCollectionCount(api);358359 360 const collection = await queryCollectionExpectSuccess(api, result.collectionId);361362 363 364 expect(result.success).to.be.true;365 expect(result.collectionId).to.be.equal(collectionCountAfter);366 367 expect(collection).to.be.not.null;368 expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.');369 expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicesPublicKey));370 expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name);371 expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description);372 expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix);373374 collectionId = result.collectionId;375 });376377 return collectionId;378}379380export async function createCollectionWithPropsExpectSuccess(params: Partial<CreateCollectionParams> = {}): Promise<number> {381 const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params};382383 let collectionId = 0;384 await usingApi(async (api) => {385 386 const collectionCountBefore = await getCreatedCollectionCount(api);387388 389 const alicePrivateKey = privateKey('//Alice');390391 let modeprm = {};392 if (mode.type === 'NFT') {393 modeprm = {nft: null};394 } else if (mode.type === 'Fungible') {395 modeprm = {fungible: mode.decimalPoints};396 } else if (mode.type === 'ReFungible') {397 modeprm = {refungible: null};398 }399400 const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm});401 const events = await submitTransactionAsync(alicePrivateKey, tx);402 const result = getCreateCollectionResult(events);403404 405 const collectionCountAfter = await getCreatedCollectionCount(api);406407 408 const collection = await queryCollectionExpectSuccess(api, result.collectionId);409410 411 412 expect(result.success).to.be.true;413 expect(result.collectionId).to.be.equal(collectionCountAfter);414 415 expect(collection).to.be.not.null;416 expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.');417 expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicesPublicKey));418 expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name);419 expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description);420 expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix);421422423 collectionId = result.collectionId;424 });425426 return collectionId;427}428429export async function createCollectionWithPropsExpectFailure(params: Partial<CreateCollectionParams> = {}) {430 const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params};431432 await usingApi(async (api) => {433 434 const collectionCountBefore = await getCreatedCollectionCount(api);435436 437 const alicePrivateKey = privateKey('//Alice');438439 let modeprm = {};440 if (mode.type === 'NFT') {441 modeprm = {nft: null};442 } else if (mode.type === 'Fungible') {443 modeprm = {fungible: mode.decimalPoints};444 } else if (mode.type === 'ReFungible') {445 modeprm = {refungible: null};446 }447448 const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm});449 await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;450451452 453 const collectionCountAfter = await getCreatedCollectionCount(api);454455 expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.');456 });457}458459export async function createCollectionExpectFailure(params: Partial<CreateCollectionParams> = {}) {460 const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params};461462 let modeprm = {};463 if (mode.type === 'NFT') {464 modeprm = {nft: null};465 } else if (mode.type === 'Fungible') {466 modeprm = {fungible: mode.decimalPoints};467 } else if (mode.type === 'ReFungible') {468 modeprm = {refungible: null};469 }470471 await usingApi(async (api) => {472 473 const collectionCountBefore = await getCreatedCollectionCount(api);474475 476 const alicePrivateKey = privateKey('//Alice');477 const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});478 await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;479480 481 const collectionCountAfter = await getCreatedCollectionCount(api);482483 484 expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.');485 });486}487488export async function findUnusedAddress(api: ApiPromise, seedAddition = ''): Promise<IKeyringPair> {489 let bal = 0n;490 let unused;491 do {492 const randomSeed = 'seed' + Math.floor(Math.random() * Math.floor(10000)) + seedAddition;493 const keyring = new Keyring({type: 'sr25519'});494 unused = keyring.addFromUri(`//${randomSeed}`);495 bal = (await api.query.system.account(unused.address)).data.free.toBigInt();496 } while (bal !== 0n);497 return unused;498}499500export async function getAllowance(api: ApiPromise, collectionId: number, owner: CrossAccountId | string, approved: CrossAccountId | string, tokenId: number) {501 return (await api.rpc.unique.allowance(collectionId, normalizeAccountId(owner), normalizeAccountId(approved), tokenId)).toBigInt();502}503504export function findUnusedAddresses(api: ApiPromise, amount: number): Promise<IKeyringPair[]> {505 return Promise.all(new Array(amount).fill(null).map(() => findUnusedAddress(api, '_' + Date.now())));506}507508export async function findNotExistingCollection(api: ApiPromise): Promise<number> {509 const totalNumber = await getCreatedCollectionCount(api);510 const newCollection: number = totalNumber + 1;511 return newCollection;512}513514function getDestroyResult(events: EventRecord[]): boolean {515 let success = false;516 events.forEach(({event: {method}}) => {517 if (method == 'ExtrinsicSuccess') {518 success = true;519 }520 });521 return success;522}523524export async function destroyCollectionExpectFailure(collectionId: number, senderSeed = '//Alice') {525 await usingApi(async (api) => {526 527 const alicePrivateKey = privateKey(senderSeed);528 const tx = api.tx.unique.destroyCollection(collectionId);529 await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;530 });531}532533export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed = '//Alice') {534 await usingApi(async (api) => {535 536 const alicePrivateKey = privateKey(senderSeed);537 const tx = api.tx.unique.destroyCollection(collectionId);538 const events = await submitTransactionAsync(alicePrivateKey, tx);539 const result = getDestroyResult(events);540 expect(result).to.be.true;541542 543 expect(await getDetailedCollectionInfo(api, collectionId)).to.be.null;544 });545}546547export async function setCollectionLimitsExpectSuccess(sender: IKeyringPair, collectionId: number, limits: any) {548 await usingApi(async (api) => {549 const tx = api.tx.unique.setCollectionLimits(collectionId, limits);550 const events = await submitTransactionAsync(sender, tx);551 const result = getGenericResult(events);552553 expect(result.success).to.be.true;554 });555}556557export const setCollectionPermissionsExpectSuccess = async (sender: IKeyringPair, collectionId: number, permissions: {mintMode?: boolean, access?: 'Normal' | 'AllowList', nesting?: 'Disabled' | 'Owner' | {OwnerRestricted: number[]}}) => {558 await usingApi(async(api) => {559 const tx = api.tx.unique.setCollectionPermissions(collectionId, permissions);560 const events = await submitTransactionAsync(sender, tx);561 const result = getGenericResult(events);562563 expect(result.success).to.be.true;564 });565}566567export async function setCollectionLimitsExpectFailure(sender: IKeyringPair, collectionId: number, limits: any) {568 await usingApi(async (api) => {569 const tx = api.tx.unique.setCollectionLimits(collectionId, limits);570 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;571 const result = getGenericResult(events);572573 expect(result.success).to.be.false;574 });575}576577export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string, sender = '//Alice') {578 await usingApi(async (api) => {579580 581 const senderPrivateKey = privateKey(sender);582 const tx = api.tx.unique.setCollectionSponsor(collectionId, sponsor);583 const events = await submitTransactionAsync(senderPrivateKey, tx);584 const result = getGenericResult(events);585586 587 const collection = await queryCollectionExpectSuccess(api, collectionId);588589 590 expect(result.success).to.be.true;591 expect(collection.sponsorship.toJSON()).to.deep.equal({592 unconfirmed: sponsor,593 });594 });595}596597export async function removeCollectionSponsorExpectSuccess(collectionId: number, sender = '//Alice') {598 await usingApi(async (api) => {599600 601 const alicePrivateKey = privateKey(sender);602 const tx = api.tx.unique.removeCollectionSponsor(collectionId);603 const events = await submitTransactionAsync(alicePrivateKey, tx);604 const result = getGenericResult(events);605606 607 const collection = await queryCollectionExpectSuccess(api, collectionId);608609 610 expect(result.success).to.be.true;611 expect(collection.sponsorship.toJSON()).to.be.deep.equal({disabled: null});612 });613}614615export async function removeCollectionSponsorExpectFailure(collectionId: number, senderSeed = '//Alice') {616 await usingApi(async (api) => {617618 619 const alicePrivateKey = privateKey(senderSeed);620 const tx = api.tx.unique.removeCollectionSponsor(collectionId);621 await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;622 });623}624625export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string, senderSeed = '//Alice') {626 await usingApi(async (api) => {627628 629 const alicePrivateKey = privateKey(senderSeed);630 const tx = api.tx.unique.setCollectionSponsor(collectionId, sponsor);631 await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;632 });633}634635export async function confirmSponsorshipExpectSuccess(collectionId: number, senderSeed = '//Alice') {636 await usingApi(async (api) => {637638 639 const sender = privateKey(senderSeed);640 const tx = api.tx.unique.confirmSponsorship(collectionId);641 const events = await submitTransactionAsync(sender, tx);642 const result = getGenericResult(events);643644 645 const collection = await queryCollectionExpectSuccess(api, collectionId);646647 648 expect(result.success).to.be.true;649 expect(collection.sponsorship.toJSON()).to.be.deep.equal({650 confirmed: sender.address,651 });652 });653}654655656export async function confirmSponsorshipExpectFailure(collectionId: number, senderSeed = '//Alice') {657 await usingApi(async (api) => {658659 660 const sender = privateKey(senderSeed);661 const tx = api.tx.unique.confirmSponsorship(collectionId);662 await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;663 });664}665666export async function enableContractSponsoringExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) {667 await usingApi(async (api) => {668 const tx = api.tx.unique.enableContractSponsoring(contractAddress, enable);669 const events = await submitTransactionAsync(sender, tx);670 const result = getGenericResult(events);671672 expect(result.success).to.be.true;673 });674}675676export async function enableContractSponsoringExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) {677 await usingApi(async (api) => {678 const tx = api.tx.unique.enableContractSponsoring(contractAddress, enable);679 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;680 const result = getGenericResult(events);681682 expect(result.success).to.be.false;683 });684}685686export async function setTransferFlagExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) {687688 await usingApi(async (api) => {689690 const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);691 const events = await submitTransactionAsync(sender, tx);692 const result = getGenericResult(events);693694 expect(result.success).to.be.true;695 });696}697698export async function setTransferFlagExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) {699700 await usingApi(async (api) => {701702 const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);703 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;704 const result = getGenericResult(events);705706 expect(result.success).to.be.false;707 });708}709710export async function setContractSponsoringRateLimitExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) {711 await usingApi(async (api) => {712 const tx = api.tx.unique.setContractSponsoringRateLimit(contractAddress, rateLimit);713 const events = await submitTransactionAsync(sender, tx);714 const result = getGenericResult(events);715716 expect(result.success).to.be.true;717 });718}719720export async function setContractSponsoringRateLimitExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) {721 await usingApi(async (api) => {722 const tx = api.tx.unique.setContractSponsoringRateLimit(contractAddress, rateLimit);723 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;724 const result = getGenericResult(events);725726 expect(result.success).to.be.false;727 });728}729730export async function getNextSponsored(731 api: ApiPromise,732 collectionId: number,733 account: string | CrossAccountId,734 tokenId: number,735): Promise<number> {736 return Number((await api.rpc.unique.nextSponsored(collectionId, account, tokenId)).unwrapOr(-1));737}738739export async function toggleContractAllowlistExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, value = true) {740 await usingApi(async (api) => {741 const tx = api.tx.unique.toggleContractAllowList(contractAddress, value);742 const events = await submitTransactionAsync(sender, tx);743 const result = getGenericResult(events);744745 expect(result.success).to.be.true;746 });747}748749export async function isAllowlistedInContract(contractAddress: AccountId | string, user: string) {750 let allowlisted = false;751 await usingApi(async (api) => {752 allowlisted = (await api.query.unique.contractAllowList(contractAddress, user)).toJSON() as boolean;753 });754 return allowlisted;755}756757export async function addToContractAllowListExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) {758 await usingApi(async (api) => {759 const tx = api.tx.unique.addToContractAllowList(contractAddress.toString(), user.toString());760 const events = await submitTransactionAsync(sender, tx);761 const result = getGenericResult(events);762763 expect(result.success).to.be.true;764 });765}766767export async function removeFromContractAllowListExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) {768 await usingApi(async (api) => {769 const tx = api.tx.unique.removeFromContractAllowList(contractAddress.toString(), user.toString());770 const events = await submitTransactionAsync(sender, tx);771 const result = getGenericResult(events);772773 expect(result.success).to.be.true;774 });775}776777export async function removeFromContractAllowListExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) {778 await usingApi(async (api) => {779 const tx = api.tx.unique.removeFromContractAllowList(contractAddress.toString(), user.toString());780 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;781 const result = getGenericResult(events);782783 expect(result.success).to.be.false;784 });785}786787export async function setOffchainSchemaExpectSuccess(sender: IKeyringPair, collectionId: number, data: number[]) {788 await usingApi(async (api) => {789 const tx = api.tx.unique.setOffchainSchema(collectionId, '0x' + Buffer.from(data).toString('hex'));790 const events = await submitTransactionAsync(sender, tx);791 const result = getGenericResult(events);792793 expect(result.success).to.be.true;794 });795}796797export async function setOffchainSchemaExpectFailure(sender: IKeyringPair, collectionId: number, data: number[]) {798 await usingApi(async (api) => {799 const tx = api.tx.unique.setOffchainSchema(collectionId, '0x' + Buffer.from(data).toString('hex'));800 await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;801 });802}803804export interface CreateFungibleData {805 readonly Value: bigint;806}807808export interface CreateReFungibleData { }809export interface CreateNftData { }810811export type CreateItemData = {812 NFT: CreateNftData;813} | {814 Fungible: CreateFungibleData;815} | {816 ReFungible: CreateReFungibleData;817};818819export async function burnItemExpectSuccess(sender: IKeyringPair, collectionId: number, tokenId: number, value = 1) {820 await usingApi(async (api) => {821 const balanceBefore = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId);822 823 expect(balanceBefore >= BigInt(value)).to.be.true;824825 const tx = api.tx.unique.burnItem(collectionId, tokenId, value);826 const events = await submitTransactionAsync(sender, tx);827 const result = getGenericResult(events);828 expect(result.success).to.be.true;829830 const balanceAfter = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId);831 expect(balanceAfter + BigInt(value)).to.be.equal(balanceBefore);832 });833}834835export async function836approveExpectSuccess(837 collectionId: number,838 tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string, amount: number | bigint = 1,839) {840 await usingApi(async (api: ApiPromise) => {841 const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount);842 const events = await submitTransactionAsync(owner, approveUniqueTx);843 const result = getGenericResult(events);844 expect(result.success).to.be.true;845846 expect(await getAllowance(api, collectionId, owner.address, approved, tokenId)).to.be.equal(BigInt(amount));847 });848}849850export async function adminApproveFromExpectSuccess(851 collectionId: number,852 tokenId: number, admin: IKeyringPair, owner: CrossAccountId | string, approved: CrossAccountId | string, amount: number | bigint = 1,853) {854 await usingApi(async (api: ApiPromise) => {855 const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount);856 const events = await submitTransactionAsync(admin, approveUniqueTx);857 const result = getGenericResult(events);858 expect(result.success).to.be.true;859860 expect(await getAllowance(api, collectionId, owner, approved, tokenId)).to.be.equal(BigInt(amount));861 });862}863864export async function865transferFromExpectSuccess(866 collectionId: number,867 tokenId: number,868 accountApproved: IKeyringPair,869 accountFrom: IKeyringPair | CrossAccountId,870 accountTo: IKeyringPair | CrossAccountId,871 value: number | bigint = 1,872 type = 'NFT',873) {874 await usingApi(async (api: ApiPromise) => {875 const from = normalizeAccountId(accountFrom);876 const to = normalizeAccountId(accountTo);877 let balanceBefore = 0n;878 if (type === 'Fungible' || type === 'ReFungible') {879 balanceBefore = await getBalance(api, collectionId, to, tokenId);880 }881 const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom), to, collectionId, tokenId, value);882 const events = await submitTransactionAsync(accountApproved, transferFromTx);883 const result = getCreateItemResult(events);884 885 expect(result.success).to.be.true;886 if (type === 'NFT') {887 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to);888 }889 if (type === 'Fungible') {890 const balanceAfter = await getBalance(api, collectionId, to, tokenId);891 if (JSON.stringify(to) !== JSON.stringify(from)) {892 expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value));893 } else {894 expect(balanceAfter).to.be.equal(balanceBefore);895 }896 }897 if (type === 'ReFungible') {898 expect(await getBalance(api, collectionId, to, tokenId)).to.be.equal(balanceBefore + BigInt(value));899 }900 });901}902903export async function904transferFromExpectFail(905 collectionId: number,906 tokenId: number,907 accountApproved: IKeyringPair,908 accountFrom: IKeyringPair,909 accountTo: IKeyringPair,910 value: number | bigint = 1,911) {912 await usingApi(async (api: ApiPromise) => {913 const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom.address), normalizeAccountId(accountTo.address), collectionId, tokenId, value);914 const events = await expect(submitTransactionExpectFailAsync(accountApproved, transferFromTx)).to.be.rejected;915 const result = getCreateCollectionResult(events);916 917 expect(result.success).to.be.false;918 });919}920921922async function getBlockNumber(api: ApiPromise): Promise<number> {923 return new Promise<number>(async (resolve) => {924 const unsubscribe = await api.rpc.chain.subscribeNewHeads((head) => {925 unsubscribe();926 resolve(head.number.toNumber());927 });928 });929}930931export async function addCollectionAdminExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | CrossAccountId) {932 await usingApi(async (api) => {933 const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(address));934 const events = await submitTransactionAsync(sender, changeAdminTx);935 const result = getCreateCollectionResult(events);936 expect(result.success).to.be.true;937 });938}939940export async function941getFreeBalance(account: IKeyringPair): Promise<bigint> {942 let balance = 0n;943 await usingApi(async (api) => {944 balance = BigInt((await api.query.system.account(account.address)).data.free.toString());945 });946947 return balance;948}949950export async function transferBalanceTo(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) {951 const tx = api.tx.balances.transfer(target, amount);952 const events = await submitTransactionAsync(source, tx);953 const result = getGenericResult(events);954 expect(result.success).to.be.true;955}956957export async function958scheduleTransferExpectSuccess(959 collectionId: number,960 tokenId: number,961 sender: IKeyringPair,962 recipient: IKeyringPair,963 value: number | bigint = 1,964 blockSchedule: number,965) {966 await usingApi(async (api: ApiPromise) => {967 const blockNumber: number | undefined = await getBlockNumber(api);968 const expectedBlockNumber = blockNumber + blockSchedule;969970 expect(blockNumber).to.be.greaterThan(0);971 const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value);972 const scheduleTx = api.tx.scheduler.schedule(expectedBlockNumber, null, 0, transferTx as any);973974 await submitTransactionAsync(sender, scheduleTx);975976 const recipientBalanceBefore = (await api.query.system.account(recipient.address)).data.free.toBigInt();977978 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(sender.address));979980 981 await waitNewBlocks(blockSchedule + 1);982983 const recipientBalanceAfter = (await api.query.system.account(recipient.address)).data.free.toBigInt();984985 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(recipient.address));986 expect(recipientBalanceAfter).to.be.equal(recipientBalanceBefore);987 });988}989990991export async function992transferExpectSuccess(993 collectionId: number,994 tokenId: number,995 sender: IKeyringPair,996 recipient: IKeyringPair | CrossAccountId,997 value: number | bigint = 1,998 type = 'NFT',999) {1000 await usingApi(async (api: ApiPromise) => {1001 const from = normalizeAccountId(sender);1002 const to = normalizeAccountId(recipient);10031004 let balanceBefore = 0n;1005 if (type === 'Fungible') {1006 balanceBefore = await getBalance(api, collectionId, to, tokenId);1007 }1008 const transferTx = api.tx.unique.transfer(to, collectionId, tokenId, value);1009 const events = await executeTransaction(api, sender, transferTx);10101011 const result = getTransferResult(api, events);1012 expect(result.collectionId).to.be.equal(collectionId);1013 expect(result.itemId).to.be.equal(tokenId);1014 expect(result.sender).to.be.deep.equal(normalizeAccountId(sender.address));1015 expect(result.recipient).to.be.deep.equal(to);1016 expect(result.value).to.be.equal(BigInt(value));10171018 if (type === 'NFT') {1019 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to);1020 }1021 if (type === 'Fungible') {1022 const balanceAfter = await getBalance(api, collectionId, to, tokenId);1023 if (JSON.stringify(to) !== JSON.stringify(from)) {1024 expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value));1025 } else {1026 expect(balanceAfter).to.be.equal(balanceBefore);1027 }1028 }1029 if (type === 'ReFungible') {1030 expect(await getBalance(api, collectionId, to, tokenId) >= value).to.be.true;1031 }1032 });1033}10341035export async function1036transferExpectFailure(1037 collectionId: number,1038 tokenId: number,1039 sender: IKeyringPair,1040 recipient: IKeyringPair | CrossAccountId,1041 value: number | bigint = 1,1042) {1043 await usingApi(async (api: ApiPromise) => {1044 const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value);1045 const events = await expect(submitTransactionExpectFailAsync(sender, transferTx)).to.be.rejected;1046 const result = getGenericResult(events);1047 1048 1049 1050 expect(result.success).to.be.false;1051 1052 });1053}10541055export async function1056approveExpectFail(1057 collectionId: number,1058 tokenId: number, owner: IKeyringPair, approved: IKeyringPair, amount: number | bigint = 1,1059) {1060 await usingApi(async (api: ApiPromise) => {1061 const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved.address), collectionId, tokenId, amount);1062 const events = await expect(submitTransactionExpectFailAsync(owner, approveUniqueTx)).to.be.rejected;1063 const result = getCreateCollectionResult(events);1064 1065 expect(result.success).to.be.false;1066 });1067}10681069export async function getBalance(1070 api: ApiPromise,1071 collectionId: number,1072 owner: string | CrossAccountId,1073 token: number,1074): Promise<bigint> {1075 return (await api.rpc.unique.balance(collectionId, normalizeAccountId(owner), token)).toBigInt();1076}1077export async function getTokenOwner(1078 api: ApiPromise,1079 collectionId: number,1080 token: number,1081): Promise<CrossAccountId> {1082 const owner = (await api.rpc.unique.tokenOwner(collectionId, token)).toJSON() as any;1083 if (owner == null) throw new Error('owner == null');1084 return normalizeAccountId(owner);1085}1086export async function getTopmostTokenOwner(1087 api: ApiPromise,1088 collectionId: number,1089 token: number,1090): Promise<CrossAccountId> {1091 const owner = (await api.rpc.unique.topmostTokenOwner(collectionId, token)).toJSON() as any;1092 if (owner == null) throw new Error('owner == null');1093 return normalizeAccountId(owner);1094}1095export async function isTokenExists(1096 api: ApiPromise,1097 collectionId: number,1098 token: number,1099): Promise<boolean> {1100 return (await api.rpc.unique.tokenExists(collectionId, token)).toJSON();1101}1102export async function getLastTokenId(1103 api: ApiPromise,1104 collectionId: number,1105): Promise<number> {1106 return (await api.rpc.unique.lastTokenId(collectionId)).toJSON();1107}1108export async function getAdminList(1109 api: ApiPromise,1110 collectionId: number,1111): Promise<string[]> {1112 return (await api.rpc.unique.adminlist(collectionId)).toHuman() as any;1113}1114export async function getConstMetadata(1115 api: ApiPromise,1116 collectionId: number,1117 tokenId: number,1118): Promise<number[]> {1119 return [...(await api.rpc.unique.constMetadata(collectionId, tokenId))];1120}11211122export async function createFungibleItemExpectSuccess(1123 sender: IKeyringPair,1124 collectionId: number,1125 data: CreateFungibleData,1126 owner: CrossAccountId | string = sender.address,1127) {1128 return await usingApi(async (api) => {1129 const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), {Fungible: data});11301131 const events = await submitTransactionAsync(sender, tx);1132 const result = getCreateItemResult(events);11331134 expect(result.success).to.be.true;1135 return result.itemId;1136 });1137}11381139export async function createMultipleItemsWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) {1140 await usingApi(async (api) => {1141 const to = normalizeAccountId(owner);1142 const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData);11431144 const events = await submitTransactionAsync(sender, tx);1145 const result = getCreateItemsResult(events);11461147 for (const res of result) {1148 expect(await api.rpc.unique.tokenProperties(collectionId, res.itemId)).not.to.be.empty;1149 }1150 });1151}11521153export async function createMultipleItemsWithPropsExpectFailure(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) {1154 await usingApi(async (api) => {1155 const to = normalizeAccountId(owner);1156 const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData);11571158 const events = await expect(await submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;1159 const result = getCreateItemsResult(events);11601161 expect(result).to.be.equal(false);1162 });1163}11641165export async function createMultipleItemsExWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any) {1166 await usingApi(async (api) => {1167 const tx = api.tx.unique.createMultipleItemsEx(collectionId, itemsData);11681169 const events = await submitTransactionAsync(sender, tx);1170 const result = getCreateItemsResult(events);11711172 for (const res of result) {1173 expect(await api.rpc.unique.tokenProperties(collectionId, res.itemId)).not.to.be.empty;1174 }1175 });1176}11771178export async function createItemWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, props: Array<Property>, owner: CrossAccountId | string = sender.address) {1179 let newItemId = 0;1180 await usingApi(async (api) => {1181 const to = normalizeAccountId(owner);1182 const itemCountBefore = await getLastTokenId(api, collectionId);1183 const itemBalanceBefore = await getBalance(api, collectionId, to, newItemId);11841185 let tx;1186 if (createMode === 'Fungible') {1187 const createData = {fungible: {value: 10}};1188 tx = api.tx.unique.createItem(collectionId, to, createData as any);1189 } else if (createMode === 'ReFungible') {1190 const createData = {refungible: {const_data: [], pieces: 100}};1191 tx = api.tx.unique.createItem(collectionId, to, createData as any);1192 } else {1193 const data = api.createType('UpDataStructsCreateItemData', {NFT: {constData: 'test', properties: props}});1194 tx = api.tx.unique.createItem(collectionId, to, data as UpDataStructsCreateItemData);1195 }11961197 const events = await submitTransactionAsync(sender, tx);1198 const result = getCreateItemResult(events);11991200 const itemCountAfter = await getLastTokenId(api, collectionId);1201 const itemBalanceAfter = await getBalance(api, collectionId, to, newItemId);12021203 if (createMode === 'NFT') {1204 expect(await api.rpc.unique.tokenProperties(collectionId, result.itemId)).not.to.be.empty;1205 }12061207 1208 1209 expect(result.success).to.be.true;1210 if (createMode === 'Fungible') {1211 expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n);1212 } else {1213 expect(itemCountAfter).to.be.equal(itemCountBefore + 1);1214 }1215 expect(collectionId).to.be.equal(result.collectionId);1216 expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString());1217 expect(to).to.be.deep.equal(result.recipient);1218 newItemId = result.itemId;1219 });1220 return newItemId;1221}12221223export async function createItemWithPropsExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, props: Array<Property>, owner: CrossAccountId | string = sender.address) {1224 await usingApi(async (api) => {12251226 let tx;1227 if (createMode === 'NFT') {1228 const data = api.createType('UpDataStructsCreateItemData', {NFT: {constData: 'test', properties: props}});1229 tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), data);1230 } else {1231 tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode);1232 }123312341235 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;1236 const result = getCreateItemResult(events);12371238 expect(result.success).to.be.false;1239 });1240}12411242export async function createItemExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) {1243 let newItemId = 0;1244 await usingApi(async (api) => {1245 const to = normalizeAccountId(owner);1246 const itemCountBefore = await getLastTokenId(api, collectionId);1247 const itemBalanceBefore = await getBalance(api, collectionId, to, newItemId);12481249 let tx;1250 if (createMode === 'Fungible') {1251 const createData = {fungible: {value: 10}};1252 tx = api.tx.unique.createItem(collectionId, to, createData as any);1253 } else if (createMode === 'ReFungible') {1254 const createData = {refungible: {const_data: [], pieces: 100}};1255 tx = api.tx.unique.createItem(collectionId, to, createData as any);1256 } else {1257 const createData = {nft: {const_data: []}};1258 tx = api.tx.unique.createItem(collectionId, to, createData as any);1259 }12601261 const events = await submitTransactionAsync(sender, tx);1262 const result = getCreateItemResult(events);12631264 const itemCountAfter = await getLastTokenId(api, collectionId);1265 const itemBalanceAfter = await getBalance(api, collectionId, to, newItemId);12661267 1268 1269 expect(result.success).to.be.true;1270 if (createMode === 'Fungible') {1271 expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n);1272 } else {1273 expect(itemCountAfter).to.be.equal(itemCountBefore + 1);1274 }1275 expect(collectionId).to.be.equal(result.collectionId);1276 expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString());1277 expect(to).to.be.deep.equal(result.recipient);1278 newItemId = result.itemId;1279 });1280 return newItemId;1281}12821283export async function createItemExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) {1284 await usingApi(async (api) => {1285 const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode);12861287 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;1288 const result = getCreateItemResult(events);12891290 expect(result.success).to.be.false;1291 });1292}12931294export async function setPublicAccessModeExpectSuccess(1295 sender: IKeyringPair, collectionId: number,1296 accessMode: 'Normal' | 'AllowList',1297) {1298 await usingApi(async (api) => {12991300 1301 const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: accessMode});1302 const events = await submitTransactionAsync(sender, tx);1303 const result = getGenericResult(events);13041305 1306 const collection = await queryCollectionExpectSuccess(api, collectionId);13071308 1309 1310 expect(result.success).to.be.true;1311 expect(collection.permissions.access.toHuman()).to.be.equal(accessMode);1312 });1313}13141315export async function setPublicAccessModeExpectFail(1316 sender: IKeyringPair, collectionId: number,1317 accessMode: 'Normal' | 'AllowList',1318) {1319 await usingApi(async (api) => {13201321 1322 const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: accessMode});1323 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;1324 const result = getGenericResult(events);13251326 1327 1328 expect(result.success).to.be.false;1329 });1330}13311332export async function enableAllowListExpectSuccess(sender: IKeyringPair, collectionId: number) {1333 await setPublicAccessModeExpectSuccess(sender, collectionId, 'AllowList');1334}13351336export async function enableAllowListExpectFail(sender: IKeyringPair, collectionId: number) {1337 await setPublicAccessModeExpectFail(sender, collectionId, 'AllowList');1338}13391340export async function disableAllowListExpectSuccess(sender: IKeyringPair, collectionId: number) {1341 await setPublicAccessModeExpectSuccess(sender, collectionId, 'Normal');1342}13431344export async function setMintPermissionExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) {1345 await usingApi(async (api) => {13461347 1348 const tx = api.tx.unique.setCollectionPermissions(collectionId, {mintMode: enabled});1349 const events = await submitTransactionAsync(sender, tx);1350 const result = getGenericResult(events);1351 expect(result.success).to.be.true;13521353 1354 const collection = await queryCollectionExpectSuccess(api, collectionId);13551356 expect(collection.permissions.mintMode.toHuman()).to.be.equal(enabled);1357 });1358}13591360export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) {1361 await setMintPermissionExpectSuccess(sender, collectionId, true);1362}13631364export async function setMintPermissionExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) {1365 await usingApi(async (api) => {1366 1367 const tx = api.tx.unique.setCollectionPermissions(collectionId, {mintMode: enabled});1368 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;1369 const result = getCreateCollectionResult(events);1370 1371 expect(result.success).to.be.false;1372 });1373}13741375export async function setChainLimitsExpectFailure(sender: IKeyringPair, limits: IChainLimits) {1376 await usingApi(async (api) => {1377 1378 const tx = api.tx.unique.setChainLimits(limits);1379 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;1380 const result = getCreateCollectionResult(events);1381 1382 expect(result.success).to.be.false;1383 });1384}13851386export async function isAllowlisted(api: ApiPromise, collectionId: number, address: string | CrossAccountId) {1387 return (await api.rpc.unique.allowed(collectionId, normalizeAccountId(address))).toJSON();1388}13891390export async function addToAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId | CrossAccountId) {1391 await usingApi(async (api) => {1392 expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.false;13931394 1395 const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address));1396 const events = await submitTransactionAsync(sender, tx);1397 const result = getGenericResult(events);1398 expect(result.success).to.be.true;13991400 expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true;1401 });1402}14031404export async function addToAllowListAgainExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId) {1405 await usingApi(async (api) => {14061407 expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true;14081409 1410 const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address));1411 const events = await submitTransactionAsync(sender, tx);1412 const result = getGenericResult(events);1413 expect(result.success).to.be.true;14141415 expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true;1416 });1417}14181419export async function addToAllowListExpectFail(sender: IKeyringPair, collectionId: number, address: string | AccountId) {1420 await usingApi(async (api) => {14211422 1423 const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address));1424 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;1425 const result = getGenericResult(events);14261427 1428 1429 expect(result.success).to.be.false;1430 });1431}14321433export async function removeFromAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: CrossAccountId) {1434 await usingApi(async (api) => {1435 1436 const tx = api.tx.unique.removeFromAllowList(collectionId, normalizeAccountId(address));1437 const events = await submitTransactionAsync(sender, tx);1438 const result = getGenericResult(events);14391440 1441 1442 expect(result.success).to.be.true;1443 });1444}14451446export async function removeFromAllowListExpectFailure(sender: IKeyringPair, collectionId: number, address: CrossAccountId) {1447 await usingApi(async (api) => {1448 1449 const tx = api.tx.unique.removeFromAllowList(collectionId, normalizeAccountId(address));1450 const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;1451 const result = getGenericResult(events);14521453 1454 1455 expect(result.success).to.be.false;1456 });1457}14581459export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number)1460 : Promise<UpDataStructsRpcCollection | null> => {1461 return (await api.rpc.unique.collectionById(collectionId)).unwrapOr(null);1462};14631464export const getCreatedCollectionCount = async (api: ApiPromise): Promise<number> => {1465 1466 return (await api.rpc.unique.collectionStats()).created.toNumber();1467};14681469export async function queryCollectionExpectSuccess(api: ApiPromise, collectionId: number): Promise<UpDataStructsRpcCollection> {1470 return (await api.rpc.unique.collectionById(collectionId)).unwrap();1471}14721473export async function waitNewBlocks(blocksCount = 1): Promise<void> {1474 await usingApi(async (api) => {1475 const promise = new Promise<void>(async (resolve) => {1476 const unsubscribe = await api.rpc.chain.subscribeNewHeads(() => {1477 if (blocksCount > 0) {1478 blocksCount--;1479 } else {1480 unsubscribe();1481 resolve();1482 }1483 });1484 });1485 return promise;1486 });1487}