OmniConnect
OmniConnect is a software development kit launched by Bitget Wallet aimed at developers, enabling seamless integration of Mini-Apps within the Telegram ecosystem to a multi-chain environment. After integrating this kit, Telegram Mini Apps can directly interact with Bitget Wallet for signing, transactions, and other DApp operations across multiple chains.
- OmniConnect: An open protocol that links wallets and DApps (Web3 applications) via a bridge server, establishing remote connections between TG MiniApp and/or Bitget Wallet Lite.
- Chains officially supported by OmniConnect:
- EVM (Ethereum Virtual Machine-compatible blockchains, such as Fantom, Base, Arbitrum, etc.)
- Wallets supported by OmniConnect:
- Bitget Wallet Lite
- Quick experience with the online demo:
- NPM Package:
Installation and Initialization
To integrate Omni Connect into your DApp, you can use pnpm:
pnpm add @bitget-wallet/omni-connect
pnpm add @bitget-wallet/omni-connect
Before connecting the wallet, you need to instantiate an object for subsequent operations such as connecting the wallet and sending transactions.
const connector = new OmniConnect({ metadata, namespace })
const connector = new OmniConnect({ metadata, namespace })
Request Parameters
metaData
- object: Application metadata- name - string: Application name
- iconUrl - string: Application icon url
- url - string: The main website of the application
- privacyPolicyUrl - string(optional):
- termsOfUseUrl - string(optional):
namespace
- object: Necessary namespace information for requesting a connection. The value must be within the supported range, otherwise the connection will fail.- eip155 - object: EVM value is "eip155"
- chains: - Array: Chain ID information
- eip155 - object: EVM value is "eip155"
Example
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"],
},
},
});
Connect to Wallet
Connect to the wallet to get the wallet address, which serves as an identifier and is necessary for signing transactions; connector.connect(options)
Request Parameters
options
- object(optional)- ret - string: PreventType.CLOSE You can specify not to close the wallet after connecting to it (the default behavior is to close it)
Restore Connection
If the user has previously connected their wallet, use this method to restore the connection state. (The SDK has restored the connection by default)
Example
connector.restoreConnection();
connector.restoreConnection();
Sign message
Method for sign messages to wallets, supporting signatures;
connector.signMessage({ method, params: [address, message] })
Request Parameters
- method - string: Request method name value
eth_sign
|personal_sign
|eth_signTypedData
|eth_signTypedData_v4
- params - Array:
- [0] - address - string: Wallet Address
- [1] - message - string | object: Message to sign
- settings - object(optional): Open the TG wallet configuration
- preventPopup - string: PreventType.OPEN(Prevent new windows from opening)
Example
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);
}
Send transaction
Methods for sending messages to wallets to support transactions;
connector.sendTransaction({ method: "eth_sendTransaction", params: [txData], })
Request Parameters
method
- string: Request method name valueeth_sendTransaction
params
- Array:- [0] - txData - object:
- chainId - hexstring: Chain id
- from - string: Sender address.
- to - string: Recipient address, or null if this is a contract creation transaction.
- value - hexstring: Value to be transferred, in wei.
- nonce - hexstring(optional): Anti-replay parameter.
- gasLimit - hexstring: gasLimit
- maxPriorityFeePerGas - hexstring(optional): Maximum fee, in wei, the sender is willing to pay per gas above the base fee.
- maxFeePerGas - hexstring(optional): Maximum total fee (base fee + priority fee), in wei, the sender is willing to pay per gas.
- [0] - txData - object:
- settings - object(optional): Open the TG wallet configuration
- preventPopup - string: PreventType.OPEN(Prevent new windows from opening)
Example
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);
}
Monitor wallet state changes
The wallet statuses are: connection status, sign result, etc. You can use this method to get the status.
onStatusChange( callback: (walletInfo) => void, errorsHandler?: (err) => void ): () => void;
Request Parameters
callback
- (walletInfo) => void- id - number | string: Request id
- namespaceKey - string: 'eip155'
- event - string: Event type
- connected - boolean: Connection Status
- result - object
- address - string: Wallet connection address
- signature - string: Sign result
errorsHandler
- (err) => void- code - number: Error code
- message - string: Error message
Example
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}`);
}
);
Call unsubscribe
to save resources when you no longer need to listen for updates.
subscription?.unsubscribe();
subscription?.unsubscribe();
Disconnect
Disconnect the connected wallet
connector.disconnect()
Error codes
Exceptions that may be thrown during connection:
UNKNOWN_ERROR Unknown Exception
BAD_REQUEST_ERROR Request error
UNKNOWN_APP_ERROR Unknown app exception
USER_REJECTS_ERROR User Rejected
METHOD_NOT_SUPPORTED 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
}
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
}
Complete development example
"use client";
import React from "react";
import {
EIP155_SIGNING_METHODS,
OmniConnect,
PreventType,
} 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";
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 connectFlag = useRef(false);
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);
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);
const subscription = connector.onStatusChange(
(walletInfo) => {
console.log("onStatusChange", walletInfo);
const { id, namespaceKey, event, connected, result } = walletInfo;
switch (event) {
case "connect":
case "disconnect":
setConnected(connected);
setAddress(result?.address);
setTxData({
...txData,
from: result?.address,
});
if (connected && !!connectFlag.current) {
connector.signMessage({
method: EIP155_SIGNING_METHODS.ETH_SIGN,
params: [address, msg.eth_sign],
settings: {
preventPopup: PreventType.OPEN,
},
});
connectFlag.current = false;
}
break;
case "signMessage":
setMsgSig(result);
break;
case "signTransaction":
case "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);
};
const txFromRef = useRef<any>(null);
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
connected ? connector.disconnect() : connector.connect();
}}
>
{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 {
await connector.signMessage({
method: "eth_sign",
params: [address, msg.eth_sign],
});
} catch (error) {
console.log(error);
}
}}
>
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched personal_sign");
connector.signMessage({
method: "personal_sign",
params: [address, msg.personal_sign],
});
}}
>
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched eth_signTypedData");
connector.signMessage({
method: "eth_signTypedData",
params: [address, msg.eth_signTypedData],
});
}}
>
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched eth_signTypedData_v4");
connector.signMessage({
method: "eth_signTypedData_v4",
params: [address, msg.eth_signTypedData_v4],
});
}}
>
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>
<form
ref={txFromRef}
onSubmit={(e) => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched signTransaction");
const currValue = txFromRef.current?.txInput?.value;
connector.signTransaction({
method: "eth_signTransaction",
params: [{ ...JSON.parse(currValue), from: address } || txData],
});
e.preventDefault();
}}
>
<div className="w-full p-5 bg-white rounded-md my-4">
<h2 className="text-center text-lg font-bold mb-3">
SignTransaction - Uncontrolled components
</h2>
<textarea
rows={6}
name="txInput"
className="w-full min-h-10 p-2 text-black border border-solid border-[#dedede] rounded-md"
defaultValue={JSON.stringify(txData)}
/>
<div style={{ wordBreak: "break-all" }} className="break-all mb-3">
signature:{signature}
</div>
<input
className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg my-4`}
type="submit"
value="signTransaction"
/>
</div>
</form>
<div className="w-full p-5 bg-white rounded-md my-4">
<h2 className="text-center text-lg font-bold mb-3">
SendTransaction - Controlled Components
</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>
<button
className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg my-4`}
type="button"
onClick={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched sendTransaction");
connector.sendTransaction({
method: "eth_sendTransaction",
params: [txData],
});
}}
>
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched connect & sign");
connector.connect({ ret: PreventType.CLOSE });
connectFlag.current = true;
}}
>
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 React from "react";
import {
EIP155_SIGNING_METHODS,
OmniConnect,
PreventType,
} 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";
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 connectFlag = useRef(false);
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);
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);
const subscription = connector.onStatusChange(
(walletInfo) => {
console.log("onStatusChange", walletInfo);
const { id, namespaceKey, event, connected, result } = walletInfo;
switch (event) {
case "connect":
case "disconnect":
setConnected(connected);
setAddress(result?.address);
setTxData({
...txData,
from: result?.address,
});
if (connected && !!connectFlag.current) {
connector.signMessage({
method: EIP155_SIGNING_METHODS.ETH_SIGN,
params: [address, msg.eth_sign],
settings: {
preventPopup: PreventType.OPEN,
},
});
connectFlag.current = false;
}
break;
case "signMessage":
setMsgSig(result);
break;
case "signTransaction":
case "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);
};
const txFromRef = useRef<any>(null);
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
connected ? connector.disconnect() : connector.connect();
}}
>
{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 {
await connector.signMessage({
method: "eth_sign",
params: [address, msg.eth_sign],
});
} catch (error) {
console.log(error);
}
}}
>
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched personal_sign");
connector.signMessage({
method: "personal_sign",
params: [address, msg.personal_sign],
});
}}
>
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched eth_signTypedData");
connector.signMessage({
method: "eth_signTypedData",
params: [address, msg.eth_signTypedData],
});
}}
>
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched eth_signTypedData_v4");
connector.signMessage({
method: "eth_signTypedData_v4",
params: [address, msg.eth_signTypedData_v4],
});
}}
>
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>
<form
ref={txFromRef}
onSubmit={(e) => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched signTransaction");
const currValue = txFromRef.current?.txInput?.value;
connector.signTransaction({
method: "eth_signTransaction",
params: [{ ...JSON.parse(currValue), from: address } || txData],
});
e.preventDefault();
}}
>
<div className="w-full p-5 bg-white rounded-md my-4">
<h2 className="text-center text-lg font-bold mb-3">
SignTransaction - Uncontrolled components
</h2>
<textarea
rows={6}
name="txInput"
className="w-full min-h-10 p-2 text-black border border-solid border-[#dedede] rounded-md"
defaultValue={JSON.stringify(txData)}
/>
<div style={{ wordBreak: "break-all" }} className="break-all mb-3">
signature:{signature}
</div>
<input
className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg my-4`}
type="submit"
value="signTransaction"
/>
</div>
</form>
<div className="w-full p-5 bg-white rounded-md my-4">
<h2 className="text-center text-lg font-bold mb-3">
SendTransaction - Controlled Components
</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>
<button
className={`w-full h-[48px] rounded-full bg-[#3894ff] text-white text-lg my-4`}
type="button"
onClick={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched sendTransaction");
connector.sendTransaction({
method: "eth_sendTransaction",
params: [txData],
});
}}
>
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={() => {
setClickToastVisible(true);
setTimeout(() => {
setClickToastVisible(false);
}, 500);
console.log("touched connect & sign");
connector.connect({ ret: PreventType.CLOSE });
connectFlag.current = true;
}}
>
connect & sign
</button>
</div>
)}
</div>
<Toast
message="Chain switched successfully"
isVisible={isToastVisible}
onClose={() => setToastVisible(false)}
/>
<Toast
message="Touched"
isVisible={isClickToastVisible}
onClose={() => setClickToastVisible(false)}
/>
</div>
);
}