Skip to content
Draft
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
176 changes: 108 additions & 68 deletions src/api/actors.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,130 @@
// A global singleton for our internet computer actors.
import * as Agent from '@dfinity/agent';
import { InterfaceFactory } from '@dfinity/candid/lib/cjs/idl';

import type { All } from './idl/all.d';
import { idlFactory as allIDL } from './idl/all';
import type { CandidUI } from './idl/candid-ui.d';
import { idlFactory as candidIDL } from './idl/candid-ui';
import { sandboxEvalInterface } from '../services/sandbox';
import { mapOptional } from '../services/common';
import type { All } from './idl/all.d';
import { idlFactory as allIDL } from './idl/all';
import DABCanisters from './idl/dab-canisters.did.d';
import { idlFactory as dabCanistersIDL } from './idl/dab-canisters.did';
import DABTokens from './idl/dab-tokens.did.d';
import { idlFactory as dabTokensIDL } from './idl/dab-tokens.did';
import DABNFTs from './idl/dab-nfts.did.d';
import { idlFactory as dabNFTsIDL } from './idl/dab-nfts.did';

/////////////
// Config //
///////////
// TODO: Clean up these error classes. We only user the parent, because introspecting error classes between sandbox and origin would require de/serialization that I don't feel like dealing with.
class CandidInterfaceError extends Error { }

class CanisterExposesNoInterfaceError extends CandidInterfaceError { }


export class ActorHelper {
boundryUrl: URL;
isLocal: boolean;
interfaces: { [key: string]: 'ok' };
constructor(url: URL) {
this.boundryUrl = new URL(url.origin);
this.isLocal = this.boundryUrl.toString().includes('localhost') || this.boundryUrl.toString().includes('127.0.0.1');
this.interfaces = {};
}

const canisters: { [key: string]: string } = {
candidUI: 'a4gq6-oaaaa-aaaab-qaa4q-cai',
dabCanisters: 'curr3-vaaaa-aaaah-abbdq-cai',
dabTokens: 'qwt65-nyaaa-aaaah-qcl4q-cai',
dabNFTs: 'aipdg-waaaa-aaaah-aaq5q-cai',
};
createActor<T>(
canisterId: string,
idl: InterfaceFactory,
config?: Agent.ActorConfig,
): Agent.ActorSubclass<T> {
const agent = new Agent.HttpAgent({ host: this.boundryUrl.toString() });
return Agent.Actor.createActor<T>(idl, { canisterId, agent, ...config });
}

const host = 'https://ic0.app';
const hostLocal = 'http://localhost:8000';
createCandidUIActor(): Agent.ActorSubclass<CandidUI> {
let canisterId = this.isLocal ? 'b77ix-eeaaa-aaaaa-qaada-cai' : 'a4gq6-oaaaa-aaaab-qaa4q-cai';
return this.createActor<CandidUI>(canisterId, candidIDL);
}

////////////
// Agent //
//////////
createDABCansitersActor(): Agent.ActorSubclass<DABCanisters> | undefined {
if (this.isLocal) {
return undefined;
}
let canisterId = 'curr3-vaaaa-aaaah-abbdq-cai';
return this.createActor<DABCanisters>(canisterId, dabCanistersIDL);
}

export const agent = new Agent.HttpAgent({ host });
const agentLocal = new Agent.HttpAgent({ host: hostLocal });
agentLocal.fetchRootKey();
createDABTokensActor(): Agent.ActorSubclass<DABTokens> | undefined {
if (this.isLocal) {
return undefined;
}
let canister = 'qwt65-nyaaa-aaaah-qcl4q-cai';
return this.createActor<DABTokens>(canister, dabTokensIDL);
}

/////////////
// Actors //
///////////
createDABNFTsActor(): Agent.ActorSubclass<DABNFTs> | undefined {
if (this.isLocal) {
return undefined;
}
let canister = 'aipdg-waaaa-aaaah-aaq5q-cai';
return this.createActor<DABNFTs>(canister, dabNFTsIDL);
}

export const candidUI = actor<CandidUI>(canisters.candidUI, candidIDL);
export const dabCanisters = actor<DABCanisters>(
canisters.dabCanisters,
dabCanistersIDL,
);
export const dabTokens = actor<DABTokens>(canisters.dabTokens, dabTokensIDL);
export const dabNFTs = actor<DABNFTs>(canisters.dabNFTs, dabNFTsIDL);
export const canister = generic<All>(allIDL);
/**
* Get interface factory from memory, or attempt to import it. Throws an error if an interface factory can't be imported.
* @deprecated We can't transmit IDLs to and from the sandbox
*/
async getCanisterIDL(
canisterId: string,
): Promise<'ok'> {
if (!(canisterId in this.interfaces)) {
const i = await this.importCandidInterface(canisterId);
if (!i) {
throw new CanisterExposesNoInterfaceError(
`Could not retrieve IDL for canister ${canisterId}`,
);
}
this.interfaces[canisterId] = i;
}
return this.interfaces[canisterId];
}

//////////
// Lib //
////////
/**
* Attempts to import the interface bindings for a given canister, which can be used for effective candid decoding. Currently will not work for Rust canisters (will return undefined).
*/
async importCandidInterface(
canisterId: string,
): Promise<'ok' | undefined> {
const candid = await this.fetchCandidInterface(canisterId);
if (!candid) return undefined;
const js = await this.convertCandidToJavascript(candid);
if (!js) return undefined;
return sandboxEvalInterface(canisterId, this.boundryUrl, js);
}

// Map of existing actors
// const actors: {
// [key: string]: {
// actor: Agent.ActorSubclass<unknown>
// local: Agent.ActorSubclass<unknown>
// idl: InterfaceFactory
// }
// } = {}
/**
* Attempts to retrieve the candid interface for a given canister. Relies on `canister.__get_candid_interface_tmp_hack`, which works for Motoko canisters, but not Rust canisters.
* @returns Candid interface in text format
*/
async fetchCandidInterface(
canisterId: string,
): Promise<string | undefined> {
try {
const canister = this.createActor<All>(canisterId, allIDL);
return await canister.__get_candid_interface_tmp_hack();
} catch (e) {
return undefined;
}
}

// Create an actor.
export function actor<T>(
canisterId: string,
idl: InterfaceFactory,
isLocal = false,
config?: Agent.ActorConfig,
): Agent.ActorSubclass<T> {
const actor =
// (actors[canisterId].actor as Agent.ActorSubclass<T>) ||
Agent.Actor.createActor<T>(idl, { canisterId, agent, ...config });
const local =
// (actors[canisterId].local as Agent.ActorSubclass<T>) ||
Agent.Actor.createActor<T>(idl, {
canisterId,
agent: agentLocal,
...config,
});
// actors[canisterId] = { actor, idl, local }
return isLocal ? local : actor;
}
/**
* Attempts to convert a candid interface definition into a javascript interface definition, using the dfinity Candid UI canister.
* @param candid Candid interface in text format
* @returns Javascript interface in text format
*/
async convertCandidToJavascript(
candid: string,
): Promise<string | undefined> {
const candidUI = this.createCandidUIActor();
return mapOptional(await candidUI.did_to_js(candid));
}

// Create a function that creates an actor using a generic IDL.
function generic<T>(idl: InterfaceFactory, local = false) {
return function (canisterId: string): Agent.ActorSubclass<T> {
return actor<T>(canisterId, idl, local);
};
}
}
9 changes: 6 additions & 3 deletions src/services/candid/decode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ActorHelper } from '../../api/actors';
import { sandboxDecodeCandidArgs, sandboxDecodeCandidVals } from '../sandbox';
import { decodeNoInterface } from './decode-no-interface';
import { CandidInterfaceError, getCanisterIDL } from './interfaces';
import { CandidInterfaceError } from './errors';

export interface CandidDecodeResult {
result: any;
Expand All @@ -14,9 +15,10 @@ export async function decodeCandidArgs(
canisterId: string,
method: string,
data: ArrayBuffer,
actorHelper: ActorHelper
): Promise<CandidDecodeResult> {
try {
await getCanisterIDL(canisterId);
await actorHelper.getCanisterIDL(canisterId);
return {
result: await sandboxDecodeCandidArgs(canisterId, method, data),
withInterface: true,
Expand All @@ -39,9 +41,10 @@ export async function decodeCandidVals(
canisterId: string,
method: string,
data: ArrayBuffer,
actorHelper: ActorHelper
): Promise<CandidDecodeResult> {
try {
await getCanisterIDL(canisterId);
await actorHelper.getCanisterIDL(canisterId);
return {
result: await sandboxDecodeCandidVals(canisterId, method, data),
withInterface: true,
Expand Down
5 changes: 5 additions & 0 deletions src/services/candid/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class CandidInterfaceError extends Error { }

export class CanisterExposesNoInterfaceError extends CandidInterfaceError { }

export class InterfaceMismatchError extends CandidInterfaceError { }
3 changes: 1 addition & 2 deletions src/services/candid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ export {
CandidDecodeResult,
} from './decode';
export {
fetchCandidInterface,
CandidInterfaceError,
InterfaceMismatchError,
CanisterExposesNoInterfaceError,
} from './interfaces';
} from './errors';
67 changes: 0 additions & 67 deletions src/services/candid/interfaces.ts

This file was deleted.

11 changes: 7 additions & 4 deletions src/services/capture/handler.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ActorHelper } from '../../api/actors';
import { DecodedRequest, decodeRequest } from './request';
import { DecodedResponse, decodeResponse } from './response';
import { shouldCapture } from './select';
Expand All @@ -9,17 +10,19 @@ export async function captureInternetComputerMessageFromNetworkEvent(
event: chrome.devtools.network.Request,
): Promise<
| {
request: DecodedRequest;
response: DecodedResponse;
}
request: DecodedRequest;
response: DecodedResponse;
}
| undefined
> {
if (!shouldCapture(event)) return;

console.debug('Full network event stub', event);


const request = await decodeRequest(event);
const response = await decodeResponse(event, request);
const actorHelper = new ActorHelper(request.boundary);
const response = await decodeResponse(event, request, actorHelper);

return { request, response };
}
Loading