Issuing ESDT Tokens Programmatically with Python

Passing Interest
20 min readNov 10, 2021

Originally published on Publish0x.com

This guide is a brief walkthrough of the neccessary steps to issue new ESDT tokens on the Elrond Network programmatically. A minumum of programming knowledge is helpful but hopefully I can explain all of the steps clearly enough even for non-programmers. I will set up this article with an overview for people who know their way around Python (the language I will be working in. There are a few that will work.), and then move on to go into detail for those who need further explanation. Aside from running a script, most people will want to make a token with the interface in the wallet.

The relevant documentation for this process can be found on the Elrond website here.

The process will require a code interpreter. I will be explaining the process for Python and it will require a version later than 3.8, which can be found here. (Note: This will also require either Linux or Mac OS. At this time Windows may work but it is not supported by the Elrond team and you use at your own risk.

In order to use Python to communicare with the Elrond network you will need their module called erdpy, which can be found here.

There are also code examples that you can look at for reference at their github here, and another code example can be found here.

If you would like to learn more about Python, may I reccommend Real Python for a clear and easy to follow basics course and guidance on how to set up a text editor such as Sublime Text.

Now, we should begin with three things:

  • Mac OS or Linux to run a terminal
  • The latest version of Python
  • the latest version of erdpy

(You will also need to make a new wallet for testing and derive it’s PEM.) With those elements in place, lets go through the basic code required to issue your first ESDT token on the Elrond Network, using code and the Command Line.

The Basic Call To The Metachain

When communicating to the network, you will be sending a json formatted file with all of the relevant information for your call. Depending on the nature of the call they will contain different information and the call to issue new tokens will look like this:

IssuanceTransaction {
Sender: <account address of the token manager>
Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u
Value: 50000000000000000 # (0.05 EGLD)
GasLimit: 60000000
Data: "issue" +
"@" + <token name in hexadecimal encoding> +
"@" + <token ticker in hexadecimal encoding> +
"@" + <initial supply in hexadecimal encoding> +
"@" + <number of decimals in hexadecimal encoding>
}

(Note: the values for some of these differ between the docs and the updated code examples on github. I will be using the more recent information from github in this article.)

In order to send this with Python we need to set our code up like this:

from erdpy import config
from erdpy.accounts import Account
from erdpy.proxy import ElrondProxy
from erdpy.transactions import Transaction
from erdpy.wallet import signing
PEM_FILE = "{filename}.pem"proxy = ElrondProxy("https://devnet-gateway.elrond.com") # Devnet
sender = Account(pem_file=PEM_FILE)
sender.sync_nonce(proxy)
tx = Transaction()
tx.nonce = sender.nonce
tx.value = str(int(0.05 * 10**18)) # cost for the smart contract call .05 EGLD
tx.sender = sender.address.bech32()
tx.receiver = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"
tx.gasPrice = 1000000000
tx.gasLimit = 60000000
tx.data = "issue"\
"@" + <token name in hexadecimal encoding> +\
"@" + <token ticker in hexadecimal encoding> +\
"@" + <initial supply in hexadecimal encoding> +\
"@" + <number of decimals in hexadecimal encoding> +\
"@" + <canUpgrade in hexadecimal encoding> +\
"@" + <true in hexadecimal encoding>
tx.chainID = "D" # Devnet
tx.version = config.get_tx_version()
tx.signature = signing.sign_transaction(tx, sender)
tx.send(proxy)print("Transaction sent: ", tx.data)

(***You can if you want, set any of the variables in this call and also turn them on or off at any time later. This feature plus the relative ease and low cost of minting is something to keep in mind when evaluating different projects. The different variables and what they do will be a topic for another article.)

To run from the commandline, you will run the code with this line:

(erdpy-venv) iMac/~/Developer/elrond $ python3.9 elrond_token_issuer.py

and this will submit the transaction, and then print out the data. Without this line at the end, the code will still run but it will simple display the prompt again with no further information.

You can now check your wallet, remembering to sign in with devnet, (devnet-wallet.elrond.com). Your new tokens should be present in your wallet precisely to your specifications.

This code is set up to run on Devnet so that you can troubleshoot it with test EGLD until you have the confidence to run with real consequences.

When you are ready to run on mainnet,

you will switch this line:

proxy = ElrondProxy("https://devnet-gateway.elrond.com")  # Devnet

to:

proxy = ElrondProxy("https://gateway.elrond.com")

and this line:

tx.chainID = "D"  # Devnet

to:

tx.chainID = config.get_chain_id()

If you made it through all of that then congratulations, you are now .05 EGLD lighter and looking at a shiny new token in your wallet. For those of you who need more details of what exactly is happening here (and I sure would), let’s move on for a top to bottom explanation.

Step by Step

The above should be plenty of information for anyone with Python experience to digest and work out the details. As this guide is meant for those who have little to no experience coding and therefore probably little to no experience with command line, I will now go through step by step and line by line to explain every aspect of what is going on here.

The first thing to understand is command line, on Mac this is called Terminal. You can use the Finder to make a new directory to work in for this project. I recommend making a directory called “developer” and a subdirectory underneath that called “elrond” to use for all of your elrond projects. To navigate to this project directory you will need two commands, [ls, and cd]. Calling ls will list all files and subdirectories, cd will let you navigate to any subdirectory by typing cd <subdirectory name>, cd .. will take you back one directory, and python <filename.py> calls on python to run the code found in the file.

Setup

Navigate to the folder and type ls. You should see four things:

(erdpy-venv) iMac/~/Developer/elrond $ ls
<wallet>.pem*
erdpy-up.py
elrond_token_issuer.py
env/

<wallet>.pem is the keys file for the wallet that you will use. Keep this in a secure place when not in use, on a USB drive or something else offline. When you are more advanced you can tell your code to path straight to the file wherever you stored it but for now the code will look for this file in the working directory. If you do not have this file yet, we are about to make it.

erdpy-up.py is the module from the Elrond team that we installed above. It contains all the code you need to interact with the Elrond blockchain.

elrond_token_issuer.py is the text file we will make containing our code for this transaction. You may use any name you wish.

env/ is a subfolder that erdpy uses to create a virtual environment, which is a typical procedure for developers where every change to their system, versions etc. is self contained in the project. This is recommended to use when running erdpy and can be initiated with the following code, (also found in the docs):

iMac/~/Developer/elrond $ source erdpy-activate

after which your command prompt should change to

(erdpy-venv) iMac/~/Developer/elrond $

The preceding parenthetical means the environment is active. It may be neccessary to run this (or any other command) by preceding the whole thing with sudo. This stands for "superuser do" and this will prompt you for your password (the same one that protects your computer) in order to grant permission. In this case it will look like this:

iMac/~/Developer/elrond $ sudo source erdpy-activate

and in all other cases it will look like sudo <command>.

Derive PEM file

You will need to create a wallet for this and then after issuing the token, this wallet will recieve the issuance and be marked as the manager for the token. You will then use this wallet you want to alter it’s features (which includes transferring ownership to another wallet if you so wish). The PEM file contains the keys to the wallet so use caution for how you store and use this file. After you create this, anyone who comes into possession of it will be granted access. The safest approach would be to store it on an offline device such as a usb drive and make a path to it in the code. For now simply place it in the working directory and then be mindful to store it offline when not in use. Until you are using this wallet for mainnet transactions the security risk is minimal but be aware.

The process is explained here and can be derived with the following command:

erdpy --verbose wallet derive ./<wallet-name>.pem --mnemonic

where wallet-name is the name you want to give your wallet file. This can be anything you want as long as it does not contain spaces (which will confuse the command. Try - or _ instead if you need one.) This command will prompt you for your seed words. Type them in (in order with a space inbetween each) and this will generate the PEM file for you in the same directory.

Running the Code

We now have erdpy installed and have derived the PEM file. Now let’s create the code file that will run our transaction. Open your text editor, (can be any one you like but I like Sublime with the setup offered by Real Python), copy and paste the lines of code into the document and save in the same folder with the ending .py.

The code, one more time is:

from erdpy import config
from erdpy.accounts import Account
from erdpy.proxy import ElrondProxy
from erdpy.transactions import Transaction
from erdpy.wallet import signing
PEM_FILE = "{filename}.pem"proxy = ElrondProxy("https://devnet-gateway.elrond.com") # Devnet
sender = Account(pem_file=PEM_FILE)
sender.sync_nonce(proxy)
tx = Transaction()
tx.nonce = sender.nonce
tx.value = str(int(0.05 * 10**18)) # cost for the smart contract call .05 EGLD
tx.sender = sender.address.bech32()
tx.receiver = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"
tx.gasPrice = 1000000000
tx.gasLimit = 60000000
tx.data = "issue"\
"@" + <token name in hexadecimal encoding> +\
"@" + <token ticker in hexadecimal encoding> +\
"@" + <initial supply in hexadecimal encoding> +\
"@" + <number of decimals in hexadecimal encoding> +\
"@" + <canUpgrade in hexadecimal encoding> +\
"@" + <true in hexadecimal encoding>
tx.chainID = "D" # Devnet
tx.version = config.get_tx_version()
tx.signature = signing.sign_transaction(tx, sender)
tx.send(proxy)print("Transaction sent: ", tx.data)
print(tx.chainID, tx.version)

After this is saved, to run this code the command is:

(erdpy-venv) iMac/~/Developer/elrond $ python3.9 elrond_token_issuer.py

This tells python to run the script, which talks to the metachain via erdpy, which will extract payment from your wallet and issue the tokens you requested. Make sure you are using Devnet so this is all play money until you are ready for the real thing. You can check this by making sure the proxy is set to https://devnet-gateway.elrond.com.

Note, my Python command is written as python3.9. This is because I have an older system that handles their updates differently. Newer systems will be able to simply write python. You can check the version of python by the typing:

(erdpy-venv) iMac/~/Developer/elrond $ python --version

As long as this outputs a version that is 3.8 or greater you are okay.

So, let’s take a look at what exactly this is doing and what to write for those variables.

Line By Line

The top of the script shows the import statements.

from erdpy import config
from erdpy.accounts import Account
from erdpy.proxy import ElrondProxy
from erdpy.transactions import Transaction
from erdpy.wallet import signing

These are all of the elements from erdpy that we will use in our script. Edrpy has many many functions you can access to talk to the network and these are the ones we will need for this project. Each time we use one of these commands it referrs back to the code in erdpy and runs the code in there based on your inputs. If the input is absent or invalid it should come back to us with an error and explain what was wrong.

config configures our transaction for the network. Account handles our wallet and allows us to access the chain. ElrondProxy tells erdpy which chain we are using. Transaction takes all of our input and delivers it to the chain in JSON format (JavaScript Object Notation, a typical data structure used to communicate between apps on the internet). signing authorizes the transaction we send.

The JSON file we send for an issuance transaction will look something like this on the other end:

IssuanceTransaction {
Sender: <account address of the token manager>
Receiver: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u
Value: 50000000000000000 # (0.05 EGLD)
GasLimit: 60000000
Data: "issue" +
"@" + <token name in hexadecimal encoding> +
"@" + <token ticker in hexadecimal encoding> +
"@" + <initial supply in hexadecimal encoding> +
"@" + <number of decimals in hexadecimal encoding>
}

The system will take this and use the information in it to process the request. The python script takes the information, creates and sends this information in this JSON format to the chain using erdpy (which is more code written by the elrond team for these purposes. There are many modules like this depending on the programming language you are writing in).

Next we have the wallet.

PEM_FILE = "{filename}.pem"

This tells python to take the PEM from the same directory and store it in a variable called PEM_FILE. This will allow us to call the PEM anywhere else in our script by simply typing PEM_FILE. This looks for the file in the same directory so if no file exists there the script will stop and report an error. Notice the quotes, this is important and tells python that it is a string. Be sure to replace the {} and everything inbetween with your filename. This is also where you can path it directly to another device when you know how to do that, and in that case it would look there.

proxy = ElrondProxy("https://devnet-gateway.elrond.com")  # Devnet
sender = Account(pem_file=PEM_FILE)
sender.sync_nonce(proxy)

We access the network (devnet) using ElrondProxy() and store that in a variable called proxy. Next we access our wallet with Account() and tell it to use our variable PEM_FILE (in which we have stored our wallet keys) and send it as the variable pem_file. Notice the change in case, Account uses a variable called pem_file, lower case. This is set in stone and cannot be changed. Our variable is named the same but with uppercase, and you may use any other name if you find this confusing (such as keys, etc). You will enter here pem_file=<variable holding the wallet>, and all of this will access the wallet and then store it in our code in a variable called sender.

Account() is a type of data structure called a Class. A Class can hold all sorts of information, including more functions. When store Account() in sender, we are turning sender into an object with all of those variables and scripts held within it. So sender.sync_nonce(proxy), is calling the funcion called sync_nonce that is part of Account(). We send that our variable called proxy, which tells erdpy that we are accessing the wallet on the network stored in proxy (which is devnet here). These three lines sets up our transaction to take place with our wallet, on devnet.

tx = Transaction()
tx.nonce = sender.nonce

Now we set up the transaction. We start by calling the Transaction() and storing it in a variable called tx. This means tx will have all of the properties of Transaction(). This will organize all of our data neatly into a JSON format for the chain.

Next we call the nonce for the transaction from sender.nonce and assign it to tx.nonce.

tx.value = str(int(0.05 * 10**18)) # cost for the smart contract call .05 EGLD

This line sets the value for the transaction. In this case as noted, the cost for the smart contract. These fees are subject to change and it’s always good to keep up with the latest fees when putting scripts together. This is a flat fee required to issue new tokens on the chain. This is the line that you should pay attention to when deciding if you are ready to try this on mainnet because a mistake in your code may see this amount extracted from your wallet and give you nothing back in exchange. The transaction may error and no fee at all extracted from your wallet but there are some cases where this will happen. Just be aware.

The line itself looks far more complicated than it is. It is easiest to read this from the inside out.

0.05 * 10**18

In Python * stands for multiplication and ** stands for exponent. This expression means 0.05 times 10 to the power of 18. The above expression is therefore 50000000000000000 and this number is the same as .05 EGLD. EGLD has 18 decimal places and when sending transactions on the chain it is expecting them to be written in this format. This goes the same for ESDT tokens. When issuing you will be expected to send a few properties by default in order to issue. One of these are the decimal places. As each token may have a different number of decimals you should look at the token on the chain to see the details for each one, and be mindful of your own when sending. A token with 8 decimals for instance at 0.05 could be written as 0.05 * 10**8, which would mean 5000000. Both of those terms (0.05 * 10**8 and 5000000) are the same number and you may write them either way (or any other way that leads to the value you are looking for. Don't make the mistake of setting the value to 0.05, because this will be less than the minimum on the system, which is 1 and sending of 1 of a coin with 18 decimals would be read as 0.000000000000000001.

Confused yet? It will make sense in time. We have to write out the whole values of these tokens with all decimal places as a whole number. So, 420 tokens with 8 decimal places would have to be written as 420 and 8 zeroes or 42000000000. 420 * 10**8 is a nicer more readable way to write that out. If you simply write 420 hoping to send 420, you will be dissapointed to see your transaction for 0.0000042.

This line then converts this to an integer with the function int(), and then a string with the function srt(). Integers (whole numbers) and strings (any form of text) are two basic ways of handling data in a script. You can learn more about the differences of what these mean from Real Python or any other school for programming but essentially, it's important to understand that 5 and "5" are two entirely different expressions and are required to be in their proper format for the specific function you are using. One essential difference is that 5 + 5 equals 10 but "5" + "5" takes those strings and puts them together, giving you "55".

So, this line takes the number 50000000000000000, (standing for 0.05 EGLD, the price of issuing tokens), and stores it as a string "50000000000000000" in the value property of the Transaction().

tx.sender = sender.address.bech32()
tx.receiver = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"

This takes the address of the sender (via the Account() object stored in our sender variable), and sets it as the sender property of our transaction variable, tx (via Transaction()). Then it stores an erd address (note, as a string) in the reciever property. This address is for the smart contract on the chain that handles issuance. This is also where you would store the address of the recipient when you are sending tokens.

tx.gasPrice = 1000000000
tx.gasLimit = 60000000

These numbers are set to handle the gas fees when sending transactions. Note that these are different from value from before, which was specifically for the smart contract. When sending tokens to someone the value to be set at 0 EGLD. These numbers, gasPrice and gasLimit are taken from the code examples provided by Elrond and I am still not entirely sure about these two lines other than the limit is what you will not exceed and the price is what you are paying. The system will use only the gas it needs and give the amount that you do not use for gas back to you (with an ugly red, if benign too much gas used error in the explorer. It is okay to overshoot here, but the transaction will fail if you send too little. The exact right goldilocks zone is something I am still figuring out. It will be different depending on the amount of data sent (such as in the note), with more data costing a bit more in gas. In any case, use these values and you should be fine. I will update when I have a better understanding.

tx.data = "issue"\
"@" + <token name in hexadecimal encoding> +\
"@" + <token ticker in hexadecimal encoding> +\
"@" + <initial supply in hexadecimal encoding> +\
"@" + <number of decimals in hexadecimal encoding> +\
"@" + <canUpgrade in hexadecimal encoding> +\
"@" + <true in hexadecimal encoding>

Now we come to the actual line being sent to the chain. This will tell the chain precisely what we want it to do. If you look at the note from any ESDT transaction you recieve you will see something like this

ESDTTransfer@4145524f2d343538626266@02DA0279145E3100

This is a similar format to what we are writing here. The first part explains what type of transaction it is and the second part, separated by @'s tells us the details, encoded in hexadecimal. The markers at the ends of these lines simply adds them all together into one long string and breaks up the line allowing us to write them in a more readable format.

The whole line will look like: issue@<token name in hexadecimal encoding>@<token ticker in hexadecimal encoding>@<initial supply in hexadecimal encoding>@<number of decimals in hexadecimal encoding>@<canUpgrade in hexadecimal encoding>@<true in hexadecimal encoding>

Which is hell to look at but just wait until we translate them into hexadecimal. Hexadecimal is base 16, the number system that is used often computing because 16 fits more nicely into the binary system (base 2, 0–1) than decimal (our usual 0–9, base 10 number system). So, binary is a base of 2 numbers 0, 1 and counting higher than one goes as 0, 1, 10, 11, 100, 101, 110, 111, etc. Those numbers represent 0, 1, 2, 3, 4, 5, 6, and so on in our usual base 10 system. They start over after they reach 1 and add a zero just as we do in base 10 when we reach 9. You can do this with base 3 (0, 1, 2) and every other base you can imagine. Base 8 is also popular for computing for similar reasons as base 16. So then what do we do when counting in a system with more numbers than… we have numbers? We use letters. Counting in base 16 goes 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f. f in this case is our 15 and to write 16 then, you would write 10.

You do not need to know the math behind this at all to understand this code. This is merely to show what is happening when you see these outputs and what are all of those letters doing with those numbers???… It is entirely possible to do this programmatically in python and enter your own plain human readable text, base ten numbers (also called decimal if you want to read more) and let the computer handle the rest but I want you to see what is happening here.

Use these two encoders one for strings here, and one for numbers here. Note the difference between these. One is converting decimal numbers (the ones we are all used to) to hexadecimal (the one the chain reads), and the other one is converting text to hexadecimal. In coding, every letter has a numerical value attatched to it and this is taking that and turning it into a hexadecimal number. The difference is important, for if you use the wrong one the chain won’t know what you are trying to say.

Which one is which is easy to understand. Elements that are words or letters are strings, the ones that are numbers are numbers and each needs a different encoder. Encoding a number as a string and vice versa will result in an error or a mistake.

Of these six lines, according to the docs you only need the first four. The last two are simply there as convenience so that we can update the tokens later without first changing that feature. You can in fact, set as many of the available features as you want at issuance but, let’s keep it simple for now.

Lets say we want to make a token that is called Elephant Token. Right off, we will have to remove that space. Either call it Elephant_Token, Elephant-Token, ElephantToken, or drop token off altogether since it is a token after all. I prefer to simply say Elephant or maybe Elephantium.

We need a name, a ticker, a supply, and decimal places. So let’s make one called Eliphantium, ticker ELPT, a supply of 420,420,420,420 and 18 decimal places like EGLD. The first two are strings and the second two are numbers so with the encoders we will get:

Name: 456C697068616E7469756D
Ticker: 454C5054
Supply: 61E3028344
Decimals: 12

Try this in reverse to see that you’ve got it right.

456C697068616E7469756D = Eliphantium
454C5054 = ELPT
61E3028344 = 420420420420
12 = 18

Nice. But wait, 420420420420 and 12 decimals will give us only .420420420420 tokens… We need to add the zeros for the decimal places too like before. 420420420420 * 10**18 = 420420420420000000000000000000 which is 54E739EF2D4E77128A2900000

Okay now we have:

Name:456C697068616E7469756D
Ticker:454C5054
Supply: 54E739EF2D4E77128A2900000
Decimals: 12

One last step before this will work for us. The number of digits need to be divisible by two in order for the smart contract to use them. This is one of those errors that will run successfully and use your fees but then return nothing to your wallet if mistaken. Take a look at our supply, 54E739EF2D4E77128A2900000. Is this divisible by 2? Let's count: 5 4E 73 9E F2 D4 E7 71 28 A2 90 00 00... no, we have an odd number of digits here. The solution to this is to add a zero on the first pair, making it 05 4E 73 9E F2 D4 E7 71 28 A2 90 00 00. Does this still work for our code? Let's double check in the decoder:

54E739EF2D4E77128A2900000 = 420420420420000000000000000000

Great!

Adding these to our code now we get:

tx.data = "issue"\
"@" + "456C697068616E7469756D" +\
"@" + "454C5054" +\
"@" + "054E739EF2D4E77128A2900000" +\
"@" + "12" +\
"@" + <canUpgrade in hexadecimal encoding> +\
"@" + <true in hexadecimal encoding>

Notice how we are putting these in quotes so they can be put together and sent as one long string. Now the last two which is simply encoding those two as strings in hexadecimal. We now have:

tx.data = "issue"\
"@" + "456C697068616E7469756D" +\
"@" + "454C5054" +\
"@" + "054E739EF2D4E77128A2900000" +\
"@" + "12" +\
"@" + "63616E55706772616465" +\
"@" + "74727565"

Which will send the following string to the smart contract when we process the transaction:

“issue@456C697068616E7469756D@454C5054@054E739EF2D4E77128A2900000@12@63616E55706772616465@74727565”

Take a look at the note now on any ESDT transfer you recieve and see if you can decipher what happened from the hexadecimal strings encoded there.

tx.chainID = "D"  # Devnet
tx.version = config.get_tx_version()
tx.signature = signing.sign_transaction(tx, sender)

These lines in turn, set the chain ID to Devnet, grabs the version from the chain and assigns it to version, and then grabs the signature for our wallet and assigns it to signature for the transaction. Everything before calling Transaction() was setting up what we need to begin. From calling Transaction() to here we were setting all of the neccessary elements in the transaction object (that we have been storing in tx)

We now have everything we need and can send the transaction with one line:

tx.send(proxy)

This will take all of the information we just stored in the Transaction() object we just created and send is all to the proxy (in this case Devnet), and if all goes well you will see nothing in the command line and be presented with another prompt. This is why I added this last print statement, so that you will be notified of the transaction completing, and what was actually sent:

print("Transaction sent: ", tx.data)

In our case right now this will display:

Transaction sent: issue@456C697068616E7469756D@454C5054@054E739EF2D4E77128A2900000@12@63616E55706772616465@74727565

Now check your wallet on devnet and see if the transaction showed up in the queue. Look for the tokens now in your wallet. Did they arrive? Look at the transaction on the explorer, can you read all of it? Any of it? Try it again with more tokens and different names and numbers and get familiar with how the whole thing operates. Later on we will explore how to send these token, how to send multiple transactions at once, and how to edit the features and what all of them mean, and then do the same with NFTs.

I hope this tutorial was easy to follow. If you have any further questions or need help troubleshooting your code, be sure to stop by the ESDT Managers Telegram channel and say hi!

Thanks for stopping by my Elrond blog! Please share with others and let me know if there are topics you would like me to dive into for future articles

If you would like to contribute and help me write more articles like these you may tip me with $EGLD, ESDT tokens, or NFTs to my Elrond address:

erd1ztcpuncemxcpes6t58cs28acre0zarjh6zj32a4e9vyhfp3g5agq0qtvqv

or to my herotag: pippiwestwood.

Don’t have a Maiar wallet? Download one here and start your Elrond journey! Use my referral code if you want: txs89adg0p to get $10 in free $EGLD.

0x0x Pippi 💕

Social Media:
Twitter: @pippiwestwood
Telegram: @pippiwestwood
Discord: Saoirse — シャリシャ#2112
eMoon: SaoirseNS
Publish0x: PippiWestwood

--

--