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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ jobs:
- run: pnpm install
- run: pnpm build

typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3.6.3
- run: pnpm install
- run: pnpm typecheck

format:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"format": "prettier --write .",
"pretest": "just build-icrc21-canister",
"test": "vitest run",
"typecheck": "tsc --noEmit --skipLibCheck",
"build": "node rmdir.mjs && node esbuild.mjs",
"prepack": "pnpm run build",
"clean": "pnpm run build",
Expand Down
40 changes: 24 additions & 16 deletions src/icrc21-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import type {
SubmitResponse,
CallRequest,
v4ResponseBody,
ApiQueryResponse,
QueryFields,
ReadStateOptions,
ReadStateResponse,
} from "@icp-sdk/core/agent";
import type { JsonObject } from "@icp-sdk/core/candid";
import {
HttpAgent,
AnonymousIdentity,
Expand Down Expand Up @@ -129,7 +134,7 @@ export class Icrc21Agent implements Agent {
});

// Verify the call wasn't rejected by the canister
this.checkForRejection(this.certificateLookup(result));
this.checkForRejection(await this.certificateLookup(result, canisterId));

return result;
}
Expand Down Expand Up @@ -177,7 +182,10 @@ export class Icrc21Agent implements Agent {
(consentMessageResponse.response.body as v4ResponseBody).certificate
);

const lookup = this.certificateLookup(consentMessageResponse, canisterId);
const lookup = await this.certificateLookup(
consentMessageResponse,
canisterId
);
this.checkForRejection(lookup);
this.checkForIcrc21Error(lookup);

Expand All @@ -190,20 +198,20 @@ export class Icrc21Agent implements Agent {
}

/** Returns a lookup function for reading fields from a call response certificate. */
private certificateLookup(
private async certificateLookup(
response: SubmitResponse,
canisterId?: Principal
): (field: string) => Uint8Array | undefined {
canisterId: Principal
): Promise<(field: string) => Uint8Array | undefined> {
const rootKey = this.anonymousAgent.rootKey;
if (!rootKey) return () => undefined;

const body = response.response.body as v4ResponseBody | undefined;
if (!body?.certificate) return () => undefined;

const cert = Certificate.createUnverified({
const cert = await Certificate.create({
certificate: new Uint8Array(body.certificate),
rootKey,
principal: canisterId ?? Principal.fromText("aaaaa-aa"),
principal: { canisterId },
});

const requestId = response.requestId;
Expand Down Expand Up @@ -252,28 +260,28 @@ export class Icrc21Agent implements Agent {
}
}

async status(): Promise<Record<string, unknown>> {
async fetchRootKey(): Promise<Uint8Array> {
throw new Error("Icrc21Agent does not implement fetchRootKey()");
}

async status(): Promise<JsonObject> {
throw new Error("Icrc21Agent does not implement status()");
}

async query(
canisterId: Principal | string,
options: {
methodName: string;
arg: Uint8Array;
effectiveCanisterId?: Principal;
},
options: QueryFields,
identity?: Identity | Promise<Identity>
): Promise<Record<string, unknown>> {
): Promise<ApiQueryResponse> {
throw new Error("Icrc21Agent does not implement query()");
}

async readState(
effectiveCanisterId: Principal | string,
options: { paths: Uint8Array[][] },
options: ReadStateOptions,
identity?: Identity,
request?: unknown
): Promise<Record<string, unknown>> {
): Promise<ReadStateResponse> {
throw new Error("Icrc21Agent does not implement readState()");
}
}
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,7 +810,7 @@ async function claimNeurons() {

async function getNeuron(neuronId: bigint) {
const identity = await getIdentity();
const governance = GovernanceCanister.create({
const governance = NnsGovernanceCanister.create({
agent: await getCurrentAgent(identity),
});
const neuron = await governance.getNeuron({
Expand Down Expand Up @@ -878,10 +878,10 @@ async function icrc21Call({
const certBytes = new Uint8Array(body.certificate);
const rootKey = icrc21Agent.rootKey;
if (rootKey) {
const cert = Certificate.createUnverified({
const cert = await Certificate.create({
certificate: certBytes,
rootKey,
principal: canisterId,
principal: { canisterId },
});
const replyBytes = lookupResultToBuffer(
cert.lookup_path([
Expand Down
24 changes: 13 additions & 11 deletions src/ledger/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class LedgerIdentity extends SignIdentity {
/**
* Connect to a ledger hardware wallet.
*/
private static async _connect(): Promise<[LedgerApp, Transport]> {
private static async _connect(): Promise<[typeof LedgerApp, Transport]> {
async function getTransport() {
if (await TransportWebHID.isSupported()) {
// We're in a web browser.
Expand Down Expand Up @@ -136,7 +136,7 @@ export class LedgerIdentity extends SignIdentity {
}
}
private static async _fetchPublicKeyFromDevice(
app: LedgerApp,
app: typeof LedgerApp,
derivePath: string
): Promise<Secp256k1PublicKey> {
const resp = await app.getAddressAndPubKey(derivePath);
Expand Down Expand Up @@ -175,7 +175,7 @@ export class LedgerIdentity extends SignIdentity {
* and verify the address/pubkey are the same as on the device screen.
*/
public async showAddressAndPubKeyOnDevice(): Promise<void> {
this._executeWithApp(async (app: LedgerApp) => {
this._executeWithApp(async (app: typeof LedgerApp) => {
await app.showAddressAndPubKey(this.derivePath);
});
}
Expand All @@ -184,7 +184,7 @@ export class LedgerIdentity extends SignIdentity {
* @returns The verion of the `Internet Computer' app installed on the Ledger device.
*/
public async getVersion(): Promise<Version> {
return this._executeWithApp(async (app: LedgerApp) => {
return this._executeWithApp(async (app: typeof LedgerApp) => {
const res = await app.getVersion();
if (
isNullish(res.major) ||
Expand All @@ -206,7 +206,7 @@ export class LedgerIdentity extends SignIdentity {
}

public async getSupportedTokens(): Promise<TokenInfo[]> {
return this._executeWithApp(async (app: LedgerApp) => {
return this._executeWithApp(async (app: typeof LedgerApp) => {
const res = await app.tokenRegistry();
if (nonNullish(res.tokenRegistry)) {
return res.tokenRegistry;
Expand All @@ -224,7 +224,7 @@ export class LedgerIdentity extends SignIdentity {
}

public async sign(blob: Uint8Array): Promise<Signature> {
return await this._executeWithApp(async (app: LedgerApp) => {
return await this._executeWithApp(async (app: typeof LedgerApp) => {
const resp: ResponseSign = await app.sign(
this.derivePath,
Buffer.from(blob),
Expand Down Expand Up @@ -258,7 +258,7 @@ export class LedgerIdentity extends SignIdentity {
canisterCall: string,
certificate: string
): Promise<Signature> {
return await this._executeWithApp(async (app: LedgerApp) => {
return await this._executeWithApp(async (app: typeof LedgerApp) => {
const resp: ResponseSign = await app.signBls(
this.derivePath,
consentRequest,
Expand Down Expand Up @@ -312,11 +312,13 @@ export class LedgerIdentity extends SignIdentity {
if (this._icrc21Flag) {
// Use ICRC-21 signing (consent message verification + signature)
const consentRequestHex = bytesToHexString(
Cbor.encode({ content: this._icrc21ConsentMessageRequest })
Array.from(Cbor.encode({ content: this._icrc21ConsentMessageRequest }))
);
const canisterCallHex = bytesToHexString(
Array.from(_prepareCborForLedger(body))
);
const canisterCallHex = bytesToHexString(_prepareCborForLedger(body));
const certificateHex = bytesToHexString(
this._icrc21ConsentMessageResponseCertificate!
Array.from(this._icrc21ConsentMessageResponseCertificate!)
);
try {
signature = await this.signIcrc21(
Expand Down Expand Up @@ -346,7 +348,7 @@ export class LedgerIdentity extends SignIdentity {
}

private async _executeWithApp<T>(
func: (app: LedgerApp) => Promise<T>
func: (app: typeof LedgerApp) => Promise<T>
): Promise<T> {
const [app, transport] = await LedgerIdentity._connect();

Expand Down
5 changes: 3 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "CommonJS",
"module": "preserve",
"target": "esnext",
"lib": ["esnext", "dom"],
"strict": true,
Expand All @@ -9,5 +9,6 @@
"moduleResolution": "bundler",
"outDir": "dist",
"resolveJsonModule": true
}
},
"exclude": ["scripts"]
}
Loading