Prerequisites
- XDEFI (opens in a new tab) wallet extension installed in your browser (opens in a new tab)
- Unisat (opens in a new tab) wallet extension
- Bitcoin Testnet wallet with some testnet BTC (use one of the faucets (opens in a new tab) available)
- Node.js (opens in a new tab) installed on your machine with
npmoryarn - Learn how to Deposit and call ZetaChain's EVM contracts from Bitcoin.
Create a new project
Initialize a new project:
npm init -yInstall a simple HTTP server:
yarn add --dev http-serverHTML Page with a Simple Form
Create a page with a simple form:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bitcoin</title>
</head>
<body>
<input type="number" id="amount" placeholder="Amount in tBTC" />
<input type="text" id="contract" placeholder="Omnichain contract address" />
<input type="text" id="message" placeholder="Contract call parameters" />
<select id="walletSelect">
<option value="xdefi">XDEFI</option>
<option value="unisat">Unisat</option>
</select>
<button onclick="sendTransaction()">Send transaction</button>
<script src="./script.js"></script>
</body>
</html>This HTML page contains a simple form with three inputs:
- Amount - the amount of tBTC to send
- Contract - the address of the omnichain contract to call
- Params - the parameters to pass to the contract call
- Wallet Select - a dropdown with a list of available wallets
- Send transaction - a button that will call the
sendTransactionfunction
Send Transaction Handler
To call an omnichain contract from Bitcoin you need to send a token transfer transaction to the ZetaChain's TSS address on Bitcoin with a memo that conforms to the required format.
The memo is composed of two parts:
- The first part is the contract address encoded as hex string (without the
0xprefix). This is the address of the omnichain contract you want to call. - The second part is the message encoded as hex string (without the
0xprefix). This is the message you want to pass to the contract call.
For example, if the omnichain contract address is
0xc79E6DC99C5928C5b08ae7a0f79412521996938e and it expects to receive one
argument that happens to be an address
0x3724C896Cdc958611873D81547A98565c8cb849d, the memo passed to the wallet
would look like:
c79E6DC99C5928C5b08ae7a0f79412521996938e3724C896Cdc958611873D81547A98565c8cb849dThe list of TSS address is available in the docs, for the purposes of this tutorial we will use a hard-coded value.
Get the contract address and parameters from the form and ensure that the 0x
prefix is removed.
The amount is passed in tBTC, so we need to multiply it by 1e8 to convert it
to satoshis.
async function sendTransaction() {
const tss = "tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur";
const contract = document.getElementById("contract").value.replace(/^0x/, "");
if (contract.length !== 40) return alert("Not a valid contract address");
const message = document.getElementById("message").value.replace(/^0x/, "");
const amount = parseFloat(document.getElementById("amount").value) * 1e8;
if (isNaN(amount)) return alert("Amount must be a number");
const params = { contract, message, amount, tss };
const wallet = document.getElementById("walletSelect")?.value;
switch (wallet) {
case "unisat":
await useUnisat(params);
break;
case "xdefi":
await useXDEFI(params);
break;
}
}Use XDEFI Wallet
XDEFI injects itself into the window object, so we can access it from the
window.xfi property. Use the getAccounts method to get the list of accounts
and use the first one.
For XDEFI to properly encode the memo, you need to prefix it with hex::.
async function useXDEFI(p) {
if (!window.xfi) return alert("XDEFI wallet not installed");
const wallet = window.xfi;
window.xfi.bitcoin.changeNetwork("testnet");
const account = (await wallet?.bitcoin?.getAccounts())?.[0];
if (!account) return alert("No account found");
const tx = {
method: "transfer",
params: [
{
feeRate: 10,
from: account,
recipient: p.tss,
amount: {
amount: p.amount,
decimals: 8,
},
memo: `hex::${p.contract}${p.message}`,
},
],
};
window.xfi.bitcoin.request(tx, (err, res) => {
if (e) {
return alert(`Couldn't send transaction, ${JSON.stringify(err)}`);
} else if (res) {
return alert(`Broadcasted a transaction, ${JSON.stringify(res)}`);
}
});
}Call the request method on the window.xfi.bitcoin object to send the
transaction.
Now that we have the from address (your Bitcoin address), recipient address
(ZetaChain's TSS address), amount and a properly formatted memo, call
window.xfi.bitcoin.request to create and broadcast a transaction.
Learn more about how to use XDEFI programmatically in the XDEFI docs (opens in a new tab).
Use Unisat Wallet
Unisat injects itself into the window object, so we can access it from the
window.unisat property. Use the requestAccounts method to get the list of
accounts.
Prepare the memo by concatenating the contract address and the message and converting the resulting string to lower case.
Use unisat.sendBitcoin() to make a token transfer to the TSS address.
async function useUnisat(p) {
if (!window.unisat) return alert("Unisat wallet not installed");
try {
await window.unisat.requestAccounts();
const memos = [`${p.contract}${p.message}`.toLowerCase()];
const tx = await unisat.sendBitcoin(p.tss, p.amount, { memos });
return alert(`Broadcasted a transaction: ${JSON.stringify(tx)}`);
} catch (e) {
return alert(`Couldn't send transaction, ${JSON.stringify(e)}`);
}
}Send a Transaction
Start the HTTP server:
npx http-serverOpen the page in your browser and fill in the form. You can test functionality with your own contract address or follow one of the provided tutorials, for example, the Staking tutorial to deploy a contract that you can call from Bitcoin.
Fill out the form and click the "Send transaction" button. You will be prompted to confirm the transaction.
Once the transaction is confirmed, you will see the transaction hash in an alert window. You can use a Bitcoin block explorer like Blockstream (opens in a new tab) to check the transaction status.
Once the transaction is confirmed on Bitcoin, you can check the transaction
status on ZetaChain using the cctx command from the Hardhat
template (opens in a new tab).