Skip to content

Bitget Wallet OmniConnect

OmniConnectBitget Wallet 推出的一款面向开发者的软件开发套件,支持 Telegram 生态中的 Mini-App 无缝连接至多链生态系统,Telegram Mini App 集成该套件后,可以直接通过 Bitget Wallet,在多链上进行签名、交易等 DApp 交互操作。

  • OmniConnect: 一个链接钱包和 DApps(Web3 应用程序)的开放协议,它使用一个桥梁服务器在 TG MiniApp 和/或 Bitget Wallet Lite 之间建立远程链接
  • OmniConnect 官方支持的链
    • EVM(EVM 指代采用以太坊虚拟机技术的区块链网络,常见如 Fantom、Base、Arbitrum 等)
  • OmniConnect 已支持的钱包
    • Bitget Wallet Lite
  • 快速体验在线示例Demo
  • NPM Package:

安装及初始化

将 OmniConnect 集成到您的 DApp 中,使用 pnpm:

bash
pnpm add @bitget-wallet/omni-connect
pnpm add @bitget-wallet/omni-connect

连接钱包之前,需要先实例化一个对象,用于后续连接钱包、发送交易等操作。

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

请求参数

  • metaData - object: 应用的元数据
    • name - string: 应用名称
    • iconUrl - string: 应用的图标地址
    • url - string: 应用的主网站
    • privacyPolicyUrl - string(optional):
    • termsOfUseUrl - string(optional):
  • namespace - object: 请求连接的必要 namespace 信息,取值必须在支持范围内,否则无法连接成功
    • eip155 - object: EVM 取值 为"eip155"
      • chains: - Array: 链 id 信息

示例

js
import { OmniConnect, PreventType } from "@bitget-wallet/omni-connect";

const connector = new OmniConnect({
  metadata: {
    name: "<Your DAppName>",
    iconUrl: "<Your iconUrl>",
    url: "<Your website url>",
    privacyPolicyUrl: "", // optional
    termsOfUseUrl: "", // optional
  },
  namespace: {
    eip155: {
      chains: ["1", "56"],
    },
  },
});
import { OmniConnect, PreventType } from "@bitget-wallet/omni-connect";

const connector = new OmniConnect({
  metadata: {
    name: "<Your DAppName>",
    iconUrl: "<Your iconUrl>",
    url: "<Your website url>",
    privacyPolicyUrl: "", // optional
    termsOfUseUrl: "", // optional
  },
  namespace: {
    eip155: {
      chains: ["1", "56"],
    },
  },
});

连接钱包

连接钱包去获取钱包地址,作为标识符和用于签名交易的必要参数; connector.connect(options)

请求参数

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

恢复连接

如果用户已成功连接过钱包,再次进入或页面刷新时可调用此方法恢复连接状态。(sdk 已默认恢复连接)

示例

js
connector.restoreConnection();
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(optional): 打开 TG 钱包的配置
    • preventPopup - string: PreventType.OPEN(阻止打开新窗口)

示例

js
const address = "0x..";

try {
  await connector.signMessage({
    method: "eth_sign",
    params: [address, "Hello World!"],
  });
} catch (error) {
  console.log(error);
}
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: 接受者地址,如果是合约创建交易,则为空
      • value - hexstring: 需要转移的值,以 wei 为单位
      • nonce - hexstring(optional): 防重放参数
      • gasLimit - hexstring: gasLimit
      • maxPriorityFeePerGas - hexstring(optional): 最高费用,以 wei 为单位,表示发送者愿意为高于基本费用的每 gas 支付的费用。
      • maxFeePerGas - hexstring(optional): 发送者愿意按每 gas 支付的最高总费用(基本费用 + 优先费用),以 wei 为单位。
  • settings - object(optional): 打开 TG 钱包的配置
    • preventPopup - string: PreventType.OPEN(阻止打开新窗口)

示例

js
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);
}
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: 错误信息

示例

js
const subscription = connector.onStatusChange(
  (walletInfo) => {
    console.log("onStatusChange", walletInfo);
    const { id, namespaceKey, event, connected, result } = walletInfo;
    switch (event) {
      case "connect":
      case "disconnect":
        // connected or disconnect logic..
        break;
      case "signMessage":
        break;
      case "signTransaction":
      case "sendTransaction":
        // handle result?.signature, result?.reciept
        break;
      default:
        break;
    }
  },
  (err) => {
    const { code, message } = err;
    console.error(`error stream: code: ${code}, message: ${message}`);
  }
);
const subscription = connector.onStatusChange(
  (walletInfo) => {
    console.log("onStatusChange", walletInfo);
    const { id, namespaceKey, event, connected, result } = walletInfo;
    switch (event) {
      case "connect":
      case "disconnect":
        // connected or disconnect logic..
        break;
      case "signMessage":
        break;
      case "signTransaction":
      case "sendTransaction":
        // handle result?.signature, result?.reciept
        break;
      default:
        break;
    }
  },
  (err) => {
    const { code, message } = err;
    console.error(`error stream: code: ${code}, message: ${message}`);
  }
);

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

js
subscription?.unsubscribe();
subscription?.unsubscribe();

断开钱包连接

断开已连接的钱包

connector.disconnect()

错误码

在连接,交易,断开连接的过程中可能抛出的异常:

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

js
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
}
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

js
"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>
  );
}
"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>
  );
}

支持的 ChainIds

已支持主网