Deposit Assets

Early on in the lifecycle of a state channel -- i.e. after exchanging some setup states, but before executing any application logic -- participants will want to "fund it". They will stake assets on the channel so that the state updates are meaningful. The simplest way to do this is with an on chain deposit; a more advanced possibility is fund a new channel from an existing funded channel.

To start, let's explore the on chain deposit case. We will need to understand how channels are uniquely identified.

Compute a channel id

The id of a channel is the keccak256 hash of the abi encoded chainId, participants and channelNonce.

By choosing a new channelNonce each time the same participants execute a state channel supported by the same chain, they can avoid replay attacks. The getChannelId helper exported by @statechannels/nitro-protocol accepts an object of type Channel (which contains all the necessary properties) and computes the channel id for you.

// In lesson4.test.ts
// Import Ethereum utilities
import {Wallet} from 'ethers';
// Import statechannels utilities
import {Channel, getChannelId} from '@statechannels/nitro-protocol';
const participants = [];
for (let i = 0; i < 3; i++) {
// As many participants as you like
participants[i] = Wallet.createRandom().address;
}
const chainId = '0x1234'; // E.g. '0x1' for mainnnet. Using a mock here
const channelNonce = 0;
const channel: Channel = {chainId, channelNonce, participants};
const channelId = getChannelId(channel);

deposit into the ETH asset holder

The deposit method allows ETH to be escrowed against a channel. We have the following call signature:

function deposit(bytes32 destination, uint256 expectedHeld, uint256 amount) public payable

There are a few rules to obey when calling deposit. Firstly, destination must NOT be an external destination. Secondly, the on-chain holdings for destination must be greater than or equal to expectedHeld. Thirdly, the holdings for destination must be less than the sum of the amount expected to be held and the amount declared in the deposit.

The first rule prevents funds being escrowed against something other than a channelId: funds may only be unlocked from channels, so you shouldn't deposit into anything else. The second rule prevents loss of funds: since holdings are paid out in preferential order, depositing before a counterparty has deposited implies that they can withdraw your funds. The check is performed in the same transaction as the deposit, making this safe even in the event of a chain re-org that reverts a previous participant's deposit. The third rule prevents the deposit of uneccessary funds: if my aim was to increase the holdings to a certain level, but they are already at or above that level, then I want my deposit to transaction revert.

Because we are depositing ETH, we must remember to send the right amount of ETH with the transaction. Depositing ERC20 tokens will be covered in a future tutorial.

import {ethers} from 'ethers';
import {randomChannelId, randomChannelId} from '@statechannels/nitro-protocol';
// In lesson5.test.ts
/*
Get an appropriate representation of 1 wei, and
use randomChannelId() as a dummy channelId.
WARNING: don't do this in the wild: you won't be able to recover these funds.
*/
const amount = ethers.utils.parseUnits('1', 'wei');
const destination = randomChannelId();
/*
Attempt to deposit 1 wei against the channel id we created.
Inspect the error message in the console for a hint about the bug on the next line
*/
const expectedHeld = 0;
const tx0 = ETHAssetHolder.deposit(destination, expectedHeld, amount, {
value: amount,
});

Destinations

A Destination is a bytes32 and either:

  1. A ChannelId (see the section on channelId), or
  2. An ExternalDestination, which is an ethereum address left-padded with zeros.
tip

In JavaScript, the ExternalDestination corresponding to address may be computed as

'0x' + address.padStart(64, '0')

or

ethers.utils.hexZeroPad(address, 32);
Last updated on by geoknee