If you enjoy coding in Python and want to explore the Stellar blockchain, here is a very basic tutorial to learn how to create a custom token and trade it in the Stellar built-in marketplace.
As shown in my previous post, we need the py-stellar-base
Python3 library. Also, we are going to use the Stellar testnet
(so we won’t waste our Lumens for now).
Feel free to rearrange and modify the code below in order to complete your tests. You’ll also appreciate the speed of Stellar blockchain since transactions are validated in seconds!
For further information, please refer to the official Stellar documentation for developers and the py-stellar-base
API
reference. Also check this blog post by Stellar co-founder Jed Mccaleb.
Create some test accounts
First of all, we need some wallets. For this example, let’s create four accounts (this can be directly done in the python
CLI
, if you prefer).
from stellar_base.keypair import Keypair
keypair = Keypair.random()
print('address: {}\nsecret: {}'.format(keypair.address().decode(), keypair.seed().decode()))
Let’s give a name to those accounts and create a dict
variable like the following one, using the public addresses and the corresponding secret seeds you just computed:
accounts = {
'amadeus': {
'address': 'PUBLIC-ADDRESS-FOR-AMADEUS',
'password': 'SECRET-SEED-FOR-AMADEUS'
},
'bella': {
'address': 'PUBLIC-ADDRESS-FOR-BELLA',
'password': 'SECRET-SEED-FOR-BELLA'
},
'charlie': {
'address': 'PUBLIC-ADDRESS-FOR-CHARLIE',
'password': 'SECRET-SEED-FOR-CHARLIE'
},
'debbie': {
'address': 'PUBLIC-ADDRESS-FOR-DEBBIE',
'password': 'SECRET-SEED-FOR-DEBBIE'
}
}
So, our four accounts are named amadeus
, bella
, charlie
and debbie
.
Also set the following constant:
base_url = 'https://horizon-testnet.stellar.org'
Note that setting base_url = 'https://horizon.stellar.org'
and using LIVENET
instead of TESTNET
in the following, all operations will be completed in the actual Stellar blockchain - except that you must provide initial funds to your wallets.
Before going on, let’s define a useful function in order to check all native and custom balances for an account:
import requests
from time import sleep
def get_balance(account):
"""
account: account codename
"""
print('--- {} ---'.format(account))
r = requests.get(base_url + '/accounts/' + accounts[account]['address'])
if r.status_code == 200:
balances = r.json()
balances = balances.get('balances', [])
for b in balances:
asset = b.get('asset_type')
if asset == 'native':
print('{} XLM'.format(b.get('balance')))
else:
print('{} {} - issuer: {}'.format(b.get('balance'),
b.get('asset_code'),
b.get('asset_issuer')))
else:
print('account not found')
print('')
# check all accounts
for k in accounts.keys():
get_balance(k)
sleep(5) # don't bother testnet too much
Alternatively, use the awesome Stellar Expert GUI.
Now, let’s create amadeus
and bella
accounts asking Friendbot for some XLM
funds:
r = requests.get('{}/friendbot?addr={}'.format(base_url, accounts['amadeus']['address']))
print(r.text)
sleep(5)
r = requests.get('{}/friendbot?addr={}'.format(base_url, accounts['bella']['address']))
print(r.text)
We are going to use charlie
as token issuer and debbie
as token distribution account. In order to complete the following tasks, the minimum balance required to charlie
should be 1.00002 XLM
. Anyway, amadeus
is going to donate 5 XLM
to charlie
as in the following code (actually, account charlie
is being created, so we define create_account()
function):
from stellar_base.builder import Builder
accounts = {
...
}
base_url = 'https://horizon-testnet.stellar.org'
def create_account(from_account, to_account, amount):
"""
from_account: account codename making the initial funding
to_account: account codename being created
amount: initial balance (string, XLM)
"""
builder = Builder(secret=accounts[from_account]['password'],
horizon_uri=base_url,
network='TESTNET')
builder.append_create_account_op(destination=accounts[to_account]['address'],
starting_balance=amount,
source=accounts[from_account]['address'])
builder.sign()
response = builder.submit()
print(repr(response))
create_account('amadeus', 'charlie', '5')
In the same way, bella
is going to create debbie
account sharing 2000 XLM
with her. Please note that the amount must be specified as string in order to work properly.
create_account('bella', 'debbie', '2000')
Create your custom token
Choose a name for your token. Tokens in the Stellar blockchain are uniquely identified by the asset code and the issuer address. For this example we will use NOOBCOIN
.
The distribution account (debbie
) must set a trustline to the issuer (charlie
). We define a function to do this (set_trustline()
):
def set_trustline(from_account, asset_code, asset_issuer):
"""
from_account: account codename creating the trustline
asset_code: asset code to trust
asset_issuer: asset issuer to trust
"""
builder = Builder(secret=accounts[from_account]['password'],
horizon_uri=base_url,
network='TESTNET')
builder.append_change_trust_op(asset_code=asset_code,
asset_issuer=accounts[asset_issuer]['address'],
source=accounts[from_account]['address'],
limit=None)
builder.sign()
response = builder.submit()
print(repr(response))
set_trustline('debbie', 'NOOBCOIN', 'charlie')
Now charlie
sends, say, one million NOOBCOIN
to debbie
. In order to do this, let’s define a function (do_payment()
) that can be also reused for any other payments, including XLM
:
def do_payment(from_account, to_account, amount, asset_code='XLM', asset_issuer=None):
"""
from_account: account codename making the payment
to_account: account codename receiving the payment
amount: payment amount (string)
asset_code (optional): asset code (default = XLM)
asset_issuer (optional): asset issuer codename (default = None and not required if XLM)
"""
issuer = accounts[asset_issuer]['address'] if asset_issuer is not None else None
builder = Builder(secret=accounts[from_account]['password'],
horizon_uri=base_url,
network='TESTNET')
builder.append_payment_op(destination=accounts[to_account]['address'],
amount=amount,
asset_code=asset_code,
asset_issuer=issuer)
builder.sign()
response = builder.submit()
print(repr(response))
do_payment('charlie', 'debbie', '1000000', 'NOOBCOIN', 'charlie')
And we don’t want to generate more NOOBCOIN
in the future, so let’s lock charlie
account forever (well, at least until next testnet
reset):
builder = Builder(secret=accounts['charlie']['password'],
horizon_uri=base_url,
network='TESTNET')
builder.append_set_options_op(source=accounts['charlie']['address'],
master_weight=0,
low_threshold=1,
med_threshold=1,
high_threshold=1)
builder.sign()
response = builder.submit()
print(repr(response))
amadeus
and bella
are very interested in our brand new token, so they are setting a trustline to the token issuer (charlie
) too, just like debbie
did before:
set_trustline('amadeus', 'NOOBCOIN', 'charlie')
set_trustline('bella', 'NOOBCOIN', 'charlie')
At this point, we have the following balances:
--- amadeus ---
0.0000000 NOOBCOIN - issuer: <charlie's address>
9994.9999800 XLM
--- bella ---
0.0000000 NOOBCOIN - issuer: <charlie's address>
7999.9999800 XLM
--- charlie ---
4.9999800 XLM
--- debbie ---
1000000.0000000 NOOBCOIN - issuer: <charlie's address>
1999.9999900 XLM
Note: the cost of each transaction is 0.00001 XLM
.
Trading our token
Use Stellar marketplace to exchange tokens with other assets, including XLM
. The easiest way to do so in the livenet
is to use StellarX. For the moment, we want to do some more tests in our sandbox.
Exchange assets on the Stellar blockchain is as easy as to put an offer on it. So, define the following manage_offer()
function:
def manage_offer(account, selling_code, selling_issuer, buying_code, buying_issuer, amount, price):
"""
account: account codename putting this offer in the blockchain
selling_code: code of the asset to sell
selling_issuer: codename of the issuer for the above parameter or None if selling_code = 'XLM'
buying_code: code of the asset to buy
buying_issuer: codename of the issuer for the above parameter or None if buying_code = 'XLM'
amount: amount of asset being sold (string)
price: price of 1 unit of selling in terms of buying = selling_amount / buying_amount (string)
"""
selling_issuer = accounts[selling_issuer]['address'] if selling_issuer is not None else None
buying_issuer = accounts[buying_issuer]['address'] if buying_issuer is not None else None
builder = Builder(secret=accounts[account]['password'], horizon_uri=base_url, network='TESTNET')
builder.append_manage_offer_op(selling_code=selling_code,
selling_issuer=selling_issuer,
buying_code=buying_code,
buying_issuer=buying_issuer,
amount=amount,
price=price,
offer_id=0) # new offer
builder.sign()
response = builder.submit()
print(repr(response))
Let’s suppose that debbie
wants to sell 1000 NOOBCOIN
for 250 XLM
. That’s a 0.25 XLM
each NOOBCOIN
. So debbie
makes an offer with the following code:
manage_offer(account='debbie',
selling_code='NOOBCOIN',
selling_issuer='charlie',
buying_code='XLM',
buying_issuer=None,
amount='1000',
price='0.25')
Try it yourself on Stellar Expert by searching for the name of your token.
Now bella
wants to buy 800 NOOBCOIN
at the above price (1 NOOBCOIN
= 0.25 XLM
), so she issues the following offer:
manage_offer(account='bella',
selling_code='XLM',
selling_issuer=None,
buying_code='NOOBCOIN',
buying_issuer='charlie',
amount='200',
price='4')
An automatic exchange between bella
and debbie
is performed at once! And there are still 200 NOOBCOIN
available from debbie
’s initial offer. Get a balance overview by using the above get_balance()
function.
Now amadeus
offers to buy some tokens at 0.5 XLM
each. He makes the following offer:
manage_offer(account='amadeus',
selling_code='XLM',
selling_issuer=None,
buying_code='NOOBCOIN',
buying_issuer='charlie',
amount='1000',
price='2')
What happens now? Luckily, amadeus
bought the remaining available tokens (200
) at the lower price (50 XLM
total). But he’s still selling away the remaining 950 XLM
for 1900 NOOBCOIN
, until his order is matched or canceled.
But now it’s your turn: enjoy!
Exercise. Using
requests
, write a function which search for current best price of a token, if any offer exists.
Happy trading on the Stellar blockchain!