diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73208ca..ade8f80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: diff --git a/package.json b/package.json index 76cd9f4..673077d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/icrc21-agent.ts b/src/icrc21-agent.ts index 48b2f6a..20ec967 100644 --- a/src/icrc21-agent.ts +++ b/src/icrc21-agent.ts @@ -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, @@ -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; } @@ -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); @@ -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; @@ -252,28 +260,28 @@ export class Icrc21Agent implements Agent { } } - async status(): Promise> { + async fetchRootKey(): Promise { + throw new Error("Icrc21Agent does not implement fetchRootKey()"); + } + + async status(): Promise { 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 - ): Promise> { + ): Promise { throw new Error("Icrc21Agent does not implement query()"); } async readState( effectiveCanisterId: Principal | string, - options: { paths: Uint8Array[][] }, + options: ReadStateOptions, identity?: Identity, request?: unknown - ): Promise> { + ): Promise { throw new Error("Icrc21Agent does not implement readState()"); } } diff --git a/src/index.ts b/src/index.ts index 3dd621b..446d6b1 100755 --- a/src/index.ts +++ b/src/index.ts @@ -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({ @@ -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([ diff --git a/src/ledger/identity.ts b/src/ledger/identity.ts index d829ee9..cf0f873 100644 --- a/src/ledger/identity.ts +++ b/src/ledger/identity.ts @@ -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. @@ -136,7 +136,7 @@ export class LedgerIdentity extends SignIdentity { } } private static async _fetchPublicKeyFromDevice( - app: LedgerApp, + app: typeof LedgerApp, derivePath: string ): Promise { const resp = await app.getAddressAndPubKey(derivePath); @@ -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 { - this._executeWithApp(async (app: LedgerApp) => { + this._executeWithApp(async (app: typeof LedgerApp) => { await app.showAddressAndPubKey(this.derivePath); }); } @@ -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 { - return this._executeWithApp(async (app: LedgerApp) => { + return this._executeWithApp(async (app: typeof LedgerApp) => { const res = await app.getVersion(); if ( isNullish(res.major) || @@ -206,7 +206,7 @@ export class LedgerIdentity extends SignIdentity { } public async getSupportedTokens(): Promise { - 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; @@ -224,7 +224,7 @@ export class LedgerIdentity extends SignIdentity { } public async sign(blob: Uint8Array): Promise { - 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), @@ -258,7 +258,7 @@ export class LedgerIdentity extends SignIdentity { canisterCall: string, certificate: string ): Promise { - return await this._executeWithApp(async (app: LedgerApp) => { + return await this._executeWithApp(async (app: typeof LedgerApp) => { const resp: ResponseSign = await app.signBls( this.derivePath, consentRequest, @@ -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( @@ -346,7 +348,7 @@ export class LedgerIdentity extends SignIdentity { } private async _executeWithApp( - func: (app: LedgerApp) => Promise + func: (app: typeof LedgerApp) => Promise ): Promise { const [app, transport] = await LedgerIdentity._connect(); diff --git a/tsconfig.json b/tsconfig.json index 697e234..2efe577 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "CommonJS", + "module": "preserve", "target": "esnext", "lib": ["esnext", "dom"], "strict": true, @@ -9,5 +9,6 @@ "moduleResolution": "bundler", "outDir": "dist", "resolveJsonModule": true - } + }, + "exclude": ["scripts"] }