Skip to content

Latest commit

Β 

History

83 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🚩 Challenge 5: πŸ“Ί A State Channel Application

readme-5

🐌 The Ethereum blockchain has great decentralization & security properties. These properties come at a price: transaction throughput is low, and transactions can be expensive (search term: blockchain trilemma). This makes many traditional web applications infeasible on a blockchain... or does it?

🍰 A number of approaches to scaling have been developed, collectively referred to as layer-2s (L2s). Among them is the concept of payment channels, state channels, and state channel networks. This tutorial walks through the creation of a simple state channel application, where users seeking a service lock collateral on-chain with a single transaction, interact with their service provider entirely off-chain, and finalize the interaction with a second on-chain transaction.

πŸ§‘β€πŸ€β€πŸ§‘ State channels really excel as a scaling solution in cases where a fixed set of participants want to exchange value-for-service at high frequency. The canonical example is in file sharing or media streaming: the server exchanges chunks of a file in exchange for micropayments.

πŸ§™ In our case, the service provider is a Guru who provides off-the-cuff wisdom to each client Rube through a one-way chat box. Each character of text that is delivered is expected to be compensated with a payment of 0.01 ETH.

πŸ“– Read more about state channels in the Ethereum Docs.

❗ OpenZepplin's ECDSA Library provides an easy way to verify signed messages, but for this challenge we'll write the code ourselves.

πŸ… Main Quests

  • πŸ›£οΈ Build a packages/hardhat/contracts/Streamer.sol contract that collects ETH from numerous client addresses using a payable fundChannel() function and keeps track of balances.
  • πŸ’΅ Exchange paid services off-chain between the packages/hardhat/contracts/Streamer.sol contract owner (the Guru) and rube clients with funded channels. The Guru provides the service in exchange for signed vouchers which can later be redeemed on-chain.
  • ⏱ Create a Challenge mechanism with a timeout, so that rubes are protected from a Guru who goes offline while funds are locked on-chain (either by accident, or as a theft attempt).
  • ⁉ Consider some security / usability holes in the current design.

πŸ’¬ Meet other builders working on this challenge and get help in the State Channel Telegram!


Checkpoint 0: πŸ“¦ Environment πŸ“š

Before you begin, you need to install the following tools:

Then download the challenge to your computer and install dependencies by running:

git clone https://github.com/scaffold-eth/se-2-challenges.git challenge-5-state-channels
cd challenge-5-state-channels
git checkout challenge-5-state-channels
yarn install

in the same terminal, start your local network (a blockchain emulator in your computer):

yarn chain

in a second terminal window, πŸ›° deploy your contract (locally):

cd challenge-5-state-channels
yarn deploy

in a third terminal window, start your πŸ“± frontend:

cd challenge-5-state-channels
yarn start

πŸ“± Open http://localhost:3000 to see the app.

πŸ‘©β€πŸ’» Rerun yarn deploy whenever you want to deploy new contracts to the frontend. If you haven't made any contract changes, you can run yarn deploy --reset for a completely fresh deploy.


Checkpoint 1: πŸ§˜β€β™€οΈ Configure Guru & Rube πŸ“ƒ

Like the token vendor challenge, we'll be building an Ownable contract. The contract owner is the Guru (the service provider in this application), and you will use multiple browser windows or tabs to assume the roles of Guru and rube (service provider & client).

πŸ‘ contract Streamer inherits Ownable with the is keyword. Ownable comes from openzeppelin-contracts - a collection of high quality smart contract library code.

πŸ“ In packages/hardhat/deploy/00_deploy_streamer.js, uncomment the lines of code that deploy the contract and transfer ownership. You will need to enter your own frontend address, to act as Guru.

You'll have to redeploy with yarn deploy --reset.

We'll need another active address to act as the rube in our app. To do this just open a new tab in your browser.

⚠️ Note: previous challenges created new addresses by opening an incognito window or a different browser. This will not work for this challenge, because the off-chain application uses a very simple communication pipe that doesn't work between different browsers or private windows.

wallets-setup

πŸ₯… Goals:

  • Does your original frontend address receive the Hello Guru UI?
  • Does your alternate addresses receive the Hello Rube UI?

Checkpoint 2: πŸ’Έ Fund a Channel πŸ“Ί

Like the decentralized staking challenge, we'll track balances for individual channels / users in a mapping:

mapping (address => uint256) balances;

Rubes seeking wisdom will use a payable fundChannel() function, which will update this mapping with the supplied balance.

πŸ“ Edit packages/hardhat/contracts/Streamer.sol to complete the fundChannel() function

πŸ‘ Check packages/nextjs/app/streamer/page.tsx to see the frontend calling this function. (ctrl-f fundChannel)

Run yarn deploy and open a channel in the Rube's tab. (You may need some funds from the faucet)

channel-open

πŸ₯… Goals:

  • Does opening a channel cause a Received Wisdom box to appear?
  • Do opened channels appear on the Guru's UI as well?
  • Using the Debug Contracts tab, does a repeated call to fundChannel fail?

Checkpoint 3: πŸ’± Exchange the Service πŸ‘·β€β™‚οΈ

Now that the channel is funded and all participants have observed the funding via the emitted event, we can begin our off-chain exchange of service. We are now working in packages/nextjs/app/streamer/page.tsx.

Functions of note:

  • provideService: The Guru sends wisdom over the wire to the client.
  • reimburseService: The rube creates a voucher for the received service, signs it, and returns it.
  • processVoucher: The service provider receives and stores vouchers.

The first two functions are complete - we will work on processVoucher, where the service provider examines returned payments, confirms their authenticity, and stores them.

πŸ“ Edit packages/nextjs/app/streamer/page.tsx to complete the processVoucher() function and secure this off-chain exchange. You'll need to recreate the encoded message that the client has signed, and then verify that the received signature was in fact produced by the client on that same data.

πŸ₯… Goals:

  • Secure your service! Validate the incoming voucher & signature according to instructions inside processVoucher()
  • With an open channel, start sending advice. Can you see the claimable balance update as service is rendered? This should happen only if rube has "Autopay" active.

guru-advice rube-received-advice

βš”οΈ Side Quest:

  • Can provideService be modified to prevent continued service to clients who don't keep up with their payments?

πŸ’¬ Hint: You'll want to compare the size of your best voucher against the size of your provided wisdom. If there's too big a discrepency, cut them off!

Checkpoint 4: πŸ’° Recover Service Provider's Earnings πŸ’΅

Now that we've collected some vouchers, we'd like to redeem them on-chain and move funds from the Streamer contract's balances map to the Guru's own address. The withdrawEarnings function of packages/hardhat/contracts/Streamer.sol takes a Struct named voucher (balance + signature) as input, and should:

  • Recover the signer using ecrecover(bytes32, uint8, bytes32, bytes32) on the prefixedHashed message and supplied signature.
    • Hint: ecrecover takes the signature in its decomposed form with v,,r, ands values. The string signature produced in packages/nextjs/app/streamer/page.tsx is just a concatenation of these values, which we split using ethers.Signature.from to create the on-chain friendly signature. Read about the ecrecover function here
  • Check that the signer has a running channel with balance greater than the voucher's updatedBalance
  • Calculate the payout (balances[signer] - updatedBalance)
  • Update the channel balance.
  • Send the payout to the Guru.

πŸ’‘ Reminders:

  • Changes to contracts must be redeployed to the local chain with yarn deploy --reset.
  • For troubleshooting / debugging, your contract can use hardhat's console.log, which will print to your console running the chain.

πŸ“ Edit packages/hardhat/contracts/Streamer.sol to complete the withdrawEarnings() function as described.

πŸ“ Edit packages/nextjs/app/streamer/page.tsx to enable the UI button for withdrawals.

πŸ₯… Goals:

  • Recover funds on-chain for services rendered! After the Guru submits a voucher to chain, you should be able to see the wallet's ETH balance increase.

eth-locked-updated guru-balance-updated

βš”οΈ Side Quest:

  • withdrawEarnings is a function that only the service provider would be interested in calling. Should it be marked onlyOwner? (the onlyOwner modifier makes a function accessible only to the contract owner - anyone else who tries to call it will be immediately rejected).

Checkpoint 5: πŸ’ͺ Challenge & Close the Channel πŸ”½

So far so good:

  • Rubes can connect to the Guru via an on-chain deposit.
  • The pair can then transact off-chain with high throughput.
  • The Guru can recover earnings with their received vouchers.

But what if a rube is unimpressed with the service and wishes to close a channel to recover whatever funds remain? What if the Guru is a no-show after the initial channel funding deposit?

A payment channel is a cryptoeconomic protocol - care needs to be taken so that everyone's financial interests are protected. We'll implement a two step challenge and close mechanism that allows rubes to recover unspent funds, while keeping the Guru's earnings safe.

πŸ“ Edit packages/hardhat/contracts/Streamer.sol to create a public challengeChannel() function.

πŸ“ Edit packages/nextjs/app/streamer/page.tsx to enable the challenge and closure buttons for service clients(rubes).

The challengeChannel() function should:

  • Check in the balances map that a channel is already open in the name of msg.sender
  • Declare this channel to be closing by setting canCloseAt[msg.sender] to block.timestamp + 30 seconds
  • Emit a Challenged event with the sender's address.

The emitted event gives notice to the Guru that the channel will soon be emptied, so they should apply whatever vouchers they have before the timeout period ends.

guru-alert

πŸ“ Edit packages/hardhat/contracts/Streamer.sol to create a public defundChannel() function.

The defundChannel() function should:

  • Check that msg.sender has a channel that can be closed, by ensuring a non-zero canCloseAt[msg.sender] is before the current timestamp.
  • Transfer balances[msg.sender] to the sender.
  • Emit a Closed event.

⚠ Make sure the defundChannel declaration is uncommented in packages\nextjs\app\streamer\page.tsx

πŸ₯… Goals:

  • Launch a challenge as a channel client. If wisdom has been given, the Guru's UI should show an alert via their Cash out latest voucher button.
  • Recover the Guru's best voucher before the channel closes.
  • Close the channel and recover rube funds.

close-channel-button

βš”οΈ Side Quests:

  • Currently, the service provider has to manually submit their vouchers after a challenge is registered on chain. Should their channel wallet do that automatically? Can you implement that in this application?
  • Suppose some rube enjoyed their first round of advice. Is it safe for them to open a new channel with packages/hardhat/contracts/Streamer.sol? (Hint: what data does the Guru still hold?)

⚠️ Test it!

  • Now is a good time to run yarn test to run the automated testing function. It will test that you hit the core checkpoints. You are looking for all green checkmarks and passing tests!

Checkpoint 6: πŸ’Ύ Deploy your contracts! πŸ›°

πŸ“‘ Edit the defaultNetwork to your choice of public EVM networks in packages/hardhat/hardhat.config.ts

πŸ” You will need to generate a deployer address using yarn generate This creates a mnemonic and saves it locally.

πŸ‘©β€πŸš€ Use yarn account to view your deployer account balances.

⛽️ You will need to send ETH to your deployer address with your wallet, or get it from a public faucet of your chosen network.

πŸš€ Run yarn deploy to deploy your smart contract to a public network (selected in hardhat.config.ts)

πŸ’¬ Hint: You can set the defaultNetwork in hardhat.config.ts to sepolia OR you can yarn deploy --network sepolia.


Checkpoint 7: 🚒 Ship your frontend! 🚁

✏️ Edit your frontend config in packages/nextjs/scaffold.config.ts to change the targetNetwork to chains.sepolia or any other public network.

πŸ’» View your frontend at http://localhost:3000 and verify you see the correct network.

πŸ“‘ When you are ready to ship the frontend app...

πŸ“¦ Run yarn vercel to package up your frontend and deploy.

Follow the steps to deploy to Vercel. Once you log in (email, github, etc), the default options should work. It'll give you a public URL.

If you want to redeploy to the same production URL you can run yarn vercel --prod. If you omit the --prod flag it will deploy it to a preview/test URL.

🦊 Since we have deployed to a public testnet, you will now need to connect using a wallet you own or use a burner wallet. By default πŸ”₯ burner wallets are only available on hardhat . You can enable them on every chain by setting onlyLocalBurnerWallet: false in your frontend config (scaffold.config.ts in packages/nextjs/)

Configuration of Third-Party Services for Production-Grade Apps.

By default, πŸ— Scaffold-ETH 2 provides predefined API keys for popular services such as Alchemy and Etherscan. This allows you to begin developing and testing your applications more easily, avoiding the need to register for these services.
This is great to complete your SpeedRunEthereum.

For production-grade applications, it's recommended to obtain your own API keys (to prevent rate limiting issues). You can configure these at:

  • πŸ”·ALCHEMY_API_KEY variable in packages/hardhat/.env and packages/nextjs/.env.local. You can create API keys from the Alchemy dashboard.

  • πŸ“ƒETHERSCAN_API_KEY variable in packages/hardhat/.env with your generated API key. You can get your key here.

πŸ’¬ Hint: It's recommended to store env's for nextjs in Vercel/system env config for live apps and use .env.local for local testing.


Checkpoint 8: πŸ“œ Contract Verification

Run the yarn verify --network your_network command to verify your contracts on etherscan πŸ›°

πŸ‘‰ Search this address on Etherscan to get the URL you submit to πŸƒβ€β™€οΈSpeedRunEthereum.com.


πŸƒ Head to your next challenge here.

πŸ’¬ Problems, questions, comments on the stack? Post them to the πŸ— scaffold-eth developers chat

Releases

Packages

Used by

Contributors

Languages