What is Computr?

computr is a browser extension wallet for the Internet Computer built around one constraint: the signing key never lives inside the browser. Instead of holding a private key, computr intercepts every update call, shows the exact dfx canister sign command needed, and accepts the signed response pasted back. This makes it usable on machines where a hot wallet would be a liability.

For dApp developers, computr exposes a Plug-compatible API under window.ic.computr. If your dApp already works with Plug, it will work with computr with minimal changes, typically replacing window.ic.plug with window.ic.computr, or checking for both.

How it works

When your dApp makes an update call through a computr actor:

dApp calls actor method
→
Signing popup opens
→
User runs dfx sign externally
→
User pastes signed response
→
computr submits to chain
→
Result returned to dApp

Query calls go through without interruption. The popup only appears for update calls. Because signing happens externally, every update is reviewed individually; there is no whitelist of pre-approved canisters.

Detecting Computr

Check for the extension before calling any API methods. The provider is injected on http:// and https:// pages only, not on extension or local file pages.

if (!window.ic?.computr) {
  // not installed, or page not loaded over HTTP/HTTPS
  return;
}

computr also injects itself as window.ic.plug by default (Plug spoof mode). If your dApp targets Plug, it will already work. When writing new integrations, prefer window.ic.computr so your code is explicit. To support both wallets:

const wallet = window.ic?.computr ?? window.ic?.plug ?? null;
if (!wallet) return;

For the modern wallet discovery approach, see ICRC-94 discovery.

API Reference

All methods are on window.ic.computr and return Promises.

requestConnect

window.ic.computr.requestConnect(params?) → Promise<boolean>

Prompts the user to approve a connection from your dApp's origin. Returns true on approval, throws on rejection. After connecting, principalId and accountId are available on the provider.

Param Type Description
params.host string optional IC network URL. Defaults to https://ic0.app.
const connected = await window.ic.computr.requestConnect();

console.log(window.ic.computr.principalId);  // "abc12-..."
console.log(window.ic.computr.accountId);    // hex account ID

isConnected

window.ic.computr.isConnected() → Promise<boolean>

Returns whether this origin has an active connection. Call this on page load before showing a connect button.

if (!await window.ic.computr.isConnected()) {
  await window.ic.computr.requestConnect();
}

disconnect

window.ic.computr.disconnect() → Promise<void>

Ends the session for this origin and clears principalId and accountId.

await window.ic.computr.disconnect();

getPrincipal

window.ic.computr.getPrincipal(opts?) → Promise<Principal | string | null>

Returns the connected user's principal as a Principal object, or as a plain string with { asString: true }. Returns null if not connected. You can also read window.ic.computr.principalId directly as a synchronous string after connecting.

Param Type Description
opts.asString boolean optional Return as a plain string instead of a Principal object.
const id = await window.ic.computr.getPrincipal({ asString: true });
// "abc12-def34-..."

const p = await window.ic.computr.getPrincipal();
console.log(p.toText());

createActor

window.ic.computr.createActor(params) → Promise<Actor>

Creates a typed actor for a canister. Update calls through this actor open the computr signing popup. Query calls go through immediately without interruption.

Param Type Description
canisterId string required The canister's principal ID.
interfaceFactory IDL.InterfaceFactory required The Candid interface factory, the same one you'd pass to Actor.createActor in @dfinity/agent. computr uses it to decode and display call arguments in the popup. If you have a declarations folder from dfx generate, use the exported idlFactory directly.
const myCanisterIdl = ({ IDL }) => IDL.Service({
  greet:     IDL.Func([IDL.Text], [IDL.Text], ['query']),
  increment: IDL.Func([], [IDL.Nat], []),
});

const actor = await window.ic.computr.createActor({
  canisterId: 'aaaaa-aa',
  interfaceFactory: myCanisterIdl,
});

const greeting = await actor.greet('world');  // query — no popup
const count    = await actor.increment();      // update — popup opens

requestTransfer

window.ic.computr.requestTransfer(params) → Promise<{ height: bigint }>

Sends ICP from the connected identity to a destination principal via icrc1_transfer. Opens the signing popup. Returns the block height on success.

Param Type Description
to string required Destination principal ID as text.
amount bigint required Amount in e8s (1 ICP = 100,000,000 e8s).
opts.fee bigint optional Fee in e8s. Defaults to the standard ICP fee (10,000 e8s).
const { height } = await window.ic.computr.requestTransfer({
  to: 'abc12-def34-...',
  amount: 100_000_000n,  // 1 ICP
});

icrc1Transfer

window.ic.computr.icrc1Transfer(params) → Promise<{ height: bigint }>

Sends any ICRC-1 token by ledger canister ID. Works for ICP, ckBTC, ckETH, or any custom token. Opens the signing popup. Returns the block height on success.

Param Type Description
to.owner Principal | string required Destination principal.
to.subaccount Uint8Array[] optional Destination subaccount (ICRC-1 opt encoding; wrap bytes in an array).
amount bigint required Amount in the token's base unit.
canisterId string optional Ledger canister ID. Defaults to the ICP ledger (ryjl3-tyaaa-aaaaa-aaaba-cai).
fee bigint optional Fee in the token's base unit.
memo Uint8Array optional Arbitrary memo bytes.
// ckBTC
const { height } = await window.ic.computr.icrc1Transfer({
  to: { owner: 'abc12-def34-...' },
  amount: 100_000n,
  canisterId: 'mxzaz-hqaaa-aaaar-qaada-cai',
});

// ICP (canisterId defaults to ICP ledger)
await window.ic.computr.icrc1Transfer({
  to: { owner: recipientPrincipal },
  amount: 50_000_000n,
});

request (ICRC-25/27/49)

window.ic.computr.request(params) → Promise<any>

JSON-RPC 2.0 style interface implementing ICRC-25, ICRC-27, and ICRC-49. An alternative integration path for dApps that speak IC wallet standards natively rather than the Plug-style API.

MethodReturns
icrc25_supported_standardsList of supported standard URIs
icrc25_permissionsGranted permission scopes
icrc27_accounts{accounts: [{owner: principalText}]}
icrc34_delegationRejects — delegation not supported; use external signing
icrc49_call_canister{contentMap, certificate} — executes a signed canister call
// ICRC-27: get connected account
const { accounts } = await window.ic.computr.request({
  method: 'icrc27_accounts'
});
// [{ owner: 'abc12-...' }]

// ICRC-49: call a canister (arg is base64-encoded Candid)
const result = await window.ic.computr.request({
  method: 'icrc49_call_canister',
  params: {
    canisterId: 'aaaaa-aa',
    method:     'increment',
    arg:        'DIDL\x00\x00',
  }
});

Properties

Read-only properties available after a successful requestConnect:

PropertyTypeDescription
principalId string | undefined Connected principal as text. Synchronous; safe to read after connect.
accountId string | undefined Hex-encoded ICP account identifier derived from the principal (64 hex chars).
versions { provider: string, extension: string } Version strings for feature detection. provider tracks the injected API; extension tracks the extension build.
const { provider, extension } = window.ic.computr.versions;
const principal = window.ic.computr.principalId; // "abc12-..."
const account   = window.ic.computr.accountId;   // "a1b2c3..."

Stub methods not implemented

The following methods exist for Plug API compatibility but return dummy data. Do not rely on them for real logic:

MethodReturns
requestBalance()Hardcoded 100000000n (1 ICP). Use the Tokens tab or query the ledger directly.
signMessage(msg)Empty ArrayBuffer — not a real signature.
batchTransactions(txs)Always true without executing anything.
getICNSInfo(){ names: [], reverseResolvedName: undefined }
requestBurnXTC(){ success: true }
requestImportToken(){ success: true }

Guides

Connecting a dApp

Check for an existing connection on page load. Only call requestConnect when the user explicitly asks to connect; calling it on load will open the approval popup immediately, which is jarring.

async function initWallet() {
  const computr = window.ic?.computr;
  if (!computr) return;

  if (await computr.isConnected()) {
    showConnectedState(computr.principalId);
    return;
  }

  document.getElementById('connect-btn').addEventListener('click', async () => {
    try {
      await computr.requestConnect();
      showConnectedState(computr.principalId);
    } catch (e) {
      console.error('Connection rejected:', e);
    }
  });
}

window.addEventListener('load', initWallet);

Calling a canister

Define your canister's Candid interface as an IDL factory, then create an actor. It behaves exactly like one from @dfinity/agent: queries return immediately, updates open the signing popup.

const counterIdl = ({ IDL }) => IDL.Service({
  get:       IDL.Func([], [IDL.Nat], ['query']),
  increment: IDL.Func([], [IDL.Nat], []),
  add:       IDL.Func([IDL.Nat], [IDL.Nat], []),
});

const counter = await window.ic.computr.createActor({
  canisterId: 'aaaaa-aa',
  interfaceFactory: counterIdl,
});

const current = await counter.get();        // query — no popup
const next    = await counter.increment(); // update — popup opens

With generated declarations from dfx generate:

import { idlFactory, canisterId } from './declarations/counter';

const actor = await window.ic.computr.createActor({ canisterId, interfaceFactory: idlFactory });

Sending tokens

Use requestTransfer for ICP, or icrc1Transfer for any ICRC-1 token. Both open the signing popup; if the user cancels, the promise rejects.

// ICP — amount in e8s (1 ICP = 100_000_000)
try {
  const { height } = await window.ic.computr.requestTransfer({
    to: recipientPrincipalText,
    amount: 100_000_000n,
  });
  console.log('Confirmed at block', height);
} catch (e) {
  console.error(e.message); // InsufficientFunds, cancelled, etc.
}
// Any ICRC-1 token
const { height } = await window.ic.computr.icrc1Transfer({
  to: { owner: recipientPrincipalText },
  amount: 10_000_000n,
  canisterId: 'mxzaz-hqaaa-aaaar-qaada-cai', // ckBTC
  fee: 10n,
});

ICRC-94 wallet discovery

computr announces itself via the ICRC-94 standard, which is the recommended way to detect any IC wallet without polling window.ic. Listen for the icrc94:announceProvider event instead of checking window.ic.computr directly:

window.addEventListener('icrc94:announceProvider', (e) => {
  const { uuid, name, rdns, sendMessage } = e.detail;
  // uuid:  "71edc834-bab2-4d59-8860-c36a01fee7b8"
  // name:  "Computr"
  // rdns:  "io.computr.wallet"
  wallets.push(e.detail);
});

// Ask any installed wallets to announce themselves
window.dispatchEvent(new Event('icrc94:requestProvider'));

computr re-announces on every icrc94:requestProvider event, so fire it after adding your listener. The sendMessage function in the event detail is the communication handle for ICRC-49 calls through this discovery path.

Signing timeout

All canister calls wait up to 10 minutes for the user to complete the external signing workflow. Design your UI to reflect this: show a pending state, and handle a rejection if the user cancels the popup. The long timeout is intentional — it accommodates air-gapped workflows where signing may involve manual steps.

Developer Testing Tools

computr exposes features that are especially useful when testing dApp frontend logic and verifying that your backend enforces all critical checks server-side.

Plug wallet spoofing

By default, computr also injects itself as window.ic.plug, making it usable with any dApp that already has Plug wallet integration. This means you can test your Plug-compatible dApp with computr's external signing workflow without any code changes. Toggle this in Settings → General → Spoof as Plug.

Editable args

Before copying the dfx canister sign command, click Edit args in the signing popup to modify the call arguments. The command updates to reflect the changes. Use this to test how your canister handles edge-case inputs, or to verify that critical logic is enforced on-chain rather than only in the frontend.

Response interception

Enable Settings → General → Intercept response to pause after each call and inspect the decoded IC reply before it reaches your dApp. From this screen you can:

These tools make it straightforward to verify that your frontend does not treat wallet-provided data as authoritative. Any critical state change should be verified against the IC backend, not inferred from what the wallet returns.

Call log

Every call is recorded with method, canister, arguments, and decoded response. Badges mark entries where args were edited (A) or the response was overridden (R). Open it from Settings → View Call Logs. The log exports as JSON.