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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/pic/src/pocket-ic-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -168,6 +169,14 @@ export class PocketIcClient {
return decodeGetTopologyResponse(res);
}

public async getDefaultEffectiveCanisterId(): Promise<Principal> {
this.assertInstanceNotDeleted();

const res = await this.get<EncodedGetTopologyResponse>('/_/topology');

return base64DecodePrincipal(res.default_effective_canister_id.canister_id);
}

public async getTime(): Promise<GetTimeResponse> {
this.assertInstanceNotDeleted();

Expand Down
32 changes: 32 additions & 0 deletions packages/pic/src/pocket-ic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Principal> {
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.
Expand Down
44 changes: 44 additions & 0 deletions packages/pic/tests/src/getDefaultEffectiveCanisterId.spec.ts
Original file line number Diff line number Diff line change
@@ -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());
});
});