Create and buy/sell tokens on Stellar blockchain

How to create tokens and trade them in the Stellar built-in marketplace

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).

1
2
3
4
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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:

1
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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:

1
2
3
4
5
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):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.

1
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()):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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:

1
2
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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:

1
2
3
4
5
6
7
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:

1
2
3
4
5
6
7
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:

1
2
3
4
5
6
7
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!