Interacting with universal contracts on ZetaChain from Bitcoin happens through the Bitcoin Gateway, a threshold signature scheme (TSS) address. The private key to this address is distributed among ZetaChain's validator set using MPC.
The Bitcoin Gateway supports the following operations:
- Deposit: Send BTC to a ZetaChain account or contract.
- Call: Trigger a smart contract on ZetaChain using a BTC transaction.
- Deposit and Call: Deposit BTC and immediately invoke a contract.
There are two ways to interact with the Bitcoin Gateway:
Method | Max Payload | Cost | Revert Address | Best For |
---|---|---|---|---|
Inscriptions | 400 KB* | Higher (2 tx) | Customizable | Structured cross-chain calls, custom logic |
OP_RETURN | 60 bytes** | Lower (1 tx) | Matches sender | Simple deposits and small data payloads |
* Limited only by Bitcoin's transaction and witness size limits. Typical payloads range from 1–30 KB; larger payloads may not relay through standard nodes.
** Not counting the required 20 bytes for the universal contract address.
📝 Recommended Usage
For most call and deposit and call operations, use inscriptions with ABI encoding. This supports structured data, complex contract interactions, and custom revert behavior.
For simple deposit operations, especially to EOAs, you can use OP_RETURN, which is lower cost and easier to construct.
Inscription Overview ⚡️
Inscriptions enable rich interaction between Bitcoin and ZetaChain by embedding structured metadata into Bitcoin transactions using a commit-reveal flow. This method encodes ABI data and optional Bitcoin revert logic into the Bitcoin blockchain.
Commit and Reveal Each interaction consists of two Bitcoin transactions:
- Commit: Encodes the payload as a Taproot-inscribed output. It commits to the data but doesn't reveal it yet.
- Reveal: Broadcasts the actual data that was committed to, including logic for contract interaction on ZetaChain.
✉️ Envelope Format (Witness Script)
OP_PUSHBYTES_32 <32-byte public key> OP_CHECKSIG
OP_FALSE
OP_IF
OP_PUSH 0x...
OP_PUSH 0x...
OP_ENDIF
🧩 Payload Format
The inscription data consists of: a 4-byte ZetaChain header and ABI- or Compact-encoded fields (depending on format).
Header
Byte Index | Description |
---|---|
0 | Fixed identifier: 0x5a (ASCII 'Z' ) for ZetaChain inscriptions |
1 | Encoding format (lower nibble). Example: 0x00 = ABI, 0x01 = CompactShort, 0x02 = CompactLong |
2 | Operation code (upper nibble). Example: 0x10 for Call = 0x02 << 4 |
3 | Flags bitmask. Indicates which fields are set. Common value: 0x07 (receiver + payload + revert) |
Fields
Fields are encoded differently based on the encoding format.
Format | Value |
---|---|
EncodingFmtABI | 0b0000 |
EncodingFmtCompactShort | 0b0001 |
EncodingFmtCompactLong | 0b0010 |
Compact encoding is space-efficient and can be useful when optimizing
transaction size. Use CompactShort
when all dynamic fields (payload and revert
address) are under 255 bytes. Use CompactLong
when any field may exceed that
threshold.
ABI encoding
For calls involving structured input, ZetaChain uses Ethereum-style ABI encoding. This allows full compatibility with Solidity contracts. You can pass complex types like address, bytes, uint256[], etc., and encode them client-side before embedding them in the inscription.
- Receiver address: A 20-byte Ethereum-style address of the ZetaChain account or universal contract.
- Payload: Optional encoded data (e.g., an ABI-encoded function call) for use in the contract’s onCall handler.
- Revert address (optional): A Bitcoin address to return funds if the cross-chain call fails.
Field | Value |
---|---|
Header | 4 bytes |
ABI data | abi.encode(receiver, payload, revertAddress) |
Note: the ABI-encoded data must exclude the 4-byte function selector. Only the packed argument values should be included in the payload.
Compact encoding
Each field is encoded in a more concise form:
[receiver (20 bytes)] + [len][payload bytes] + [len][revert address bytes]
- Receiver is raw 20 bytes
- Payload and Revert Address are length-prefixed:
- CompactShort: 1-byte length prefix (max 255 bytes)
- CompactLong: 2-byte length prefix (max 65535 bytes)
Field | Value |
---|---|
Header | 4 bytes |
Receiver | 20 bytes |
Payload | [len:1 or 2] + bytes |
Revert | [len:1 or 2] + address bytes |
🔁 Operation Types (OpCode)
Operation | Code | Description |
---|---|---|
Deposit | 0b0010 | Only receiver, no payload. Optional revert address |
DepositAndCall | 0b0000 | Transfers BTC and invokes onCall() with payload. Revert required |
Call | 0b0001 | Sends no BTC, invokes onCall() with payload. Revert optional |
Invalid | 0b0011 | Reserved |
Inscription: Deposit
- No call data is included.
- BTC is deposited to the ZRC-20 equivalent on ZetaChain.
- Useful when transferring BTC as ZRC-20 BTC to an EOA on ZetaChain
📌 Example:
Inscription: Call
- The encoded data includes:
- Contract address (as receiver)
- Payload
- No BTC is transferred to the contract — it's a logic-only interaction.
- Ideal for triggering universal contract execution that does not require BTC
📌 Example:
Inscriptions: Deposit and Call
- Combines the previous two:
- BTC is transferred and
- A contract function is invoked with encoded parameters.
- Enables rich interactions like "send BTC and trigger a swap", "deposit and mint", or any other cross-chain composable logic.
📌 Example:
When to Use Inscriptions
Use inscriptions when:
- Your payload exceeds 60 bytes (which OP_RETURN can’t handle)
- You need to encode structured ABI arguments
- You want to specify a custom revert address for fail-safety
- You’re triggering logic, not just transferring BTC
Memo (OP_RETURN) Overview 💾
This method involves sending a standard Bitcoin transaction with an OP_RETURN
output that encodes the recipient (universal contract or EOA) and an optional
short message.
To initiate a cross-chain transaction from Bitcoin, the transaction must have at least two outputs:
- First output: BTC amount sent to the Bitcoin Gateway (TSS) address.
- Second output:
OP_RETURN PUSH_x [DATA]
⚠️ If the transaction does not include both required outputs in the correct order, ZetaChain will not initiate the cross-chain transaction. The BTC will still be sent to the Gateway address, but no smart contract call or token minting will occur.
Memo: Deposit
To deposit BTC as ZRC-20 BTC to an EOA or a universal contract on ZetaChain:
[DATA] = [EOA or contract address (20 bytes)]
📌 Example:
Memo: Call
To call a universal contract on ZetaChain:
[DATA] = [contract address (20 bytes)] + [call payload (max 60 bytes)]
This will execute the onCall
method on the target contract.
⚠️ If your payload is larger than 60 bytes consider using inscriptions instead.
Memo: Deposit and Call
To deposit BTC and call a universal contract on ZetaChain:
[DATA] = [contract address (20 bytes)] + [call payload (max 60 bytes)]
Fees
Unlike EVM-based chains, each deposited Bitcoin output incurs a fee when it is spent. To address this, both the depositor and the withdrawer share the cost of the spend. This fee is charged in advance as a deposit fee.
The Bitcoin deposit fee is calculated with the following formula:
depositFee = (txFee / txVsize) * 68 vB * 2
Where:
txFee = totalInputValue - totalOutputValue
txVsize
is the virtual size of the Bitcoin transaction.