Monday, April 5, 2021
Vts-Block
No Result
View All Result
  • Home
  • Blockchain
  • Cryptocurrency
  • Bitcoin
  • Crypto vs Bitcoin
  • Dogecoin
  • Litecoin
  • Ethereum
  • Ripple
  • ICO
  • BTC
  • Home
  • Blockchain
  • Cryptocurrency
  • Bitcoin
  • Crypto vs Bitcoin
  • Dogecoin
  • Litecoin
  • Ethereum
  • Ripple
  • ICO
  • BTC
No Result
View All Result
Vts-Block
No Result
View All Result
Home Ethereum

Create an API to interact with Ethereum Blockchain using Golang PART 1

by admin
October 18, 2020
in Ethereum
0
152
SHARES
1.9k
VIEWS
Share on FacebookShare on Twitter


Author profile picture

Hello of us! On this tutorial, we’re going to discover ways to create a easy REST API to work together with the Ethereum blockchain utilizing Golang. 

Web3.js is the de-facto library to work together for interacting with Ethereum in JavaScript and Node.js. It takes care of encoding payloads and producing the RPC calls. Web3.js may be very standard and closely documented.

However, (geth), the most well-liked Ethereum implementation, is written in Go. It’s a whole Ethereum node. For those who construct a dApp in Go, then you definately’ll be utilizing the go-ethereum libraries straight which suggests you are able to do every part the node can do.

So, for this tutorial, I selected to make use of Go as our weapon.

In easy phrases, interacting with the blockchain implies that you’ll be making RPC calls over HTTP. Any language will be capable to do this for you however, utilizing a compiled language reminiscent of Go gives you a greater efficiency… If efficiency is necessary to you or your crew.

Sufficient of boring introduction!

For our tutorial, we’re going to arrange 4 endpoints:

Get the most recent blockGet transaction by hashGet handle balanceTransfer ether to an handle

This isn’t an enormous deal, however I believe is cool as a place to begin to extra complicated implementations.

If you wish to get the entire code you possibly can obtain it here.

SET UP

I used go 1.13.8 for this tutorial, to get your Go model simply run:

$ go model
# outputs
go model go1.13.8 darwin/amd64

First we’re going to create our mission by operating the next command:

That can create on the root of your working listing the file

go.mod

with the next content material:

module github.com/LuisAcerv/goeth-api

go 1.13

Now let’s create our mission construction. I’ve to say right here that you need to use the construction that higher matches your wants.

On the root of your mission create a brand new

foremost.go

file.

$ echo "bundle foremost" >> foremost.go

Now we have to create three directories:

$ mkdir handler
$ mkdir fashions
$ mkdir modules

And inside every of these folders we’re going to create a

foremost.go

file, and we must always have the next construction:

.
├── handler
│   └── foremost.go
├── fashions
│   └── foremost.go
├── modules
│   └── foremost.go
├── go.mod
├── go.sum
├── foremost.go

Ganache-CLI

As a way to work together with the Ethereum blockchain, we’d like a supplier, a supplier is a node we’ve got entry to make RPC calls over HTTP. You should utilize a testnet reminiscent of ropsten or kovan by means of a supplier reminiscent of Infura, however for this tutorial, we’re going to arrange an area digital node utilizing ganache.

On the official truffle website you possibly can obtain ganache, is fairly straightforward to arrange and gives you all you want for testing proposes. It comes with a pleasant UI which can present you the transactions, accounts and logs of your “node”.

After you have ganache put in and operating we’re good to start out writing some code.

We’ve a

./fashions/foremost.go

file. This file comprises the constructions we’re going to use in our API.

We add the next content material:

bundle fashions

// Block information construction
sort Block struct {
	BlockNumber       int64         `json:"blockNumber"`
	Timestamp         uint64        `json:"timestamp"`
	Issue        uint64        `json:"problem"`
	Hash              string        `json:"hash"`
	TransactionsCount int           `json:"transactionsCount"`
	Transactions      []Transaction `json:"transactions"`
}

// Transaction information construction
sort Transaction struct {
	Hash     string `json:"hash"`
	Worth    string `json:"worth"`
	Fuel      uint64 `json:"fuel"`
	GasPrice uint64 `json:"gasPrice"`
	Nonce    uint64 `json:"nonce"`
	To       string `json:"to"`
	Pending  bool   `json:"pending"`
}

// TransferEthRequest information construction
sort TransferEthRequest struct {
	PrivKey string `json:"privKey"`
	To      string `json:"to"`
	Quantity  int64  `json:"quantity"`
}

// HashResponse information construction
sort HashResponse struct {
	Hash string `json:"hash"`
}

// BalanceResponse information construction
sort BalanceResponse struct {
	Tackle string `json:"handle"`
	Stability string `json:"steadiness"`
	Image  string `json:"image"`
	Items   string `json:"items"`
}

// Error information construction
sort Error struct {
	Code    uint64 `json:"code"`
	Message string `json:"message"`
}

Now that we’ve got our fashions outlined and prepared for use, we’re going to create the strategies in command of interacting with the blockchain.

To begin with we need to set up the

go-ethereum

module, and we are able to do this by operating the next command:

$ go get -u github.com/ethereum/go-ethereum

In our

./modules/foremost.go

we’re going to create a operate that retrieves the most recent block from the blockchain.

This operate goes to provide us the details about the block and the transactions embedded in it.

func GetLatestBlock(consumer ethclient.Shopper) *Fashions.Block {
	// We add a get well operate from panics to stop our API from crashing because of an surprising error
	defer func() {
		if err := get well(); err != nil {
			fmt.Println(err)
		}
	}()

	// Question the most recent block
	header, _ := consumer.HeaderByNumber(context.Background(), nil)
	blockNumber := huge.NewInt(header.Quantity.Int64())
	block, err := consumer.BlockByNumber(context.Background(), blockNumber)

	if err != nil {
		log.Deadly(err)
	}

	// Construct the response to our mannequin
	_block := &Fashions.Block{
		BlockNumber:       block.Quantity().Int64(),
		Timestamp:         block.Time(),
		Issue:        block.Issue().Uint64(),
		Hash:              block.Hash().String(),
		TransactionsCount: len(block.Transactions()),
		Transactions:      []Fashions.Transaction{},
	}

	for _, tx := vary block.Transactions() {
		_block.Transactions = append(_block.Transactions, Fashions.Transaction{
			Hash:     tx.Hash().String(),
			Worth:    tx.Worth().String(),
			Fuel:      tx.Fuel(),
			GasPrice: tx.GasPrice().Uint64(),
			Nonce:    tx.Nonce(),
			To:       tx.To().String(),
		})
	}

	return _block
}

Now, we need to have a operate to retrieve details about a given transaction, for instance, if we switch ether to from one account to a different, the API will reply with de transaction hash, and we are able to use it to get the transaction info.

To try this we add a brand new operate to our

./modules/foremost.go
// GetTxByHash by a given hash
func GetTxByHash(consumer ethclient.Shopper, hash frequent.Hash) *Fashions.Transaction {

	defer func() {
		if err := get well(); err != nil {
			fmt.Println(err)
		}
	}()

	tx, pending, err := consumer.TransactionByHash(context.Background(), hash)
	if err != nil {
		fmt.Println(err)
	}

	return &Fashions.Transaction{
		Hash:     tx.Hash().String(),
		Worth:    tx.Worth().String(),
		Fuel:      tx.Fuel(),
		GasPrice: tx.GasPrice().Uint64(),
		To:       tx.To().String(),
		Pending:  pending,
		Nonce:    tx.Nonce(),
	}
}

One other factor we need to know is our steadiness, for that we have to add one other operate.

// GetAddressBalance returns the given handle steadiness =P
func GetAddressBalance(consumer ethclient.Shopper, handle string) (string, error) {
	account := frequent.HexToAddress(handle)
	steadiness, err := consumer.BalanceAt(context.Background(), account, nil)
	if err != nil {
		return "0", err
	}

	return steadiness.String(), nil
}

The final operate we’re going to add into our modules bundle is the one that may enable us to ship ether from one account to a different.

And I need to make a parenthesis right here. This operate requires that the consumer sends the sender personal key to signal the transaction, that implies that your consumer will broadcast the consumer’s personal key by means of HTTP and if you’re going to do one thing like this utilizing with this code or one other else, then at the least be sure you are utilizing HTTPS.

Stated that allow’s add the operate to our modules bundle.

func TransferEth(consumer ethclient.Shopper, privKey string, to string, quantity int64) (string, error) {

	defer func() {
		if err := get well(); err != nil {
			fmt.Println(err)
		}
	}()

	// Assuming you've got already related a consumer, the subsequent step is to load your personal key.
	privateKey, err := crypto.HexToECDSA(privKey)
	if err != nil {
		return "", err
	}

	// Perform requires the general public handle of the account we're sending from -- which we are able to derive from the personal key.
	publicKey := privateKey.Public()
	publicKeyECDSA, okay := publicKey.(*ecdsa.PublicKey)
	if !okay {
		return "", err
	}

	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

	// Now we are able to learn the nonce that we must always use for the account's transaction.
	nonce, err := consumer.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		return "", err
	}

	worth := huge.NewInt(quantity) // in wei (1 eth)
	gasLimit := uint64(21000)   // in items
	gasPrice, err := consumer.SuggestGasPrice(context.Background())
	if err != nil {
		return "", err
	}

	// We determine who we're sending the ETH to.
	toAddress := frequent.HexToAddress(to)
	var information []byte

	// We create the transaction payload
	tx := varieties.NewTransaction(nonce, toAddress, worth, gasLimit, gasPrice, information)

	chainID, err := consumer.NetworkID(context.Background())
	if err != nil {
		return "", err
	}

	// We signal the transaction utilizing the sender's personal key
	signedTx, err := varieties.SignTx(tx, varieties.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		return "", err
	}

	// Now we're lastly able to broadcast the transaction to the whole community
	err = consumer.SendTransaction(context.Background(), signedTx)
	if err != nil {
		return "", err
	}

	// We return the transaction hash
	return signedTx.Hash().String(), nil
}

Good! we’ve got a operate to switch ether, now all collectively:

bundle modules

import (
	"context"
	"crypto/ecdsa"
	"fmt"
	"log"
	"math/huge"

	Fashions "github.com/LuisAcerv/goeth-api/fashions"
	"github.com/ethereum/go-ethereum/frequent"
	"github.com/ethereum/go-ethereum/core/varieties"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)

// GetLatestBlock from blockchain
func GetLatestBlock(consumer ethclient.Shopper) *Fashions.Block {
	// We add a get well operate from panics to stop our API from crashing because of an surprising error
	defer func() {
		if err := get well(); err != nil {
			fmt.Println(err)
		}
	}()

	// Question the most recent block
	header, _ := consumer.HeaderByNumber(context.Background(), nil)
	blockNumber := huge.NewInt(header.Quantity.Int64())
	block, err := consumer.BlockByNumber(context.Background(), blockNumber)

	if err != nil {
		log.Deadly(err)
	}

	// Construct the response to our mannequin
	_block := &Fashions.Block{
		BlockNumber:       block.Quantity().Int64(),
		Timestamp:         block.Time(),
		Issue:        block.Issue().Uint64(),
		Hash:              block.Hash().String(),
		TransactionsCount: len(block.Transactions()),
		Transactions:      []Fashions.Transaction{},
	}

	for _, tx := vary block.Transactions() {
		_block.Transactions = append(_block.Transactions, Fashions.Transaction{
			Hash:     tx.Hash().String(),
			Worth:    tx.Worth().String(),
			Fuel:      tx.Fuel(),
			GasPrice: tx.GasPrice().Uint64(),
			Nonce:    tx.Nonce(),
			To:       tx.To().String(),
		})
	}

	return _block
}

// GetTxByHash by a given hash
func GetTxByHash(consumer ethclient.Shopper, hash frequent.Hash) *Fashions.Transaction {

	defer func() {
		if err := get well(); err != nil {
			fmt.Println(err)
		}
	}()

	tx, pending, err := consumer.TransactionByHash(context.Background(), hash)
	if err != nil {
		fmt.Println(err)
	}

	return &Fashions.Transaction{
		Hash:     tx.Hash().String(),
		Worth:    tx.Worth().String(),
		Fuel:      tx.Fuel(),
		GasPrice: tx.GasPrice().Uint64(),
		To:       tx.To().String(),
		Pending:  pending,
		Nonce:    tx.Nonce(),
	}
}

// TransferEth from one account to a different
func TransferEth(consumer ethclient.Shopper, privKey string, to string, quantity int64) (string, error) {

	defer func() {
		if err := get well(); err != nil {
			fmt.Println(err)
		}
	}()

	// Assuming you've got already related a consumer, the subsequent step is to load your personal key.
	privateKey, err := crypto.HexToECDSA(privKey)
	if err != nil {
		return "", err
	}

	// Perform requires the general public handle of the account we're sending from -- which we are able to derive from the personal key.
	publicKey := privateKey.Public()
	publicKeyECDSA, okay := publicKey.(*ecdsa.PublicKey)
	if !okay {
		return "", err
	}

	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

	// Now we are able to learn the nonce that we must always use for the account's transaction.
	nonce, err := consumer.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		return "", err
	}

	worth := huge.NewInt(quantity) // in wei (1 eth)
	gasLimit := uint64(21000)   // in items
	gasPrice, err := consumer.SuggestGasPrice(context.Background())
	if err != nil {
		return "", err
	}

	// We determine who we're sending the ETH to.
	toAddress := frequent.HexToAddress(to)
	var information []byte

	// We create the transaction payload
	tx := varieties.NewTransaction(nonce, toAddress, worth, gasLimit, gasPrice, information)

	chainID, err := consumer.NetworkID(context.Background())
	if err != nil {
		return "", err
	}

	// We signal the transaction utilizing the sender's personal key
	signedTx, err := varieties.SignTx(tx, varieties.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		return "", err
	}

	// Now we're lastly able to broadcast the transaction to the whole community
	err = consumer.SendTransaction(context.Background(), signedTx)
	if err != nil {
		return "", err
	}

	// We return the transaction hash
	return signedTx.Hash().String(), nil
}

// GetAddressBalance returns the given handle steadiness =P
func GetAddressBalance(consumer ethclient.Shopper, handle string) (string, error) {
	account := frequent.HexToAddress(handle)
	steadiness, err := consumer.BalanceAt(context.Background(), account, nil)
	if err != nil {
		return "0", err
	}

	return steadiness.String(), nil
}

Now we have to arrange our API to work together with the features we wrote by means of HTTP endpoints. To do that we’re going to use gorilla/mux.

In our foremost file we’re going the add the next content material:

bundle foremost

import (
	"fmt"
	"log"
	"internet/http"

	Handlers "github.com/LuisAcerv/goeth-api/handler"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/gorilla/mux"
)

func foremost() {
	// Create a consumer occasion to hook up with our providr
	consumer, err := ethclient.Dial("http://localhost:7545")

	if err != nil {
		fmt.Println(err)
	}

	// Create a mux router
	r := mux.NewRouter()

	// We'll outline a single endpoint
	r.Deal with("/api/v1/eth/{module}", Handlers.ClientHandler{consumer})
	log.Deadly(http.ListenAndServe(":8080", r))
}

Now we have to create handler for our endpoint, since we’ve got added a parameter

module

to our endpoint we can deal with our features with a single handler. As I mentioned earlier than be happy to make use of the structure you would like on your personal mission.

Now in our

./handler/foremost.go

we’re going to add the next content material:

bundle handlers

import (
	"encoding/json"
	"fmt"
	"internet/http"

	Fashions "github.com/LuisAcerv/goeth-api/fashions"
	Modules "github.com/LuisAcerv/goeth-api/modules"
	"github.com/ethereum/go-ethereum/frequent"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/gorilla/mux"
)

// ClientHandler ethereum consumer occasion
sort ClientHandler struct {
	*ethclient.Shopper
}

func (consumer ClientHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Get parameter from url request
	vars := mux.Vars(r)
	module := vars["module"]

	// Get the question parameters from url request
	handle := r.URL.Question().Get("handle")
	hash := r.URL.Question().Get("hash")

	// Set our response header
	w.Header().Set("Content material-Sort", "utility/json")

	// Deal with every request utilizing the module parameter:
	change module {
	case "latest-block":
		_block := Modules.GetLatestBlock(*consumer.Shopper)
		json.NewEncoder(w).Encode(_block)

	case "get-tx":
		if hash == "" {
			json.NewEncoder(w).Encode(&Fashions.Error{
				Code:    400,
				Message: "Malformed request",
			})
			return
		}
		txHash := frequent.HexToHash(hash)
		_tx := Modules.GetTxByHash(*consumer.Shopper, txHash)

		if _tx != nil {
			json.NewEncoder(w).Encode(_tx)
			return
		}

		json.NewEncoder(w).Encode(&Fashions.Error{
			Code:    404,
			Message: "Tx Not Discovered!",
		})

	case "send-eth":
		decoder := json.NewDecoder(r.Physique)
		var t Fashions.TransferEthRequest

		err := decoder.Decode(&t)

		if err != nil {
			fmt.Println(err)
			json.NewEncoder(w).Encode(&Fashions.Error{
				Code:    400,
				Message: "Malformed request",
			})
			return
		}
		_hash, err := Modules.TransferEth(*consumer.Shopper, t.PrivKey, t.To, t.Quantity)

		if err != nil {
			fmt.Println(err)
			json.NewEncoder(w).Encode(&Fashions.Error{
				Code:    500,
				Message: "Inside server error",
			})
			return
		}

		json.NewEncoder(w).Encode(&Fashions.HashResponse{
			Hash: _hash,
		})

	case "get-balance":
		if handle == "" {
			json.NewEncoder(w).Encode(&Fashions.Error{
				Code:    400,
				Message: "Malformed request",
			})
			return
		}

		steadiness, err := Modules.GetAddressBalance(*consumer.Shopper, handle)

		if err != nil {
			fmt.Println(err)
			json.NewEncoder(w).Encode(&Fashions.Error{
				Code:    500,
				Message: "Inside server error",
			})
			return
		}

		json.NewEncoder(w).Encode(&Fashions.BalanceResponse{
			Tackle: handle,
			Stability: steadiness,
			Image:  "Ether",
			Items:   "Wei",
		})

	}

}

That is it, if we go to our terminal and run:

We will begin testing our API. Be sure you are operating your ganache occasion and in your browser go to: http://localhost:8080/api/v1/eth/latest-block and if every part is okay then it is best to see one thing like this:

Now let’s attempt to switch some ether from one account to a different, first copy the personal key of the sender from ganache:

And in addition copy an handle to ship the ether:

Now utilizing

curl

, let’s make a switch!:

$ curl -d '{"privKey":"12a770fe34a793800abaab0a48b7a394ae440b9117d000178af81b61cda8ff15", "to":"0xa8Ce5Fb2DAB781B8f743a8096601eB01Ff0a246d", "quantity":1000000000000000000}' -H "Content material-Sort: utility/json" -X POST http://localhost:8080/api/v1/eth/send-eth

It’s best to obtain the transaction hash as response:

{"hash":"0xa5417ae03a817e41ddf36303f3ea985e6bd64a504c662d988bcb47913be8d472"}

Now let’s get the transaction info utilizing our API, go to: http://localhost:8080/api/v1/eth/get-tx?hash=<tx-hash-from-response>

And it is best to see one thing like this:

And eventually, let’s test the steadiness of the handle by going to http://localhost:8080/api/v1/eth/get-balance?address=<the-recipient-address>

That is it, we’ve got created a easy API to start out interacting with the Ethereum blockchain and carry out some fundamental actions.

Within the following half we’re going to enhance our current code and we’re going to add performance to work together with sensible contracts and ERC20 tokens.

Repository: https://github.com/LuisAcerv/goeth-api

Try this tutorial on how create bitcoin HD Pockets utilizing Go.

And that is it, if you wish to speak then comply with me on twitter.

See you quickly with the subsequent half or in one other coding journey.

Comfortable hacking!

Associated

Tags

The Noonification banner

Subscribe to get your day by day round-up of high tech tales!





Source link

  • Trending
  • Comments
  • Latest
Christie’s to auction Beeple NFT art and will accept ether as payment

Christie’s to auction Beeple NFT art and will accept ether as payment

February 19, 2021
(GBTC) – Silicon Valley Payments Company Ripple’s Cryptocurrency XRP Up 133% In A Week

(GBTC) – Silicon Valley Payments Company Ripple’s Cryptocurrency XRP Up 133% In A Week

November 24, 2020
Everything you need to know about Crypto Trading

Everything you need to know about Crypto Trading

October 19, 2020
Plant Milk Market(COVID-19 impact) Growth Report 2020 By Ripple Foods, Danone, WhiteWave Foods, Archer-Daniels-Midland – BCFocus

Plant Milk Market(COVID-19 impact) Growth Report 2020 By Ripple Foods, Danone, WhiteWave Foods, Archer-Daniels-Midland – BCFocus

November 9, 2020
Global Cryptocurrency Market SWOT Analysis,Key Indicators,Forecast 2027 : ZEB IT Service, Coinsecure, Coinbase, Bitstamp, Litecoin – KSU

Global Cryptocurrency Market SWOT Analysis,Key Indicators,Forecast 2027 : ZEB IT Service, Coinsecure, Coinbase, Bitstamp, Litecoin – KSU

0
Making a case for Bitcoin’s survival in the greater market

Making a case for Bitcoin’s survival in the greater market

0
XRP and blockchain adoption will explode in the next months

XRP and blockchain adoption will explode in the next months

0
Cybersecurity and Cryptocurrency Prodigy Helping Institutions With Eradication of Ransomware

Cybersecurity and Cryptocurrency Prodigy Helping Institutions With Eradication of Ransomware

0
Global Cryptocurrency Market SWOT Analysis,Key Indicators,Forecast 2027 : ZEB IT Service, Coinsecure, Coinbase, Bitstamp, Litecoin – KSU

Global Cryptocurrency Market SWOT Analysis,Key Indicators,Forecast 2027 : ZEB IT Service, Coinsecure, Coinbase, Bitstamp, Litecoin – KSU

April 5, 2021
Ethereum, Litecoin, and Ripple’s XRP – Daily Tech Analysis – April 5th, 2021

Ethereum, Litecoin, and Ripple’s XRP – Daily Tech Analysis – April 5th, 2021

April 5, 2021
Traders holding Litecoin in portfolios can look forward to this

Traders holding Litecoin in portfolios can look forward to this

April 5, 2021
How Investors Can Send Dogecoin to $10

How Investors Can Send Dogecoin to $10

April 4, 2021

Recent News

Global Cryptocurrency Market SWOT Analysis,Key Indicators,Forecast 2027 : ZEB IT Service, Coinsecure, Coinbase, Bitstamp, Litecoin – KSU

Global Cryptocurrency Market SWOT Analysis,Key Indicators,Forecast 2027 : ZEB IT Service, Coinsecure, Coinbase, Bitstamp, Litecoin – KSU

April 5, 2021
Ethereum, Litecoin, and Ripple’s XRP – Daily Tech Analysis – April 5th, 2021

Ethereum, Litecoin, and Ripple’s XRP – Daily Tech Analysis – April 5th, 2021

April 5, 2021

Live Price

Name Price24H (%)
bitcoin
Bitcoin (BTC)
$57,395.00
-0.48%
ethereum
Ethereum (ETH)
$2,038.66
-0.48%
Binance Coin
Binance Coin (BNB)
$344.12
0.26%
tether
Tether (USDT)
$1.00
0.03%
Polkadot
Polkadot (DOT)
$43.11
-3.63%
cardano
Cardano (ADA)
$1.18
-0.33%
ripple
XRP (XRP)
$0.70
16.76%
Uniswap
Uniswap (UNI)
$30.22
-1.01%
JasmyCoin
JasmyCoin (JASMY)
$2.08
-2.13%
litecoin
Litecoin (LTC)
$202.77
0.40%
  • Home
  • About Us
  • Contact Us
  • Privacy & Policy

© 2020 Vts-Block

No Result
View All Result
  • Home
  • Blockchain
  • Cryptocurrency
  • Bitcoin
  • Crypto vs Bitcoin
  • Dogecoin
  • Litecoin
  • Ethereum
  • Ripple
  • ICO
  • BTC

© 2020 Vts-Block