Subscribe to our free newsletter

To make sure you won't miss any valuable content we share with our community.

Interacting with Terra Network Using Terra Python SDK

In this article, we are going to get familiar with the Terra network and how it works. Then, we will see how we can interact with the Terra network using Terra Python SDK to interact with Terra Testnet called Bombay 12 and Mainnet called Columbus-5. In this article and the next ones, we are going to see how we can connect to the Terra network using Python, create an account and a wallet, create and sign a transaction, estimate gas fees, and swap tokens.

What Is Terra SDK?

Terra is a DeFi protocol on Blockchain that powers the universal payment systems which are based on fiat currencies using the fiat-based stable coins. This protocol uses a mixture of wide adoption of stable coins and at the same time censorship resistance of Bitcoin. Terra is built on Cosmos SDK and tendermint. The main language that the Terra smart contracts are written in, is Rust. However, there are SDKs for Python and JavaScript, and creating the smart contracts called WASM contracts is explained in more detail in JavaScript. But don’t worry most of the interactions and useful functions can be found in Python. Throughout this tutorial, we are going to see all the things we can do to develop the Terra network using Terra.py SDK.

Dealing with Terra Smart Contracts Using Terra Python SDK

If you have already worked with Ethereum smart contracts or have read our content on brownie, solidity, and Ethereum network smart contracts, you will so easily understand most of the concepts and tools that we are going to talk about. However, if you haven’t, you can still follow along with this tutorial and get familiar with the new world of smart contracts. As always, like all the blockchains that we have interacted with, we start with the test network to get used to the scripts and then learn how to switch to mainnet. To get started with Terra python SDK, let us install it using the following command:

pip3 install terra-sdk

There are also other requirements that we will cover later in our tutorial. Now let’s connect to Terra network:

terra = LCDClient("https://lcd.terra.dev", "columbus-5")
print(terra.tendermint.node_info())

Result:

{‘node_info’: {‘protocol_version’: {‘p2p’: ‘8’, ‘block’: ’11’, ‘app’: ‘0’}, ‘id’: ‘979a7f9c646623535ba2ddc69c10f3d505ccd97a’, ‘listen_addr’: ‘tcp://0.0.0.0:26656’, ‘network’: ‘columbus-5’, ‘version’: ‘0.34.14’, ‘channels’: ‘40202122233038606100’, ‘moniker’: ‘rpc5-read4’, ‘other’: {‘tx_index’: ‘on’, ‘rpc_address’: ‘tcp://0.0.0.0:26657’}}, ‘application_version’: {‘name’: ‘terra’, ‘server_name’: ‘terrad’, ‘version’: ‘0.5.17-36- gfc7ef220’, ‘commit’: ‘fc7ef220ddcd683d8e1a37a3ac484abb4e8b88ef’, ‘build_tags’: ‘netgo,ledger’, ‘go’: ‘go version go1.18 linux/amd64’, ‘build_deps’: [‘filippo.io/[email protected]’, ‘github.com/99designs/[email protected] => github.com/cosmos/[email protected]’, ‘github.com/ChainSafe/[email protected]’, ‘github.com/CosmWasm/[email protected]’, ‘github.com/Workiva/[email protected]’, ‘github.com/armon/[email protected]’, ‘github.com/beorn7/[email protected]’, ‘github.com/bgentry/[email protected]’, ‘github.com/btcsuite/[email protected]’, ‘github.com/cespare/xxhash/[email protected]’, ‘github.com/coinbase/[email protected]’, ‘github.com/confio/ics23/[email protected]’, ‘github.com/cosmos/[email protected]’, ‘github.com/cosmos/[email protected] => github.com/terra-money/[email protected]’, ‘github.com/cosmos/[email protected]’, ‘github.com/cosmos/[email protected]’, ‘github.com/cosmos/[email protected]’, ‘github.com/cosmos/[email protected] => github.com/terramoney/[email protected]’, ‘github.com/cosmos/[email protected]’, ‘github.com/davecgh/[email protected]’, ‘github.com/desertbit/[email protected] 20180107155436-c41aec40b27f’, ‘github.com/dvsekhvalnov/[email protected] 20200901110807-248326c1351b’, ‘github.com/felixge/[email protected]’, ‘github.com/fsnotify/[email protected]’, ‘github.com/go-kit/[email protected]’, ‘github.com/go-logfmt/[email protected]’, ‘github.com/godbus/[email protected] 20190726142602-4481cbc300e2’, ‘github.com/gogo/[email protected]’, ‘github.com/gogo/[email protected] => github.com/regen-network/[email protected] alpha.regen.1’, ‘github.com/golang/[email protected]’, ‘github.com/golang/[email protected]’, ‘github.com/google/[email protected]’, ‘github.com/google/orderedc[email protected]’, ‘github.com/gorilla/[email protected]’, ‘github.com/gorilla/[email protected]’, ‘github.com/gorilla/[email protected]’, ‘github.com/grpc-ecosystem/[email protected]’, ‘github.com/grpcecosystem/[email protected]’, ‘github.com/gsterjov/[email protected] 20161001094733-a6f4afe4910c’, ‘github.com/gtank/[email protected]’, ‘github.com/gtank/[email protected]’, ‘github.com/hashicorp/[email protected]’, ‘github.com/hashicorp/[email protected]’, ‘github.com/hashicorp/[email protected]’, ‘github.com/hdevalence/[email protected] 59a8610d2b87’, ‘github.com/improbable-eng/[email protected]’, ‘github.com/klauspost/[email protected]’, ‘github.com/lib/[email protected]’, ‘github.com/libp2p/[email protected]’, ‘github.com/magiconair/[email protected]’, ‘github.com/mattn/[email protected]’, ‘github.com/matttproud/[email protected]’, ‘github.com/mimoo/[email protected]’, ‘github.com/minio/[email protected]’, ‘github.com/mitchellh/[email protected]’, ‘github.com/mtibben/[email protected]’, ‘github.com/pelletier/[email protected]’, ‘github.com/pkg/[email protected]’, ‘github.com/pmezard/[email protected]’, ‘github.com/prometheus/[email protected]’, ‘github.com/prometheus/[email protected]’, ‘github.com/prometheus/[email protected]’, ‘github.com/prometheus/[email protected]’, ‘github.com/rakyll/[email protected]’, ‘github.com/rcrowley/[email protected] 20200313005456-10cdbea86bc0’, ‘github.com/regen-network/[email protected]’, ‘github.com/rs/[email protected]’, ‘github.com/rs/[email protected]’, ‘github.com/spf13/[email protected]’, ‘github.com/spf13/[email protected]’, ‘github.com/spf13/[email protected]’, ‘github.com/spf13/[email protected]’, ‘github.com/spf13/[email protected]’, ‘github.com/spf13/[email protected]’, ‘github.com/stretchr/[email protected]’, ‘github.com/subosito/[email protected]’, ‘github.com/syndtr/[email protected]’, ‘github.com/tendermint/[email protected]’, ‘github.com/tendermint/[email protected] 20191022145703-50d29ede1e15’, ‘github.com/tendermint/[email protected]’, ‘github.com/tendermint/[email protected] => github.com/terra-money/[email protected]’, ‘github.com/tendermint/[email protected] => github.com/terra-money/[email protected] performance.7’, ‘github.com/zondax/[email protected]’, ‘golang.org/x/[email protected] 20210817164053-32db794688a5’, ‘golang.org/x/[email protected] ad29c8ab022f’, ‘golang.org/x/[email protected]’, ‘golang.org/x/[email protected]’, ‘golang.org/x/[email protected]’, ‘google.golang.org/[email protected] 66f60bf46e71’, ‘google.golang.org/[email protected] => google.golang.org/[email protected]’, ‘google.golang.org/[email protected]’, ‘gopkg.in/[email protected]’, ‘gopkg.in/[email protected]’, ‘gopkg.in/[email protected] 20210107192922-496545a6307b’, ‘nhooyr.io/[email protected]’], ‘cosmos_sdk_version’: ‘v0.44.5’}}

For the first step, we can create an account by creating a mnemonic key:

from terra_sdk.client.lcd import LCDClient
from terra_sdk.key.mnemonic import MnemonicKey
mk = MnemonicKey()
print(mk.mnemonic)


Now, let’s test run the code:

python3 Test.py

Result:

tonight panda own all coconut home tackle gap food uncle vacant account clap napkin boring mango cable funny tooth vanish opinion genre differ impact

Notice that the above mnemonic key works the same as the private key and you will need to save it somewhere safe. Because if you forget it, you will never be able to recover the phrases. Besides, do not pass this mnemonic key to anyone. Because if you have money in it, they will be able to withdraw it anytime they want. We can get the account address by:

print(mk.acc_address)

Result:

terra1krrcxa8ccs7dwagtuevt2m4d78z0uwf4qe25yu

And the private key by:

print(mk.private_key)

Result:

b’\xf1\x92IyzN<\x14\xf2{N\x8a=\x86\x06\x9c \xab\n~/*\xb0\xb8b+z\xecS\x10\xcd,'

And the private key by:

print(mk.private_key)

Besides, make sure you save the mnemonic key somewhere safe. Let’s get some Luna from Terra Testnet Faucet (https://faucet.terra.money/). We need to copy the account address we’ve got in the box:

Terra network using Terra Python SDK

Once we get the message, we will be redirected to the terrascope which is similar to Etherscan and we can see the test Luna that we have been given:

Terra network using Terra Python SDK

Terra network using Terra Python SDK-transaction details

We can also see the details of our account if we click on the “to” address:

Terra network using Terra Python SDK-creating account

We can retrieve the balance of our account using the following code:

from terra_sdk.client.lcd import LCDClient
from terra_sdk.key.mnemonic import MnemonicKey
terra = LCDClient("https://lcd.terra.dev", " bombay-12")
mk = MnemonicKey(mnemonic="disorder solar ride ecology father pear conduct 
     anger distance soul slow outside market twenty badge alter busy inspire tag 
     enforce chicken shaft giggle measure")
print(terra.bank.balance(mk.acc_address))


Notice that in the above code, we have managed to call back the account using the mnemonic key we got when we used the mk = MnemonicKey() script. We have also connected to the bombay-12 Testnet. Now let’s run the code: python3 Test.py Result: Coins('1000000000uluna’)

What We Learned and What Will Be in the Afterwards

Up to now, for Terra network using Terra Python SDK, we have learned how to connect to Terra mainnet and testnet, create an account, and get the mnemonic key, private key, and the balance of the account. In the next part of this article, we are going to go deeper into transactions and interactions with the Terra smart contracts. We will create a wallet, create and sign a transaction, etc.

In this article we are going to continue our journey in Terra python SDK and interact with more of its functionalities. In this article and the next one we are going to see how we can connect to the Terra network, create an account and a wallet, create and sign transactions, estimate gas fees, and swap tokens. Of course, all of these operations are going to be executed on a testnet rather a mainnet in case we do lose real money.S

Introduction: In this article, we are going to cover more interactions with Terra Testnet by creating and signing transactions, transferring money, creating a wallet and an account, and send JSON messages to Terra smart contracts. 1. Setting up the network and the chain id: Up to now, we have learned how to connect to Terra chain mainnet and testnet, how to get some test Luna and how we can create test accounts. One thing to notice is how we can switch between different networks (mainnet to testnet). As we have mentioned before, there are 2 parameters for defining every network: 1. URL 2. Chain ID Terra chain has had many test networks such as tequila which is outdated, bombay (the newer one), and other custom networks created by developers. You can also create your own node using the guides on the Terra Documentation website (https://docs.terra.money/docs/full-node/run-a-full-terra-node/join-a-network.html# ) If you are on mainnet, you can enter as https://lcd.terra.dev URL and as Columbus-5 chain id and if you are on testnet, you can use https://bombay-lcd.terra.dev/ as URL and bombay-12 as your chain id. Notice that at the current time of writing, the Terra The faucet is on Bombay test network. So if you want to interact with the test ulunas sent from Bombay Faucet, you should use the specific URL and chain id of this test network. 2. Creating an Account on Terra Testnet: Using the below script, we can create an account, and get a mnemonic key for it. Make sure you copy the mnemonic somewhere so that you can use it later when you want to get some test Luna from Terra Faucet or when you want to get the balance of the account or create and sign a transaction using it.

from terra_sdk.client.lcd import LCDClient
from terra_sdk.key.mnemonic import MnemonicKey
terra = LCDClient("https://bombay-lcd.terra.dev/", "bombay-12")
Account1 = MnemonicKey()
print(Account1.mnemonic)


result: call oil decrease loud pull indicate diet post sign cereal adapt rug reform alcohol math illegal major anchor unhappy govern win round ritual various Now, we can use the Bombay Faucet to get some test uluna. And using the below script we can see the balance of account 1 containing uluna,

print(terra.bank.balance(Account1.acc_address)[0]['uluna'].amount)


result: 5000000 and using the below code, we can see all the assets of the account1: print(terra.bank.balance(Account1.acc_address)[0]) result: Coins('5000000uluna') 3. Transferring Luna from one account to another using python script: Now, we want to create a transaction using the Account1 with the mnemonic key that we have got and creating a new account called Account2. Notice that we define each account with its specific mnemonic key and here when we want to use the created account (Account1), we use the mnemonic key to reuse it, otherwise, a new account will be created with no funds in it.

Account1 = MnemonicKey(mnemonic="call oil decrease loud pull indicate diet post 
     sign cereal adapt rug reform alcohol math illegal 
     major anchor unhappy govern win round 
     ritual various")


And here comes the complete code to create a transaction and send a quarter of the balance of account 1 to account 2. We will also create a wallet for account 1 to be able to execute the transaction.

from terra_sdk.client.lcd import LCDClient
from terra_sdk.key.mnemonic import MnemonicKey
from terra_sdk.core.fee import Fee
from terra_sdk.core.bank import MsgSend
from terra_sdk.client.lcd.api.tx import CreateTxOptions

terra = LCDClient("https://bombay-lcd.terra.dev/", "bombay-12")
Account1 = MnemonicKey(mnemonic="call oil decrease loud pull indicate diet post 
     sign cereal adapt rug reform alcohol math illegal 
     major anchor unhappy govern win round 
     ritual various")
Account2 = MnemonicKey()
wallet = terra.wallet(Account1)
print(terra.bank.balance(Account1.acc_address)[0]['uluna'].amount)
print(terra.bank.balance(Account2.acc_address)[0]['uluna'].amount)
Amount_To_Send = (terra.bank.balance(Account1.acc_address)[0])/4
print(Amount_To_Send)

tx = wallet.create_and_sign_tx(CreateTxOptions(
     msgs = [MsgSend(from_address=Account1.acc_address, 
          to_address = Account2.acc_address, 
          amount=Amount_To_Send )],
     memo = "Only Testing",
     gas_prices = "1uluna", 
     gas_adjustment = "1.5")) 

result = terra.tx.broadcast(tx)
print(result)


result: 5000000 0 1250000uluna BlockTxBroadcastResult(height=8795417, txhash='10B07115283D05126EF4DCA72576BB3E4DD812B24B002614913020532 2999645', raw_log='[{"events":[{"type":"coin_received","attributes": [{"key":"receiver","value":"terra1e20tg4gat39ex4ezer724r5qs9dhnshjg065d4"}, {"key":"amount","value":"1250000uluna"}]},{"type":"coin_spent","attributes": [{"key":"spender","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"amount","value":"1250000uluna"}]},{"type":"message","attributes": [{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"}, {"key":"sender","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"module","value":"bank"}]},{"type":"transfer","attributes": [{"key":"recipient","value":"terra1e20tg4gat39ex4ezer724r5qs9dhnshjg065d4"}, {"key":"sender","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"amount","value":"1250000uluna"}]}]}]', gas_wanted=95229, gas_used=76149, logs=[TxLog(msg_index=0, log='', events=[{'type': 'coin_received', 'attributes': [{'key': 'receiver', 'value': 'terra1e20tg4gat39ex4ezer724r5qs9dhnshjg065d4'}, {'key': 'amount', 'value': '1250000uluna'}]}, {'type': 'coin_spent', 'attributes': [{'key': 'spender', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'amount', 'value': '1250000uluna'}]}, {'type': 'message', 'attributes': [{'key': 'action', 'value': '/cosmos.bank.v1beta1.MsgSend'}, {'key': 'sender', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'module', 'value': 'bank'}]}, {'type': 'transfer', 'attributes': [{'key': 'recipient', 'value': 'terra1e20tg4gat39ex4ezer724r5qs9dhnshjg065d4'}, {'key': 'sender', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'amount', 'value': '1250000uluna'}]}], events_by_type={'coin_received': {'receiver': ['terra1e20tg4gat39ex4ezer724r5qs9dhnshjg065d4'], 'amount': ['1250000uluna']}, 'coin_spent': {'spender': ['terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'], 'amount': ['1250000uluna']}, 'message': {'action': ['/cosmos.bank.v1beta1.MsgSend'], 'sender': ['terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'], 'module': ['bank']}, 'transfer': {'recipient': ['terra1e20tg4gat39ex4ezer724r5qs9dhnshjg065d4'], 'sender': ['terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'], 'amount': ['1250000uluna']}})], code=0, codespace='', info=None, data=None, timestamp=None) As you can see after a few seconds, the result of the transaction is printed out and you can get some of the data you need from this very long output. If we want to check our transaction details we can head over to Terra Finder (https://finder.terra.money/ ) and paste the txhash from the resulting output. Notice that you should choose the network ( mainnet, testnet or local) from the top-right menu. and you will be able to see the details of the transaction similar to what we saw in Etherscan for Ethereum blockchain. Conclusion: In this section, we have learned how to connect to the Terra Tesnet blockchain called Bombay-12, create an account, create a wallet, transfer money from one account to another and create and sign a transaction using python Terra SDK. In the next part, we will use this SDK to swap the tokens in the Terra network as well as estimate the gas fees.

In this article we are going to get familiar with the Terra Network Using Terra Python SDK and how it works. Then, we will see how we can use Terra Python SDK to interact with Terra Testnet called Bombay-12 and Mainnet called Columbus-5. In this 3rd part of Terra Python SDK, we are going to fetch price data from Terra Network, estimate gas fees for our transactions, and swap tokens using the Python SDK for Terra.

Terra Network Using Terra Python SDK: the Last Part

In this 3rd part of Terra Python SDK, we are going to fetch price data from Terra Network, estimate the gas fee for our transactions, and swap tokens using the Python SDK for Terra.

Requesting Price Data

We can fetch the price of uluna from FCD terra node with the following script:

from terra_sdk.client.lcd import LCDClient
from terra_sdk.key.mnemonic import MnemonicKey
import requests
import json

terra = LCDClient("https://bombay-lcd.terra.dev/", "bombay-12")
Account1 = MnemonicKey(mnemonic="call oil decrease loud pull indicate diet post 
     sign cereal adapt rug reform alcohol 
     math illegal major anchor unhappy 
     govern win round ritual various")

Account2 = MnemonicKey()
wallet = terra.wallet(Account1)
print(requests.get('https://fcd.terra.dev/v1/market/price?denom=uusd&interval=1h').json())


Result: {'lastPrice': 90.48744281649324, 'oneDayVariation': '2.03086440151527', 'oneDayVariationRate': '0.02244360475114567212', 'prices': [{'denom': 'uusd', 'price': 88.96415361266797, 'datetime': 1650888000000}, {'denom': 'uusd', 'price': 89.0900844450184, 'datetime': 1650891600000}, {'denom': 'uusd', 'price': 90.66006870152763, 'datetime': 1650895200000}, {'denom': 'uusd', 'price': 92.52536809614227, 'datetime': 1650898800000}, {'denom': 'uusd', 'price': 94.04939366114037, 'datetime': 1650902400000}, {'denom': 'uusd', 'price': 94.3395565349883, 'datetime': 1650906000000}, {'denom': 'uusd', 'price': 94.4164196214135, 'datetime': 1650909600000}, {'denom': 'uusd', 'price': 95.56109206654655, 'datetime': 1650913200000}, {'denom': 'uusd', 'price': 95.81135596156976, 'datetime': 1650916800000}, {'denom': 'uusd', 'price': 95.28242522306937, 'datetime': 1650920400000}, {'denom': 'uusd', 'price': 95.58401419222808, 'datetime': 1650924000000}, {'denom': 'uusd', 'price': 96.91434728345023, 'datetime': 1650927600000}, {'denom': 'uusd', 'price': 96.80871579823581, 'datetime': 1650931200000}, {'denom': 'uusd', 'price': 96.17697287838071, 'datetime': 1650934800000}, {'denom': 'uusd', 'price': 96.03501028344412, 'datetime': 1650938400000}, {'denom': 'uusd', 'price': 96.22807016895509, 'datetime': 1650942000000}, {'denom': 'uusd', 'price': 96.06570662357241, 'datetime': 1650945600000}, {'denom': 'uusd', 'price': 95.70176476651612, 'datetime': 1650949200000}, {'denom': 'uusd', 'price': 95.60364542853246, 'datetime': 1650952800000}, {'denom': 'uusd', 'price': 96.30584856654487, 'datetime': 1650956400000}, {'denom': 'uusd', 'price': 96.28680905438578, 'datetime': 1650960000000}, {'denom': 'uusd', 'price': 96.0928405310103, 'datetime': 1650963600000}, {'denom': 'uusd', 'price': 96.37104756437165, 'datetime': 1650967200000}, {'denom': 'uusd', 'price': 96.34377771748161, 'datetime': 1650970800000}, {'denom': 'uusd', 'price': 95.68353760426069, 'datetime': 1650974400000}, {'denom': 'uusd', 'price': 95.93167829936388, 'datetime': 1650978000000}, {'denom': 'uusd', 'price': 93.96949533359299, 'datetime': 1650981600000}, {'denom': 'uusd', 'price': 91.55576611528446, 'datetime': 1650985200000}, {'denom': 'uusd', 'price': 89.93169609113293, 'datetime': 1650988800000}, {'denom': 'uusd', 'price': 89.23415738171494, 'datetime': 1650992400000}, {'denom': 'uusd', 'price': 89.06808181910975, 'datetime': 1650996000000}, {'denom': 'uusd', 'price': 88.48694667915775, 'datetime': 1650999600000}, {'denom': 'uusd', 'price': 88.11627958903455, 'datetime': 1651003200000}, {'denom': 'uusd', 'price': 89.09030726339398, 'datetime': 1651006800000}, {'denom': 'uusd', 'price': 89.06232865592419, 'datetime': 1651010400000}, {'denom': 'uusd', 'price': 88.54428184656634, 'datetime': 1651014000000}, {'denom': 'uusd', 'price': 88.778354665981, 'datetime': 1651017600000}, {'denom': 'uusd', 'price': 89.00243656718825, 'datetime': 1651021200000}, {'denom': 'uusd', 'price': 88.7637772736078, 'datetime': 1651024800000}, {'denom': 'uusd', 'price': 88.43322633009933, 'datetime': 1651028400000}, {'denom': 'uusd', 'price': 88.03606836235143, 'datetime': 1651032000000}, {'denom': 'uusd', 'price': 88.30151817608929, 'datetime': 1651035600000}, {'denom': 'uusd', 'price': 88.60340905121288, 'datetime': 1651039200000}, {'denom': 'uusd', 'price': 89.83111008867395, 'datetime': 1651042800000}, {'denom': 'uusd', 'price': 89.64450032950381, 'datetime': 1651046400000}, {'denom': 'uusd', 'price': 89.34933700739906, 'datetime': 1651050000000}, {'denom': 'uusd', 'price': 89.27527287128059, 'datetime': 1651053600000}, {'denom': 'uusd', 'price': 88.73870274154594, 'datetime': 1651057200000}, {'denom': 'uusd', 'price': 88.83721399177365, 'datetime': 1651060800000}, {'denom': 'uusd', 'price': 89.2133569858777, 'datetime': 1651064400000}]} Gas Fee Estimation on Terra Blockchain: Gas fee estimation is an important step when you want to execute a transaction on a blockchain. Using the following code in which we use “https://fcd.terra.dev/v1/txs/gas_prices”, we can get the gas fee in different tokens, so that we can later estimate the gas fee for our transaction:

from terra_sdk.client.lcd import LCDClient
from terra_sdk.key.mnemonic import MnemonicKey
import requests
import json
terra = LCDClient("https://bombay-lcd.terra.dev/", "bombay-12")
Account1 = MnemonicKey(mnemonic="call oil decrease loud pull indicate diet post
     sign cereal adapt rug reform alcohol math illegal major anchor unhappy govern win
     round ritual various")
Account2 = MnemonicKey()
wallet = terra.wallet(Account1)
fees = requests.get("https://fcd.terra.dev/v1/txs/gas_prices").json()
print(fees)


Result: {'uluna': '0.01133', 'usdr': '0.104938', 'uusd': '0.15', 'ukrw': '170.0', 'umnt': '428.571', 'ueur': '0.125', 'ucny': '0.98', 'ujpy': '16.37', 'ugbp': '0.11', 'uinr': '10.88', 'ucad': '0.19', 'uchf': '0.14', 'uaud': '0.19', 'usgd': '0.2', 'uthb': '4.62', 'usek': '1.25', 'unok': '1.25', 'udkk': '0.9', 'uidr': '2180.0', 'uphp': '7.6', 'uhkd': '1.17', 'umyr': '0.6', 'utwd': '4.0'} And you can see the .json output of the fee in different tokens. Now, it is time to estimate the gas fee using fee = str(float(fees[“uusd”])) + “uusd”

from terra_sdk.client.lcd import LCDClient
from terra_sdk.key.mnemonic import MnemonicKey
import requests
import json
from terra_sdk.core.bank import MsgSend
terra = LCDClient("https://bombay-lcd.terra.dev/", "bombay-12")
Account1 = MnemonicKey(mnemonic="call oil decrease loud pull indicate diet post
     sign cereal adapt rug reform alcohol math illegal major anchor unhappy govern win
     round ritual various")
Account2 = MnemonicKey()
wallet = terra.wallet(Account1)
fees = requests.get("https://fcd.terra.dev/v1/txs/gas_prices").json()
Amount_To_Send = (terra.bank.balance(Account1.acc_address)[0])/10
print(Amount_To_Send)
msg = MsgSend(from_address=Account1.acc_address, 
     to_address=Account2.acc_address, 
     amount=Amount_To_Send )

fee = str(float(fees["uusd"])) + "uusd"
print(fee)


Result: 67360uluna 0.15uusd Swapping Tokens on Terra blockchain: One of the most useful features of Terra blockchain is the ability to swap between different tokens of its chain. For example, here we want to swap our Bombay testnet uluna with test UST. The below script shows how we simply do that. To make sure that the swapping transaction has been successfully completed, we check the balance before and after the transaction.

from terra_sdk.client.lcd import LCDClient
from terra_sdk.key.mnemonic import MnemonicKey
from terra_sdk.core.market import MsgSwap
from terra_sdk.client.lcd.api.tx import CreateTxOptions
terra = LCDClient("https://bombay-lcd.terra.dev/", "bombay-12")
Account1 = MnemonicKey(mnemonic="call oil decrease loud pull indicate diet post
     sign cereal adapt rug reform alcohol math illegal major anchor unhappy govern win
     round ritual various")
msg = MsgSwap(Account1.acc_address, "100uluna", "uusd")
wallet = terra.wallet(Account1)
print(terra.bank.balance(Account1.acc_address)[0])
tx = wallet.create_and_sign_tx(CreateTxOptions(msgs = [msg],
     memo = "Swapping luna for UST",
     gas_prices = "1uluna",
     gas_adjustment = "1.3",
     fee_denoms = ["uluna", "uusd"]
     ))
result = terra.tx.broadcast(tx)
print(result)
print(terra.bank.balance(Account1.acc_address)[0])


Result: 421527uluna,17604uusd BlockTxBroadcastResult(height=8805631, txhash='D87F2D6ACD0EAA919EA6AAE66DC173DCF162A58668560C9284EA4 52D42279125', raw_log='[{"events":[{"type":"burn","attributes": [{"key":"burner","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"100uluna"}]},{"type":"coin_received","attributes": [{"key":"receiver","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"100uluna"}, {"key":"receiver","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"8864uusd"}, {"key":"receiver","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"amount","value":"8820uusd"}, {"key":"receiver","value":"terra1jgp27m8fykex4e4jtt0l7ze8q528ux2lh4zh0f"}, {"key":"amount","value":"44uusd"}]},{"type":"coin_spent","attributes": [{"key":"spender","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"amount","value":"100uluna"}, {"key":"spender","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"100uluna"}, {"key":"spender","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"8820uusd"}, {"key":"spender","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"44uusd"}]},{"type":"coinbase","attributes": [{"key":"minter","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"8864uusd"}]},{"type":"message","attributes": [{"key":"action","value":"/terra.market.v1beta1.MsgSwap"}, {"key":"sender","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"sender","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"sender","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"module","value":"market"}]},{"type":"swap","attributes": [{"key":"offer","value":"100uluna"}, {"key":"trader","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"recipient","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"swap_coin","value":"8820uusd"},{"key":"swap_fee","value":"44uusd"}]}, {"type":"transfer","attributes": [{"key":"recipient","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"sender","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"amount","value":"100uluna"}, {"key":"recipient","value":"terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr"}, {"key":"sender","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"8820uusd"}, {"key":"recipient","value":"terra1jgp27m8fykex4e4jtt0l7ze8q528ux2lh4zh0f"}, {"key":"sender","value":"terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s"}, {"key":"amount","value":"44uusd"}]}]}]', gas_wanted=126031, gas_used=108830, logs=[TxLog(msg_index=0, log='', events=[{'type': 'burn', 'attributes': [{'key': 'burner', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '100uluna'}]}, {'type': 'coin_received', 'attributes': [{'key': 'receiver', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '100uluna'}, {'key': 'receiver', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '8864uusd'}, {'key': 'receiver', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'amount', 'value': '8820uusd'}, {'key': 'receiver', 'value': 'terra1jgp27m8fykex4e4jtt0l7ze8q528ux2lh4zh0f'}, {'key': 'amount', 'value': '44uusd'}]}, {'type': 'coin_spent', 'attributes': [{'key': 'spender', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'amount', 'value': '100uluna'}, {'key': 'spender', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '100uluna'}, {'key': 'spender', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '8820uusd'}, {'key': 'spender', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '44uusd'}]}, {'type': 'coinbase', 'attributes': [{'key': 'minter', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '8864uusd'}]}, {'type': 'message', 'attributes': [{'key': 'action', 'value': '/terra.market.v1beta1.MsgSwap'}, {'key': 'sender', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'sender', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'sender', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'module', 'value': 'market'}]}, {'type': 'swap', 'attributes': [{'key': 'offer', 'value': '100uluna'}, {'key': 'trader', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'recipient', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'swap_coin', 'value': '8820uusd'}, {'key': 'swap_fee', 'value': '44uusd'}]}, {'type': 'transfer', 'attributes': [{'key': 'recipient', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'sender', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'amount', 'value': '100uluna'}, {'key': 'recipient', 'value': 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'}, {'key': 'sender', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '8820uusd'}, {'key': 'recipient', 'value': 'terra1jgp27m8fykex4e4jtt0l7ze8q528ux2lh4zh0f'}, {'key': 'sender', 'value': 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'}, {'key': 'amount', 'value': '44uusd'}]}], events_by_type={'burn': {'burner': ['terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'], 'amount': ['100uluna']}, 'coin_received': {'receiver': ['terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s', 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s', 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr', 'terra1jgp27m8fykex4e4jtt0l7ze8q528ux2lh4zh0f'], 'amount': ['100uluna', '8864uusd', '8820uusd', '44uusd']}, 'coin_spent': {'spender': ['terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr', 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s', 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s', 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'], 'amount': ['100uluna', '100uluna', '8820uusd', '44uusd']}, 'coinbase': {'minter': ['terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'], 'amount': ['8864uusd']}, 'message': {'action': ['/terra.market.v1beta1.MsgSwap','sender': ['terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr', 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s', 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'], 'module': ['market']}, 'swap': {'offer': ['100uluna'], 'trader': ['terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'], 'recipient': ['terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr'], 'swap_coin': ['8820uusd'], 'swap_fee': ['44uusd']}, 'transfer': {'recipient': ['terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s', 'terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr', 'terra1jgp27m8fykex4e4jtt0l7ze8q528ux2lh4zh0f'], 'sender': ['terra16q9hkwe9t5jac20e08tz4qwflwkjxymw9qm3kr', 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s', 'terra1untf85jwv3kt0puyyc39myxjvplagr3wstgs5s'], 'amount': ['100uluna', '8820uusd', '44uusd']}})], code=0, codespace='', info=None, data=None, timestamp=None) 295396uluna,26424uusd And as you can see we have got some uusd in exchange for ulunas.

Learned to Work with Terra Network Using Terra Python SDK

In the last 3 articles, we have learned how to work with Terra Testnet and we have connected to Bombay-12 test network in addition to creating a wallet, an account, creating and signing a transaction, transferring money, swapping tokens and estimating gas fee for a transaction.

Download this Article in PDF format

metaverse

Curious about What We’re Up To?

In Arashtad we provide custom services in various design and development fields. 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications are just some of our expertise.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Request a Quote
Blockchain Development

Interacting with Local Terra Smart Contracts Using Python

In this article, we first connect to local Terra using Docker. Then we try to interact with local Terra smart contracts using Python scripts and at the same time use the CosmWasm functions inside LocalTerra\src folder written in Rust programming language. Finally, we will manage to interact with it.

Connecting to Local Terra with Docker with Python

First off, To start our interaction with local Terra, we need to make sure docker is running: sudo usermod -aG docker $USER Make sure you sign in to your docker profile to be able to easily log in using the following command docker login. docker-compose up Result: terrad_1 | 8:48PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"3F119B6520D17CCC29A23F016B9E40151E3A5784A3F83D65BD0DD2D282FB1A4F"," parts": {"hash":"5928F59D217852E3722E46A9134D40F9329D540555EAD1DAE4B5EFA8A41E88D4"," total":1}},"height":1948,"pol_round":- 1,"round":0,"signature":"cYh80NzZezistnyNzEP2yFDnsoc7JOB+/8J1wnell- Gq76IE3qvH9jU2KvQx6u4y+A9xYrIQEJonzxa//vOZfBw==","timestamp":"2022- 05-08T20:48:12.151658182Z"} terrad_1 | 8:48PM INF received complete proposal block hash=3F119B6520D17CCC29A23F016B9E40151E3A5784A3F83D65BD0DD2D282FB1A4F height=1948 module=consensus terrad_1 | 8:48PM INF finalizing commit of block hash=3F119B6520D17CCC29A23F016B9E40151E3A5784A3F83D65BD0DD2D282FB1A4F height=1948 module=consensus num_txs=0 root=332F90B4F0706BCB055547- FA8443A1F623FC3DAE4043162730F59D15612D5FAE terrad_1 | 8:48PM INF minted coins from module account amount=226578914uluna from=mint module=x/bank terrad_1 | 8:48PM INF executed block height=1948 module=state num_invalid_txs=0 num_valid_txs=0 terrad_1 | 8:48PM INF commit synced commit=436F6D6D697449447B5B393820323020343020323436203134362031303 32036342032313320393520313132203131372031353920393320313536203231312 03632203332203836203137203132352032333920323131203533203337203139352 03637203739203635203830203235322032203138355D3A3739437D terrad_1 | 8:48PM INF committed state app_hash=621428F6926740D55F70759F5D9CD33E2056117DEFD33525C3434F41 50FC02B9 height=1948 module=state num_txs=0 terrad_1 | 8:48PM INF indexed block height=1948 module=txindex terrad_1 | 8:48PM INF Timed out dur=4990.525864 height=1949 module=consensus round=0 step=1 terrad_1 | 8:48PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"43FEBF954F5FFEA7350E4D8D38FCCA39E439F2D3026ADE9ED3E21D2706AC2596"," parts": {"hash":"112F8B0B6AA51D1D0FCCE2D049F739EC7FEB9B6C2092B1141A55E88AC453FF57"," total":1}},"height":1 949,"pol_round":-1,"round":0,"signature":"tuusiNhk5kRrKpLykCNWb/4b1nrfk22n- RCyaLh650ouCugzK7rGOHq+4eQvBu3HQ3/Y4mF/RFbA67tqO2+nrBQ==","timestamp":" 2022-05-08T20:48:17.164422986Z"} terrad_1 | 8:48PM INF received complete proposal block hash=43FEBF954F5FFEA7350E4D8D38FCCA39E439F2D3026ADE9ED3E21D2706AC2596 height=1949 module=consensus terrad_1 | 8:48PM INF finalizing commit of block hash=43FEBF954F5FFEA7350E4D8D38FCCA39E439F2D3026ADE9ED3E21D2706AC2596 height=1949 module=consensus num_txs=0 root=621428F6926740D55F70759F5D9CD33E2056117DEFD33525C3434F4150FC 02B9 terrad_1 | 8:48PM INF minted coins from module account amount=226578918uluna from=mint module=x/bank terrad_1 | 8:48PM INF executed block height=1949 module=state num_invalid_txs=0 num_valid_txs=0 terrad_1 | 8:48PM INF commit synced commit=436F6D6D697449447B5B352032323420323437203734203133382033312 03231322035322032343220313637203134342037382038332036392032303720313 93920313335203337203230342032382033362031353320333920313930203232382 0333020313434203538203138342032343620313835203230375D3A3739447D terrad_1 | 8:48PM INF committed state app_hash=05E0F74A8A1FD434F2A7904E5345CFC78725CC1C249927BEE41E903AB8F6B9CF height=1949 module=state num_txs=0 terrad_1 | 8:48PM INF indexed block height=1949 module=txindex terrad_1 | 8:48PM INF Timed out dur=4987.072234 height=1950 module=consensus round=0 step=1 terrad_1 | 8:48PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"5E1C9EA0F662335- DAD4936E50C904AF1FF3D8BBF1083A967A8C4D8EA762942F7","parts": {"hash":"0C3DA2F2B1CDCDF56C432F85E3EE1B0A21064EF69E4DB2D5CAFBE0609FDDE699"," total":1}},"height":1950,"pol_round":- 1,"round":0,"signature":"ZQeA6WQaamDpW07PD64ezc+U1vs/IW3XzMp/yShIIMbJChJFiOJwDB+ dtipWPORdKHNrWVVFlei4drGlx70iCg==","timestamp":"2022- 05-08T20:48:22.181798507Z"} terrad_1 | 8:48PM INF received complete proposal block hash=5E1C9EA0F662335DAD4936E50C904AF1FF3D8BBF1083A967A8C4D8EA762942F7 height=1950 module=consensus terrad_1 | 8:48PM INF finalizing commit of block hash=5E1C9EA0F662335- DAD4936E50C904AF1FF3D8BBF1083A967A8C4D8EA762942F7 height=1950 module=consensus num_txs=0 root=05E0F74A8A1FD434F2A7904E5345CFC78725CC1C249927BEE41E903AB8F6B9CF terrad_1 | 8:48PM INF minted coins from module account amount=226578923uluna from=mint module=x/bank terrad_1 | 8:48PM INF executed block height=1950 module=state num_invalid_txs=0 num_valid_txs=0 terrad_1 | 8:48PM INF commit synced commit=436F6D6D697449447B5B323133203133342032343920393320313830203 13234203136342033372033342032313620313733203236203734203133322032343 22032352031383520313731203230322037302032333520313635203339203235352 0323532203233302035312037203134203135203537203130335D3A3739457D terrad_1 | 8:48PM INF committed state app_hash=D586F95DB47- CA42522D8AD1A4A84F219B9ABCA46EBA527FFFCE633070E0F3967 height=1950 module=state num_txs=0 terrad_1 | 8:48PM INF indexed block height=1950 module=txindex terrad_1 | 8:48PM INF Timed out dur=4983.73452 height=1951 module=consensus round=0 step=1 terrad_1 | 8:48PM INF received proposal module=consensus proposal={"Type":32,"block_id":{"hash":"2D43F5FF6625BD376D71F60269615A5E556BEFF85FC44461B05AF17E54958490"," parts": {"hash":"8377DFE5E66B80B3B9DA5894F5778DF1585741A1C718626AE14167158F7A5DAE"," total":1}},"height":1951,"pol_round ":-1,"round":0,"signature":"Xtc1Wggm2dD/n3YNFjvykNUmXFkN9gNuVL/ yKab4js2DMl2xGJ7ZAjDW/0RceJIBle9SWX13smUamxfsd2wbCQ==","timestamp":" 2022-05-08T20:48:27.197935715Z"} terrad_1 | 8:48PM INF received complete proposal block hash=2D43F5FF6625BD376D71F60269615A5E556BEFF85FC44461B05AF17E54958490 height=1951 module=consensus terrad_1 | 8:48PM INF finalizing commit of block hash=2D43F5FF6625BD376D71F60269615A5E556BEFF85FC44461B05AF17E54958490 height=1951 module=consensus num_txs=1 root=D586F95DB47- CA42522D8AD1A4A84F219B9ABCA46EBA527FFFCE633070E0F3967 terrad_1 | 8:48PM INF minted coins from module account amount=226578928uluna from=mint module=x/bank terrad_1 | 8:48PM INF executed block height=1951 module=state num_invalid_txs=0 num_valid_txs=1

Interacting with Local Terra Using Python: Storing the Contract

Then you can create a new folder inside the local Terra directory and name it testing. Then create another new folder inside the testing folder and name it artifacts. From the artifacts folder of some of the created projects in the local Terra directory (Like NewProjectName), copy the .wasm file and paste it into the new artifacts folder. After that create a new python file inside of the testing folder and name it contract.py. You can write the following code in the contract.py.
		
from terra_sdk.client.localterra import LocalTerra
from terra_sdk.util.contract import read_file_as_b64
from terra_sdk.core.fee import Fee
from terra_sdk.client.lcd.api.tx import CreateTxOptions
from terra_sdk.core.wasm import MsgStoreCode

lt = LocalTerra()
deployer = lt.wallets["test1"]

def store_contract(contract_name:str) -> str:
	contract_bytes = read_file_as_b64(f"artifacts/{contract_name}.wasm")
	store_code = MsgStoreCode(
	deployer.key.acc_address,
	contract_bytes
	)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [store_code],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	print(result)

store_contract("Testing")

		
	
In the above code, at first, we connect to local Terra, then we create a wallet for the deployer of the contract (which is us). After that, we create a function called store contract, inside which we read the .wasm file in the artifacts folder and store it in a variable called contract_bytes. Then we create a message called the MsgStoreCode using the contract_bytes variable and after that, we create and sign a transaction using the store_code message. Finally, we broadcast the transaction and use the store_contract function with the name of the contract called Testing (The name of our project folder).

And run it in another bash, so as not to interfere with the docker running the local Terra, use the following command to start our first interaction with Terra network. python3 contract.py Result: BlockTxBroadcastResult(height=1626, txhash='1143BD1F71A9E951F3B99D7EEE4BF0EE304F0C6C4A63DADA96F7CE243F25F39A', raw_log='[{"events":[{"type":"message","attributes": [{"key":"action","value":"/terra.wasm.v1beta1.MsgStoreCode"}, {"key":"module","value":"wasm"}]},{"type":"store_code","attributes": [{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}, {"key":"code_id","value":"1"}]}]}]', gas_wanted=4000000, gas_used=1606062, logs=[TxLog(msg_index=0, log='', events=[{'type': 'message', 'attributes': [{'key': 'action', 'value': '/terra.wasm.v1beta1.MsgStoreCode'}, {'key': 'module', 'value': 'wasm'}]}, {'type': 'store_code', 'attributes': [{'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}, {'key': 'code_id', 'value': '1'}]}], events_by_type={'message': {'action': ['/terra.wasm.v1beta1.MsgStoreCode'], 'module': ['wasm']}, 'store_code': {'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'], 'code_id': ['1']}})], code=0, codespace='', info=None, data=None, timestamp= None) In the above dictionary, you can see data like txhash (transaction hash), code_id, sender of the store_code, and so on.

Interacting with Local Terra Using Python: Getting the Code ID

Now, let’s get the code id of the transaction using the very same code and making some small changes:
		
from terra_sdk.client.localterra import LocalTerra
from terra_sdk.util.contract import read_file_as_b64,get_code_id
from terra_sdk.core.fee import Fee
from terra_sdk.client.lcd.api.tx import CreateTxOptions
from terra_sdk.core.wasm import MsgStoreCode

lt = LocalTerra()
deployer = lt.wallets["test1"]

def store_contract(contract_name:str) -> str:
	contract_bytes = read_file_as_b64(f"artifacts/{contract_name}.wasm")
	store_code = MsgStoreCode(
	deployer.key.acc_address,
	contract_bytes
	)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [store_code],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	code_id = get_code_id(result)
	return(code_id)

print(store_contract("Testing"))

		
	
We have written the very same code as the previous one with the difference that this time we have specifically returned the code_id in the dictionary and have printed it. Now, let’s run the code using the following command on the terminal, and remember not to lose your connection with local Terra (keep the docker up and running). python3 contract.py Result: 3 Let us try another time: python3 contract.py Result: 4 And one more time to make sure everything works as it should: python3 contract.py Result: 5

Sending JSON Messages to Local Terra Smart Contracts Using Python

The following article is the continuation of the previous one and here we try to interact with local Terra smart contracts using python scripts and at the same time using the CosmWasm functions inside LocalTerra\src folder written in Rust programming language. We will instantiate from the contract and execute it by sending JSON messages to Local Terra smart contracts.

Instantiating form the Contract of the LocalTerra

In the following code, we use the MsgInstantiateContract from terra_sdk.core.wasm to instantiate from the contract, using the code_id and return the contract address:
		
from terra_sdk.client.localterra import LocalTerra
from terra_sdk.util.contract import read_file_as_b64,get_code_id, get_contract_address
from terra_sdk.core.fee import Fee
from terra_sdk.client.lcd.api.tx import CreateTxOptions
from terra_sdk.core.wasm import MsgStoreCode,MsgInstantiateContract

lt = LocalTerra()
deployer = lt.wallets["test1"]

def store_contract(contract_name:str) -> str:
	contract_bytes = read_file_as_b64(f"artifacts/{contract_name}.wasm")
	store_code = MsgStoreCode(
		deployer.key.acc_address,
		contract_bytes
		)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [store_code],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	code_id = get_code_id(result)
	return(code_id)

def instantiate_contract(code_id: str,init_msg)-> str:
	instantiate = MsgInstantiateContract(
		deployer.key.acc_address,
		deployer.key.acc_address,
		code_id = code_id,
		init_msg = init_msg,
		)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [instantiate],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	contract_address = get_contract_address(result)
	return contract_address

code_id = store_contract("Testing")
contract_address = instantiate_contract(code_id,{"count": 15})
print(code_id, contract_address)

		
	
The goal of the above code is to instantiate from the contract using the code_id of the store_contract transaction. To do so, we define a function called instantiate_contract. Inside this function, we create a message with MsgInstantiateContract function. Then we will create and sign the transaction using the created message. Finally, we will broadcast the transaction and return the contract address. Notice that the function that we have defined has 2 attributes: code_id and init_msg. When we call this function, we can enter the code_id we have got from the output of store_contract. For init_msg, we enter the {"count": number} in which number can be any unsigned integer number. In the end, we print the code_id with the contract address which is the output of the instantiate_contract function.

Now, let’s try our script and see if it prints out the code_id and the contract address: python3 contract.py Result: 11 terra1wkgucw0zply6e5c4h30a4z5qljhazepw4jpf2s Try another time: python3 contract.py Result: 12 terra1mhf9cr9f70rd052rptnevcje2puq6heekrxdwa As you can see the code_id has incremented.

And another time: python3 contract.py Result: 13 terra1uw258kwqftsywjfv972y4d3f4mn0hrlkpt7y3y

Executing the Contract

Now, using the MsgExecuteContract, contract address, and account address, we execute the contract inside the local Terra directory.
		
from terra_sdk.client.localterra import LocalTerra
from terra_sdk.util.contract import read_file_as_b64,get_code_id, get_contract_address
from terra_sdk.core.fee import Fee
from terra_sdk.client.lcd.api.tx import CreateTxOptions
from terra_sdk.core.wasm import MsgStoreCode,MsgInstantiateContract, MsgExecuteContract

lt = LocalTerra()
deployer = lt.wallets["test1"]

def store_contract(contract_name:str) -> str:
	contract_bytes = read_file_as_b64(f"artifacts/{contract_name}.wasm")
	store_code = MsgStoreCode(
	deployer.key.acc_address,
	contract_bytes
	)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [store_code],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	code_id = get_code_id(result)
	return(code_id)

def instantiate_contract(code_id: str,init_msg)-> str:
	instantiate = MsgInstantiateContract(
		deployer.key.acc_address,
		deployer.key.acc_address,
		code_id = code_id,
		init_msg = init_msg,
		)
	tx = deployer.create_and_sign_tx(CreateTxOptions(
		msgs = [instantiate],
		fee = Fee(4000000,"10000000uluna")
		))
	result = lt.tx.broadcast(tx)
	contract_address = get_contract_address(result)
	return contract_address

def execute_contract(sender,contract_addr: str, execute_msg):
	execute = MsgExecuteContract(
	sender = sender.key.acc_address, contract = contract_addr,execute_msg = execute_msg)
	tx = sender.create_and_sign_tx(CreateTxOptions(
		msgs = [execute],
		fee = Fee(4000000,"10000000uluna")
		))
	)
	result = lt.tx.broadcast(tx)
	return result

code_id = store_contract("Testing")
contract_address = instantiate_contract(code_id,{"count": 15})
execute = execute_contract(deployer,contract_address,{"increment":{}})
execute1 = execute_contract(deployer,contract_address,{"increment":{}})
execute2 = execute_contract(deployer,contract_address,{"increment":{}})
execute3 = execute_contract(deployer,contract_address,{"increment":{}})
execute4 = execute_contract(deployer,contract_address,{"increment":{}})

print(execute1)
print(lt.wasm.contract_query(contract_address,{"get_count":{}}))
print(code_id, contract_address)
		
	
In the above Python script, we will use the execute_contract next to store_contract and instantiate_contract functions. In the execute function, we have done the 3 main jobs we did for other functions, creating the message, creating and signing the transaction , and broadcasting it. The execute function has 3 attributes:
  1. 1. The sender (deployer of the contract).
  2. 2. The contract address that we have got from instantiate_contract function.
  3. 3. The execute message which can be increment, reset ,etc.
Outside the execute function, we call it 5 times with the increment message. And to see the results, we print the second execution and also the query result of the get_count message which is going to check if we have successfully executed the contract 5 times. If so, the result must show that the count = 20. (15 +1+1+1+1+1 = 20).

Now, let’s see if our code prints out the result of local Terra contract execution, as well as the code_id, query result, and the contract address: python3 contract.py Result: BlockTxBroadcastResult(height=8030, txhash='F51A179323A3489F4629FB0ECA4B83C64237961F9244409EFBDBF88EFC8CFA97', raw_log='[{"events": [{"type":"execute_contract","attributes":[{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}, {"key":"contract_address","value":"terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz"}]},{" type":"from_contract","attributes":[{"key":"contract_ address","value":"terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz"}, {"key":"method","value":"try_increment"}]},{"type":"message","attributes": [{"key":"action","value":"/terra.wasm.v1beta1.MsgExecuteContract"},{"key":"module"," value":"wasm"},{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}]},{" type":"wasm","attributes": [{"key":"contract_address","value":"terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz"},{" key":"method","value":"try_increment"}]}]}]', gas_wanted=4000000, gas_used=102492, logs=[TxLog(msg_index=0, log='', events=[{'type': 'execute_contract', 'attributes': [{'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}, {'key': 'contract_address', 'value': 'terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'}]}, {'type': 'from_contract', 'attributes': [{'key': 'contract_address', 'value': 'terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'}, {'key': 'method', 'value': 'try_increment'}]}, {'type': 'message', 'attributes': [{'key': 'action', 'value': '/terra.wasm.v1beta1.MsgExecuteContract'}, {'key': 'module', 'value': 'wasm'}, {'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}]}, {'type': 'wasm', 'attributes': [{'key': 'contract_address', 'value': 'terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'}, {'key': 'method', 'value': 'try_increment'}]}], events_by_type={'execute_contract': {'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'], 'contract_address': ['terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz']}, 'from_contract': {'contract_address': ['terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'], 'method': ['try_increment']}, 'message': {'action': ['/terra.wasm.v1beta1.MsgExecuteContract'], 'module': ['wasm'], 'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v']}, 'wasm': {'contract_address': ['terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz'], 'method': ['try_increment']}})], code=0, codespace='', info=None, data=None, timestamp=None) {'count': 20} 17 terra1hteg5pjt0r27589yd67jwhq8an4j3w4u02qfmz Notice that it takes a few seconds for the contract to be instantiated and the transaction to be executed. As we have determined the count = 15 in the instantiate_contract function and executed the contract with the increment message 5 times, The result of the query count will be 20.

Try one more time, and you will see the same result with the code_id incremented. python3 contract.py Result: BlockTxBroadcastResult(height=8269, txhash='73C112C7B0DA968209310068DFCA9C26378A47EA383C3A4DBE350DCFE54079E8', raw_log='[{"events": [{"type":"execute_contract","attributes":[{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}, {"key":"contract_address","value":"terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra"}]},{" type":"from_contract","attributes": [{"key":"contract_address","value":"terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra"},{" key":"method","value":"try_increment"}]},{"type":"message","attributes": [{"key":"action","value":"/terra.wasm.v1beta1.MsgExecuteContract"},{"key":"module"," value":"wasm"},{"key":"sender","value":"terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"}]},{" type":"wasm","attributes": [{"key":"contract_address","value":"terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra"},{" key":"method","value":"try_increment"}]}]}]', gas_wanted=4000000, gas_used=102492, logs=[TxLog(msg_index=0, log='', events=[{'type': 'execute_contract', 'attributes': [{'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}, {'key': 'contract_address', 'value': 'terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'}]}, {'type': 'from_contract', 'attributes': [{'key': 'contract_address', 'value': 'terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'}, {'key': 'method', 'value': 'try_increment'}]}, {'type': 'message', 'attributes': [{'key': 'action', 'value': '/terra.wasm.v1beta1.MsgExecuteContract'}, {'key': 'module', 'value': 'wasm'}, {'key': 'sender', 'value': 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'}]}, {'type': 'wasm', 'attributes': [{'key': 'contract_address', 'value': 'terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'}, {'key': 'method', 'value': 'try_increment'}]}], events_by_type={'execute_contract': {'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v'], 'contract_address': ['terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra']}, 'from_contract': {'contract_address': ['terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'], 'method': ['try_increment']}, 'message': {'action': ['/terra.wasm.v1beta1.MsgExecuteContract'], 'module': ['wasm'], 'sender': ['terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v']}, 'wasm': {'contract_address': ['terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra'], 'method': ['try_increment']}})], code=0, codespace='', info=None, data=None, timestamp=None) {'count': 20} 18 terra1wfvvdh7vtq82xz3x6h63ul9xn8hxh5xhkwl5ra

Wrapping Up

In this article, we have managed to connect to local Terra using Docker and interact with it by the means of Python scripts. In detail, we have managed to store the contract and get the code_id of the transaction. In the next parts, we will focus on the other functions such as instantiating and executing the contract to complete the process of interacting with Terra smart contracts.

Finally, we have managed to interact with local Terra using the Python scripts. At first, we connected to local Terra using Docker. Then, we created a wallet, that, stored our contract, instantiated from the stored contract using the code_id, and executed it.

Download this Article in PDF format

3d websites

Care to Know Us More?

In Arashtad, we have gathered a professional team of developers who are working in fields such as 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications as well as blockchain developemnet.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Fill in the Form
Blockchain Development

Creating NFTs with Smart Contracts and Python Brownie

In our previous article about the Non-Fungible tokens, we explained about the methods of ERC-721. In this tutorial, we are going to use those methods to write the simple_collectible.sol smart contract and deploy it on the Rinkeby chain. In other words, we are going to create NFTs with Python Brownie tools. But before that, we also want to connect our test wallet to https://testnets.opensea.io in order to create an account in OpenSea which is a marketplace for NFTs.

Creating NFTs with Python Brownie

In this tutorial, we are going to use those methods to write the simple_collectible.sol smart contract and deploy it on the Rinkeby chain. But before that, we also want to connect our test wallet to https://testnets.opensea.io in order to create an account in OpenSea which is a marketplace for NFTs.

nft

To begin writing our smart contract, we have 2 options:

1. Use Brownie not mix by typing the below command in the terminal: brownie bake nft-mix And we will see all the necessary files with scripts in them are created.

2. start Brownie from scratch: mkdir simpleNFT cd simpleNFT brownie init To better understand NFT smart contracts, we start from scratch. Now, let’s begin writing the smart contract of our NFT:

// SPDX-License-Identifier: MIT
pragma solidity 0.6.6;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract SimpleCollectible is ERC721 {
	uint256 public tokenCounter;
	constructor () public ERC721 ("Dogie", "DOG"){
		tokenCounter = 0;
}

	function createCollectible(string memory tokenURI) public returns (uint256){
		uint256 newTokenId = tokenCounter;
		_safeMint(msg.sender, newTokenId);
		_setTokenURI(newTokenId, tokenURI);
		tokenCounter = tokenCounter + 1;
		return newTokenId;
	}
}


Notice that we have used safeMint and setTokenURI from "@openzeppelin/contracts/token/ERC721/ERC721.sol" that we had imported at the beginning of our contract and we should also add it to the dependencies of the brownie-config.yaml file. Now, let’s deploy our contract using the below scripts at deploy_and_create.py file:

sample_token_uri = "ipfs://Qmd9MCGtdVz2miNumBHDbvj8bigSgTwnr4SbyH6DNnpWdt?filename=0-PUG.json"
OPENSEA_URL = "https://testnets.opensea.io/assets/{}/{}"
def main():
    account = get_account()
    simple_collectible = SimpleCollectible.deploy({"from":account})
    tx = simple_collectible.createCollectible(sample_token_uri,{"from": account})
    tx.wait(1)
    print(f"Awesome, you can{OPENSEA_URL.format(simple_collectible.address,
    	simple_collectible.tokenCounter() - 1)}")

Also, don’t forget the helpful_scripts.py file in the scripts folder:

from brownie import accounts, network, config, Contract
from web3 import Web3

LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["hardhat", "development", "ganache", "mainnet-fork"]

def get_account(index=None, id=None):
        if index:
		return accounts[index]
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		return accounts[0]
	if id:
		return accounts.load(id)
	return accounts.add(config["wallets"]["from_key"])


The complete brownie-config.yaml file:

dependencies:
	- OpenZeppelin/[email protected]
	- smartcontractkit/[email protected]

compiler:
	solc:
		remappings:
			- '@openzeppelin=OpenZeppelin/[email protected]'
			- '@chainlink=smartcontractkit/[email protected]'
dotenv: .env
wallets:
	from_key: ${PRIVATE_KEY}
networks:
	development:
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000
	rinkeby:
		vrf_coordinator: '0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B'
		link_token: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709'
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000 # 0.1



And our .env file:

export PRIVATE_KEY=' Paste your private key here'
export WEB3_INFURA_PROJECT_ID='INFURA ID'
export ETHERSCAN_TOKEN=' Your API Key goes here'


Now that we have set everything up, it is time to compile our project: brownie compile Result: Brownie v1.18.1 - Python development framework for Ethereum Compiling contracts... Solc version: 0.6.6 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - OpenZeppelin/[email protected]/ERC165 - OpenZeppelin/[email protected]/IERC165 - OpenZeppelin/[email protected]/SafeMath - OpenZeppelin/[email protected]/ERC721 - OpenZeppelin/[email protected]/IERC721 - OpenZeppelin/[email protected]/IERC721Enumerable - OpenZeppelin/[email protected]/IERC721Metadata - OpenZeppelin/[email protected]/IERC721Receiver - OpenZeppelin/[email protected]/Address - OpenZeppelin/[email protected]/Context - OpenZeppelin/[email protected]/EnumerableMap - OpenZeppelin/[email protected]/EnumerableSet - OpenZeppelin/[email protected]/Strings - SimpleCollectible Project has been compiled. Build artifacts saved at /home/mohamad/NFT-Simple/build/contracts And deploy our NFT smart contract: brownie run scripts/deploy_and_create --network rinkeby Result: Brownie v1.18.1 - Python development framework for Ethereum NftSimpleProject is the active project. Running 'scripts/deploy_and_create.py::main'... Transaction sent: 0x4e328bf153e05a1fc06fd4060ca447ede90e7f8337e737c16508674cd6c7a951 Gas price: 1.000000061 gwei Gas limit: 2017295 Nonce: 67 SimpleCollectible.constructor confirmed Block: 10499268 Gas used: 1833905 (90.91%) SimpleCollectible deployed at: 0x657191536F5C1ec2EcfA1bD9bD4e14Ca8047F7bc Transaction sent: 0xfba6f759c7619d09a1339c975c51073846d2409c473c4f65889870cd9eae48a2 Gas price: 1.000000061 gwei Gas limit: 279109 Nonce: 68 SimpleCollectible.createCollectible confirmed Block: 10499269 Gas used: 253736 (90.91%) SimpleCollectible.createCollectible confirmed Block: 10499269 Gas used: 253736 (90.91%) Awesome, you can view your NFT at https://testnets.opensea.io/assets/0x657191536F5C1ec2EcfA1bD9bD4e14Ca8047F7bc/0 As you can see, we have successfully created our NFT and we can see it using the link given in the terminal.

Wrapping Up

In this tutorial, we have managed to write the different parts of the simplest NFT project, from the smart contract to the deployments and dependencies, configurations, environment variables, and so on. In the end, we deployed the contract on the Rinkeby test network.

Download this Article in PDF format

metaverse

We Are Working on Metaverses

Metaverses, 3d modeling, 3d application, 3d websites, and even animations are just some of our vase expertise.

Arashtad Serivces
Tell us about your ideas
Fill in the Form
Blockchain Development

An Introduction to the Non-Fungible Tokens (NFTs)

In this tutorial, we are going to introduce the Non-Fungible Tokens or ERC-721 standard. In addition to that, we are going to get familiar with the interface of ERC-721 so that we can later use them in NFT deployments. Every NFT project also needs an ERC-721 JSON metadata that we are going to introduce in different sections.

Introduction to Non-Fungible Tokens

Non-Fungible or ERC-721 tokens are the kind of tokens that have unique features in other words, they cannot be exchanged by one another because they are all identified as unique. The use cases for NFTs are platforms that offer collectible items (like artworks), lottery tickets, numbered seats for cinemas, concerts, access keys, and so on.

ERC-721 Interface

As mentioned earlier, the NFTs follow the ERC-721 standard. Here we take a look at the ERC-721 interface, which we are going to use in the next parts of our NFT tutorials when we want to write a contract for our collectibles:

pragma solidity ^ 0.4.20;

interface ERC721 {
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
}


The above event called Transfer emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are created (`from` == 0) and destroyed (`to` == 0).
Exception: during contract creation, any number of NFTs may be created and assigned without emitting Transfer. At the time of any transfer, the approved address for that NFT (if any) is reset to none.

event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);


The above Approval event emits when the approved address for an NFT is changed or reaffirmed. The zero address indicates there is no approved address. When a Transfer event emits, this also indicates that the approved address for that NFT (if any) is reset to none.

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);


ApprovalForAll event emits when an operator is enabled or disabled for an owner. The operator can manage all NFTs of the owner.

function balanceOf(address _owner) external view returns (uint256);


The above function counts all NFTs assigned to an owner. NFTs assigned to the zero address is considered invalid, and this function throws for queries about the zero address. This function returns the number of NFTs owned by `_owner`, possibly zero.
_owner = An address for whom to query the balance

function ownerOf(uint256 _tokenId) external view returns (address);


The above function finds the owner of an NFT. The NFTs that are assigned to zero address are considered invalid, and queries about them do throw. _tokenId is the identifier for an NFT. The function returns the address of the owner of the NFT.

function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;


The above function transfers the ownership of an NFT from one address to another address. It throws an error in the following cases:
  1. `msg.sender` is not the current owner, an authorized operator, or the approved address for this NFT.
  2. `_from` is not the current owner.
  3. `_to` is the zero address.
  4. `_tokenId` is not a valid NFT.
When transfer is complete, this function checks if `_to` is a smart contract (code size > 0). If so, it calls `onERC721Received` on `_to` and throws an error if the return value is not:

`bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.


The attributes of the function are as follows:
  1. _from The current owner of the NFT
  2. _to The new owner
  3. _tokenId The NFT to transfer
  4. _data Additional data with no specified format, sent in the call to `_to`

function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;


The above function transfers the ownership of an NFT from one address to another. This function works the same as the previous one with an extra data parameter, except that this function just sets data to “”.
The attributes of this function are as follows:
  1. _from The current owner of the NFT
  2. _to The new owner
  3. _tokenId The NFT to transfer

function transferFrom(address _from, address _to, uint256 _tokenId) external payable;


The above function transfers ownership of an NFT. The caller is responsible to confirm that `_to` is capable of receiving NFTs otherwise they may be permanently lost. It throws an error if:
  1. `msg.sender` is not the current owner, an authorized operator, or the approved address for this NFT.
  2. `_from` is not the current owner.
  3. `_to` is the zero address.
  4. `_tokenId` is not a valid NFT.
The attributes of this function are:
  1. _from The current owner of the NFT
  2. _to The new owner
  3. _tokenId The NFT to transfer

function approve(address _approved, uint256 _tokenId) external payable;


The above function changes or reaffirms the approved address for an NFT. The zero address indicates there is no approved address. It throws an error if:
`msg.sender` is not the current NFT owner or an authorizedoperator of the current owner.
The attributes are:
  1. _approved: The new approved NFT controller
  2. _tokenId: The NFT to approve

function setApprovalForAll(address _operator, bool _approved) external;


The above function enables or disables the approval for a third party (“operator”) to manage all of `msg.sender`‘s assets. Emits the ApprovalForAll event. The contract MUST allow multiple operators per owner.
The attributes are:
  1. _operator: Address to add to the set of authorized operators
  2. _approved: True if the operator is approved, false to revoke approval

function getApproved(uint256 _tokenId) external view returns (address);


The above function gets the approved address for a single NFT. Throws error if `_tokenId` is not a valid NFT.
The function attributes are:
    1. _tokenId The NFT to find the approved address for
The function returns:
The approved address for this NFT or the zero address if there is none otherwise.

function isApprovedForAll(address _owner, address _operator) external view returns (bool);


The above function queries if an address is an authorized operator for another address. The parameters of this function are:
  1. _owner: The address that owns the NFTs
  2. _operator: The address that acts on behalf of the owner
The function returns:
True if `_operator` is an approved operator for `_owner`, false.

interface ERC165 {
            function supportsInterface(bytes4 interfaceID) external view returns (bool);
}


The above function is related to the ERC-165 interface and queries if a contract implements an interface. Interface identification is specified in ERC-165. This function uses less than 30,000 gas. The attributes of this function are:
    1. Param interfaceID The interface identifier, as specified in ERC-165.
The function returns:
`true` if the contract implements `interfaceID` and `interfaceID` is not 0xffffffff, `false` otherwise.

ERC-721 Metadata

We also have an ERC721 JSON metadata of each NFT in the following format which specifies the features of the NFT. And we are going to use it in our future NFT projects.

{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents"
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to                                                 
                         which this NFT represents. Consider making any images at a width between 
                         320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        }
    }
}


Last Consideration

In this article, we have introduced the ERC-721 standard for NFTs (Non-fungible tokens). Furthermore, we have taken a deeper look at the ERC-721 interface functions and attributes in addition to its JSON metadata format.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we’re working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

Lottery Project Compiling: A Complete Tutorial

In this tutorial, We are going to finally compile the lottery project. After making sure that it has been successfully compiled, we will run all the written tests to make sure everything works correctly in the contract. In the end, we will deploy the Decentralized Application on the Rinkeby test network. To be able to interact with the deployed contract, we will get some test Link tokens from Rinkeby Faucet.

Compiling the Lottery Project Dapp

In this part, we are going to finally compile, run and test our Lottery Dapp. brownie compile The result should be: Brownie v1.18.1 - Python development framework for Ethereum 1.33MiB [00:03, 402kiB/s] New compatible solc version available: 0.7.0 Compiling contracts... Solc version: 0.7.0 Optimizer: Enabled Runs: 200 EVM Version: Istanbul WARNING: Unable to compile OpenZeppelin/[email protected] due to a CompilerError - you may still be able to import sources from the package, but will be unable to load the package directly. Downloading from https://solc-bin.ethereum.org/linux-amd64/solc-linux-amd64-v0.4.26+commit.4563c3fc 100%|██████████████████████████████████████████████████████████████████████| 5.24M/5.24M [00:10<00:00, 498kiB/s] solc 0.4.26 successfully installed at: /home/mohamad/.solcx/solc-v0.4.26 Downloading from https://solc-bin.ethereum.org/linux-amd64/solc-linux-amd64-v0.6.6+commit.6c089d02 100%|██████████████████████████████████████████████████████████████████████| 8.92M/8.92M [00:23<00:00, 378kiB/s] solc 0.6.6 successfully installed at: /home/mohamad/.solcx/solc-v0.6.6 Compiling contracts... Solc version: 0.6.12 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - OpenZeppelin/[email protected]/Ownable - OpenZeppelin/[email protected]/Context - smartcontractkit/[email protected]/LinkTokenReceiver - smartcontractkit/[email protected]/VRFConsumerBase - smartcontractkit/[email protected]/VRFRequestIDBase - smartcontractkit/[email protected]/AggregatorInterface - smartcontractkit/[email protected]/AggregatorV2V3Interface - smartcontractkit/[email protected]/AggregatorV3Interface - smartcontractkit/[email protected]/ChainlinkRequestInterface - smartcontractkit/[email protected]/LinkTokenInterface - smartcontractkit/[email protected]/SafeMathChainlink - Lottery - MockOracle - MockV3Aggregator Compiling contracts... Solc version: 0.4.26 Optimizer: Enabled Runs: 200 EVM Version: Byzantium Generating build data... - smartcontractkit/[email protected]/ERC677Token - smartcontractkit/[email protected]/ERC20 - smartcontractkit/[email protected]/ERC20Basic - smartcontractkit/[email protected]/ERC677 - smartcontractkit/[email protected]/ERC677Receiver - smartcontractkit/[email protected]/BasicToken - smartcontractkit/[email protected]/SafeMathChainlink - smartcontractkit/[email protected]/StandardToken - LinkToken Compiling contracts... Solc version: 0.6.6 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - smartcontractkit/[email protected]/VRFConsumerBase - smartcontractkit/[email protected]/VRFRequestIDBase - smartcontractkit/[email protected]/LinkTokenInterface - smartcontractkit/[email protected]/SafeMathChainlink - VRFCoordinatorMock Generating interface ABIs... Project has been compiled. Build artifacts saved at /home/mohamad/smartcontract_lottery/build/contracts

Applying the Tests

Now, we apply our tests with following command in the terminal: brownie test -k test_get_entrance_fee Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.42s ======================================== Terminating local RPC client...

Test Number 2

brownie test -k test_cant_enter_unless_started Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.10s ======================================== Terminating local RPC client...

Test Number 3

brownie test -k test_can_start_and_enter_lottery Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.10s ======================================== Terminating local RPC client...

Test Number 4

brownie test -k test_can_end_lottery Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.34s ======================================== Terminating local RPC client...

Test Number 5

brownie test -k test_can_pick_winner_correctly Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 ============================================= test session starts ============================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /home/mohamad/smartcontract_lottery plugins: eth-brownie-1.18.1, web3-5.27.0, xdist-1.34.0, forked-1.4.0, hypothesis-6.27.3 collected 6 items / 5 deselected / 1 selected Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... tests/test_lottery_unit.py . [100%] ======================================= 1 passed, 5 deselected in 2.53s ======================================== Terminating local RPC client…

Deploying Our Lottery Contract

All the tests have been successfully run. Now it is time to finally deploy our contract: brownie run scripts/deploy_lottery.py Result: Brownie v1.18.1 - Python development framework for Ethereum Python-dotenv could not parse statement starting at line 1 Python-dotenv could not parse statement starting at line 1 SmartcontractLotteryProject is the active project. Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... Running 'scripts/deploy_lottery.py::main'... Transaction sent: 0x8dece070c593b0dcea9caa4740f89c80ff2fe29744f8e6b6ed26bdf879c7ec9f Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 MockV3Aggregator.constructor confirmed Block: 1 Gas used: 430659 (3.59%) MockV3Aggregator deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 Transaction sent: 0xab119cd52792e53c56eee35f433c3f4b8ae409e8a5d397ac2dd16aa2d9d88e27 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 1 LinkToken.constructor confirmed Block: 2 Gas used: 669136 (5.58%) LinkToken deployed at: 0x602C71e4DAC47a042Ee7f46E0aee17F94A3bA0B6 Transaction sent: 0xb549afbc42364cb3e72877bf6d3634d655fbe0b608a09e921a3f881983dff488 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 2 VRFCoordinatorMock.constructor confirmed Block: 3 Gas used: 276395 (2.30%) VRFCoordinatorMock deployed at: 0xE7eD6747FaC5360f88a2EFC03E00d25789F69291 Deployed! Transaction sent: 0xbb65e39ea0771823b51b07af3e19122d0a9be34b51e768a536a215a9571fdcc7 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 3 Lottery.constructor confirmed Block: 4 Gas used: 897156 (7.48%) Lottery deployed at: 0x6951b5Bd815043E3F842c1b026b0Fa888Cc2DD85 Deployed lottery! Transaction sent: 0xdada264c84b388a9674518ac1fff2a6e7c2851f9adf5dc7ecd871bed9d0c8629 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 4 Lottery.startLottery confirmed Block: 5 Gas used: 28902 (0.24%) Lottery.startLottery confirmed Block: 5 Gas used: 28902 (0.24%) The lottery is started! Transaction sent: 0x9556bd5fecc58be55b36e84b6612ae264d00fe685e4862f0458846a30e305e2e Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 5 Lottery.enter confirmed Block: 6 Gas used: 70995 (0.59%) Lottery.enter confirmed Block: 6 Gas used: 70995 (0.59%) You entered the lottery! Transaction sent: 0x94f77b768b68faefacdc57046a42caadf5d954a0d0e089bb551f6594cb44037e Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 6 LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) Fund contract! LinkToken.transfer confirmed Block: 7 Gas used: 51398 (0.43%) Transaction sent: 0x6591ec2cc1bfcddb9aea2f80e2723a773506e71bc8b649d58cf02c9708da1847 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 7 Lottery.endLottery confirmed Block: 8 Gas used: 79626 (0.66%) Lottery.endLottery confirmed Block: 8 Gas used: 79626 (0.66%) 0x0000000000000000000000000000000000000000 is the new winner! Terminating local RPC client...

Getting Some Test Link

By the way, before the deployment of this contract, make sure you have some Link Rinkeby tokens in your Metamask wallet. If you don’t have any, you can first add a Link Rinkeby token from Rinkeby Etherscan (By entering the decimals and contract address and so on). Then use Rinkeby Faucet which gives you 10 Link tokens. By using this link, you can get a Rinkeby Link token to be able to test your project:

https://docs.chain.link/docs/link-token-contracts/#rinkeby

Deploying Lottery Project on the Rinkeby Testnet

After running all the tests successfully and deploying it on Ganache-CLI, it is finally time to deploy our Lottery smart contract on Rinkeby chain, and inter-act with it using Rinkeby etherscan. To do so, in the terminal, we type: brownie run scripts/deploy_lottery.py --network rinkeby Result: Brownie v1.18.1 - Python development framework for Ethereum SmartcontractLotteryProject is the active project. Running 'scripts/deploy_lottery.py::main'... Transaction sent: 0xd0172e3f2d1caf7a56b7a1f88b5b95d152843f38f8e2f0bba68d2a5c3b534df7 Gas price: 1.000006975 gwei Gas limit: 995904 Nonce: 54 Lottery.constructor confirmed Block: 10461557 Gas used: 905368 (90.91%) Lottery deployed at: 0x2a5D4140962F09f4a5B4F5f7C563af4Eb45B79b4 Waiting for https://api-rinkeby.etherscan.io/api to process contract... Verification submitted successfully. Waiting for result... Verification pending... Verification complete. Result: Pass - Verified Deployed lottery! Transaction sent: 0x7b803fc2bedd3a1be5fa689e527e317baa4177c35ba589c1ea69ce3d6834298c Gas price: 1.000008155 gwei Gas limit: 31572 Nonce: 55 Lottery.startLottery confirmed Block: 10461565 Gas used: 28702 (90.91%) Lottery.startLottery confirmed Block: 10461565 Gas used: 28702 (90.91%) The lottery is started! Transaction sent: 0x7f2f38f4cfc0d34753c2635e8477b4c32118c3211ea3a3e6613a034a83242f99 Gas price: 1.000008155 gwei Gas limit: 97460 Nonce: 56 Lottery.enter confirmed Block: 10461566 Gas used: 88600 (90.91%) Lottery.enter confirmed Block: 10461566 Gas used: 88600 (90.91%) You entered the lottery! Transaction sent: 0xe1c566be0f8aff444154cf2d655cfc75082d83a7d13b9fb04f4315db3cece877 Gas price: 1.000008524 gwei Gas limit: 56992 Nonce: 57 LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) Fund contract! LinkToken.transfer confirmed Block: 10461567 Gas used: 51811 (90.91%) Transaction sent: 0x585048e0b048833c35f3bd8257425e1e6f1f781cbd507df97abd756e7343c606 Gas price: 1.000008079 gwei Gas limit: 174062 Nonce: 58 Lottery.endLottery confirmed Block: 10461568 Gas used: 153439 (88.15%) Lottery.endLottery confirmed Block: 10461568 Gas used: 153439 (88.15%) 0x25E681EE76469E4cF846567b772e94e082907117 is the new winner!

Rinkeby Etherscan

Now, we can copy the and paste 0x2a5D4140962F09f4a5B4F5f7C563af4Eb45B79b4 address in Rinkeby etherscan to be able to interact with your smart contract:

https://rinkeby.etherscan.io

Once you enter Etherscan and paste your address, you will be able to see your address, ABI of the contract, dependency contracts, contract creation code, and so on.

Interacting with the Deployed Contract

To interact with the deployed smart contract, head over to Read the contract and write the contract.

You can read some of the details of the public variables after deployment in the Read contract section:

You can also run another lottery using Write Contract section:

Connecting to Metamask Wallet

But, before writing anything in there, make sure you connect to your wallet by clicking on Connect to Web3 button:

Now you can start the lottery:

Enter the lottery:

And execute other functions:

And that’s it. You have written a lottery smart contract, deployed it on a test net, Rinkeby chain, and other networks, and learned how to interact with it using Rinkeby Etherscan!

Lottery Project: Done!

In this tutorial, we have managed to deploy the contract on Rinkeby Testnet and interact with it from the address of the deployment on the Rinkeby Etherscan. To make this interaction possible from Etherscan, we connected the Metamask account of the admin of the contract to this website and applied all the stages of the lottery from the contract section.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

Lottery Project Using Brownie: A Full Scale Dapp

In this article, we are going to get started with the lottery project using Brownie. The main purpose of a lottery project in every network is to check the reliability of the randomness and use it for different purposes such as the lottery itself. In the lottery project, we are going to create a decentralized application using Brownie. In the end, we will be able to run the smart contract via Etherscan.

Using Brownie for Lottery Project

In this tutorial, we are going to first write a smart contract related to a lottery and write scripts related to testing and deploying the smart contract. We also want to make it a full-scale decentralized application, meaning that it is going to be an end-to-end Dapp with easy to use user experience. Every lottery needs a random variable. As randomness is a very complicated concept when it is going to be applied on the internet and here we are going to run it via the blockchain, it must be protected from hacks and cheating. What makes randomness really complex, is that we are dealing with deterministic variables rather than probabilistic ones. As a result, we are going to use some Chainlink tools to cover this complexity.

In this first part, we only focus on the lottery.sol related to the smart contracts, its dependency files, and the Brownie-config.yaml in addition to seeing how the Solidity scripts are written and how they work.

Getting Started with the Lottery Project

It is also important to notice that this tutorial is written to explain the GitHub repository published by Patrick Alpha C more explicitly and solve the probable issues that the programmers might face when running and facing bugs related to the Solidity version or the configuration of the project. With that said let’s get started:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";


We have first declared the version of Solidity and the license comment before that. Also, we have imported 3 necessary Solidity libraries including AggregatorV3Interface which is used for fetching the Ethereum price from Chainlink oracle, openzeppelin Ownable.sol which is used for determining the owner of the contract and finally the VRFConsumer.sol which is used to get a random number.

contract Lottery is VRFConsumerBase, Ownable {
        address payable[] public players;
	address payable public recentWinner;
	uint256 public randomness;
	uint256 public usdEntryFee;
	AggregatorV3Interface internal ethUsdPriceFeed;
	enum LOTTERY_STATE {
		OPEN,
		CLOSED,
		CALCULATING_WINNER
	}
	LOTTERY_STATE public lottery_state;
	uint256 public fee;
	bytes32 public keyhash;
	event RequestedRandomness(bytes32 requestId);

	constructor(address _priceFeedAddress,
			address _vrfCoordinator,
			address _link,
			uint256 _fee,
			bytes32 _keyhash
			) public VRFConsumerBase(_vrfCoordinator, _link) {
		usdEntryFee = 50 * (10**18);
		ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress);
		lottery_state = LOTTERY_STATE.CLOSED;
		fee = _fee;
		keyhash = _keyhash;
	}
}

We start the contract named Lottery and determine their declarations ownable, VRFConsumerBase. Inside the contract we define the variables that we are going to work with including:
  • The array of the addresses of the participants in the lottery defined here as players (declared as payable and public),
  • The recent winner who has won the prize in the last lottery,
  • The randomness which is the random number we receive from VRFC,
  • UsdEntryFee which is the minimum amount that a player needs to participate in the lottery, ethUsdPriceFeed which is the conversion rate of ETH to USD.
The lottery state is declared as Enum and has 3 states Open: Close, and Calculating winner.
  1. Open is when everyone can participate in the lottery,
  2. Closed is when nobody can participate
  3. Calculating is used when the random number related to the winner is being calculated.
We also have a constructor for some variables which are going to be explained later.

function enter() public payable {
	// $50 minimum
	require(lottery_state == LOTTERY_STATE.OPEN);
	require(msg.value >= getEntranceFee(), "Not enough ETH!");
	players.push(msg.sender);
}


The above function is related to the entrance to the lottery. At first, the state of the lottery is open and we check if the players pay the minimum entrance fee to participate in it.

function getEntranceFee() public view returns (uint256) {
	(, int256 price, , , ) = ethUsdPriceFeed.latestRoundData();
	uint256 adjustedPrice = uint256(price) * 10**10;
	// 18 decimals
	// $50, $2,000 / ETH
	// 50/2,000
	// 50 * 100000 / 2000
	uint256 costToEnter = (usdEntryFee * 10**18) / adjustedPrice;
	return costToEnter;
}


The above function is responsible for checking the price of the Ethereum in USD and determining the price of participation in ETH.

function startLottery() public onlyOwner {
	require(lottery_state == LOTTERY_STATE.CLOSED,"Can't start a new lottery yet!");
	lottery_state = LOTTERY_STATE.OPEN;
}


The above function starts the lottery and before that checks if the lottery has not yet got closed.

function endLottery() public onlyOwner {
	lottery_state = LOTTERY_STATE.CALCULATING_WINNER;
	bytes32 requestId = requestRandomness(keyhash, fee);	
	emit RequestedRandomness(requestId);
}


The above function gives the authority to only the owner of the contract. This function also determines the winner to end the lottery. This process is done by requesting a random number.

function fulfillRandomness(bytes32 _requestId, uint256 _randomness)internal override{
	require(lottery_state == LOTTERY_STATE.CALCULATING_WINNER,"You aren't there yet!");
	require(_randomness > 0, "random-not-found");
	uint256 indexOfWinner = _randomness % players.length;
	recentWinner = players[indexOfWinner];
	recentWinner.transfer(address(this).balance);
	// Reset
	players = new address payable[](0);
	lottery_state = LOTTERY_STATE.CLOSED;
	randomness = _randomness;
}


The above function requires the lottery state to be in the calculating winner. It also checks whether the given random number is positive. Then it uses the remainder operator to calculate the id of the winner from the random number it gets. After that, it transfers the balance of the contract to the player that has won the lottery And finally changes the state of the lottery to closed.

Configuration

Notice that up to here you need to enter the following into the Brownie-config.yaml:

dependencies:
	smartcontractkit/[email protected]
 	OpenZeppelin/[email protected]
compiler:
	solc:
		remappings:
			'@chainlink=smartcontractkit/[email protected]'
			'@openzeppelin=OpenZeppelin/[email protected]'
dotenv: .env
networks:
	default: development
	development:
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000
	rinkeby:
		vrf_coordinator:'0xb3dCcb4Cf7a26f6cf6B120Cf5A73875B7BBc655B'
		eth_usd_price_feed:'0x8A753747A1Fa494EC906cE90E9f37563A8AF630e'
		link_token: '0x01BE23585060835E02B77ef475b0Cc51aA1e0709'
		keyhash:'0x2ed0feb3e7fd2022120aa84fab1945545a9f2ffc9076fd6156fa96eaff4c1311'
		fee: 100000000000000000
		verify: True
	mainnet-fork:
		eth_usd_price_feed:'0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'
		verify: False
wallets:
	from_key: ${PRIVATE_KEY}



Up to here, we only need the following to be able to compile the Brownie project:

dependencies:
	smartcontractkit/[email protected]
 	OpenZeppelin/[email protected]
compiler:
	solc:
		remappings:
			'@chainlink=smartcontractkit/[email protected]'
			'@openzeppelin=OpenZeppelin/[email protected]'


In the next parts, we are going to see how we can deploy and test this smart contract on different networks.

Deployment of the Lottery Project Using Brownie

In this section, we are going to continue the lottery project and focus on its deployment of it. The main python file as always is the deploy.py where we are going to deploy the four stages of a lottery: deploy the lottery, start the lottery, enter the lottery and end the lottery. The prize here is paid with a Chainlink token. As a result, we will add the ChainlinkTokenInterface contract to the contracts directory.

Managing the Folders of the Lottery Project Directory

In order to deploy the smart contract that we have just written, we need to complete the deployment and test folders and also add some other files like the dependencies of our smart contract. As always, the first step is to tyoe in the terminal: brownie init Then, you will see the folders and files that are created afterward. Then it is time to write our deploy.py file. As you know every lottery contract has 4 stages, deploying the lottery, starting it, entering the lottery, and ending it. So accordingly, we have 4 functions:

from scripts.helpful_scripts import get_account, get_contract, fund_with_link
from brownie import Lottery, network, config
import time

def deploy_lottery():
	account = get_account()
	lottery = Lottery.deploy(get_contract("eth_usd_price_feed").address, 						 
		get_contract("vrf_coordinator").address,
		get_contract("link_token").address,
		config["networks"][network.show_active()]["fee"],
		config["networks"][network.show_active()]["keyhash"],
		{"from": account},
		publish_source=config["networks"][network.show_active()].get("verify", False),)								
	print("Deployed lottery!")
	return lottery


In every contract deployment, we should determine some of the specifications related to a contract such as contract addresses (address of the dependency contracts) and some config specifications about every network.

def start_lottery():
	account = get_account()
	lottery = Lottery[-1]
	starting_tx = lottery.startLottery({"from": account})
	starting_tx.wait(1)
	print("The lottery is started!")


The above function starts the lottery. At first, it gets the account address which uses the get account function from helpful_scripts.py, then it gets the latest lottery smart contract, After that, it starts the lottery transaction using the startLottery function inside the contract.

def enter_lottery():
	account = get_account()
	lottery = Lottery[-1]
	value = lottery.getEntranceFee() + 100000000
	tx = lottery.enter({"from": account, "value": value})
	tx.wait(1)
	print("You entered the lottery!")


The above function also gets the account and the latest lottery contract records, then determine the value of the entrance fee (notice that 10000000 that has been added up with the entrance fee, is not a big number as it is in the Wei unit). Then, we enter the participant by using the enter function from the lottery.sol contract.

def end_lottery():
	account = get_account()
	lottery = Lottery[-1]
	# fund the contract
	# then end the lottery
	tx = fund_with_link(lottery.address)
	tx.wait(1)
	ending_transaction = lottery.endLottery({"from": account})
	ending_transaction.wait(1)
	time.sleep(180)
	print(f"{lottery.recentWinner()} is the new winner!")


In the above function, again get the account and the lottery contract, fund the winner with some link token (using the function written in helpful_scripts.py), and ends the transaction using the function in the lottery contract.sol.

def main():
	deploy_lottery()
	start_lottery()
	enter_lottery()
	end_lottery()


In the above main function, we apply other functions in the sequence of a lottery.

Helpful_Scripts.py

Now, it is time to go after helpful_scripts.py file and explain the codes:

from brownie import (accounts, network, config, MockV3Aggregator, VRFCoordinatorMock, LinkToken, 
	Contract, interface,)
FORKED_LOCAL_ENVIRONMENTS = ["mainnet-fork", "mainnet-fork-dev"]
LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["development", "ganache-local"]

def get_account(index=None, id=None):
	if index:
		return accounts[index]
	if id:
		return accounts.load(id)
	if ( network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS or network.show_active() in 
			FORKED_LOCAL_ENVIRONMENTS):
		return accounts[0]
	return accounts.add(config["wallets"]["from_key"])


The above function determines the address of the available account according to the active network.

contract_to_mock = {"eth_usd_price_feed": MockV3Aggregator,"vrf_coordinator": VRFCoordinatorMock,
	"link_token": LinkToken,}
def get_contract(contract_name):
	contract_type = contract_to_mock[contract_name]
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		if len(contract_type) <= 0:
			# MockV3Aggregator.length
			deploy_mocks()
			contract = contract_type[-1]
			# MockV3Aggregator[-1]
	else:
		contract_address = config["networks"][network.show_active()][contract_name]
		contract = Contract.from_abi(contract_type._name, contract_address, contract_type.abi)
	return contract


The above function will grab the contract addresses from the Brownie config when defined. Otherwise, it will deploy a mock version of that contract, and return that mock contract.

DECIMALS = 8
INITIAL_VALUE = 200000000000
def deploy_mocks(decimals=DECIMALS, initial_value=INITIAL_VALUE):
	account = get_account()
	MockV3Aggregator.deploy(decimals, initial_value, {"from": account})
	link_token = LinkToken.deploy({"from": account})
	VRFCoordinatorMock.deploy(link_token.address, {"from": account})
	print("Deployed!")


The above function deploys the mock version of the 2 contracts: 1. VRFCoordinator and 2. V3Aggregator contracts.

def fund_with_link(contract_address, account=None, link_token=None,amount=100000000000000000): # 0.1 LINK
	account = account if account else get_account()
	link_token = link_token if link_token else get_contract("link_token")
	tx = link_token.transfer(contract_address, amount, {"from": account})
	tx.wait(1)
	print("Fund contract!")
	return tx


The above function funds the winner with a link token. Notice that we should create a file in the interfaces folder for the link token contract and name it LinkTokenInterface.sol. You can copy and paste the contract below:

Link Token Interface Contract

The below contract which is called LinkTokenInterface.sol, should be added to the contacts folder. We use the functions of this contract in the helpful_scripts.py and deploy.py to fund the winner of the lottery with some ChainLink tokens. If you look at the contract in detail, you will see that the functions and methods are the same as of an ERC-20 token contract.

pragma solidity ^ 0.6.6;

interface LinkTokenInterface {
	function allowance(address owner, address spender) external view returns (uint256 remaining);
	function approve(address spender, uint256 value) external returns (bool success);
	function balanceOf(address owner) external view returns (uint256 balance);
	function decimals() external view returns (uint8 decimalPlaces);
	function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);
	function increaseApproval(address spender, uint256 subtractedValue)external;
	function name() external view returns (string memory tokenName);
	function symbol() external view returns (string memory tokenSymbol);
	function totalSupply() external view returns (uint256 totalTokensIssued);
	function transfer(address to, uint256 value) external returns (bool success);
	function transferAndCall(
		address to,
		uint256 value,
		bytes calldata data
		) external returns (bool success);
	function transferFrom(
		address from,
		address to,
		uint256 value
		) external returns (bool success);
}


Compiling Our Complete Lottery Project Using Brownie for Final Deployment

In this section, following the lottery project, we are going to complete the scripts that should be written so that we can finally compile our complete contract and be able to deploy it fully. The focus of this section is on the testing of the contract, where we are going to test the different functionalities of different stages of the lottery project. In the following of the lottery project, we are going to complete the scripts that should be written so that we can finally compile our complete contract and be able to deploy it fully.

Brownie_config.sol

There are some .sol dependencies that need to be copied and pasted into the contracts/test folder so that we can use them to deploy our main lottery smart contract. So make sure you copy them from this link and paste them into your directory.

.env File

Also, in the .env file enter the private key of your test Metamask account (Do not use a Metamask wallet with real crypto in it), your Etherscan Token, and Infura ID.

export WEB3_INFURA_PROJECT_ID=''
export PRIVATE_KEY=''
export ETHERSCAN_TOKEN=''


Testing the Contract Functionalities

Now, every standard smart contract deployment needs testing and this one is not an exception, we start our test with test_lottery_unit.py:

from scripts.helpful_scripts import ( LOCAL_BLOCKCHAIN_ENVIRONMENTS,get_account,fund_with_link,
	get_contract,)
from brownie import Lottery, accounts, config, network, exceptions
from scripts.deploy_lottery import deploy_lottery
from web3 import Web3
import pytest

def test_get_entrance_fee():
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	# Arrange
	lottery = deploy_lottery()
	# Act
	# 2,000 eth / usd
	# usdEntryFee is 50
	# 2000/1 == 50/x == 0.025
	expected_entrance_fee = Web3.toWei(0.025, "ether")
	entrance_fee = lottery.getEntranceFee()
	# Assert
	assert expected_entrance_fee == entrance_fee


The above test considers the price of ETH is 2000 dollars and the entrance fee is 50 dollars so the 0.025 ether is required to participate in the lottery. as a result, we expect the entrance fee to be 0.025 ether, so we call the getEn-tranceFee() function to check the validity of the result.

def test_cant_enter_unless_started():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	# Act / Assert
	with pytest.raises(exceptions.VirtualMachineError):
		lottery.enter({"from": get_account(), "value": lot-tery.getEntranceFee()})


The above test checks whether the participant can enter or not at the times the lottery has been closed or has not opened yet.

def test_can_start_and_enter_lottery():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	# Act
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	# Assert
	assert lottery.players(0) == account


The above test checks whether the player who has participated in the lottery is recorded in the list of lottery players in other words it checks if the player has entered or not.

def test_can_end_lottery():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	fund_with_link(lottery)
	lottery.endLottery({"from": account})
	assert lottery.lottery_state() == 2



The above test checks whether the end lottery function works. To do so, it first passes all the other tests and applies the other stages of the lottery and at the end tests the lottery state.

def test_can_pick_winner_correctly():
	# Arrange
	if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	lottery.enter({"from": get_account(index=1), "value": 	lot-tery.getEntranceFee()})
	lottery.enter({"from": get_account(index=2), "value": 	lot-tery.getEntranceFee()})
	fund_with_link(lottery)
	starting_balance_of_account = account.balance()
	balance_of_lottery = lottery.balance()
	transaction = lottery.endLottery({"from": account})
	request_id = transaction.events["RequestedRandomness"]["requestId"]
	STATIC_RNG = 777
	get_contract("vrf_coordinator").callBackWithRandomness(request_id, STATIC_RNG, lottery.address,
		{"from": account})
	# 777 % 3 = 0
	assert lottery.recentWinner() == account
	assert lottery.balance() == 0
	assert account.balance() == starting_balance_of_account + bal-ance_of_lottery
	


The above test uses all of the operations of the lottery that have been tested to test the correctness of the winner picking. To do so, it first enters an account into the lottery and at the end, tests whether the winner is in the same account, whether the lottery contract balance has turned 0 as a result of sending money to the winner, and whether the winner’s account has been added up with the balance of the lottery contract.

from brownie import network
import pytest
from scripts.helpful_scripts import ( LOCAL_BLOCKCHAIN_ENVIRONMENTS,
	get_account, fund_with_link,)
from scripts.deploy_lottery import deploy_lottery
import time

def test_can_pick_winner():
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		pytest.skip()
	lottery = deploy_lottery()
	account = get_account()
	lottery.startLottery({"from": account})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	lottery.enter({"from": account, "value": lottery.getEntranceFee()})
	fund_with_link(lottery)
	lottery.endLottery({"from": account})
	time.sleep(180)
	assert lottery.recentWinner() == account
	assert lottery.balance() == 0


The above test also does the same process in a little bit different manner.

It is important to know that we should do all of the tests one by one to be able to debug the functions. As you can see, we started our first test with the first stage of the contract.

Final Word on Lottery Project Deployment Using Brownie

Firstly, we have managed to get started with the lottery project and have written the smart contract for it. In addition to that, we have added some dependency contracts like VRFConsumerBase, Openzeppelin, and V3AggregatorInterface.

Secondly, we have managed to write the deploy.py and helpful_scripts.py to interact with the lottery smart contract and deploy the different stages of a lottery such as deploying the lottery, starting it, entering it using different accounts, and ending it. In the end, the winner is going to be awarded some Chainlink tokens.

Thirdly, we have managed to complete the whole lottery project script file so that we can finally compile and deploy the smart contract. In most of the parts of this tutorial, we focused on testing the contract scripts for testing the different functionalities related to the different stages of the contract.

Download this Article in PDF format

web developement

Check Out Our Services

In Arashtad, we're working on 3D games, metaverses, and other types of WebGL and 3D applications with our 3D web development team. However, our services are not limited to these. Back-end developments, front-end developments, 3d modeling, and animations are in our arsenal too.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development

Getting Started with Decentralized Apps (Dapps) Using Python

If you have ever heard of blockchain, bitcoin, web3, etc, you are most probably familiar with the word Dapp. Dapps or Decentralized applications are web3-based apps that operate autonomously using smart contracts. A simple example of Dapp is a wallet that is connected to a blockchain like Ethereum and operates the transactions automatically without the need for any third-party organizations or central servers. In this article, we are going to get more familiar with the Dapps using Python.

Using Python for Dapps: Installing the Dependencies

The first step for getting started with the development of decentralized applications in python is learning how to work with smart contracts using web3.py tools. With that being said, let’s get started by installing web3.py on your operating system. The default operating system for this tutorial is Linux but we will mention the necessary installation guides for those who work with Windows or Mac OS. On Linux, make sure you have pip3 installed. To do so, we write:

pip3 –version

If you see a result like this: pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8) Then, you have it installed. Otherwise in your terminal, write:

sudo apt install python3-pip

Now it’s time to install web3, first of all, there are 2 dependencies to be installed:

pip install eth-tester web3
pip install eth-tester[py-evm]

Finally, we can install web3:

pip3 install web3

Notice that on windows instead of pip3, you should write pip and make sure you have installed pip on your operating system as well. You also need to have some Visual C++ dependencies installed when working on Windows. Otherwise, you will face some errors during the installation of web3.
After we have installed all the necessary packages, it is time to write our very first web3 scripts in python. Before we do that, we must connect to the Ethereum node. To do that, there are 2 options:
  1. Running a local node on your computer using the Geth or Parity command: This method takes a lot of memory to download all the transactions on the Ethereum blockchain, not to mention the necessity to permanently connect to the internet to maintain a node, bandwidth conditions that should be met and so on.
  2. Using a hosted node, like Infura or other similar ones.

Infura:

As we want to have our first experience with web3 in python, using Infura as a simple and free method is more preferred than other options. So, to do that you need to first sign up in infura.io:

Dapps using Python

And create a new project:

Dapps

After creating a new project go to settings and in the key section copy the RPC URL which is in the following format:
https://”selected endpoint”.infura.io/v3/”Your Project ID”
It is recommended that you select Mainnet for your endpoint.

Python Scripts for Dapps

Now that we have our hosted node to be able to connect to Ethereum blockchain, it is time to get into some python scripts:

from web3 import Web3

infura_url = "https://mainnet.infura.io/v3/"
web3 = Web3(Web3.HTTPProvider(infura_url))
print(web3.isConnected())
print(web3.eth.blockNumber)
print(web3.eth.get_block('latest')


Result: True 14395685 AttributeDict({'baseFeePerGas': 27155659376, 'difficulty': 12673708419879581, 'extraData': HexBytes…….(to be continued) The above code checks whether we are connected to web3 or not, the number of blocks on the Ethereum blockchain and the latest block that is mined. Now, we go after some of the more useful functions like checking the validation of an account, its balance, tracking the transactions it has had and so on. To do that, we need an account address which we can get from etherscan.io. Then we will write:

validation = web3.isAddress(0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8)
print(validation)

Result: True

check_sum = web3.toChecksumAddress(accountAddress)
balance = web3.eth.getBalance(check_sum)
print(check_sum)
print(balance)


Result: 0xa1bAA6C66930a3FB9803726f41D5E4F855805028 207816291093185845 Notice that if instead of web3.eth.getBalance(check_sum) you had written web3.eth.getBalance(account), you would have face an error like this: raise InvalidAddress( web3.exceptions.InvalidAddress: ('Web3.py only accepts checksum addresses. The software that gave you this non-checksum address should be considered unsafe, please file it as a bug on their platform. Try using an ENS name instead. Or, if you must accept lower safety, use Web3.toChecksumAddress(lower_case_address).', '0xa1baa6c66930a3fb9803726f41d5e4f855805028') Also, remember the value that balance function returns is in Wei and needs to be changed to Ether, USD, or any other currency of your choice. If you want to change it to ether you can use the following code:

etherEquivalent = web3.fromWei(balance, 'ether')

Result: 0.200593459662878024

Conversion to US Dollar

If you want to convert the balance into a fiat currency like USD, EURO, or any other currency around the world, you need to get an API key from coin market cap website using this link. Sign up and copy your API key in the following script:

from requests import Request, Session
from requests.exceptions import ConnectionError, Timeout, TooManyRedirects
import json
url = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest'

parameters = {
      'start':'2',  # rank of the first token you want to fetch
      'limit':'2',  # rank of the last token you want to fetch
      'convert': 'USD '  # You can write any other currency you want to fetch your data in
}
headers = {
      'Accepts': 'application/json',
      'X-CMC_PRO_API_KEY':' Paste your API key in here',
}

session = Session()
session.headers.update(headers)

try:
      response = session.get(url, params=parameters)
      data = json.loads(response.text)
      print(data['data'][0]['quote']['USD']['price'])  #you can specify the data that you want 
                                                   #inside the dictionary
except (ConnectionError, Timeout, TooManyRedirects) as e:
      print(e)


Result: 2590.1887169488728 Before running this code you need to consider the highlighted parts of the scripts. Here because we have wanted to convert Ethereum to USD, the start and limit variables are both 2 which means we want “our currency” equivalent of Ethereum, which here we have specified to be USD in the converted variable. Now, in order to have the USD equivalent of the balance, we use a simple multiplication:

USD_equivalent = data['data'][0]['quote']['USD']['price'] * etherEquivalent

Result: 519.5749159125255 Which shows that the account holds about 520 dollars in Ethereum.
The other useful web3 function is tracking the transactions. The following code returns different properties of a specific transaction:

transaction = 
      web3.eth.get_transaction('0x77196fdfeb5b076f2fa4eb65fb59c5287010f171eef5635ef9c16905c4b2ebeb')

print (transaction)


Result: AttributeDict({'accessList': [], 'blockHash': HexBytes('0x84915d8a27c3127f9ac2417d44613a46c6e08dad34b1c5a16f493c8f4dc8d228'), 'blockNumber': 14395742, 'chainId': '0x1', 'from': '0xa1bAA6C66930a3FB9803726f41D5E4F855805028', 'gas': 108070, 'gasPrice': 22953253192, 'hash': HexBytes('0x77196fdfeb5b076f2fa4eb65fb59c5287010f171eef5635ef9c16905c4b2ebeb'), 'input': '0xefbd73f4000000000...30000000000000000000000000d3814659415aa213a2a76f5a9252a477b0e3f63', 'maxFeePerGas': 38902649594, 'maxPriorityFeePerGas': 1000000000, 'nonce': 55, 'r': HexBytes('0x1caebee1505e611b5ae766af9339839bd1eaf2b0f8171cab49b1bd94e0362288'), 's': HexBytes('0x429a1717741dd4a153ae65d985a9ab2d230274b7311e47b46ba4193f5bcf7379'), 'to': '0x92069da581FC2f0705EaBEc9690779d23Ace6c3A', 'transactionIndex': 102, 'type': '0x2', 'v': 0, 'value': 0}) As you can see, the result is a dictionary with some attributes. Here we can specifically print some of the more important ones such as “to” , “from”, or “gasPrice”.

print (transaction['to'])

Result: 0x92069da581FC2f0705EaBEc9690779d23Ace6c3A

print (transaction['from'])

Result: 0xa1bAA6C66930a3FB9803726f41D5E4F855805028

print (transaction['gasPrice]')

Result: 22953253192 There is also a 2nd way to look up for a transaction, which is using a transaction reciept:

transaction_receipt = 
      web3.eth.get_transaction_receipt(
            '0xd0f9e247581f9d4c5177fb315e7115e50fc9f673e0915b4b64f3ef5c1b8b81aa')
print(transaction_receipt)


Result: AttributeDict({'blockHash': HexBytes('0x166eff2ec3e1375ff70c1dd49b7e4e00dab4802f094fbf81d4021d6d0ac48cb8'), 'blockNumber': 13557150, 'contractAddress': None, 'cumulativeGasUsed': 1719841, 'effectiveGasPrice': 270600000000, 'from': '0xDBD0C0C297035F3D9FD6788B6deC7A28dAd97C63', 'gasUsed': 47216, 'logs': [AttributeDict({'address': '0xd665ce6Ef8AdA72B1CF946A6a71508bDD6D2EE04', 'blockHash': HexBytes('0x166eff2ec3e1375ff70c1dd49b7e4e00dab4802f094fbf81d4021d6d0ac48cb8'), 'blockNumber': 13557150, 'data': '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'logIndex': 23, 'removed': False, 'topics': [HexBytes('0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925'), HexBytes('0x000000000000000000000000dbd0c0c297035f3d9fd6788b6dec7a28dad97c63'), HexBytes('0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d')], 'transactionHash': HexBytes('0xd0f9e247581f9d4c5177fb315e7115e50fc9f673e0915b4b64f3ef5c1b8b81aa'), 'transactionIndex': 46})], 'logsBloom': HexBytes('0x000000000000000000000000000000000000000000000000000104...'), 'status': 1, 'to': '0xd665ce6Ef8AdA72B1CF946A6a71508bDD6D2EE04', 'transactionHash': HexBytes('0xd0f9e247581f9d4c5177fb315e7115e50fc9f673e0915b4b64f3ef5c1b8b81aa'), 'transactionIndex': 46, 'type': '0x0'})

In Summary

In this tutorial, we have got familiar with the python web3 tools and decentralized applications. In addition to that, we have managed to create an account on the Infura website to connect to it as a host node or HTTP provider. After connecting to the Ethereum Mainnet, we have checked the balance of an arbitrary account and using the coin market cap API, we have converted the price from Wei to US dollar.

Download this Article in PDF format

metaverse

We Are Working on Metaverses

Metaverses, 3d modeling, 3d application, 3d websites, and even animations are just some of our vase expertise.

Arashtad Serivces
Tell us about your ideas
Fill in the Form
Blockchain Development

ERC-20 Event and Function + Sample Smart Contract

In this article, we are going to analyze the ERC-20 event and function deeper and get familiar with the building blocks of any ERC-20 smart contract. Learning these methods and events will help us write the smart contract of our desired token. Throughout this article, you will see the functionalities of all the smart contracts that we wrote for creating an ERC20 token in the last article.

Functions and Event of all ERC-20 contracts:

In general, every ERC-20 token has the below functions and events:

function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)


ERC-20 Event


event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)


A Sample ERC-20 Smart Contract: Method & Event

For the following contract, we are going to explain more about the ERC-20 method and event (TokenERC20.sol):

pragma solidity ^0.6.0;

interface tokenRecipient {
	function receiveApproval(address _from, uint256 _value, address _token, bytes calldata 
                _extraData) external;
}

contract TokenERC20 {
	string public name;
	string public symbol;
	uint8 public decimals = 18;
	uint256 public totalSupply;
}

Name, symbol, decimals, and total supply are the main variables of every ERC-20 smart contract.

mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;


Created a mapping between the addresses and balances (array of addresses and balances).

event Transfer(address indexed from, address indexed to, uint256 value);

A public event that will inform the clients about the transfer (sender, receiver, and value sent)

event Approval(address indexed _owner, address indexed _spender, uint256 _value);

A public event that will inform the clients about the transfer (sender, receiver, and value sent).

event Burn(address indexed from, uint256 value);

The burn event will inform the clients about the amount that has been burnt.

constructor(uint256 initialSupply,
		string memory tokenName,
		string memory tokenSymbol
		) public {
	totalSupply = initialSupply * 10 ** uint256(decimals);
	balanceOf[msg.sender] = totalSupply;
	name = tokenName;
	symbol = tokenSymbol;
}


The above constructor determines the total supply according to the decimals. And transfers all of it to the balance of the owner of the contract. In end, the name and the symbol of the token are determined for display.

function _transfer(address _from, address _to, uint _value) internal {
	require(_to != address(0x0));
}

The receiver should not be addressed as 0x0 or the owner of the contract.

require(balanceOf[_from] >= _value);

The sender must have enough balance to send the desired value.

require(balanceOf[_to] + _value >= balanceOf[_to]);

After the transaction, the receiver’s balance must increase. In other words, the value must be greater or equal to zero.

uint previousBalances = balanceOf[_from] + balanceOf[_to];

Before the transfer, the balance of the sender and receiver is calculated for the later tests.

balanceOf[_from] -= _value;

After the transfer, the balance of the sender is subtracted by the value.

balanceOf[_to] += _value;

After the transfer, the balance of the receiver is added to the value of the transfer.

emit Transfer(_from, _to, _value);

The event is emitted to notify the clients about the transaction.

assert(balanceOf[_from] + balanceOf[_to] == previousBalances);

We test whether, after the transfer, the sum of the balances equals the previous sum of balances.The following function uses the data of the _transfer to verify the success of the transaction.

function transfer(address _to, uint256 _value) public returns (bool success) {
	_transfer(msg.sender, _to, _value);
	return true;
}


The following function will allow the sender to send just the value to the receiver.

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
	require(_value <= allowance[_from][msg.sender]);
	allowance[_from][msg.sender] -= _value;
	_transfer(_from, _to, _value);
	return true;
}


The function below checks the output of the above function if it has returned true and will let the contract know.

function approveAndCall(address _spender, uint256 _value, bytes memory 					 
	_extraData) public returns (bool success) {
	tokenRecipient spender = tokenRecipient(_spender);
	if (approve(_spender, _value)) {
		spender.receiveApproval(msg.sender, _value, address(this), _extraData);							
		return true;
	}
}

The function below burns the amount of transfer value, or in other words, it will decrease the amount that has been transferred from the total supply.

function burn(uint256 _value) public returns (bool success) {
	require(balanceOf[msg.sender] >= _value);
	balanceOf[msg.sender] -= _value; // Subtract from the sender
	totalSupply -= _value;
	emit Burn(msg.sender, _value);
	return true;
}


The following function is the same as above with the difference that it will ask for the allowance of the sender for the burning.


function burnFrom(address _from, uint256 _value) public returns (bool success) {
	require(balanceOf[_from] >= _value);
	require(_value <= allowance[_from][msg.sender]);
	balanceOf[_from] -= _value
	allowance[_from][msg.sender] -= _value; 						
	totalSupply -= _value;
	emit Burn(_from, _value);
	return true;	
}


Final Word

In this article, we have studied an ERC-20 smart contract. We have analyzed the necessary functions, methods, and events that every ERC-20 token smart contract must have. Furthermore, we have taken a look at how a sample token contract has been written in solidity with the consideration of all the events, methods, and necessary functions. Notice, that you can write an ERC-20 smart contract in a very different way as long as you include the necessary method and event in it.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Tell us about your ideas
Fill in the Form
Blockchain Development

How to Interact with the ERC-20 Smart Contract

In this tutorial we are going to run the ERC-20 smart contract deployment on the Rinkeby chain and see the actual token created in our Metamask wallet. After you create and deploy your ERC-20 smart contract, you will be able to see that this token is added on the Rinkeby Etherscan, and by adding the address and specifications of the token, you can add it to your wallet and also you will be able to do the same on Uniswap afterward to be able to swap your token with other tokens.

ERC-20 Smart Contract

In our last related article on how to create an ERC-20 token, we learned how to create a token with the ERC-20 standard. Moreover, we showed how to run it via Ganache-CLI. In this tutorial, we are going to run the very same deployment on the Rinkeby network, and see the actual token created in our Metamask wallet. After you create and deploy your ERC-20 smart contract, you will be able to see that this token is added to the Rinkeby Etherscan. Then, by adding the address and specifications of the token, you can add it to your wallet. Also, you will be able to do the same on Uniswap afterward to be able to swap your token with other tokens (notice that swapping your token is only possible on Mainnet but not on the Rinkeby test network).

Deploying on Rinkeby Testnet:

To begin with the deployment of our ERC-20 token, type the following command in the terminal:

brownie run scripts/1_deploy_token.py --network rinkeby

Result:

Brownie v1.18.1 - Python development framework for Ethereum Compiling contracts... Solc version: 0.6.12 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - TokenERC20 - tokenRecipient Compiling contracts... Solc version: 0.8.13 Optimizer: Enabled Runs: 200 EVM Version: Istanbul Generating build data... - OpenZeppelin/[email protected]/ERC20 - OpenZeppelin/[email protected]/IERC20 - OpenZeppelin/[email protected]/IERC20Metadata - OpenZeppelin/[email protected]/Context - OurToken MytokenProject is the active project. Running 'scripts/1_deploy_token.py::main'... Transaction sent: 0xa85a8d1595d290657f726f1533d105c2d2b6604e051409dc0c2e4d9a74bf547e Gas price: 1.000000019 gwei Gas limit: 745852 Nonce: 62 TokenERC20.constructor confirmed Block: 10468341 Gas used: 678048 (90.91%) TokenERC20 deployed at: 0xDD7f08A04c3d66beD75aFFA5f0e70e46de4567eb

Finding the token on Rinkeby Etherscan:

By copying and pasting the “0xDD7f08A04c3d66beD75aFFA5f0e70e46de4567eb” into the Rinkeby Etherscan, you will be able to see that we have indeed created a token called Token:

Adding the token to the Metamask:

Now, let’s head over to our Metamask, and in the Rinkeby network, import our new token by entering the address of the contract deployment:

ERC-20 smart contract

Adding the token to the Metamask:

Now, let’s head over to our Metamask, and in the Rinkeby network, import our new token by entering the address of the contract deployment:

ERC-20 smart contract ERC-20 smart contract

And you will see that our initial supply which was 1000 has been multiplied by 10 to power our decimals (which was 18). As a result, you can see 21 zeros after one as the total supply (All of which are in our account).

Transferring the tokens:

Now, we can send these tokens to other accounts. For example, here we transfer our TKN token to account 2.

ERC-20 smart contract

Importing the token in Uniswap:

We can also go to Uniswap and see if we can add our token to it.

ERC

let’s first connect to our Metamask wallet:

ERC

Then, press the “Select A Token” button. And in order to add your token in Uniswap, you should first enter your address in the box and you should be able to find the TKN token. Press import:

ERC

And we have our token in Uniswap!

ERC

Last Thought on ERC-20 Smart Contract

In this article, we have managed to run the deployment of the ERC-20 smart contract on the Rinkeby chain test network. Then, we copied the address of the deployed contract in the Rinkeby Etherscan and found our created token there. Afterward, we entered the specifications of the token in the add token section of the Metamask wallet and added the created token to it. Finally, we imported our token into the Uniswap Rinkeby Testnet.

Download this Article in PDF format

3d websites

Care to Know Us More?

In Arashtad, we have gathered a professional team of developers who are working in fields such as 3D websites, 3D games, metaverses, and other types of WebGL and 3D applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Request a Quote
ThreeJS Development

How to Create an ERC-20 Token + Writing Smart Contract

In this tutorial, we are going to get familiar with the methods and events of the ERC-20 token. In addition to that, we want to create an ERC-20 token on the Rinkeby Testnet. By writing the smart contract, the deployment python file, helpful_scripts.py, and other files that help the creation and deployment of the ERC-20 token smart contract. This tutorial will go on in the next articles where we will explain the ERC-20 smart contract deeper and provide guidelines to add the created tokens to your Metamask wallet.

What Is ERC-20?

The ERC-20 is a token standard that implements an API for tokens within smart contracts. The acronym stands for Ethereum Request for Comments 20. The ERC-20 proposes a standard for fungible tokens (as opposed to Non-Fungible Tokens or NFTs that use ERC- 721) meaning that they have a property that makes each of them the same. This standard provides the following functionalities:

    1. Transfering tokens from one account to another.
    2. Tetting the balance of an account.
    3. Getting the total supply of the token available on the network.
    4. Approving whether a third party can spend tokens from an account.

Methods and Events of an ERC-20 Token

If a smart contract implements the events and methods of ERC-20, it can be called an ERC-20 token contract.

Methods:


function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)


Events:


event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)


Getting Started with the ERC-20 Token in Brownie

Now, we begin our project by typing in the terminal:

brownie init

And in our contracts folder, we create a file named OurToken.sol and paste the below contract we have copied from this link. You can change the name of the token whatever you want:


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract OurToken is ERC20 {
	// wei
	constructor(uint256 initialSupply) ERC20("OurToken", "OT") {
	_mint(msg.sender, initialSupply);
	}
}


We also create a deploy_token.py file in the scripts folder and paste the below code in it:


from brownie import accounts, config, TokenERC20, EasyToken
from scripts.helpful_scripts import get_account

initial_supply = 1000000000000000000000 # 1000
token_name = "Token"
token_symbol = "TKN"

def main():
	account = get_account()
	erc20 = TokenERC20.deploy(initial_supply, token_name, token_symbol, {"from": account})


And helpful_scripts.py goes like this:


from brownie import network, accounts, config

LOCAL_BLOCKCHAIN_ENVIRONMENTS = ["hardhat", "development", "mainnet-fork"]

def get_account():
	if network.show_active() in LOCAL_BLOCKCHAIN_ENVIRONMENTS:
		return accounts[0]
	if network.show_active() in config["networks"]:
		account = accounts.add(config["wallets"]["from_key"])
		return account
	return None
	


ERC-20 Token Smart Contract

And finally this is the contract that contains the ERC-20 methods and events (TokenERC20.sol):


pragma solidity ^0.6.0;

interface tokenRecipient {
	function receiveApproval(address _from, uint256 _value, address _token, bytes 	calldata _extraData) external;
	}

contract TokenERC20 {
	// Public variables of the token
	string public name;
	string public symbol;
	uint8 public decimals = 18;
	// 18 decimals is the strongly suggested default, avoid changing it
	uint256 public totalSupply;
	// This creates an array with all balances
	mapping (address => uint256) public balanceOf;
	mapping (address => mapping (address => uint256)) public allowance;
	// This generates a public event on the blockchain that will notify clients
	event Transfer(address indexed from, address indexed to, uint256 value);
	// This generates a public event on the blockchain that will notify clients
	event Approval(address indexed _owner, address indexed _spender, uint256 	_value);
	event Burn(address indexed from, uint256 value);
	constructor(
			uint256 initialSupply,
			string memory tokenName,
			string memory tokenSymbol
			) public {
		totalSupply = initialSupply * 10 ** uint256(decimals);  							 
                //Update total supply with the decimal amount

		balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens
		name = tokenName; // Set the name for display purposes
		symbol = tokenSymbol; // Set the symbol for display purposes
	}

	function _transfer(address _from, address _to, uint _value) internal {
		// Prevent transfer to 0x0 address. Use burn() instead
		require(_to != address(0x0));
		// Check if the sender has enough
		require(balanceOf[_from] >= _value);
		// Check for overflows
		require(balanceOf[_to] + _value >= balanceOf[_to]);
		// Save this for an assertion in the future
		uint previousBalances = balanceOf[_from] + balanceOf[_to];
		// Subtract from the sender
		balanceOf[_from] -= _value;
		// Add the same to the recipient
		balanceOf[_to] += _value;
		emit Transfer(_from, _to, _value);
		// Asserts are used to use static analysis to find bugs in your code. They should never 
                //fail 							
		assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
	}


	function transfer(address _to, uint256 _value) public returns (bool success) {
		_transfer(msg.sender, _to, _value);
		return true;
	}

	function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
		require(_value <= allowance[_from][msg.sender]); // Check allowance
		allowance[_from][msg.sender] -= _value;
		_transfer(_from, _to, _value);
		return true;
	}

	function approve(address _spender, uint256 _value) public returns (bool success) {
		allowance[msg.sender][_spender] = _value;
		emit Approval(msg.sender, _spender, _value);
		return true;
	}

	function approveAndCall(address _spender, uint256 _value, bytes memory extraData)				 
                              public returns (bool success) {
		tokenRecipient spender = tokenRecipient(_spender);
		if (approve(_spender, _value)) {
                              spender.receiveApproval(msg.sender, _value, address(this), _extraData);
			return true;
		}
	}

	function burn(uint256 _value) public returns (bool success) {
		require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
		balanceOf[msg.sender] -= _value; // Subtract from the sender
		totalSupply -= _value; // Updates totalSupply
		emit Burn(msg.sender, _value);
		return true;
	}

	function burnFrom(address _from, uint256 _value) public returns (bool success) {
		require(balanceOf[_from] >= _value); // Check if the targeted balance is enough
		require(_value <= allowance[_from][msg.sender]); // Check allowance
		balanceOf[_from] -= _value; // Subtract from the targeted balance
		allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance
		totalSupply -= _value; // Update totalSupply
		emit Burn(_from, _value);
		return true;
	}
}



We are going to explain the above contract deeper in the next article. But for now, it is time to deploy the contract itself:

brownie run scripts/1_deploy_token.py

Result:

Brownie v1.18.1 - Python development framework for Ethereum Erc20BrownieProject is the active project. Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 8545'... Running 'scripts/1_deploy_token.py::main'... Transaction sent: 0xe677ec7a8e17a7e750bb65f908bf77c92b5bbd368d975f7c3b36131544c9cf02 Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 0 TokenERC20.constructor confirmed Block: 1 Gas used: 670648 (5.59%) TokenERC20 deployed at: 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 Terminating local RPC client…

From the above results, we are going to use the address that the smart contract is deployed at the "TokenERC20" Which is 0x3194cBDC3dbcd3E11a07892e7bA5c3394048Cc87 in this case, and track in the Etherscan.

Final Word

In this article, we have got familiar with the methods and events of an ERC-20 token. Besides, we have created a project containing the ERC-20 token smart contract, the deployment python files, and so on to create a typical ERC-20 token with a certain number of tokens created in our Metamask wallet. In the tutorials, we will see how we can add these tokens in the Uniswap and also find them on the Etherscan.

Download this Article in PDF format

metaverse

Care to Know About Metaverse?

In Arashtad, we are providing custom services on 3d developments such as 3d websites, 3d models, metaverses and all 3d applications.

Arashtad Serivces
Drop us a message and tell us about your ideas.
Tell Us What You Need
Blockchain Development