Putting it together

Now we combine all the components that we previously setup in a setup function. It will be called once for Alice and once for Bob from main like this:

func main() {
	// Setup Alice and Bob.
	alice, bob := setup(RoleAlice), setup(RoleBob)
	// Run our example protocol: Bob Opens, Updates and Closes.
	if err := bob.openChannel(); err != nil {
		panic(fmt.Errorf("opening channel: %w", err))
	time.Sleep(100 * time.Millisecond) // Wait for Alice to be ready.
	if err := bob.updateChannel(); err != nil {
		panic(fmt.Errorf("updating channel: %w", err))
	if err := bob.closeChannel(); err != nil {
		panic(fmt.Errorf("closing channel: %w", err))
	// Wait for both nodes to stop.
	fmt.Println("Waiting for Alice")
	fmt.Println("Waiting for Bob")
func setup(role Role) *node {
	fmt.Println("Starting ", role)
	account, wallet, err := setupWallet(role)
	if err != nil {
		panic(fmt.Sprintf("setting up wallet: %v", err))
	transactor := createTransactor(wallet)

	_, contractBackend, err := connectToChain(transactor)
	if err != nil {
		panic(fmt.Sprintf("connecting to chain: %v", err))

	adjudicator, assetholder, err := setupContracts(role, contractBackend, account.Account)
	if err != nil {
		panic(fmt.Errorf("setting up contracts: %w", err))

	listener, bus, err := setupNetwork(role, account)
	if err != nil {
		panic(fmt.Errorf("setting up network: %w", err))

	funder := setupFunder(contractBackend, account.Account, assetholder)
	cl, err := client.New(cfg.addrs[role], bus, funder, adjudicator, wallet)
	if err != nil {
		panic(fmt.Errorf("creating client: %w", err))
	// Create the node that defines all event handlers for go-perun.
	node := &node{role: role, account: account, transactor: transactor,
		contractBackend: contractBackend, assetholder: assetholder, listener: listener,
		bus: bus, client: cl, ch: nil, done: make(chan struct{})}
	// Set the NewChannel handler.
	// Start Proposal- and UpdateHandlers.
	go cl.Handle(node, node)
	// Listen on incoming connections.
	go bus.Listen(listener)
	return node

Running the App

Now we can finally test if everything works together.

First start your local Ethereum blockchain specifying the block time, the number of accounts, and the mnemonic:

ganache-cli -b 5 -a 2 -m "pistol kiwi shrug future ozone ostrich match remove crucial oblige cream critic"

The chain is running when you see an output like this:

Ganache CLI v6.12.1 (ganache-core: 2.13.1)

Available Accounts
(0) 0x2EE1ac154435f542ECEc55C5b0367650d8A5343B (100 ETH)
(1) 0x70765701b79a4e973dAbb4b30A72f5a845f22F9E (100 ETH)

Private Keys
(0) 0xb691bc22c5a30f64876c6136553023d522dcdf0744306dccf4f034a465532e27
(1) 0xb5dc82fc5f4d82b59a38ac963a15eaaedf414f496a037bb4a52310915ac84097

HD Wallet
Mnemonic:      pistol kiwi shrug future ozone ostrich match remove crucial oblige cream critic
Base HD Path:  m/44'/60'/0'/0/{account_index}

Gas Price

Gas Limit

Call Gas Limit

Listening on

You can see Alice’ and Bobs addresses starting with 0x2EE… and 0x707… having both 100 ETH.

Now run the tutorial application via the following command:

go run .

If everything works, you should see the following output:

Starting  Alice
Deployed contracts
 Adjudicator at 0x079557d7549d7D44F4b00b51d2C532674129ed51
 AssetHolder at 0x923439be515b6A928cB9650d70000a9044e49E85
Setting up listener for
Starting  Bob
Validated contracts
 Adjudicator at 0x079557d7549d7D44F4b00b51d2C532674129ed51
 AssetHolder at 0x923439be515b6A928cB9650d70000a9044e49E85
Setting up listener for
Opening channel from Bob to Alice
Received channel proposal from 0x307837303736353730316237396134653937336441626234623330413732663561383435663232463945
Alice HandleNewChannel with id 0x644eba7aca469e34229de7b7f0ce12f31ef4bc30f17a35a7d95412e2d64296d0
Accepted channel with id 0x644eba7aca469e34229de7b7f0ce12f31ef4bc30f17a35a7d95412e2d64296d0
Bob HandleNewChannel with id 0x644eba7aca469e34229de7b7f0ce12f31ef4bc30f17a35a7d95412e2d64296d0
🎉 Opened channel with id 0x644eba7aca469e34229de7b7f0ce12f31ef4bc30f17a35a7d95412e2d64296d0
Alice HandleUpdate Bals=[15000000000000000000, 5000000000000000000]
HandleAdjudicatorEvent called id=0x644eba7aca469e34229de7b7f0ce12f31ef4bc30f17a35a7d95412e2d64296d0
HandleAdjudicatorEvent called id=0x644eba7aca469e34229de7b7f0ce12f31ef4bc30f17a35a7d95412e2d64296d0
Waiting for Alice
Waiting for Bob


Running the code twice will produce different addresses since the state of the chain changed.
Always restart the chain if you need a deterministic testing environment.

The ganache-cli window will output the two deploy transactions. The first for ~2M gas is the Adjudicator

Transaction: 0xe3b4e79293d042e264224fe64f1028e4a1c7da02ba8d63f9a125081479e83d2f
Contract created: 0x079557d7549d7d44f4b00b51d2c532674129ed51
Gas usage: 2090042
Block Number: 1
Block Time: Wed Jan 13 2021 16:40:28 GMT+0100 (Central European Standard Time)

and then the AssetHolder for ~870K gas

Transaction: 0x1fb382d4640a1fa986a1d2c6451dfbcb5d86bd00ac05bc0a091294c002fb0c09
Contract created: 0x923439be515b6a928cb9650d70000a9044e49e85
Gas usage: 870103
Block Number: 2
Block Time: Wed Jan 13 2021 16:40:29 GMT+0100 (Central European Standard Time)


The transactions are really quick on the local chain. On a testnet or main-chain each transaction would take about 15 seconds.