wallet-integration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWallet Integration
钱包集成
What This Is
内容概述
Wallet integration on the Internet Computer uses the ICRC signer standards — a popup-based model where every action requires explicit user approval via JSON-RPC 2.0 over .
window.postMessageThis skill covers integration using . Other integration paths (IdentityKit, signer-js) exist but are not covered here.
@dfinity/oisy-wallet-signerThe signer model = explicit per-action approval. establishes a channel. Nothing more.
connect()It is not:
- A session system
- A delegated identity (no ICRC-34)
- A background executor
ICRC standards implemented:
- ICRC-21 — Canister call consent messages
- ICRC-25 — Signer interaction standard (permissions)
- ICRC-27 — Accounts
- ICRC-29 — Window PostMessage transport
- ICRC-49 — Call canister
Not implemented:
- ICRC-46 — Session-based delegation (not supported; use a delegation-capable model if you need sessions)
在Internet Computer上集成钱包需遵循ICRC签名器标准——这是一种基于弹窗的模型,每个操作都需要用户通过JSON-RPC 2.0 over 进行明确批准。
window.postMessage本文档介绍基于的集成方案。其他集成路径(如IdentityKit、signer-js)不在本文讨论范围内。
@dfinity/oisy-wallet-signer签名器模型 = 逐操作明确批准。仅用于建立通信通道,无其他作用。
connect()本方案不是:
- 会话系统
- 委托身份(不支持ICRC-34)
- 后台执行器
已实现的ICRC标准:
- ICRC-21 — 容器调用同意消息
- ICRC-25 — 签名器交互标准(权限)
- ICRC-27 — 账户
- ICRC-29 — Window PostMessage传输
- ICRC-49 — 调用容器
未实现的标准:
- ICRC-46 — 基于会话的委托(不支持;如需会话功能,请使用支持委托的模型)
When to Use
适用场景
- Clear, intentional, high-value actions: token transfers (ICP / ICRC-1 / ICRC-2), NFT mint/claim, single approvals
- Funding / deposit flows: "Top up", "Deposit into protocol"
- Any action where a confirmation dialogue per operation feels natural
- 清晰、明确的高价值操作:代币转账(ICP / ICRC-1 / ICRC-2)、NFT铸造/申领、单次审批
- 充值/存款流程:“充值”、“存入协议”
- 任何需要逐操作确认对话框的场景
When NOT to Use
不适用场景
- Delegation or sessions: sign once / act many times, background execution, autonomous behaviour
- High-frequency interactions: games, social actions, rapid write operations
- Invisible writes: autosave, cron jobs, auto-compounding
Decision test: If your app still feels good when every meaningful update shows a confirmation dialogue, this library is appropriate. If not, use a delegation-capable model instead.
- 委托或会话:一次签名/多次操作、后台执行、自主行为
- 高频交互:游戏、社交操作、快速写入操作
- 隐形写入:自动保存、定时任务、自动复利
决策测试:如果你的应用在每次重要更新时显示确认对话框仍能保持良好体验,那么本库是合适的选择。否则,请使用支持委托的模型。
Prerequisites
前置条件
- installed
@dfinity/oisy-wallet-signer - Peer dependencies installed: ,
@dfinity/utils,@dfinity/zod-schemas,@icp-sdk/canisters,@icp-sdk/corezod - A non-anonymous identity on the signer side (e.g. )
Ed25519KeyIdentity - For local development: a running local network ()
icp network start -d
bash
npm i @dfinity/oisy-wallet-signer @dfinity/utils @dfinity/zod-schemas @icp-sdk/canisters @icp-sdk/core zod- 已安装
@dfinity/oisy-wallet-signer - 已安装 peer 依赖:、
@dfinity/utils、@dfinity/zod-schemas、@icp-sdk/canisters、@icp-sdk/corezod - 签名器侧具备非匿名身份(如)
Ed25519KeyIdentity - 本地开发环境:运行中的本地网络()
icp network start -d
bash
npm i @dfinity/oisy-wallet-signer @dfinity/utils @dfinity/zod-schemas @icp-sdk/canisters @icp-sdk/core zodHow It Works
工作原理
End-to-End Lifecycle
端到端生命周期
text
1. dApp: IcrcWallet.connect({url}) → opens popup, polls icrc29_status
2. dApp: wallet.requestPermissionsNotGranted() → prompts user if needed
3. dApp: wallet.accounts() → signer prompts, returns accounts
4. dApp: wallet.transfer({...}) → signer fetches ICRC-21 consent message
→ signer prompts user with consent
→ signer executes canister call
→ returns block index
5. dApp: wallet.disconnect() → closes popup, cleans uptext
1. dApp: IcrcWallet.connect({url}) → 打开弹窗,轮询icrc29_status
2. dApp: wallet.requestPermissionsNotGranted() → 必要时提示用户
3. dApp: wallet.accounts() → 签名器提示用户,返回账户信息
4. dApp: wallet.transfer({...}) → 签名器获取ICRC-21同意消息
→ 签名器向用户展示同意提示
→ 签名器执行容器调用
→ 返回区块索引
5. dApp: wallet.disconnect() → 关闭弹窗,清理资源Pitfalls
常见陷阱
-
Importing classes from the wrong entry point.,
Signer,RelyingParty, andIcpWalletare not exported from the main entry point. Import them from their dedicated subpaths or you getIcrcWallet.undefinedtypescript// WRONG — will fail import {Signer} from '@dfinity/oisy-wallet-signer'; // CORRECT import {Signer} from '@dfinity/oisy-wallet-signer/signer'; import {IcpWallet} from '@dfinity/oisy-wallet-signer/icp-wallet'; import {IcrcWallet} from '@dfinity/oisy-wallet-signer/icrc-wallet'; -
Usingwithout
IcrcWallet. UnlikeledgerCanisterId(which defaults to the ICP ledgerIcpWallet),ryjl3-tyaaa-aaaaa-aaaba-cai,IcrcWallet.transfer(), and.approve()all require.transferFrom(). Omitting it causes a runtime error.ledgerCanisterId -
Forgetting to register prompts on the signer side. The signer returns error 501 () if a request arrives and no prompt handler is registered for it. Register all four prompts (
PERMISSIONS_PROMPT_NOT_REGISTERED,ICRC25_REQUEST_PERMISSIONS,ICRC27_ACCOUNTS,ICRC21_CALL_CONSENT_MESSAGE) before the signer can handle any relying party traffic.ICRC49_CALL_CANISTER -
Sending concurrent requests to the signer. The signer processes one request at a time. A second request while one is in-flight returns error 503 (). Serialize your calls — wait for each response before sending the next. Read-only methods (
BUSY,icrc29_status) are exempt.icrc25_supported_standards -
Assuming= authenticated session.
connect()only opens aconnect()channel. The user has not pre-authorized anything. Permissions default topostMessage— the signer will prompt the user on first use of each method. Callask_on_useafter connecting to request all permissions upfront in a single prompt instead of per-method prompts.requestPermissionsNotGranted() -
Not handling the consent message state machine. Theprompt fires multiple times with different statuses:
ICRC21_CALL_CONSENT_MESSAGE→loading|result. If you only handleerror, the UI breaks on loading and error states. Always branch onresult.payload.status -
not matching
sender. The signer validates thatownerin everysenderrequest matches the signer'sicrc49_call_canisteridentity. A mismatch returns error 502 (owner). Always use theSENDER_NOT_ALLOWEDfromowner.accounts() -
Not calling. Both
disconnect()andSigner.disconnect()must be called on clean-up. Forgetting this leaks event listeners and leaves popup windows open.wallet.disconnect() -
Ignoring permission expiration. Permissions default to a 7-day validity period. After expiry, they silently revert to. Don't cache permission state client-side beyond a session.
ask_on_use -
Auto-triggering signing on connect. Never fire a canister call immediately after. Let the user initiate the action. The signer is designed for intentional, user-driven operations.
connect()
-
从错误的入口点导入类。、
Signer、RelyingParty和IcpWallet不从主入口点导出。需从其专用子路径导入,否则会得到IcrcWallet。undefinedtypescript// 错误写法 — 会失败 import {Signer} from '@dfinity/oisy-wallet-signer'; // 正确写法 import {Signer} from '@dfinity/oisy-wallet-signer/signer'; import {IcpWallet} from '@dfinity/oisy-wallet-signer/icp-wallet'; import {IcrcWallet} from '@dfinity/oisy-wallet-signer/icrc-wallet'; -
使用时未传入
IcrcWallet。与ledgerCanisterId(默认使用ICP账本IcpWallet)不同,ryjl3-tyaaa-aaaaa-aaaba-cai、IcrcWallet.transfer()和.approve()都必须传入.transferFrom()。省略该参数会导致运行时错误。ledgerCanisterId -
忘记在签名器侧注册提示处理程序。如果请求到达时没有为其注册提示处理程序,签名器会返回错误501()。在签名器处理任何依赖方请求之前,需注册所有四个提示(
PERMISSIONS_PROMPT_NOT_REGISTERED、ICRC25_REQUEST_PERMISSIONS、ICRC27_ACCOUNTS、ICRC21_CALL_CONSENT_MESSAGE)。ICRC49_CALL_CANISTER -
向签名器发送并发请求。签名器一次只能处理一个请求。如果在一个请求处理过程中发送第二个请求,会返回错误503()。请序列化调用——等待每个响应返回后再发送下一个请求。只读方法(
BUSY、icrc29_status)除外。icrc25_supported_standards -
认为等同于已认证会话。
connect()仅打开一个connect()通道,用户并未预先授权任何操作。权限默认为postMessage——签名器会在首次使用每个方法时提示用户。可在连接后调用ask_on_use,一次性请求所有权限,而非逐方法提示。requestPermissionsNotGranted() -
未处理同意消息状态机。提示会多次触发,状态包括:
ICRC21_CALL_CONSENT_MESSAGE→loading|result。如果仅处理error状态,UI在加载和错误状态下会崩溃。请始终根据result进行分支处理。payload.status -
与
sender不匹配。签名器会验证每个owner请求中的icrc49_call_canister是否与签名器的sender身份匹配。不匹配会返回错误502(owner)。请始终使用SENDER_NOT_ALLOWED返回的accounts()。owner -
未调用。在清理时必须同时调用
disconnect()和Signer.disconnect()。忘记调用会导致事件监听器泄漏,并使弹窗窗口保持打开状态。wallet.disconnect() -
忽略权限过期。权限默认有效期为7天。过期后,权限会自动恢复为。请勿在客户端缓存权限状态超过一个会话周期。
ask_on_use -
连接后自动触发签名。切勿在后立即触发容器调用,应让用户主动发起操作。签名器专为用户驱动的明确操作设计。
connect()
Implementation
实现步骤
Import Map
导入映射
typescript
// Constants, errors, and types — from main entry point
import {
ICRC25_REQUEST_PERMISSIONS,
ICRC25_PERMISSION_GRANTED,
ICRC25_PERMISSION_DENIED,
ICRC25_PERMISSION_ASK_ON_USE,
ICRC27_ACCOUNTS,
ICRC21_CALL_CONSENT_MESSAGE,
ICRC49_CALL_CANISTER,
DEFAULT_SIGNER_WINDOW_CENTER,
DEFAULT_SIGNER_WINDOW_TOP_RIGHT,
RelyingPartyResponseError,
RelyingPartyDisconnectedError
} from '@dfinity/oisy-wallet-signer';
import type {
PermissionsPromptPayload,
AccountsPromptPayload,
ConsentMessagePromptPayload,
CallCanisterPromptPayload,
IcrcAccounts,
SignerOptions,
RelyingPartyOptions
} from '@dfinity/oisy-wallet-signer';
// Classes — from dedicated subpaths
import {Signer} from '@dfinity/oisy-wallet-signer/signer';
import {RelyingParty} from '@dfinity/oisy-wallet-signer/relying-party';
import {IcpWallet} from '@dfinity/oisy-wallet-signer/icp-wallet';
import {IcrcWallet} from '@dfinity/oisy-wallet-signer/icrc-wallet';typescript
// 常量、错误和类型 — 从主入口点导入
import {
ICRC25_REQUEST_PERMISSIONS,
ICRC25_PERMISSION_GRANTED,
ICRC25_PERMISSION_DENIED,
ICRC25_PERMISSION_ASK_ON_USE,
ICRC27_ACCOUNTS,
ICRC21_CALL_CONSENT_MESSAGE,
ICRC49_CALL_CANISTER,
DEFAULT_SIGNER_WINDOW_CENTER,
DEFAULT_SIGNER_WINDOW_TOP_RIGHT,
RelyingPartyResponseError,
RelyingPartyDisconnectedError
} from '@dfinity/oisy-wallet-signer';
import type {
PermissionsPromptPayload,
AccountsPromptPayload,
ConsentMessagePromptPayload,
CallCanisterPromptPayload,
IcrcAccounts,
SignerOptions,
RelyingPartyOptions
} from '@dfinity/oisy-wallet-signer';
// 类 — 从专用子路径导入
import {Signer} from '@dfinity/oisy-wallet-signer/signer';
import {RelyingParty} from '@dfinity/oisy-wallet-signer/relying-party';
import {IcpWallet} from '@dfinity/oisy-wallet-signer/icp-wallet';
import {IcrcWallet} from '@dfinity/oisy-wallet-signer/icrc-wallet';dApp Side (Relying Party)
dApp侧(依赖方)
Choosing the Right Class
选择合适的类
| Class | Use for |
|---|---|
| ICP ledger operations — |
| Any ICRC ledger — |
| Low-level custom canister calls via protected |
| 类名 | 适用场景 |
|---|---|
| ICP账本操作 — |
| 任何ICRC账本 — |
| 通过受保护的 |
Connect, Permissions, Accounts
连接、权限与账户
typescript
const wallet = await IcrcWallet.connect({
url: 'https://your-wallet.example.com/sign', // URL of the wallet implementing the signer
host: 'https://icp-api.io',
windowOptions: {width: 576, height: 625, position: 'center'},
connectionOptions: {timeoutInMilliseconds: 120_000},
onDisconnect: () => {
/* wallet popup closed */
}
});
const {allPermissionsGranted} = await wallet.requestPermissionsNotGranted();
const accounts = await wallet.accounts();
const {owner} = accounts[0];typescript
const wallet = await IcrcWallet.connect({
url: 'https://your-wallet.example.com/sign', // 实现签名器的钱包URL
host: 'https://icp-api.io',
windowOptions: {width: 576, height: 625, position: 'center'},
connectionOptions: {timeoutInMilliseconds: 120_000},
onDisconnect: () => {
/* 钱包弹窗已关闭 */
}
});
const {allPermissionsGranted} = await wallet.requestPermissionsNotGranted();
const accounts = await wallet.accounts();
const {owner} = accounts[0];IcpWallet — ICP Transfers and Approvals
IcpWallet — ICP转账与授权
Uses — no needed.
{owner, request}ledgerCanisterIdtypescript
const wallet = await IcpWallet.connect({url: 'https://your-wallet.example.com/sign'});
const accounts = await wallet.accounts();
const {owner} = accounts[0];
await wallet.icrc1Transfer({
owner,
request: {to: {owner: recipientPrincipal, subaccount: []}, amount: 100_000_000n}
});
await wallet.icrc2Approve({
owner,
request: {spender: {owner: spenderPrincipal, subaccount: []}, amount: 500_000_000n}
});使用 — 无需。
{owner, request}ledgerCanisterIdtypescript
const wallet = await IcpWallet.connect({url: 'https://your-wallet.example.com/sign'});
const accounts = await wallet.accounts();
const {owner} = accounts[0];
await wallet.icrc1Transfer({
owner,
request: {to: {owner: recipientPrincipal, subaccount: []}, amount: 100_000_000n}
});
await wallet.icrc2Approve({
owner,
request: {spender: {owner: spenderPrincipal, subaccount: []}, amount: 500_000_000n}
});IcrcWallet — Any ICRC Ledger
IcrcWallet — 任意ICRC账本
Uses — is required.
{owner, ledgerCanisterId, params}ledgerCanisterIdtypescript
const wallet = await IcrcWallet.connect({url: 'https://your-wallet.example.com/sign'});
const accounts = await wallet.accounts();
const {owner} = accounts[0];
await wallet.transfer({
owner,
ledgerCanisterId: 'mxzaz-hqaaa-aaaar-qaada-cai',
params: {to: {owner: recipientPrincipal, subaccount: []}, amount: 1_000_000n}
});
await wallet.approve({
owner,
ledgerCanisterId: 'mxzaz-hqaaa-aaaar-qaada-cai',
params: {spender: {owner: spenderPrincipal, subaccount: []}, amount: 5_000_000n}
});
await wallet.transferFrom({
owner,
ledgerCanisterId: 'mxzaz-hqaaa-aaaar-qaada-cai',
params: {from: {owner: fromPrincipal, subaccount: []}, to: {owner: toPrincipal, subaccount: []}, amount: 1_000_000n}
});使用 — 必填。
{owner, ledgerCanisterId, params}ledgerCanisterIdtypescript
const wallet = await IcrcWallet.connect({url: 'https://your-wallet.example.com/sign'});
const accounts = await wallet.accounts();
const {owner} = accounts[0];
await wallet.transfer({
owner,
ledgerCanisterId: 'mxzaz-hqaaa-aaaar-qaada-cai',
params: {to: {owner: recipientPrincipal, subaccount: []}, amount: 1_000_000n}
});
await wallet.approve({
owner,
ledgerCanisterId: 'mxzaz-hqaaa-aaaar-qaada-cai',
params: {spender: {owner: spenderPrincipal, subaccount: []}, amount: 5_000_000n}
});
await wallet.transferFrom({
owner,
ledgerCanisterId: 'mxzaz-hqaaa-aaaar-qaada-cai',
params: {from: {owner: fromPrincipal, subaccount: []}, to: {owner: toPrincipal, subaccount: []}, amount: 1_000_000n}
});Query Methods and Disconnect
查询方法与断开连接
typescript
const standards = await wallet.supportedStandards();
const currentPermissions = await wallet.permissions();
await wallet.disconnect();typescript
const standards = await wallet.supportedStandards();
const currentPermissions = await wallet.permissions();
await wallet.disconnect();Error Handling (dApp Side)
错误处理(dApp侧)
typescript
try {
await wallet.transfer({...});
} catch (err) {
if (err instanceof RelyingPartyResponseError) {
switch (err.code) {
case 3000: /* PERMISSION_NOT_GRANTED */ break;
case 3001: /* ACTION_ABORTED — user rejected */ break;
case 4000: /* NETWORK_ERROR */ break;
}
}
if (err instanceof RelyingPartyDisconnectedError) {
/* popup closed unexpectedly */
}
}typescript
try {
await wallet.transfer({...});
} catch (err) {
if (err instanceof RelyingPartyResponseError) {
switch (err.code) {
case 3000: /* PERMISSION_NOT_GRANTED */ break;
case 3001: /* ACTION_ABORTED — 用户拒绝 */ break;
case 4000: /* NETWORK_ERROR */ break;
}
}
if (err instanceof RelyingPartyDisconnectedError) {
/* 弹窗意外关闭 */
}
}Wallet Side (Signer)
钱包侧(签名器)
Initialise and Register All Prompts
初始化并注册所有提示处理程序
typescript
const signer = Signer.init({
owner: identity,
host: 'https://icp-api.io',
sessionOptions: {
sessionPermissionExpirationInMilliseconds: 7 * 24 * 60 * 60 * 1000
}
});
signer.register({
method: ICRC25_REQUEST_PERMISSIONS,
prompt: ({requestedScopes, confirm, origin}: PermissionsPromptPayload) => {
confirm(
requestedScopes.map(({scope}) => ({
scope,
state: userApproved ? ICRC25_PERMISSION_GRANTED : ICRC25_PERMISSION_DENIED
}))
);
}
});
signer.register({
method: ICRC27_ACCOUNTS,
prompt: ({approve, reject, origin}: AccountsPromptPayload) => {
approve([{owner: identity.getPrincipal().toText()}]);
}
});
signer.register({
method: ICRC21_CALL_CONSENT_MESSAGE,
prompt: (payload: ConsentMessagePromptPayload) => {
if (payload.status === 'loading') {
// show spinner
} else if (payload.status === 'result') {
// payload.consentInfo: { Ok: ... } (from canister) or { Warn: ... } (signer-generated fallback)
// show consent UI, then: payload.approve() or payload.reject()
} else if (payload.status === 'error') {
// show error, optionally payload.details
}
}
});
signer.register({
method: ICRC49_CALL_CANISTER,
prompt: (payload: CallCanisterPromptPayload) => {
if (payload.status === 'executing') {
/* show progress */
} else if (payload.status === 'result') {
/* call succeeded */
} else if (payload.status === 'error') {
/* call failed */
}
}
});
typescript
const signer = Signer.init({
owner: identity,
host: 'https://icp-api.io',
sessionOptions: {
sessionPermissionExpirationInMilliseconds: 7 * 24 * 60 * 60 * 1000
}
});
signer.register({
method: ICRC25_REQUEST_PERMISSIONS,
prompt: ({requestedScopes, confirm, origin}: PermissionsPromptPayload) => {
confirm(
requestedScopes.map(({scope}) => ({
scope,
state: userApproved ? ICRC25_PERMISSION_GRANTED : ICRC25_PERMISSION_DENIED
}))
);
}
});
signer.register({
method: ICRC27_ACCOUNTS,
prompt: ({approve, reject, origin}: AccountsPromptPayload) => {
approve([{owner: identity.getPrincipal().toText()}]);
}
});
signer.register({
method: ICRC21_CALL_CONSENT_MESSAGE,
prompt: (payload: ConsentMessagePromptPayload) => {
if (payload.status === 'loading') {
// 显示加载状态
} else if (payload.status === 'result') {
// payload.consentInfo: { Ok: ... }(来自容器,已验证)或 { Warn: ... }(签名器生成的回退信息)
// 显示同意UI,然后调用 payload.approve() 或 payload.reject()
} else if (payload.status === 'error') {
// 显示错误信息,可选择展示 payload.details
}
}
});
signer.register({
method: ICRC49_CALL_CANISTER,
prompt: (payload: CallCanisterPromptPayload) => {
if (payload.status === 'executing') {
/* 显示进度 */
} else if (payload.status === 'result') {
/* 调用成功 */
} else if (payload.status === 'error') {
/* 调用失败 */
}
}
});
Consent Message: Ok
vs Warn
OkWarn同意消息:Ok
vs Warn
OkWarn- — canister implements ICRC-21; message is canister-verified
{ Ok: consentInfo } - — signer generated a fallback (for
{ Warn: { consentInfo, canisterId, method, arg } },icrc1_transfer,icrc2_approve)icrc2_transfer_from
Always distinguish these in the UI — warn the user when the message is signer-generated.
- — 容器已实现ICRC-21;消息已通过容器验证
{ Ok: consentInfo } - — 签名器生成的回退信息(适用于
{ Warn: { consentInfo, canisterId, method, arg } }、icrc1_transfer、icrc2_approve)icrc2_transfer_from
请在UI中明确区分这两种状态——当消息由签名器生成时,需向用户发出警告。
Disconnect
断开连接
typescript
signer.disconnect();typescript
signer.disconnect();Error Code Reference
错误码参考
| Code | Name | Meaning |
|---|---|---|
| 500 | | Origin mismatch |
| 501 | | Missing prompt handler |
| 502 | | |
| 503 | | Concurrent request rejected |
| 504 | | Owner identity not set |
| 1000 | | Catch-all |
| 2000 | | Method not supported |
| 3000 | | Permission denied |
| 3001 | | User cancelled |
| 4000 | | IC call failure |
| 错误码 | 名称 | 含义 |
|---|---|---|
| 500 | | 来源不匹配 |
| 501 | | 缺少提示处理程序 |
| 502 | | |
| 503 | | 并发请求被拒绝 |
| 504 | | 未设置所有者身份 |
| 1000 | | 通用错误 |
| 2000 | | 方法不支持 |
| 3000 | | 权限被拒绝 |
| 3001 | | 用户已取消 |
| 4000 | | IC调用失败 |
Permission States
权限状态
| State | Constant | Behavior |
|---|---|---|
| Granted | | Proceeds without prompting |
| Denied | | Rejected immediately (error 3000) |
| Ask on use | | Prompts user on access (default) |
Permissions stored in as with timestamps. Default validity: 7 days.
localStorageoisy_signer_{origin}_{owner}| 状态 | 常量 | 行为 |
|---|---|---|
| 已授权 | | 无需提示直接执行 |
| 已拒绝 | | 立即拒绝(错误码3000) |
| 使用时询问 | | 首次访问时提示用户(默认) |
权限以为键存储在中,并包含时间戳。默认有效期:7天。
oisy_signer_{origin}_{owner}localStorageDeploy & Test
部署与测试
Local Development — Your Own Signer
本地开发 — 自定义签名器
If you are building both the dApp and the wallet/signer, start a local network and pass to both sides:
hostbash
icp network start -dtypescript
// dApp side — point to your local wallet's /sign route
const wallet = await IcrcWallet.connect({
url: 'http://localhost:5174/sign',
host: 'http://localhost:8000'
});
// Wallet/signer side — same local network host
const signer = Signer.init({
owner: identity,
host: 'http://localhost:8000'
});如果同时开发dApp和钱包/签名器,请启动本地网络,并在两侧传入参数:
hostbash
icp network start -dtypescript
// dApp侧 — 指向本地钱包的/sign路由
const wallet = await IcrcWallet.connect({
url: 'http://localhost:5174/sign',
host: 'http://localhost:8000'
});
// 钱包/签名器侧 — 使用相同的本地网络host
const signer = Signer.init({
owner: identity,
host: 'http://localhost:8000'
});Local Development — Using the Pseudo Wallet Signer
本地开发 — 使用伪钱包签名器
If you are building a dApp (relying party) and need a signer to test against locally, the library provides a pseudo wallet signer in its demo:
bash
git clone https://github.com/dfinity/oisy-wallet-signer
cd oisy-wallet-signer
npm ci
cd demo
npm ci
npm run sync:all
npm run dev:wallet # starts the pseudo wallet on port 5174Then connect from your dApp:
typescript
const wallet = await IcpWallet.connect({
url: 'http://localhost:5174/sign',
host: 'http://localhost:8000' // match your local network port
});如果仅开发dApp(依赖方),需要一个签名器进行本地测试,本库在demo中提供了伪钱包签名器:
bash
git clone https://github.com/dfinity/oisy-wallet-signer
cd oisy-wallet-signer
npm ci
cd demo
npm ci
npm run sync:all
npm run dev:wallet # 在端口5174启动伪钱包然后从dApp连接:
typescript
const wallet = await IcpWallet.connect({
url: 'http://localhost:5174/sign',
host: 'http://localhost:8000' // 与本地网络端口保持一致
});Mainnet
主网部署
On mainnet, point to the wallet's production signer URL and omit (defaults to ):
hosthttps://icp-api.iotypescript
const wallet = await IcpWallet.connect({
url: 'https://your-wallet.example.com/sign'
});在主网环境中,指向钱包的生产签名器URL,并省略参数(默认值为):
hosthttps://icp-api.iotypescript
const wallet = await IcpWallet.connect({
url: 'https://your-wallet.example.com/sign'
});Expected Behavior
预期行为
Connection
连接
- resolves with a wallet instance; throws
connect()on timeoutRelyingPartyDisconnectedError - returns an array containing at least ICRC-21, ICRC-25, ICRC-27, ICRC-29, ICRC-49
wallet.supportedStandards()
- 会返回钱包实例;超时会抛出
connect()RelyingPartyDisconnectedError - 返回的数组至少包含ICRC-21、ICRC-25、ICRC-27、ICRC-29、ICRC-49
wallet.supportedStandards()
Permissions
权限
- triggers the signer's permissions prompt
requestPermissionsNotGranted() - After approval, returns scopes with state
wallet.permissions()granted - A second call returns without prompting again
{allPermissionsGranted: true}
- 会触发签名器的权限提示
requestPermissionsNotGranted() - 授权后,返回的权限范围状态为
wallet.permissions()granted - 再次调用会返回,无需再次提示
{allPermissionsGranted: true}
Accounts
账户
- returns at least one
wallet.accounts()(principal as text){owner: string} - The returned matches the signer's identity principal
owner
- 至少返回一个
wallet.accounts()(主体的文本形式){owner: string} - 返回的与签名器的身份主体一致
owner
Transfers and Approvals
转账与授权
- /
icrc1Transfer(),transfer()/icrc2Approve(), andapprove()all resolve with atransferFrom()block indexbigint - Each triggers the consent message prompt on the signer before execution
- /
icrc1Transfer()、transfer()/icrc2Approve()以及approve()都会返回transferFrom()类型的区块索引bigint - 每个操作在执行前都会触发签名器的同意消息提示