请求与响应
应用向钱包发送请求。钱包向应用发送响应和事件。
type AppMessage = ConnectRequest | AppRequest;
type WalletMessage = WalletResponse | WalletEvent;
应用清单(manifest)
应用需要有自己的清单,以向钱包传递元信息。清单是一个命名为 tonconnect-manifest.json 的 JSON 文件,遵循以下格式:
{
  "url": "<app-url>",                        // 必填
  "name": "<app-name>",                      // 必填
  "iconUrl": "<app-icon-url>",               // 必填
  "termsOfUseUrl": "<terms-of-use-url>",     // 可选
  "privacyPolicyUrl": "<privacy-policy-url>" // 可选
}
最佳实践是将清单放置在应用的根目录中,例如 https://myapp.com/tonconnect-manifest.json。这允许钱包更好地处理您的应用,并改善与您的应用相关联的用户体验。
确保清单可以通过其 URL 被 GET 访问。
字段描述
- url-- 应用 URL。将用作 DApp 标识符。点击钱包中的图标后将用来打开 DApp。建议传递不带结尾斜杠的 url,例如 'https://mydapp.com' 而不是 'https://mydapp.com/'。
- name-- 应用名称。可以简单,不会用作标识符。
- iconUrl-- 应用图标的 Url。必须是 PNG、ICO 等格式。不支持 SVG 图标。最好传递一个 180x180px 的 PNG 图标的 url。
- termsOfUseUrl--(可选)使用条款文档的 url。对于普通应用是可选的,但对于放置在 Tonkeeper 推荐应用列表中的应用是必需的。
- privacyPolicyUrl--(可选)隐私政策文档的 url。对于普通应用是可选的,但对于放置在 Tonkeeper 推荐应用列表中的应用是必需的。
初始化连接
应用的请求消息是 InitialRequest。
type ConnectRequest = {
  manifestUrl: string;
  items: ConnectItem[], // 与应用共享的数据项
}
// 未来我们可能会添加其他个人项目。
// 或者,我们可能会请求每项服务的 ID 而不是钱包地址。
type ConnectItem = TonAddressItem | TonProofItem | ...;
type TonAddressItem = {
  name: "ton_addr";
}
type TonProofItem = {
  name: "ton_proof";
  payload: string; // 任意载荷,例如 nonce + 过期时间戳。
}
ConnectRequest 描述:
- manifestUrl:应用的 tonconnect-manifest.json 的链接
- items:与应用共享的数据项。
如果用户批准请求,钱包将以 ConnectEvent 消息作出响应。
type ConnectEvent = ConnectEventSuccess | ConnectEventError;
type ConnectEventSuccess = {
  event: "connect";
  id: number; // 递增的事件计数器
  payload: {
      items: ConnectItemReply[];
      device: DeviceInfo;   
  }
}
type ConnectEventError = {
  event: "connect_error",
  id: number; // 递增的事件计数器
  payload: {
      code: number;
      message: string;
  }
}
type DeviceInfo = {
  platform: "iphone" | "ipad" | "android" | "windows" | "mac" | "linux";
  appName:      string; // 例如 "Tonkeeper"  
  appVersion:  string; // 例如 "2.3.367"
  maxProtocolVersion: number;
  features: Feature[]; // RPC 中支持的特性和方法列表
                                // 目前只有一个特性 -- 'SendTransaction'; 
}
type Feature = { name: 'SendTransaction', maxMessages: number } | // `maxMessages` 是钱包支持的一次 `SendTransaction` 中的最大消息数
        { name: 'SignData' };
type ConnectItemReply = TonAddressItemReply | TonProofItemReply ...;
// 由钱包返回的不受信任的数据。
// 如果您需要保证用户拥有此地址和公钥,您需要额外请求 ton_proof。
type TonAddressItemReply = {
  name: "ton_addr";
  address: string; // TON 地址原始 (`0:<hex>`)
  network: NETWORK; // 网络 global_id
  publicKey: string; // HEX 字符串,不带 0x
  walletStateInit: string; // Base64(不安全 URL)编码的钱包合约的 stateinit cell
}
type TonProofItemReply = TonProofItemReplySuccess | TonProofItemReplyError;
type TonProofItemReplySuccess = {
  name: "ton_proof";
  proof: {
    timestamp: string; // 签名操作的 64 位 unix epoch 时间(秒)
    domain: {
      lengthBytes: number; // AppDomain 长度
      value: string;  // 应用域名(作为 url 部分,无编码) 
    };
    signature: string; // base64 编码的签名
    payload: string; // 请求中的载荷
  }
}
type TonProofItemReplyError = {
  name: "ton_addr";
  error: {
      code: ConnectItemErrorCode;
      message?: string;
  }
}
enum NETWORK {
  MAINNET = '-239',
  TESTNET = '-3'
}
连接事件错误代码:
| code | 描述 | 
|---|---|
| 0 | 未知错误 | 
| 1 | 错误请求 | 
| 2 | 未找到应用清单 | 
| 3 | 应用清单内容错误 | 
| 100 | 未知应用 | 
| 300 | 用户拒绝连接 | 
连接项目错误代码:
| code | 描述 | 
|---|---|
| 0 | 未知错误 | 
| 400 | 方法不被支持 | 
如果钱包不支持所请求的 ConnectItem(例如 "ton_proof"),它必须发送对应于所请求项目的以下 ConnectItemReply 回复。
结构如下:
type ConnectItemReplyError = {
  name: "<requested-connect-item-name>";
  error: {
      code: 400;
      message?: string;
  }
}
地址证明签名(ton_proof)
如果请求了 TonProofItem,钱包证明其拥有选定账户的密钥。签名消息绑定到:
- 唯一前缀,以将消息与链上消息分开。(ton-connect)
- 钱包地址。
- 应用域
- 签名时间戳
- 应用的自定义载荷(其中服务器可能会放置其 nonce、cookie id、过期时间)。
message = utf8_encode("ton-proof-item-v2/") ++ 
          Address ++
          AppDomain ++
          Timestamp ++  
          Payload 
signature = Ed25519Sign(privkey, sha256(0xffff ++ utf8_encode("ton-connect") ++ sha256(message)))
其中:
- Address是作为序列编码的钱包地址:- workchain:32 位有符号整数大端序;
- hash:256 位无符号整数大端序;
 
- AppDomain是 Length ++ EncodedDomainName- Length是 utf-8 编码的应用域名长度的 32 位值(字节)
- EncodedDomainName是- Length字节的 utf-8 编码应用域名
 
- Timestamp是签名操作的 64 位 unix epoch 时间
- Payload是变长的二进制字符串。
注意:载荷是变长的不受信任数据。为了避免使用不必要的长度前缀,我们简单地将其放在消息的末尾。
必须通过公钥验证签名:
- 首先,尝试通过在 - Address处部署的智能合约上的- get_public_keyget 方法获取公钥。
- 如果智能合约还未部署,或者缺少 get 方法,您需要: - 解析 - TonAddressItemReply.walletStateInit并从 stateInit 获取公钥。您可以将- walletStateInit.code与标准钱包合约的代码进行比较,并根据找到的钱包版本解析数据。
- 检查 - TonAddressItemReply.publicKey是否等于获取到的公钥
- 检查 - TonAddressItemReply.walletStateInit.hash()是否等于- TonAddressItemReply.address。- .hash()意味着 BoC 哈希。
 
消息
- 应用发给钱包的所有消息都是操作请求。
- 钱包发给应用的消息可以是对应用请求的响应,也可以是钱包一侧用户操作触发的事件。
可用操作:
- sendTransaction
- signData
- disconnect
可用事件:
- connect
- connect_error
- disconnect
结构
所有应用请求都具有以下结构(如 json-rpc 2.0)
interface AppRequest {
    method: string;
    params: string[];
    id: string;
}
其中
- method:操作名称('sendTransaction', 'signMessage', ...)
- params:操作特定参数的数组
- id:递增的标识符,允许匹配请求和响应
钱包消息是响应或事件。
响应是格式化为 json-rpc 2.0 响应的对象。响应的 id 必须与请求的 id 匹配。
钱包不接受任何 id 未大于该会话的最后处理请求 id 的请求。
type WalletResponse = WalletResponseSuccess | WalletResponseError;
interface WalletResponseSuccess {
    result: string;
    id: string;
}
interface WalletResponseError {
    error: { code: number; message: string; data?: unknown };
    id: string;
}
事件是一个带有 event 属性的对象,event 等于事件名称,id 是递增的事件计数器(不 关联 request.id 因为事件没有请求),以及包含事件附加数据的 payload。
interface WalletEvent {
    event: WalletEventName;
    id: number; // 递增的事件计数器
    payload: <event-payload>; // 每个事件特定的载荷
}
type WalletEventName = 'connect' | 'connect_error' | 'disconnect';
钱包在生成新事件时必须增加 id。(每个接下来的事件必须有的 id > 前一个事件的 id)
DApp 不接受任何 id 未大于该会话的最后处理事件 id 的事件。
方法
签署并发送交易
应用发送 SendTransactionRequest:
interface SendTransactionRequest {
    method: 'sendTransaction';
    params: [<transaction-payload>];
    id: string;
}
其中 <transaction-payload> 是具有以下属性的 JSON:
- valid_until(整数,可选):unix 时间戳。该时刻之后交易将无效。
- network(NETWORK,可选):DApp打算发送交易的网络(主网或测试网)。如果未设置,交易将发送到钱包当前设置的网络,但这不安全,DApp 应始终努力设置网络。如果设置了- network参数,但钱包设置了不同的网络,钱包应显示警告并不允许发送此交易。
- from(以- <wc>:<hex>格式的字符串,可选)- DApp打算从中发送交易的发送者地址。如果未设置,钱包允许用户在交易批准时选择发送者的地址。如果设置了- from参数,钱包不应允许用户选择发送者的地址;如果从指定地址发送不可能,钱包应显示警告并不允许发送此交易。
- messages(信息数组):1-4 条从钱包合约到其他账户的输出消息。所有消息按顺序发送出去,但 钱包无法保证消息会按相同顺序被传递和执行。
消息结构:
- address(字符串):消息目的地
- amount(小数字符串):要发送的纳币数量。
- payload(base64 编码的字符串,可选):以 Base64 编码的原始cell BoC。
- stateInit(base64 编码的字符串,可选):以 Base64 编码的原始cell BoC。
常见情况
- 无 payload,无 stateInit:简单转账,无消息。
- payload 前缀为 32 个零位,无 stateInit:带文本消息的简单转账。
- 无 payload 或前缀为 32 个零位;存在 stateInit:合约部署。
示例
{
  "valid_until": 1658253458,
  "network": "-239", // enum NETWORK { MAINNET = '-239', TESTNET = '-3'}
  "from": "0:348bcf827469c5fc38541c77fdd91d4e347eac200f6f2d9fd62dc08885f0415f",
  "messages": [
    {
      "address": "0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F",
      "amount": "20000000",
      "stateInit": "base64bocblahblahblah==" //部署合约
    },{
      "address": "0:E69F10CC84877ABF539F83F879291E5CA169451BA7BCE91A37A5CED3AB8080D3",
      "amount": "60000000",
      "payload": "base64bocblahblahblah==" //将 nft 转移至新部署的账户 0:412410771DA82CBA306A55FA9E0D43C9D245E38133CB58F1457DFB8D5CD8892F
    }
  ]
}
钱包以 SendTransactionResponse 回复:
type SendTransactionResponse = SendTransactionResponseSuccess | SendTransactionResponseError; 
interface SendTransactionResponseSuccess {
    result: <boc>;
    id: string;
    
}
interface SendTransactionResponseError {
   error: { code: number; message: string };
   id: string;
}
错误代码:
| code | 描述 | 
|---|---|
| 0 | 未知错误 | 
| 1 | 错误请求 | 
| 100 | 未知应用 | 
| 300 | 用户拒绝了交易 | 
| 400 | 方法不支持 | 
签署数据(实验性)
警告:这目前是一个实验性方法,其签名未来可能会变化
应用发送 SignDataRequest:
interface SignDataRequest {
    method: 'signData';
    params: [<sign-data-payload>];
    id: string;
}
其中 <sign-data-payload> 是具有以下属性的 JSON:
- schema_crc(整数):指示payload cell的布局,进而定义域分割。
- cell(字符串,base64 编码cell):根据其 TL-B 定义包含任意数据。
- publicKey(HEX 字符串,不含0x,可选):DApp打算用来签署数据的密钥对的公钥。如果未设置,钱包在签名时不受限制使用哪个密钥对。如果设置了- publicKey参数,钱包应使用与此公钥对应的密钥对签名;如果使用指定的密钥对签名不可能,钱包应显示警告并不允许签署此数据。
签名将以以下方式计算:
ed25519(uint32be(schema_crc) ++ uint64be(timestamp) ++ cell_hash(X), privkey)
钱包应根据 schema_crc 解码cell,并向用户显示相应数据。 如果钱包不知道 schema_crc,钱包应向用户显示危险通知/UI。
钱包以 SignDataResponse 回复:
type SignDataResponse = SignDataResponseSuccess | SignDataResponseError; 
interface SignDataResponseSuccess {
    result: {
      signature: string; // base64 编码的签名
      timestamp: string; // 创建签名时的 UNIX 时间戳(UTC,秒)
    };
    id: string;
}
interface SignDataResponseError {
   error: { code: number; message: string };
   id: string;
}
错误代码:
| code | 描述 | 
|---|---|
| 0 | 未知错误 | 
| 1 | 错误请求 | 
| 100 | 未知应用 | 
| 300 | 用户拒绝了请求 | 
| 400 | 方法不支持 | 
断开连接操作
当用户在 dApp 中断开钱包的连接时,DApp 应通知钱包以帮助钱包节省资源并删除不必要的会话。 允许钱包更新其界面到断开连接状态。
interface DisconnectRequest {
    method: 'disconnect';
    params: [];
    id: string;
}
钱包以 DisconnectResponse 回复:
type DisconnectResponse = DisconnectResponseSuccess | DisconnectResponseError; 
interface DisconnectResponseSuccess {
    id: string;
    result: { };
}
interface DisconnectResponseError {
   error: { code: number; message: string };
   id: string;
}
如果断开是由 dApp 初始化的,钱包 不应 发出 'Disconnect' 事件。
错误代码:
| code | 描述 | 
|---|---|
| 0 | 未知错误 | 
| 1 | 错误请求 | 
| 100 | 未知应用 | 
| 400 | 方法不支持 | 
钱包事件
断开连接当用户在钱包中删除应用时触发该事件。应用必须对该事件做出反应并删除保存的会话。如果用户在应用端断开钱包连接,那么事件不会触发,会话信息仍保留在本地存储中
interface DisconnectEvent {
    type: "disconnect",
    id: number; // 递增的事件计数器
    payload: { }
}
type ConnectEventSuccess = {
    event: "connect",
    id: number; // 递增的事件计数器
    payload: {
        items: ConnectItemReply[];
        device: DeviceInfo;
    }
}
type ConnectEventError = {
    event: "connect_error",
    id: number; // 递增的事件计数器
    payload: {
        code: number;
        message: string;
    }
}