下载

arrow_down

开发者服务

arrow_down

更多

arrow_down
activityactivityactivityactivity
  • themelight
  • languageIcon

  • menu
Skip to Content
Zh CnDocs
参考
DappOmniConnect

OmniConnect

OmniConnectBitget Wallet推出的面向开发者的软件开发工具包,旨在实现 Telegram 生态系统内的 Mini-Apps 与多链环境的无缝集成。集成此工具包后,Telegram Mini Apps 可以直接与 Bitget Wallet 进行跨多链的签名、交易和其他 DApp 操作。

  • OmniConnect:一个开放协议,通过桥接服务器连接钱包和 DApps(Web3 应用程序),在 TG MiniApp 和/或 Bitget Wallet Lite 之间建立远程连接。
  • OmniConnect 官方支持的链
    • EVM(以太坊虚拟机兼容区块链,如 Fantom、Base、Arbitrum 等)
  • OmniConnect 支持的钱包
    • Bitget Wallet Lite
  • 在线演示快速体验
  • NPM 包

安装和初始化

要将 Omni Connect 集成到您的 DApp 中,您可以使用 pnpm:

pnpm add @bitget-wallet/omni-connect

在连接钱包之前,您需要实例化一个对象以进行后续操作,如连接钱包和发送交易。

const connector = new OmniConnect({ metadata, namespace })

请求参数

  • metaData - object:应用程序元数据
    • name - string:应用程序名称
    • iconUrl - string:应用程序图标 URL
    • url - string:应用程序的主网站
    • privacyPolicyUrl - string(可选):
    • termsOfUseUrl - string(可选):
  • namespace - object:请求连接的必要命名空间信息。值必须在支持范围内,否则连接将失败。
    • eip155 - object:EVM 值为”eip155”
      • chains: - Array:链 ID 信息

示例

import { OmniConnect, PreventType } from '@bitget-wallet/omni-connect' const connector = new OmniConnect({ metadata: { name: '<您的DApp名称>', iconUrl: '<您的图标URL>', url: '<您的网站URL>', privacyPolicyUrl: '', // 可选 termsOfUseUrl: '', // 可选 }, namespace: { eip155: { chains: ['1', '56'], }, }, })

连接钱包

连接到钱包以获取钱包地址,该地址用作标识符,是签名交易所必需的; connector.connect(options)

请求参数

  • options - object(可选)
    • ret - string:PreventType.CLOSE 您可以指定连接后不关闭钱包(默认行为是关闭它)

恢复连接

如果用户之前已连接其钱包,请使用此方法恢复连接状态。(SDK 已默认恢复连接)

示例

connector.restoreConnection()

签名消息

向钱包签名消息的方法,支持签名;

connector.signMessage({ method, params: [address, message] })

请求参数

  • method - string:请求方法名称值 eth_sign | personal_sign | eth_signTypedData | eth_signTypedData_v4
  • params - Array:
    • [0] - address - string:钱包地址
    • [1] - message - string | object:要签名的消息
  • settings - object(可选):打开 TG 钱包配置
    • preventPopup - string:PreventType.OPEN(防止打开新窗口)

示例

const address = '0x..' try { await connector.signMessage({ method: 'eth_sign', params: [address, 'Hello World!'], }) } catch (error) { console.log(error) }

发送交易

向钱包发送消息以支持交易的方法;

connector.sendTransaction({ method: "eth_sendTransaction", params: [txData], })

请求参数

  • method - string:请求方法名称值 eth_sendTransaction
  • params - Array:
    • [0] - txData - object:
      • chainId - hexstring:链 ID
      • from - string:发送者地址。
      • to - string:接收者地址,如果这是合约创建交易则为 null。
      • value - hexstring:要转移的值,以 wei 为单位。
      • nonce - hexstring(可选):防重放参数。
      • gasLimit - hexstring:gasLimit
      • maxPriorityFeePerGas - hexstring(可选):发送者愿意在基础费用之上为每个 gas 支付的最大费用,以 wei 为单位。
      • maxFeePerGas - hexstring(可选):发送者愿意为每个 gas 支付的最大总费用(基础费用+优先费用),以 wei 为单位。
  • settings - object(可选):打开 TG 钱包配置
    • preventPopup - string:PreventType.OPEN(防止打开新窗口)

示例

const address = '0x..' try { await connector.signTransaction({ method: 'eth_signTransaction', params: [ { chainId: '0x38', data: '0x', from: address, to: '0x..', value: '0x0', gasLimit: '0x5208', maxPriorityFeePerGas: '0x3b9aca00', //wei maxFeePerGas: '0x2540be400', //wei }, ], }) } catch (error) { console.log(error) }

监控钱包状态变化

钱包状态包括:连接状态、签名结果等。您可以使用此方法获取状态。

onStatusChange( callback: (walletInfo) => void, errorsHandler?: (err) => void ): () => void;

请求参数

  • callback - (walletInfo) => void
    • id - number | string:请求 ID
    • namespaceKey - string:‘eip155’
    • event - string:事件类型
    • connected - boolean:连接状态
    • result - object
      • address - string:钱包连接地址
      • signature - string:签名结果
  • errorsHandler - (err) => void
    • code - number:错误代码
    • message - string:错误消息

示例

const subscription = connector.onStatusChange( (walletInfo) => { console.log('onStatusChange', walletInfo) const { id, namespaceKey, event, connected, result } = walletInfo switch (event) { case 'connect': case 'disconnect': // 连接或断开连接逻辑.. break case 'signMessage': break case 'signTransaction': case 'sendTransaction': // 处理result?.signature, result?.reciept break default: break } }, (err) => { const { code, message } = err console.error(`错误流:代码:${code},消息:${message}`) } )

当您不再需要监听更新时,调用unsubscribe以节省资源。

subscription?.unsubscribe()

断开连接

断开已连接的钱包

connector.disconnect()

错误代码

连接期间可能抛出的异常:

UNKNOWN_ERROR 未知异常
BAD_REQUEST_ERROR 请求错误
UNKNOWN_APP_ERROR 未知应用异常
USER_REJECTS_ERROR 用户拒绝
METHOD_NOT_SUPPORTED 方法不支持

export enum SIGN_DATA_ERROR_CODES { UNKNOWN_ERROR = 0, BAD_REQUEST_ERROR = 1, UNKNOWN_APP_ERROR = 100, USER_REJECTS_ERROR = 300, METHOD_NOT_SUPPORTED = 400 } export enum SEND_TRANSACTION_ERROR_CODES { UNKNOWN_ERROR = 0, BAD_REQUEST_ERROR = 1, UNKNOWN_APP_ERROR = 100, USER_REJECTS_ERROR = 300, METHOD_NOT_SUPPORTED = 400 }

完整开发示例

OmniConnect DappDemo

"use client"; // import Image from "next/image"; import React from "react"; import { Eip155SigningMethods, OmniConnect, PreventType, RequestMethods, } from "@bitget-wallet/omni-connect"; import { useState, useEffect, useMemo, useRef } from "react"; import { hashMessage } from "@ethersproject/hash"; import { recoverAddress, verifyMessage } from "ethers"; import { toChecksumAddress } from "ethereumjs-util"; import { recoverTypedSignature, SignTypedDataVersion, } from "@metamask/eth-sig-util"; import { decodeUTF8, encodeUTF8 } from "tweetnacl-util"; import ethUtil from "ethereumjs-util"; export const Toast = ({ message, isVisible, onClose }) => { if (!isVisible) return null; return ( <div className="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-75 z-50"> <div className="bg-white rounded-lg shadow-lg p-4 text-center animate-fadeIn"> <p className="text-gray-800">{message}</p> </div> </div> ); }; export default function Home() { const [connector, setConnector] = useState({} as any); const [connected, setConnected] = useState(false); const [address, setAddress] = useState(""); const [signature, setSignature] = useState(); const [reciept, setReciept] = useState(); const [msg, setMsg] = useState({ eth_sign: "Hello World!", personal_sign: "Personal:FOMO NUCN, just beyond FOMO3D", eth_signTypedData: [ { type: "string", name: "Message", value: "Hi, Alice!", }, { type: "uint32", name: "A number", value: "1337", }, ], eth_signTypedData_v4: { domain: { chainId: 56, // Give a user-friendly name to the specific contract you're signing for. name: "Ether Mail", // Add a verifying contract to make sure you're establishing contracts with the proper entity. verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", // This identifies the latest version. version: "1", }, message: { contents: "Hello, Bob!", attachedMoneyInEth: 4.2, from: { name: "Cow", wallets: [ "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", ], }, to: [ { name: "Bob", wallets: [ "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", "0xB0B0b0b0b0b0B000000000000000000000000000", ], }, ], }, primaryType: "Mail", types: { // This refers to the domain the contract is hosted on. EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ], // Not an EIP712Domain definition. Group: [ { name: "name", type: "string" }, { name: "members", type: "Person[]" }, ], // Refer to primaryType. Mail: [ { name: "from", type: "Person" }, { name: "to", type: "Person[]" }, { name: "contents", type: "string" }, ], // Not an EIP712Domain definition. Person: [ { name: "name", type: "string" }, { name: "wallets", type: "address[]" }, ], }, }, }); const [msgSig, setMsgSig] = useState({} as any); const [txData, setTxData] = useState({ chainId: "0x38", data: "0x", from: address, to: "0x1A0A18AC4BECDDbd6389559687d1A73d8927E416", value: "0x0", // nonce: "0x1", gasLimit: "0x5208", maxPriorityFeePerGas: "0x3b9aca00", //wei maxFeePerGas: "0x2540be400", //wei }); const [chainId, setChainId] = useState("1"); const [isToastVisible, setToastVisible] = useState(false); const [isClickToastVisible, setClickToastVisible] = useState(false); const init = async (connector) => { const result = await connector.provider.restoreConnection(); // setAddress(result?.account); }; useEffect(() => { const connector = new OmniConnect({ metadata: { name: "<Your DAppName>", iconUrl: "<Your iconUrl>", url: "<Your website url>", privacyPolicyUrl: "", // optional termsOfUseUrl: "", // optional }, namespace: { eip155: { chains: ["1", "56"], }, }, }); setConnector(connector); init(connector); const subscription = connector.provider.onStatusChange( (walletInfo) => { console.log("onStatusChange", walletInfo); const { id, namespaceKey, event, connected, result } = walletInfo; switch (event) { case RequestMethods.Connect: case RequestMethods.Disconnect: setConnected(connected); setAddress(result?.address); setTxData({ ...txData, from: result?.address, }); break; case RequestMethods.SignMessage: setMsgSig(result); break; case RequestMethods.SignTransaction: case RequestMethods.SendTransaction: setSignature(result?.signature); setReciept(result?.reciept); break; default: break; } }, (err) => { const { code, message } = err; console.error(`error stream: code: ${code}, message: ${message}`); } ); return () => { subscription?.unsubscribe(); }; }, []); const onSelectChain = (e) => { if (e.target.value !== chainId) { setToastVisible(true); setTimeout(() => { setToastVisible(false); }, 1300); } setChainId(e.target.value); }; return ( <div className="w-full bg-[#efefef] box-border"> <div className="fixed w-full h-[55px] text-large font-bold text-white flex justify-center items-center bg-[#3894ff] "> <img className="w-6 h-6 rounded-full mr-2" src={"/logo.png"} /> OmniConnect </div> <div className="flex flex-col max-w-[600px] mx-auto px-5 py-[55px] gap-3 font-[family-name:var(--font-geist-sans)]"> {/* connect */} <div className="w-full p-5 bg-white rounded-md my-4"> <div className="flex flex-col justify-between"> <div className="mb-3">App namespace: Eip155</div> <div className="mb-3"> Chain: <select name="chain" id="chain" onChange={onSelectChain}> <option value="1">ETH</option> <option value="56">BNB</option> </select> </div> <div className="mb-3">ChainId: {chainId}</div> <div className="mb-3"> Connect Status: {connected ? "connected" : "unconnect"} </div> </div> <div className="break-all">{connected && `address: ${address}`}</div> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-6 `} type="button" onClick={async () => { setClickToastVisible(true); setTimeout(() => { setClickToastVisible(false); }, 500); if (!!connected) { connector.provider.disconnect(); } else { try { const { account, chainId } = await connector.provider.connect( { request: { chainId: "1" }, } ); console.log("result", account, chainId); } catch (error) { console.log("error", error); } } }} > {connected ? "disconnect" : "connect"} </button> </div> <div className="w-full p-5 bg-white rounded-md my-4"> <h2 className="text-center text-lg font-bold mb-3">Eth Sign</h2> <textarea name="eth_sign" rows={1} className="w-full p-2 text-black border border-solid border-[#dedede] rounded-md" value={msg.eth_sign} onChange={(e) => setMsg({ ...msg, eth_sign: e.target.value, }) } /> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-6`} type="button" onClick={async () => { setClickToastVisible(true); setTimeout(() => { setClickToastVisible(false); }, 500); console.log("touched eth_sign"); try { const signature = await connector.provider.signMessage({ method: Eip155SigningMethods.EthSign, params: [address, msg.eth_sign], }); // handle result... console.log("signature", signature); } catch (error) { const { code, message } = error; console.error(`error code: ${code}, message: ${message}`); } }} > eth_sign </button> <div className="mt-5"> <div style={{ wordBreak: "break-all" }} className="break-all mb-3"> signature:{" "} {msgSig?.messageType === "eth_sign" && msgSig.signature} </div> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-4`} type="button" onClick={() => { if (!connected) return; const verifySigner = recoverAddress( hashMessage(msg.eth_sign), msgSig.signature ); console.log("verifySigner", verifySigner); console.log( `equal to address: ${address} ? ${verifySigner === address}` ); alert( `equal to address: ${address} ? ${verifySigner === address}` ); }} > verify eth_sign message </button> </div> </div> <div className="w-full p-5 bg-white rounded-md my-4"> {/* personal_sign */} <div className="border-b border-[#dedede]"> <h2 className="text-center text-lg font-bold mb-3"> Sign Message personal </h2> <textarea rows={1} className="w-full min-h-10 p-2 text-black border border-solid border-[#dedede] rounded-md" value={msg.personal_sign} onChange={(e) => setMsg({ ...msg, personal_sign: e.target.value, }) } /> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg my-4`} type="button" onClick={async () => { setClickToastVisible(true); setTimeout(() => { setClickToastVisible(false); }, 500); console.log("touched personal_sign"); try { const signature = await connector.provider.signMessage({ method: Eip155SigningMethods.PersonalSign, params: [ uint8ArraytoHex(decodeUTF8(msg.personal_sign)), address, ], }); console.log("signature", signature); } catch (error) { const { code, message } = error; console.error(`error code: ${code}, message: ${message}`); } }} > personal_sign </button> <div className="mt-5"> <div style={{ wordBreak: "break-all" }} className="break-all mb-3" > signature:{" "} {msgSig?.messageType === "personal_sign" && msgSig.signature} </div> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-4`} type="button" onClick={() => { if (!connected) return; const verifySigner = verifyMessage( msg.personal_sign, msgSig.signature ); console.log("verifySigner", verifySigner); console.log( `equal to address: ${address} ? ${verifySigner === address}` ); alert( `equal to address: ${address} ? ${verifySigner === address}` ); }} > verify personal_sign message </button> </div> </div> {/* eth_signTypedData*/} <div className="border-b border-[#dedede] py-4"> <h2 className="text-center text-lg font-bold mb-3"> Sign Message signTypedData </h2> <textarea rows={3} className="w-full min-h-16 p-2 text-black border border-solid border-[#dedede] rounded-md mt-4" readOnly value={JSON.stringify(msg.eth_signTypedData)} onChange={(e) => setMsg({ ...msg, eth_signTypedData: JSON.parse(e.target.value), }) } /> <button type="button" className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-4`} onClick={async () => { setClickToastVisible(true); setTimeout(() => { setClickToastVisible(false); }, 500); console.log("touched eth_signTypedData"); try { const signature = await connector.provider.signMessage({ method: Eip155SigningMethods.EthSignTypedData, params: [address, msg.eth_signTypedData], }); console.log("signature", signature); } catch (error) { const { code, message } = error; console.error(`error code: ${code}, message: ${message}`); } }} > eth_signTypedData </button> <div className="mt-5"> <div style={{ wordBreak: "break-all" }} className="break-all mb-3" > signature:{" "} {msgSig?.messageType === "eth_signTypedData" && msgSig.signature} </div> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-4`} type="button" onClick={() => { if (!connected) return; const verifySigner = recoverTypedSignature({ data: msg.eth_signTypedData, signature: msgSig.signature, version: SignTypedDataVersion.V1, }); console.log("verifySigner", verifySigner); alert( `equal to address: ${toChecksumAddress(address)} ? ${ toChecksumAddress(verifySigner) === toChecksumAddress(address) }` ); }} > verify eth_signTypedData message </button> </div> </div> {/* eth_signTypedData_v4*/} <div className=" py-4"> <h2 className="text-center text-lg font-bold mb-3"> Sign Message signTypedData_v4 </h2> <textarea readOnly rows={6} className="w-full min-h-16 p-2 text-black border border-solid border-[#dedede] rounded-md mt-4" defaultValue={JSON.stringify(msg.eth_signTypedData_v4)} /> <button type="button" className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-6`} onClick={async () => { setClickToastVisible(true); setTimeout(() => { setClickToastVisible(false); }, 500); console.log("touched eth_signTypedData_v4"); try { const signature = await connector.provider.signMessage({ method: Eip155SigningMethods.EthSignTypedDataV4, params: [address, msg.eth_signTypedData_v4], }); console.log("signature", signature); } catch (error) { const { code, message } = error; console.error(`error code: ${code}, message: ${message}`); } }} > eth_signTypedData_v4 </button> <div className="mt-5"> <div style={{ wordBreak: "break-all" }} className="break-all mb-3" > signature:{" "} {msgSig?.messageType === "eth_signTypedData_v4" && msgSig.signature} </div> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-4`} type="button" onClick={() => { if (!connected) return; const verifySigner = recoverTypedSignature({ data: msg.eth_signTypedData_v4 as any, signature: msgSig.signature, version: SignTypedDataVersion.V4, }); console.log("verifySigner", verifySigner); alert( `equal to address: ${toChecksumAddress(address)} ? ${ toChecksumAddress(verifySigner) === toChecksumAddress(address) }` ); }} > verify eth_signTypedData_v4 message </button> </div> </div> </div> <div className="w-full p-5 bg-white rounded-md my-4"> <h2 className="text-center text-lg font-bold mb-3"> SignTransaction </h2> <textarea rows={6} name="txInput" className="w-full min-h-10 p-2 text-black border border-solid border-[#dedede] rounded-md" value={JSON.stringify(txData)} onChange={(e) => { setTxData(JSON.parse(e.target.value)); }} /> <div style={{ wordBreak: "break-all" }} className="break-all mb-3"> signature:{signature} </div> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg my-4`} type="button" onClick={async () => { setClickToastVisible(true); setTimeout(() => { setClickToastVisible(false); }, 500); console.log("touched signTransaction"); try { const signature = await connector.provider.signTransaction({ method: Eip155SigningMethods.EthSignTransaction, params: [txData], }); console.log("signature", signature); } catch (error) { const { code, message } = error; console.error(`error code: ${code}, message: ${message}`); } }} > signTransaction </button> </div> <div className="w-full p-5 bg-white rounded-md my-4"> <h2 className="text-center text-lg font-bold mb-3"> SendTransaction </h2> <textarea rows={6} className="w-full min-h-10 p-2 text-black border border-solid border-[#dedede] rounded-md" value={JSON.stringify(txData)} onChange={(e) => { setTxData(JSON.parse(e.target.value)); }} /> <div style={{ wordBreak: "break-all" }} className="break-all mb-3"> signature:{signature} </div> <div style={{ wordBreak: "break-all" }} className="break-all mb-3"> reciept:{reciept} </div> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg my-4`} type="button" onClick={async () => { setClickToastVisible(true); setTimeout(() => { setClickToastVisible(false); }, 500); console.log("touched sendTransaction"); try { const reciept = await connector.provider.sendTransaction({ method: Eip155SigningMethods.EthSendTransaction, params: [txData], }); console.log("reciept", reciept); } catch (error) { const { code, message } = error; console.error(`error code: ${code}, message: ${message}`); } }} > sendTransaction </button> </div> {!connected && ( <div className="w-full p-5 bg-white rounded-md my-4"> <button className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg mt-5`} type="button" onClick={async () => { setClickToastVisible(true); setTimeout(() => { setClickToastVisible(false); }, 500); console.log("touched connect & sign"); try { const result = await connector.provider.connect({ ret: PreventType.Close, }); console.log("result", result); const signRes = await connector.provider.signMessage({ method: Eip155SigningMethods.EthSign, params: [address, msg.eth_sign], settings: { preventPopup: PreventType.Open, }, }); console.log("signRes", signRes); } catch (error) { console.error(error); } }} > connect & sign </button> </div> )} </div> <Toast message="Chain switched successfully" isVisible={isToastVisible} onClose={() => setToastVisible(false)} /> <Toast message="Touched" isVisible={isClickToastVisible} onClose={() => setClickToastVisible(false)} /> </div> ); }

支持的链 ID

参见支持的主网

Last updated on