├── .dockerignore ├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ └── build.yml ├── .gitignore ├── .node-version ├── .nvmrc ├── Dockerfile ├── LANGS.md ├── LICENSE ├── Makefile ├── README.md ├── assets ├── cover.jpg ├── cover.psd ├── cover_small.jpg ├── cover_small.psd ├── gopher.ai ├── gopher.png └── gopher.svg ├── book.json ├── code ├── LICENSE ├── README.md ├── abi_pack.go ├── account_balance.go ├── address.go ├── address_check.go ├── block_subscribe.go ├── blocks.go ├── client.go ├── client_simulated.go ├── contract_bytecode.go ├── contract_deploy.go ├── contract_load.go ├── contract_read.go ├── contract_read_erc20.go ├── contract_write.go ├── contract_write_estimate_gas.go ├── contracts │ ├── Store.go │ ├── Store.sol │ ├── Store_sol_Store.abi │ └── Store_sol_Store.bin ├── contracts_0xprotocol │ ├── Exchange.go │ ├── Exchange.sol │ └── Exchange.sol:Exchange.abi ├── contracts_erc20 │ ├── erc20.go │ ├── erc20.sol │ └── erc20_sol_ERC20.abi ├── event_read.go ├── event_read_0xprotocol.go ├── event_read_by_transaction.go ├── event_read_erc20.go ├── event_subscribe.go ├── go.mod ├── go.sum ├── helper │ ├── helper.go │ └── helper_test.go ├── keystore.go ├── rlp.go ├── rpc_call.go ├── rpc_client_version.go ├── signature_generate.go ├── signature_verify.go ├── swarm_download.go ├── swarm_upload.go ├── transaction_raw_create.go ├── transaction_raw_decode.go ├── transaction_raw_read.go ├── transaction_raw_send.go ├── transactions.go ├── transfer_eth.go ├── transfer_tokens.go ├── trie.go ├── txpool.go ├── util │ ├── util.go │ └── util_test.go ├── util_usage.go ├── wallet_generate.go ├── whisper_client.go ├── whisper_keypair.go ├── whisper_poll.go ├── whisper_send.go └── whisper_subscribe.go ├── en ├── GLOSSARY.md ├── README.md ├── SUMMARY.md ├── account-balance-token │ └── README.md ├── account-balance │ └── README.md ├── accounts │ └── README.md ├── address-check │ └── README.md ├── block-query │ └── README.md ├── block-subscribe │ └── README.md ├── client-setup │ └── README.md ├── client-simulated │ └── README.md ├── client │ └── README.md ├── cover.jpg ├── cover_small.jpg ├── event-read-0xprotocol │ └── README.md ├── event-read-erc20 │ └── README.md ├── event-read │ └── README.md ├── event-subscribe │ └── README.md ├── events │ └── README.md ├── faucets │ └── README.md ├── hd-wallet │ └── README.md ├── keystore │ └── README.md ├── resources │ └── README.md ├── signature-generate │ └── README.md ├── signature-verify │ └── README.md ├── signatures │ └── README.md ├── sitemap.xml ├── smart-contract-bytecode │ └── README.md ├── smart-contract-compile │ └── README.md ├── smart-contract-deploy │ └── README.md ├── smart-contract-load │ └── README.md ├── smart-contract-read-erc20 │ └── README.md ├── smart-contract-read │ └── README.md ├── smart-contract-write │ └── README.md ├── smart-contracts │ └── README.md ├── styles │ └── website.css ├── swarm-download │ └── README.md ├── swarm-setup │ └── README.md ├── swarm-upload │ └── README.md ├── swarm │ └── README.md ├── test │ └── README.md ├── transaction-query │ └── README.md ├── transaction-raw-create │ └── README.md ├── transaction-raw-send │ └── README.md ├── transactions │ └── README.md ├── transfer-eth │ └── README.md ├── transfer-tokens │ └── README.md ├── util-go │ └── README.md ├── util │ └── README.md ├── wallet-generate │ └── README.md ├── whisper-client │ └── README.md ├── whisper-keys │ └── README.md ├── whisper-poll │ └── README.md ├── whisper-send │ └── README.md ├── whisper-subscribe │ └── README.md └── whisper │ └── README.md ├── go.mod ├── go.sum ├── package.json ├── styles └── website.css ├── vendor └── modules.txt └── zh ├── GLOSSARY.md ├── README.md ├── SUMMARY.md ├── account-balance-token └── README.md ├── account-balance └── README.md ├── accounts └── README.md ├── address-check └── README.md ├── block-query └── README.md ├── block-subscribe └── README.md ├── client-setup └── README.md ├── client-simulated └── README.md ├── client └── README.md ├── cover.jpg ├── cover_small.jpg ├── event-read-0xprotocol └── README.md ├── event-read-erc20 └── README.md ├── event-read └── README.md ├── event-subscribe └── README.md ├── events └── README.md ├── faucets └── README.md ├── hd-wallet └── README.md ├── keystore └── README.md ├── resources └── README.md ├── signature-generate └── README.md ├── signature-verify └── README.md ├── signatures └── README.md ├── sitemap.xml ├── smart-contract-bytecode └── README.md ├── smart-contract-compile └── README.md ├── smart-contract-deploy └── README.md ├── smart-contract-load └── README.md ├── smart-contract-read-erc20 └── README.md ├── smart-contract-read └── README.md ├── smart-contract-write └── README.md ├── smart-contracts └── README.md ├── styles └── website.css ├── swarm-download └── README.md ├── swarm-setup └── README.md ├── swarm-upload └── README.md ├── swarm └── README.md ├── test └── README.md ├── transaction-query └── README.md ├── transaction-raw-create └── README.md ├── transaction-raw-send └── README.md ├── transactions └── README.md ├── transfer-eth └── README.md ├── transfer-tokens └── README.md ├── util-go └── README.md ├── util └── README.md ├── wallet-generate └── README.md ├── whisper-client └── README.md ├── whisper-keys └── README.md ├── whisper-poll └── README.md ├── whisper-send └── README.md ├── whisper-subscribe └── README.md └── whisper └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | _book 3 | /*.epub 4 | /*.mobi 5 | /*.pdf 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [miguelmota] 2 | patreon: miguelmota 3 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | # Number of days of inactivity before an Issue or Pull Request becomes stale 4 | daysUntilStale: 365 5 | 6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. 7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. 8 | daysUntilClose: 30 9 | 10 | # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) 11 | onlyLabels: [] 12 | 13 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable 14 | exemptLabels: 15 | - pinned 16 | - security 17 | - "[Status] Maybe Later" 18 | 19 | # Set to true to ignore issues in a project (defaults to false) 20 | exemptProjects: false 21 | 22 | # Set to true to ignore issues in a milestone (defaults to false) 23 | exemptMilestones: false 24 | 25 | # Set to true to ignore issues with an assignee (defaults to false) 26 | exemptAssignees: false 27 | 28 | # Label to use when marking as stale 29 | staleLabel: wontfix 30 | 31 | # Comment to post when marking as stale. Set to `false` to disable 32 | markComment: > 33 | This issue has been automatically marked as stale because it has not had 34 | recent activity. It will be closed if no further activity occurs. Thank you 35 | for your contributions. 36 | 37 | # Comment to post when removing the stale label. 38 | # unmarkComment: > 39 | # Your comment here. 40 | 41 | # Comment to post when closing a stale Issue or Pull Request. 42 | # closeComment: > 43 | # Your comment here. 44 | 45 | # Limit the number of actions per hour, from 1-30. Default is 30 46 | limitPerRun: 30 47 | 48 | # Limit to only `issues` or `pulls` 49 | # only: issues 50 | 51 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 52 | # pulls: 53 | # daysUntilStale: 30 54 | # markComment: > 55 | # This pull request has been automatically marked as stale because it has not had 56 | # recent activity. It will be closed if no further activity occurs. Thank you 57 | # for your contributions. 58 | 59 | # issues: 60 | # exemptLabels: 61 | # - confirmed 62 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | run-linter: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Install node v10 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: 10 18 | env: 19 | RUNNER_TEMP: /tmp 20 | 21 | - name: Checkout git repository 22 | uses: actions/checkout@v2 23 | 24 | - name: Install dependencies 25 | run: | 26 | apt-get update -y 27 | apt-get install -y \ 28 | build-essential \ 29 | python \ 30 | pkg-config \ 31 | libcairo2-dev \ 32 | libjpeg-dev \ 33 | libgif-dev \ 34 | libglib2.0-0 \ 35 | libgl-dev \ 36 | libnss3-dev \ 37 | libx11-6 \ 38 | libx11-xcb1 \ 39 | libxcb1 \ 40 | libxcursor1 \ 41 | libxdamage1 \ 42 | libxext6 \ 43 | libxfixes3 \ 44 | libxi6 \ 45 | libxrandr2 \ 46 | libxrender1 \ 47 | libxss1 \ 48 | libxtst6 \ 49 | libxcb1-dev \ 50 | libxcomposite-dev 51 | wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | bash 52 | make install 53 | 54 | - name: Build 55 | run: | 56 | make build 57 | make ebooks 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | _book 3 | todo.txt 4 | *.pdf 5 | *.epub 6 | *.mobi 7 | deploy.sh 8 | node_modules 9 | code/sandbox/ 10 | sandbox.go 11 | sandbox*.go 12 | tmp/ 13 | build/ 14 | 15 | .ecrecover.go 16 | 17 | # Local Netlify folder 18 | .netlify 19 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 11.15.0 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 11.15.0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update -y 4 | RUN apt-get install -y \ 5 | build-essential \ 6 | curl \ 7 | wget \ 8 | python \ 9 | pkg-config \ 10 | libcairo2-dev \ 11 | libjpeg-dev \ 12 | libgif-dev \ 13 | libglib2.0-0 \ 14 | libgl-dev \ 15 | libnss3-dev \ 16 | libx11-6 \ 17 | libx11-xcb1 \ 18 | libxcb1 \ 19 | libxcursor1 \ 20 | libxdamage1 \ 21 | libxext6 \ 22 | libxfixes3 \ 23 | libxi6 \ 24 | libxrandr2 \ 25 | libxrender1 \ 26 | libxss1 \ 27 | libxtst6 \ 28 | libxcb1-dev \ 29 | libxcomposite-dev 30 | 31 | RUN wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | bash 32 | RUN rm /bin/sh && ln -s /bin/bash /bin/sh 33 | RUN useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1001 ubuntu 34 | 35 | RUN mkdir -p /usr/local/nvm 36 | ENV NVM_DIR /usr/local/nvm 37 | ENV NODE_VERSION 11.15.0 38 | 39 | RUN curl https://raw.githubusercontent.com/creationix/nvm/v0.39.1/install.sh | bash \ 40 | && source $NVM_DIR/nvm.sh \ 41 | && nvm install $NODE_VERSION \ 42 | && nvm alias default $NODE_VERSION \ 43 | && nvm use default 44 | 45 | ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules 46 | ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH 47 | 48 | COPY Makefile . 49 | RUN make install-gitbook 50 | 51 | USER ubuntu 52 | RUN mkdir -p /home/ubuntu/app 53 | COPY . /home/ubuntu/app 54 | WORKDIR /home/ubuntu/app 55 | 56 | RUN make install-modules 57 | RUN make build 58 | RUN make ebooks 59 | 60 | CMD ["echo", "complete"] 61 | -------------------------------------------------------------------------------- /LANGS.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | * [English](en/) 4 | * [Chinese中文](zh/) 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | .PHONY: install-modules 4 | install-modules: 5 | (rm -rf node_modules && npm install) || rm -f package-lock.json && rm -rf ~/.node-gyp && (npm install || (cd node_modules/canvas && node-gyp rebuild)) 6 | gitbook install 7 | 8 | .PHONY: install-gitbook 9 | install-gitbook: 10 | npm install gitbook-cli@latest node-gyp -g 11 | # install ebook-convert 12 | # on arch: sudo pacman -S calibre 13 | 14 | .PHONY: install 15 | install: install-gitbook install-modules 16 | 17 | .PHONY: serve 18 | serve: 19 | gitbook serve 20 | 21 | .PHONY: build 22 | build: 23 | gitbook build 24 | 25 | .PHONY: deploy 26 | deploy: 27 | ./deploy.sh 28 | 29 | .PHONY: deploy-all 30 | deploy-all: build ebooks ebooks-cp deploy 31 | 32 | .PHONY: ebooks-cp 33 | ebooks-cp: 34 | cp ethereum-development-with-go* _book 35 | 36 | .PHONY: ebooks 37 | ebooks: pdf epub mobi 38 | 39 | .PHONY: pdf 40 | pdf: pdf-en pdf-zh 41 | 42 | .PHONY: pdf-en 43 | pdf-en: 44 | gitbook pdf ./en ethereum-development-with-go.pdf 45 | 46 | .PHONY: pdf-zh 47 | pdf-zh: 48 | gitbook pdf ./zh ethereum-development-with-go-zh.pdf 49 | 50 | .PHONY: epub 51 | epub: epub-en epub-zh 52 | 53 | .PHONY: epub-en 54 | epub-en: 55 | gitbook epub ./en ethereum-development-with-go.epub 56 | 57 | .PHONY: epub-zh 58 | epub-zh: 59 | gitbook epub ./zh ethereum-development-with-go-zh.epub 60 | 61 | .PHONY: mobi 62 | mobi: mobi-en mobi-zh 63 | 64 | .PHONY: mobi-en 65 | mobi-en: 66 | gitbook mobi ./en ethereum-development-with-go.mobi 67 | 68 | .PHONY: mobi-zh 69 | mobi-zh: 70 | gitbook mobi ./zh ethereum-development-with-go-zh.mobi 71 | 72 | .PHONY: plugins-install 73 | plugins-install: 74 | gitbook install 75 | -------------------------------------------------------------------------------- /assets/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/assets/cover.jpg -------------------------------------------------------------------------------- /assets/cover.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/assets/cover.psd -------------------------------------------------------------------------------- /assets/cover_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/assets/cover_small.jpg -------------------------------------------------------------------------------- /assets/cover_small.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/assets/cover_small.psd -------------------------------------------------------------------------------- /assets/gopher.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/assets/gopher.ai -------------------------------------------------------------------------------- /assets/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/assets/gopher.png -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "./", 3 | "title": "Ethereum Development with Go", 4 | "description": "A little book on getting started with Ethereum development using Go (Golang). Learn how to interact with smart contract contract, subscribe to blockchain events, verify signatures, and much more.", 5 | "author": "Miguel Mota", 6 | "isbn": "", 7 | "language": "en", 8 | "direction": "ltr", 9 | "gitbook": ">= 3.0.0", 10 | "styles": { 11 | "website": "styles/website.css" 12 | }, 13 | "plugins": [ 14 | "meta" 15 | ], 16 | "pluginsConfig": { 17 | "analytics": { 18 | "google": "UA-39494276-11" 19 | }, 20 | "fontsettings": { 21 | "theme": "white", 22 | "family": "sans", 23 | "size": 2 24 | }, 25 | "autocover": { 26 | "font": { 27 | "size": null, 28 | "family": "Impact", 29 | "color": "#FFF" 30 | }, 31 | "size": { 32 | "w": 1800, 33 | "h": 2360 34 | }, 35 | "background": { 36 | "color": "#09F" 37 | } 38 | }, 39 | "meta": { 40 | "data": [ 41 | { 42 | "name": "keywords", 43 | "content": "ethereum, go, golang, development, examples, tutorial, explained, guide, help, reference, stackoverflow, blockchain, smart contracts, swarm, whisper, deploy, erc-20, tokens, coins, eth, gopher, ethereum go book, go ethereum book, ethereum golang, ethereum golang tutorial, golang ethereum tutorial, ethereum golang guide, golang ethereum guide, golang interact smart contracts" 44 | }, 45 | { 46 | "name": "googlebot", 47 | "content": "index,follow" 48 | }, 49 | { 50 | "name": "application-name", 51 | "content": "Go Ethereum" 52 | } 53 | ] 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /code/LICENSE: -------------------------------------------------------------------------------- 1 | MIT license 2 | 3 | Copyright (C) 2017 Miguel Mota 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /code/README.md: -------------------------------------------------------------------------------- 1 | # Code samples 2 | 3 | > The code samples used in the book. 4 | 5 | ## Getting started 6 | 7 | Running an example: 8 | 9 | ```bash 10 | go run account_balance.go 11 | ``` 12 | 13 | ## License 14 | 15 | [MIT](./LICENSE) 16 | -------------------------------------------------------------------------------- /code/abi_pack.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "strings" 8 | 9 | ethereum "github.com/ethereum/go-ethereum" 10 | "github.com/ethereum/go-ethereum/accounts/abi" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/ethclient" 13 | 14 | store "./contracts" // for demo 15 | ) 16 | 17 | func main() { 18 | client, err := ethclient.Dial("https://rinkeby.infura.io") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | abiContract, err := abi.JSON(strings.NewReader(string(store.StoreABI))) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | var key [32]byte 29 | copy(key[:], []byte("foo")) 30 | iargs := []interface{}{key} 31 | 32 | packed, err := abiContract.Pack("items", iargs...) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | fmt.Println(packed) // [72 243 67 243 102 111 111 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 38 | 39 | contractAddress := common.HexToAddress("0x147b8eb97fd247d06c4006d269c90c1908fb5d54") 40 | 41 | msg := ethereum.CallMsg{ 42 | To: &contractAddress, 43 | Data: packed, 44 | } 45 | 46 | output, err := client.CallContract(context.Background(), msg, nil) 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | var item [32]byte 52 | err = abiContract.Unpack(&item, "items", output) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | fmt.Println(string(item[:])) // "bar" 58 | } 59 | -------------------------------------------------------------------------------- /code/account_balance.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math" 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | ) 13 | 14 | func main() { 15 | client, err := ethclient.Dial("https://cloudflare-eth.com") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 21 | balance, err := client.BalanceAt(context.Background(), account, nil) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | fmt.Println(balance) // 25893860171173005034 26 | 27 | blockNumber := big.NewInt(5532993) 28 | balanceAt, err := client.BalanceAt(context.Background(), account, blockNumber) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | fmt.Println(balanceAt) // 25729324269165216042 33 | 34 | fbalance := new(big.Float) 35 | fbalance.SetString(balanceAt.String()) 36 | ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18))) 37 | fmt.Println(ethValue) // 25.729324269165216041 38 | 39 | pendingBalance, err := client.PendingBalanceAt(context.Background(), account) 40 | fmt.Println(pendingBalance) // 25729324269165216042 41 | } 42 | -------------------------------------------------------------------------------- /code/address.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | ) 8 | 9 | func main() { 10 | address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 11 | 12 | fmt.Println(address.Hex()) // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F 13 | fmt.Println(address.Hash().Hex()) // 0x00000000000000000000000071c7656ec7ab88b098defb751b7401b5f6d8976f 14 | fmt.Println(address.Bytes()) // [113 199 101 110 199 171 136 176 152 222 251 117 27 116 1 181 246 216 151 111] 15 | } 16 | -------------------------------------------------------------------------------- /code/address_check.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "regexp" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/ethclient" 11 | ) 12 | 13 | func main() { 14 | re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") 15 | 16 | fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true 17 | fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false 18 | 19 | client, err := ethclient.Dial("https://cloudflare-eth.com") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // 0x Protocol Token (ZRX) smart contract address 25 | address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498") 26 | bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | isContract := len(bytecode) > 0 32 | 33 | fmt.Printf("is contract: %v\n", isContract) // is contract: true 34 | 35 | // a random user account address 36 | address = common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4") 37 | bytecode, err = client.CodeAt(context.Background(), address, nil) // nil is latest block 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | isContract = len(bytecode) > 0 43 | 44 | fmt.Printf("is contract: %v\n", isContract) // is contract: false 45 | } 46 | -------------------------------------------------------------------------------- /code/block_subscribe.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum/core/types" 9 | "github.com/ethereum/go-ethereum/ethclient" 10 | ) 11 | 12 | func main() { 13 | client, err := ethclient.Dial("wss://ropsten.infura.io/ws") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | headers := make(chan *types.Header) 19 | sub, err := client.SubscribeNewHead(context.Background(), headers) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | for { 25 | select { 26 | case err := <-sub.Err(): 27 | log.Fatal(err) 28 | case header := <-headers: 29 | fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 30 | 31 | block, err := client.BlockByHash(context.Background(), header.Hash()) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 37 | fmt.Println(block.Number().Uint64()) // 3477413 38 | fmt.Println(block.Time()) // 1529525947 39 | fmt.Println(block.Nonce()) // 130524141876765836 40 | fmt.Println(len(block.Transactions())) // 7 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /code/blocks.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | 9 | "github.com/ethereum/go-ethereum/ethclient" 10 | ) 11 | 12 | func main() { 13 | client, err := ethclient.Dial("https://cloudflare-eth.com") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | header, err := client.HeaderByNumber(context.Background(), nil) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | fmt.Println(header.Number.String()) // 5671744 24 | 25 | blockNumber := big.NewInt(5671744) 26 | block, err := client.BlockByNumber(context.Background(), blockNumber) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | fmt.Println(block.Number().Uint64()) // 5671744 32 | fmt.Println(block.Time()) // 1527211625 33 | fmt.Println(block.Difficulty().Uint64()) // 3217000136609065 34 | fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9 35 | fmt.Println(len(block.Transactions())) // 144 36 | 37 | count, err := client.TransactionCount(context.Background(), block.Hash()) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | fmt.Println(count) // 144 43 | } 44 | -------------------------------------------------------------------------------- /code/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/ethereum/go-ethereum/ethclient" 8 | ) 9 | 10 | func main() { 11 | client, err := ethclient.Dial("https://cloudflare-eth.com") 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | fmt.Println("we have a connection") 17 | _ = client 18 | } 19 | -------------------------------------------------------------------------------- /code/client_simulated.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | 9 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 10 | "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/core" 13 | "github.com/ethereum/go-ethereum/core/types" 14 | "github.com/ethereum/go-ethereum/crypto" 15 | ) 16 | 17 | func main() { 18 | privateKey, err := crypto.GenerateKey() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | auth := bind.NewKeyedTransactor(privateKey) 24 | 25 | balance := new(big.Int) 26 | balance.SetString("10000000000000000000", 10) // 10 eth in wei 27 | 28 | address := auth.From 29 | genesisAlloc := map[common.Address]core.GenesisAccount{ 30 | address: { 31 | Balance: balance, 32 | }, 33 | } 34 | 35 | blockGasLimit := uint64(4712388) 36 | client := backends.NewSimulatedBackend(genesisAlloc, blockGasLimit) 37 | 38 | fromAddress := auth.From 39 | nonce, err := client.PendingNonceAt(context.Background(), fromAddress) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | value := big.NewInt(1000000000000000000) // in wei (1 eth) 45 | gasLimit := uint64(21000) // in units 46 | gasPrice, err := client.SuggestGasPrice(context.Background()) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d") 52 | var data []byte 53 | tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) 54 | signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKey) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | err = client.SendTransaction(context.Background(), signedTx) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | fmt.Printf("tx sent: %s\n", signedTx.Hash().Hex()) // tx sent: 0xec3ceb05642c61d33fa6c951b54080d1953ac8227be81e7b5e4e2cfed69eeb51 65 | 66 | client.Commit() 67 | 68 | receipt, err := client.TransactionReceipt(context.Background(), signedTx.Hash()) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | if receipt == nil { 73 | log.Fatal("receipt is nil. Forgot to commit?") 74 | } 75 | 76 | fmt.Printf("status: %v\n", receipt.Status) // status: 1 77 | } 78 | -------------------------------------------------------------------------------- /code/contract_bytecode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "fmt" 7 | "log" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/ethclient" 11 | ) 12 | 13 | func main() { 14 | client, err := ethclient.Dial("https://rinkeby.infura.io") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 20 | bytecode, err := client.CodeAt(context.Background(), contractAddress, nil) // nil is latest block 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | fmt.Println(hex.EncodeToString(bytecode)) // 60806...10029 26 | } 27 | -------------------------------------------------------------------------------- /code/contract_deploy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 11 | "github.com/ethereum/go-ethereum/crypto" 12 | "github.com/ethereum/go-ethereum/ethclient" 13 | 14 | store "./contracts" // for demo 15 | ) 16 | 17 | func main() { 18 | client, err := ethclient.Dial("https://rinkeby.infura.io") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | publicKey := privateKey.Public() 29 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 30 | if !ok { 31 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 32 | } 33 | 34 | fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 35 | nonce, err := client.PendingNonceAt(context.Background(), fromAddress) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | gasPrice, err := client.SuggestGasPrice(context.Background()) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | auth := bind.NewKeyedTransactor(privateKey) 46 | auth.Nonce = big.NewInt(int64(nonce)) 47 | auth.Value = big.NewInt(0) // in wei 48 | auth.GasLimit = uint64(300000) // in units 49 | auth.GasPrice = gasPrice 50 | 51 | input := "1.0" 52 | address, tx, instance, err := store.DeployStore(auth, client, input) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | fmt.Println(address.Hex()) // 0x147B8eb97fD247D06C4006D269c90C1908Fb5D54 58 | fmt.Println(tx.Hash().Hex()) // 0xdae8ba5444eefdc99f4d45cd0c4f24056cba6a02cefbf78066ef9f4188ff7dc0 59 | 60 | _ = instance 61 | } 62 | -------------------------------------------------------------------------------- /code/contract_load.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | 10 | store "./contracts" // for demo 11 | ) 12 | 13 | func main() { 14 | client, err := ethclient.Dial("https://rinkeby.infura.io") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 20 | instance, err := store.NewStore(address, client) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | fmt.Println("contract is loaded") 26 | _ = instance 27 | } 28 | -------------------------------------------------------------------------------- /code/contract_read.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | 10 | store "./contracts" // for demo 11 | ) 12 | 13 | func main() { 14 | client, err := ethclient.Dial("https://rinkeby.infura.io") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 20 | instance, err := store.NewStore(address, client) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | version, err := instance.Version(nil) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | fmt.Println(version) // "1.0" 31 | } 32 | -------------------------------------------------------------------------------- /code/contract_read_erc20.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math" 7 | "math/big" 8 | 9 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | 13 | token "./contracts_erc20" // for demo 14 | ) 15 | 16 | func main() { 17 | client, err := ethclient.Dial("https://cloudflare-eth.com") 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | 22 | // Golem (GNT) Address 23 | tokenAddress := common.HexToAddress("0xa74476443119A942dE498590Fe1f2454d7D4aC0d") 24 | instance, err := token.NewToken(tokenAddress, client) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | address := common.HexToAddress("0x0536806df512d6cdde913cf95c9886f65b1d3462") 30 | bal, err := instance.BalanceOf(&bind.CallOpts{}, address) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | name, err := instance.Name(&bind.CallOpts{}) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | symbol, err := instance.Symbol(&bind.CallOpts{}) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | decimals, err := instance.Decimals(&bind.CallOpts{}) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | 50 | fmt.Printf("name: %s\n", name) // "name: Golem Network" 51 | fmt.Printf("symbol: %s\n", symbol) // "symbol: GNT" 52 | fmt.Printf("decimals: %v\n", decimals) // "decimals: 18" 53 | 54 | fmt.Printf("wei: %s\n", bal) // "wei: 74605500647408739782407023" 55 | 56 | fbal := new(big.Float) 57 | fbal.SetString(bal.String()) 58 | value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals)))) 59 | 60 | fmt.Printf("balance: %f", value) // "balance: 74605500.647409" 61 | } 62 | -------------------------------------------------------------------------------- /code/contract_write.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/crypto" 13 | "github.com/ethereum/go-ethereum/ethclient" 14 | 15 | store "./contracts" // for demo 16 | ) 17 | 18 | func main() { 19 | client, err := ethclient.Dial("https://rinkeby.infura.io") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | publicKey := privateKey.Public() 30 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 31 | if !ok { 32 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 33 | } 34 | 35 | fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 36 | nonce, err := client.PendingNonceAt(context.Background(), fromAddress) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | gasPrice, err := client.SuggestGasPrice(context.Background()) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | auth := bind.NewKeyedTransactor(privateKey) 47 | auth.Nonce = big.NewInt(int64(nonce)) 48 | auth.Value = big.NewInt(0) // in wei 49 | auth.GasLimit = uint64(300000) // in units 50 | auth.GasPrice = gasPrice 51 | 52 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 53 | instance, err := store.NewStore(address, client) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | key := [32]byte{} 59 | value := [32]byte{} 60 | copy(key[:], []byte("foo")) 61 | copy(value[:], []byte("bar")) 62 | 63 | tx, err := instance.SetItem(auth, key, value) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | 68 | fmt.Printf("tx sent: %s\n", tx.Hash().Hex()) // tx sent: 0x8d490e535678e9a24360e955d75b27ad307bdfb97a1dca51d0f3035dcee3e870 69 | 70 | result, err := instance.Items(nil, key) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | fmt.Println(string(result[:])) // "bar" 76 | } 77 | -------------------------------------------------------------------------------- /code/contract_write_estimate_gas.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "fmt" 7 | "log" 8 | "strings" 9 | 10 | ethereum "github.com/ethereum/go-ethereum" 11 | "github.com/ethereum/go-ethereum/accounts/abi" 12 | "github.com/ethereum/go-ethereum/common" 13 | "github.com/ethereum/go-ethereum/crypto" 14 | "github.com/ethereum/go-ethereum/ethclient" 15 | 16 | store "github.com/miguelmota/ethereum-development-with-go-book/code/contracts" 17 | ) 18 | 19 | func main() { 20 | client, err := ethclient.Dial("http://rinkeby.infura.io") 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | publicKey := privateKey.Public() 31 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 32 | if !ok { 33 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 34 | } 35 | 36 | fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 37 | gasPrice, err := client.SuggestGasPrice(context.Background()) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 43 | 44 | key := [32]byte{} 45 | value := [32]byte{} 46 | copy(key[:], []byte("foo")) 47 | copy(value[:], []byte("bar")) 48 | 49 | parsed, err := abi.JSON(strings.NewReader(store.StoreABI)) 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | encodedData, err := parsed.Pack("setItem", key, value) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | estimatedGas, err := client.EstimateGas(context.Background(), ethereum.CallMsg{ 60 | From: fromAddress, 61 | To: &address, 62 | Data: encodedData, 63 | GasPrice: gasPrice, 64 | }) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | 69 | fmt.Println(estimatedGas) 70 | } 71 | -------------------------------------------------------------------------------- /code/contracts/Store.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Store { 4 | event ItemSet(bytes32 key, bytes32 value); 5 | 6 | string public version; 7 | mapping (bytes32 => bytes32) public items; 8 | 9 | constructor(string _version) public { 10 | version = _version; 11 | } 12 | 13 | function setItem(bytes32 key, bytes32 value) external { 14 | items[key] = value; 15 | emit ItemSet(key, value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /code/contracts/Store_sol_Store.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"items","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"key","type":"bytes32"},{"name":"value","type":"bytes32"}],"name":"setItem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_version","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"ItemSet","type":"event"}] -------------------------------------------------------------------------------- /code/contracts/Store_sol_Store.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b506040516103d53803806103d5833981018060405281019080805182019291905050508060009080519060200190610049929190610050565b50506100f5565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061009157805160ff19168380011785556100bf565b828001600101855582156100bf579182015b828111156100be5782518255916020019190600101906100a3565b5b5090506100cc91906100d0565b5090565b6100f291905b808211156100ee5760008160009055506001016100d6565b5090565b90565b6102d1806101046000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806348f343f31461005c57806354fd4d50146100a9578063f56256c714610139575b600080fd5b34801561006857600080fd5b5061008b6004803603810190808035600019169060200190929190505050610178565b60405180826000191660001916815260200191505060405180910390f35b3480156100b557600080fd5b506100be610190565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fe5780820151818401526020810190506100e3565b50505050905090810190601f16801561012b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014557600080fd5b506101766004803603810190808035600019169060200190929190803560001916906020019092919050505061022e565b005b60016020528060005260406000206000915090505481565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102265780601f106101fb57610100808354040283529160200191610226565b820191906000526020600020905b81548152906001019060200180831161020957829003601f168201915b505050505081565b8060016000846000191660001916815260200190815260200160002081600019169055507fe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4828260405180836000191660001916815260200182600019166000191681526020019250505060405180910390a150505600a165627a7a7230582017c3e13a732c8ac8bf1a24a092160e1e21e4541a2b209b6a3b0867df4a0e6c210029 -------------------------------------------------------------------------------- /code/contracts_0xprotocol/Exchange.sol:Exchange.abi: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"name":"maker","type":"address"},{"indexed":false,"name":"taker","type":"address"},{"indexed":true,"name":"feeRecipient","type":"address"},{"indexed":false,"name":"makerToken","type":"address"},{"indexed":false,"name":"takerToken","type":"address"},{"indexed":false,"name":"filledMakerTokenAmount","type":"uint256"},{"indexed":false,"name":"filledTakerTokenAmount","type":"uint256"},{"indexed":false,"name":"paidMakerFee","type":"uint256"},{"indexed":false,"name":"paidTakerFee","type":"uint256"},{"indexed":true,"name":"tokens","type":"bytes32"},{"indexed":false,"name":"orderHash","type":"bytes32"}],"name":"LogFill","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"maker","type":"address"},{"indexed":true,"name":"feeRecipient","type":"address"},{"indexed":false,"name":"makerToken","type":"address"},{"indexed":false,"name":"takerToken","type":"address"},{"indexed":false,"name":"cancelledMakerTokenAmount","type":"uint256"},{"indexed":false,"name":"cancelledTakerTokenAmount","type":"uint256"},{"indexed":true,"name":"tokens","type":"bytes32"},{"indexed":false,"name":"orderHash","type":"bytes32"}],"name":"LogCancel","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"errorId","type":"uint8"},{"indexed":true,"name":"orderHash","type":"bytes32"}],"name":"LogError","type":"event"}] -------------------------------------------------------------------------------- /code/contracts_erc20/erc20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract ERC20 { 4 | string public constant name = ""; 5 | string public constant symbol = ""; 6 | uint8 public constant decimals = 0; 7 | 8 | function totalSupply() public constant returns (uint); 9 | function balanceOf(address tokenOwner) public constant returns (uint balance); 10 | function allowance(address tokenOwner, address spender) public constant returns (uint remaining); 11 | function transfer(address to, uint tokens) public returns (bool success); 12 | function approve(address spender, uint tokens) public returns (bool success); 13 | function transferFrom(address from, address to, uint tokens) public returns (bool success); 14 | 15 | event Transfer(address indexed from, address indexed to, uint tokens); 16 | event Approval(address indexed tokenOwner, address indexed spender, uint tokens); 17 | } 18 | -------------------------------------------------------------------------------- /code/contracts_erc20/erc20_sol_ERC20.abi: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"tokens","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"tokens","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tokenOwner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"tokens","type":"uint256"}],"name":"Approval","type":"event"}] -------------------------------------------------------------------------------- /code/event_read.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | "strings" 9 | 10 | "github.com/ethereum/go-ethereum" 11 | "github.com/ethereum/go-ethereum/accounts/abi" 12 | "github.com/ethereum/go-ethereum/common" 13 | "github.com/ethereum/go-ethereum/crypto" 14 | "github.com/ethereum/go-ethereum/ethclient" 15 | 16 | store "./contracts" // for demo 17 | ) 18 | 19 | func main() { 20 | client, err := ethclient.Dial("wss://rinkeby.infura.io/ws") 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 26 | query := ethereum.FilterQuery{ 27 | FromBlock: big.NewInt(2394201), 28 | ToBlock: big.NewInt(2394201), 29 | Addresses: []common.Address{ 30 | contractAddress, 31 | }, 32 | } 33 | 34 | logs, err := client.FilterLogs(context.Background(), query) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | contractAbi, err := abi.JSON(strings.NewReader(string(store.StoreABI))) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | for _, vLog := range logs { 45 | fmt.Println(vLog.BlockHash.Hex()) // 0x3404b8c050aa0aacd0223e91b5c32fee6400f357764771d0684fa7b3f448f1a8 46 | fmt.Println(vLog.BlockNumber) // 2394201 47 | fmt.Println(vLog.TxHash.Hex()) // 0x280201eda63c9ff6f305fcee51d5eb86167fab40ca3108ec784e8652a0e2b1a6 48 | 49 | event := struct { 50 | Key [32]byte 51 | Value [32]byte 52 | }{} 53 | err := contractAbi.Unpack(&event, "ItemSet", vLog.Data) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | fmt.Println(string(event.Key[:])) // foo 59 | fmt.Println(string(event.Value[:])) // bar 60 | 61 | var topics [4]string 62 | for i := range vLog.Topics { 63 | topics[i] = vLog.Topics[i].Hex() 64 | } 65 | 66 | fmt.Println(topics[0]) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4 67 | } 68 | 69 | eventSignature := []byte("ItemSet(bytes32,bytes32)") 70 | hash := crypto.Keccak256Hash(eventSignature) 71 | fmt.Println(hash.Hex()) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4 72 | } 73 | -------------------------------------------------------------------------------- /code/event_read_by_transaction.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/ethclient" 11 | ) 12 | 13 | func main() { 14 | client, err := ethclient.Dial("https://cloudflare-eth.com") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | txID := common.HexToHash("0xe330601b05c54116da3b06dd17cf483ab3106fb969989458c8587aac1c34fbf3") 20 | receipt, err := client.TransactionReceipt(context.Background(), txID) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | logID := "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" 26 | for _, vLog := range receipt.Logs { 27 | if vLog.Topics[0].Hex() == logID { 28 | if len(vLog.Topics) > 2 { 29 | id := new(big.Int) 30 | id.SetBytes(vLog.Topics[3].Bytes()) 31 | 32 | fmt.Println(id.Uint64()) // 1133 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /code/event_read_erc20.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | "strings" 9 | 10 | token "./contracts_erc20" // for demo 11 | "github.com/ethereum/go-ethereum" 12 | "github.com/ethereum/go-ethereum/accounts/abi" 13 | "github.com/ethereum/go-ethereum/common" 14 | "github.com/ethereum/go-ethereum/crypto" 15 | "github.com/ethereum/go-ethereum/ethclient" 16 | ) 17 | 18 | // LogTransfer .. 19 | type LogTransfer struct { 20 | From common.Address 21 | To common.Address 22 | Tokens *big.Int 23 | } 24 | 25 | // LogApproval .. 26 | type LogApproval struct { 27 | TokenOwner common.Address 28 | Spender common.Address 29 | Tokens *big.Int 30 | } 31 | 32 | func main() { 33 | client, err := ethclient.Dial("https://cloudflare-eth.com") 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | 38 | // 0x Protocol (ZRX) token address 39 | contractAddress := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498") 40 | query := ethereum.FilterQuery{ 41 | FromBlock: big.NewInt(6383820), 42 | ToBlock: big.NewInt(6383840), 43 | Addresses: []common.Address{ 44 | contractAddress, 45 | }, 46 | } 47 | 48 | logs, err := client.FilterLogs(context.Background(), query) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | contractAbi, err := abi.JSON(strings.NewReader(string(token.TokenABI))) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | logTransferSig := []byte("Transfer(address,address,uint256)") 59 | LogApprovalSig := []byte("Approval(address,address,uint256)") 60 | logTransferSigHash := crypto.Keccak256Hash(logTransferSig) 61 | logApprovalSigHash := crypto.Keccak256Hash(LogApprovalSig) 62 | 63 | for _, vLog := range logs { 64 | fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber) 65 | fmt.Printf("Log Index: %d\n", vLog.Index) 66 | 67 | switch vLog.Topics[0].Hex() { 68 | case logTransferSigHash.Hex(): 69 | fmt.Printf("Log Name: Transfer\n") 70 | 71 | var transferEvent LogTransfer 72 | 73 | err := contractAbi.Unpack(&transferEvent, "Transfer", vLog.Data) 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | 78 | transferEvent.From = common.HexToAddress(vLog.Topics[1].Hex()) 79 | transferEvent.To = common.HexToAddress(vLog.Topics[2].Hex()) 80 | 81 | fmt.Printf("From: %s\n", transferEvent.From.Hex()) 82 | fmt.Printf("To: %s\n", transferEvent.To.Hex()) 83 | fmt.Printf("Tokens: %s\n", transferEvent.Tokens.String()) 84 | 85 | case logApprovalSigHash.Hex(): 86 | fmt.Printf("Log Name: Approval\n") 87 | 88 | var approvalEvent LogApproval 89 | 90 | err := contractAbi.Unpack(&approvalEvent, "Approval", vLog.Data) 91 | if err != nil { 92 | log.Fatal(err) 93 | } 94 | 95 | approvalEvent.TokenOwner = common.HexToAddress(vLog.Topics[1].Hex()) 96 | approvalEvent.Spender = common.HexToAddress(vLog.Topics[2].Hex()) 97 | 98 | fmt.Printf("Token Owner: %s\n", approvalEvent.TokenOwner.Hex()) 99 | fmt.Printf("Spender: %s\n", approvalEvent.Spender.Hex()) 100 | fmt.Printf("Tokens: %s\n", approvalEvent.Tokens.String()) 101 | } 102 | 103 | fmt.Printf("\n\n") 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /code/event_subscribe.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum" 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/core/types" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | ) 13 | 14 | func main() { 15 | client, err := ethclient.Dial("wss://rinkeby.infura.io/ws") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 21 | query := ethereum.FilterQuery{ 22 | Addresses: []common.Address{contractAddress}, 23 | } 24 | 25 | logs := make(chan types.Log) 26 | sub, err := client.SubscribeFilterLogs(context.Background(), query, logs) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | for { 32 | select { 33 | case err := <-sub.Err(): 34 | log.Fatal(err) 35 | case vLog := <-logs: 36 | fmt.Println(vLog) // pointer to log event 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /code/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/miguelmota/ethereum-development-with-go-book/code 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/allegro/bigcache v1.2.1 // indirect 7 | github.com/aristanetworks/goarista v0.0.0-20210107181124-fad53805024e // indirect 8 | github.com/deckarep/golang-set v1.7.1 // indirect 9 | github.com/ethereum/go-ethereum v1.8.20 10 | github.com/pborman/uuid v1.2.1 // indirect 11 | github.com/rjeczalik/notify v0.9.2 // indirect 12 | github.com/rs/cors v1.7.0 // indirect 13 | github.com/syndtr/goleveldb v1.0.0 // indirect 14 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect 15 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /code/keystore.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/ethereum/go-ethereum/accounts/keystore" 10 | ) 11 | 12 | func createKs() { 13 | ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) 14 | password := "secret" 15 | account, err := ks.NewAccount(password) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 21 | } 22 | 23 | func importKs() { 24 | file := "./tmp/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3" 25 | ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) 26 | jsonBytes, err := ioutil.ReadFile(file) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | password := "secret" 32 | account, err := ks.Import(jsonBytes, password, password) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 38 | 39 | if err := os.Remove(file); err != nil { 40 | log.Fatal(err) 41 | } 42 | } 43 | 44 | func main() { 45 | createKs() 46 | //importKs() 47 | } 48 | -------------------------------------------------------------------------------- /code/rlp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "log" 8 | 9 | "github.com/ethereum/go-ethereum/rlp" 10 | ) 11 | 12 | type simplestruct struct { 13 | A uint 14 | B string 15 | } 16 | 17 | func main() { 18 | foo := &simplestruct{ 19 | A: 123, 20 | B: "hello", 21 | } 22 | 23 | // encode 24 | 25 | serialized, err := rlp.EncodeToBytes(&foo) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | log.Println(fmt.Sprintf("%x", serialized)) // c77b8568656c6c6f 31 | 32 | // decode 33 | 34 | bar := new(simplestruct) 35 | 36 | b, err := hex.DecodeString("c77b8568656c6c6f") 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | r := bytes.NewReader(b) 42 | err = rlp.Decode(r, &bar) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | fmt.Println(bar.A) // 123 48 | fmt.Println(bar.B) // "hello" 49 | 50 | // look at tags "-" "tail" "nil" 51 | } 52 | -------------------------------------------------------------------------------- /code/rpc_call.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/rpc" 9 | ) 10 | 11 | // https://stackoverflow.com/questions/53237759/how-to-correctly-send-rpc-call-using-golang-to-get-smart-contract-owner/53260846#53260846 12 | 13 | func main() { 14 | client, err := rpc.DialHTTP("https://cloudflare-eth.com") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer client.Close() 19 | 20 | type request struct { 21 | To string `json:"to"` 22 | Data string `json:"data"` 23 | } 24 | 25 | var result string 26 | 27 | req := request{"0xcc13fc627effd6e35d2d2706ea3c4d7396c610ea", "0x8da5cb5b"} 28 | if err := client.Call(&result, "eth_call", req, "latest"); err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | owner := common.HexToAddress(result) 33 | fmt.Printf("%s\n", owner.Hex()) // 0x281017b4E914b79371d62518b17693B36c7a221e 34 | } 35 | -------------------------------------------------------------------------------- /code/rpc_client_version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ethereum/go-ethereum/rpc" 7 | ) 8 | 9 | func main() { 10 | client, err := rpc.DialHTTP("https://cloudflare-eth.com") 11 | if err != nil { 12 | panic(err) 13 | } 14 | 15 | var res string 16 | req := struct { 17 | To string `json:"to"` 18 | Data string `json:"data"` 19 | }{} 20 | 21 | if err := client.Call(&res, "web3_clientVersion", req, "latest"); err != nil { 22 | panic(err) 23 | } 24 | 25 | fmt.Println(res) // Geth/v1.8.22-omnibus-260f7fbd/linux-amd64/go1.11.1 26 | } 27 | -------------------------------------------------------------------------------- /code/signature_generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/ethereum/go-ethereum/common/hexutil" 8 | "github.com/ethereum/go-ethereum/crypto" 9 | ) 10 | 11 | func main() { 12 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | data := []byte("hello") 18 | hash := crypto.Keccak256Hash(data) 19 | fmt.Println(hash.Hex()) // 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8 20 | 21 | signature, err := crypto.Sign(hash.Bytes(), privateKey) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | fmt.Println(hexutil.Encode(signature)) // 0x789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301 27 | } 28 | -------------------------------------------------------------------------------- /code/signature_verify.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "fmt" 7 | "log" 8 | 9 | "github.com/ethereum/go-ethereum/common/hexutil" 10 | "github.com/ethereum/go-ethereum/crypto" 11 | ) 12 | 13 | func main() { 14 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | publicKey := privateKey.Public() 20 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 21 | if !ok { 22 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 23 | } 24 | 25 | publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) 26 | 27 | data := []byte("hello") 28 | hash := crypto.Keccak256Hash(data) 29 | fmt.Println(hash.Hex()) // 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8 30 | 31 | signature, err := crypto.Sign(hash.Bytes(), privateKey) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | fmt.Println(hexutil.Encode(signature)) // 0x789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301 37 | 38 | sigPublicKey, err := crypto.Ecrecover(hash.Bytes(), signature) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | matches := bytes.Equal(sigPublicKey, publicKeyBytes) 44 | fmt.Println(matches) // true 45 | 46 | sigPublicKeyECDSA, err := crypto.SigToPub(hash.Bytes(), signature) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | sigPublicKeyBytes := crypto.FromECDSAPub(sigPublicKeyECDSA) 52 | matches = bytes.Equal(sigPublicKeyBytes, publicKeyBytes) 53 | fmt.Println(matches) // true 54 | 55 | signatureNoRecoverID := signature[:len(signature)-1] // remove recovery id 56 | verified := crypto.VerifySignature(publicKeyBytes, hash.Bytes(), signatureNoRecoverID) 57 | fmt.Println(verified) // true 58 | } 59 | -------------------------------------------------------------------------------- /code/swarm_download.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | 8 | bzzclient "github.com/ethersphere/swarm/api/client" 9 | ) 10 | 11 | func main() { 12 | client := bzzclient.NewClient("http://127.0.0.1:8500") 13 | manifestHash := "2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0" 14 | manifest, isEncrypted, err := client.DownloadManifest(manifestHash) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | fmt.Println(isEncrypted) // false 19 | 20 | for _, entry := range manifest.Entries { 21 | fmt.Println(entry.Hash) // 42179060941352ba7b400b16c40f1e1290423a826de2a70587034dc14bc4ab2f 22 | fmt.Println(entry.ContentType) // text/plain; charset=utf-8 23 | fmt.Println(entry.Size) // 12 24 | fmt.Println(entry.Path) // "" 25 | } 26 | 27 | file, err := client.Download(manifestHash, "") 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | content, err := ioutil.ReadAll(file) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | fmt.Println(string(content)) // hello world 37 | } 38 | -------------------------------------------------------------------------------- /code/swarm_upload.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | bzzclient "github.com/ethersphere/swarm/api/client" 8 | ) 9 | 10 | func main() { 11 | client := bzzclient.NewClient("http://127.0.0.1:8500") 12 | 13 | file, err := bzzclient.Open("hello.txt") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | manifestHash, err := client.Upload(file, "", false, false, false) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | fmt.Println(manifestHash) // 2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0 23 | } 24 | -------------------------------------------------------------------------------- /code/transaction_raw_create.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "encoding/hex" 7 | "fmt" 8 | "log" 9 | "math/big" 10 | 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/core/types" 13 | "github.com/ethereum/go-ethereum/crypto" 14 | "github.com/ethereum/go-ethereum/ethclient" 15 | ) 16 | 17 | func main() { 18 | client, err := ethclient.Dial("https://rinkeby.infura.io") 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | 23 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | publicKey := privateKey.Public() 29 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 30 | if !ok { 31 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 32 | } 33 | 34 | fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 35 | nonce, err := client.PendingNonceAt(context.Background(), fromAddress) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | value := big.NewInt(1000000000000000000) // in wei (1 eth) 41 | gasLimit := uint64(21000) // in units 42 | gasPrice, err := client.SuggestGasPrice(context.Background()) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d") 48 | var data []byte 49 | tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) 50 | 51 | chainID, err := client.NetworkID(context.Background()) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | 56 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | ts := types.Transactions{signedTx} 62 | rawTxBytes := ts.GetRlp(0) 63 | rawTxHex := hex.EncodeToString(rawTxBytes) 64 | 65 | fmt.Printf(rawTxHex) // f86...772 66 | } 67 | -------------------------------------------------------------------------------- /code/transaction_raw_decode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum/core/types" 9 | "github.com/ethereum/go-ethereum/rlp" 10 | ) 11 | 12 | func main() { 13 | rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772" 14 | 15 | tx := new(types.Transaction) 16 | rawTxBytes, err := hex.DecodeString(rawTx) 17 | rlp.DecodeBytes(rawTxBytes, &tx) 18 | 19 | fmt.Println(tx.Hash().Hex()) 20 | 21 | msg, err := tx.AsMessage(types.NewEIP155Signer(tx.ChainId())) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | fmt.Println(msg.From().Hex()) 27 | // 0x96216849c49358B10257cb55b28eA603c874b05E 28 | } 29 | -------------------------------------------------------------------------------- /code/transaction_raw_read.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/core/types" 10 | "github.com/ethereum/go-ethereum/crypto" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | "github.com/ethereum/go-ethereum/rlp" 13 | ) 14 | 15 | func main() { 16 | client, err := ethclient.Dial("https://rinkeby.infura.io") 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | txHash := common.HexToHash("0x5a8febc5744151e4fc81e24a252057976c1b1695a0dd55763cdea4c986dadaef") 22 | tx, _, err := client.TransactionByHash(context.Background(), txHash) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | v, r, s := tx.RawSignatureValues() 28 | R := r.Bytes() 29 | S := s.Bytes() 30 | V := byte(v.Uint64() - 27 + 4) 31 | 32 | sig := make([]byte, 65) 33 | copy(sig[32-len(R):32], R) 34 | copy(sig[64-len(S):64], S) 35 | sig[64] = V 36 | fmt.Println("V", V) 37 | _ = V 38 | 39 | //signer := types.HomesteadSigner{} 40 | signer := types.NewEIP155Signer(tx.ChainId()) 41 | hash := signer.Hash(tx) 42 | 43 | rawTx, err := rlp.EncodeToBytes(tx) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | fmt.Printf("%x\n", rawTx) // 49 | 50 | publicKeyBytes, err := crypto.Ecrecover(hash.Bytes(), sig) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | fmt.Printf("%x\n", publicKeyBytes) // 56 | 57 | publicKeyECDSA, err := crypto.UnmarshalPubkey(publicKeyBytes) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | 62 | address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() 63 | fmt.Println(address) // 0xb3de5b46f54d50cfed7fc6af4986d9077fe2ef81 64 | } 65 | -------------------------------------------------------------------------------- /code/transaction_raw_send.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "fmt" 7 | "log" 8 | 9 | "github.com/ethereum/go-ethereum/core/types" 10 | "github.com/ethereum/go-ethereum/ethclient" 11 | "github.com/ethereum/go-ethereum/rlp" 12 | ) 13 | 14 | func main() { 15 | client, err := ethclient.Dial("https://rinkeby.infura.io") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772" 21 | 22 | rawTxBytes, err := hex.DecodeString(rawTx) 23 | 24 | tx := new(types.Transaction) 25 | rlp.DecodeBytes(rawTxBytes, &tx) 26 | 27 | err = client.SendTransaction(context.Background(), tx) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f 33 | } 34 | -------------------------------------------------------------------------------- /code/transactions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/core/types" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | ) 13 | 14 | func main() { 15 | client, err := ethclient.Dial("https://cloudflare-eth.com") 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | blockNumber := big.NewInt(5671744) 21 | block, err := client.BlockByNumber(context.Background(), blockNumber) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | for _, tx := range block.Transactions() { 27 | fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2 28 | fmt.Println(tx.Value().String()) // 10000000000000000 29 | fmt.Println(tx.Gas()) // 105000 30 | fmt.Println(tx.GasPrice().Uint64()) // 102000000000 31 | fmt.Println(tx.Nonce()) // 110644 32 | fmt.Println(tx.Data()) // [] 33 | fmt.Println(tx.To().Hex()) // 0x55fE59D8Ad77035154dDd0AD0388D09Dd4047A8e 34 | 35 | chainID, err := client.NetworkID(context.Background()) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | if msg, err := tx.AsMessage(types.NewEIP155Signer(chainID)); err == nil { 41 | fmt.Println(msg.From().Hex()) // 0x0fD081e3Bb178dc45c0cb23202069ddA57064258 42 | } 43 | 44 | receipt, err := client.TransactionReceipt(context.Background(), tx.Hash()) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | 49 | fmt.Println(receipt.Status) // 1 50 | } 51 | 52 | blockHash := common.HexToHash("0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9") 53 | count, err := client.TransactionCount(context.Background(), blockHash) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | 58 | for idx := uint(0); idx < count; idx++ { 59 | tx, err := client.TransactionInBlock(context.Background(), blockHash, idx) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2 65 | } 66 | 67 | txHash := common.HexToHash("0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2") 68 | tx, isPending, err := client.TransactionByHash(context.Background(), txHash) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | fmt.Println(tx.Hash().Hex()) // 0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2 74 | fmt.Println(isPending) // false 75 | } 76 | -------------------------------------------------------------------------------- /code/transfer_eth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/core/types" 12 | "github.com/ethereum/go-ethereum/crypto" 13 | "github.com/ethereum/go-ethereum/ethclient" 14 | ) 15 | 16 | func main() { 17 | client, err := ethclient.Dial("https://rinkeby.infura.io") 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | 22 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | publicKey := privateKey.Public() 28 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 29 | if !ok { 30 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 31 | } 32 | 33 | fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 34 | nonce, err := client.PendingNonceAt(context.Background(), fromAddress) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | 39 | value := big.NewInt(1000000000000000000) // in wei (1 eth) 40 | gasLimit := uint64(21000) // in units 41 | gasPrice, err := client.SuggestGasPrice(context.Background()) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d") 47 | var data []byte 48 | tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) 49 | 50 | chainID, err := client.NetworkID(context.Background()) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | 60 | err = client.SendTransaction(context.Background(), signedTx) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | 65 | fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) 66 | } 67 | -------------------------------------------------------------------------------- /code/transfer_tokens.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | 10 | ethereum "github.com/ethereum/go-ethereum" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/common/hexutil" 13 | "github.com/ethereum/go-ethereum/core/types" 14 | "github.com/ethereum/go-ethereum/crypto" 15 | "github.com/ethereum/go-ethereum/ethclient" 16 | "golang.org/x/crypto/sha3" 17 | ) 18 | 19 | func main() { 20 | client, err := ethclient.Dial("https://rinkeby.infura.io") 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | publicKey := privateKey.Public() 31 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 32 | if !ok { 33 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 34 | } 35 | 36 | fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 37 | nonce, err := client.PendingNonceAt(context.Background(), fromAddress) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | value := big.NewInt(0) // in wei (0 eth) 43 | gasPrice, err := client.SuggestGasPrice(context.Background()) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d") 49 | tokenAddress := common.HexToAddress("0x28b149020d2152179873ec60bed6bf7cd705775d") 50 | 51 | transferFnSignature := []byte("transfer(address,uint256)") 52 | hash := sha3.NewLegacyKeccak256() 53 | hash.Write(transferFnSignature) 54 | methodID := hash.Sum(nil)[:4] 55 | fmt.Println(hexutil.Encode(methodID)) // 0xa9059cbb 56 | 57 | paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32) 58 | fmt.Println(hexutil.Encode(paddedAddress)) // 0x0000000000000000000000004592d8f8d7b001e72cb26a73e4fa1806a51ac79d 59 | 60 | amount := new(big.Int) 61 | amount.SetString("1000000000000000000000", 10) // sets the value to 1000 tokens, in the token denomination 62 | 63 | paddedAmount := common.LeftPadBytes(amount.Bytes(), 32) 64 | fmt.Println(hexutil.Encode(paddedAmount)) // 0x00000000000000000000000000000000000000000000003635c9adc5dea00000 65 | 66 | var data []byte 67 | data = append(data, methodID...) 68 | data = append(data, paddedAddress...) 69 | data = append(data, paddedAmount...) 70 | 71 | gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{ 72 | To: &tokenAddress, 73 | Data: data, 74 | }) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | fmt.Println(gasLimit) // 23256 79 | 80 | tx := types.NewTransaction(nonce, tokenAddress, value, gasLimit, gasPrice, data) 81 | 82 | chainID, err := client.NetworkID(context.Background()) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) 88 | if err != nil { 89 | log.Fatal(err) 90 | } 91 | 92 | err = client.SendTransaction(context.Background(), signedTx) 93 | if err != nil { 94 | log.Fatal(err) 95 | } 96 | 97 | fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) // tx sent: 0xa56316b637a94c4cc0331c73ef26389d6c097506d581073f927275e7a6ece0bc 98 | } 99 | -------------------------------------------------------------------------------- /code/trie.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/ethdb" 9 | ethtrie "github.com/ethereum/go-ethereum/trie" 10 | ) 11 | 12 | func main() { 13 | diskdb := ethdb.NewMemDatabase() 14 | triedb := ethtrie.NewDatabase(diskdb) 15 | trie, err := ethtrie.New(common.Hash{}, triedb) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | trie.Update([]byte("foo"), []byte("bar")) 21 | value, err := trie.TryGet([]byte("foo")) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | fmt.Println(string(value)) // bar 27 | } 28 | -------------------------------------------------------------------------------- /code/txpool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/davecgh/go-spew/spew" 5 | "github.com/ethereum/go-ethereum/common" 6 | "github.com/ethereum/go-ethereum/core" 7 | "github.com/ethereum/go-ethereum/core/state" 8 | "github.com/ethereum/go-ethereum/core/types" 9 | "github.com/ethereum/go-ethereum/ethdb" 10 | "github.com/ethereum/go-ethereum/event" 11 | "github.com/ethereum/go-ethereum/params" 12 | ) 13 | 14 | type testBlockChain struct { 15 | statedb *state.StateDB 16 | gasLimit uint64 17 | chainHeadFeed *event.Feed 18 | } 19 | 20 | func (bc *testBlockChain) CurrentBlock() *types.Block { 21 | return types.NewBlock(&types.Header{ 22 | GasLimit: bc.gasLimit, 23 | }, nil, nil, nil) 24 | } 25 | 26 | func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { 27 | return bc.CurrentBlock() 28 | } 29 | 30 | func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { 31 | return bc.statedb, nil 32 | } 33 | 34 | func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { 35 | return bc.chainHeadFeed.Subscribe(ch) 36 | } 37 | 38 | func main() { 39 | statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) 40 | blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} 41 | 42 | txpool := core.NewTxPool(core.TxPoolConfig{}, params.RinkebyChainConfig, blockchain) 43 | 44 | pendingTxs, queuedTxs := txpool.Content() 45 | 46 | spew.Dump(pendingTxs) 47 | spew.Dump(queuedTxs) 48 | 49 | pendingTxs, err = txpool.Pending() 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | spew.Dump(pendingTxs) 55 | 56 | pendingTxs, err = txpool.Pending() 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | spew.Dump(pendingTxs) 62 | 63 | tx := types.Transaction{} // types.NewTransaction(...) 64 | if err := txpool.AddRemote(tx); err != nil { 65 | panic(err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /code/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | "reflect" 7 | "regexp" 8 | "strconv" 9 | 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/ethereum/go-ethereum/common/hexutil" 12 | "golang.org/x/crypto/sha3" 13 | "github.com/shopspring/decimal" 14 | ) 15 | 16 | // PublicKeyBytesToAddress ... 17 | func PublicKeyBytesToAddress(publicKey []byte) common.Address { 18 | var buf []byte 19 | 20 | hash := sha3.NewLegacyKeccak256() 21 | hash.Write(publicKey[1:]) // remove EC prefix 04 22 | buf = hash.Sum(nil) 23 | address := buf[12:] 24 | 25 | return common.HexToAddress(hex.EncodeToString(address)) 26 | } 27 | 28 | // IsValidAddress validate hex address 29 | func IsValidAddress(iaddress interface{}) bool { 30 | re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") 31 | switch v := iaddress.(type) { 32 | case string: 33 | return re.MatchString(v) 34 | case common.Address: 35 | return re.MatchString(v.Hex()) 36 | default: 37 | return false 38 | } 39 | } 40 | 41 | // IsZeroAddress validate if it's a 0 address 42 | func IsZeroAddress(iaddress interface{}) bool { 43 | var address common.Address 44 | switch v := iaddress.(type) { 45 | case string: 46 | address = common.HexToAddress(v) 47 | case common.Address: 48 | address = v 49 | default: 50 | return false 51 | } 52 | 53 | zeroAddressBytes := common.FromHex("0x0000000000000000000000000000000000000000") 54 | addressBytes := address.Bytes() 55 | return reflect.DeepEqual(addressBytes, zeroAddressBytes) 56 | } 57 | 58 | // ToDecimal wei to decimals 59 | func ToDecimal(ivalue interface{}, decimals int) decimal.Decimal { 60 | value := new(big.Int) 61 | switch v := ivalue.(type) { 62 | case string: 63 | value.SetString(v, 10) 64 | case *big.Int: 65 | value = v 66 | } 67 | 68 | mul := decimal.NewFromFloat(float64(10)).Pow(decimal.NewFromFloat(float64(decimals))) 69 | num, _ := decimal.NewFromString(value.String()) 70 | result := num.Div(mul) 71 | 72 | return result 73 | } 74 | 75 | // ToWei decimals to wei 76 | func ToWei(iamount interface{}, decimals int) *big.Int { 77 | amount := decimal.NewFromFloat(0) 78 | switch v := iamount.(type) { 79 | case string: 80 | amount, _ = decimal.NewFromString(v) 81 | case float64: 82 | amount = decimal.NewFromFloat(v) 83 | case int64: 84 | amount = decimal.NewFromFloat(float64(v)) 85 | case decimal.Decimal: 86 | amount = v 87 | case *decimal.Decimal: 88 | amount = *v 89 | } 90 | 91 | mul := decimal.NewFromFloat(float64(10)).Pow(decimal.NewFromFloat(float64(decimals))) 92 | result := amount.Mul(mul) 93 | 94 | wei := new(big.Int) 95 | wei.SetString(result.String(), 10) 96 | 97 | return wei 98 | } 99 | 100 | // CalcGasCost calculate gas cost given gas limit (units) and gas price (wei) 101 | func CalcGasCost(gasLimit uint64, gasPrice *big.Int) *big.Int { 102 | gasLimitBig := big.NewInt(int64(gasLimit)) 103 | return gasLimitBig.Mul(gasLimitBig, gasPrice) 104 | } 105 | 106 | // SigRSV signatures R S V returned as arrays 107 | func SigRSV(isig interface{}) ([32]byte, [32]byte, uint8) { 108 | var sig []byte 109 | switch v := isig.(type) { 110 | case []byte: 111 | sig = v 112 | case string: 113 | sig, _ = hexutil.Decode(v) 114 | } 115 | 116 | sigstr := common.Bytes2Hex(sig) 117 | rS := sigstr[0:64] 118 | sS := sigstr[64:128] 119 | R := [32]byte{} 120 | S := [32]byte{} 121 | copy(R[:], common.FromHex(rS)) 122 | copy(S[:], common.FromHex(sS)) 123 | vStr := sigstr[128:130] 124 | vI, _ := strconv.Atoi(vStr) 125 | V := uint8(vI + 27) 126 | 127 | return R, S, V 128 | } 129 | -------------------------------------------------------------------------------- /code/util_usage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "math/big" 7 | 8 | util "./util" // for demo 9 | "github.com/ethereum/go-ethereum/common/hexutil" 10 | ) 11 | 12 | func main() { 13 | publicKeyBytes, _ := hex.DecodeString("049a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05") 14 | address := util.PublicKeyBytesToAddress(publicKeyBytes) 15 | fmt.Println(address.Hex()) // 0x96216849c49358B10257cb55b28eA603c874b05E 16 | 17 | valid := util.IsValidAddress("0x323b5d4c32345ced77393b3530b1eed0f346429d") 18 | fmt.Println(valid) // true 19 | 20 | zeroed := util.IsZeroAddress("0x0") 21 | fmt.Println(zeroed) // true 22 | 23 | wei := util.ToWei(0.02, 18) 24 | fmt.Println(wei) // 20000000000000000 25 | 26 | wei = new(big.Int) 27 | wei.SetString("20000000000000000", 10) 28 | eth := util.ToDecimal(wei, 18) 29 | fmt.Println(eth) // 0.02 30 | 31 | gasLimit := uint64(21000) 32 | gasPrice := new(big.Int) 33 | gasPrice.SetString("2000000000", 10) 34 | gasCost := util.CalcGasCost(gasLimit, gasPrice) 35 | fmt.Println(gasCost) // 42000000000000 36 | 37 | sig := "0x789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301" 38 | r, s, v := util.SigRSV(sig) 39 | fmt.Println(hexutil.Encode(r[:])[2:]) // 789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c6 40 | fmt.Println(hexutil.Encode(s[:])[2:]) // 2621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde023 41 | fmt.Println(v) // 28 42 | } 43 | -------------------------------------------------------------------------------- /code/wallet_generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | "golang.org/x/crypto/sha3" 11 | ) 12 | 13 | func main() { 14 | privateKey, err := crypto.GenerateKey() 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | privateKeyBytes := crypto.FromECDSA(privateKey) 20 | fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19 21 | 22 | publicKey := privateKey.Public() 23 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 24 | if !ok { 25 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 26 | } 27 | 28 | publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) 29 | fmt.Println(hexutil.Encode(publicKeyBytes)[4:]) // 9a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05 30 | 31 | address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() 32 | fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E 33 | 34 | hash := sha3.NewLegacyKeccak256() 35 | hash.Write(publicKeyBytes[1:]) 36 | fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e 37 | } 38 | -------------------------------------------------------------------------------- /code/whisper_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/ethereum/go-ethereum/whisper/shhclient" 8 | ) 9 | 10 | func main() { 11 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | _ = client 17 | fmt.Println("we have a whisper connection") 18 | } 19 | -------------------------------------------------------------------------------- /code/whisper_keypair.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum/whisper/shhclient" 9 | ) 10 | 11 | func main() { 12 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | keyID, err := client.NewKeyPair(context.Background()) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | 22 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 23 | } 24 | -------------------------------------------------------------------------------- /code/whisper_poll.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum/whisper/shhclient" 9 | "github.com/ethereum/go-ethereum/whisper/whisperv6" 10 | ) 11 | 12 | func main() { 13 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | filterID, err := client.NewMessageFilter(context.Background(), whisperv6.Criteria{ 19 | PrivateKeyID: privateKeyID, 20 | }) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | fmt.Println(filterID) // 21171f8b4e7ac0d7a1ce0d121b647ce10d4f0293b95d8fba69c5b4e9d0f235a6 25 | 26 | messages := make(chan *whisperv6.Message) 27 | sub, err := client.SubscribeMessages(context.Background(), whisper6.Criteria{ 28 | PrivateKeyID: privateKeyID, 29 | }, messages) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | go func() { 34 | for { 35 | select { 36 | case err := <-sub.Err(): 37 | log.Fatal(err) 38 | case message := <-messages: 39 | fmt.Printf(string(message.Payload)) // "Hello" 40 | } 41 | } 42 | }() 43 | 44 | /* 45 | messages, err := client.FilterMessages(context.Background(), filterID) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | fmt.Println(messages) 50 | for _, message := range messages { 51 | fmt.Printf(string(message.Payload)) // "Hello" 52 | } 53 | */ 54 | } 55 | -------------------------------------------------------------------------------- /code/whisper_send.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | "github.com/ethereum/go-ethereum/whisper/shhclient" 10 | "github.com/ethereum/go-ethereum/whisper/whisperv6" 11 | ) 12 | 13 | func main() { 14 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | keyID, err := client.NewKeyPair(context.Background()) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 24 | 25 | publicKey, err := client.PublicKey(context.Background(), keyID) 26 | if err != nil { 27 | log.Print(err) 28 | } 29 | fmt.Println(hexutil.Encode(publicKey)) // 0x04f17356fd52b0d13e5ede84f998d26276f1fc9d08d9e73dcac6ded5f3553405db38c2f257c956f32a0c1fca4c3ff6a38a2c277c1751e59a574aecae26d3bf5d1d 30 | 31 | message := whisperv6.NewMessage{ 32 | Payload: []byte("Hello"), 33 | PublicKey: publicKey, 34 | TTL: 60, 35 | PowTime: 2, 36 | PowTarget: 2.5, 37 | } 38 | 39 | messageHash, err := client.Post(context.Background(), message) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | fmt.Println(messageHash) // 0xdbfc815d3d122a90d7fb44d1fc6a46f3d76ec752f3f3d04230fe5f1b97d2209a 44 | } 45 | -------------------------------------------------------------------------------- /code/whisper_subscribe.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "runtime" 9 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | "github.com/ethereum/go-ethereum/whisper/shhclient" 12 | "github.com/ethereum/go-ethereum/whisper/whisperv6" 13 | ) 14 | 15 | func main() { 16 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | keyID, err := client.NewKeyPair(context.Background()) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 26 | 27 | messages := make(chan *whisperv6.Message) 28 | criteria := whisperv6.Criteria{ 29 | PrivateKeyID: keyID, 30 | } 31 | sub, err := client.SubscribeMessages(context.Background(), criteria, messages) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | go func() { 37 | for { 38 | select { 39 | case err := <-sub.Err(): 40 | log.Fatal(err) 41 | case message := <-messages: 42 | fmt.Printf(string(message.Payload)) // "Hello" 43 | os.Exit(0) 44 | } 45 | } 46 | }() 47 | 48 | publicKey, err := client.PublicKey(context.Background(), keyID) 49 | if err != nil { 50 | log.Print(err) 51 | } 52 | fmt.Println(hexutil.Encode(publicKey)) // 0x04f17356fd52b0d13e5ede84f998d26276f1fc9d08d9e73dcac6ded5f3553405db38c2f257c956f32a0c1fca4c3ff6a38a2c277c1751e59a574aecae26d3bf5d1d 53 | 54 | message := whisperv6.NewMessage{ 55 | Payload: []byte("Hello"), 56 | PublicKey: publicKey, 57 | TTL: 60, 58 | PowTime: 2, 59 | PowTarget: 2.5, 60 | } 61 | 62 | messageHash, err := client.Post(context.Background(), message) 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | fmt.Println(messageHash) // 0xdbfc815d3d122a90d7fb44d1fc6a46f3d76ec752f3f3d04230fe5f1b97d2209a 67 | 68 | runtime.Goexit() // wait for goroutines to finish 69 | } 70 | -------------------------------------------------------------------------------- /en/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Client](client/README.md) 4 | * [Setting up the Client](client-setup/README.md) 5 | * [Accounts](accounts/README.md) 6 | * [Account Balances](account-balance/README.md) 7 | * [Account Token Balances](account-balance-token/README.md) 8 | * [Generating New Wallets](wallet-generate/README.md) 9 | * [Keystores](keystore/README.md) 10 | * [HD Wallets](hd-wallet/README.md) 11 | * [Address Check](address-check/README.md) 12 | * [Transactions](transactions/README.md) 13 | * [Querying Blocks](block-query/README.md) 14 | * [Querying Transactions](transaction-query/README.md) 15 | * [Transferring ETH](transfer-eth/README.md) 16 | * [Transferring Tokens (ERC-20)](transfer-tokens/README.md) 17 | * [Subscribing to New Blocks](block-subscribe/README.md) 18 | * [Create Raw Transaction](transaction-raw-create/README.md) 19 | * [Send Raw Transaction](transaction-raw-send/README.md) 20 | * [Smart Contracts](smart-contracts/README.md) 21 | * [Smart Contract Compilation & ABI](smart-contract-compile/README.md) 22 | * [Deploying a Smart Contract](smart-contract-deploy/README.md) 23 | * [Loading a Smart Contract](smart-contract-load/README.md) 24 | * [Querying a Smart Contract](smart-contract-read/README.md) 25 | * [Writing to a Smart Contract](smart-contract-write/README.md) 26 | * [Reading Smart Contract Bytecode](smart-contract-bytecode/README.md) 27 | * [Querying an ERC20 Token Smart Contract](smart-contract-read-erc20/README.md) 28 | * [Event Logs](events/README.md) 29 | * [Subscribing to Event Logs](event-subscribe/README.md) 30 | * [Reading Event Logs](event-read/README.md) 31 | * [Reading ERC-20 Token Event Logs](event-read-erc20/README.md) 32 | * [Reading 0x Protocol Event Logs](event-read-0xprotocol/README.md) 33 | * [Signatures](signatures/README.md) 34 | * [Generating Signatures](signature-generate/README.md) 35 | * [Verifying Signatures](signature-verify/README.md) 36 | * [Testing](test/README.md) 37 | * [Faucets](faucets/README.md) 38 | * [Using a Simulated Client](client-simulated/README.md) 39 | * [Swarm](swarm/README.md) 40 | * [Setting Up Swarm](swarm-setup/README.md) 41 | * [Uploading Files to Swarm](swarm-upload/README.md) 42 | * [Download Files From Swarm](swarm-download/README.md) 43 | * [Whisper](whisper/README.md) 44 | * [Connecting Whisper Client](whisper-client/README.md) 45 | * [Generating Whisper Key Pair](whisper-keys/README.md) 46 | * [Sending Messages on Whisper](whisper-send/README.md) 47 | * [Subscribing to Whisper Messages](whisper-subscribe/README.md) 48 | * [Utilities](util/README.md) 49 | * [Collection of Utility Functions](util-go/README.md) 50 | * [Glossary](GLOSSARY.md) 51 | * [Resources](resources/README.md) 52 | -------------------------------------------------------------------------------- /en/account-balance-token/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to read account token balances from the blockchain with Go. 3 | --- 4 | 5 | # Account Token Balances 6 | 7 | To learn how to read account token (ERC20) balances, head over to the [section on ERC20 token smart contracts](../smart-contract-read-erc20). 8 | -------------------------------------------------------------------------------- /en/account-balance/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to read account balances from the blockchain with Go. 3 | --- 4 | 5 | # Account Balances 6 | 7 | Reading the balance of an account is pretty simple; call the `BalanceAt` method of the client passing it the account address and optional block number. Setting `nil` as the block number will return the latest balance. 8 | 9 | ```go 10 | account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 11 | balance, err := client.BalanceAt(context.Background(), account, nil) 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | fmt.Println(balance) // 25893180161173005034 17 | ``` 18 | 19 | Passing the block number let's you read the account balance at the time of that block. The block number must be a `big.Int`. 20 | 21 | ```go 22 | blockNumber := big.NewInt(5532993) 23 | balance, err := client.BalanceAt(context.Background(), account, blockNumber) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | fmt.Println(balance) // 25729324269165216042 29 | ``` 30 | 31 | Numbers in ethereum are dealt using the smallest possible unit because they're fixed-point precision, which in the case of ETH it's *wei*. To read the ETH value you must do the calculation `wei / 10^18`. Because we're dealing with big numbers we'll have to import the native Go `math` and `math/big` packages. Here's how'd you do the conversion. 32 | 33 | ```go 34 | fbalance := new(big.Float) 35 | fbalance.SetString(balance.String()) 36 | ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18))) 37 | 38 | fmt.Println(ethValue) // 25.729324269165216041 39 | ``` 40 | 41 | #### Pending balance 42 | 43 | Sometimes you'll want to know what the pending account balance is, for example after submitting or waiting for a transaction to be confirmed. The client provides a similar method to `BalanceAt` called `PendingBalanceAt` which accepts the account address as a parameter. 44 | 45 | ```go 46 | pendingBalance, err := client.PendingBalanceAt(context.Background(), account) 47 | fmt.Println(pendingBalance) // 25729324269165216042 48 | ``` 49 | 50 | --- 51 | 52 | ### Full code 53 | 54 | [account_balance.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/account_balance.go) 55 | 56 | ```go 57 | package main 58 | 59 | import ( 60 | "context" 61 | "fmt" 62 | "log" 63 | "math" 64 | "math/big" 65 | 66 | "github.com/ethereum/go-ethereum/common" 67 | "github.com/ethereum/go-ethereum/ethclient" 68 | ) 69 | 70 | func main() { 71 | client, err := ethclient.Dial("https://cloudflare-eth.com") 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 77 | balance, err := client.BalanceAt(context.Background(), account, nil) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | fmt.Println(balance) // 25893180161173005034 82 | 83 | blockNumber := big.NewInt(5532993) 84 | balanceAt, err := client.BalanceAt(context.Background(), account, blockNumber) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | fmt.Println(balanceAt) // 25729324269165216042 89 | 90 | fbalance := new(big.Float) 91 | fbalance.SetString(balanceAt.String()) 92 | ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18))) 93 | fmt.Println(ethValue) // 25.729324269165216041 94 | 95 | pendingBalance, err := client.PendingBalanceAt(context.Background(), account) 96 | fmt.Println(pendingBalance) // 25729324269165216042 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /en/accounts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to load an Ethereum account with Go. 3 | --- 4 | 5 | # Accounts 6 | 7 | Accounts on Ethereum are either wallet addresses or smart contract addresses. They look like `0x71c7656ec7ab88b098defb751b7401b5f6d8976f` and they're what you use for sending ETH to another user and also are used for referring to a smart contract on the blockchain when needing to interact with it. They are unique and are derived from a private key. We'll go more in depth into private/public key pairs in later sections. 8 | 9 | In order to use account addresses with go-ethereum, you must first convert them to the go-ethereum `common.Address` type. 10 | 11 | ```go 12 | address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 13 | 14 | fmt.Println(address.Hex()) // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F 15 | ``` 16 | 17 | Pretty much you'd use this type anywhere you'd pass an ethereum address to methods from go-ethereum. Now that you know the basics of accounts and addresses, let's learn how to retrieve the ETH account balance in the next section. 18 | 19 | --- 20 | 21 | ### Full code 22 | 23 | [address.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/address.go) 24 | 25 | ```go 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | 31 | "github.com/ethereum/go-ethereum/common" 32 | ) 33 | 34 | func main() { 35 | address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 36 | 37 | fmt.Println(address.Hex()) // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F 38 | fmt.Println(address.Hash().Hex()) // 0x00000000000000000000000071c7656ec7ab88b098defb751b7401b5f6d8976f 39 | fmt.Println(address.Bytes()) // [113 199 101 110 199 171 136 176 152 222 251 117 27 116 1 181 246 216 151 111] 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /en/address-check/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to check if an address is a smart contract or an account with Go. 3 | --- 4 | 5 | # Address Check 6 | 7 | This section will describe how to validate an address and determine if it's a smart contract address. 8 | 9 | ## Check if Address is Valid 10 | 11 | We can use a simple regular expression to check if the ethereum address is valid: 12 | 13 | ```go 14 | re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") 15 | 16 | fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true 17 | fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false 18 | ``` 19 | 20 | ## Check if Address is an Account or a Smart Contract 21 | 22 | We can determine if an address is a smart contract if there's bytecode stored at that address. Here's an example where we fetch the code for a token smart contract and check the length to verify that it's a smart contract: 23 | 24 | ```go 25 | // 0x Protocol Token (ZRX) smart contract address 26 | address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498") 27 | bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | isContract := len(bytecode) > 0 33 | 34 | fmt.Printf("is contract: %v\n", isContract) // is contract: true 35 | ``` 36 | 37 | When there's no bytecode at the address then we know that it's not a smart contract and it's a standard ethereum account: 38 | 39 | ```go 40 | // a random user account address 41 | address := common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4") 42 | bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | isContract = len(bytecode) > 0 48 | 49 | fmt.Printf("is contract: %v\n", isContract) // is contract: false 50 | ``` 51 | 52 | --- 53 | 54 | ### Full code 55 | 56 | [address_check.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/address_check.go) 57 | 58 | ```go 59 | package main 60 | 61 | import ( 62 | "context" 63 | "fmt" 64 | "log" 65 | "regexp" 66 | 67 | "github.com/ethereum/go-ethereum/common" 68 | "github.com/ethereum/go-ethereum/ethclient" 69 | ) 70 | 71 | func main() { 72 | re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") 73 | 74 | fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true 75 | fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false 76 | 77 | client, err := ethclient.Dial("https://cloudflare-eth.com") 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | 82 | // 0x Protocol Token (ZRX) smart contract address 83 | address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498") 84 | bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | 89 | isContract := len(bytecode) > 0 90 | 91 | fmt.Printf("is contract: %v\n", isContract) // is contract: true 92 | 93 | // a random user account address 94 | address = common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4") 95 | bytecode, err = client.CodeAt(context.Background(), address, nil) // nil is latest block 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | 100 | isContract = len(bytecode) > 0 101 | 102 | fmt.Printf("is contract: %v\n", isContract) // is contract: false 103 | } 104 | ``` 105 | -------------------------------------------------------------------------------- /en/block-query/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to query blocks in Ethereum with Go. 3 | --- 4 | 5 | # Querying Blocks 6 | 7 | There's two ways you can query block information as we'll see. 8 | 9 | #### Block header 10 | 11 | You can call the client's `HeaderByNumber` to return header information about a block. It'll return the latest block header if you pass `nil`. 12 | 13 | ```go 14 | header, err := client.HeaderByNumber(context.Background(), nil) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | fmt.Println(header.Number.String()) // 5671744 20 | ``` 21 | 22 | #### Full block 23 | 24 | Call the client's `BlockByNumber` method to get the full block. You can read all the contents and metadata of the block such as block number, block timestamp, block hash, block difficulty, as well as the list of transactions and much much more. 25 | 26 | ```go 27 | blockNumber := big.NewInt(5671744) 28 | block, err := client.BlockByNumber(context.Background(), blockNumber) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | fmt.Println(block.Number().Uint64()) // 5671744 34 | fmt.Println(block.Time().Uint64()) // 1527211625 35 | fmt.Println(block.Difficulty().Uint64()) // 3217000136609065 36 | fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9 37 | fmt.Println(len(block.Transactions())) // 144 38 | ``` 39 | 40 | Call `TransactionCount` to return just the count of transactions in a block. 41 | 42 | ```go 43 | count, err := client.TransactionCount(context.Background(), block.Hash()) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | fmt.Println(count) // 144 49 | ``` 50 | 51 | In the next section we'll learn how to query transactions in a block. 52 | 53 | --- 54 | 55 | ### Full code 56 | 57 | [blocks.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/blocks.go) 58 | 59 | ```go 60 | package main 61 | 62 | import ( 63 | "context" 64 | "fmt" 65 | "log" 66 | "math/big" 67 | 68 | "github.com/ethereum/go-ethereum/ethclient" 69 | ) 70 | 71 | func main() { 72 | client, err := ethclient.Dial("https://cloudflare-eth.com") 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | 77 | header, err := client.HeaderByNumber(context.Background(), nil) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | 82 | fmt.Println(header.Number.String()) // 5671744 83 | 84 | blockNumber := big.NewInt(5671744) 85 | block, err := client.BlockByNumber(context.Background(), blockNumber) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | 90 | fmt.Println(block.Number().Uint64()) // 5671744 91 | fmt.Println(block.Time().Uint64()) // 1527211625 92 | fmt.Println(block.Difficulty().Uint64()) // 3217000136609065 93 | fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9 94 | fmt.Println(len(block.Transactions())) // 144 95 | 96 | count, err := client.TransactionCount(context.Background(), block.Hash()) 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | 101 | fmt.Println(count) // 144 102 | } 103 | ``` 104 | -------------------------------------------------------------------------------- /en/block-subscribe/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to subscribe to latest blocks in Ethereum with Go. 3 | --- 4 | 5 | # Subscribing to New Blocks 6 | 7 | In this section we'll go over how to set up a subscription to get events when there is a new block mined. First thing is we need an Ethereum provider that supports RPC over websockets. In this example we'll use the infura websocket endpoint. 8 | 9 | ```go 10 | client, err := ethclient.Dial("wss://ropsten.infura.io/ws") 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | ``` 15 | 16 | Next we'll create a new channel that will be receiving the latest block headers. 17 | 18 | ```go 19 | headers := make(chan *types.Header) 20 | ``` 21 | 22 | Now we call the client's `SubscribeNewHead` method which takes in the headers channel we just created, which will return a subscription object. 23 | 24 | ```go 25 | sub, err := client.SubscribeNewHead(context.Background(), headers) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | ``` 30 | 31 | The subscription will push new block headers to our channel so we'll use a select statement to listen for new messages. The subscription object also contains an error channel that will send a message in case of a failure with the subscription. 32 | 33 | ```go 34 | for { 35 | select { 36 | case err := <-sub.Err(): 37 | log.Fatal(err) 38 | case header := <-headers: 39 | fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 40 | } 41 | } 42 | ``` 43 | 44 | To get the full contents of the block, we can pass the block header hash to the client's `BlockByHash` function. 45 | 46 | ```go 47 | block, err := client.BlockByHash(context.Background(), header.Hash()) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 53 | fmt.Println(block.Number().Uint64()) // 3477413 54 | fmt.Println(block.Time()) // 1529525947 55 | fmt.Println(block.Nonce()) // 130524141876765836 56 | fmt.Println(len(block.Transactions())) // 7 57 | ``` 58 | 59 | As you can see, you can read the entire block's metadata fields, list of transactions, and much more. 60 | 61 | ### Full code 62 | 63 | [block_subscribe.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/block_subscribe.go) 64 | 65 | ```go 66 | package main 67 | 68 | import ( 69 | "context" 70 | "fmt" 71 | "log" 72 | 73 | "github.com/ethereum/go-ethereum/core/types" 74 | "github.com/ethereum/go-ethereum/ethclient" 75 | ) 76 | 77 | func main() { 78 | client, err := ethclient.Dial("wss://ropsten.infura.io/ws") 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | headers := make(chan *types.Header) 84 | sub, err := client.SubscribeNewHead(context.Background(), headers) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | 89 | for { 90 | select { 91 | case err := <-sub.Err(): 92 | log.Fatal(err) 93 | case header := <-headers: 94 | fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 95 | 96 | block, err := client.BlockByHash(context.Background(), header.Hash()) 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | 101 | fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 102 | fmt.Println(block.Number().Uint64()) // 3477413 103 | fmt.Println(block.Time()) // 1529525947 104 | fmt.Println(block.Nonce()) // 130524141876765836 105 | fmt.Println(len(block.Transactions())) // 7 106 | } 107 | } 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /en/client-setup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to set up a client to connect to Ethereum with Go. 3 | --- 4 | 5 | # Setting up the Client 6 | 7 | Setting up the Ethereum client in Go is a fundamental step required for interacting with the blockchain. First import the `ethclient` go-ethereum package and initialize it by calling `Dial` which accepts a provider URL. 8 | 9 | You can connect to the infura gateway if you don't have an existing client. Infura manages a bunch of Ethereum [geth and parity] nodes that are secure, reliable, scalable and lowers the barrier to entry for newcomers when it comes to plugging into the Ethereum network. 10 | 11 | ```go 12 | client, err := ethclient.Dial("https://cloudflare-eth.com") 13 | ``` 14 | 15 | You may also pass the path to the IPC endpoint file if you have a local instance of geth running. 16 | 17 | ```go 18 | client, err := ethclient.Dial("/home/user/.ethereum/geth.ipc") 19 | ``` 20 | 21 | Using the ethclient is a necessary thing you'll need to start with for every Go Ethereum project and you'll be seeing this step a lot throughout this book. 22 | 23 | ## Using Ganache 24 | 25 | [Ganache](https://github.com/trufflesuite/ganache-cli) (formally known as *testrpc*) is an Ethereum implementation written in Node.js meant for testing purposes while developing dapps locally. Here we'll walk you through how to install it and connect to it. 26 | 27 | First install ganache via [NPM](https://www.npmjs.com/package/ganache-cli). 28 | 29 | ```bash 30 | npm install -g ganache-cli 31 | ``` 32 | 33 | Then run the ganache CLI client. 34 | 35 | ```bash 36 | ganache-cli 37 | ``` 38 | 39 | Now connect to the ganache RPC host on `http://localhost:8545`. 40 | 41 | ```go 42 | client, err := ethclient.Dial("http://localhost:8545") 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | ``` 47 | 48 | You may also use the same mnemonic when starting ganache to generate the same sequence of public addresses. 49 | 50 | ```bash 51 | ganache-cli -m "much repair shock carbon improve miss forget sock include bullet interest solution" 52 | ``` 53 | 54 | I highly recommend getting familiar with ganache by reading their [documentation](http://truffleframework.com/ganache/). 55 | 56 | --- 57 | 58 | ### Full code 59 | 60 | [client.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/client.go) 61 | 62 | ```go 63 | package main 64 | 65 | import ( 66 | "fmt" 67 | "log" 68 | 69 | "github.com/ethereum/go-ethereum/ethclient" 70 | ) 71 | 72 | func main() { 73 | client, err := ethclient.Dial("https://cloudflare-eth.com") 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | 78 | fmt.Println("we have a connection") 79 | _ = client // we'll use this in the upcoming sections 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /en/client/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to setup the Ethereum client with Go. 3 | --- 4 | 5 | # Client 6 | 7 | The client is the entry point to the Ethereum network. The client is required to broadcast transactions and read blockchain data. In the [next section](../client-setup) will learn how to set up a client in a Go application. 8 | -------------------------------------------------------------------------------- /en/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/en/cover.jpg -------------------------------------------------------------------------------- /en/cover_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/en/cover_small.jpg -------------------------------------------------------------------------------- /en/events/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on Ethereum event logs with Go. 3 | --- 4 | 5 | # Events 6 | 7 | Smart contracts have the ability to "emit" events during execution. Events are also known as "logs" in Ethereum. The output of the events are stored in transaction receipts under a logs section. Events have become pretty widely used in Ethereum smart contracts to log when a significant action has occured, particularly in token contracts (i.e. ERC-20) to indicate that a token transfer has occured. These sections will walk you through the process of reading events from the blockchain as well as subscribing to events so that you get notified in real time as the transaction gets mined. 8 | -------------------------------------------------------------------------------- /en/faucets/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on using faucets on Ethereum. 3 | --- 4 | 5 | # Faucets 6 | 7 | A faucet is where you can acquire free [testnet] ETH to use while testing. 8 | 9 | Below are faucet links to each respective testnet. 10 | 11 | - Ropsten testnet - [https://faucet.ropsten.be](https://faucet.ropsten.be) 12 | - Rinkeby testnet - [https://faucet.rinkeby.io](https://faucet.rinkeby.io) 13 | - Kovan testnet - [https://gitter.im/kovan-testnet/faucet](https://gitter.im/kovan-testnet/faucet) 14 | - Sokol testnet - [https://faucet-sokol.herokuapp.com](https://faucet-sokol.herokuapp.com) 15 | -------------------------------------------------------------------------------- /en/hd-wallet/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to create or use an HD wallet in Go. 3 | --- 4 | 5 | # HD Wallet 6 | 7 | For creating or using an HD wallet, please refer to the Go package: [https://github.com/miguelmota/go-ethereum-hdwallet](https://github.com/miguelmota/go-ethereum-hdwallet) 8 | -------------------------------------------------------------------------------- /en/keystore/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on creating and importing a keystore with Go. 3 | --- 4 | 5 | # Keystores 6 | 7 | A keystore is a file containing an encrypted wallet private key. Keystores in go-ethereum can only contain one wallet key pair per file. To generate keystores first you must invoke `NewKeyStore` giving it the directory path to save the keystores. After that, you may generate a new wallet by calling the method `NewAccount` passing it a password for encryption. Every time you call `NewAccount` it will generate a new keystore file on disk. 8 | 9 | Here's a full example of generating a new keystore account. 10 | 11 | ```go 12 | ks := keystore.NewKeyStore("./wallets", keystore.StandardScryptN, keystore.StandardScryptP) 13 | password := "secret" 14 | account, err := ks.NewAccount(password) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 20 | ``` 21 | 22 | Now to import your keystore you basically need to invoke `NewKeyStore` again as usual and then call the `Import` method which accepts the keystore JSON data as bytes. The second argument is the password used to encrypt it in order to decrypt it. The third argument is to specify a new encryption password but we'll use the same one in the example. Importing the account will give you access to the account as expected but it'll generate a new keystore file! There's no point in having two of the same thing so we'll delete the old one. 23 | 24 | Here's an example of importing a keystore and accessing the account. 25 | 26 | ```go 27 | file := "./wallets/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3" 28 | ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) 29 | jsonBytes, err := ioutil.ReadFile(file) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | password := "secret" 35 | account, err := ks.Import(jsonBytes, password, password) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 41 | 42 | if err := os.Remove(file); err != nil { 43 | log.Fatal(err) 44 | } 45 | ``` 46 | 47 | ---- 48 | 49 | ### Full code 50 | 51 | [keystore.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/keystore.go) 52 | 53 | ```go 54 | package main 55 | 56 | import ( 57 | "fmt" 58 | "io/ioutil" 59 | "log" 60 | "os" 61 | 62 | "github.com/ethereum/go-ethereum/accounts/keystore" 63 | ) 64 | 65 | func createKs() { 66 | ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) 67 | password := "secret" 68 | account, err := ks.NewAccount(password) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 74 | } 75 | 76 | func importKs() { 77 | file := "./tmp/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3" 78 | ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) 79 | jsonBytes, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | 84 | password := "secret" 85 | account, err := ks.Import(jsonBytes, password, password) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | 90 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 91 | 92 | if err := os.Remove(file); err != nil { 93 | log.Fatal(err) 94 | } 95 | } 96 | 97 | func main() { 98 | createKs() 99 | //importKs() 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /en/resources/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: List of resources on Ethereum, Solidity, and Go. 3 | --- 4 | 5 | # Resources 6 | 7 | List of resources on Ethereum, Solidity, and Go. 8 | 9 | ## Best Practices 10 | 11 | - [Smart Contract Security Best Practices](https://github.com/ConsenSys/smart-contract-best-practices) 12 | - [Security Considerations](http://solidity.readthedocs.io/en/develop/security-considerations.html) 13 | - [Solidity idiosyncrasies](https://github.com/miguelmota/solidity-idiosyncrasies) 14 | 15 | ## Help & Support 16 | 17 | - StackExchange 18 | - [Ethereum](https://ethereum.stackexchange.com/) 19 | - Reddit 20 | - [ethdev](https://www.reddit.com/r/ethdev/) 21 | - [golang](https://www.reddit.com/r/golang/) 22 | - Gitter 23 | - [List of Gitter channels](https://github.com/ethereum/wiki/wiki/Gitter-Channels) 24 | 25 | ## Community 26 | 27 | - Reddit 28 | - [ethereum](https://www.reddit.com/r/ethereum/) 29 | - [ethtrader](https://www.reddit.com/r/ethtrader/) 30 | - Twitter 31 | - [ethereum](https://twitter.com/ethereum) 32 | 33 | ## Libraries 34 | 35 | - [go-ethereum](https://github.com/ethereum/go-ethereum) 36 | - [go-solidity-sha3](https://github.com/miguelmota/go-solidity-sha3) 37 | - [go-ethereum-hdwallet](https://github.com/miguelmota/go-ethereum-hdwallet) 38 | - [go-ethutil](https://github.com/miguelmota/go-ethutil) 39 | 40 | ## Developer Tools 41 | 42 | - [Truffle](https://truffleframework.com/) 43 | - [Infura](https://infura.io/) 44 | - [Remix IDE](https://remix.ethereum.org/) 45 | - [Keccak-256 Online](https://emn178.github.io/online-tools/keccak_256.html) 46 | -------------------------------------------------------------------------------- /en/signature-generate/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on generating signatures with Go. 3 | --- 4 | 5 | # Generating a Signature 6 | 7 | The components for generating a signature are: the signers private key, and the hash of the data that will be signed. Any hashing algorithm may be used as long as the output is 32 bytes. We'll be using Keccak-256 as the hashing algorithm which is what Ethereum prefers to use. 8 | 9 | First we'll load private key. 10 | 11 | ```go 12 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | ``` 17 | 18 | Next we'll take the Keccak-256 of the data that we wish to sign, in this case it'll be the word *hello*. The go-ethereum `crypto` package provides a handy `Keccak256Hash` method for doing this. 19 | 20 | ```go 21 | data := []byte("hello") 22 | hash := crypto.Keccak256Hash(data) 23 | fmt.Println(hash.Hex()) // 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8 24 | ``` 25 | 26 | Finally we sign the hash with our private, which gives us the signature. 27 | 28 | ```go 29 | signature, err := crypto.Sign(hash.Bytes(), privateKey) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | fmt.Println(hexutil.Encode(signature)) // 0x789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301 35 | ``` 36 | 37 | Now that we have successfully generated the signature, in the next section we'll learn how to verify that the signature indeed was signed by the holder of that private key. 38 | 39 | --- 40 | 41 | ### Full code 42 | 43 | [signature_generate.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/signature_generate.go) 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "fmt" 50 | "log" 51 | 52 | "github.com/ethereum/go-ethereum/common/hexutil" 53 | "github.com/ethereum/go-ethereum/crypto" 54 | ) 55 | 56 | func main() { 57 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | 62 | data := []byte("hello") 63 | hash := crypto.Keccak256Hash(data) 64 | fmt.Println(hash.Hex()) // 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8 65 | 66 | signature, err := crypto.Sign(hash.Bytes(), privateKey) 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | 71 | fmt.Println(hexutil.Encode(signature)) // 0x789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /en/signatures/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on signatures with Go. 3 | --- 4 | 5 | # Signatures 6 | 7 | A digital signature allows non-repudiation as it means the person who signed the message had to be in possession of the private key and so therefore the message is authentic. Anyone can verify the authenticity of the message as long as they have the hash of the original data and the public key of the signer. Signatures are a fundamental component is blockchain and we'll learn how to generate and verify signatures in the next few lessons. 8 | -------------------------------------------------------------------------------- /en/smart-contract-bytecode/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to read the bytecode of a deployed smart contract with Go. 3 | --- 4 | 5 | # Reading Smart Contract Bytecode 6 | 7 | Sometimes you'll need to read the bytecode of a deployed smart contract. Since all the smart contract bytecode lives on the blockchain, we can easily fetch it. 8 | 9 | First set up the client and the smart contract address you want to read the bytecode of. 10 | 11 | ```go 12 | client, err := ethclient.Dial("https://rinkeby.infura.io") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 18 | ``` 19 | 20 | Now all you have to is call the `codeAt` method of the client. The `codeAt` method accepts a smart contract address and an optional block number, and returns the bytecode in bytes format. 21 | 22 | ```go 23 | bytecode, err := client.CodeAt(context.Background(), contractAddress, nil) // nil is latest block 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | fmt.Println(hex.EncodeToString(bytecode)) // 60806...10029 29 | ``` 30 | 31 | 32 | See the same bytecode hex on etherscan [https://rinkeby.etherscan.io/address/0x147b8eb97fd247d06c4006d269c90c1908fb5d54#code](https://rinkeby.etherscan.io/address/0x147b8eb97fd247d06c4006d269c90c1908fb5d54#code) 33 | 34 | --- 35 | 36 | ### Full code 37 | 38 | [contract_bytecode.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contract_bytecode.go) 39 | 40 | ```go 41 | package main 42 | 43 | import ( 44 | "context" 45 | "encoding/hex" 46 | "fmt" 47 | "log" 48 | 49 | "github.com/ethereum/go-ethereum/common" 50 | "github.com/ethereum/go-ethereum/ethclient" 51 | ) 52 | 53 | func main() { 54 | client, err := ethclient.Dial("https://rinkeby.infura.io") 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 60 | bytecode, err := client.CodeAt(context.Background(), contractAddress, nil) // nil is latest block 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | 65 | fmt.Println(hex.EncodeToString(bytecode)) // 60806...10029 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /en/smart-contract-load/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to load an initialize a smart contract with Go. 3 | --- 4 | 5 | # Loading a Smart Contract 6 | 7 | These section requires knowledge of how to compile a smart contract's ABI to a Go contract file. If you haven't already gone through it, please [read the section](../smart-contract-compile) first. 8 | 9 | Once you've compiled your smart contract's ABI to a Go package using the `abigen` tool, the next step is to call the "New" method, which is in the format `New`, so in our example if you recall it's going to be *NewStore*. This initializer method takes in the address of the smart contract and returns a contract instance that you can start interact with it. 10 | 11 | ```go 12 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 13 | instance, err := store.NewStore(address, client) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | _ = instance // we'll be using this in the next section 19 | ``` 20 | 21 | --- 22 | 23 | ### Full code 24 | 25 | Commands 26 | 27 | ```bash 28 | solc --abi Store.sol 29 | solc --bin Store.sol 30 | abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go 31 | ``` 32 | 33 | [Store.sol](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contracts/Store.sol) 34 | 35 | ```solidity 36 | pragma solidity ^0.4.24; 37 | 38 | contract Store { 39 | event ItemSet(bytes32 key, bytes32 value); 40 | 41 | string public version; 42 | mapping (bytes32 => bytes32) public items; 43 | 44 | constructor(string _version) public { 45 | version = _version; 46 | } 47 | 48 | function setItem(bytes32 key, bytes32 value) external { 49 | items[key] = value; 50 | emit ItemSet(key, value); 51 | } 52 | } 53 | ``` 54 | 55 | [contract_load.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contract_load.go) 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "fmt" 62 | "log" 63 | 64 | "github.com/ethereum/go-ethereum/common" 65 | "github.com/ethereum/go-ethereum/ethclient" 66 | 67 | store "./contracts" // for demo 68 | ) 69 | 70 | func main() { 71 | client, err := ethclient.Dial("https://rinkeby.infura.io") 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 77 | instance, err := store.NewStore(address, client) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | 82 | fmt.Println("contract is loaded") 83 | _ = instance 84 | } 85 | ``` 86 | 87 | solc version used for these examples 88 | 89 | ```bash 90 | $ solc --version 91 | 0.4.24+commit.e67f0147.Emscripten.clang 92 | ``` 93 | -------------------------------------------------------------------------------- /en/smart-contract-read/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to query a smart contract with Go. 3 | --- 4 | 5 | # Querying a Smart Contract 6 | 7 | These section requires knowledge of how to compile a smart contract's ABI to a Go contract file. If you haven't already gone through it, please [read the section](../smart-contract-compile) first. 8 | 9 | In the previous section we learned how to initialize a contract instance in our Go application. Now we're going to read the smart contract using the provided methods by the new contract instance. If you recall we had a global variable named `version` in our contract that was set during deployment. Because it's public that means that they'll be a getter function automatically created for us. Constant and view functions also accept `bind.CallOpts` as the first argument. To learn about what options you can pass checkout the type's [documentation](https://godoc.org/github.com/ethereum/go-ethereum/accounts/abi/bind#CallOpts) but usually this is set to `nil`. 10 | 11 | ```go 12 | version, err := instance.Version(nil) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | fmt.Println(version) // "1.0" 18 | ``` 19 | 20 | --- 21 | 22 | ### Full code 23 | 24 | Commands 25 | 26 | ```bash 27 | solc --abi Store.sol 28 | solc --bin Store.sol 29 | abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go 30 | ``` 31 | 32 | [Store.sol](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contracts/Store.sol) 33 | 34 | ```solidity 35 | pragma solidity ^0.4.24; 36 | 37 | contract Store { 38 | event ItemSet(bytes32 key, bytes32 value); 39 | 40 | string public version; 41 | mapping (bytes32 => bytes32) public items; 42 | 43 | constructor(string _version) public { 44 | version = _version; 45 | } 46 | 47 | function setItem(bytes32 key, bytes32 value) external { 48 | items[key] = value; 49 | emit ItemSet(key, value); 50 | } 51 | } 52 | ``` 53 | 54 | [contract_read.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contract_read.go) 55 | 56 | ```go 57 | package main 58 | 59 | import ( 60 | "fmt" 61 | "log" 62 | 63 | "github.com/ethereum/go-ethereum/common" 64 | "github.com/ethereum/go-ethereum/ethclient" 65 | 66 | store "./contracts" // for demo 67 | ) 68 | 69 | func main() { 70 | client, err := ethclient.Dial("https://rinkeby.infura.io") 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 76 | instance, err := store.NewStore(address, client) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | version, err := instance.Version(nil) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | 86 | fmt.Println(version) // "1.0" 87 | } 88 | ``` 89 | 90 | solc version used for these examples 91 | 92 | ```bash 93 | $ solc --version 94 | 0.4.24+commit.e67f0147.Emscripten.clang 95 | ``` 96 | -------------------------------------------------------------------------------- /en/smart-contracts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to smart contracts with Go. 3 | --- 4 | 5 | # Smart Contracts 6 | 7 | In the next sections we'll learn how to compile, deploy, read, and write to smart contract using Go. 8 | -------------------------------------------------------------------------------- /en/styles/website.css: -------------------------------------------------------------------------------- 1 | .gitbook-link { 2 | display: none !important; 3 | } 4 | 5 | body::-webkit-scrollbar, 6 | .book-summary::-webkit-scrollbar { 7 | width: 8px; 8 | height: 8px; 9 | } 10 | 11 | body::-webkit-scrollbar-track, 12 | .book-summary::-webkit-scrollbar-track { 13 | background: transparent; 14 | } 15 | 16 | body::-webkit-scrollbar-thumb, 17 | .book-summary::-webkit-scrollbar-thumb { 18 | background-color: rgba(0,0,0,0.05); 19 | border: 1px solid transparent; 20 | box-shadow: inset 1px 1px 0 rgba(0,0,0,0.05), inset 0 -1px 0 rgba(0,0,0,0.1); 21 | } 22 | 23 | body::-webkit-scrollbar-thumb:hover, 24 | .book-summary::-webkit-scrollbar-thumb:hover { 25 | box-shadow: inset 1px 1px 0 rgba(0,0,0,0.1), inset 0 -1px 0 rgba(0,0,0,0.2); 26 | } 27 | -------------------------------------------------------------------------------- /en/swarm-download/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on downloadig files from swarm in Go. 3 | --- 4 | 5 | # Downloading Files from Swarm 6 | 7 | In the [previous section](../swarm-upload) we uploaded a hello.txt file to swarm and in return we got a manifest hash. 8 | 9 | ```go 10 | manifestHash := "f9192507e2e8e118bfedac428c3aa1dec4ae156e954128ec5fb27f63ee67bcac" 11 | ``` 12 | 13 | Let's inspect the manifest by downloading it first by calling `DownloadManfest`. 14 | 15 | ```go 16 | manifest, isEncrypted, err := client.DownloadManifest(manifestHash) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | ``` 21 | 22 | We can iterate over the manifest entries and see what the content-type, size, and content hash are. 23 | 24 | ```go 25 | for _, entry := range manifest.Entries { 26 | fmt.Println(entry.Hash) // 42179060941352ba7b400b16c40f1e1290423a826de2a70587034dc14bc4ab2f 27 | fmt.Println(entry.ContentType) // text/plain; charset=utf-8 28 | fmt.Println(entry.Path) // "" 29 | } 30 | ``` 31 | 32 | If you're familiar with swarm urls, they're in the format `bzz://`, so in order to download the file we specify the manifest hash and path. The path in this case is an empty string. We pass this data to the `Download` function and get back a file object. 33 | 34 | ```go 35 | file, err := client.Download(manifestHash, "") 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | ``` 40 | 41 | We may now read and print the contents of the returned file reader. 42 | 43 | ```go 44 | content, err := ioutil.ReadAll(file) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | 49 | fmt.Println(string(content)) // hello world 50 | ``` 51 | 52 | As expected, it logs *hello world* which what our original file contained. 53 | 54 | --- 55 | 56 | ### Full code 57 | 58 | Commands 59 | 60 | ```bash 61 | geth account new 62 | export BZZKEY=970ef9790b54425bea2c02e25cab01e48cf92573 63 | swarm --bzzaccount $BZZKEY 64 | ``` 65 | 66 | [swarm_download.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/swarm_download.go) 67 | 68 | ```go 69 | package main 70 | 71 | import ( 72 | "fmt" 73 | "io/ioutil" 74 | "log" 75 | 76 | bzzclient "github.com/ethersphere/swarm/api/client" 77 | ) 78 | 79 | func main() { 80 | client := bzzclient.NewClient("http://127.0.0.1:8500") 81 | manifestHash := "2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0" 82 | manifest, isEncrypted, err := client.DownloadManifest(manifestHash) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | fmt.Println(isEncrypted) // false 87 | 88 | for _, entry := range manifest.Entries { 89 | fmt.Println(entry.Hash) // 42179060941352ba7b400b16c40f1e1290423a826de2a70587034dc14bc4ab2f 90 | fmt.Println(entry.ContentType) // text/plain; charset=utf-8 91 | fmt.Println(entry.Size) // 12 92 | fmt.Println(entry.Path) // "" 93 | } 94 | 95 | file, err := client.Download(manifestHash, "") 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | 100 | content, err := ioutil.ReadAll(file) 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | fmt.Println(string(content)) // hello world 105 | } 106 | ``` 107 | -------------------------------------------------------------------------------- /en/swarm-setup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on setting up swarm node. 3 | --- 4 | 5 | # Setting up Swarm 6 | 7 | To run swarm you first need to install `geth` and `bzzd` which is the swarm daemon. 8 | 9 | ```go 10 | go install github.com/ethereum/go-ethereum/cmd/geth 11 | go install github.com/ethersphere/swarm/cmd/swarm 12 | ``` 13 | 14 | Now we'll generate a new geth account. 15 | 16 | ```bash 17 | $ geth account new 18 | 19 | Your new account is locked with a password. Please give a password. Do not forget this password. 20 | Passphrase: 21 | Repeat passphrase: 22 | Address: {970ef9790b54425bea2c02e25cab01e48cf92573} 23 | ``` 24 | 25 | Export the environment variable `BZZKEY` mapping to the geth account address we just generated. 26 | 27 | ```bash 28 | export BZZKEY=970ef9790b54425bea2c02e25cab01e48cf92573 29 | ``` 30 | 31 | And now run swarm with the specified account to be our swarm account. Swarm by default will run on port `8500`. 32 | 33 | ```bash 34 | $ swarm --bzzaccount $BZZKEY 35 | Unlocking swarm account 0x970EF9790B54425BEA2C02e25cAb01E48CF92573 [1/3] 36 | Passphrase: 37 | WARN [06-12|13:11:41] Starting Swarm service 38 | ``` 39 | 40 | Now that we have the swarm daemon set up and running, let's learn how to upload files to swarm in the [next section](../swarm-upload). 41 | 42 | --- 43 | 44 | ### Full code 45 | 46 | Commands 47 | 48 | ```bash 49 | go install github.com/ethereum/go-ethereum/cmd/geth 50 | go install github.com/ethersphere/swarm/cmd/swarm 51 | geth account new 52 | export BZZKEY=970ef9790b54425bea2c02e25cab01e48cf92573 53 | swarm --bzzaccount $BZZKEY 54 | ``` 55 | -------------------------------------------------------------------------------- /en/swarm-upload/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on uploading files to swarm with go. 3 | --- 4 | 5 | # Uploading Files to Swarm 6 | 7 | In the [previous section](../swarm-setup) we setup a swarm node running as a daemon on port `8500`. Now import the swarm package ethersphere `swarm/api/client`. I'll be aliasing the package to `bzzclient`. 8 | 9 | ```go 10 | import ( 11 | bzzclient "github.com/ethersphere/swarm/api/client" 12 | ) 13 | ``` 14 | 15 | Invoke `NewClient` function passing it the swarm daemon url. 16 | 17 | ```go 18 | client := bzzclient.NewClient("http://127.0.0.1:8500") 19 | ``` 20 | 21 | Create an example text file `hello.txt` with the content *hello world*. We'll be uploading this to swarm. 22 | 23 | ```txt 24 | hello world 25 | ``` 26 | 27 | In our Go application we'll open the file we just created using `Open` from the client package. This function will return a `File` type which represents a file in a swarm manifest and is used for uploading and downloading content to and from swarm. 28 | 29 | ```go 30 | file, err := bzzclient.Open("hello.txt") 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | ``` 35 | 36 | Now we can invoke the `Upload` function from our client instance giving it the file object. The second argument is an optional existing manifest string to add the file to, otherwise it'll create on for us. The third argument is if we want our data to be encrypted. 37 | 38 | The hash returned is the swarm hash of a manifest that contains the hello.txt file as its only entry. So by default both the primary content and the manifest is uploaded. The manifest makes sure you could retrieve the file with the correct mime type. 39 | 40 | ```go 41 | manifestHash, err := client.Upload(file, "", false) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | fmt.Println(manifestHash) // 2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0 47 | ``` 48 | 49 | Now we can access our file at `bzz://2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0` which learn how to do in the [next section](../swarm-download). 50 | 51 | --- 52 | 53 | ### Full code 54 | 55 | Commands 56 | 57 | ```bash 58 | geth account new 59 | export BZZKEY=970ef9790b54425bea2c02e25cab01e48cf92573 60 | swarm --bzzaccount $BZZKEY 61 | ``` 62 | 63 | [hello.txt](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/hello.txt) 64 | 65 | ```txt 66 | hello world 67 | ``` 68 | 69 | [swarm_upload.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/swarm_upload.go) 70 | 71 | ```go 72 | package main 73 | 74 | import ( 75 | "fmt" 76 | "log" 77 | 78 | bzzclient "github.com/ethersphere/swarm/api/client" 79 | ) 80 | 81 | func main() { 82 | client := bzzclient.NewClient("http://127.0.0.1:8500") 83 | 84 | file, err := bzzclient.Open("hello.txt") 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | 89 | manifestHash, err := client.Upload(file, "", false, false, false) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | fmt.Println(manifestHash) // 2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /en/swarm/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on swarm with Go. 3 | --- 4 | 5 | # Swarm 6 | 7 | Swarm in Ethereum's decentralized and distributed storage solution, comparable to IPFS. Swarm is a peer to peer data sharing network in which files are addressed by the hash of their content. Similar to Bittorrent, it is possible to fetch the data from many nodes at once and as long as a single node hosts a piece of data, it will remain accessible everywhere. This approach makes it possible to distribute data without having to host any kind of server - data accessibility is location independent. Other nodes in the network can be incentivised to replicate and store the data themselves, obviating the need for hosting services when the original nodes are not connected to the network. 8 | 9 | Swarm's incentive mechanism, Swap (Swarm Accounting Protocol), is a protocol by which peers in the Swarm network keep track of chunks delivered and received and the resulting (micro-) payments owed. On its own, SWAP can function in a wider context however it's usually presented as a generic micropayment scheme suited for pairwise accounting between peers. while generic by design, the first use of it is for accounting of bandwidth as part of the incentivisation of data transfer in the Swarm decentralised peer to peer storage network. 10 | 11 | -------------------------------------------------------------------------------- /en/test/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to test your Ethereum application with Go. 3 | --- 4 | 5 | # Testing 6 | 7 | - [Faucets](../faucets) 8 | - [Using a Simulated Client](../client-simulated) 9 | 10 | -------------------------------------------------------------------------------- /en/transaction-raw-send/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to send a raw Ethereum transaction with Go. 3 | --- 4 | 5 | # Send Raw Transaction 6 | 7 | In the [previous section](../transaction-raw-create) we learned how to create a raw transaction. Now we'll learn how to broadcast it to the Ethereum network in order for it to get processed and mined. 8 | 9 | First decode the raw transaction hex to bytes format. 10 | 11 | ```go 12 | rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772" 13 | 14 | rawTxBytes, err := hex.DecodeString(rawTx) 15 | ``` 16 | 17 | Now initialize a new `types.Transaction` pointer and call `DecodeBytes` from the go-ethereum `rlp` package passing it the raw transaction bytes and the pointer to the ethereum transaction type. RLP is an encoding method used by Ethereum to serialized and derialized data. 18 | 19 | ```go 20 | tx := new(types.Transaction) 21 | rlp.DecodeBytes(rawTxBytes, &tx) 22 | ``` 23 | 24 | Now we can easily broadcast the transaction with our ethereum client. 25 | 26 | ```go 27 | err := client.SendTransaction(context.Background(), tx) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f 33 | ``` 34 | 35 | You can see the transaction on etherscan: [https://rinkeby.etherscan.io/tx/0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f](https://rinkeby.etherscan.io/tx/0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f) 36 | 37 | --- 38 | 39 | ### Full code 40 | 41 | [transaction_raw_sendreate.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/transaction_raw_send.go) 42 | 43 | ```go 44 | package main 45 | 46 | import ( 47 | "context" 48 | "encoding/hex" 49 | "fmt" 50 | "log" 51 | 52 | "github.com/ethereum/go-ethereum/core/types" 53 | "github.com/ethereum/go-ethereum/ethclient" 54 | "github.com/ethereum/go-ethereum/rlp" 55 | ) 56 | 57 | func main() { 58 | client, err := ethclient.Dial("https://rinkeby.infura.io") 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | 63 | rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772" 64 | 65 | rawTxBytes, err := hex.DecodeString(rawTx) 66 | 67 | tx := new(types.Transaction) 68 | rlp.DecodeBytes(rawTxBytes, &tx) 69 | 70 | err = client.SendTransaction(context.Background(), tx) 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /en/transactions/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on transactions with Go. 3 | --- 4 | 5 | # Transactions 6 | 7 | These sections will discuss how to query and make transactions on Ethereum using the go-ethereum `ethclient` package. 8 | -------------------------------------------------------------------------------- /en/util/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Ethereum utilities in Go. 3 | --- 4 | 5 | # Utilities 6 | 7 | - [Collection of Utility Functions](../util-go) 8 | -------------------------------------------------------------------------------- /en/whisper-client/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on whisper with Go. 3 | --- 4 | 5 | # Connecting Whisper Client 6 | 7 | To use whisper, we must first connect to an Ethereum node running whisper. Unfortunately, public gateways such as infura don't support whisper because there is no incentive for processing the messages for free. Infura might support whisper in the near future but for now we must run our own `geth` node. Once you [install geth](https://geth.ethereum.org/downloads/), run it with the `--shh` flag on to enable the whisper protocol, as well as the `--ws` flag to enable websocket support in order to receive messages in real time, and also enable the `--rpc` flag because we'll be communicating over RPC. 8 | 9 | ```bash 10 | geth --rpc --shh --ws 11 | ``` 12 | 13 | Now in our Go application we'll import the go-ethereum whisper client package found at `whisper/shhclient` and initialize the client to connect our local geth node over websockets using the default websocket port `8546`. 14 | 15 | ```go 16 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | _ = client // we'll be using this in the next section 22 | ``` 23 | 24 | Now that we're dialed in let's create a key pair for encrypting the message before we send it in the [next section](../whisper-keys). 25 | 26 | --- 27 | 28 | ### Full code 29 | 30 | Commands 31 | 32 | ```bash 33 | geth --rpc --shh --ws 34 | ``` 35 | 36 | [whisper_client.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/whisper_client.go) 37 | 38 | ```go 39 | package main 40 | 41 | import ( 42 | "log" 43 | 44 | "github.com/ethereum/go-ethereum/whisper/shhclient" 45 | ) 46 | 47 | func main() { 48 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | _ = client // we'll be using this in the next section 54 | fmt.Println("we have a whisper connection") 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /en/whisper-keys/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on how to generate whisper key pairs with Go. 3 | --- 4 | 5 | # Generating Whisper Key Pair 6 | 7 | In whisper, messages have to be encrypted with either a symmetric or an asymmetric key to prevent them from being read by anyone other than the intended recipient. 8 | 9 | After you've connected to the whisper client you'll need to call the client's `NewKeyPair` method to generate a new public and private pair that the node will manage. The result of this function will be a unique ID that references the key pair which we'll be using for encrypting and decrypting the message in the next few sections. 10 | 11 | ```go 12 | keyID, err := client.NewKeyPair(context.Background()) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 18 | ``` 19 | 20 | Let's learn how to send an encrypted message in the [next section](../whisper-send). 21 | 22 | --- 23 | 24 | ### Full code 25 | 26 | Commands 27 | 28 | ```bash 29 | geth --rpc --shh --ws 30 | ``` 31 | 32 | [whisper_keypair.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/whisper_keypair.go) 33 | 34 | ```go 35 | package main 36 | 37 | import ( 38 | "context" 39 | "fmt" 40 | "log" 41 | 42 | "github.com/ethereum/go-ethereum/whisper/shhclient" 43 | ) 44 | 45 | func main() { 46 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | keyID, err := client.NewKeyPair(context.Background()) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | 56 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /en/whisper-poll/README.md: -------------------------------------------------------------------------------- 1 | FilterMessages retrieves all messages that are received between the last call to this function and match the criteria that where given when the filter was created. 2 | 3 | NewMessageFilter creates a filter within the node. This filter can be used to poll for new messages (see FilterMessages) that satisfy the given criteria. A filter can timeout when it was polled for in whisper.filterTimeout. 4 | -------------------------------------------------------------------------------- /en/whisper/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Tutorial on whisper with Go. 3 | --- 4 | 5 | # Whisper 6 | 7 | Whisper is a simple peer-to-peer identity-based messaging system designed to be a building block in the next generation of decentralized applications. It was designed to provide resilience and privacy at considerable expense. In the upcoming sections we'll set up an Ethereum node with whisper support and then we'll learn how to send and receive encrypted messages on the whisper protocol. 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/miguelmota/ethereum-development-with-go 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/allegro/bigcache v1.1.0 // indirect 7 | github.com/aristanetworks/goarista v0.0.0-20190115004922-b7a59f2ffb23 // indirect 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/deckarep/golang-set v1.7.1 // indirect 10 | github.com/edsrzf/mmap-go v1.0.0 // indirect 11 | github.com/ethereum/go-ethereum v1.9.2 // indirect 12 | github.com/ethersphere/swarm v0.5.7 // indirect 13 | github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect 14 | github.com/go-stack/stack v1.8.0 // indirect 15 | github.com/golang/protobuf v1.4.0 // indirect 16 | github.com/hashicorp/golang-lru v0.5.4 // indirect 17 | github.com/huin/goupnp v1.0.0 // indirect 18 | github.com/jackpal/go-nat-pmp v1.0.2 // indirect 19 | github.com/karalabe/hid v1.0.0 // indirect 20 | github.com/kr/pretty v0.1.0 // indirect 21 | github.com/mattn/go-colorable v0.1.6 // indirect 22 | github.com/opentracing/opentracing-go v1.1.0 // indirect 23 | github.com/pborman/uuid v1.2.0 // indirect 24 | github.com/pkg/errors v0.9.1 // indirect 25 | github.com/rjeczalik/notify v0.9.2 // indirect 26 | github.com/rs/cors v1.6.0 // indirect 27 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect 28 | github.com/uber/jaeger-client-go v2.23.0+incompatible // indirect 29 | github.com/uber/jaeger-lib v2.2.0+incompatible // indirect 30 | go.uber.org/atomic v1.6.0 // indirect 31 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect 32 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect 33 | golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect 34 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 35 | gopkg.in/urfave/cli.v1 v1.20.0 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethereum-development-with-go", 3 | "version": "1.0.0", 4 | "description": "> A little book on [Ethereum](https://www.ethereum.org/) Development with [Go](https://golang.org/) (golang)", 5 | "main": "index.js", 6 | "dependencies": { 7 | "canvas": "^1.6.11", 8 | "gitbook-plugin-analytics": "^0.2.1", 9 | "gitbook-plugin-autocover": "^2.0.1", 10 | "gitbook-plugin-ga": "^2.0.0" 11 | }, 12 | "devDependencies": { 13 | "netlify-cli": "^10.3.1" 14 | }, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1", 17 | "build-deploy": "make build && make ebooks-cp && npm run deploy", 18 | "deploy": "netlify deploy --prod" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/miguelmota/ethereum-development-with-go-book.git" 23 | }, 24 | "author": { 25 | "name": "Miguel Mota", 26 | "email": "hello@miguelmota.com", 27 | "url": "https://miguelmota.com/" 28 | }, 29 | "license": { 30 | "type": "CC0-1.0", 31 | "url": "https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/LICENSE" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/miguelmota/ethereum-development-with-go-bok/issues" 35 | }, 36 | "homepage": "https://github.com/miguelmota/ethereum-development-with-go-book#readme" 37 | } 38 | -------------------------------------------------------------------------------- /styles/website.css: -------------------------------------------------------------------------------- 1 | .gitbook-link { 2 | display: none !important; 3 | } 4 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/allegro/bigcache v1.1.0 2 | ## explicit 3 | # github.com/aristanetworks/goarista v0.0.0-20190115004922-b7a59f2ffb23 4 | ## explicit 5 | # github.com/davecgh/go-spew v1.1.1 6 | ## explicit 7 | # github.com/deckarep/golang-set v1.7.1 8 | ## explicit 9 | # github.com/edsrzf/mmap-go v1.0.0 10 | ## explicit 11 | # github.com/ethereum/go-ethereum v1.9.2 12 | ## explicit 13 | # github.com/ethersphere/swarm v0.5.7 14 | ## explicit 15 | # github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 16 | ## explicit 17 | # github.com/go-stack/stack v1.8.0 18 | ## explicit 19 | # github.com/golang/protobuf v1.4.0 20 | ## explicit 21 | # github.com/hashicorp/golang-lru v0.5.4 22 | ## explicit 23 | # github.com/huin/goupnp v1.0.0 24 | ## explicit 25 | # github.com/jackpal/go-nat-pmp v1.0.2 26 | ## explicit 27 | # github.com/karalabe/hid v1.0.0 28 | ## explicit 29 | # github.com/kr/pretty v0.1.0 30 | ## explicit 31 | # github.com/mattn/go-colorable v0.1.6 32 | ## explicit 33 | # github.com/opentracing/opentracing-go v1.1.0 34 | ## explicit 35 | # github.com/pborman/uuid v1.2.0 36 | ## explicit 37 | # github.com/pkg/errors v0.9.1 38 | ## explicit 39 | # github.com/rjeczalik/notify v0.9.2 40 | ## explicit 41 | # github.com/rs/cors v1.6.0 42 | ## explicit 43 | # github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 44 | ## explicit 45 | # github.com/uber/jaeger-client-go v2.23.0+incompatible 46 | ## explicit 47 | # github.com/uber/jaeger-lib v2.2.0+incompatible 48 | ## explicit 49 | # go.uber.org/atomic v1.6.0 50 | ## explicit 51 | # golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 52 | ## explicit 53 | # golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a 54 | ## explicit 55 | # golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f 56 | ## explicit 57 | # gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 58 | ## explicit 59 | # gopkg.in/urfave/cli.v1 v1.20.0 60 | ## explicit 61 | -------------------------------------------------------------------------------- /zh/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 这本迷你书可以帮助你学习如何用Go语言部署,编译,与智能合约交互,发交易,使用Swarm和Whisper协议。就这么简单:) 3 | --- 4 | 5 | # 用Go来做以太坊开发 6 | 7 | 这本迷你书的本意是给任何想用Go进行以太坊开发的同学一个概括的介绍。本意是如果你已经对以太坊和Go有一些熟悉,但是对于怎么把两者结合起来还有些无从下手,那这本书就是一个好的起点。你会学习如何用Go与智能合约交互,还有如何完成一些日常的查询和任务。 8 | 9 | 这本书里有很多我希望我当初学习用Go以太坊开发的时候能有的代码范例。你上手Go语言以太坊开发的大部分所需知识,这本书里面都会手把手介绍到。 10 | 11 | 当然了,以太坊还是一直在飞速的发展的进化的。所以难免会有些过期的内容,或者你认为有可以值得提升的地方,那就是你提 [issue](https://github.com/miguelmota/ethereum-development-with-go-book/issues) 或者 [pull request](https://github.com/miguelmota/ethereum-development-with-go-book/pulls) 的好机会了:)这本书是完全开源并且免费的,你可以在 [github](https://github.com/miguelmota/ethereum-development-with-go-book) 上看到源码. 12 | 13 | #### 在线电子书 14 | 15 | [https://goethereumbook.org](https://goethereumbook.org/) 16 | 17 | #### 电子书 18 | 19 | 电子书有三种格式。 20 | 21 | - [PDF](https://goethereumbook.org/ethereum-development-with-go-zh.pdf) 22 | - [EPUB](https://goethereumbook.org/ethereum-development-with-go-zh.epub) 23 | - [MOBI](https://goethereumbook.org/ethereum-development-with-go-zh.mobi) 24 | 25 | ## 介绍 26 | 27 | > 以太坊是一个开源,公开,基于区块链的分布式计算平台和具备智能合约(脚本)功能的操作系统。它通过基于交易的状态转移支持中本聪共识的一个改进算法。 28 | 29 | -[维基百科](https://en.wikipedia.org/wiki/Ethereum) 30 | 31 | 以太坊是一个区块链,允许开发者创建完全去中心化运行的应用程序,这意味着没有单个实体可以将其删除或修改它。部署到以太坊上的每个应用都由以太坊网络上每个完整客户端执行。 32 | 33 | #### Solidity 34 | 35 | Solidity是一种用于编写智能合约的图灵完备编程语言。Solidity被编译成以太坊虚拟机可执行的字节码。 36 | 37 | #### go-ethereum 38 | 39 | 本书中,我们将使用Go的官方以太坊实现[go-ethereum](https://github.com/ethereum/go-ethereum)来和以太坊区块链进行交互。Go-ethereum,也被简称为Geth,是最流行的以太坊客户端。因为它是用Go开发的,当使用Golang开发应用程序时,Geth提供了读写区块链的一切功能。 40 | 41 | 本书的例子在go-ethereum版本`1.8.10-stable`和Go版本`go1.10.2`下完成测试。 42 | 43 | #### Block Explorers 44 | 45 | [Etherscan](https://etherscan.io)是一个用于查看和深入研究区块链上数据的网站。这些类型的网站被称为*区块浏览器*,因为它们允许您查看区块(包含交易)的内容。区块是区块链的基础构成要素。区块包含在已分配的出块时间内开采出的所有交易数据。区块浏览器也允许您查看智能合约执行期间释放的事件以及诸如支付的gas和交易的以太币数量等。 46 | 47 | ### Swarm and Whisper 48 | 49 | 我们还将深入研究蜂群(Swarm)和耳语(Whisper),分别是一个文件存储协议和一个点对点的消息传递协议,它们是实现完全去中心化和分布式应用程序需要的另外两个核心。 50 | 51 | 52 | 53 | 图片来源 54 | 55 | ## 寻求帮助 56 | 57 | 寻求Go(Golang)帮助可以加入[gophers slack]((https://invite.slack.golangbridge.org/))上的[#ethereum]((https://gophers.slack.com/messages/C9HP1S9V2/))频道。 58 | 59 | --- 60 | 61 | 介绍部分足够了,让我们[开始](../zh/client)吧。 62 | -------------------------------------------------------------------------------- /zh/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # 总目录 2 | 3 | * [客户端](client/README.md) 4 | * [创建客户端](client-setup/README.md) 5 | * [以太坊账户](accounts/README.md) 6 | * [账户余额](account-balance/README.md) 7 | * [账户代币余额](account-balance-token/README.md) 8 | * [生成新钱包](wallet-generate/README.md) 9 | * [密匙库](keystore/README.md) 10 | * [分层确定性钱包](hd-wallet/README.md) 11 | * [地址验证](address-check/README.md) 12 | * [交易](transactions/README.md) 13 | * [查询区块](block-query/README.md) 14 | * [查询交易](transaction-query/README.md) 15 | * [ETH转账](transfer-eth/README.md) 16 | * [代币转账](transfer-tokens/README.md) 17 | * [监听新区块](block-subscribe/README.md) 18 | * [创建裸交易](transaction-raw-create/README.md) 19 | * [发送裸交易](transaction-raw-send/README.md) 20 | * [智能合约](smart-contracts/README.md) 21 | * [智能合约 & ABI](smart-contract-compile/README.md) 22 | * [部署智能合约](smart-contract-deploy/README.md) 23 | * [加载智能合约](smart-contract-load/README.md) 24 | * [查询智能合约](smart-contract-read/README.md) 25 | * [写入智能合约](smart-contract-write/README.md) 26 | * [读取智能合约二进制码](smart-contract-bytecode/README.md) 27 | * [查询ERC20代币智能合约](smart-contract-read-erc20/README.md) 28 | * [事件日志](events/README.md) 29 | * [监听事件日志](event-subscribe/README.md) 30 | * [读取事件日志](event-read/README.md) 31 | * [读取ERC20代币的事件日志](event-read-erc20/README.md) 32 | * [读取0x Protocol事件日志](event-read-0xprotocol/README.md) 33 | * [签名](signatures/README.md) 34 | * [生成签名](signature-generate/README.md) 35 | * [验证签名](signature-verify/README.md) 36 | * [测试](test/README.md) 37 | * [发币龙头](faucets/README.md) 38 | * [使用模拟客户端](client-simulated/README.md) 39 | * [Swarm存储](swarm/README.md) 40 | * [创建 Swarm存储](swarm-setup/README.md) 41 | * [上传文件到Swarm](swarm-upload/README.md) 42 | * [从Swarm下载文件](swarm-download/README.md) 43 | * [Whisper通信协议](whisper/README.md) 44 | * [创建Whisper客户端](whisper-client/README.md) 45 | * [生成Whisper密匙对](whisper-keys/README.md) 46 | * [在Whisper上发送消息](whisper-send/README.md) 47 | * [监听Whisper消息](whisper-subscribe/README.md) 48 | * [工具](util/README.md) 49 | * [工具集合](util-go/README.md) 50 | * [专有词汇表](GLOSSARY.md) 51 | * [资源](resources/README.md) 52 | -------------------------------------------------------------------------------- /zh/account-balance-token/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go从区块链读取账户代币余额教程。 3 | --- 4 | 5 | # 账户代币余额 6 | 7 | 要学习如何读取账户代币(ECR20)余额,请前往[ECR20代币智能合约章节](../smart-contract-read-erc20)。 -------------------------------------------------------------------------------- /zh/account-balance/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go从区块链读取账户余额教程。 3 | --- 4 | 5 | # 账户余额 6 | 7 | 读取一个账户的余额相当简单。调用客户端的`BalanceAt`方法,给它传递账户地址和可选的区块号。将区块号设置为`nil`将返回最新的余额。 8 | 9 | ```go 10 | account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 11 | balance, err := client.BalanceAt(context.Background(), account, nil) 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | fmt.Println(balance) // 25893180161173005034 17 | ``` 18 | 19 | 传区块号能让您读取该区块时的账户余额。区块号必须是`big.Int`类型。 20 | 21 | ```go 22 | blockNumber := big.NewInt(5532993) 23 | balance, err := client.BalanceAt(context.Background(), account, blockNumber) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | fmt.Println(balance) // 25729324269165216042 29 | ``` 30 | 31 | 以太坊中的数字是使用尽可能小的单位来处理的,因为它们是定点精度,在ETH中它是*wei*。要读取ETH值,您必须做计算`wei/10^18`。因为我们正在处理大数,我们得导入原生的Go`math`和`math/big`包。这是您做的转换。 32 | 33 | ```go 34 | fbalance := new(big.Float) 35 | fbalance.SetString(balance.String()) 36 | ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18))) 37 | 38 | fmt.Println(ethValue) // 25.729324269165216041 39 | ``` 40 | 41 | #### 待处理的余额 42 | 43 | 有时您想知道待处理的账户余额是多少,例如,在提交或等待交易确认后。客户端提供了类似`BalanceAt`的方法,名为`PendingBalanceAt`,它接收账户地址作为参数。 44 | 45 | ```go 46 | pendingBalance, err := client.PendingBalanceAt(context.Background(), account) 47 | fmt.Println(pendingBalance) // 25729324269165216042 48 | ``` 49 | 50 | --- 51 | 52 | ### 完整代码 53 | 54 | [account_balance.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/account_balance.go) 55 | 56 | ```go 57 | package main 58 | 59 | import ( 60 | "context" 61 | "fmt" 62 | "log" 63 | "math" 64 | "math/big" 65 | 66 | "github.com/ethereum/go-ethereum/common" 67 | "github.com/ethereum/go-ethereum/ethclient" 68 | ) 69 | 70 | func main() { 71 | client, err := ethclient.Dial("https://cloudflare-eth.com") 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | account := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 77 | balance, err := client.BalanceAt(context.Background(), account, nil) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | fmt.Println(balance) // 25893180161173005034 82 | 83 | blockNumber := big.NewInt(5532993) 84 | balanceAt, err := client.BalanceAt(context.Background(), account, blockNumber) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | fmt.Println(balanceAt) // 25729324269165216042 89 | 90 | fbalance := new(big.Float) 91 | fbalance.SetString(balanceAt.String()) 92 | ethValue := new(big.Float).Quo(fbalance, big.NewFloat(math.Pow10(18))) 93 | fmt.Println(ethValue) // 25.729324269165216041 94 | 95 | pendingBalance, err := client.PendingBalanceAt(context.Background(), account) 96 | fmt.Println(pendingBalance) // 25729324269165216042 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /zh/accounts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go加载以太坊账户的教程。 3 | --- 4 | 5 | # 账户 6 | 7 | 以太坊上的账户要么是钱包地址要么是智能合约地址。它们看起来像是`0x71c7656ec7ab88b098defb751b7401b5f6d8976f`,它们用于将ETH发送到另一个用户,并且还用于在需要和区块链交互时指一个智能合约。它们是唯一的,且是从私钥导出的。我们将在后面的章节更深入地介绍公私钥对。 8 | 9 | 要使用go-ethereum的账户地址,您必须先将它们转化为go-ethereum中的`common.Address`类型。 10 | 11 | ```go 12 | address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 13 | 14 | fmt.Println(address.Hex()) // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F 15 | ``` 16 | 17 | 您可以在几乎任何地方使用这种类型,您可以将以太坊地址传递给go-ethereum的方法。既然您已经了解账户和地址的基础知识,那么让我们在下一节中学习如何检索ETH账户余额。 18 | 19 | --- 20 | 21 | ### 完整代码 22 | 23 | [address.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/address.go) 24 | 25 | ```go 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | 31 | "github.com/ethereum/go-ethereum/common" 32 | ) 33 | 34 | func main() { 35 | address := common.HexToAddress("0x71c7656ec7ab88b098defb751b7401b5f6d8976f") 36 | 37 | fmt.Println(address.Hex()) // 0x71C7656EC7ab88b098defB751B7401B5f6d8976F 38 | fmt.Println(address.Hash().Hex()) // 0x00000000000000000000000071c7656ec7ab88b098defb751b7401b5f6d8976f 39 | fmt.Println(address.Bytes()) // [113 199 101 110 199 171 136 176 152 222 251 117 27 116 1 181 246 216 151 111] 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /zh/address-check/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go检查地址是智能合约或账户的教程。 3 | --- 4 | 5 | # 地址检查 6 | 7 | 本节将介绍如何确认一个地址并确定其是否为智能合约地址。 8 | 9 | ## 检查地址是否有效 10 | 11 | 我们可以使用简单的正则表达式来检查以太坊地址是否有效: 12 | 13 | ```go 14 | re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") 15 | 16 | fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true 17 | fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false 18 | ``` 19 | 20 | ## 检查地址是否为账户或智能合约 21 | 22 | 我们可以确定,若在该地址存储了字节码,该地址是智能合约。这是一个示例,在例子中,我们获取一个代币智能合约的字节码并检查其长度以验证它是一个智能合约: 23 | 24 | ```go 25 | // 0x Protocol Token (ZRX) smart contract address 26 | address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498") 27 | bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | isContract := len(bytecode) > 0 33 | 34 | fmt.Printf("is contract: %v\n", isContract) // is contract: true 35 | ``` 36 | 37 | 当地址上没有字节码时,我们知道它不是一个智能合约,它是一个标准的以太坊账户。 38 | 39 | ```go 40 | // a random user account address 41 | address := common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4") 42 | bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | isContract = len(bytecode) > 0 48 | 49 | fmt.Printf("is contract: %v\n", isContract) // is contract: false 50 | ``` 51 | 52 | --- 53 | 54 | ### 完整代码 55 | 56 | [address_check.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/address_check.go) 57 | 58 | ```go 59 | package main 60 | 61 | import ( 62 | "context" 63 | "fmt" 64 | "log" 65 | "regexp" 66 | 67 | "github.com/ethereum/go-ethereum/common" 68 | "github.com/ethereum/go-ethereum/ethclient" 69 | ) 70 | 71 | func main() { 72 | re := regexp.MustCompile("^0x[0-9a-fA-F]{40}$") 73 | 74 | fmt.Printf("is valid: %v\n", re.MatchString("0x323b5d4c32345ced77393b3530b1eed0f346429d")) // is valid: true 75 | fmt.Printf("is valid: %v\n", re.MatchString("0xZYXb5d4c32345ced77393b3530b1eed0f346429d")) // is valid: false 76 | 77 | client, err := ethclient.Dial("https://cloudflare-eth.com") 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | 82 | // 0x Protocol Token (ZRX) smart contract address 83 | address := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498") 84 | bytecode, err := client.CodeAt(context.Background(), address, nil) // nil is latest block 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | 89 | isContract := len(bytecode) > 0 90 | 91 | fmt.Printf("is contract: %v\n", isContract) // is contract: true 92 | 93 | // a random user account address 94 | address = common.HexToAddress("0x8e215d06ea7ec1fdb4fc5fd21768f4b34ee92ef4") 95 | bytecode, err = client.CodeAt(context.Background(), address, nil) // nil is latest block 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | 100 | isContract = len(bytecode) > 0 101 | 102 | fmt.Printf("is contract: %v\n", isContract) // is contract: false 103 | } 104 | ``` 105 | -------------------------------------------------------------------------------- /zh/block-query/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go查询以太坊区块。 3 | --- 4 | 5 | # 查询区块 6 | 7 | 正如我们所见,您可以有两种方式查询区块信息。 8 | 9 | #### 区块头 10 | 11 | 您可以调用客户端的`HeaderByNumber`来返回有关一个区块的头信息。若您传入`nil`,它将返回最新的区块头。 12 | 13 | ```go 14 | header, err := client.HeaderByNumber(context.Background(), nil) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | fmt.Println(header.Number.String()) // 5671744 20 | ``` 21 | 22 | #### 完整区块 23 | 24 | 调用客户端的`BlockByNumber`方法来获得完整区块。您可以读取该区块的所有内容和元数据,例如,区块号,区块时间戳,区块摘要,区块难度以及交易列表等等。 25 | 26 | ```go 27 | blockNumber := big.NewInt(5671744) 28 | block, err := client.BlockByNumber(context.Background(), blockNumber) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | fmt.Println(block.Number().Uint64()) // 5671744 34 | fmt.Println(block.Time().Uint64()) // 1527211625 35 | fmt.Println(block.Difficulty().Uint64()) // 3217000136609065 36 | fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9 37 | fmt.Println(len(block.Transactions())) // 144 38 | ``` 39 | 40 | 调用`Transaction`只返回一个区块的交易数目。 41 | 42 | ```go 43 | count, err := client.TransactionCount(context.Background(), block.Hash()) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | fmt.Println(count) // 144 49 | ``` 50 | 51 | 在下个章节,我们将学习查询区块中的交易。 52 | 53 | ### 完整代码 54 | 55 | [blocks.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/blocks.go) 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "context" 62 | "fmt" 63 | "log" 64 | "math/big" 65 | 66 | "github.com/ethereum/go-ethereum/ethclient" 67 | ) 68 | 69 | func main() { 70 | client, err := ethclient.Dial("https://cloudflare-eth.com") 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | header, err := client.HeaderByNumber(context.Background(), nil) 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | 80 | fmt.Println(header.Number.String()) // 5671744 81 | 82 | blockNumber := big.NewInt(5671744) 83 | block, err := client.BlockByNumber(context.Background(), blockNumber) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | 88 | fmt.Println(block.Number().Uint64()) // 5671744 89 | fmt.Println(block.Time().Uint64()) // 1527211625 90 | fmt.Println(block.Difficulty().Uint64()) // 3217000136609065 91 | fmt.Println(block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9 92 | fmt.Println(len(block.Transactions())) // 144 93 | 94 | count, err := client.TransactionCount(context.Background(), block.Hash()) 95 | if err != nil { 96 | log.Fatal(err) 97 | } 98 | 99 | fmt.Println(count) // 144 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /zh/block-subscribe/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go订阅以太坊中最新区块的教程。 3 | --- 4 | 5 | # 订阅新区块 6 | 7 | 在本节中,我们将讨论如何设置订阅以便在新区块被开采时获取事件。首先,我们需要一个支持websocket RPC的以太坊服务提供者。在示例中,我们将使用infura 的websocket端点。 8 | 9 | ```go 10 | client, err := ethclient.Dial("wss://ropsten.infura.io/ws") 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | ``` 15 | 16 | 接下来,我们将创建一个新的通道,用于接收最新的区块头。 17 | 18 | ```go 19 | headers := make(chan *types.Header) 20 | ``` 21 | 22 | 现在我们调用客户端的`SubscribeNewHead`方法,它接收我们刚创建的区块头通道,该方法将返回一个订阅对象。 23 | 24 | ```go 25 | sub, err := client.SubscribeNewHead(context.Background(), headers) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | ``` 30 | 31 | 订阅将推送新的区块头事件到我们的通道,因此我们可以使用一个select语句来监听新消息。订阅对象还包括一个error通道,该通道将在订阅失败时发送消息。 32 | 33 | ```go 34 | for { 35 | select { 36 | case err := <-sub.Err(): 37 | log.Fatal(err) 38 | case header := <-headers: 39 | fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 40 | } 41 | } 42 | ``` 43 | 44 | 要获得该区块的完整内容,我们可以将区块头的摘要传递给客户端的`BlockByHash`函数。 45 | 46 | ```go 47 | block, err := client.BlockByHash(context.Background(), header.Hash()) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 53 | fmt.Println(block.Number().Uint64()) // 3477413 54 | fmt.Println(block.Time().Uint64()) // 1529525947 55 | fmt.Println(block.Nonce()) // 130524141876765836 56 | fmt.Println(len(block.Transactions())) // 7 57 | ``` 58 | 59 | 正如您所见,您可以读取整个区块的元数据字段,交易列表等等。 60 | 61 | ### 完整代码 62 | 63 | [block_subscribe.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/block_subscribe.go) 64 | 65 | ```go 66 | package main 67 | 68 | import ( 69 | "context" 70 | "fmt" 71 | "log" 72 | 73 | "github.com/ethereum/go-ethereum/core/types" 74 | "github.com/ethereum/go-ethereum/ethclient" 75 | ) 76 | 77 | func main() { 78 | client, err := ethclient.Dial("wss://ropsten.infura.io/ws") 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | headers := make(chan *types.Header) 84 | sub, err := client.SubscribeNewHead(context.Background(), headers) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | 89 | for { 90 | select { 91 | case err := <-sub.Err(): 92 | log.Fatal(err) 93 | case header := <-headers: 94 | fmt.Println(header.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 95 | 96 | block, err := client.BlockByHash(context.Background(), header.Hash()) 97 | if err != nil { 98 | log.Fatal(err) 99 | } 100 | 101 | fmt.Println(block.Hash().Hex()) // 0xbc10defa8dda384c96a17640d84de5578804945d347072e091b4e5f390ddea7f 102 | fmt.Println(block.Number().Uint64()) // 3477413 103 | fmt.Println(block.Time().Uint64()) // 1529525947 104 | fmt.Println(block.Nonce()) // 130524141876765836 105 | fmt.Println(len(block.Transactions())) // 7 106 | } 107 | } 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /zh/client-setup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go初始化客户端以连接以太坊的教程 3 | --- 4 | 5 | # 初始化客户端 6 | 7 | 用Go初始化以太坊客户端是和区块链交互所需的基本步骤。首先,导入go-etherem的`ethclient`包并通过调用接收区块链服务提供者URL的`Dial`来初始化它。 8 | 9 | 若您没有现有以太坊客户端,您可以连接到infura网关。Infura管理着一批安全,可靠,可扩展的以太坊[geth和parity]节点,并且在接入以太坊网络时降低了新人的入门门槛。 10 | 11 | ```go 12 | client, err := ethclient.Dial("https://cloudflare-eth.com") 13 | ``` 14 | 15 | 若您运行了本地geth实例,您还可以将路径传递给IPC端点文件。 16 | 17 | ```go 18 | client, err := ethclient.Dial("/home/user/.ethereum/geth.ipc") 19 | ``` 20 | 21 | 对每个Go以太坊项目,使用ethclient是您开始的必要事项,您将在本书中非常多的看到这一步骤。 22 | 23 | ## 使用Ganache 24 | 25 | [Ganache](https://github.com/trufflesuite/ganache-cli)(正式名称为testrpc)是一个用Node.js编写的以太坊实现,用于在本地开发去中心化应用程序时进行测试。现在我们将带着您完成安装并连接到它。 26 | 27 | 首先通过[NPM](https://www.npmjs.com/package/ganache-cli)安装ganache。 28 | 29 | ```bash 30 | npm install -g ganache-cli 31 | ``` 32 | 33 | 然后运行ganache cli客户端。 34 | 35 | ```bash 36 | ganache-cli 37 | ``` 38 | 39 | 现在连到`http://localhost:8584`上的ganache RPC主机。 40 | 41 | ```go 42 | client, err := ethclient.Dial("http://localhost:8545") 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | ``` 47 | 48 | 在启动ganache时,您还可以使用相同的助记词来生成相同序列的公开地址。 49 | 50 | ```bash 51 | ganache-cli -m "much repair shock carbon improve miss forget sock include bullet interest solution" 52 | ``` 53 | 54 | 我强烈推荐您通过阅读其[文档](http://truffleframework.com/ganache/)熟悉ganache。 55 | 56 | --- 57 | 58 | ### 完整代码 59 | 60 | [client.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/client.go) 61 | 62 | ```go 63 | package main 64 | 65 | import ( 66 | "fmt" 67 | "log" 68 | 69 | "github.com/ethereum/go-ethereum/ethclient" 70 | ) 71 | 72 | func main() { 73 | client, err := ethclient.Dial("https://cloudflare-eth.com") 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | 78 | fmt.Println("we have a connection") 79 | _ = client // we'll use this in the upcoming sections 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /zh/client/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go初始化以太坊客户端的教程。 3 | --- 4 | 5 | # 客户端 6 | 7 | 客户端是以太坊网络的入口。客户端需要广播交易和读取区块链数据。在[下一节](../client-setup)中将学习如何在Go应用程序中初始化客户端。 8 | -------------------------------------------------------------------------------- /zh/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/zh/cover.jpg -------------------------------------------------------------------------------- /zh/cover_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/miguelmota/ethereum-development-with-go-book/3b24cad5d54aff1496fe4c3590c27f0fad648ddf/zh/cover_small.jpg -------------------------------------------------------------------------------- /zh/event-subscribe/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go订阅智能合约事件日志的教程。 3 | --- 4 | 5 | # 订阅事件日志 6 | 7 | 为了订阅事件日志,我们需要做的第一件事就是拨打启用websocket的以太坊客户端。 幸运的是,Infura支持websockets。 8 | 9 | ```go 10 | client, err := ethclient.Dial("wss://rinkeby.infura.io/ws") 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | ``` 15 | 16 | 下一步是创建筛选查询。 在这个例子中,我们将阅读来自我们在之前课程中创建的示例合约中的所有事件。 17 | 18 | ```go 19 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 20 | query := ethereum.FilterQuery{ 21 | Addresses: []common.Address{contractAddress}, 22 | } 23 | ``` 24 | 25 | 我们接收事件的方式是通过Go channel。 让我们从go-ethereum`core/types`包创建一个类型为`Log`的channel。 26 | 27 | ```go 28 | logs := make(chan types.Log) 29 | ``` 30 | 31 | 现在我们所要做的就是通过从客户端调用`SubscribeFilterLogs`来订阅,它接收查询选项和输出通道。 这将返回包含unsubscribe和error方法的订阅结构。 32 | 33 | 34 | ```go 35 | sub, err := client.SubscribeFilterLogs(context.Background(), query, logs) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | ``` 40 | 41 | 最后,我们要做的就是使用select语句设置一个连续循环来读入新的日志事件或订阅错误。 42 | 43 | ```go 44 | for { 45 | select { 46 | case err := <-sub.Err(): 47 | log.Fatal(err) 48 | case vLog := <-logs: 49 | fmt.Println(vLog) // pointer to event log 50 | } 51 | } 52 | ``` 53 | 54 | 我们会在[下个章节](../event-read)介绍如何解析日志。 55 | 56 | --- 57 | 58 | ### 完整代码 59 | 60 | Commands 61 | 62 | ```bash 63 | solc --abi Store.sol 64 | solc --bin Store.sol 65 | abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go 66 | ``` 67 | 68 | [Store.sol](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contracts/Store.sol) 69 | 70 | ```solidity 71 | pragma solidity ^0.4.24; 72 | 73 | contract Store { 74 | event ItemSet(bytes32 key, bytes32 value); 75 | 76 | string public version; 77 | mapping (bytes32 => bytes32) public items; 78 | 79 | constructor(string _version) public { 80 | version = _version; 81 | } 82 | 83 | function setItem(bytes32 key, bytes32 value) external { 84 | items[key] = value; 85 | emit ItemSet(key, value); 86 | } 87 | } 88 | ``` 89 | 90 | [event_subscribe.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/event_subscribe.go) 91 | 92 | ```go 93 | package main 94 | 95 | import ( 96 | "context" 97 | "fmt" 98 | "log" 99 | 100 | "github.com/ethereum/go-ethereum" 101 | "github.com/ethereum/go-ethereum/common" 102 | "github.com/ethereum/go-ethereum/core/types" 103 | "github.com/ethereum/go-ethereum/ethclient" 104 | ) 105 | 106 | func main() { 107 | client, err := ethclient.Dial("wss://rinkeby.infura.io/ws") 108 | if err != nil { 109 | log.Fatal(err) 110 | } 111 | 112 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 113 | query := ethereum.FilterQuery{ 114 | Addresses: []common.Address{contractAddress}, 115 | } 116 | 117 | logs := make(chan types.Log) 118 | sub, err := client.SubscribeFilterLogs(context.Background(), query, logs) 119 | if err != nil { 120 | log.Fatal(err) 121 | } 122 | 123 | for { 124 | select { 125 | case err := <-sub.Err(): 126 | log.Fatal(err) 127 | case vLog := <-logs: 128 | fmt.Println(vLog) // pointer to event log 129 | } 130 | } 131 | } 132 | ``` 133 | 134 | ```bash 135 | $ solc --version 136 | 0.4.24+commit.e67f0147.Emscripten.clang 137 | ``` 138 | -------------------------------------------------------------------------------- /zh/events/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go与以太坊事件日志交互的教程。 3 | --- 4 | 5 | # 事件 6 | 7 | 智能合约具有在执行期间“发出”事件的能力。 事件在以太坊中也称为“日志”。 事件的输出存储在日志部分下的事务处理中。 事件已经在以太坊智能合约中被广泛使用,以便在发生相对重要的动作时记录,特别是在代币合约(即ERC-20)中,以指示代币转账已经发生。 这些部分将引导您完成从区块链中读取事件以及订阅事件的过程,以便交易事务被矿工打包入块的时候及时收到通知。 8 | 9 | -------------------------------------------------------------------------------- /zh/faucets/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 使用以太坊测试网发币水龙头的教程。 3 | --- 4 | 5 | # 水龙头 6 | 7 | 水龙头是免费在[测试网]获得ETH的方式。 8 | 9 | 这里罗列了各个测试网的龙头。 10 | 11 | - Ropsten testnet - [https://faucet.ropsten.be](https://faucet.ropsten.be) 12 | - Rinkeby testnet - [https://faucet.rinkeby.io](https://faucet.rinkeby.io) 13 | - Kovan testnet - [https://gitter.im/kovan-testnet/faucet](https://gitter.im/kovan-testnet/faucet) 14 | - Sokol testnet - [https://faucet-sokol.herokuapp.com](https://faucet-sokol.herokuapp.com) 15 | -------------------------------------------------------------------------------- /zh/hd-wallet/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go创建或使用分层确定性(HD)钱包。 3 | --- 4 | 5 | # 分层确定性(HD)Wallet 6 | 7 | 关于创建或使用一个分层确定性(HD)钱包,请参考Go包: [https://github.com/miguelmota/go-ethereum-hdwallet](https://github.com/miguelmota/go-ethereum-hdwallet) -------------------------------------------------------------------------------- /zh/keystore/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go创建和导入keystore。 3 | --- 4 | 5 | # Keystores 6 | 7 | keystore是一个包含经过加密了的钱包私钥。go-ethereum中的keystore,每个文件只能包含一个钱包密钥对。要生成keystore,首先您必须调用`NewKeyStore`,给它提供保存keystore的目录路径。然后,您可调用`NewAccount`方法创建新的钱包,并给它传入一个用于加密的口令。您每次调用`NewAccount`,它将在磁盘上生成新的keystore文件。 8 | 9 | 这是一个完整的生成新的keystore账户的示例。 10 | 11 | ```go 12 | ks := keystore.NewKeyStore("./wallets", keystore.StandardScryptN, keystore.StandardScryptP) 13 | password := "secret" 14 | account, err := ks.NewAccount(password) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 20 | ``` 21 | 22 | 现在要导入您的keystore,您基本上像往常一样再次调用`NewKeyStore`,然后调用`Import`方法,该方法接收keystore的JSON数据作为字节。第二个参数是用于加密私钥的口令。第三个参数是指定一个新的加密口令,但我们在示例中使用一样的口令。导入账户将允许您按期访问该账户,但它将生成新keystore文件!有两个相同的事物是没有意义的,所以我们将删除旧的。 23 | 24 | 这是一个导入keystore和访问账户的示例。 25 | 26 | ```go 27 | file := "./wallets/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3" 28 | ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) 29 | jsonBytes, err := ioutil.ReadFile(file) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | password := "secret" 35 | account, err := ks.Import(jsonBytes, password, password) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 41 | 42 | if err := os.Remove(file); err != nil { 43 | log.Fatal(err) 44 | } 45 | ``` 46 | 47 | ---- 48 | 49 | ### 完整代码 50 | 51 | [keystore.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/keystore.go) 52 | 53 | ```go 54 | package main 55 | 56 | import ( 57 | "fmt" 58 | "io/ioutil" 59 | "log" 60 | "os" 61 | 62 | "github.com/ethereum/go-ethereum/accounts/keystore" 63 | ) 64 | 65 | func createKs() { 66 | ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) 67 | password := "secret" 68 | account, err := ks.NewAccount(password) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | 73 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 74 | } 75 | 76 | func importKs() { 77 | file := "./tmp/UTC--2018-07-04T09-58-30.122808598Z--20f8d42fb0f667f2e53930fed426f225752453b3" 78 | ks := keystore.NewKeyStore("./tmp", keystore.StandardScryptN, keystore.StandardScryptP) 79 | jsonBytes, err := ioutil.ReadFile(file) 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | 84 | password := "secret" 85 | account, err := ks.Import(jsonBytes, password, password) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | 90 | fmt.Println(account.Address.Hex()) // 0x20F8D42FB0F667F2E53930fed426f225752453b3 91 | 92 | if err := os.Remove(file); err != nil { 93 | log.Fatal(err) 94 | } 95 | } 96 | 97 | func main() { 98 | createKs() 99 | //importKs() 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /zh/resources/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: Ethereum, Solidity 还有 Go的学习资源列表。 3 | --- 4 | 5 | # 资源 6 | 7 | Ethereum, Solidity 还有 Go的学习资源列表。 8 | 9 | ## 参考规范 10 | 11 | - [智能合约参考规范](https://github.com/ConsenSys/smart-contract-best-practices) 12 | - [安全考虑](http://solidity.readthedocs.io/en/develop/security-considerations.html) 13 | - [Solidity的一些坑](https://github.com/miguelmota/solidity-idiosyncrasies) 14 | 15 | ## 寻求帮助和问题解答 16 | 17 | - StackExchange 18 | - [以太坊](https://ethereum.stackexchange.com/) 19 | - Reddit 20 | - [ethdev](https://www.reddit.com/r/ethtrader/) 21 | - [golang](https://www.reddit.com/r/golang/) 22 | - Gitter 23 | - [List of Gitter channels](https://github.com/ethereum/wiki/wiki/Gitter-Channels) 24 | 25 | ## 社区 26 | 27 | - Reddit 28 | - [ethereum](https://www.reddit.com/r/ethereum/) 29 | - [ethtrader](https://www.reddit.com/r/ethtrader/) 30 | - Twitter 31 | - [ethereum](https://twitter.com/ethereum) 32 | 33 | ## 库 34 | 35 | - [go-ethereum](https://github.com/ethereum/go-ethereum) 36 | - [go-solidity-sha3](https://github.com/miguelmota/go-solidity-sha3) 37 | - [go-ethereum-hdwallet](https://github.com/miguelmota/go-ethereum-hdwallet) 38 | - [go-ethutil](https://github.com/miguelmota/go-ethutil) 39 | 40 | ## 开发者工具 41 | 42 | - [Truffle](https://truffleframework.com/) 43 | - [Infura](https://infura.io/) 44 | - [Remix IDE](https://remix.ethereum.org/) 45 | - [Keccak-256 Online](https://emn178.github.io/online-tools/keccak_256.html) 46 | -------------------------------------------------------------------------------- /zh/signature-generate/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go生成签名的教程。 3 | --- 4 | 5 | # 生成一个签名 6 | 7 | 用于生成签名的组件是:签名者私钥,以及将要签名的数据的哈希。 只要输出为32字节,就可以使用任何哈希算法。 我们将使用Keccak-256作为哈希算法,这是以太坊常常使用的算法。 8 | 9 | 首先,我们将加载私钥。 10 | 11 | ```go 12 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | ``` 17 | 18 | 接下来我们将获取我们希望签名的数据的Keccak-256,在这个例子里,它将是*hello*。 go-ethereum`crypto`包提供了一个方便的`Keccak256Hash`方法来实现这一目的。 19 | 20 | ```go 21 | data := []byte("hello") 22 | hash := crypto.Keccak256Hash(data) 23 | fmt.Println(hash.Hex()) // 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8 24 | ``` 25 | 26 | 最后,我们使用私钥签名哈希,得到签名。 27 | 28 | ```go 29 | signature, err := crypto.Sign(hash.Bytes(), privateKey) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | fmt.Println(hexutil.Encode(signature)) // 0x789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301 35 | ``` 36 | 37 | 现在我们已经成功生成了签名,在下个章节中,我们将学习如何验证签名确实是由该私钥的持有者签名的。 38 | 39 | --- 40 | 41 | ### 完整代码 42 | 43 | [signature_generate.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/signature_generate.go) 44 | 45 | ```go 46 | package main 47 | 48 | import ( 49 | "fmt" 50 | "log" 51 | 52 | "github.com/ethereum/go-ethereum/common/hexutil" 53 | "github.com/ethereum/go-ethereum/crypto" 54 | ) 55 | 56 | func main() { 57 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | 62 | data := []byte("hello") 63 | hash := crypto.Keccak256Hash(data) 64 | fmt.Println(hash.Hex()) // 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8 65 | 66 | signature, err := crypto.Sign(hash.Bytes(), privateKey) 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | 71 | fmt.Println(hexutil.Encode(signature)) // 0x789a80053e4927d0a898db8e065e948f5cf086e32f9ccaa54c1908e22ac430c62621578113ddbb62d509bf6049b8fb544ab06d36f916685a2eb8e57ffadde02301 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /zh/signatures/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go签名的教程。 3 | --- 4 | 5 | # 签名 6 | 7 | 数字签名允许不可否认性,因为这意味着签署消息的人必须拥有私钥,来证明消息是真实的。 任何人都可以验证消息的真实性,只要它们具有原始数据的散列和签名者的公钥即可。 签名是区块链的基本组成部分,我们将在接下来的几节课中学习如何生成和验证签名。 8 | -------------------------------------------------------------------------------- /zh/smart-contract-bytecode/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go来读取已经部署的智能合约的字节码的教程。 3 | --- 4 | 5 | # 读取智能合约的字节码 6 | 7 | 有时您需要读取已部署的智能合约的字节码。 由于所有智能合约字节码都存在于区块链中,因此我们可以轻松获取它。 8 | 9 | 首先设置客户端和要读取的字节码的智能合约地址。 10 | 11 | ```go 12 | client, err := ethclient.Dial("https://rinkeby.infura.io") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 18 | ``` 19 | 20 | 现在你需要调用客户端的`codeAt`方法。 `codeAt`方法接受智能合约地址和可选的块编号,并以字节格式返回字节码。 21 | 22 | ```go 23 | bytecode, err := client.CodeAt(context.Background(), contractAddress, nil) // nil is latest block 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | fmt.Println(hex.EncodeToString(bytecode)) // 60806...10029 29 | ``` 30 | 31 | 32 | 你也可以在etherscan上查询16进制格式的字节码 [https://rinkeby.etherscan.io/address/0x147b8eb97fd247d06c4006d269c90c1908fb5d54#code](https://rinkeby.etherscan.io/address/0x147b8eb97fd247d06c4006d269c90c1908fb5d54#code) 33 | 34 | --- 35 | 36 | ### 完整代码 37 | 38 | [contract_bytecode.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contract_bytecode.go) 39 | 40 | ```go 41 | package main 42 | 43 | import ( 44 | "context" 45 | "encoding/hex" 46 | "fmt" 47 | "log" 48 | 49 | "github.com/ethereum/go-ethereum/common" 50 | "github.com/ethereum/go-ethereum/ethclient" 51 | ) 52 | 53 | func main() { 54 | client, err := ethclient.Dial("https://rinkeby.infura.io") 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | 59 | contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 60 | bytecode, err := client.CodeAt(context.Background(), contractAddress, nil) // nil is latest block 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | 65 | fmt.Println(hex.EncodeToString(bytecode)) // 60806...10029 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /zh/smart-contract-compile/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go编译智能合约并读取ABI的教程。 3 | --- 4 | 5 | # 智能合约的编译与ABI 6 | 7 | 与智能合约交互,我们要先生成相应智能合约的应用二进制接口ABI(application binary interface),并把ABI编译成我们可以在Go应用中调用的格式。 8 | 9 | 第一步是安装 [Solidity编译器](https://solidity.readthedocs.io/en/latest/installing-solidity.html) (`solc`). 10 | 11 | Solc 在Ubuntu上有snapcraft包。 12 | 13 | ```bash 14 | sudo snap install solc --edge 15 | ``` 16 | 17 | Solc在macOS上有Homebrew的包。 18 | 19 | ```bash 20 | brew update 21 | brew tap ethereum/ethereum 22 | brew install solidity 23 | ``` 24 | 25 | 其他的平台或者从源码编译的教程请查阅官方solidity文档[install guide](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source). 26 | 27 | 我们还得安装一个叫`abigen`的工具,来从solidity智能合约生成ABI。 28 | 29 | 假设您已经在计算机上设置了Go,只需运行以下命令即可安装`abigen`工具。 30 | 31 | ```bash 32 | go get -u github.com/ethereum/go-ethereum 33 | cd $GOPATH/src/github.com/ethereum/go-ethereum/ 34 | make 35 | make devtools 36 | ``` 37 | 38 | 我们将创建一个简单的智能合约来测试。 学习更复杂的智能合约,或者智能合约的开发的内容则超出了本书的范围。 我强烈建议您查看[truffle framework](http://truffleframework.com/) 来学习开发和测试智能合约。 39 | 40 | 这里只是一个简单的合约,就是一个键/值存储,只有一个外部方法来设置任何人的键/值对。 我们还在设置值后添加了要发出的事件。 41 | 42 | ```solidity 43 | pragma solidity ^0.4.24; 44 | 45 | contract Store { 46 | event ItemSet(bytes32 key, bytes32 value); 47 | 48 | string public version; 49 | mapping (bytes32 => bytes32) public items; 50 | 51 | constructor(string _version) public { 52 | version = _version; 53 | } 54 | 55 | function setItem(bytes32 key, bytes32 value) external { 56 | items[key] = value; 57 | emit ItemSet(key, value); 58 | } 59 | } 60 | ``` 61 | 62 | 虽然这个智能合约很简单,但它将适用于这个例子。 63 | 64 | 现在我们可以从一个solidity文件生成ABI。 65 | 66 | ```bash 67 | solc --abi Store.sol 68 | ``` 69 | 70 | 它会将其写入名为“Store_sol_Store.abi”的文件中 71 | 72 | 现在让我们用`abigen`将ABI转换为我们可以导入的Go文件。 这个新文件将包含我们可以用来与Go应用程序中的智能合约进行交互的所有可用方法。 73 | 74 | ```bash 75 | abigen --abi=Store_sol_Store.abi --pkg=store --out=Store.go 76 | ``` 77 | 78 | 为了从Go部署智能合约,我们还需要将solidity智能合约编译为EVM字节码。 EVM字节码将在事务的数据字段中发送。 在Go文件上生成部署方法需要bin文件。 79 | 80 | 81 | ```bash 82 | solc --bin Store.sol 83 | ``` 84 | 85 | 现在我们编译Go合约文件,其中包括deploy方法,因为我们包含了bin文件。 86 | 87 | ```bash 88 | abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go 89 | ``` 90 | 91 | 在接下来的课程中,我们将学习如何部署智能合约,然后与之交互。 92 | 93 | 94 | --- 95 | 96 | ### 完整代码 97 | 98 | Commands 99 | 100 | ```bash 101 | go get -u github.com/ethereum/go-ethereum 102 | cd $GOPATH/src/github.com/ethereum/go-ethereum/ 103 | make 104 | make devtools 105 | 106 | solc --abi Store.sol 107 | solc --bin Store.sol 108 | abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go 109 | ``` 110 | 111 | [Store.sol](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contracts/Store.sol) 112 | 113 | ```solidity 114 | pragma solidity ^0.4.24; 115 | 116 | contract Store { 117 | event ItemSet(bytes32 key, bytes32 value); 118 | 119 | string public version; 120 | mapping (bytes32 => bytes32) public items; 121 | 122 | constructor(string _version) public { 123 | version = _version; 124 | } 125 | 126 | function setItem(bytes32 key, bytes32 value) external { 127 | items[key] = value; 128 | emit ItemSet(key, value); 129 | } 130 | } 131 | ``` 132 | 133 | solc version used for these examples 134 | 135 | ```bash 136 | $ solc --version 137 | 0.4.24+commit.e67f0147.Emscripten.clang 138 | ``` 139 | -------------------------------------------------------------------------------- /zh/smart-contract-load/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go加载和初始化智能合约的教程。 3 | --- 4 | 5 | # 加载智能合约 6 | 7 | 这写章节需要了解如何将智能合约的ABI编译成Go的合约文件。如果你还没看, 前先读[上一个章节](../smart-contract-compile) 。 8 | 9 | 一旦使用`abigen`工具将智能合约的ABI编译为Go包,下一步就是调用“New”方法,其格式为“New”,所以在我们的例子中如果你 回想一下它将是*NewStore*。 此初始化方法接收智能合约的地址,并返回可以开始与之交互的合约实例。 10 | 11 | ```go 12 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 13 | instance, err := store.NewStore(address, client) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | _ = instance // we'll be using this in the 下个章节 19 | ``` 20 | 21 | --- 22 | 23 | ### 完整代码 24 | 25 | Commands 26 | 27 | ```bash 28 | solc --abi Store.sol 29 | solc --bin Store.sol 30 | abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go 31 | ``` 32 | 33 | [Store.sol](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contracts/Store.sol) 34 | 35 | ```solidity 36 | pragma solidity ^0.4.24; 37 | 38 | contract Store { 39 | event ItemSet(bytes32 key, bytes32 value); 40 | 41 | string public version; 42 | mapping (bytes32 => bytes32) public items; 43 | 44 | constructor(string _version) public { 45 | version = _version; 46 | } 47 | 48 | function setItem(bytes32 key, bytes32 value) external { 49 | items[key] = value; 50 | emit ItemSet(key, value); 51 | } 52 | } 53 | ``` 54 | 55 | [contract_load.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contract_load.go) 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "fmt" 62 | "log" 63 | 64 | "github.com/ethereum/go-ethereum/common" 65 | "github.com/ethereum/go-ethereum/ethclient" 66 | 67 | store "./contracts" // for demo 68 | ) 69 | 70 | func main() { 71 | client, err := ethclient.Dial("https://rinkeby.infura.io") 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 77 | instance, err := store.NewStore(address, client) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | 82 | fmt.Println("contract is loaded") 83 | _ = instance 84 | } 85 | ``` 86 | 87 | solc version used for these examples 88 | 89 | ```bash 90 | $ solc --version 91 | 0.4.24+commit.e67f0147.Emscripten.clang 92 | ``` 93 | -------------------------------------------------------------------------------- /zh/smart-contract-read/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: Tutorial on how to query a smart contract with Go. 3 | --- 4 | 5 | # Querying a Smart Contract 6 | 7 | 这写章节需要了解如何将智能合约的ABI编译成Go的合约文件。如果你还没看, 前先读[上一个章节](../smart-contract-compile) 。 8 | 9 | 在上个章节我们学习了如何在Go应用程序中初始化合约实例。 现在我们将使用新合约实例提供的方法来阅读智能合约。 如果你还记得我们在部署过程中设置的合约中有一个名为`version`的全局变量。 因为它是公开的,这意味着它们将成为我们自动创建的getter函数。 常量和view函数也接受`bind.CallOpts`作为第一个参数。了解可用的具体选项要看相应类的[文档](https://godoc.org/github.com/ethereum/go-ethereum/accounts/abi/bind#CallOpts) 一般情况下我们可以用 `nil`。 10 | 11 | ```go 12 | version, err := instance.Version(nil) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | fmt.Println(version) // "1.0" 18 | ``` 19 | 20 | --- 21 | 22 | ### 完整代码 23 | 24 | Commands 25 | 26 | ```bash 27 | solc --abi Store.sol 28 | solc --bin Store.sol 29 | abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go 30 | ``` 31 | 32 | [Store.sol](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contracts/Store.sol) 33 | 34 | ```solidity 35 | pragma solidity ^0.4.24; 36 | 37 | contract Store { 38 | event ItemSet(bytes32 key, bytes32 value); 39 | 40 | string public version; 41 | mapping (bytes32 => bytes32) public items; 42 | 43 | constructor(string _version) public { 44 | version = _version; 45 | } 46 | 47 | function setItem(bytes32 key, bytes32 value) external { 48 | items[key] = value; 49 | emit ItemSet(key, value); 50 | } 51 | } 52 | ``` 53 | 54 | [contract_read.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/contract_read.go) 55 | 56 | ```go 57 | package main 58 | 59 | import ( 60 | "fmt" 61 | "log" 62 | 63 | "github.com/ethereum/go-ethereum/common" 64 | "github.com/ethereum/go-ethereum/ethclient" 65 | 66 | store "./contracts" // for demo 67 | ) 68 | 69 | func main() { 70 | client, err := ethclient.Dial("https://rinkeby.infura.io") 71 | if err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54") 76 | instance, err := store.NewStore(address, client) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | version, err := instance.Version(nil) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | 86 | fmt.Println(version) // "1.0" 87 | } 88 | ``` 89 | 90 | solc version used for these examples 91 | 92 | ```bash 93 | $ solc --version 94 | 0.4.24+commit.e67f0147.Emscripten.clang 95 | ``` 96 | -------------------------------------------------------------------------------- /zh/smart-contracts/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go使用以太坊智能合约的教程。 3 | --- 4 | 5 | # 智能合约 6 | 7 | 在下个章节中我们会介绍如何用Go来编译,部署,写入和读取智能合约。 8 | -------------------------------------------------------------------------------- /zh/styles/website.css: -------------------------------------------------------------------------------- 1 | .gitbook-link { 2 | display: none !important; 3 | } 4 | 5 | body::-webkit-scrollbar, 6 | .book-summary::-webkit-scrollbar { 7 | width: 8px; 8 | height: 8px; 9 | } 10 | 11 | body::-webkit-scrollbar-track, 12 | .book-summary::-webkit-scrollbar-track { 13 | background: transparent; 14 | } 15 | 16 | body::-webkit-scrollbar-thumb, 17 | .book-summary::-webkit-scrollbar-thumb { 18 | background-color: rgba(0,0,0,0.05); 19 | border: 1px solid transparent; 20 | box-shadow: inset 1px 1px 0 rgba(0,0,0,0.05), inset 0 -1px 0 rgba(0,0,0,0.1); 21 | } 22 | 23 | body::-webkit-scrollbar-thumb:hover, 24 | .book-summary::-webkit-scrollbar-thumb:hover { 25 | box-shadow: inset 1px 1px 0 rgba(0,0,0,0.1), inset 0 -1px 0 rgba(0,0,0,0.2); 26 | } 27 | -------------------------------------------------------------------------------- /zh/swarm-download/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go从Swarm下载文件的教程。 3 | --- 4 | 5 | # 从Swarm下载文件 6 | 7 | 在[上个章节](../swarm-upload) 我们将一个hello.txt文件上传到swarm,作为返回值,我们得到了一个内容清单哈希。 8 | 9 | ```go 10 | manifestHash := "f9192507e2e8e118bfedac428c3aa1dec4ae156e954128ec5fb27f63ee67bcac" 11 | ``` 12 | 13 | 让我们首先通过调用“DownloadManfest”来下载它,并检查清单的内容。 14 | 15 | 16 | ```go 17 | manifest, isEncrypted, err := client.DownloadManifest(manifestHash) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | ``` 22 | 23 | 我们可以遍历清单条目,看看内容类型,大小和内容哈希是什么。 24 | 25 | ```go 26 | for _, entry := range manifest.Entries { 27 | fmt.Println(entry.Hash) // 42179060941352ba7b400b16c40f1e1290423a826de2a70587034dc14bc4ab2f 28 | fmt.Println(entry.ContentType) // text/plain; charset=utf-8 29 | fmt.Println(entry.Path) // "" 30 | } 31 | ``` 32 | 33 | 如果您熟悉swarm url,它们的格式为`bzz:/ / `,因此为了下载文件,我们指定了清单哈希和路径。 在这个例子里,路径是一个空字符串。 我们将这些数据传递给`Download`函数并返回一个文件对象。 34 | 35 | 36 | ```go 37 | file, err := client.Download(manifestHash, "") 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | ``` 42 | 43 | 我们现在可以阅读并打印返回的文件阅读器的内容。 44 | 45 | ```go 46 | content, err := ioutil.ReadAll(file) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | fmt.Println(string(content)) // hello world 52 | ``` 53 | 54 | 正如预期的那样,它记录了我们原始文件所包含的 *hello world*。 55 | 56 | --- 57 | 58 | ### 完整代码 59 | 60 | Commands 61 | 62 | ```bash 63 | geth account new 64 | export BZZKEY=970ef9790b54425bea2c02e25cab01e48cf92573 65 | swarm --bzzaccount $BZZKEY 66 | ``` 67 | 68 | [swarm_download.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/swarm_download.go) 69 | 70 | ```go 71 | package main 72 | 73 | import ( 74 | "fmt" 75 | "io/ioutil" 76 | "log" 77 | 78 | bzzclient "github.com/ethereum/go-ethereum/swarm/api/client" 79 | ) 80 | 81 | func main() { 82 | client := bzzclient.NewClient("http://127.0.0.1:8500") 83 | manifestHash := "2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0" 84 | manifest, isEncrypted, err := client.DownloadManifest(manifestHash) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | fmt.Println(isEncrypted) // false 89 | 90 | for _, entry := range manifest.Entries { 91 | fmt.Println(entry.Hash) // 42179060941352ba7b400b16c40f1e1290423a826de2a70587034dc14bc4ab2f 92 | fmt.Println(entry.ContentType) // text/plain; charset=utf-8 93 | fmt.Println(entry.Size) // 12 94 | fmt.Println(entry.Path) // "" 95 | } 96 | 97 | file, err := client.Download(manifestHash, "") 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | 102 | content, err := ioutil.ReadAll(file) 103 | if err != nil { 104 | log.Fatal(err) 105 | } 106 | fmt.Println(string(content)) // hello world 107 | } 108 | ``` 109 | -------------------------------------------------------------------------------- /zh/swarm-setup/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 搭建swarm节点的教程。 3 | --- 4 | 5 | # 搭建 Swarm 节点 6 | 7 | 要运行swarm,首先需要安装`geth`和`bzzd`,这是swarm背景进程。 8 | 9 | ```go 10 | go get -d github.com/ethereum/go-ethereum 11 | go install github.com/ethereum/go-ethereum/cmd/geth 12 | go install github.com/ethereum/go-ethereum/cmd/swarm 13 | ``` 14 | 15 | 然后我们将生成一个新的geth帐户。 16 | 17 | 18 | ```bash 19 | $ geth account new 20 | 21 | Your new account is locked with a password. Please give a password. Do not forget this password. 22 | Passphrase: 23 | Repeat passphrase: 24 | Address: {970ef9790b54425bea2c02e25cab01e48cf92573} 25 | ``` 26 | 27 | 将环境变量`BZZKEY`导出,并设定为我们刚刚生成的geth帐户地址。 28 | 29 | ```bash 30 | export BZZKEY=970ef9790b54425bea2c02e25cab01e48cf92573 31 | ``` 32 | 33 | 然后使用设定的帐户运行swarm,并作为我们的swarm帐户。 默认情况下,Swarm将在端口“8500”上运行。 34 | 35 | ```bash 36 | $ swarm --bzzaccount $BZZKEY 37 | Unlocking swarm account 0x970EF9790B54425BEA2C02e25cAb01E48CF92573 [1/3] 38 | Passphrase: 39 | WARN [06-12|13:11:41] Starting Swarm service 40 | ``` 41 | 42 | 现在swarm进程已经可以运行了,那么我们会在[下个章节](../swarm-upload)学习如何上传文件。 43 | 44 | --- 45 | 46 | ### 完整代码 47 | 48 | Commands 49 | 50 | ```bash 51 | go get -d github.com/ethereum/go-ethereum 52 | go install github.com/ethereum/go-ethereum/cmd/geth 53 | go install github.com/ethereum/go-ethereum/cmd/swarm 54 | geth account new 55 | export BZZKEY=970ef9790b54425bea2c02e25cab01e48cf92573 56 | swarm --bzzaccount $BZZKEY 57 | ``` 58 | -------------------------------------------------------------------------------- /zh/swarm-upload/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go来上传文件到Swarm的教程。 3 | --- 4 | 5 | # 上传文件到Swarm 6 | 7 | 在[上个章节](../swarm-setup) 我们在端口“8500”上运行了一个作为背景进程的swarm节点。 接下来就导入swarm包go-ethereum`swearm/api/client`。 我将把包装别名为`bzzclient`。 8 | 9 | 10 | ```go 11 | import ( 12 | bzzclient "github.com/ethereum/go-ethereum/swarm/api/client" 13 | ) 14 | ``` 15 | 16 | 调用`NewClient`函数向它传递swarm背景程序的url。 17 | 18 | ```go 19 | client := bzzclient.NewClient("http://127.0.0.1:8500") 20 | ``` 21 | 22 | 用内容 *hello world* 创建示例文本文件`hello.txt`。 我们将会把这个文件上传到swarm。 23 | 24 | ```txt 25 | hello world 26 | ``` 27 | 28 | 在我们的Go应用程序中,我们将使用Swarm客户端软件包中的“Open”打开我们刚刚创建的文件。 该函数将返回一个`File`类型,它表示swarm清单中的文件,用于上传和下载swarm内容。 29 | 30 | ```go 31 | file, err := bzzclient.Open("hello.txt") 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | ``` 36 | 37 | 现在我们可以从客户端实例调用`Upload`函数,为它提供文件对象。 第二个参数是一个可选添的现有内容清单字符串,用于添加文件,否则它将为我们创建。 第三个参数是我们是否希望我们的数据被加密。 38 | 39 | 返回的哈希值是文件的内容清单的哈希值,其中包含hello.txt文件作为其唯一条目。 默认情况下,主要内容和清单都会上传。 清单确保您可以使用正确的mime类型检索文件。 40 | 41 | 42 | ```go 43 | manifestHash, err := client.Upload(file, "", false) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | fmt.Println(manifestHash) // 2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0 49 | ``` 50 | 51 | 然后我们就可以在这里查看上传的文件 `bzz://2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0`,具体如何下载,我们会在[下个章节](../swarm-download)介绍。 52 | 53 | --- 54 | 55 | ### 完整代码 56 | 57 | Commands 58 | 59 | ```bash 60 | geth account new 61 | export BZZKEY=970ef9790b54425bea2c02e25cab01e48cf92573 62 | swarm --bzzaccount $BZZKEY 63 | ``` 64 | 65 | [hello.txt](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/hello.txt) 66 | 67 | ```txt 68 | hello world 69 | ``` 70 | 71 | [swarm_upload.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/swarm_upload.go) 72 | 73 | ```go 74 | package main 75 | 76 | import ( 77 | "fmt" 78 | "log" 79 | 80 | bzzclient "github.com/ethereum/go-ethereum/swarm/api/client" 81 | ) 82 | 83 | func main() { 84 | client := bzzclient.NewClient("http://127.0.0.1:8500") 85 | 86 | file, err := bzzclient.Open("hello.txt") 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | 91 | manifestHash, err := client.Upload(file, "", false) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | fmt.Println(manifestHash) // 2e0849490b62e706a5f1cb8e7219db7b01677f2a859bac4b5f522afd2a5f02c0 96 | } 97 | ``` 98 | -------------------------------------------------------------------------------- /zh/swarm/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go进行Swarm相关开发的教程。 3 | --- 4 | 5 | # Swarm 6 | 7 | Swarm是以太坊的去中心化和分布式的存储解决方案,与IPFS类似。 Swarm是一种点对点数据共享网络,其中文件通过其内容的哈希来寻址。与Bittorrent类似,可以同时从多个节点获取数据,只要单个节点承载分发数据,它就可以随处被访问。这种方法可以在不必依靠托管任何类型服务器的情况下分发数据 - 数据可访问性与位置无关。可以激励网络中的其他节点自己复制和存储数据,从而在原节点未连接到网络时避免了对托管服务的依赖。 8 | 9 | Swarm的激励机制Swap(Swarm Accounting Protocol)是一种协议,通过该协议,Swarm网络中的个体可以跟踪传送和接收的数据块,以及由此产生相应的(微)付款。 SWAP本身可以在更广泛的背景下运行,但它通常表现为适用于点对点之间成对会计的通用微支付方案。虽然设计通用,但它的第一个用途是将带宽计算作为Swarm去中心化的点对点存储网络中数据传输的激励的一部分。 10 | 11 | 12 | -------------------------------------------------------------------------------- /zh/test/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go来测试以太坊应用的教程。 3 | --- 4 | 5 | # Testing 6 | 7 | - [发币水龙头](../faucets) 8 | - [使用模拟客户端](../client-simulated) 9 | 10 | -------------------------------------------------------------------------------- /zh/transaction-raw-create/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go构建以太坊原始交易的教程。 3 | --- 4 | 5 | # 构建原始交易(Raw Transaction) 6 | 7 | 如果你看过[上个章节](../transfer-eth), 那么你知道如何加载你的私钥来签名交易。 我们现在假设你知道如何做到这一点,现在你想让原始交易数据能够在以后广播它。 8 | 9 | 首先构造事务对象并对其进行签名,例如: 10 | 11 | ```go 12 | tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) 13 | 14 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | ``` 19 | 20 | 现在,在我们以原始字节格式获取事务之前,我们需要初始化一个`types.Transactions`类型,并将签名后的交易作为第一个值。 21 | 22 | ```go 23 | ts := types.Transactions{signedTx} 24 | ``` 25 | 26 | 这样做的原因是因为`Transactions`类型提供了一个`GetRlp`方法,用于以RLP编码格式返回事务。 RLP是以太坊用于序列化对象的特殊编码方法。 结果是原始字节。 27 | 28 | ```go 29 | rawTxBytes := ts.GetRlp(0) 30 | ``` 31 | 32 | 最后,我们可以非常轻松地将原始字节转换为十六进制字符串。 33 | 34 | ```go 35 | rawTxHex := hex.EncodeToString(rawTxBytes) 36 | 37 | fmt.Printf(rawTxHex) 38 | // f86d8202b38477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ba0699ff162205967ccbabae13e07cdd4284258d46ec1051a70a51be51ec2bc69f3a04e6944d508244ea54a62ebf9a72683eeadacb73ad7c373ee542f1998147b220e 39 | ``` 40 | 41 | 接下来,你就可以广播原始交易数据。在[下一章](../transaction-raw-send) 我们将学习如何广播一个原始交易。 42 | 43 | --- 44 | 45 | ### 完整代码 46 | 47 | [transaction_raw_create.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/transaction_raw_create.go) 48 | 49 | ```go 50 | package main 51 | 52 | import ( 53 | "context" 54 | "crypto/ecdsa" 55 | "encoding/hex" 56 | "fmt" 57 | "log" 58 | "math/big" 59 | 60 | "github.com/ethereum/go-ethereum/common" 61 | "github.com/ethereum/go-ethereum/core/types" 62 | "github.com/ethereum/go-ethereum/crypto" 63 | "github.com/ethereum/go-ethereum/ethclient" 64 | ) 65 | 66 | func main() { 67 | client, err := ethclient.Dial("https://rinkeby.infura.io") 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | 72 | privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19") 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | 77 | publicKey := privateKey.Public() 78 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 79 | if !ok { 80 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 81 | } 82 | 83 | fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 84 | nonce, err := client.PendingNonceAt(context.Background(), fromAddress) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | 89 | value := big.NewInt(1000000000000000000) // in wei (1 eth) 90 | gasLimit := uint64(21000) // in units 91 | gasPrice, err := client.SuggestGasPrice(context.Background()) 92 | if err != nil { 93 | log.Fatal(err) 94 | } 95 | 96 | toAddress := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d") 97 | var data []byte 98 | tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) 99 | 100 | chainID, err := client.NetworkID(context.Background()) 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | 105 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) 106 | if err != nil { 107 | log.Fatal(err) 108 | } 109 | 110 | ts := types.Transactions{signedTx} 111 | rawTxBytes := ts.GetRlp(0) 112 | rawTxHex := hex.EncodeToString(rawTxBytes) 113 | 114 | fmt.Printf(rawTxHex) // f86...772 115 | } 116 | ``` 117 | -------------------------------------------------------------------------------- /zh/transaction-raw-send/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go发送以太坊原始交易事务的教程。 3 | --- 4 | 5 | # 发送原始交易事务 6 | 7 | 在[上个章节中](../transaction-raw-create) 我们学会了如何创建原始事务。 现在,我们将学习如何将其广播到以太坊网络,以便最终被处理和被矿工打包到区块。 8 | 9 | 首先将原始事务十六进制解码为字节格式。 10 | 11 | ```go 12 | rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772" 13 | 14 | rawTxBytes, err := hex.DecodeString(rawTx) 15 | ``` 16 | 17 | 接下来初始化一个新的`types.Transaction`指针并从go-ethereum`rlp`包中调用`DecodeBytes`,将原始事务字节和指针传递给以太坊事务类型。 RLP是以太坊用于序列化和反序列化数据的编码方法。 18 | 19 | 20 | ```go 21 | tx := new(types.Transaction) 22 | rlp.DecodeBytes(rawTxBytes, &tx) 23 | ``` 24 | 25 | 现在,我们可以使用我们的以太坊客户端轻松地广播交易。 26 | 27 | ```go 28 | err := client.SendTransaction(context.Background(), tx) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f 34 | ``` 35 | 36 | 然后你可以去Etherscan看交易的确认过程: [https://rinkeby.etherscan.io/tx/0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f](https://rinkeby.etherscan.io/tx/0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f) 37 | 38 | --- 39 | 40 | ### 完整代码 41 | 42 | [transaction_raw_sendreate.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/transaction_raw_send.go) 43 | 44 | ```go 45 | package main 46 | 47 | import ( 48 | "context" 49 | "encoding/hex" 50 | "fmt" 51 | "log" 52 | 53 | "github.com/ethereum/go-ethereum/core/types" 54 | "github.com/ethereum/go-ethereum/ethclient" 55 | "github.com/ethereum/go-ethereum/rlp" 56 | ) 57 | 58 | func main() { 59 | client, err := ethclient.Dial("https://rinkeby.infura.io") 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | rawTx := "f86d8202b28477359400825208944592d8f8d7b001e72cb26a73e4fa1806a51ac79d880de0b6b3a7640000802ca05924bde7ef10aa88db9c66dd4f5fb16b46dff2319b9968be983118b57bb50562a001b24b31010004f13d9a26b320845257a6cfc2bf819a3d55e3fc86263c5f0772" 65 | 66 | rawTxBytes, err := hex.DecodeString(rawTx) 67 | 68 | tx := new(types.Transaction) 69 | rlp.DecodeBytes(rawTxBytes, &tx) 70 | 71 | err = client.SendTransaction(context.Background(), tx) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0xc429e5f128387d224ba8bed6885e86525e14bfdc2eb24b5e9c3351a1176fd81f 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /zh/transactions/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go发交易事务(`Transaction`)的教程。 3 | --- 4 | 5 | # 交易(`Transaction`) 6 | 7 | 这些部分将讨论如何使用go-ethereum`ethclient`包在以太坊上查询和发送交易。注意这里的交易`transaction` 是指广义的对以太坊状态的更改,它既可以指具体的以太币转账,代币的转账,或者其他对智能合约的创建或者调用。而不仅仅是传统意义的买卖交易。 8 | -------------------------------------------------------------------------------- /zh/util/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: Go实现的以太坊的工具函数. 3 | --- 4 | 5 | # 工具函数 6 | 7 | - [工具函数集合](../util-go) 8 | -------------------------------------------------------------------------------- /zh/wallet-generate/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go生成以太坊钱包的教程。 3 | --- 4 | 5 | # 生成新钱包 6 | 7 | 要首先生成一个新的钱包,我们需要导入go-ethereum`crypto`包,该包提供用于生成随机私钥的`GenerateKey`方法。 8 | 9 | ```go 10 | privateKey, err := crypto.GenerateKey() 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | ``` 15 | 16 | 然后我们可以通过导入golang`crypto/ecdsa`包并使用`FromECDSA`方法将其转换为字节。 17 | 18 | ```go 19 | privateKeyBytes := crypto.FromECDSA(privateKey) 20 | ``` 21 | 22 | 我们现在可以使用go-ethereum`hexutil`包将它转换为十六进制字符串,该包提供了一个带有字节切片的`Encode`方法。 然后我们在十六进制编码之后删除“0x”。 23 | 24 | ```go 25 | fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19 26 | ``` 27 | 28 | 这就是用于签署交易的私钥,将被视为密码,永远不应该被共享给别人,因为谁拥有它可以访问你的所有资产。 29 | 30 | 由于公钥是从私钥派生的,因此go-ethereum的加密私钥具有一个返回公钥的`Public`方法。 31 | 32 | ```go 33 | publicKey := privateKey.Public() 34 | ``` 35 | 36 | 将其转换为十六进制的过程与我们使用转化私钥的过程类似。 我们剥离了`0x`和前2个字符`04`,它始终是EC前缀,不是必需的。 37 | 38 | ```go 39 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 40 | if !ok { 41 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 42 | } 43 | 44 | publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) 45 | fmt.Println(hexutil.Encode(publicKeyBytes)[4:]) // 9a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05 46 | ``` 47 | 48 | 现在我们拥有公钥,就可以轻松生成你经常看到的公共地址。 为了做到这一点,go-ethereum加密包有一个`PubkeyToAddress`方法,它接受一个ECDSA公钥,并返回公共地址。 49 | 50 | ```go 51 | address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() 52 | fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E 53 | ``` 54 | 55 | 公共地址其实就是公钥的Keccak-256哈希,然后我们取最后40个字符(20个字节)并用“0x”作为前缀。 以下是使用 `golang.org/x/crypto/sha3` 的 Keccak256函数手动完成的方法。 56 | 57 | ```go 58 | hash := sha3.NewLegacyKeccak256() 59 | hash.Write(publicKeyBytes[1:]) 60 | fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e 61 | ``` 62 | 63 | --- 64 | 65 | ### 完整代码 66 | 67 | [wallet_generate.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/wallet_generate.go) 68 | 69 | ```go 70 | package main 71 | 72 | import ( 73 | "crypto/ecdsa" 74 | "fmt" 75 | "log" 76 | 77 | "github.com/ethereum/go-ethereum/common/hexutil" 78 | "github.com/ethereum/go-ethereum/crypto" 79 | "golang.org/x/crypto/sha3" 80 | ) 81 | 82 | func main() { 83 | privateKey, err := crypto.GenerateKey() 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | 88 | privateKeyBytes := crypto.FromECDSA(privateKey) 89 | fmt.Println(hexutil.Encode(privateKeyBytes)[2:]) // 0xfad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19 90 | 91 | publicKey := privateKey.Public() 92 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 93 | if !ok { 94 | log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") 95 | } 96 | 97 | publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) 98 | fmt.Println(hexutil.Encode(publicKeyBytes)[4:]) // 0x049a7df67f79246283fdc93af76d4f8cdd62c4886e8cd870944e817dd0b97934fdd7719d0810951e03418205868a5c1b40b192451367f28e0088dd75e15de40c05 99 | 100 | address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() 101 | fmt.Println(address) // 0x96216849c49358B10257cb55b28eA603c874b05E 102 | 103 | hash := sha3.NewLegacyKeccak256() 104 | hash.Write(publicKeyBytes[1:]) 105 | fmt.Println(hexutil.Encode(hash.Sum(nil)[12:])) // 0x96216849c49358b10257cb55b28ea603c874b05e 106 | } 107 | ``` 108 | -------------------------------------------------------------------------------- /zh/whisper-client/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go使用whisper客户端的教程。 3 | --- 4 | 5 | # 连接Whisper客户端 6 | 7 | 8 | 要使用连接Whisper客户端,我们必须首先连接到运行whisper的以太坊节点。 不幸的是,诸如infura之类的公共网关不支持whisper,因为没有金钱动力免费处理这些消息。 Infura可能会在不久的将来支持whisper,但现在我们必须运行我们自己的`geth`节点。一旦你[安装 geth](https://geth.ethereum.org/downloads/), 运行geth的时候加 `--shh` flag来支持whisper协议, 并且加 `--ws`flag和 `--rpc`,来支持websocket来接收实时信息, 9 | 10 | ```bash 11 | geth --rpc --shh --ws 12 | ``` 13 | 14 | 现在在我们的Go应用程序中,我们将导入在`whisper/shhclient`中找到的go-ethereum whisper客户端软件包并初始化客户端,使用默认的websocket端口“8546”通过websockets连接我们的本地geth节点。 15 | 16 | ```go 17 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | 22 | _ = client // we'll be using this in the 下个章节 23 | ``` 24 | 25 | 现在我们已经拨打了,让我们创建一个密钥对来加密消息,然后再发送消息 [在下一章节](../whisper-keys). 26 | 27 | --- 28 | 29 | ### 完整代码 30 | 31 | Commands 32 | 33 | ```bash 34 | geth --rpc --shh --ws 35 | ``` 36 | 37 | [whisper_client.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/whisper_client.go) 38 | 39 | ```go 40 | package main 41 | 42 | import ( 43 | "log" 44 | 45 | "github.com/ethereum/go-ethereum/whisper/shhclient" 46 | ) 47 | 48 | func main() { 49 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | _ = client // we'll be using this in the 下个章节 55 | fmt.Println("we have a whisper connection") 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /zh/whisper-keys/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go生成whisper密钥对的。 3 | --- 4 | 5 | # 生成 Whisper 密匙对 6 | 7 | 在Whisper中,消息必须使用对称或非对称密钥加密,以防止除预期接收者以外的任何人读取消息。 8 | 9 | 在连接到Whisper客户端后,您需要调用客户端的`NewKeyPair`方法来生成该节点将管理的新公共和私有对。 此函数的结果将是一个唯一的ID,它引用我们将在接下来的几节中用于加密和解密消息的密钥对。 10 | 11 | 12 | ```go 13 | keyID, err := client.NewKeyPair(context.Background()) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 19 | ``` 20 | 21 | 在[下一章节](../whisper-send) 让我们学习如何发送一个加密的消息。 22 | 23 | --- 24 | 25 | ### 完整代码 26 | 27 | Commands 28 | 29 | ```bash 30 | geth --rpc --shh --ws 31 | ``` 32 | 33 | [whisper_keypair.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/whisper_keypair.go) 34 | 35 | ```go 36 | package main 37 | 38 | import ( 39 | "context" 40 | "fmt" 41 | "log" 42 | 43 | "github.com/ethereum/go-ethereum/whisper/shhclient" 44 | ) 45 | 46 | func main() { 47 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | keyID, err := client.NewKeyPair(context.Background()) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /zh/whisper-poll/README.md: -------------------------------------------------------------------------------- 1 | FilterMessages会拉取在上次调用此函数到这次调用之间接收的所有消息,并匹配过滤器时给定的条件过滤消息。 2 | 3 | NewMessageFilter在节点内创建过滤器。 此过滤器可用于轮询满足给定条件的新消息(请参阅FilterMessages)。 在whisper.filterTimeout中轮询过滤器时,过滤器可能会超时。 4 | -------------------------------------------------------------------------------- /zh/whisper-send/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go在whisper上发送消息的教程。 3 | --- 4 | 5 | # 在Whisper上发送消息 6 | 7 | 8 | 在我们能够创建消息之前,我们必须首先使用公钥来加密消息。在[上个章节](../whisper-keys)中,我们学习了如何使用`NewKeyPair`函数生成公钥和私钥对,该函数返回了引用该密钥对的密钥ID。 我们现在必须调用`PublicKey`函数以字节格式读取密钥对的公钥,我们将使用它来加密消息。 9 | 10 | ```go 11 | publicKey, err := client.PublicKey(context.Background(), keyID) 12 | if err != nil { 13 | log.Print(err) 14 | } 15 | 16 | fmt.Println(hexutil.Encode(publicKey)) // 0x04f17356fd52b0d13e5ede84f998d26276f1fc9d08d9e73dcac6ded5f3553405db38c2f257c956f32a0c1fca4c3ff6a38a2c277c1751e59a574aecae26d3bf5d1d 17 | ``` 18 | 19 | 现在我们将通过从go-ethereum`whisper/whisperv6`包中初始化`NewMessage`结构来构造我们的私语消息,这需要以下属性: 20 | 21 | - `Payload` 字节格式的消息内容 22 | - `PublicKey` 加密的公钥 23 | - `TTL` 消息的活跃时间 24 | - `PowTime` 做工证明的时间上限 25 | - `PowTarget` 做工证明的时间下限 26 | 27 | ```go 28 | message := whisperv6.NewMessage{ 29 | Payload: []byte("Hello"), 30 | PublicKey: publicKey, 31 | TTL: 60, 32 | PowTime: 2, 33 | PowTarget: 2.5, 34 | } 35 | ``` 36 | 37 | 我们现在可以通过调用客户端的`Post`函数向网络广播,给它消息,它是否会返回消息的哈希值。 38 | 39 | ```go 40 | messageHash, err := client.Post(context.Background(), message) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | 45 | fmt.Println(messageHash) // 0xdbfc815d3d122a90d7fb44d1fc6a46f3d76ec752f3f3d04230fe5f1b97d2209a 46 | ``` 47 | 48 | 在[下个章节](../whisper-subscribe)中我们将看到如何创建消息订阅以便能够实时接收消息。 49 | 50 | --- 51 | 52 | ### 完整代码 53 | 54 | Commands 55 | 56 | ```bash 57 | geth --shh --rpc --ws 58 | ``` 59 | 60 | [whisper_send.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/whisper_send.go) 61 | 62 | ```go 63 | package main 64 | 65 | import ( 66 | "context" 67 | "fmt" 68 | "log" 69 | 70 | "github.com/ethereum/go-ethereum/common/hexutil" 71 | "github.com/ethereum/go-ethereum/whisper/shhclient" 72 | "github.com/ethereum/go-ethereum/whisper/whisperv6" 73 | ) 74 | 75 | func main() { 76 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | 81 | keyID, err := client.NewKeyPair(context.Background()) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 86 | 87 | publicKey, err := client.PublicKey(context.Background(), keyID) 88 | if err != nil { 89 | log.Print(err) 90 | } 91 | fmt.Println(hexutil.Encode(publicKey)) // 0x04f17356fd52b0d13e5ede84f998d26276f1fc9d08d9e73dcac6ded5f3553405db38c2f257c956f32a0c1fca4c3ff6a38a2c277c1751e59a574aecae26d3bf5d1d 92 | 93 | message := whisperv6.NewMessage{ 94 | Payload: []byte("Hello"), 95 | PublicKey: publicKey, 96 | TTL: 60, 97 | PowTime: 2, 98 | PowTarget: 2.5, 99 | } 100 | messageHash, err := client.Post(context.Background(), message) 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | fmt.Println(messageHash) // 0xdbfc815d3d122a90d7fb44d1fc6a46f3d76ec752f3f3d04230fe5f1b97d2209a 105 | } 106 | ``` 107 | -------------------------------------------------------------------------------- /zh/whisper-subscribe/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go在whisper中监听/订阅消息的教程。 3 | --- 4 | 5 | # 监听/订阅Whisper消息 6 | 7 | 在本节中,我们将订阅websockets上的Whisper消息。 我们首先需要的是一个通道,它将从`whisper/whisperv6`包中的`Message`类型接收Whispe消息。 8 | 9 | ```go 10 | messages := make(chan *whisperv6.Message) 11 | ``` 12 | 13 | 在我们调用订阅之前,我们首先需要确定消息的过滤标准。 从whisperv6包中初始化一个新的`Criteria`对象。 由于我们只对定位到我们的消息感兴趣,因此我们将条件对象上的`PrivateKeyID`属性设置为我们用于加密消息的相同密钥ID。 14 | 15 | ```go 16 | criteria := whisperv6.Criteria{ 17 | PrivateKeyID: keyID, 18 | } 19 | ``` 20 | 21 | 接下来,我们调用客户端的`SubscribeMessages`方法,该方法订阅符合给定条件的消息。 HTTP不支持此方法; 仅支持双向连接,例如websockets和IPC。 最后一个参数是我们之前创建的消息通道。 22 | 23 | ```go 24 | sub, err := client.SubscribeMessages(context.Background(), criteria, messages) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | ``` 29 | 30 | 现在我们已经订阅了,我们可以使用`select`语句来读取消息,并处理订阅中的错误。 如果您从上一节回忆起来,消息内容在`Payload`属性中作为字节切片,我们可以将其转换回人类可读的字符串。 31 | 32 | ```go 33 | for { 34 | select { 35 | case err := <-sub.Err(): 36 | log.Fatal(err) 37 | case message := <-messages: 38 | fmt.Printf(string(message.Payload)) // "Hello" 39 | } 40 | } 41 | ``` 42 | 43 | 查看下面的完整代码,获取完整的栗子。 这就是消息订阅的所有内容。 44 | 45 | --- 46 | 47 | ### 完整代码 48 | 49 | Commands 50 | 51 | ```bash 52 | geth --shh --rpc --ws 53 | ``` 54 | 55 | [whisper_subscribe.go](https://github.com/miguelmota/ethereum-development-with-go-book/blob/master/code/whisper_subscribe.go) 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "context" 62 | "fmt" 63 | "log" 64 | "os" 65 | "runtime" 66 | 67 | "github.com/ethereum/go-ethereum/common/hexutil" 68 | "github.com/ethereum/go-ethereum/whisper/shhclient" 69 | "github.com/ethereum/go-ethereum/whisper/whisperv6" 70 | ) 71 | 72 | func main() { 73 | client, err := shhclient.Dial("ws://127.0.0.1:8546") 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | 78 | keyID, err := client.NewKeyPair(context.Background()) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | fmt.Println(keyID) // 0ec5cfe4e215239756054992dbc2e10f011db1cdfc88b9ba6301e2f9ea1b58d2 83 | 84 | messages := make(chan *whisperv6.Message) 85 | criteria := whisperv6.Criteria{ 86 | PrivateKeyID: keyID, 87 | } 88 | sub, err := client.SubscribeMessages(context.Background(), criteria, messages) 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | 93 | go func() { 94 | for { 95 | select { 96 | case err := <-sub.Err(): 97 | log.Fatal(err) 98 | case message := <-messages: 99 | fmt.Printf(string(message.Payload)) // "Hello" 100 | os.Exit(0) 101 | } 102 | } 103 | }() 104 | 105 | publicKey, err := client.PublicKey(context.Background(), keyID) 106 | if err != nil { 107 | log.Print(err) 108 | } 109 | fmt.Println(hexutil.Encode(publicKey)) // 0x04f17356fd52b0d13e5ede84f998d26276f1fc9d08d9e73dcac6ded5f3553405db38c2f257c956f32a0c1fca4c3ff6a38a2c277c1751e59a574aecae26d3bf5d1d 110 | 111 | message := whisperv6.NewMessage{ 112 | Payload: []byte("Hello"), 113 | PublicKey: publicKey, 114 | TTL: 60, 115 | PowTime: 2, 116 | PowTarget: 2.5, 117 | } 118 | 119 | messageHash, err := client.Post(context.Background(), message) 120 | if err != nil { 121 | log.Fatal(err) 122 | } 123 | fmt.Println(messageHash) // 0xdbfc815d3d122a90d7fb44d1fc6a46f3d76ec752f3f3d04230fe5f1b97d2209a 124 | 125 | runtime.Goexit() // wait for goroutines to finish 126 | } 127 | ``` 128 | -------------------------------------------------------------------------------- /zh/whisper/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 概述: 用Go使用whisper的教程。 3 | --- 4 | 5 | # Whisper 6 | 7 | Whisper是一种简单的基于点对点身份的消息传递系统,旨在成为下一去中心化的应用程序的构建块。 它旨在以相当的代价提供弹性和隐私。 在接下来的部分中,我们将设置一个支持Whisper的以太坊节点,然后我们将学习如何在Whisper协议上发送和接收加密消息。 8 | 9 | --------------------------------------------------------------------------------