From f1c5a7f2d79f322b115c64d8c0eea414d9e5eafa Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Fri, 2 Feb 2024 14:37:29 +0100 Subject: [PATCH 01/20] WIP: Computing module --- .../modules/computing/computing-contract.ts | 72 +++++++++++++++++++ .../sdk/src/modules/computing/computing.ts | 43 +++++++++++ packages/sdk/src/modules/storage/storage.ts | 2 +- packages/sdk/src/tests/computing.test.ts | 28 ++++++++ packages/sdk/src/tests/helpers/helper.ts | 4 ++ packages/sdk/src/types/computing.ts | 27 +++++++ 6 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 packages/sdk/src/modules/computing/computing-contract.ts create mode 100644 packages/sdk/src/modules/computing/computing.ts create mode 100644 packages/sdk/src/tests/computing.test.ts create mode 100644 packages/sdk/src/types/computing.ts diff --git a/packages/sdk/src/modules/computing/computing-contract.ts b/packages/sdk/src/modules/computing/computing-contract.ts new file mode 100644 index 0000000..9c2e2b1 --- /dev/null +++ b/packages/sdk/src/modules/computing/computing-contract.ts @@ -0,0 +1,72 @@ +import { ApillonApi } from '../../lib/apillon-api'; +import { ApillonModel } from '../../lib/apillon'; +import { + ComputingContractData, + ComputingContractStatus, + ComputingContractType, +} from '../../types/computing'; + +export class ComputingContract extends ApillonModel { + /** + * Name of the contract. + */ + public name: string = null; + + /** + * Contract description. + */ + public description: string = null; + + /** + * The computing contract's type + */ + public contractType: ComputingContractType = null; + + /** + * The computing contract's status + */ + public contractStatus: ComputingContractStatus = null; + + /** + * The computing contract's on-chain address + */ + public contractAddress: string = null; + + /** + * The computing contract's on-chain deployer address + */ + public deployerAddress: string = null; + + /** + * The computing contract's deployment transaction hash + */ + public transactionHash: string = null; + + /** + * The computing contract's additional data + */ + public data: ComputingContractData = null; + + /** + * Constructor which should only be called via Computing class. + * @param uuid Unique identifier of the contract. + * @param data Data to populate computing contract with. + */ + constructor(uuid: string, data?: Partial) { + super(uuid); + this.API_PREFIX = `/computing/contracts/${uuid}`; + this.populate(data); + } + + /** + * Gets a computing contract. + * @returns ComputingContract instance + */ + async get(): Promise { + const data = await ApillonApi.get< + ComputingContract & { contractUuid: string } + >(this.API_PREFIX); + + return new ComputingContract(data.contractUuid, data); + } +} diff --git a/packages/sdk/src/modules/computing/computing.ts b/packages/sdk/src/modules/computing/computing.ts new file mode 100644 index 0000000..0661651 --- /dev/null +++ b/packages/sdk/src/modules/computing/computing.ts @@ -0,0 +1,43 @@ +import { ApillonModule } from '../../lib/apillon'; +import { ApillonApi } from '../../lib/apillon-api'; +import { constructUrlWithQueryParams } from '../../lib/common'; +import { IApillonList } from '../../types/apillon'; +import { IContractFilters } from '../../types/computing'; +import { ComputingContract } from './computing-contract'; + +export class Computing extends ApillonModule { + /** + * API url for computing. + */ + private API_PREFIX = '/computing/contracts'; + + /** + * Lists all computing contracts. + * @param {IContractFilters} params Filter for listing collections. + * @returns Array of ComputingContract objects. + */ + public async listContracts( + params?: IContractFilters, + ): Promise> { + const url = constructUrlWithQueryParams(this.API_PREFIX, params); + + const data = await ApillonApi.get< + IApillonList + >(url); + + return { + ...data, + items: data.items.map( + (contract) => new ComputingContract(contract.contractUuid, contract), + ), + }; + } + + /** + * @param uuid Unique contract identifier. + * @returns An instance of ComputingContract. + */ + public contract(uuid: string): ComputingContract { + return new ComputingContract(uuid); + } +} diff --git a/packages/sdk/src/modules/storage/storage.ts b/packages/sdk/src/modules/storage/storage.ts index 5d7b7c3..73d0bd2 100644 --- a/packages/sdk/src/modules/storage/storage.ts +++ b/packages/sdk/src/modules/storage/storage.ts @@ -12,7 +12,7 @@ export class Storage extends ApillonModule { /** * Lists all buckets. - * @param {ICollectionFilters} params Filter for listing collections. + * @param {IApillonPagination} params Filter for listing collections. * @returns Array of StorageBucket objects. */ public async listBuckets( diff --git a/packages/sdk/src/tests/computing.test.ts b/packages/sdk/src/tests/computing.test.ts new file mode 100644 index 0000000..c99c50e --- /dev/null +++ b/packages/sdk/src/tests/computing.test.ts @@ -0,0 +1,28 @@ +import { Computing } from '../modules/computing/computing'; +import { ComputingContract } from '../modules/computing/computing-contract'; +import { getComputingContractUUID, getConfig } from './helpers/helper'; + +describe('Computing tests', () => { + let computing: Computing; + let contractUuid: string; + + beforeAll(() => { + computing = new Computing(getConfig()); + contractUuid = getComputingContractUUID(); + }); + + test('List contracts', async () => { + const { items } = await computing.listContracts(); + expect(items.length).toBeGreaterThanOrEqual(0); + items.forEach((contract) => { + expect(contract instanceof ComputingContract).toBeTruthy(); + expect(contract.name).toBeTruthy(); + }); + }); + + test('Get specific contract', async () => { + const contract = computing.contract(contractUuid); + expect(contract).toBeInstanceOf(ComputingContract); + expect(contract.uuid).toEqual(contractUuid); + }); +}); diff --git a/packages/sdk/src/tests/helpers/helper.ts b/packages/sdk/src/tests/helpers/helper.ts index 087a7a2..c5c29d1 100644 --- a/packages/sdk/src/tests/helpers/helper.ts +++ b/packages/sdk/src/tests/helpers/helper.ts @@ -30,3 +30,7 @@ export function getWebsiteUUID() { export function getMintAddress() { return process.env['MINT_ADDRESS']; } + +export function getComputingContractUUID() { + return process.env['COMPUTING_CONTRACT_UUID']; +} diff --git a/packages/sdk/src/types/computing.ts b/packages/sdk/src/types/computing.ts new file mode 100644 index 0000000..00350aa --- /dev/null +++ b/packages/sdk/src/types/computing.ts @@ -0,0 +1,27 @@ +import { IApillonPagination } from './apillon'; + +export enum ComputingContractType { + SCHRODINGER = 1, +} + +export enum ComputingContractStatus { + CREATED = 0, + DEPLOY_INITIATED = 1, + DEPLOYING = 2, //INSTANTIATING + DEPLOYED = 3, //INSTANTIATED + TRANSFERRING = 4, + TRANSFERRED = 5, + FAILED = 6, +} + +export type ComputingContractData = { + nftContractAddress: string; + nftChainRpcUrl: string; + restrictToOwner: string; + ipfsGatewayUrl: string; + clusterId: string; +}; + +export interface IContractFilters extends IApillonPagination { + contractStatus?: ComputingContractStatus; +} From 518afc2c5d13aabd7f08f6b7e6d2808536423518 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Mon, 5 Feb 2024 14:33:17 +0100 Subject: [PATCH 02/20] Add computing contract create action --- .../modules/computing/computing-contract.ts | 2 +- .../sdk/src/modules/computing/computing.ts | 23 ++++++++++++- packages/sdk/src/tests/computing.test.ts | 32 ++++++++++++++++++- packages/sdk/src/types/apillon.ts | 6 ++++ packages/sdk/src/types/computing.ts | 25 ++++++++++++++- 5 files changed, 84 insertions(+), 4 deletions(-) diff --git a/packages/sdk/src/modules/computing/computing-contract.ts b/packages/sdk/src/modules/computing/computing-contract.ts index 9c2e2b1..76a0e0e 100644 --- a/packages/sdk/src/modules/computing/computing-contract.ts +++ b/packages/sdk/src/modules/computing/computing-contract.ts @@ -59,7 +59,7 @@ export class ComputingContract extends ApillonModel { } /** - * Gets a computing contract. + * Gets a computing contract's details. * @returns ComputingContract instance */ async get(): Promise { diff --git a/packages/sdk/src/modules/computing/computing.ts b/packages/sdk/src/modules/computing/computing.ts index 0661651..e39a6fa 100644 --- a/packages/sdk/src/modules/computing/computing.ts +++ b/packages/sdk/src/modules/computing/computing.ts @@ -2,7 +2,10 @@ import { ApillonModule } from '../../lib/apillon'; import { ApillonApi } from '../../lib/apillon-api'; import { constructUrlWithQueryParams } from '../../lib/common'; import { IApillonList } from '../../types/apillon'; -import { IContractFilters } from '../../types/computing'; +import { + IContractFilters, + ICreateComputingContract, +} from '../../types/computing'; import { ComputingContract } from './computing-contract'; export class Computing extends ApillonModule { @@ -33,6 +36,24 @@ export class Computing extends ApillonModule { }; } + /** + * Creates a new computing contract based on the provided data. + * @param {ICreateComputingContract} data Data for creating the contract. + * @returns {ComputingContract} Newly created computing contract. + */ + public async createContract( + data: ICreateComputingContract, + ): Promise { + const contract = await ApillonApi.post< + ComputingContract & { contractUuid: string } + >(this.API_PREFIX, { + ...data, + restrictToOwner: data.restrictToOwner || false, + contractType: 1, // Hardcoded until new type is added + }); + return new ComputingContract(contract.contractUuid, contract); + } + /** * @param uuid Unique contract identifier. * @returns An instance of ComputingContract. diff --git a/packages/sdk/src/tests/computing.test.ts b/packages/sdk/src/tests/computing.test.ts index c99c50e..aaf6353 100644 --- a/packages/sdk/src/tests/computing.test.ts +++ b/packages/sdk/src/tests/computing.test.ts @@ -1,3 +1,4 @@ +import { ChainRpcUrl } from '../docs-index'; import { Computing } from '../modules/computing/computing'; import { ComputingContract } from '../modules/computing/computing-contract'; import { getComputingContractUUID, getConfig } from './helpers/helper'; @@ -5,24 +6,53 @@ import { getComputingContractUUID, getConfig } from './helpers/helper'; describe('Computing tests', () => { let computing: Computing; let contractUuid: string; + const name = 'Schrodinger SDK Test'; + const description = 'Schrodinger SDK Test computing contract'; + const nftContractAddress = '0xe6C61ef02729a190Bd940A3077f8464c27C2E593'; beforeAll(() => { computing = new Computing(getConfig()); contractUuid = getComputingContractUUID(); }); + test('Create new contracts', async () => { + const contract = await computing.createContract({ + name, + description, + nftContractAddress, + nftChainRpcUrl: ChainRpcUrl.MOONBASE, + }); + expect(contract).toBeInstanceOf(ComputingContract); + expect(contract.name).toEqual(name); + expect(contract.description).toEqual(description); + expect(contract.uuid).toBeTruthy(); + expect(contract.data.nftContractAddress).toEqual(nftContractAddress); + expect(contract.data.nftChainRpcUrl).toEqual(ChainRpcUrl.MOONBASE); + + contractUuid = contract.uuid; + }); + test('List contracts', async () => { const { items } = await computing.listContracts(); + expect(items.length).toBeGreaterThanOrEqual(0); items.forEach((contract) => { expect(contract instanceof ComputingContract).toBeTruthy(); expect(contract.name).toBeTruthy(); }); + expect( + items.find((contract) => contract.uuid === contractUuid), + ).toBeTruthy(); }); test('Get specific contract', async () => { - const contract = computing.contract(contractUuid); + const contract = await computing.contract(contractUuid).get(); + expect(contract).toBeInstanceOf(ComputingContract); + expect(contract.name).toEqual(name); + expect(contract.description).toEqual(description); expect(contract.uuid).toEqual(contractUuid); + expect(contract.data.nftContractAddress).toEqual(nftContractAddress); + expect(contract.data.nftChainRpcUrl).toEqual(ChainRpcUrl.MOONBASE); }); }); diff --git a/packages/sdk/src/types/apillon.ts b/packages/sdk/src/types/apillon.ts index d9c3c8d..9fc5352 100644 --- a/packages/sdk/src/types/apillon.ts +++ b/packages/sdk/src/types/apillon.ts @@ -16,3 +16,9 @@ export enum LogLevel { ERROR = 2, VERBOSE = 3, } + +export enum ChainRpcUrl { + ASTAR = 'https://evm.astar.network', + MOONBASE = 'https://rpc.api.moonbase.moonbeam.network', + MOONBEAM = 'https://rpc.api.moonbeam.network', +} diff --git a/packages/sdk/src/types/computing.ts b/packages/sdk/src/types/computing.ts index 00350aa..abd5f6f 100644 --- a/packages/sdk/src/types/computing.ts +++ b/packages/sdk/src/types/computing.ts @@ -1,4 +1,4 @@ -import { IApillonPagination } from './apillon'; +import { ChainRpcUrl, IApillonPagination } from './apillon'; export enum ComputingContractType { SCHRODINGER = 1, @@ -25,3 +25,26 @@ export type ComputingContractData = { export interface IContractFilters extends IApillonPagination { contractStatus?: ComputingContractStatus; } + +export interface ICreateComputingContract { + name: string; + description?: string; + /** + * Bucket where the encrypted files will be stored via IPFS + * @optional If this parameter is not passed, a new bucket will be created for the contract + */ + bucket_uuid?: string; + /** + * Contract address of NFT which will be used to authenticate decryption + */ + nftContractAddress: string; + /** + * RPC URL of the chain the NFT collection exists on + */ + nftChainRpcUrl: ChainRpcUrl | string; + /** + * If true, only the owner is able to use the contract for data encryption/decryption + * @default false + */ + restrictToOwner?: boolean; +} From 90e37b7ab50f3e6f040a6f1fffebc500a20e2fde Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Mon, 5 Feb 2024 16:00:12 +0100 Subject: [PATCH 03/20] Implement computing contract actions --- .../modules/computing/computing-contract.ts | 81 +++++++++++++++++++ .../sdk/src/modules/computing/computing.ts | 6 +- .../sdk/src/modules/nft/nft-collection.ts | 9 +-- packages/sdk/src/tests/computing.test.ts | 56 ++++++++++++- packages/sdk/src/tests/helpers/helper.ts | 4 + packages/sdk/src/types/computing.ts | 37 ++++++++- 6 files changed, 180 insertions(+), 13 deletions(-) diff --git a/packages/sdk/src/modules/computing/computing-contract.ts b/packages/sdk/src/modules/computing/computing-contract.ts index 76a0e0e..092dccc 100644 --- a/packages/sdk/src/modules/computing/computing-contract.ts +++ b/packages/sdk/src/modules/computing/computing-contract.ts @@ -4,7 +4,14 @@ import { ComputingContractData, ComputingContractStatus, ComputingContractType, + ComputingTransactionType, + IAssignCidToNftData, + IComputingTransaction, + IEncryptData, } from '../../types/computing'; +import { IApillonList, IApillonPagination } from '../../types/apillon'; +import { constructUrlWithQueryParams } from '../../lib/common'; +import { ApillonLogger } from '../../lib/apillon-logger'; export class ComputingContract extends ApillonModel { /** @@ -69,4 +76,78 @@ export class ComputingContract extends ApillonModel { return new ComputingContract(data.contractUuid, data); } + + /** + * Gets list of transactions for this computing contract. + * @param {IApillonPagination} params Pagination filters. + * @returns List of transactions. + */ + public async listTransactions( + params?: IApillonPagination, + ): Promise> { + const url = constructUrlWithQueryParams( + `${this.API_PREFIX}/transactions`, + params, + ); + return await ApillonApi.get>(url); + } + + /** + * Transfers ownership of the computing contract. + * @param {string} accountAddress The address of the new owner. + * @returns Success status + */ + async transferOwnership(accountAddress: string): Promise { + const { success } = await ApillonApi.post<{ success: boolean }>( + `${this.API_PREFIX}/transfer-ownership`, + { accountAddress }, + ); + if (success) { + ApillonLogger.log( + `Ownership transferred successfully to ${accountAddress}`, + ); + } + return success; + } + + /** + * Calls the encrypt method on the computing contract. + * @param {IEncryptData} data The data to use for encryption. + * @returns The encrypted data in the form of a string. + */ + async encrypt(data: IEncryptData): Promise { + return ApillonApi.post<{ encryptedContent: string }>( + `${this.API_PREFIX}/encrypt`, + data, + ); + } + + /** + * Assigns a CID to an NFT on the contract. + * @param {IAssignCidToNftData} data The payload for assigning a CID to an NFT + * @returns Success status + */ + async assignCidToNft(data: IAssignCidToNftData): Promise { + const { success } = await ApillonApi.post<{ success: boolean }>( + `${this.API_PREFIX}/assign-cid-to-nft`, + data, + ); + if (success) { + ApillonLogger.log( + `CID assigned successfully to NFT with ID=${data.nftId}`, + ); + } + return success; + } + + protected override serializeFilter(key: string, value: any) { + const serialized = super.serializeFilter(key, value); + const enums = { + contractType: ComputingContractType[serialized], + contractStatus: ComputingContractStatus[serialized], + transactionType: ComputingTransactionType[serialized], + transactionStatus: ComputingContractStatus[serialized], + }; + return Object.keys(enums).includes(key) ? enums[key] : serialized; + } } diff --git a/packages/sdk/src/modules/computing/computing.ts b/packages/sdk/src/modules/computing/computing.ts index e39a6fa..3428b63 100644 --- a/packages/sdk/src/modules/computing/computing.ts +++ b/packages/sdk/src/modules/computing/computing.ts @@ -3,7 +3,7 @@ import { ApillonApi } from '../../lib/apillon-api'; import { constructUrlWithQueryParams } from '../../lib/common'; import { IApillonList } from '../../types/apillon'; import { - IContractFilters, + IContractListFilters, ICreateComputingContract, } from '../../types/computing'; import { ComputingContract } from './computing-contract'; @@ -16,11 +16,11 @@ export class Computing extends ApillonModule { /** * Lists all computing contracts. - * @param {IContractFilters} params Filter for listing collections. + * @param {IContractListFilters} params Filter for listing collections. * @returns Array of ComputingContract objects. */ public async listContracts( - params?: IContractFilters, + params?: IContractListFilters, ): Promise> { const url = constructUrlWithQueryParams(this.API_PREFIX, params); diff --git a/packages/sdk/src/modules/nft/nft-collection.ts b/packages/sdk/src/modules/nft/nft-collection.ts index d6633f6..51ba945 100644 --- a/packages/sdk/src/modules/nft/nft-collection.ts +++ b/packages/sdk/src/modules/nft/nft-collection.ts @@ -247,14 +247,7 @@ export class NftCollection extends ApillonModel { params, ); - const data = await ApillonApi.get>(url); - - return { - ...data, - items: data.items.map((t) => - JSON.parse(JSON.stringify(t, this.serializeFilter)), - ), - }; + return await ApillonApi.get>(url); } protected override serializeFilter(key: string, value: any) { diff --git a/packages/sdk/src/tests/computing.test.ts b/packages/sdk/src/tests/computing.test.ts index aaf6353..f0b382b 100644 --- a/packages/sdk/src/tests/computing.test.ts +++ b/packages/sdk/src/tests/computing.test.ts @@ -1,7 +1,15 @@ import { ChainRpcUrl } from '../docs-index'; import { Computing } from '../modules/computing/computing'; import { ComputingContract } from '../modules/computing/computing-contract'; -import { getComputingContractUUID, getConfig } from './helpers/helper'; +import { + ComputingContractStatus, + ComputingTransactionType, +} from '../types/computing'; +import { + getComputingContractUUID, + getConfig, + getPhalaAddress, +} from './helpers/helper'; describe('Computing tests', () => { let computing: Computing; @@ -9,10 +17,12 @@ describe('Computing tests', () => { const name = 'Schrodinger SDK Test'; const description = 'Schrodinger SDK Test computing contract'; const nftContractAddress = '0xe6C61ef02729a190Bd940A3077f8464c27C2E593'; + let receivingAddress: string; beforeAll(() => { computing = new Computing(getConfig()); contractUuid = getComputingContractUUID(); + receivingAddress = getPhalaAddress(); }); test('Create new contracts', async () => { @@ -55,4 +65,48 @@ describe('Computing tests', () => { expect(contract.data.nftContractAddress).toEqual(nftContractAddress); expect(contract.data.nftChainRpcUrl).toEqual(ChainRpcUrl.MOONBASE); }); + + test('List all transactions for computing contract', async () => { + const { items } = await computing + .contract(contractUuid) + .listTransactions({ limit: 10 }); + expect(items.length).toBeGreaterThanOrEqual(0); + items.forEach((transaction) => { + expect(transaction).toBeDefined(); + expect(transaction.transactionHash).toBeDefined(); + expect( + Object.keys(ComputingContractStatus).includes( + transaction.transactionStatus.toString(), + ), + ); + expect( + Object.keys(ComputingTransactionType).includes( + transaction.transactionType.toString(), + ), + ); + }); + }); + + test.skip('Encrypt data using computing contract', async () => { + const encryptedData = await computing + .contract(contractUuid) + .encrypt({ content: 'Test content' }); + expect(encryptedData).toHaveProperty('encryptedContent'); + expect(typeof encryptedData.encryptedContent).toBe('string'); + }); + + test.skip('Assign CID to NFT', async () => { + const success = await computing.contract(contractUuid).assignCidToNft({ + nftId: 1, + cid: 'QmTestCid', + }); + expect(success).toBeTruthy(); + }); + + test.skip('Transfer ownership of computing contract', async () => { + const success = await computing + .contract(contractUuid) + .transferOwnership(receivingAddress); + expect(success).toBeTruthy(); + }); }); diff --git a/packages/sdk/src/tests/helpers/helper.ts b/packages/sdk/src/tests/helpers/helper.ts index c5c29d1..3fef417 100644 --- a/packages/sdk/src/tests/helpers/helper.ts +++ b/packages/sdk/src/tests/helpers/helper.ts @@ -31,6 +31,10 @@ export function getMintAddress() { return process.env['MINT_ADDRESS']; } +export function getPhalaAddress() { + return process.env['PHALA_ADDRESS']; +} + export function getComputingContractUUID() { return process.env['COMPUTING_CONTRACT_UUID']; } diff --git a/packages/sdk/src/types/computing.ts b/packages/sdk/src/types/computing.ts index abd5f6f..3023048 100644 --- a/packages/sdk/src/types/computing.ts +++ b/packages/sdk/src/types/computing.ts @@ -14,6 +14,13 @@ export enum ComputingContractStatus { FAILED = 6, } +export enum ComputingTransactionType { + DEPLOY_CONTRACT = 1, + TRANSFER_CONTRACT_OWNERSHIP = 2, + DEPOSIT_TO_CONTRACT_CLUSTER = 3, + ASSIGN_CID_TO_NFT = 4, +} + export type ComputingContractData = { nftContractAddress: string; nftChainRpcUrl: string; @@ -22,7 +29,7 @@ export type ComputingContractData = { clusterId: string; }; -export interface IContractFilters extends IApillonPagination { +export interface IContractListFilters extends IApillonPagination { contractStatus?: ComputingContractStatus; } @@ -48,3 +55,31 @@ export interface ICreateComputingContract { */ restrictToOwner?: boolean; } + +export interface IComputingTransaction { + walletAddress: string; + transactionType: ComputingTransactionType; + transactionStatus: ComputingContractStatus; + transactionStatusMessage: string; + transactionHash: string; + updateTime: string; + createTime: string; +} + +export interface IEncryptData { + /** + * Base64 string of file contents. + */ + content: string; +} + +export interface IAssignCidToNftData { + /** + * CID of the encrypted file stored in a bucket + */ + cid: string; + /** + * Token ID of the NFT which will be used to decrypt the file's content + */ + nftId: number; +} From f1ca863947ba633c39f93e95a40503b94f3f49c0 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Tue, 6 Feb 2024 13:47:44 +0100 Subject: [PATCH 04/20] Implement file encryption and CID mapping logic --- packages/sdk/src/lib/apillon-api.ts | 2 + packages/sdk/src/lib/apillon.ts | 4 +- .../modules/computing/computing-contract.ts | 72 ++++++++++++++++--- .../sdk/src/modules/computing/computing.ts | 7 +- packages/sdk/src/tests/computing.test.ts | 27 ++++--- packages/sdk/src/types/computing.ts | 13 ++-- packages/sdk/src/types/storage.ts | 4 ++ 7 files changed, 96 insertions(+), 33 deletions(-) diff --git a/packages/sdk/src/lib/apillon-api.ts b/packages/sdk/src/lib/apillon-api.ts index 56001b2..5d4fb41 100644 --- a/packages/sdk/src/lib/apillon-api.ts +++ b/packages/sdk/src/lib/apillon-api.ts @@ -61,6 +61,8 @@ export class ApillonApi { } }, ); + + return config; } public static async get(url: string, config?: any): Promise { diff --git a/packages/sdk/src/lib/apillon.ts b/packages/sdk/src/lib/apillon.ts index cc78181..617474e 100644 --- a/packages/sdk/src/lib/apillon.ts +++ b/packages/sdk/src/lib/apillon.ts @@ -31,8 +31,10 @@ export interface ApillonConfig { } export class ApillonModule { + protected config: ApillonConfig; + public constructor(config?: ApillonConfig) { - ApillonApi.initialize(config); + this.config = ApillonApi.initialize(config); ApillonLogger.initialize( config?.debug ? LogLevel.VERBOSE : config?.logLevel || LogLevel.ERROR, ); diff --git a/packages/sdk/src/modules/computing/computing-contract.ts b/packages/sdk/src/modules/computing/computing-contract.ts index 092dccc..10e6b3e 100644 --- a/packages/sdk/src/modules/computing/computing-contract.ts +++ b/packages/sdk/src/modules/computing/computing-contract.ts @@ -1,17 +1,18 @@ import { ApillonApi } from '../../lib/apillon-api'; -import { ApillonModel } from '../../lib/apillon'; +import { ApillonConfig, ApillonModel } from '../../lib/apillon'; import { ComputingContractData, ComputingContractStatus, ComputingContractType, ComputingTransactionType, - IAssignCidToNftData, IComputingTransaction, IEncryptData, } from '../../types/computing'; import { IApillonList, IApillonPagination } from '../../types/apillon'; import { constructUrlWithQueryParams } from '../../lib/common'; import { ApillonLogger } from '../../lib/apillon-logger'; +import { Storage } from '../storage/storage'; +import { FileMetadata } from '../../docs-index'; export class ComputingContract extends ApillonModel { /** @@ -24,6 +25,11 @@ export class ComputingContract extends ApillonModel { */ public description: string = null; + /** + * The bucket where files encrypted by this contract are stored + */ + public bucketUuid: string = null; + /** * The computing contract's type */ @@ -54,15 +60,26 @@ export class ComputingContract extends ApillonModel { */ public data: ComputingContractData = null; + /** + * Apillon config used to initialize a storage module + * for saving encrypted files + */ + private config: ApillonConfig; + /** * Constructor which should only be called via Computing class. * @param uuid Unique identifier of the contract. * @param data Data to populate computing contract with. */ - constructor(uuid: string, data?: Partial) { + constructor( + uuid: string, + data?: Partial, + config?: ApillonConfig, + ) { super(uuid); this.API_PREFIX = `/computing/contracts/${uuid}`; this.populate(data); + this.config = config; } /** @@ -74,7 +91,7 @@ export class ComputingContract extends ApillonModel { ComputingContract & { contractUuid: string } >(this.API_PREFIX); - return new ComputingContract(data.contractUuid, data); + return this.populate(data); } /** @@ -111,30 +128,63 @@ export class ComputingContract extends ApillonModel { } /** - * Calls the encrypt method on the computing contract. + * - Calls the encrypt method on the computing contract + * - Uploads the encrypted file to the bucket + * - Assigns the encrypted file's CID to the NFT used for decryption authentication * @param {IEncryptData} data The data to use for encryption. * @returns The encrypted data in the form of a string. */ - async encrypt(data: IEncryptData): Promise { - return ApillonApi.post<{ encryptedContent: string }>( + async encryptFile(data: IEncryptData): Promise { + ApillonLogger.log(`Encrypting file...`); + const { encryptedContent } = await ApillonApi.post( `${this.API_PREFIX}/encrypt`, - data, + { + ...data, + content: data.content.toString('base64'), + }, ); + if (!encryptedContent) { + throw new Error('Failed to encrypt file'); + } + if (!this.bucketUuid) { + await this.get(); + } + + ApillonLogger.log(`Uploading encrypted file to bucket...`); + const files = await new Storage(this.config) + .bucket(this.bucketUuid) + .uploadFiles( + [ + { + fileName: data.fileName, + content: Buffer.from(encryptedContent, 'utf-8'), + contentType: 'multipart/form-data', + }, + ], + { awaitCid: true }, + ); + ApillonLogger.log(`Assigning file CID to NFT ID...`); + await this.assignCidToNft({ cid: files[0].CID, nftId: data.nftId }); + + return files; } /** * Assigns a CID to an NFT on the contract. - * @param {IAssignCidToNftData} data The payload for assigning a CID to an NFT + * @param data The payload for assigning a CID to an NFT * @returns Success status */ - async assignCidToNft(data: IAssignCidToNftData): Promise { + private async assignCidToNft(data: { + cid: string; + nftId: number; + }): Promise { const { success } = await ApillonApi.post<{ success: boolean }>( `${this.API_PREFIX}/assign-cid-to-nft`, data, ); if (success) { ApillonLogger.log( - `CID assigned successfully to NFT with ID=${data.nftId}`, + `Encrypted file CID assigned successfully to NFT with ID=${data.nftId}`, ); } return success; diff --git a/packages/sdk/src/modules/computing/computing.ts b/packages/sdk/src/modules/computing/computing.ts index 3428b63..1320d44 100644 --- a/packages/sdk/src/modules/computing/computing.ts +++ b/packages/sdk/src/modules/computing/computing.ts @@ -31,7 +31,8 @@ export class Computing extends ApillonModule { return { ...data, items: data.items.map( - (contract) => new ComputingContract(contract.contractUuid, contract), + (contract) => + new ComputingContract(contract.contractUuid, contract, this.config), ), }; } @@ -51,7 +52,7 @@ export class Computing extends ApillonModule { restrictToOwner: data.restrictToOwner || false, contractType: 1, // Hardcoded until new type is added }); - return new ComputingContract(contract.contractUuid, contract); + return new ComputingContract(contract.contractUuid, contract, this.config); } /** @@ -59,6 +60,6 @@ export class Computing extends ApillonModule { * @returns An instance of ComputingContract. */ public contract(uuid: string): ComputingContract { - return new ComputingContract(uuid); + return new ComputingContract(uuid, null, this.config); } } diff --git a/packages/sdk/src/tests/computing.test.ts b/packages/sdk/src/tests/computing.test.ts index f0b382b..c3ac0dd 100644 --- a/packages/sdk/src/tests/computing.test.ts +++ b/packages/sdk/src/tests/computing.test.ts @@ -6,10 +6,13 @@ import { ComputingTransactionType, } from '../types/computing'; import { + getBucketUUID, getComputingContractUUID, getConfig, getPhalaAddress, } from './helpers/helper'; +import { resolve } from 'path'; +import * as fs from 'fs'; describe('Computing tests', () => { let computing: Computing; @@ -18,11 +21,13 @@ describe('Computing tests', () => { const description = 'Schrodinger SDK Test computing contract'; const nftContractAddress = '0xe6C61ef02729a190Bd940A3077f8464c27C2E593'; let receivingAddress: string; + let bucket_uuid: string; beforeAll(() => { computing = new Computing(getConfig()); contractUuid = getComputingContractUUID(); receivingAddress = getPhalaAddress(); + bucket_uuid = getBucketUUID(); }); test('Create new contracts', async () => { @@ -31,11 +36,13 @@ describe('Computing tests', () => { description, nftContractAddress, nftChainRpcUrl: ChainRpcUrl.MOONBASE, + bucket_uuid, }); expect(contract).toBeInstanceOf(ComputingContract); expect(contract.name).toEqual(name); expect(contract.description).toEqual(description); expect(contract.uuid).toBeTruthy(); + expect(contract.bucketUuid).toEqual(bucket_uuid); expect(contract.data.nftContractAddress).toEqual(nftContractAddress); expect(contract.data.nftChainRpcUrl).toEqual(ChainRpcUrl.MOONBASE); @@ -88,19 +95,17 @@ describe('Computing tests', () => { }); test.skip('Encrypt data using computing contract', async () => { - const encryptedData = await computing + const html = fs.readFileSync( + resolve(__dirname, './helpers/website/index.html'), + ); + const files = await computing .contract(contractUuid) - .encrypt({ content: 'Test content' }); - expect(encryptedData).toHaveProperty('encryptedContent'); - expect(typeof encryptedData.encryptedContent).toBe('string'); - }); + .encryptFile({ content: html, fileName: 'index.html', nftId: 5 }); - test.skip('Assign CID to NFT', async () => { - const success = await computing.contract(contractUuid).assignCidToNft({ - nftId: 1, - cid: 'QmTestCid', - }); - expect(success).toBeTruthy(); + expect(files).toHaveLength(1); + expect(files[0].fileName).toBe('index.html'); + expect(files[0].CID).toBeDefined(); + expect(files[0].CIDv1).toBeDefined(); }); test.skip('Transfer ownership of computing contract', async () => { diff --git a/packages/sdk/src/types/computing.ts b/packages/sdk/src/types/computing.ts index 3023048..8560876 100644 --- a/packages/sdk/src/types/computing.ts +++ b/packages/sdk/src/types/computing.ts @@ -68,18 +68,17 @@ export interface IComputingTransaction { export interface IEncryptData { /** - * Base64 string of file contents. + * fileName for the encrypted file that will be stored in the bucket */ - content: string; -} - -export interface IAssignCidToNftData { + fileName: string; /** - * CID of the encrypted file stored in a bucket + * Contents of the file to encrypt */ - cid: string; + content: Buffer; /** * Token ID of the NFT which will be used to decrypt the file's content + * + * The NFT should be a part of the contract's `data.nftContractAddress` field. */ nftId: number; } diff --git a/packages/sdk/src/types/storage.ts b/packages/sdk/src/types/storage.ts index 2135834..f772b39 100644 --- a/packages/sdk/src/types/storage.ts +++ b/packages/sdk/src/types/storage.ts @@ -48,6 +48,10 @@ export interface FileMetadata { * The file's CID on IPFS */ CID?: string; + /** + * The file's CIDv1 on IPFS + */ + CIDv1?: string; } export interface IFileUploadRequest { From b265f12c7ac2e31ca69e9b950f154459ebf63a24 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Tue, 6 Feb 2024 14:09:46 +0100 Subject: [PATCH 05/20] Computing JSDoc and SDK version update --- package.json | 2 +- packages/sdk/package.json | 2 +- packages/sdk/src/docs-index.ts | 3 +++ packages/sdk/src/index.ts | 1 + .../src/modules/computing/computing-contract.ts | 4 ++-- packages/sdk/src/modules/nft/nft-collection.ts | 4 ++-- packages/sdk/src/types/computing.ts | 17 ++++++++++++++++- 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 33b220b..0fbb1f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apillon-web3-tools", - "version": "2.0.0", + "version": "2.1.0", "description": "Monorepo for Apillon tools", "author": "Apillon", "license": "MIT", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 73a6940..6f22f2d 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,7 +1,7 @@ { "name": "@apillon/sdk", "description": "▶◀ Apillon SDK for NodeJS ▶◀", - "version": "2.0.0", + "version": "2.1.0", "author": "Apillon", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/sdk/src/docs-index.ts b/packages/sdk/src/docs-index.ts index 86d6f63..e15f872 100644 --- a/packages/sdk/src/docs-index.ts +++ b/packages/sdk/src/docs-index.ts @@ -3,6 +3,7 @@ export * from './types/nfts'; export * from './types/hosting'; export * from './types/storage'; export * from './types/identity'; +export * from './types/computing'; export * from './lib/apillon'; export * from './modules/storage/storage'; export * from './modules/storage/storage-bucket'; @@ -15,3 +16,5 @@ export * from './modules/hosting/hosting-website'; export * from './modules/nft/nft'; export * from './modules/nft/nft-collection'; export * from './modules/identity/identity'; +export * from './modules/computing/computing'; +export * from './modules/computing/computing-contract'; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index fe1b1e0..7c7a0ef 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -9,3 +9,4 @@ export * from './modules/storage/storage'; export * from './modules/hosting/hosting'; export * from './modules/nft/nft'; export * from './modules/identity/identity'; +export * from './modules/computing/computing'; diff --git a/packages/sdk/src/modules/computing/computing-contract.ts b/packages/sdk/src/modules/computing/computing-contract.ts index 10e6b3e..9c55527 100644 --- a/packages/sdk/src/modules/computing/computing-contract.ts +++ b/packages/sdk/src/modules/computing/computing-contract.ts @@ -97,7 +97,7 @@ export class ComputingContract extends ApillonModel { /** * Gets list of transactions for this computing contract. * @param {IApillonPagination} params Pagination filters. - * @returns List of transactions. + * @returns {IComputingTransaction[]} List of transactions. */ public async listTransactions( params?: IApillonPagination, @@ -132,7 +132,7 @@ export class ComputingContract extends ApillonModel { * - Uploads the encrypted file to the bucket * - Assigns the encrypted file's CID to the NFT used for decryption authentication * @param {IEncryptData} data The data to use for encryption. - * @returns The encrypted data in the form of a string. + * @returns The uploaded encrypted file metadata */ async encryptFile(data: IEncryptData): Promise { ApillonLogger.log(`Encrypting file...`); diff --git a/packages/sdk/src/modules/nft/nft-collection.ts b/packages/sdk/src/modules/nft/nft-collection.ts index 51ba945..0946abd 100644 --- a/packages/sdk/src/modules/nft/nft-collection.ts +++ b/packages/sdk/src/modules/nft/nft-collection.ts @@ -200,7 +200,7 @@ export class NftCollection extends ApillonModel { * Burns a nft. * @warn Can only burn NFTs if the collection is revokable. * @param tokenId Token ID of the NFT we want to burn. - * @returns Status. + * @returns Success status and transaction hash. */ public async burn(tokenId: string): Promise { if (this.isRevokable != null && !this.isRevokable) { @@ -237,7 +237,7 @@ export class NftCollection extends ApillonModel { /** * Gets list of transactions that occurred on this collection through Apillon. * @param params Filters. - * @returns List of transactions. + * @returns {ITransaction[]} List of transactions. */ public async listTransactions( params?: ITransactionFilters, diff --git a/packages/sdk/src/types/computing.ts b/packages/sdk/src/types/computing.ts index 8560876..4d10253 100644 --- a/packages/sdk/src/types/computing.ts +++ b/packages/sdk/src/types/computing.ts @@ -22,10 +22,25 @@ export enum ComputingTransactionType { } export type ComputingContractData = { + /** + * Contract address of NFT which will be used to authenticate decryption + */ nftContractAddress: string; + /** + * RPC URL of the chain the NFT collection exists on + */ nftChainRpcUrl: string; - restrictToOwner: string; + /** + * If true, only the owner is able to use the contract for data encryption/decryption + */ + restrictToOwner: boolean; + /** + * The IPFS gateway where the encrypted files are stored on + */ ipfsGatewayUrl: string; + /** + * Identifier of the Phala computing cluster the contract runs on + */ clusterId: string; }; From 2ebf6794392eecb5908714dbe51a5aa417b84303 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Thu, 8 Feb 2024 11:28:34 +0100 Subject: [PATCH 06/20] Add computing module example in SDK README --- packages/sdk/README.md | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 1d68c05..862a8f5 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -46,6 +46,12 @@ View each individual module examples in the sections below. This wiki only contains the basic installation and examples of SDK usage. For additional information on using the SDK, see the [Detailed SDK documentation](https://sdk-docs.apillon.io/). +### Examples + +Examples for using Apillon can be found in a demo repo [here](https://github.com/Apillon/apillon-sdk-demo). Instructions on running the examples are in the [README file](https://github.com/Apillon/apillon-sdk-demo/blob/master/README.md). + +> You can run examples directly in your browser via [CodeSandbox](https://codesandbox.io/p/github/Apillon/apillon-sdk-demo/master). + ## Hosting Hosting module encapsulates functionalities for Hosting service available on Apillon dashboard. @@ -364,4 +370,53 @@ async function validatePolkadotWalletSignature() { } ``` +## Computing + +The Computing module provides functionalities for managing computing contracts, including creating contracts, listing contracts, and interacting with specific contracts for operations like encryption and ownership transfer. + +### Usage example + +```ts +const computing = new Computing({ + key: 'yourApiKey', + secret: 'yourApiSecret', +}); +// List all computing contracts +const contracts = await computing.listContracts(); +console.log(contracts); + +// Create a new computing contract +const newContract = await computing.createContract({ + name: 'New Contract', + description: 'Description of the new contract', + nftContractAddress: '0xabc...', + nftChainRpcUrl: ChainRpcUrl.ASTAR, +}); + +// Interact with a specific computing contract +const contract = computing.contract(newContract.uuid); + +// Get details of the contract +const contractDetails = await contract.get(); +console.log(contractDetails); + +// List transactions of the contract +const transactions = await contract.listTransactions(); +console.log(transactions); + +// Encrypt a file and upload it to the associated bucket +const encryptionResult = await contract.encryptFile({ + fileName: 'example.txt', + content: Buffer.from('Hello, world!'), + nftId: 1, // NFT ID used for decryption authentication +}); +console.log(encryptionResult); + +// Transfer ownership of the contract +const newOwnerAddress = '0xNewOwnerAddress'; +const successResult = await contract.transferOwnership(newOwnerAddress); +console.log( + `Ownership transfer was ${successResult ? 'successful' : 'unsuccessful'}.`, +); +``` From e64891afd2baa434705ef527f99eac190a3de9b1 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Fri, 16 Feb 2024 09:48:16 +0100 Subject: [PATCH 07/20] Unit test updates --- packages/cli/README.md | 13 ++++++++----- .../sdk/src/modules/hosting/hosting-website.ts | 4 ++-- .../sdk/src/modules/storage/storage-bucket.ts | 13 ++++++------- packages/sdk/src/tests/computing.test.ts | 6 +++--- packages/sdk/src/tests/storage.test.ts | 12 +++--------- packages/sdk/src/util/file-utils.ts | 15 +++++++++------ 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 7f02e4b..c5183a1 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -438,7 +438,7 @@ Lists all IPNS records for a specific bucket. **Example** ```sh -apillon ipns list --bucket-uuid "123e4567-e89b-12d3-a456-426655440000" +apillon storage ipns list --bucket-uuid "123e4567-e89b-12d3-a456-426655440000" ``` **Example response** @@ -484,41 +484,44 @@ Creates a new IPNS record for a specific bucket. **Example** ```sh -apillon ipns create --bucket-uuid "123e4567-e89b-12d3-a456-426655440000" --name "my-ipns-record" --cid "QmWX5CcNvnaVmgGBn4o82XW9uW1uLvsHQDdNrANrQeSdXm" +apillon storage ipns create --bucket-uuid "123e4567-e89b-12d3-a456-426655440000" --name "my-ipns-record" --cid "QmWX5CcNvnaVmgGBn4o82XW9uW1uLvsHQDdNrANrQeSdXm" ``` #### `storage ipns get` Retrieves information about a specific IPNS record. **Options** +- `-b, --bucket-uuid `: UUID of the bucket. - `-i, --ipns-uuid `: UUID of the IPNS record. **Example** ```sh -apillon ipns get --ipns-uuid "123e4567-e89b-12d3-a456-426655440000" +apillon storage ipns get --ipns-uuid "123e4567-e89b-12d3-a456-426655440000" ``` #### `storage ipns publish` Publishes an IPNS record to IPFS and links it to a CID. **Options** +- `-b, --bucket-uuid `: UUID of the bucket. - `-i, --ipns-uuid `: UUID of the IPNS record. - `-c, --cid `: CID to which this IPNS name will point. **Example** ```sh -apillon ipns publish --ipns-uuid "123e4567-e89b-12d3-a456-426655440000" --cid "QmWX5CcNvnaVmgGBn4o82XW9uW1uLvsHQDdNrANrQeSdXm" +apillon storage ipns publish --ipns-uuid "123e4567-e89b-12d3-a456-426655440000" --cid "QmWX5CcNvnaVmgGBn4o82XW9uW1uLvsHQDdNrANrQeSdXm" ``` #### `storage ipns delete` Deletes an IPNS record from a specific bucket. **Options** +- `-b, --bucket-uuid `: UUID of the bucket. - `-i, --ipns-uuid `: UUID of the IPNS record. **Example** ```sh -apillon ipns delete --ipns-uuid "123e4567-e89b-12d3-a456-426655440000" +apillon storage ipns delete --ipns-uuid "123e4567-e89b-12d3-a456-426655440000" ``` ## NFT Commands diff --git a/packages/sdk/src/modules/hosting/hosting-website.ts b/packages/sdk/src/modules/hosting/hosting-website.ts index 34b8839..d41933d 100644 --- a/packages/sdk/src/modules/hosting/hosting-website.ts +++ b/packages/sdk/src/modules/hosting/hosting-website.ts @@ -92,7 +92,7 @@ export class HostingWebsite extends ApillonModel { folderPath: string, params?: IFileUploadRequest, ): Promise { - await uploadFiles(folderPath, this.API_PREFIX, params); + await uploadFiles({ apiPrefix: this.API_PREFIX, folderPath, params }); } /** @@ -104,7 +104,7 @@ export class HostingWebsite extends ApillonModel { files: FileMetadata[], params?: IFileUploadRequest, ): Promise { - await uploadFiles(null, this.API_PREFIX, params, files); + await uploadFiles({ apiPrefix: this.API_PREFIX, params, files }); } /** diff --git a/packages/sdk/src/modules/storage/storage-bucket.ts b/packages/sdk/src/modules/storage/storage-bucket.ts index 3040f93..f57c07b 100644 --- a/packages/sdk/src/modules/storage/storage-bucket.ts +++ b/packages/sdk/src/modules/storage/storage-bucket.ts @@ -123,11 +123,11 @@ export class StorageBucket extends ApillonModel { folderPath: string, params?: IFileUploadRequest, ): Promise { - const { files: uploadedFiles, sessionUuid } = await uploadFiles( + const { files: uploadedFiles, sessionUuid } = await uploadFiles({ + apiPrefix: this.API_PREFIX, folderPath, - this.API_PREFIX, params, - ); + }); if (!params?.awaitCid) { return uploadedFiles; @@ -145,12 +145,11 @@ export class StorageBucket extends ApillonModel { files: FileMetadata[], params?: IFileUploadRequest, ): Promise { - const { files: uploadedFiles, sessionUuid } = await uploadFiles( - null, - this.API_PREFIX, + const { files: uploadedFiles, sessionUuid } = await uploadFiles({ + apiPrefix: this.API_PREFIX, params, files, - ); + }); if (!params?.awaitCid) { return uploadedFiles; diff --git a/packages/sdk/src/tests/computing.test.ts b/packages/sdk/src/tests/computing.test.ts index c3ac0dd..65d9242 100644 --- a/packages/sdk/src/tests/computing.test.ts +++ b/packages/sdk/src/tests/computing.test.ts @@ -96,14 +96,14 @@ describe('Computing tests', () => { test.skip('Encrypt data using computing contract', async () => { const html = fs.readFileSync( - resolve(__dirname, './helpers/website/index.html'), + resolve(__dirname, './helpers/website/style.css'), ); const files = await computing .contract(contractUuid) - .encryptFile({ content: html, fileName: 'index.html', nftId: 5 }); + .encryptFile({ content: html, fileName: 'style.css', nftId: 5 }); expect(files).toHaveLength(1); - expect(files[0].fileName).toBe('index.html'); + expect(files[0].fileName).toBe('style.css'); expect(files[0].CID).toBeDefined(); expect(files[0].CIDv1).toBeDefined(); }); diff --git a/packages/sdk/src/tests/storage.test.ts b/packages/sdk/src/tests/storage.test.ts index 34afc3a..6343214 100644 --- a/packages/sdk/src/tests/storage.test.ts +++ b/packages/sdk/src/tests/storage.test.ts @@ -108,21 +108,15 @@ describe('Storage tests', () => { }); test('upload files from buffer', async () => { - const html = fs.readFileSync( - resolve(__dirname, './helpers/website/index.html'), - ); + // const html = fs.readFileSync( + // resolve(__dirname, './helpers/website/index.html'), + // ); const css = fs.readFileSync( resolve(__dirname, './helpers/website/style.css'), ); console.time('File upload complete'); await storage.bucket(bucketUuid).uploadFiles( [ - { - fileName: 'index.html', - contentType: 'text/html', - path: null, - content: html, - }, { fileName: 'style.css', contentType: 'text/css', diff --git a/packages/sdk/src/util/file-utils.ts b/packages/sdk/src/util/file-utils.ts index 6389457..4f5397b 100644 --- a/packages/sdk/src/util/file-utils.ts +++ b/packages/sdk/src/util/file-utils.ts @@ -75,12 +75,15 @@ async function uploadFilesToS3( await Promise.all(uploadWorkers); } -export async function uploadFiles( - folderPath: string, - apiPrefix: string, - params?: IFileUploadRequest, - files?: FileMetadata[], -): Promise<{ sessionUuid: string; files: FileMetadata[] }> { +export async function uploadFiles(uploadParams: { + apiPrefix: string; + folderPath?: string; + files?: FileMetadata[]; + params?: IFileUploadRequest; +}): Promise<{ sessionUuid: string; files: FileMetadata[] }> { + const { folderPath, apiPrefix, params } = uploadParams; + let files = uploadParams.files; + if (folderPath) { ApillonLogger.log(`Preparing to upload files from ${folderPath}...`); } else if (files?.length) { From a5ce1bce910995ed26da03c7e681870917efaea0 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Tue, 20 Feb 2024 13:18:38 +0100 Subject: [PATCH 08/20] Replace return type with existing type --- packages/sdk/src/modules/identity/identity.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/sdk/src/modules/identity/identity.ts b/packages/sdk/src/modules/identity/identity.ts index 75563d9..deaad33 100644 --- a/packages/sdk/src/modules/identity/identity.ts +++ b/packages/sdk/src/modules/identity/identity.ts @@ -101,10 +101,7 @@ export class Identity extends ApillonModule { */ public validatePolkadotWalletSignature( data: IValidatePolkadotWalletSignature, - ): { - isValid: boolean; - address: string; - } { + ): VerifySignedMessageResult { const { message, signature, walletAddress, timestamp } = data; const signingMessage = From c9ddc970d0102bd26cc1884ff0b29fb823e8ec2d Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Fri, 23 Feb 2024 13:34:06 +0100 Subject: [PATCH 09/20] Update computing data types and create call --- .../modules/computing/computing-contract.ts | 7 ++-- .../sdk/src/modules/computing/computing.ts | 1 - packages/sdk/src/tests/computing.test.ts | 37 ++++++++++++++--- packages/sdk/src/types/computing.ts | 40 +++++++++++-------- 4 files changed, 59 insertions(+), 26 deletions(-) diff --git a/packages/sdk/src/modules/computing/computing-contract.ts b/packages/sdk/src/modules/computing/computing-contract.ts index 5e9c175..959a392 100644 --- a/packages/sdk/src/modules/computing/computing-contract.ts +++ b/packages/sdk/src/modules/computing/computing-contract.ts @@ -7,8 +7,9 @@ import { ComputingTransactionType, IComputingTransaction, IEncryptData, + ITransactionListFilters, } from '../../types/computing'; -import { IApillonList, IApillonPagination } from '../../types/apillon'; +import { IApillonList } from '../../types/apillon'; import { constructUrlWithQueryParams } from '../../lib/common'; import { ApillonLogger } from '../../lib/apillon-logger'; import { Storage } from '../storage/storage'; @@ -96,11 +97,11 @@ export class ComputingContract extends ApillonModel { /** * Gets list of transactions for this computing contract. - * @param {IApillonPagination} params Pagination filters. + * @param {ITransactionListFilters} params Query filters. * @returns {IComputingTransaction[]} List of transactions. */ public async listTransactions( - params?: IApillonPagination, + params?: ITransactionListFilters, ): Promise> { const url = constructUrlWithQueryParams( `${this.API_PREFIX}/transactions`, diff --git a/packages/sdk/src/modules/computing/computing.ts b/packages/sdk/src/modules/computing/computing.ts index 1320d44..391bfde 100644 --- a/packages/sdk/src/modules/computing/computing.ts +++ b/packages/sdk/src/modules/computing/computing.ts @@ -49,7 +49,6 @@ export class Computing extends ApillonModule { ComputingContract & { contractUuid: string } >(this.API_PREFIX, { ...data, - restrictToOwner: data.restrictToOwner || false, contractType: 1, // Hardcoded until new type is added }); return new ComputingContract(contract.contractUuid, contract, this.config); diff --git a/packages/sdk/src/tests/computing.test.ts b/packages/sdk/src/tests/computing.test.ts index 65d9242..565dd6d 100644 --- a/packages/sdk/src/tests/computing.test.ts +++ b/packages/sdk/src/tests/computing.test.ts @@ -17,11 +17,12 @@ import * as fs from 'fs'; describe('Computing tests', () => { let computing: Computing; let contractUuid: string; + let receivingAddress: string; + let bucket_uuid: string; + const name = 'Schrodinger SDK Test'; const description = 'Schrodinger SDK Test computing contract'; const nftContractAddress = '0xe6C61ef02729a190Bd940A3077f8464c27C2E593'; - let receivingAddress: string; - let bucket_uuid: string; beforeAll(() => { computing = new Computing(getConfig()); @@ -30,13 +31,15 @@ describe('Computing tests', () => { bucket_uuid = getBucketUUID(); }); - test('Create new contracts', async () => { + test('Create new contract', async () => { const contract = await computing.createContract({ name, description, - nftContractAddress, - nftChainRpcUrl: ChainRpcUrl.MOONBASE, bucket_uuid, + contractData: { + nftContractAddress, + nftChainRpcUrl: ChainRpcUrl.MOONBASE, + }, }); expect(contract).toBeInstanceOf(ComputingContract); expect(contract.name).toEqual(name); @@ -49,6 +52,20 @@ describe('Computing tests', () => { contractUuid = contract.uuid; }); + test('Creating new contract with missing contract data should fail', async () => { + const createContract = () => + computing.createContract({ + name, + description, + bucket_uuid, + contractData: { + nftContractAddress: undefined, + nftChainRpcUrl: undefined, + }, + }); + await expect(createContract).rejects.toThrow(); + }); + test('List contracts', async () => { const { items } = await computing.listContracts(); @@ -94,6 +111,16 @@ describe('Computing tests', () => { }); }); + test('List all transactions with specific type', async () => { + const { items } = await computing.contract(contractUuid).listTransactions({ + transactionType: ComputingTransactionType.DEPLOY_CONTRACT, + }); + expect(items.length).toEqual(1); + expect(items[0].transactionType).toEqual( + ComputingTransactionType.DEPLOY_CONTRACT, + ); + }); + test.skip('Encrypt data using computing contract', async () => { const html = fs.readFileSync( resolve(__dirname, './helpers/website/style.css'), diff --git a/packages/sdk/src/types/computing.ts b/packages/sdk/src/types/computing.ts index 4d10253..9419ddb 100644 --- a/packages/sdk/src/types/computing.ts +++ b/packages/sdk/src/types/computing.ts @@ -21,7 +21,16 @@ export enum ComputingTransactionType { ASSIGN_CID_TO_NFT = 4, } -export type ComputingContractData = { +export enum ComputingTransactionStatus { + PENDING = 1, + CONFIRMED = 2, + FAILED = 3, + ERROR = 4, + WORKER_SUCCESS = 5, + WORKER_FAILED = 6, +} + +export interface SchrodingerContractData { /** * Contract address of NFT which will be used to authenticate decryption */ @@ -29,11 +38,15 @@ export type ComputingContractData = { /** * RPC URL of the chain the NFT collection exists on */ - nftChainRpcUrl: string; + nftChainRpcUrl: ChainRpcUrl | string; /** * If true, only the owner is able to use the contract for data encryption/decryption + * @default true */ - restrictToOwner: boolean; + restrictToOwner?: boolean; +} + +export interface ComputingContractData extends SchrodingerContractData { /** * The IPFS gateway where the encrypted files are stored on */ @@ -42,7 +55,7 @@ export type ComputingContractData = { * Identifier of the Phala computing cluster the contract runs on */ clusterId: string; -}; +} export interface IContractListFilters extends IApillonPagination { contractStatus?: ComputingContractStatus; @@ -56,19 +69,7 @@ export interface ICreateComputingContract { * @optional If this parameter is not passed, a new bucket will be created for the contract */ bucket_uuid?: string; - /** - * Contract address of NFT which will be used to authenticate decryption - */ - nftContractAddress: string; - /** - * RPC URL of the chain the NFT collection exists on - */ - nftChainRpcUrl: ChainRpcUrl | string; - /** - * If true, only the owner is able to use the contract for data encryption/decryption - * @default false - */ - restrictToOwner?: boolean; + contractData: SchrodingerContractData; } export interface IComputingTransaction { @@ -81,6 +82,11 @@ export interface IComputingTransaction { createTime: string; } +export interface ITransactionListFilters extends IApillonPagination { + transactionStatus?: ComputingTransactionStatus; + transactionType?: ComputingTransactionType; +} + export interface IEncryptData { /** * fileName for the encrypted file that will be stored in the bucket From e7e1e1e4407dc806030c70232f1896bed303d185 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Tue, 5 Mar 2024 10:50:55 +0100 Subject: [PATCH 10/20] Update LogLevel in docs --- packages/sdk/README.md | 10 +++++----- packages/sdk/src/types/computing.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 862a8f5..37a48de 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -82,7 +82,7 @@ import * as fs from 'fs'; const hosting = new Hosting({ key: 'yourApiKey', secret: 'yourApiSecret', - logLevel: LogLevel.NONE, + logLevel: LogLevel.VERBOSE, }); // list all websites @@ -140,7 +140,7 @@ import * as fs from 'fs'; const storage = new Storage({ key: 'yourApiKey', secret: 'yourApiSecret', - logLevel: LogLevel.NONE, + logLevel: LogLevel.VERBOSE, }); // list buckets @@ -196,7 +196,7 @@ import { Storage, LogLevel } from '@apillon/sdk'; const storage = new Storage({ key: 'yourApiKey', secret: 'yourApiSecret', - logLevel: LogLevel.NONE, + logLevel: LogLevel.VERBOSE, }); // create and instance of a bucket directly through uuid @@ -241,7 +241,7 @@ import { const nft = new Nft({ key: 'yourApiKey', secret: 'yourApiSecret', - logLevel: LogLevel.NONE, + logLevel: LogLevel.VERBOSE, }); // create a new collection @@ -315,7 +315,7 @@ import { LogLevel } from './types/apillon'; const identity = new Identity({ key: 'yourApiKey', secret: 'yourApiSecret', - logLevel: LogLevel.NONE, + logLevel: LogLevel.VERBOSE, }); // obtain on-chain identity data for a Polkadot wallet diff --git a/packages/sdk/src/types/computing.ts b/packages/sdk/src/types/computing.ts index 9419ddb..39d773d 100644 --- a/packages/sdk/src/types/computing.ts +++ b/packages/sdk/src/types/computing.ts @@ -93,7 +93,7 @@ export interface IEncryptData { */ fileName: string; /** - * Contents of the file to encrypt + * Contents of the file to encrypt. If the file is an image, the format needs to be base64. */ content: Buffer; /** From 04142274f5d5f092bf9a55691f2e111cbc095410 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Wed, 27 Mar 2024 15:26:27 +0100 Subject: [PATCH 11/20] Add bucketType to StorageBucket class --- .../sdk/src/modules/storage/storage-bucket.ts | 16 +++++++++++++++- packages/sdk/src/types/storage.ts | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/modules/storage/storage-bucket.ts b/packages/sdk/src/modules/storage/storage-bucket.ts index f57c07b..76a8421 100644 --- a/packages/sdk/src/modules/storage/storage-bucket.ts +++ b/packages/sdk/src/modules/storage/storage-bucket.ts @@ -1,5 +1,6 @@ import { Directory } from './directory'; import { + BucketType, FileMetadata, FileUploadResult, IBucketFilesRequest, @@ -34,6 +35,11 @@ export class StorageBucket extends ApillonModel { */ public size: number = null; + /** + * Type of bucket (storage, hosting or NFT metadata) + */ + public bucketType: number = null; + /** * Bucket content which are files and directories. */ @@ -196,7 +202,7 @@ export class StorageBucket extends ApillonModel { ); await new Promise((resolve) => setTimeout(resolve, 1000)); - if (++retryTimes >= 15) { + if (++retryTimes >= 30) { ApillonLogger.log('Unable to resolve file CIDs', LogLevel.ERROR); return resolvedFiles; } @@ -246,4 +252,12 @@ export class StorageBucket extends ApillonModel { return new Ipns(this.uuid, data.ipnsUuid, data); } //#endregion + + protected override serializeFilter(key: string, value: any) { + const serialized = super.serializeFilter(key, value); + const enums = { + bucketType: BucketType[value], + }; + return Object.keys(enums).includes(key) ? enums[key] : serialized; + } } diff --git a/packages/sdk/src/types/storage.ts b/packages/sdk/src/types/storage.ts index 559c3b3..9218b2d 100644 --- a/packages/sdk/src/types/storage.ts +++ b/packages/sdk/src/types/storage.ts @@ -12,6 +12,12 @@ export enum FileStatus { AVAILABLE_ON_IPFS_AND_REPLICATED = 4, } +export enum BucketType { + STORAGE = 1, + HOSTING = 2, + NFT_METADATA = 3, +} + export interface IStorageBucketContentRequest extends IApillonPagination { directoryUuid?: string; markedForDeletion?: boolean; From 9c67a2b78bae56ea04904e2cdac0ed8facffad52 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Tue, 2 Apr 2024 12:18:26 +0200 Subject: [PATCH 12/20] Update README.md --- packages/sdk/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 37a48de..6d02839 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -9,8 +9,8 @@ as well as compresses multi step flows into single operations. ## Requirements -- npm 8.4.0 or higher -- node.js 16.17.0 or higher +- npm 10.0.0 or higher +- node.js 20.0.0 or higher - Apillon API key and secret ## Getting started @@ -390,8 +390,11 @@ console.log(contracts); const newContract = await computing.createContract({ name: 'New Contract', description: 'Description of the new contract', - nftContractAddress: '0xabc...', - nftChainRpcUrl: ChainRpcUrl.ASTAR, + bucket_uuid, + contractData: { + nftContractAddress: '0xabc...', + nftChainRpcUrl: ChainRpcUrl.ASTAR, + }, }); // Interact with a specific computing contract From 021838506e3a7d07cb677543642f965b73059d2c Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Wed, 3 Apr 2024 08:59:15 +0200 Subject: [PATCH 13/20] fix issue with file upload --- packages/sdk/package.json | 2 +- packages/sdk/src/tests/storage.test.ts | 18 +++++------------- packages/sdk/src/util/file-utils.ts | 10 +++++++++- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index e3f2bc2..a8b9d2d 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,7 +1,7 @@ { "name": "@apillon/sdk", "description": "▶◀ Apillon SDK for NodeJS ▶◀", - "version": "2.0.1", + "version": "2.0.3", "author": "Apillon", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/sdk/src/tests/storage.test.ts b/packages/sdk/src/tests/storage.test.ts index 1e5c011..c0922e3 100644 --- a/packages/sdk/src/tests/storage.test.ts +++ b/packages/sdk/src/tests/storage.test.ts @@ -77,21 +77,13 @@ describe('Storage tests', () => { }); test('upload files from folder', async () => { - try { - const uploadDir = resolve(__dirname, './helpers/website/'); - - console.time('File upload complete'); - const files = await storage - .bucket(bucketUuid) - .uploadFromFolder(uploadDir); - console.timeEnd('File upload complete'); + const uploadDir = resolve(__dirname, './helpers/website/'); - expect(files.every((f) => !!f.fileUuid)).toBeTruthy(); + console.time('File upload complete'); + const files = await storage.bucket(bucketUuid).uploadFromFolder(uploadDir); + console.timeEnd('File upload complete'); - // console.log(content); - } catch (e) { - console.log(e); - } + expect(files.every((f) => !!f.fileUuid)).toBeTruthy(); }); test('upload files from folder with awaitCid', async () => { diff --git a/packages/sdk/src/util/file-utils.ts b/packages/sdk/src/util/file-utils.ts index 3b6cd1e..f7184ba 100644 --- a/packages/sdk/src/util/file-utils.ts +++ b/packages/sdk/src/util/file-utils.ts @@ -46,7 +46,15 @@ export async function uploadFiles( for (const fileGroup of chunkify(files, fileChunkSize)) { const { files } = await ApillonApi.post( `${apiPrefix}/upload`, - { files: fileGroup, sessionUuid }, + { + files: fileGroup.map((fg) => { + // Remove content property from the payload + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { content, ...rest } = fg; + return rest; + }), + sessionUuid, + }, ); await uploadFilesToS3(files, fileGroup); From e9b5599db16151ee5ada73f7c6e429aeec6c9dfc Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Wed, 3 Apr 2024 15:23:40 +0200 Subject: [PATCH 14/20] Add support for Substrate NFTs --- packages/sdk/README.md | 21 +++++++++--- packages/sdk/package.json | 2 +- .../sdk/src/modules/nft/nft-collection.ts | 7 ++-- packages/sdk/src/modules/nft/nft.ts | 23 +++++++++++-- packages/sdk/src/tests/nft.test.ts | 32 ++++++++++++++++--- packages/sdk/src/types/nfts.ts | 19 ++++++++--- 6 files changed, 83 insertions(+), 21 deletions(-) diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 1d68c05..f27308c 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -239,7 +239,7 @@ const nft = new Nft({ }); // create a new collection -const collection1 = await nft.create({ +let collection = await nft.create({ collectionType: CollectionType.GENERIC, chain: EvmChain.MOONBEAM, name: 'SpaceExplorers', @@ -258,20 +258,31 @@ const collection1 = await nft.create({ dropPrice: 0.05, dropReserve: 100, }); +// or create a substrate collection +const substrateCollection = await nft.createSubstrate({ + collectionType: CollectionType.GENERIC, + chain: EvmChain.MOONBEAM, + name: 'SpaceExplorers', + symbol: 'SE', + ... +}); // check if collection is deployed - available on chain -if (collection1.collectionStatus == CollectionStatus.DEPLOYED) { - console.log('Collection deployed: ', collection1.transactionHash); +if (collection.collectionStatus == CollectionStatus.DEPLOYED) { + console.log('Collection deployed: ', collection.transactionHash); } // search through collections await nft.listCollections({ search: 'My NFT' }); // create and instance of collection directly through uuid -const collection = await nft.collection('uuid').get(); +collection = await nft.collection('uuid').get(); // mint a new nft in the collection -await collection.mint('0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', 1); +await collection.mint({ + receivingAddress: '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', + quantity: 1, +}); // nest mint a new nft if collection type is NESTABLE await collection.nestMint(collection.uuid, 1, 1); diff --git a/packages/sdk/package.json b/packages/sdk/package.json index a8b9d2d..6f22f2d 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,7 +1,7 @@ { "name": "@apillon/sdk", "description": "▶◀ Apillon SDK for NodeJS ▶◀", - "version": "2.0.3", + "version": "2.1.0", "author": "Apillon", "license": "MIT", "main": "./dist/index.js", diff --git a/packages/sdk/src/modules/nft/nft-collection.ts b/packages/sdk/src/modules/nft/nft-collection.ts index d6633f6..7a73998 100644 --- a/packages/sdk/src/modules/nft/nft-collection.ts +++ b/packages/sdk/src/modules/nft/nft-collection.ts @@ -1,6 +1,7 @@ import { IMintNftData, INftActionResponse, + SubstrateChain, TransactionStatus, } from './../../types/nfts'; import { ApillonApi } from '../../lib/apillon-api'; @@ -125,7 +126,7 @@ export class NftCollection extends ApillonModel { /** * Chain on which the smart contract was deployed. */ - public chain: EvmChain = null; + public chain: EvmChain | SubstrateChain = null; /** * Constructor which should only be called via Nft class. @@ -264,8 +265,8 @@ export class NftCollection extends ApillonModel { collectionStatus: CollectionStatus[serialized], transactionType: TransactionType[serialized], transactionStatus: TransactionStatus[serialized], - chain: EvmChain[serialized], - chainId: EvmChain[serialized], + chain: EvmChain[serialized] || SubstrateChain[serialized], + chainId: EvmChain[serialized] || SubstrateChain[serialized], }; return Object.keys(enums).includes(key) ? enums[key] : serialized; } diff --git a/packages/sdk/src/modules/nft/nft.ts b/packages/sdk/src/modules/nft/nft.ts index ef98425..dd88ae8 100644 --- a/packages/sdk/src/modules/nft/nft.ts +++ b/packages/sdk/src/modules/nft/nft.ts @@ -6,6 +6,8 @@ import { ICollectionFilters, ICollection, ICreateCollection, + ICreateSubstrateCollection, + ICreateCollectionBase, } from '../../types/nfts'; import { NftCollection } from './nft-collection'; @@ -44,17 +46,32 @@ export class Nft extends ApillonModule { } /** - * Deploys a new NftCollection smart contract. + * Deploys a new EVM NftCollection smart contract. * @param data NFT collection data. * @returns A NftCollection instance. */ public async create(data: ICreateCollection) { + return await this.createNft(data, true); + } + + /** + * Deploys a new Substrate NftCollection smart contract. + * @param data NFT collection data. + * @returns A NftCollection instance. + */ + public async createSubstrate(data: ICreateSubstrateCollection) { + return await this.createNft(data, false); + } + + private async createNft(data: ICreateCollectionBase, isEvm: boolean) { // If not drop, set drop properties to default 0 if (!data.drop) { data.dropStart = data.dropPrice = data.dropReserve = 0; } - const response = await ApillonApi.post(this.API_PREFIX, data); - + const response = await ApillonApi.post( + `${this.API_PREFIX}/${isEvm ? 'evm' : 'substrate'}`, + data, + ); return new NftCollection(response.collectionUuid, response); } } diff --git a/packages/sdk/src/tests/nft.test.ts b/packages/sdk/src/tests/nft.test.ts index c6936ad..599bb89 100644 --- a/packages/sdk/src/tests/nft.test.ts +++ b/packages/sdk/src/tests/nft.test.ts @@ -1,10 +1,9 @@ import { Nft } from '../modules/nft/nft'; import { NftCollection } from '../modules/nft/nft-collection'; -import { CollectionType, EvmChain } from '../types/nfts'; +import { CollectionType, EvmChain, SubstrateChain } from '../types/nfts'; import { getCollectionUUID, getConfig, getMintAddress } from './helpers/helper'; const nftData = { - chain: EvmChain.MOONBASE, collectionType: CollectionType.GENERIC, name: 'SDK Test', description: 'Created from SDK tests', @@ -14,8 +13,6 @@ const nftData = { baseUri: 'https://test.com/metadata/', baseExtension: '.json', maxSupply: 5, - isRevokable: false, - isSoulbound: false, drop: false, }; @@ -37,17 +34,39 @@ describe('Nft tests', () => { }); test('creates a new collection', async () => { - const collection = await nft.create(nftData); + const collection = await nft.create({ + ...nftData, + chain: EvmChain.MOONBASE, + isRevokable: true, + isSoulbound: true, + }); expect(collection.uuid).toBeDefined(); expect(collection.contractAddress).toBeDefined(); expect(collection.symbol).toEqual('SDKT'); expect(collection.name).toEqual('SDK Test'); expect(collection.description).toEqual('Created from SDK tests'); expect(collection.isAutoIncrement).toEqual(true); + expect(collection.isRevokable).toEqual(true); + expect(collection.isSoulbound).toEqual(true); collectionUuid = collection.uuid; }); + test('creates a new substrate collection', async () => { + const collection = await nft.createSubstrate({ + ...nftData, + chain: SubstrateChain.ASTAR, + }); + expect(collection.uuid).toBeDefined(); + expect(collection.contractAddress).toBeDefined(); + expect(collection.symbol).toEqual('SDKT'); + expect(collection.name).toEqual('SDK Test'); + expect(collection.description).toEqual('Created from SDK tests'); + expect(collection.isAutoIncrement).toEqual(true); + expect(collection.isRevokable).toEqual(false); + expect(collection.isSoulbound).toEqual(false); + }); + test('mints a new nft', async () => { const collection = nft.collection(collectionUuid); const res = await collection.mint({ @@ -98,7 +117,10 @@ describe('Nft tests', () => { const collection = await nft.create({ ...nftData, name: 'SDK Test isAutoIncrement=false', + chain: EvmChain.MOONBASE, isAutoIncrement: false, + isRevokable: false, + isSoulbound: false, }); expect(collection.uuid).toBeDefined(); expect(collection.contractAddress).toBeDefined(); diff --git a/packages/sdk/src/types/nfts.ts b/packages/sdk/src/types/nfts.ts index 3920a88..6670e1b 100644 --- a/packages/sdk/src/types/nfts.ts +++ b/packages/sdk/src/types/nfts.ts @@ -6,6 +6,10 @@ export enum EvmChain { ASTAR = 592, } +export enum SubstrateChain { + ASTAR = 8, +} + export enum CollectionStatus { CREATED = 0, DEPLOY_INITIATED = 1, @@ -36,24 +40,31 @@ export enum TransactionType { NEST_MINT_NFT = 6, } -export interface ICreateCollection { +export interface ICreateCollectionBase { collectionType: CollectionType; - chain: EvmChain; name: string; symbol: string; description?: string; baseUri: string; baseExtension: string; maxSupply?: number; - isRevokable: boolean; - isSoulbound: boolean; royaltiesAddress: string; royaltiesFees: number; drop: boolean; dropStart?: number; dropPrice?: number; dropReserve?: number; +} + +export interface ICreateCollection extends ICreateCollectionBase { + isRevokable: boolean; + isSoulbound: boolean; isAutoIncrement?: boolean; + chain: EvmChain; +} + +export interface ICreateSubstrateCollection extends ICreateCollectionBase { + chain: SubstrateChain; } //OUTPUTS From 4ebda6a6743ae19ee612352e8ca2cdc50b69fca7 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Wed, 3 Apr 2024 15:28:01 +0200 Subject: [PATCH 15/20] Change Phala SDK version --- packages/sdk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 6f22f2d..5627580 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,7 +1,7 @@ { "name": "@apillon/sdk", "description": "▶◀ Apillon SDK for NodeJS ▶◀", - "version": "2.1.0", + "version": "2.2.0", "author": "Apillon", "license": "MIT", "main": "./dist/index.js", From bdf721701fb0f02f987d54fd7dfc13ab1666de81 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Fri, 5 Apr 2024 13:13:36 +0200 Subject: [PATCH 16/20] Implement social module --- .../sdk/src/modules/social/social-channel.ts | 45 +++++++++ packages/sdk/src/modules/social/social-hub.ts | 51 ++++++++++ packages/sdk/src/modules/social/social.ts | 98 +++++++++++++++++++ packages/sdk/src/tests/social.test.ts | 74 ++++++++++++++ packages/sdk/src/types/apillon.ts | 1 + packages/sdk/src/types/social.ts | 43 ++++++++ 6 files changed, 312 insertions(+) create mode 100644 packages/sdk/src/modules/social/social-channel.ts create mode 100644 packages/sdk/src/modules/social/social-hub.ts create mode 100644 packages/sdk/src/modules/social/social.ts create mode 100644 packages/sdk/src/tests/social.test.ts create mode 100644 packages/sdk/src/types/social.ts diff --git a/packages/sdk/src/modules/social/social-channel.ts b/packages/sdk/src/modules/social/social-channel.ts new file mode 100644 index 0000000..75d3496 --- /dev/null +++ b/packages/sdk/src/modules/social/social-channel.ts @@ -0,0 +1,45 @@ +import { ApillonApi } from '../../lib/apillon-api'; +import { ApillonModel } from '../../lib/apillon'; +import { HubStatus } from '../../types/social'; + +export class SocialChannel extends ApillonModel { + /** + * Channel ID on Subsocial chain. This ID is used in widget. + */ + public channelId: number = null; + + /** + * Channel status (1: draft - deploying to chain, 5: active, 100: error). + */ + public status: HubStatus = null; + + /** + * Name of the channel. + */ + public title: string = null; + + /** + * Short description or content of the channel. + */ + public body: string = null; + + /** + * Comma separated tags associated with the channel. + */ + public tags: string = null; + + constructor(uuid: string, data?: Partial) { + super(uuid); + this.API_PREFIX = `/social/channels/${uuid}`; + this.populate(data); + } + + /** + * Fetches and populates the channel details from the API. + * @returns An instance of Channel class with filled properties. + */ + public async get(): Promise { + const data = await ApillonApi.get(this.API_PREFIX); + return this.populate(data); + } +} diff --git a/packages/sdk/src/modules/social/social-hub.ts b/packages/sdk/src/modules/social/social-hub.ts new file mode 100644 index 0000000..f40d3b2 --- /dev/null +++ b/packages/sdk/src/modules/social/social-hub.ts @@ -0,0 +1,51 @@ +import { ApillonApi } from '../../lib/apillon-api'; +import { ApillonModel } from '../../lib/apillon'; +import { HubStatus } from '../../types/social'; + +export class SocialHub extends ApillonModel { + /** + * Hub ID on Subsocial chain. + * @example https://grillapp.net/12927 + */ + public hubId: number = null; + + /** + * Hub status (1: draft - deploying to chain, 5: active, 100: error). + */ + public status: HubStatus = null; + + /** + * Name of the hub. + */ + public name: string = null; + + /** + * Short description about the hub. + */ + public about: string = null; + + /** + * Comma separated tags associated with the hub. + */ + public tags: string = null; + + /** + * Number of channels in the hub. + */ + public numOfChannels: number = null; + + constructor(uuid: string, data?: Partial) { + super(uuid); + this.API_PREFIX = `/social/hubs/${uuid}`; + this.populate(data); + } + + /** + * Fetches and populates the hub details from the API. + * @returns An instance of Hub class with filled properties. + */ + public async get(): Promise { + const data = await ApillonApi.get(this.API_PREFIX); + return this.populate(data); + } +} diff --git a/packages/sdk/src/modules/social/social.ts b/packages/sdk/src/modules/social/social.ts new file mode 100644 index 0000000..8a19c47 --- /dev/null +++ b/packages/sdk/src/modules/social/social.ts @@ -0,0 +1,98 @@ +import { ApillonModule } from '../../lib/apillon'; +import { ApillonApi } from '../../lib/apillon-api'; +import { constructUrlWithQueryParams } from '../../lib/common'; +import { IApillonList, IApillonPagination } from '../../types/apillon'; +import { + ICreateHub, + ICreateChannel, + IChannelFilters, +} from '../../types/social'; // Assume these types are defined similarly to the NFT types +import { SocialChannel } from './social-channel'; +import { SocialHub } from './social-hub'; + +export class Social extends ApillonModule { + private HUBS_API_PREFIX = '/social/hubs'; + private CHANNELS_API_PREFIX = '/social/channels'; + + /** + * Lists all hubs with optional filters. + * @param {IApillonPagination} params Optional filters for listing hubs. + * @returns A list of Hub instances. + */ + public async listHubs( + params?: IApillonPagination, + ): Promise> { + const data = await ApillonApi.get< + IApillonList + >(constructUrlWithQueryParams(this.HUBS_API_PREFIX, params)); + + return { + ...data, + items: data.items.map((hub) => new SocialHub(hub.hubUuid, hub)), + }; + } + + /** + * Lists all channels with optional filters. + * @param {IChannelFilters} params Optional filters for listing channels. + * @returns A list of Channel instances. + */ + public async listChannels( + params?: IChannelFilters, + ): Promise> { + const url = constructUrlWithQueryParams(this.CHANNELS_API_PREFIX, params); + const data = await ApillonApi.get< + IApillonList + >(url); + + return { + ...data, + items: data.items.map( + (channel) => new SocialChannel(channel.channelUuid, channel), + ), + }; + } + + /** + * Creates a new hub. + * @param {ICreateHub} hubData Data for creating the hub. + * @returns The created Hub instance. + */ + public async createHub(hubData: ICreateHub): Promise { + const hub = await ApillonApi.post( + this.HUBS_API_PREFIX, + hubData, + ); + return new SocialHub(hub.hubUuid, hub); + } + + /** + * Creates a new channel. + * @param {ICreateChannel} channelData Data for creating the channel. + * @returns The created Channel instance. + */ + public async createChannel( + channelData: ICreateChannel, + ): Promise { + const channel = await ApillonApi.post< + SocialChannel & { channelUuid: string } + >(this.CHANNELS_API_PREFIX, channelData); + return new SocialChannel(channel.channelUuid, channel); + } + + /** + * @param uuid Unique hub identifier. + * @returns An instance of Hub. + */ + public hub(uuid: string): SocialHub { + return new SocialHub(uuid); + } + + /** + * @param uuid Unique channel identifier. + * @returns An instance of SocialChannel. + */ + public channel(uuid: string): SocialChannel { + return new SocialChannel(uuid); + } +} diff --git a/packages/sdk/src/tests/social.test.ts b/packages/sdk/src/tests/social.test.ts new file mode 100644 index 0000000..ffba1d4 --- /dev/null +++ b/packages/sdk/src/tests/social.test.ts @@ -0,0 +1,74 @@ +import { Social } from '../modules/social/social'; +import { getConfig } from './helpers/helper'; + +describe('Social tests', () => { + let social: Social; + let hubUuid: string; + let channelUuid: string; + + const hubData = { + name: 'Test Hub', + about: 'This is a test hub', + tags: 'sdk,test,hub', + }; + const channelData = { + title: 'Test Channel', + body: 'This is a test channel', + tags: 'sdk,test,channel', + }; + + beforeAll(async () => { + social = new Social(getConfig()); + }); + + test('Create a new Hub', async () => { + const hub = await social.createHub(hubData); + expect(hub.uuid).toBeDefined(); + expect(hub.name).toEqual(hubData.name); + expect(hub.about).toEqual(hubData.about); + expect(hub.tags).toEqual(hubData.tags); + hubUuid = hub.uuid; + }); + + test('Get Hub', async () => { + const hub = await social.hub(hubUuid).get(); + expect(hub.uuid).toEqual(hubUuid); + expect(hub.name).toEqual(hubData.name); + expect(hub.about).toEqual(hubData.about); + expect(hub.tags).toEqual(hubData.tags); + }); + + test('List Hubs', async () => { + const response = await social.listHubs(); + expect(response.items.length).toBeGreaterThanOrEqual(1); + expect(response.items.every((h) => !!h.uuid)); + expect(response.items.some((h) => h.uuid === hubUuid)); + }); + + test('Create a new Channel', async () => { + const channel = await social.createChannel({ + ...channelData, + // hubUuid, + }); + expect(channel.uuid).toBeDefined(); + expect(channel.title).toEqual(channelData.title); + expect(channel.body).toEqual(channelData.body); + expect(channel.tags).toEqual(channelData.tags); + channelUuid = channel.uuid; + }); + + test('Get Channel', async () => { + const channel = await social.channel(channelUuid).get(); + expect(channel.uuid).toEqual(channelUuid); + expect(channel.title).toEqual(channelData.title); + expect(channel.body).toEqual(channelData.body); + expect(channel.tags).toEqual(channelData.tags); + }); + + test('List Channels', async () => { + const response = await social.listChannels(); + expect(response.items.length).toBeGreaterThanOrEqual(1); + expect(response.items.every((c) => !!c.uuid)); + expect(response.items.some((c) => c.uuid === channelUuid)); + }); +}); diff --git a/packages/sdk/src/types/apillon.ts b/packages/sdk/src/types/apillon.ts index 9fc5352..7033603 100644 --- a/packages/sdk/src/types/apillon.ts +++ b/packages/sdk/src/types/apillon.ts @@ -9,6 +9,7 @@ export interface IApillonPagination { limit?: number; orderBy?: string; desc?: boolean; + status: number; } export enum LogLevel { diff --git a/packages/sdk/src/types/social.ts b/packages/sdk/src/types/social.ts new file mode 100644 index 0000000..14a0041 --- /dev/null +++ b/packages/sdk/src/types/social.ts @@ -0,0 +1,43 @@ +import { IApillonPagination } from './apillon'; + +export enum HubStatus { + DRAFT = 1, + ACTIVE = 5, + ERORR = 100, +} + +export interface ICreateHub { + name: string; + /** + * Short description about the hub. + */ + about?: string; + /** + * Comma separated tags associated with the hub. + */ + tags?: string; +} + +export interface ICreateChannel { + title: string; + /** + * Short description or content of the channel. + */ + body: string; + /** + * Comma separated tags associated with the channel. + */ + tags?: string; + /** + * Hub in which the channel will be created + * @default Apillon default hub + */ + hubUuid?: string; +} + +export interface IChannelFilters extends IApillonPagination { + /** + * Parent hub unique identifier + */ + hubUuid?: string; +} From 520a67809d5bce370b395d104a35d07bb7345445 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Fri, 5 Apr 2024 13:41:58 +0200 Subject: [PATCH 17/20] General code updates --- packages/sdk/src/lib/apillon.ts | 3 +-- .../modules/computing/computing-contract.ts | 5 +---- .../sdk/src/modules/nft/nft-collection.ts | 5 ++--- packages/sdk/src/modules/nft/nft.ts | 12 ++++++------ packages/sdk/src/modules/storage/directory.ts | 2 +- packages/sdk/src/modules/storage/file.ts | 7 +------ .../sdk/src/modules/storage/storage-bucket.ts | 6 ++---- packages/sdk/src/tests/computing.test.ts | 1 - packages/sdk/src/tests/nft.test.ts | 2 +- packages/sdk/src/types/apillon.ts | 2 +- packages/sdk/src/types/nfts.ts | 19 +------------------ packages/sdk/src/types/storage.ts | 4 ---- packages/sdk/src/util/file-utils.ts | 14 ++++++++------ 13 files changed, 25 insertions(+), 57 deletions(-) diff --git a/packages/sdk/src/lib/apillon.ts b/packages/sdk/src/lib/apillon.ts index 617474e..60fcd93 100644 --- a/packages/sdk/src/lib/apillon.ts +++ b/packages/sdk/src/lib/apillon.ts @@ -73,8 +73,7 @@ export class ApillonModel { protected populate(data: object) { if (data != null) { Object.keys(data || {}).forEach((key) => { - const prop = this[key]; - if (prop === null) { + if (this[key] === null) { this[key] = data[key]; } }); diff --git a/packages/sdk/src/modules/computing/computing-contract.ts b/packages/sdk/src/modules/computing/computing-contract.ts index 959a392..4fe5167 100644 --- a/packages/sdk/src/modules/computing/computing-contract.ts +++ b/packages/sdk/src/modules/computing/computing-contract.ts @@ -88,10 +88,7 @@ export class ComputingContract extends ApillonModel { * @returns ComputingContract instance */ async get(): Promise { - const data = await ApillonApi.get< - ComputingContract & { contractUuid: string } - >(this.API_PREFIX); - + const data = await ApillonApi.get(this.API_PREFIX); return this.populate(data); } diff --git a/packages/sdk/src/modules/nft/nft-collection.ts b/packages/sdk/src/modules/nft/nft-collection.ts index 2335c04..5c3d716 100644 --- a/packages/sdk/src/modules/nft/nft-collection.ts +++ b/packages/sdk/src/modules/nft/nft-collection.ts @@ -9,7 +9,6 @@ import { ApillonLogger } from '../../lib/apillon-logger'; import { constructUrlWithQueryParams } from '../../lib/common'; import { IApillonList } from '../../types/apillon'; import { - ICollection, ITransactionFilters, ITransaction, CollectionType, @@ -144,7 +143,7 @@ export class NftCollection extends ApillonModel { * @returns Collection instance. */ public async get(): Promise { - const data = await ApillonApi.get(this.API_PREFIX); + const data = await ApillonApi.get(this.API_PREFIX); return this.populate(data); } @@ -224,7 +223,7 @@ export class NftCollection extends ApillonModel { * @returns Collection data. */ public async transferOwnership(address: string): Promise { - const data = await ApillonApi.post( + const data = await ApillonApi.post( `${this.API_PREFIX}/transfer`, { address }, ); diff --git a/packages/sdk/src/modules/nft/nft.ts b/packages/sdk/src/modules/nft/nft.ts index dd88ae8..3cd3448 100644 --- a/packages/sdk/src/modules/nft/nft.ts +++ b/packages/sdk/src/modules/nft/nft.ts @@ -4,7 +4,6 @@ import { constructUrlWithQueryParams } from '../../lib/common'; import { IApillonList } from '../../types/apillon'; import { ICollectionFilters, - ICollection, ICreateCollection, ICreateSubstrateCollection, ICreateCollectionBase, @@ -35,7 +34,9 @@ export class Nft extends ApillonModule { ): Promise> { const url = constructUrlWithQueryParams(this.API_PREFIX, params); - const data = await ApillonApi.get>(url); + const data = await ApillonApi.get< + IApillonList + >(url); return { ...data, @@ -68,10 +69,9 @@ export class Nft extends ApillonModule { if (!data.drop) { data.dropStart = data.dropPrice = data.dropReserve = 0; } - const response = await ApillonApi.post( - `${this.API_PREFIX}/${isEvm ? 'evm' : 'substrate'}`, - data, - ); + const response = await ApillonApi.post< + NftCollection & { collectionUuid: string } + >(`${this.API_PREFIX}/${isEvm ? 'evm' : 'substrate'}`, data); return new NftCollection(response.collectionUuid, response); } } diff --git a/packages/sdk/src/modules/storage/directory.ts b/packages/sdk/src/modules/storage/directory.ts index ea30755..cc4f9b3 100644 --- a/packages/sdk/src/modules/storage/directory.ts +++ b/packages/sdk/src/modules/storage/directory.ts @@ -21,7 +21,7 @@ export class Directory extends ApillonModel { public name: string = null; /** - * Directory unique ipfs identifier. + * Directory unique IPFS content identifier. */ public CID: string = null; diff --git a/packages/sdk/src/modules/storage/file.ts b/packages/sdk/src/modules/storage/file.ts index 117e19d..1212676 100644 --- a/packages/sdk/src/modules/storage/file.ts +++ b/packages/sdk/src/modules/storage/file.ts @@ -21,15 +21,10 @@ export class File extends ApillonModel { public name: string = null; /** - * File unique ipfs identifier. + * File unique IPFS content identifier. */ public CID: string = null; - /** - * File content identifier V1. - */ - public CIDv1: string = null; - /** * File upload status. */ diff --git a/packages/sdk/src/modules/storage/storage-bucket.ts b/packages/sdk/src/modules/storage/storage-bucket.ts index ce718bf..aaeb176 100644 --- a/packages/sdk/src/modules/storage/storage-bucket.ts +++ b/packages/sdk/src/modules/storage/storage-bucket.ts @@ -61,10 +61,8 @@ export class StorageBucket extends ApillonModel { * @returns Bucket instance */ async get(): Promise { - const data = await ApillonApi.get( - this.API_PREFIX, - ); - return new StorageBucket(data.bucketUuid, data); + const data = await ApillonApi.get(this.API_PREFIX); + return this.populate(data); } /** diff --git a/packages/sdk/src/tests/computing.test.ts b/packages/sdk/src/tests/computing.test.ts index 565dd6d..640b2be 100644 --- a/packages/sdk/src/tests/computing.test.ts +++ b/packages/sdk/src/tests/computing.test.ts @@ -132,7 +132,6 @@ describe('Computing tests', () => { expect(files).toHaveLength(1); expect(files[0].fileName).toBe('style.css'); expect(files[0].CID).toBeDefined(); - expect(files[0].CIDv1).toBeDefined(); }); test.skip('Transfer ownership of computing contract', async () => { diff --git a/packages/sdk/src/tests/nft.test.ts b/packages/sdk/src/tests/nft.test.ts index 599bb89..5b04d60 100644 --- a/packages/sdk/src/tests/nft.test.ts +++ b/packages/sdk/src/tests/nft.test.ts @@ -9,7 +9,6 @@ const nftData = { description: 'Created from SDK tests', symbol: 'SDKT', royaltiesFees: 0, - royaltiesAddress: '0x0000000000000000000000000000000000000000', baseUri: 'https://test.com/metadata/', baseExtension: '.json', maxSupply: 5, @@ -56,6 +55,7 @@ describe('Nft tests', () => { const collection = await nft.createSubstrate({ ...nftData, chain: SubstrateChain.ASTAR, + royaltiesAddress: 'b3k5JvUnYjdZrCCNkf15PFpqChMunu11aeRoLropayUmhR4', }); expect(collection.uuid).toBeDefined(); expect(collection.contractAddress).toBeDefined(); diff --git a/packages/sdk/src/types/apillon.ts b/packages/sdk/src/types/apillon.ts index 7033603..d944234 100644 --- a/packages/sdk/src/types/apillon.ts +++ b/packages/sdk/src/types/apillon.ts @@ -9,7 +9,7 @@ export interface IApillonPagination { limit?: number; orderBy?: string; desc?: boolean; - status: number; + status?: number; } export enum LogLevel { diff --git a/packages/sdk/src/types/nfts.ts b/packages/sdk/src/types/nfts.ts index 6670e1b..c01b248 100644 --- a/packages/sdk/src/types/nfts.ts +++ b/packages/sdk/src/types/nfts.ts @@ -48,7 +48,7 @@ export interface ICreateCollectionBase { baseUri: string; baseExtension: string; maxSupply?: number; - royaltiesAddress: string; + royaltiesAddress?: string; royaltiesFees: number; drop: boolean; dropStart?: number; @@ -67,23 +67,6 @@ export interface ICreateSubstrateCollection extends ICreateCollectionBase { chain: SubstrateChain; } -//OUTPUTS -export interface ICollection extends ICreateCollection { - collectionUuid: string; - contractAddress: string; - deployerAddress: string; - transactionHash: string; - collectionStatus: number; - collectionType: number; - chain: number; - name: string; - symbol: string; - description: string; - bucketUuid: string; - updateTime: string; - createTime: string; -} - export interface ITransaction { chainId: number; transactionType: TransactionType; diff --git a/packages/sdk/src/types/storage.ts b/packages/sdk/src/types/storage.ts index e243317..08f4a21 100644 --- a/packages/sdk/src/types/storage.ts +++ b/packages/sdk/src/types/storage.ts @@ -54,10 +54,6 @@ export interface FileMetadata { * The file's CID on IPFS */ CID?: string; - /** - * The file's CIDv1 on IPFS - */ - CIDv1?: string; } export type FileUploadResult = Omit; diff --git a/packages/sdk/src/util/file-utils.ts b/packages/sdk/src/util/file-utils.ts index f7184ba..78a3a2c 100644 --- a/packages/sdk/src/util/file-utils.ts +++ b/packages/sdk/src/util/file-utils.ts @@ -12,12 +12,14 @@ import { import { LogLevel } from '../types/apillon'; import { randomBytes } from 'crypto'; -export async function uploadFiles( - folderPath: string, - apiPrefix: string, - params?: IFileUploadRequest, - files?: FileMetadata[], -): Promise<{ sessionUuid: string; files: FileMetadata[] }> { +export async function uploadFiles(uploadParams: { + apiPrefix: string; + params?: IFileUploadRequest; + folderPath?: string; + files?: FileMetadata[]; +}): Promise<{ sessionUuid: string; files: FileMetadata[] }> { + const { folderPath, apiPrefix, params } = uploadParams; + let files = uploadParams.files; if (folderPath) { ApillonLogger.log(`Preparing to upload files from ${folderPath}...`); } else if (files?.length) { From 860497c9962fe60f9c9ad43ff3ca1b2a2d0b5614 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Sun, 7 Apr 2024 12:00:49 +0200 Subject: [PATCH 18/20] Add Social SDK README docs --- packages/sdk/README.md | 46 +++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 6fe3887..4da49f8 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -309,7 +309,6 @@ await collection.transferOwnership( ); ``` - ## Identity Identity module encapsulates functionalities for validating EVM and Polkadot wallet signatures, as well as fetching Polkadot Identity data for any wallet. @@ -319,8 +318,7 @@ For detailed hosting SDK method, class and property documentation visit [SDK ide ### Usage example ```ts -import { Identity } from './modules/identity/identity'; -import { LogLevel } from './types/apillon'; +import { Identity, LogLevel } from '@apillon/sdk'; // Note: for signature-related methods API config is not required const identity = new Identity({ @@ -388,6 +386,8 @@ The Computing module provides functionalities for managing computing contracts, ### Usage example ```ts +import { Computing } from '@apillon/sdk'; + const computing = new Computing({ key: 'yourApiKey', secret: 'yourApiSecret', @@ -395,7 +395,6 @@ const computing = new Computing({ // List all computing contracts const contracts = await computing.listContracts(); -console.log(contracts); // Create a new computing contract const newContract = await computing.createContract({ @@ -413,11 +412,9 @@ const contract = computing.contract(newContract.uuid); // Get details of the contract const contractDetails = await contract.get(); -console.log(contractDetails); // List transactions of the contract const transactions = await contract.listTransactions(); -console.log(transactions); // Encrypt a file and upload it to the associated bucket const encryptionResult = await contract.encryptFile({ @@ -425,7 +422,6 @@ const encryptionResult = await contract.encryptFile({ content: Buffer.from('Hello, world!'), nftId: 1, // NFT ID used for decryption authentication }); -console.log(encryptionResult); // Transfer ownership of the contract const newOwnerAddress = '0xNewOwnerAddress'; @@ -434,3 +430,39 @@ console.log( `Ownership transfer was ${successResult ? 'successful' : 'unsuccessful'}.`, ); ``` + +## Social + +The Social module provides functionalities for managing social hubs and channels within the Apillon platform. This includes creating, listing, and interacting with hubs and channels. In the background it utilizes Grill.chat, a mobile-friendly, anonymous chat application powered by Subsocial. + +### Usage example + +```ts +import { Social } from '@apillon/sdk'; + +const social = new Social({ key: 'yourApiKey', secret: 'yourApiSecret' }); +// Create a new hub +const hub = await social.createHub({ + name: 'Apillon Hub', + about: 'Hub for Apillon channels', + tags: 'apillon,web3,build', +}); + +// Get a specific hub by UUID +const hubDetails = await social.hub(hub.uuid).get(); +// List all Hubs +const hubs = await social.listHubs(); + +// Create a new channel within a hub +const channel = await social.createChannel({ + title: 'Web3 Channel', + body: "Let's discuss Web3", + tags: 'web3,crypto', + hubUuid: hub.uuid, +}); + +// Get a specific channel by UUID +const channelDetails = await social.channel(channel.uuid).get(); +// List all channels within a Hub +const channels = await social.listChannels({ hubUuid: hub.uuid }); +``` \ No newline at end of file From 127e0cababf86e20813905c47ebac963cd22aa9b Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Sun, 7 Apr 2024 12:13:20 +0200 Subject: [PATCH 19/20] Update tests and exports --- packages/sdk/README.md | 2 +- packages/sdk/src/docs-index.ts | 3 ++ packages/sdk/src/index.ts | 1 + packages/sdk/src/tests/helpers/helper.ts | 8 +++++ packages/sdk/src/tests/hosting.test.ts | 37 ++++++++++-------------- packages/sdk/src/tests/storage.test.ts | 13 +++++++-- 6 files changed, 39 insertions(+), 25 deletions(-) diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 4da49f8..47e1901 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -267,7 +267,7 @@ let collection = await nft.create({ // or create a substrate collection const substrateCollection = await nft.createSubstrate({ collectionType: CollectionType.GENERIC, - chain: EvmChain.MOONBEAM, + chain: SubstrateChain.ASTAR, name: 'SpaceExplorers', symbol: 'SE', ... diff --git a/packages/sdk/src/docs-index.ts b/packages/sdk/src/docs-index.ts index e15f872..641a34e 100644 --- a/packages/sdk/src/docs-index.ts +++ b/packages/sdk/src/docs-index.ts @@ -18,3 +18,6 @@ export * from './modules/nft/nft-collection'; export * from './modules/identity/identity'; export * from './modules/computing/computing'; export * from './modules/computing/computing-contract'; +export * from './modules/social/social'; +export * from './modules/social/social-hub'; +export * from './modules/social/social-channel'; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 7c7a0ef..0356f48 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -10,3 +10,4 @@ export * from './modules/hosting/hosting'; export * from './modules/nft/nft'; export * from './modules/identity/identity'; export * from './modules/computing/computing'; +export * from './modules/social/social'; diff --git a/packages/sdk/src/tests/helpers/helper.ts b/packages/sdk/src/tests/helpers/helper.ts index 3fef417..f8c2822 100644 --- a/packages/sdk/src/tests/helpers/helper.ts +++ b/packages/sdk/src/tests/helpers/helper.ts @@ -38,3 +38,11 @@ export function getPhalaAddress() { export function getComputingContractUUID() { return process.env['COMPUTING_CONTRACT_UUID']; } + +export function getDirectoryUUID() { + return process.env['DIRECTORY_UUID']; +} + +export function getFileUUID() { + return process.env['FILE_UUID']; +} diff --git a/packages/sdk/src/tests/hosting.test.ts b/packages/sdk/src/tests/hosting.test.ts index f03a53e..1508b6d 100644 --- a/packages/sdk/src/tests/hosting.test.ts +++ b/packages/sdk/src/tests/hosting.test.ts @@ -33,7 +33,7 @@ describe('Hosting tests', () => { const website = hosting.website(websiteUuid); const uploadDir = resolve(__dirname, './helpers/website/'); - await website.uploadFromFolder(uploadDir); + await website.uploadFromFolder(uploadDir, { ignoreFiles: false }); const deployment = await website.deploy(DeployToEnvironment.TO_STAGING); expect(deployment.environment).toEqual(DeployToEnvironment.TO_STAGING); deploymentUuid = deployment.uuid; @@ -42,32 +42,27 @@ describe('Hosting tests', () => { expect(website.lastDeploymentStatus).toEqual(DeploymentStatus.INITIATED); }); - test.skip('upload files from buffer', async () => { + test('upload files from buffer', async () => { const html = fs.readFileSync( resolve(__dirname, './helpers/website/index.html'), ); const css = fs.readFileSync( resolve(__dirname, './helpers/website/style.css'), ); - try { - console.time('File upload complete'); - await hosting.website(websiteUuid).uploadFiles([ - { - fileName: 'index.html', - contentType: 'text/html', - content: html, - }, - { - fileName: 'style.css', - contentType: 'text/css', - content: css, - }, - ]); - console.timeEnd('File upload complete'); - // console.log(content); - } catch (e) { - console.log(e); - } + console.time('File upload complete'); + await hosting.website(websiteUuid).uploadFiles([ + { + fileName: 'index.html', + contentType: 'text/html', + content: html, + }, + { + fileName: 'style.css', + contentType: 'text/css', + content: css, + }, + ]); + console.timeEnd('File upload complete'); }); test('list all deployments', async () => { diff --git a/packages/sdk/src/tests/storage.test.ts b/packages/sdk/src/tests/storage.test.ts index e7ed42b..86dd9fc 100644 --- a/packages/sdk/src/tests/storage.test.ts +++ b/packages/sdk/src/tests/storage.test.ts @@ -1,19 +1,26 @@ import { resolve } from 'path'; import { Storage } from '../modules/storage/storage'; import { StorageContentType } from '../types/storage'; -import { getBucketUUID, getConfig } from './helpers/helper'; +import { + getBucketUUID, + getConfig, + getDirectoryUUID, + getFileUUID, +} from './helpers/helper'; import * as fs from 'fs'; describe('Storage tests', () => { let storage: Storage; let bucketUuid: string; // For get and delete tests - const directoryUuid = '6c9c6ab1-801d-4915-a63e-120eed21fee0'; - const fileUuid = 'cf6a0d3d-2abd-4a0d-85c1-10b8f04cd4fc'; + let directoryUuid: string; + let fileUuid: string; beforeAll(async () => { storage = new Storage(getConfig()); bucketUuid = getBucketUUID(); + directoryUuid = getDirectoryUUID(); + fileUuid = getFileUUID(); }); test('List buckets', async () => { From 49669705425f77e605613fc39eba35e94c150078 Mon Sep 17 00:00:00 2001 From: Damjan Dimitrov Date: Tue, 9 Apr 2024 10:28:00 +0200 Subject: [PATCH 20/20] Raise major version --- package.json | 2 +- packages/cli/README.md | 6 ++---- packages/sdk/package.json | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0fbb1f9..0977766 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apillon-web3-tools", - "version": "2.1.0", + "version": "3.0.0", "description": "Monorepo for Apillon tools", "author": "Apillon", "license": "MIT", diff --git a/packages/cli/README.md b/packages/cli/README.md index c5183a1..05dfeb7 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -302,8 +302,7 @@ apillon storage list-objects --bucket-uuid "123e4567-e89b-12d3-a456-426655440000 "updateTime": "2023-11-23T08:55:46.000Z", "uuid": "14a7a891-877c-41ac-900c-7382347e1e77", "name": "index.html", - "CID": "QmWX5CcNvnaVmgGBn4o82XW9uW1uLvsHQDdNrANrQeSdXm", - "CIDv1": "bafybeidzrd7p5ddj67j2mud32cbnze2c7b2pvbhn...", + "CID": "bafybeidzrd7p5ddj67j2mud32cbnze2c7b2pvbhn...", "status": "AVAILABLE_ON_IPFS_AND_REPLICATED", "directoryUuid": null, "type": "FILE", @@ -350,8 +349,7 @@ apillon storage list-files --bucket-uuid "123e4567-e89b-12d3-a456-426655440000" "createTime": "2023-11-15T09:58:04.000Z", "updateTime": "2023-11-15T09:58:10.000Z", "name": "style.css", - "CID": "QmWX5CcNvnaVmgGBn4o82XW9uW1uLvsHQDdNrANrQeSdXm", - "CIDv1": "bafybeidzrd7p5ddj67j2mud32cbnze2c7b2pvbag...", + "CID": "bafybeidzrd7p5ddj67j2mud32cbnze2c7b2pvbag...", "status": "AVAILABLE_ON_IPFS_AND_REPLICATED", "directoryUuid": null, "type": "FILE", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 6f22f2d..eb7a6b3 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,7 +1,7 @@ { "name": "@apillon/sdk", "description": "▶◀ Apillon SDK for NodeJS ▶◀", - "version": "2.1.0", + "version": "3.0.0", "author": "Apillon", "license": "MIT", "main": "./dist/index.js",