Skip to content

Instantly share code, notes, and snippets.

@palexs
Last active November 16, 2022 08:12
Show Gist options
  • Save palexs/bfa1fab4c0eef57a4a197d73cdaf178b to your computer and use it in GitHub Desktop.
Save palexs/bfa1fab4c0eef57a4a197d73cdaf178b to your computer and use it in GitHub Desktop.
Ultimate Intro to Ethereum Ðapp Development Course Synopsis

1. Provisioning the Development Environment

brew install node
npm install -g ethereumjs-testrpc
package.json:
{
	"dependencies": {
		"web3": "0.17.0-alpha"
	}
}
testrpc // http://localhost:8545
node // node console
var Web3 = require("web3")
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
web3.eth.accounts

2. Creating Ethereum Keypairs

package.json:
{
	"dependencies": {
		"web3": "0.17.0-alpha",
		"ethereumjs-util": "4.5.0"
	}
}
testrpc // http://localhost:8545
node // node console
var Web3 = require("web3")
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
web3.sha3("This is my secret")
// '0x754cd47e34c9f614f9f395463a016ee13a774ea969e17fc345d4dc57685fcb15' // private key
node ./keypairs.js 754cd47e34c9f614f9f395463a016ee13a774ea969e17fc345d4dc57685fcb15
// 0x2c84edeffb46141befe2bffe3ae510acecabfcb1 // Ethereum address
// keypairs.js
var EthUtil = require("ethereumjs-util")

var hexToBytes = function(hex) {
  for (var bytes = [], c = 0; c < hex.length; c+=2)
  bytes.push(parseInt(hex.substr(c, 2), 16));
  return bytes;
}

var privateKeyToAddress = function(privateKey) {
  return `0x${EthUtil.privateToAddress(hexToBytes(privateKey)).toString('hex')}`
}

console.log(privateKeyToAddress(process.argv[2]))

3. The Halting Problem and Why We Need Gas

web3.toWei(20, "gwei")
web3.fromWei(100000, "ether")

4. Introduction to Transactions

package.json:
{
	"dependencies": {
		"web3": "0.17.0-alpha",
		"ethereumjs-util": "4.5.0",
		"ethereumjs-tx": "1.1.2"
	}
}
testrpc // http://localhost:8545
node // node console
var Web3 = require("web3")
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
web3.eth.getBalance(web3.eth.accounts[0])
web3.fromWei(web3.eth.getBalance(web3.eth.accounts[0]), "ether")
web3.fromWei(web3.eth.getBalance(web3.eth.accounts[0]), "ether").toNumber()
web3.eth.sendTransaction({
	from: web3.eth.accounts[0],
	to: web3.eth.accounts[1],
	value: web3.toWei(1, "ether"),
	gasLimit: 21000,
	gasPrice: 20000000000
})
// '0x2dad8a5f98d1c3d1571a88fe1b2576988d2a104f88c894a8500cc4f06f6a1fea' // transaction id
web3.eth.getTransaction("0x2dad8a5f98d1c3d1571a88fe1b2576988d2a104f88c894a8500cc4f06f6a1fea")
web3.eth.getTransactionCount(web3.eth.accounts[0]) // could be used as next nonce value
var EthTx = require("etheremjs-tx")
var pKey1x = new Buffer("c16d3d2ff6bd28f830f26a69e85db439cb131c51e4c37fb9ef5278e003761d35", "hex")
var rawTx = {
	nonce: web3.toHex(web3.eth.getTransactionCount(web3.eth.accounts[0])),
	to: web3.eth.accounts[1],
	gasPrice: web3.toHex(20000000000),
	gasLimit: web3.toHex(21000),
	value: web3.toHex(web3.toWei(25, "ether")),
	data:""
}
var tx = new EthTx(rawTx)
tx.sign(pKey1x)
tx.serialize().toString("hex")
web3.eth.sendRawTransaction(`0x${tx.serialize().toString("hex")}`, (error, transactionId) => {})

5. Sending Transactions with User Interfaces

6. Sending Transactions with Code

Use an endpoint generated by infura.io to get access to main Etehreum network. Use https://etherscan.io/ to browse through transactions.

7. Smart Contracts - Hello World

package.json:
{
	"dependencies": {
		"web3": "0.17.0-alpha",
		"solc": "^0.4.4"
	}
}
testrpc // http://localhost:8545
node // node console
var Web3 = require("web3")
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
var solc = require("solc")
// HelloWorld.sol
contract HelloWorld {
  
  function displayMessage() constant returns (string) {
    return "Hello from a smart contract";
  }
  
}
var source = "" // contract source code
var compiled = solc.compile(source)
compiled.contracts[':HelloWorld'].bytecode // EVM bytecode
compiled.contracts[':HelloWorld].opcodes
var abi = JSON.parse(compiled.contracts[':HelloWorld].interface)
var helloWorldContract = web3.eth.contract(abi)
// deploy contract to testrpc
var deployed = helloWorldContract.new({
	from: web3.eth.accounts[0],
	data: compiled.contracts.HelloWorld.bytecode,
	gas: 4700000, // use https://github.com/ethereum/browser-solidity
	gasPrice: 20000000000,
}, (error, contract) => {})

deployed.address // Deployed contract will have its own address (see testrpc output)
deployed.displayMessage.call()

8. Smart Contracts - Escrow

package.json:
{
	"dependencies": {
		"web3": "0.17.0-alpha",
		"solc": "^0.4.4"
	}
}
// Escrow.sol
contract Escrow {

  address public buyer;
  address public seller;
  address public arbiter;
  
  function Escrow(address _seller, address _arbiter) {
    buyer = msg.sender;
    seller = _seller;
    arbiter = _arbiter;
  }
  
  function payoutToSeller() {
    if(msg.sender == buyer || msg.sender == arbiter) {
      seller.send(this.balance);
    }
  }
  
  function refundToBuyer() {
    if(msg.sender == seller || msg.sender == arbiter) {
      buyer.send(this.balance);
    }
  }
  
  function getBalance() constant returns (uint) {
    return this.balance;
  }

}
var source = "" // contract source code
var buyer = web3.eth.accounts[0]
var seller = web3.eth.accounts[1]
var arbiter = web3.eth.accounts[2]
var compiled = solc.compile(source)
var bytecode = compiled.contracts[':Escrow'].bytecode
var abi = JSON.parse(compiled.contracts[':Escrow'].interface)
var escrowContract = web3.eth.contract(abi)
var deployed = escrowContract.new(seller, arbiter, {
	from: buyer,
	data: bytecode,
	gas: 4700000,
	gasPrice: 20000000000,
	value: web3.toWei(5, "ether")
}, (error, contract) => {})

deployed.address
deployed.buyer.call() // calling public getter

var balance = (account) => { return web3.fromWei(web3.eth.getBalance(account), "ether").toNumber() }
balance(deployed.address)
deployed.payoutToSeller({ from: buyer })

9. Smart Contracts - Coin Flipper (1/2)

// Config
global.config = {
  rpc: {
    host: "localhost",
    port: "8545"
  }
}

// Load Libraries
global.solc = require("solc")
global.fs = require("fs")
global.Web3 = require("web3")

// Connect Web3 Instance
global.web3 = new Web3(new Web3.providers.HttpProvider(`http://${global.config.rpc.host}:${global.config.rpc.port}`))

// Global Account Accessors
global.acct1 = web3.eth.accounts[0]
global.acct2 = web3.eth.accounts[1]
global.acct3 = web3.eth.accounts[2]
global.acct4 = web3.eth.accounts[3]
global.acct5 = web3.eth.accounts[4]

// Helper Functions
class Helpers {

  contractName(source) {
    var re1 = /contract.*{/g
    var re2 = /\s\w+\s/
    return source.match(re1).pop().match(re2)[0].trim()
  }

  createContract(source, options={}) {
    var compiled = solc.compile(source)
    var contractName = this.contractName(source)
	var contractToDeploy = compiled.contracts[`:${contractName}`]
	var bytecode = contractToDeploy.bytecode
	var abi = JSON.parse(contractToDeploy.interface)
	var contract = global.web3.eth.contract(abi)
	var gasEstimate = global.web3.eth.estimateGas({ data: bytecode })

    var deployed = contract.new(Object.assign({
      from: global.web3.eth.accounts[0],
      data: bytecode,
      gas: gasEstimate,
      gasPrice: 5
    }, options), (error, result) => { })

    return deployed
  }

  loadContract(name) {
    var path = `./${name.toLowerCase()}.sol`
    return fs.readFileSync(path, 'utf8')
  }

  deployContract(name, options={}) {
    var source = this.loadContract(name)
    return this.createContract(source, options)
  }

  etherBalance(contract) {
    switch(typeof(contract)) {
      case "object":
        if(contract.address) {
          return global.web3.fromWei(global.web3.eth.getBalance(contract.address), 'ether').toNumber()
        } else {
          return new Error("cannot call getEtherBalance on an object that does not have a property 'address'")
        }
        break
      case "string":
        return global.web3.fromWei(global.web3.eth.getBalance(contract), 'ether').toNumber()
        break
    }
  }

}

// Load Helpers into Decypher namespace
global.decypher = new Helpers()

// Start repl
require('repl').start({})
contract Flipper {

  enum GameState {noWager, wagerMade, wagerAccepted}
  GameState public currentState;

  modifier onlyState(GameState expectedState) { if(expectedState == currentState) { _; } else { throw; } }

  function Flipper() {
    currentState = GameState.noWager;
  }

  function makeWager() onlyState(GameState.noWager) returns (bool) {
    // ...
    currentState = GameState.wagerMade;
    return true;
  }

  function acceptWager() onlyState(GameState.wagerMade) returns (bool) {
    // ...
    currentState = GameState.wagerAccepted;
    return true;
  }

  function resolveBet() onlyState(GameState.wagerAccepted) returns (bool) {
    // ...
    currentState = GameState.noWager;
    return true;
  }

}

10. Smart Contracts - Coin Flipper (2/2)

web3.eth.blockNumber
web3.eth.getBlock(3)
contract Flipper {

  enum GameState {noWager, wagerMade, wagerAccepted}
  GameState public currentState;
  uint public wager;
  address public player1;
  address public player2;
  uint public seedBlockNumber;

  modifier onlyState(GameState expectedState) { if(expectedState == currentState) { _; } else { throw; } }

  function Flipper() {
    currentState = GameState.noWager;
  }

  function makeWager() onlyState(GameState.noWager) payable returns (bool) {
    wager = msg.value;
    player1 = msg.sender;
    currentState = GameState.wagerMade;
    return true;
  }

  function acceptWager() onlyState(GameState.wagerMade) payable returns (bool) {
    if(msg.value == wager) {
      player2 = msg.sender;
      seedBlockNumber = block.number;
      currentState = GameState.wagerAccepted;
      return true;
    } else {
      throw;
    }
  }

  function resolveBet() onlyState(GameState.wagerAccepted) returns (bool) {
    uint256 blockValue = uint256(block.blockhash(seedBlockNumber));
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    uint256 coinFlip = uint256(uint256(blockValue) / FACTOR); // Either 0 or 1 (50%/50%)

    if(coinFlip == 0) {
      player1.send(this.balance);
    } else {
      player2.send(this.balance);
    }
    currentState = GameState.noWager;
    return true;

  }

}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment