Computr · Developer Docs
Integrate your Internet Computer dApp with Computr wallet interface
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:
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
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
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
Ends the session for this origin and clears
principalId and accountId.
await window.ic.computr.disconnect();
getPrincipal
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
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
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
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)
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.
| Method | Returns |
|---|---|
| icrc25_supported_standards | List of supported standard URIs |
| icrc25_permissions | Granted permission scopes |
| icrc27_accounts | {accounts: [{owner: principalText}]} |
| icrc34_delegation | Rejects — 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:
| Property | Type | Description |
|---|---|---|
| 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:
| Method | Returns |
|---|---|
| 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:
- Edit response: modify the IC's actual reply before returning it to the dApp
- Fabricate response: send a completely synthetic reply to the dApp without hitting the IC at all
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.