Streaming Bitcoin Payments

This example walks through an Ethereum smart contract that makes scheduled BTC payments.

In this example, we will explore using RenVM interoperability to implement a smart contract that can pay BTC to beneficiaries on a vesting schedule. The owner of the smart contract can add new vesting schedules, and beneficiaries can claim their payments once per month. The full source code for this example is available on GitHub.

BTC Deposits

The first thing that we need to understand is how the vesting contract acquires BTC.

Whenever a smart contract wants to accept token deposits, it will typically want to execute some business logic at the same time. In the case of our example vesting contract, the addVestingSchedule function accepts a BTC deposit and simultaneously adds a new vesting schedule for that deposited BTC.

Calling the Function

To understand exactly how the vesting contract accepts BTC deposits, let's take a look at the parameters of the addVestingSchedule function:

/// @notice Allows the contract owner to add a vesting schedule for a
/// beneficiary.
/// @param _beneficiary The address of the recipient entitled to claim the vested tokens.
/// @param _startTime The start time (in seconds since Unix epoch) at which the vesting
/// period should begin.
/// @param _duration The number of months for the vesting period.
/// @param _amount The amount of Bitcoin provided to the Darknodes in Sats.
/// @param _nHash The hash of the nonce returned by the Darknodes.
/// @param _sig The signature returned by the Darknodes.
function addVestingSchedule(
// Payload
address _beneficiary,
uint256 _startTime,
uint16 _duration,
// Required
uint256 _amount,
bytes32 _nHash,
bytes calldata _sig
) external onlyOwner {
// ...

First, the addVestingSchedule function defines its application specific parameters: beneficiary, startTime, and duration. These parameters are specific to this smart contract and make up its application-specific payload. These parameters are optional and will change depending on the application.

Second, the addVestingSchedule function defines its RenVM parameters: amount, nhash, and sig. These parameters are fundamental to shifting, and they must appear in the correct order.

  1. amount is the amount of SATs that will be transferred to the smart contract.

  2. nhash is a hash produced by RenVM that guarantees the uniqueness of the transfer.

  3. sig is a signature produced by RenVM that authorises the amount of SATs to be locked and minted to this smart contract.

To get these parameters, we need to talk to RenVM. During this process, we build a BTC deposit address, deposit BTC to it, and ask RenVM to verify it and produce the nhash and sig that we need. After doing this, we can call the addVestingSchedule function. Ren SDK can be used to do all of this:

import RenJS from "@renproject/ren";
import * as Chains from "@renproject/chains";
const ren = new RenJS('testnet');
const gateway = ren.lockAndMint({
// Send BTC from the Bitcoin blockchain to the Ethereum blockchain.
asset: "BTC",
from: Chains.Bitcoin(),
to: Chains.Ethereum(web3.currentProvider).Contract({
// The contract we want to interact with
sendTo: '0xC0D3C0D3C0D3C0D3C0D3C0D3C0D3C0D3C0D3C0D3',
// The name of the function we want to call
contractFn: "addVestingSchedule",
// Arguments expected for calling `deposit`
contractParams: [{
name: '_beneficiary',
type: 'address',
value: '0xABCDEF0123456879ABCDEF0123456879ABCDEF01' // Pay to this address
}, {
name: '_startTime',
type: 'uint256',
value: 0 // Start now
}, {
name: '_duration',
type: 'uint256',
value: 12 // Vest over 12 months
gateway.gatewayAddress().then(address => alert('Deposit BTC to: ' + address));
// Wait for the BTC transfer
// Send to RenVM
// Submit to Ethereum
// Process deopsits.
gateway.on("deposit", async (deposit) => {
await mint.confirmed()
.on("target", () => (confs, target) => this.log(`${confs}/${target}`));
.on("confirmation", (confs, target) => this.log(`${confs}/${target}`));
await mint.signed()
.on("status", (status) => this.log(`Status: ${status}`));
await mint.submit()
.on("transactionHash", (txHash) => this.log(`Mint tx: ${txHash}`));
this.log(`Deposited ${amount} BTC.`);

The gateway.gatewayAddress() returns a unique Bitcoin address (known as a gateway) to which the user must transfer the expected amount of BTC. This transfer can be done using any Bitcoin wallet. On RenVM Testnet, Bitcoin Testnet3 is used and even faucets can be used to deposit into a gateway.

This is all you need to do to integrate RenVM into your dApp web app. Although, you will want to display the Bitcoin gateway address in a more stylish way!

Transferring BTC

Now that we know how to call the addVestingSchedule function, we can look into exactly how it shifts BTC to itself. In the addVestingSchedule function we see:

// Construct the payload hash and shift in new tokens using the Shifter
// contract. This will verify the signature to ensure RenVM has
// received the BTC and authorised the shift.
bytes32 pHash = keccak256(abi.encode(_beneficiary, _startTime, _duration));
registry.getGatewayBySymbol("BTC").mint(pHash, _amount, _nHash, _sig);

The application-specific parameters are encoded into an application-specific payload of bytes and hashed into a phash (short for payload hash). This is combined with the amount, nhash, and sig before being sent to the BTC Gateway contract (retrieved from the GatewayRegistry) which will verify the signature, and send the amount of SATs to the vesting contract.

The order of the application-specific parameters must match the order in which they were define in the adVestingSchedule function. This must also be the same order in which they are passed to the Ren SDK.

BTC Withdrawals

Now that we know how to send BTC to the vesting contract, the next thing we need to know is how to get BTC out of the vesting contract. Once per month, the vesting contract will allow a beneficiary to call the claim function.

/// @notice Allows a beneficiary to withdraw their vested Bitcoin.
/// @param _to The Bitcoin address to which the beneficiary will receive
/// their Bitcoin.
function claim(bytes calldata _to) external {
// ...

There are no special parameters involved. The only parameter is to which defines the Bitcoin address (on the Bitcoin blockchain) to which the beneficiary wants to receive their BTC. Whilst the vesting contract takes this as a parameter, it could be provided in any other way (e.g. stored previously as the result of another function call).

The interesting part comes in the body of the claim function:

// Burn out the tokens using the Gateway contract. This will burn the
// tokens after taking a fee. RenVM will watch for this event to transfer
// the user the BTC.
registry.getGatewayBySymbol("BTC").burn(_to, amountClaimable);

This burns the ERC20 representation of BTC on Ethereum out of existence. The Darknodes that power RenVM keep an eye on the LogBurn events and in response will transfer the amountClaimable amount of SATs to the to address specified by the beneficiary.

It is possible that RenVM fails to see the LogBurn event and in such circumstances it can be explicitly notified about the event using the Ren SDK. It is recommended that all applications with frontend/backend services also monitor for LogBurn events so that they can ensure RenVM has seen it.


Check out the full source code available on Github: