From 1aa9f6f5d902d88304951227673960e0c060598b Mon Sep 17 00:00:00 2001 From: ilbertt Date: Wed, 10 Dec 2025 14:45:26 +0100 Subject: [PATCH] feat: `getDefaultEffectiveCanisterId` method in `PocketIc` class --- packages/pic/src/pocket-ic-client.ts | 11 ++++- packages/pic/src/pocket-ic.ts | 32 ++++++++++++++ .../src/getDefaultEffectiveCanisterId.spec.ts | 44 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 packages/pic/tests/src/getDefaultEffectiveCanisterId.spec.ts diff --git a/packages/pic/src/pocket-ic-client.ts b/packages/pic/src/pocket-ic-client.ts index c139844..9614bb2 100644 --- a/packages/pic/src/pocket-ic-client.ts +++ b/packages/pic/src/pocket-ic-client.ts @@ -81,7 +81,8 @@ import { decodeGetControllersResponse, encodeGetControllersRequest, } from './pocket-ic-client-types'; -import { isNotNil } from './util'; +import { base64DecodePrincipal, isNotNil } from './util'; +import { Principal } from '@dfinity/principal'; const PROCESSING_TIME_VALUE_MS = 30_000; const AWAIT_INGRESS_STATUS_ROUNDS = 100; @@ -168,6 +169,14 @@ export class PocketIcClient { return decodeGetTopologyResponse(res); } + public async getDefaultEffectiveCanisterId(): Promise { + this.assertInstanceNotDeleted(); + + const res = await this.get('/_/topology'); + + return base64DecodePrincipal(res.default_effective_canister_id.canister_id); + } + public async getTime(): Promise { this.assertInstanceNotDeleted(); diff --git a/packages/pic/src/pocket-ic.ts b/packages/pic/src/pocket-ic.ts index 3d8c970..02779aa 100644 --- a/packages/pic/src/pocket-ic.ts +++ b/packages/pic/src/pocket-ic.ts @@ -1105,6 +1105,38 @@ export class PocketIc { return Object.values(topology); } + /** + * Get the default effective canister id for this PocketIC instance. + * This is useful when calling [`IcManagementCanister.provisionalCreateCanisterWithCycles`](https://js.icp.build/canisters/latest/api/ic-management/classes/icmanagementcanister#provisionalcreatecanisterwithcycles) + * on the management canister from `@icp-sdk/canisters/ic-management`. + * + * @returns The default effective canister id. + * + * @see [Principal](https://js.icp.build/core/latest/libs/principal/api/classes/principal/) + * + * @example + * ```ts + * import { PocketIc, PocketIcServer } from '@dfinity/pic'; + * import { IcManagementCanister } from '@icp-sdk/canisters/ic-management'; + * + * const picServer = await PocketIcServer.start(); + * const pic = await PocketIc.create(picServer.getUrl()); + * + * const defaultEffectiveCanisterId = await pic.getDefaultEffectiveCanisterId(); + * + * const managementCanister = IcManagementCanister.create({ agent }); + * const canisterId = await managementCanister.provisionalCreateCanisterWithCycles({ + * canisterId: defaultEffectiveCanisterId, + * }); + * + * await pic.tearDown(); + * await picServer.stop(); + * ``` + */ + public async getDefaultEffectiveCanisterId(): Promise { + return await this.client.getDefaultEffectiveCanisterId(); + } + /** * Get the Bitcoin subnet topology for this instance's network. * The instance network topology is configured via the {@link create} method. diff --git a/packages/pic/tests/src/getDefaultEffectiveCanisterId.spec.ts b/packages/pic/tests/src/getDefaultEffectiveCanisterId.spec.ts new file mode 100644 index 0000000..94d0187 --- /dev/null +++ b/packages/pic/tests/src/getDefaultEffectiveCanisterId.spec.ts @@ -0,0 +1,44 @@ +import { Principal } from '@dfinity/principal'; +import { PocketIc } from '../../src'; + +describe('getDefaultEffectiveCanisterId', () => { + let pic: PocketIc; + + beforeEach(async () => { + pic = await PocketIc.create(process.env.PIC_URL); + }); + + afterEach(async () => { + await pic.tearDown(); + }); + + it('should return a Principal', async () => { + const defaultEffectiveCanisterId = + await pic.getDefaultEffectiveCanisterId(); + + expect(defaultEffectiveCanisterId).toBeInstanceOf(Principal); + }); + + it('should return a canister id within a subnet canister range', async () => { + const defaultEffectiveCanisterId = + await pic.getDefaultEffectiveCanisterId(); + const topology = await pic.getTopology(); + + const isWithinRange = topology.some(subnet => + subnet.canisterRanges.some( + range => + defaultEffectiveCanisterId.gtEq(range.start) && + defaultEffectiveCanisterId.ltEq(range.end), + ), + ); + + expect(isWithinRange).toBe(true); + }); + + it('should return a consistent canister id across multiple calls', async () => { + const firstCall = await pic.getDefaultEffectiveCanisterId(); + const secondCall = await pic.getDefaultEffectiveCanisterId(); + + expect(firstCall.toText()).toEqual(secondCall.toText()); + }); +});