UNPKG

pic-js-mops

Version:

An Internet Computer Protocol canister testing library for TypeScript and JavaScript.

1,445 lines (1,369 loc) 44.2 kB
import { Principal } from '@icp-sdk/core/principal'; import { IDL } from '@icp-sdk/core/candid'; import { optional, readFileAsBytes } from './util/index.js'; import { PocketIcClient } from './pocket-ic-client.js'; import { ActorInterface, Actor, createActorClass } from './pocket-ic-actor.js'; import { CanisterFixture, CreateCanisterOptions, CreateInstanceOptions, InstallCodeOptions, ReinstallCodeOptions, SetupCanisterOptions, UpgradeCanisterOptions, SubnetTopology, SubnetType, UpdateCanisterSettingsOptions, StartCanisterOptions, StopCanisterOptions, QueryCallOptions, UpdateCallOptions, PendingHttpsOutcall, MockPendingHttpsOutcallOptions, } from './pocket-ic-types.js'; import { MANAGEMENT_CANISTER_ID, decodeCreateCanisterResponse, encodeCreateCanisterRequest, encodeInstallCodeRequest, encodeStartCanisterRequest, encodeUpdateCanisterSettingsRequest, } from './management-canister.js'; import { createDeferredActorClass, DeferredActor, } from './pocket-ic-deferred-actor.js'; /** * This class represents the main PocketIC client. * It is responsible for interacting with the PocketIC server via the REST API. * See {@link PocketIcServer} for details on the server to use with this client. * * @category API * * @example * The easist way to use PocketIC is to use {@link setupCanister} convenience method: * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { _SERVICE, idlFactory } from '../declarations'; * * const wasmPath = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const fixture = await pic.setupCanister<_SERVICE>({ idlFactory, wasmPath }); * const { actor } = fixture; * * // perform tests... * * await pic.tearDown(); * await picServer.stop(); * ``` * * If more control is needed, then the {@link createCanister}, {@link installCode} and * {@link createActor} methods can be used directly: * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { _SERVICE, idlFactory } from '../declarations'; * * const wasm = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const canisterId = await pic.createCanister(); * await pic.installCode({ canisterId, wasm }); * const actor = pic.createActor<_SERVICE>({ idlFactory, canisterId }); * * // perform tests... * * await pic.tearDown(); * await picServer.stop(); * ``` */ export class PocketIc { private constructor(private readonly client: PocketIcClient) {} /** * Creates a PocketIC instance. * * @param url The URL of an existing PocketIC server to connect to. * @param options Options for creating the PocketIC instance see {@link CreateInstanceOptions}. * @returns A new PocketIC instance. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const fixture = await pic.setupCanister<_SERVICE>({ idlFactory, wasmPath }); * const { actor } = fixture; * * await pic.tearDown(); * await picServer.stop(); * ``` */ public static async create( url: string, options?: CreateInstanceOptions, ): Promise<PocketIc> { const client = await PocketIcClient.create(url, options); return new PocketIc(client); } /** * A convenience method that creates a new canister, * installs the given WASM module to it and returns a typesafe {@link Actor} * that implements the Candid interface of the canister. * To just create a canister, see {@link createCanister}. * To just install code to an existing canister, see {@link installCode}. * To just create an Actor for an existing canister, see {@link createActor}. * * @param options Options for setting up the canister, see {@link SetupCanisterOptions}. * @returns The {@link Actor} instance. * * @see [Candid](https://internetcomputer.org/docs/current/references/candid-ref) * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { _SERVICE, idlFactory } from '../declarations'; * * const wasmPath = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const fixture = await pic.setupCanister<_SERVICE>({ idlFactory, wasmPath }); * const { actor } = fixture; * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async setupCanister<T extends ActorInterface<T> = ActorInterface>({ sender, arg, wasm, idlFactory, computeAllocation, controllers, cycles, freezingThreshold, memoryAllocation, targetCanisterId, targetSubnetId, reservedCyclesLimit, }: SetupCanisterOptions): Promise<CanisterFixture<T>> { const canisterId = await this.createCanister({ computeAllocation, controllers, cycles, freezingThreshold, memoryAllocation, reservedCyclesLimit, targetCanisterId, targetSubnetId, sender, }); await this.installCode({ canisterId, wasm, arg, sender, targetSubnetId }); const actor = this.createActor<T>(idlFactory, canisterId); return { actor, canisterId }; } /** * Creates a new canister. * For a more convenient way of creating a PocketIC instance, * creating a canister and installing code, see {@link setupCanister}. * * @param options Options for creating the canister, see {@link CreateCanisterOptions}. * @returns The Principal of the newly created canister. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const canisterId = await pic.createCanister(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async createCanister({ sender = Principal.anonymous(), cycles = 1_000_000_000_000_000_000n, controllers, computeAllocation, freezingThreshold, memoryAllocation, reservedCyclesLimit, targetCanisterId, targetSubnetId, }: CreateCanisterOptions = {}): Promise<Principal> { const payload = encodeCreateCanisterRequest({ settings: [ { controllers: optional(controllers), compute_allocation: optional(computeAllocation), memory_allocation: optional(memoryAllocation), freezing_threshold: optional(freezingThreshold), reserved_cycles_limit: optional(reservedCyclesLimit), }, ], amount: [cycles], specified_id: optional(targetCanisterId), }); const res = await this.client.updateCall({ canisterId: MANAGEMENT_CANISTER_ID, sender, method: 'provisional_create_canister_with_cycles', payload, effectivePrincipal: targetSubnetId ? { subnetId: targetSubnetId, } : undefined, }); return decodeCreateCanisterResponse(res.body).canister_id; } /** * Starts the given canister. * * @param options Options for starting the canister, see {@link StartCanisterOptions}. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.startCanister({ canisterId }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async startCanister({ canisterId, sender = Principal.anonymous(), targetSubnetId, }: StartCanisterOptions): Promise<void> { const payload = encodeStartCanisterRequest({ canister_id: canisterId, }); await this.client.updateCall({ canisterId: MANAGEMENT_CANISTER_ID, sender, method: 'start_canister', payload, effectivePrincipal: targetSubnetId ? { subnetId: targetSubnetId, } : undefined, }); } /** * Stops the given canister. * * @param options Options for stopping the canister, see {@link StopCanisterOptions}. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.stopCanister({ canisterId }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async stopCanister({ canisterId, sender = Principal.anonymous(), targetSubnetId, }: StopCanisterOptions): Promise<void> { const payload = encodeStartCanisterRequest({ canister_id: canisterId, }); await this.client.updateCall({ canisterId: MANAGEMENT_CANISTER_ID, sender, method: 'stop_canister', payload, effectivePrincipal: targetSubnetId ? { subnetId: targetSubnetId, } : undefined, }); } /** * Installs the given WASM module to the provided canister. * To create a canister to install code to, see {@link createCanister}. * For a more convenient way of creating a PocketIC instance, * creating a canister and installing code, see {@link setupCanister}. * * @param options Options for installing the code, see {@link InstallCodeOptions}. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { resolve } from 'node:path'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * const wasm = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.installCode({ canisterId, wasm }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async installCode({ arg = new Uint8Array(), sender = Principal.anonymous(), canisterId, wasm, targetSubnetId, }: InstallCodeOptions): Promise<void> { if (typeof wasm === 'string') { wasm = await readFileAsBytes(wasm); } const payload = encodeInstallCodeRequest({ arg: new Uint8Array(arg), canister_id: canisterId, mode: { install: null, }, wasm_module: new Uint8Array(wasm), }); await this.client.updateCall({ canisterId: MANAGEMENT_CANISTER_ID, sender, method: 'install_code', payload, effectivePrincipal: targetSubnetId ? { subnetId: targetSubnetId, } : undefined, }); } /** * Reinstalls the given WASM module to the provided canister. * This will reset both the canister's heap and its stable memory. * To create a canister to upgrade, see {@link createCanister}. * To install the initial WASM module to a new canister, see {@link installCode}. * * @param options Options for reinstalling the code, see {@link ReinstallCodeOptions}. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { resolve } from 'node:path'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * const wasm = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.reinstallCode({ canisterId, wasm }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async reinstallCode({ sender = Principal.anonymous(), arg = new Uint8Array(), canisterId, wasm, }: ReinstallCodeOptions): Promise<void> { if (typeof wasm === 'string') { wasm = await readFileAsBytes(wasm); } const payload = encodeInstallCodeRequest({ arg: new Uint8Array(arg), canister_id: canisterId, mode: { reinstall: null, }, wasm_module: new Uint8Array(wasm), }); await this.client.updateCall({ canisterId: MANAGEMENT_CANISTER_ID, sender, method: 'install_code', payload, }); } /** * Upgrades the given canister with the given WASM module. * This will reset the canister's heap, but preserve stable memory. * To create a canister to upgrade to, see {@link createCanister}. * To install the initial WASM module to a new canister, see {@link installCode}. * * @param options Options for upgrading the canister, see {@link UpgradeCanisterOptions}. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { resolve } from 'node:path'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * const wasm = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.upgradeCanister({ canisterId, wasm }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async upgradeCanister({ sender = Principal.anonymous(), arg = new Uint8Array(), canisterId, wasm, }: UpgradeCanisterOptions): Promise<void> { if (typeof wasm === 'string') { wasm = await readFileAsBytes(wasm); } const payload = encodeInstallCodeRequest({ arg: new Uint8Array(arg), canister_id: canisterId, mode: { upgrade: null, }, wasm_module: new Uint8Array(wasm), }); await this.client.updateCall({ canisterId: MANAGEMENT_CANISTER_ID, sender, method: 'install_code', payload, }); } /** * Updates the settings of the given canister. * * @param options Options for updating the canister settings, see {@link UpdateCanisterSettingsOptions}. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.updateCanisterSettings({ * canisterId, * controllers: [Principal.fromUint8Array(new Uint8Array([1]))], * }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async updateCanisterSettings({ canisterId, computeAllocation, controllers, freezingThreshold, memoryAllocation, reservedCyclesLimit, sender = Principal.anonymous(), }: UpdateCanisterSettingsOptions): Promise<void> { const payload = encodeUpdateCanisterSettingsRequest({ canister_id: canisterId, settings: { controllers: optional(controllers), compute_allocation: optional(computeAllocation), memory_allocation: optional(memoryAllocation), freezing_threshold: optional(freezingThreshold), reserved_cycles_limit: optional(reservedCyclesLimit), }, }); await this.client.updateCall({ canisterId: MANAGEMENT_CANISTER_ID, sender, method: 'update_settings', payload, }); } /** * Creates an {@link Actor} for the given canister. * An {@link Actor} is a typesafe class that implements the Candid interface of a canister. * To create a canister for the {@link Actor}, see {@link createCanister}. * For a more convenient way of creating a PocketIC instance, * creating a canister and installing code, see {@link setupCanister}. * * @param interfaceFactory The InterfaceFactory to use for the {@link Actor}. * @param canisterId The Principal of the canister to create the {@link Actor} for. * @typeparam T The type of the {@link Actor}. Must implement {@link ActorInterface}. * @returns The {@link Actor} instance. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * @see [InterfaceFactory](https://agent-js.icp.xyz/candid/modules/IDL.html#InterfaceFactory) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { _SERVICE, idlFactory } from '../declarations'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * const wasm = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const canisterId = await pic.createCanister(); * await pic.installCode({ canisterId, wasm }); * const actor = pic.createActor<_SERVICE>({ idlFactory, canisterId }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public createActor<T extends ActorInterface<T> = ActorInterface>( interfaceFactory: IDL.InterfaceFactory, canisterId: Principal, ): Actor<T> { const Actor = createActorClass<T>( interfaceFactory, canisterId, this.client, ); return new Actor(); } /** * Creates a {@link DeferredActor} for the given canister. * A {@link DeferredActor} is a typesafe class that implements the Candid interface of a canister. * * A {@link DeferredActor} in contrast to a normal {@link Actor} will submit the call to the PocketIc replica, * but the call will not be executed immediately. Instead, the calls are queued and a `Promise` is returned * by the {@link DeferredActor} that can be awaited to process the pending canister call. * * To create a canister for the {@link DeferredActor}, see {@link createCanister}. * For a more convenient way of creating a PocketIC instance, * creating a canister and installing code, see {@link setupCanister}. * * @param interfaceFactory The InterfaceFactory to use for the {@link DeferredActor}. * @param canisterId The Principal of the canister to create the {@link DeferredActor} for. * @typeparam T The type of the {@link DeferredActor}. Must implement {@link ActorInterface}. * @returns The {@link DeferredActor} instance. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * @see [InterfaceFactory](https://agent-js.icp.xyz/candid/modules/IDL.html#InterfaceFactory) * * @example */ public createDeferredActor<T extends ActorInterface<T> = ActorInterface>( interfaceFactory: IDL.InterfaceFactory, canisterId: Principal, ): DeferredActor<T> { const DeferredActor = createDeferredActorClass<T>( interfaceFactory, canisterId, this.client, ); return new DeferredActor(); } /** * Makes a query call to the given canister. * * @param options Options for making the query call, see {@link QueryCallOptions}. * @returns The Candid-encoded response of the query call. * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { _SERVICE, idlFactory } from '../declarations'; * * const wasm = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * canisterId = await pic.createCanister({ * sender: controllerIdentity.getPrincipal(), * }); * await pic.installCode({ canisterId, wasm }); * * const res = await pic.queryCall({ * canisterId, * method: 'greet', * }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async queryCall({ canisterId, method, arg = new Uint8Array(), sender = Principal.anonymous(), targetSubnetId, }: QueryCallOptions): Promise<ArrayBufferLike> { const res = await this.client.queryCall({ canisterId, method, payload: new Uint8Array(arg), sender, effectivePrincipal: targetSubnetId ? { subnetId: targetSubnetId, } : undefined, }); return res.body; } /** * Makes an update call to the given canister. * * @param options Options for making the update call, see {@link UpdateCallOptions}. * @returns The Candid-encoded response of the update call. * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * import { _SERVICE, idlFactory } from '../declarations'; * * const wasm = resolve('..', '..', 'canister.wasm'); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * canisterId = await pic.createCanister({ * sender: controllerIdentity.getPrincipal(), * }); * await pic.installCode({ canisterId, wasm }); * * const res = await pic.updateCall({ * canisterId, * method: 'greet', * }); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async updateCall({ canisterId, method, arg = new Uint8Array(), sender = Principal.anonymous(), targetSubnetId, }: UpdateCallOptions): Promise<ArrayBufferLike> { const res = await this.client.updateCall({ canisterId, method, payload: new Uint8Array(arg), sender, effectivePrincipal: targetSubnetId ? { subnetId: targetSubnetId, } : undefined, }); return res.body; } /** * Deletes the PocketIC instance. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async tearDown(): Promise<void> { await this.client.deleteInstance(); } /** * Make the IC produce and progress by one block. Accepts a parameter `times` to tick multiple times, * the default is `1`. * * @param times The number of new blocks to produce and progress by. Defaults to `1`. * * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.tick(); * * // or to tick multiple times * await pic.tick(3); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async tick(times: number = 1): Promise<void> { for (let i = 0; i < times; i++) { await this.client.tick(); } } /** * Get the controllers of the specified canister. * * @param canisterId The Principal of the canister to get the controllers of. * @returns The controllers of the specified canister. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const controllers = await pic.getControllers(canisterId); * * await pic.tearDown(); * await picServer.stop(); */ public async getControllers(canisterId: Principal): Promise<Principal[]> { return await this.client.getControllers({ canisterId }); } /** * Get the current time of the IC in milliseconds since the Unix epoch. * * @returns The current time in milliseconds since the UNIX epoch. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const time = await pic.getTime(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async getTime(): Promise<number> { const { millisSinceEpoch } = await this.client.getTime(); return millisSinceEpoch; } /** * Reset the time of the IC to the current time. * {@link tick} should be called after calling this method in order for query calls * and read state request to reflect the new time. * * Use {@link resetCertifiedTime} to set time and immediately have query calls and * read state requests reflect the new time. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.resetTime(); * await pic.tick(); * * const time = await pic.getTime(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async resetTime(): Promise<void> { await this.setTime(Date.now()); } /** * Reset the time of the IC to the current time and immediately have query calls and * read state requests reflect the new time. * * Use {@link resetTime} to reset time without immediately reflecting the new time. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.resetCertifiedTime(); * * const time = await pic.getTime(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async resetCertifiedTime(): Promise<void> { await this.setCertifiedTime(Date.now()); } /** * Set the current time of the IC. * {@link tick} should be called after calling this method in order for query calls * and read state request to reflect the new time. * * Use {@link setCertifiedTime} to set time and immediately have query calls and * read state requests reflect the new time. * * @param time The time to set in milliseconds since the Unix epoch. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const pic = await PocketIc.create(); * * const date = new Date(); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.setTime(date); * // or * await pic.setTime(date.getTime()); * * await pic.tick(); * * const time = await pic.getTime(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async setTime(time: Date | number): Promise<void> { if (time instanceof Date) { time = time.getTime(); } await this.client.setTime({ millisSinceEpoch: time }); } /** * Set the current time of the IC and immediately have query calls and * read state requests reflect the new time. * * Use {@link setTime} to set time without immediately reflecting the new time. * * @param time The time to set in milliseconds since the Unix epoch. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const pic = await PocketIc.create(); * * const date = new Date(); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.setCertifiedTime(date); * // or * await pic.setCertifiedTime(date.getTime()); * * const time = await pic.getTime(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async setCertifiedTime(time: Date | number): Promise<void> { if (time instanceof Date) { time = time.getTime(); } await this.client.setCertifiedTime({ millisSinceEpoch: time }); } /** * Advance the time of the IC by the given duration in milliseconds. * {@link tick} should be called after calling this method in order for query calls * and read state requests to reflect the new time. * * Use {@link advanceCertifiedTime} to advance time and immediately have query calls and * read state requests reflect the new time. * * @param duration The duration to advance the time by. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const initialTime = await pic.getTime(); * await pic.advanceTime(1_000); * await pic.tick(); * * const newTime = await pic.getTime(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async advanceTime(duration: number): Promise<void> { const currentTime = await this.getTime(); const newTime = currentTime + duration; await this.setTime(newTime); } /** * Advance the time of the IC by the given duration in milliseconds and * immediately have query calls and read state requests reflect the new time. * * Use {@link advanceTime} to advance time without immediately reflecting the new time. * * @param duration The duration to advance the time by. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const initialTime = await pic.getTime(); * await pic.advanceCertifiedTime(1_000); * * const newTime = await pic.getTime(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async advanceCertifiedTime(duration: number): Promise<void> { const currentTime = await this.getTime(); const newTime = currentTime + duration; await this.setCertifiedTime(newTime); } /** * Fetch the public key of the specified subnet. * * @param subnetId The Principal of the subnet to fetch the public key of. * @returns The public key of the specified subnet. * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const subnets = pic.getApplicationSubnets(); * const pubKey = await pic.getPubKey(subnets[0].id); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async getPubKey(subnetId: Principal): Promise<ArrayBufferLike> { return await this.client.getPubKey({ subnetId }); } /** * Gets the subnet Id of the provided canister Id. * * @param canisterId The Principal of the canister to get the subnet Id of. * @returns The canister's subnet Id if the canister exists, `null` otherwise. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const subnetId = await pic.getCanisterSubnetId(canisterId); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async getCanisterSubnetId( canisterId: Principal, ): Promise<Principal | null> { const { subnetId } = await this.client.getSubnetId({ canisterId }); return subnetId; } /** * Get the topology of this instance's network. * The topology is a list of subnets, each with a type and a list of canister ID ranges * that can be deployed to that subnet. * The instance network topology is configured via the {@link create} method. * * @returns An array of subnet topologies, see {@link SubnetTopology}. */ public async getTopology(): Promise<SubnetTopology[]> { const topology = await this.client.getTopology(); return Object.values(topology); } /** * Get the Bitcoin subnet topology for this instance's network. * The instance network topology is configured via the {@link create} method. * * @returns The subnet topology for the Bitcoin subnet, * if it exists on this instance's network. */ public async getBitcoinSubnet(): Promise<SubnetTopology | undefined> { const topology = await this.getTopology(); return topology.find(subnet => subnet.type === SubnetType.Bitcoin); } /** * Get the Fiduciary subnet topology for this instance's network. * The instance network topology is configured via the {@link create} method. * * @returns The subnet topology for the Fiduciary subnet, * if it exists on this instance's network. */ public async getFiduciarySubnet(): Promise<SubnetTopology | undefined> { const topology = await this.getTopology(); return topology.find(subnet => subnet.type === SubnetType.Fiduciary); } /** * Get the Internet Identity subnet topology for this instance's network. * The instance network topology is configured via the {@link create} method. * * @returns The subnet topology for the Internet Identity subnet, * if it exists on this instance's network. */ public async getInternetIdentitySubnet(): Promise< SubnetTopology | undefined > { const topology = await this.getTopology(); return topology.find(subnet => subnet.type === SubnetType.InternetIdentity); } /** * Get the NNS subnet topology for this instance's network. * The instance network topology is configured via the {@link create} method. * * @returns The subnet topology for the NNS subnet, * if it exists on this instance's network. */ public async getNnsSubnet(): Promise<SubnetTopology | undefined> { const topology = await this.getTopology(); return topology.find(subnet => subnet.type === SubnetType.NNS); } /** * Get the SNS subnet topology for this instance's network. * The instance network topology is configured via the {@link create} method. * * @returns The subnet topology for the SNS subnet, * if it exists on this instance's network. */ public async getSnsSubnet(): Promise<SubnetTopology | undefined> { const topology = await this.getTopology(); return topology.find(subnet => subnet.type === SubnetType.SNS); } /** * Get all application subnet topologies for this instance's network. * The instance network topology is configured via the {@link create} method. * * @returns An array of subnet topologies for each application subnet * that exists on this instance's network. */ public async getApplicationSubnets(): Promise<SubnetTopology[]> { const topology = await this.getTopology(); return topology.filter(subnet => subnet.type === SubnetType.Application); } /** * Get all system subnet topologies for this instance's network. * The instance network topology is configured via the {@link create} method. * * @returns An array of subnet topologies for each system subnet * that exists on this instance's network. */ public async getSystemSubnets(): Promise<SubnetTopology[]> { const topology = await this.getTopology(); return topology.filter(subnet => subnet.type === SubnetType.System); } /** * Gets the current cycle balance of the specified canister. * * @param canisterId The Principal of the canister to check. * @returns The current cycles balance of the canister. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const cyclesBalance = await pic.getCyclesBalance(canisterId); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async getCyclesBalance(canisterId: Principal): Promise<number> { const { cycles } = await this.client.getCyclesBalance({ canisterId }); return cycles; } /** * Add cycles to the specified canister. * * @param canisterId The Principal of the canister to add cycles to. * @param amount The amount of cycles to add. * @returns The new cycle balance of the canister. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const newCyclesBalance = await pic.addCycles(canisterId, 10_000_000); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async addCycles( canisterId: Principal, amount: number, ): Promise<number> { const { cycles } = await this.client.addCycles({ canisterId, amount }); return cycles; } /** * Set the stable memory of a given canister. * * @param canisterId The Principal of the canister to set the stable memory of. * @param stableMemory A blob containing the stable memory to set. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * const stableMemory = new Uint8Array([0, 1, 2, 3, 4]); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * await pic.setStableMemory(canisterId, stableMemory); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async setStableMemory( canisterId: Principal, stableMemory: ArrayBufferLike, ): Promise<void> { const { blobId } = await this.client.uploadBlob({ blob: new Uint8Array(stableMemory), }); await this.client.setStableMemory({ canisterId, blobId }); } /** * Get the stable memory of a given canister. * * @param canisterId The Principal of the canister to get the stable memory of. * @returns A blob containing the canister's stable memory. * * @see [Principal](https://agent-js.icp.xyz/principal/classes/Principal.html) * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * const stableMemory = await pic.getStableMemory(canisterId); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async getStableMemory( canisterId: Principal, ): Promise<ArrayBufferLike> { const { blob } = await this.client.getStableMemory({ canisterId }); return blob; } /** * Get all pending HTTPS Outcalls across all subnets on this * PocketIC instance. * * @returns An array of pending HTTPS Outcalls. * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * // queue the canister message that will send the HTTPS Outcall * const executeGoogleSearch = await deferredActor.google_search(); * * // tick for two rounds to allow the canister message to be processed * // and for the HTTPS Outcall to be queued * await pic.tick(2); * * // get all queued HTTPS Outcalls * const pendingHttpsOutcalls = await pic.getPendingHttpsOutcalls(); * * // get the first pending HTTPS Outcall * const pendingGoogleSearchOutcall = pendingHttpsOutcalls[0]; * * // mock the HTTPS Outcall * await pic.mockPendingHttpsOutcall({ * requestId: pendingGoogleSearchOutcall.requestId, * subnetId: pendingGoogleSearchOutcall.subnetId, * response: { * type: 'success', * body: new TextEncoder().encode('Google search result'), * statusCode: 200, * headers: [], * }, * }); * * // finish executing the message, including the HTTPS Outcall * const result = await executeGoogleSearch(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async getPendingHttpsOutcalls(): Promise<PendingHttpsOutcall[]> { return await this.client.getPendingHttpsOutcalls(); } /** * Mock a pending HTTPS Outcall. * * @param options Options for mocking the pending HTTPS Outcall, see {@link MockPendingHttpsOutcallOptions}. * * @example * ```ts * import { Principal } from '@icp-sdk/core/principal'; * import { PocketIc, PocketIcServer } from '@dfinity/pic'; * * const canisterId = Principal.fromUint8Array(new Uint8Array([0])); * * const picServer = await PocketIcServer.start(); * const pic = await PocketIc.create(picServer.getUrl()); * * // queue the canister message that will send the HTTPS Outcall * const executeGoogleSearch = await deferredActor.google_search(); * * // tick for two rounds to allow the canister message to be processed * // and for the HTTPS Outcall to be queued * await pic.tick(2); * * // get all queued HTTPS Outcalls * const pendingHttpsOutcalls = await pic.getPendingHttpsOutcalls(); * * // get the first pending HTTPS Outcall * const pendingGoogleSearchOutcall = pendingHttpsOutcalls[0]; * * // mock the HTTPS Outcall * await pic.mockPendingHttpsOutcall({ * requestId: pendingGoogleSearchOutcall.requestId, * subnetId: pendingGoogleSearchOutcall.subnetId, * response: { * type: 'success', * body: new TextEncoder().encode('Google search result'), * statusCode: 200, * headers: [], * }, * }); * * // finish executing the message, including the HTTPS Outcall * const result = await executeGoogleSearch(); * * await pic.tearDown(); * await picServer.stop(); * ``` */ public async mockPendingHttpsOutcall({ requestId, response, subnetId, additionalResponses = [], }: MockPendingHttpsOutcallOptions): Promise<void> { return await this.client.mockPendingHttpsOutcall({ requestId, response, subnetId, additionalResponses, }); } }