【英】Python 如何在 Stellar 网络上创建自定义令牌

274 阅读13分钟
原文链接: medium.com

How to create a custom token on Stellar network in Python

https://unsplash.com/photos/2M2SpKOtTh8

A few months back I made a post about Stellar that how you can use it in your Python applications. In this post, I am going to discuss that how you can create your own custom token, a.k.a, a coin programmatically in Python. Before I get into the code, I’d like to discuss what are tokens and their background, how they are different from Alt-coins and some Stellar network concepts. This post is lengthy so read it when you have ample time to read.

What are Tokens?

The term token is not new and many of us would have experienced the application of it one way or other. Tokens are defined as: a thing serving as a visible or tangible representation of a fact, quality, feeling, etc.

Basically, you come up with something visible that maps with some other thing, physical or not physical that does not matter. When I not physical, it means that it could be used to express your feelings. For instance, you give a gift to someone as a token of thanks to express your feelings for the person. In the world of business, tokens are usually a piece of paper or some coin like thing that you can exchange for a particular value in a shop. Let me come up with an example.

Being a South Asian, especially as a Karachiite, I am a big fan of Biryani, suppose I want to eat Biryani and visit some particular Biryani shop in my city (shout-out to Madni Biryani!). When I order Biryani, say, a Chicken or Beef, I pay at the counter and the guy over there hands me a token. The color of tokens is different based on what kind of Biryani I am looking for. I then go to the guy giving Biryani to customers, hand him over the token and he gives me the parcel of Biryani. Sounds very very familiar, right? What I just discussed is something that happens in the world of Crypto Tokens as well.

What is a Crypto Token?

From Investopedia:

Crypto tokens are special kind of virtual currency tokens that reside on their own blockchains and represent an asset or utility.

Does it sound familiar to the example I discussed above? Replace asset with the parcel of Biryani and blockchains with, um, kind of personal network where you grab the token from the counter guy and then give to the parcel guy. In both scenarios, you do return the token back to the original entity to get your desire good, at least it works like that in the Stellar world.

Now, I’d like to discuss a few Stellar related concepts to get the better idea of custom token creation.

What is a Stellar?

From Wikipedia:

Stellar is an open-source protocol for exchanging money using blockchain technology.[13] The platform’s source code is hosted on Github.[41] The Stellar network can quickly exchange government-based currencies with 2 to 5 second processing times. The platform is a distributed ledger maintained by aconsensus algorithm, which allows for decentralized control, flexible trust, low latency, and asymptotic security.

Unlike coins like BTC, Ethereum or many others there’s no concept of mining in it. You can learn further about it by visit this link. Let’s discuss different Stellar terminologies

Stellar Terminologies

Assets

In the Stellar world, an asset is something that could be tracked, held and transferred within blockchain. An asset could be anything: a currency like USD or PKR, a cryptocurrency, for instance, Bitcoin, a commodity, for instance, Gold, Silver or Iron or some fruit, for instance, Orange. In our case, the token will represent 1 plate of Chicken Biryani. You might be wondering how is it possible that the Stellar network could have both Bitcoin and Biryani on the network, the reason is that every asset or token on Stellar is actually holding credit from someone who issues it. There is no actual Bitcoin or, in our case, actual parcel of Biryani being sent over the network. This simple concept has made Stellar pretty good to cover any real-world use case. For instance, you are running a local currency exchange and deal in USD, Euro and Pakistan Rupees (PKR). You can offer three different kinds of tokens to your customers, named, USD_COOL, EURO_COOL, and PKR_COOL. Customers who hold USD_COOL can get USD paper notes once they give you back X amount of USD_COOL tokens.

Anchors

Anchors/Issuers are entities that issue certain kind of asset on the Stellar network. They maintain an account which creates a new asset and distributes to others. Anyone who holds an account on the Stellar network can issue a new asset. It could be a shop, cafe, community, bank, country or even an individual! Yeah, if I am bored and have nothing to do with life, I’d create a token, named, FRIEND_COIN and tell all my friends that anyone who does me a favor will get 1 FRIEND_COIN, those who earn 10 FRIEND_COIN will be entitled to have a free dinner with me. See, how easy it is! Crypto tokens are not all about minting money by mining or trading, you could have some pretty interesting and useful use-cases with it.

Trustlines

This is a very interesting concept that maps with the Biryani example I gave above. Trustline is kind of an agreement between an asset issuer/anchor and asset holder. As I told above, you give cash money to the counter boy and the token is issued to you a token for the plate of Biryani which you eventually get once you hand over the token to the person deals in parcels. In other words, a trustline is established between you and the Biryani shop owner when you give him money and in return, he gives you corresponding tokens. Otherwise, there is no worth of the token itself without the trust. In the Stellar world, a transaction of the token is not possible unless a trustline is not there.

Alright, now let’s create our own token by automating the entire process in Python with help of Stellar SDK.

The business Use-Case

Suppose I have been contacted by Madni Biryani’s management, they heard of blockchain and tokens and now willing to issue their own custom token. The purpose of this exercise is to provide a value-added service to their customers as well as transparency. A customer can pay via wire transfer, credit card or easyPaisa, once verified, their wallet will be created and certain tokens will be transferred which they can use to buy Biryani or to transfer to their friends and family in their respective wallets in short interval of time. After a long dinner table discussion (Offcourse with a spicy plate of Chicken Biryani and Raita) it was decided that the coin/token name will be BIRYANI. 1 BIRYANI equals to 1 plate of Chicken Biryani. Total token to be created will be 10,000 (This limit makes sense. Even the person at the counter does have a limited token supply which he distributes to his customer). Customers will have the facility to transfer token online via the web or mobile app and get their Biryani at home or use some kind of on-site setup to transfer token to the restaurant and get a plate of Biryani. Since this is a digital facility, someone can transfer a BIRYANI coin to his friend who runs short of the amount at the place and enjoys the meal. The contract is signed and as a consultant and solution provider, I have to setup Stellar based blockchain for them as well as a software interface that interacts with Stellar Blockchain(The software part is not the scope of this post. I might cover as a separate part, if possible).

Alright, now let’s write a bit of code to automate the entire workflow of token creation. I will be using Testnet for this post and will be connecting to it via Horizon Server which provides Restful APIs to interact with the Stellar core.

Development

The very first thing we have to do is to install the Stellar SDK for Python. I will use pip for installation purpose.

pip install stellar-base

Once installed, we will be performing certain steps to make it happen.

Accounts Creation

The very first step of creating the asset is to arrange the account that will issue the tokens to customers. This account is called the Issuer Account which belongs to the business entity, in our case, it will be handled by the Biryani shop owner. To map with existing workflow, we are actually going to create the drawer in which the counter guy will put all tokens that going to be given to customers. In fact, I will create a couple of accounts: one for the issuer and other for the customer who will be given the token.

from stellar_base.keypair import Keypair
def gen_address():    kp = Keypair.random()    publickey = kp.address().decode()    seed = kp.seed().decode()    return publickey, seed
if __name__ == '__main__':    address, password = gen_address()    print(address, password)

The code above is generating the string of the public and private kay. The public key will actually be the wallet address here. Every time you run this code it will generate random keys. In my case, the generated keys are given below. We will be using this for the issuer account.

if __name__ == '__main__':    ISSUER_ADDRESS = 'GCYRKVRGM5OT3QINELNL7EOSW3RCRHVPMHWM4RKPVWLMVFYN2GCGLUD7'    ISSUER_SEED = 'SCHNS2RPCPBO74F367QKUJIU4L6U4L3HPQPJJW64V2MTKJSDQNAE576Q'

A couple of constants created. Next, we have to register this account to the Testnet so that these accounts could be created. You can do this by funding this account. For TestNet it’s done by the friend bot who distributes 1,000 lumens to the account. If you try to access the account details via Horizon’s Account API, it will return something like below

{
"type": "https://stellar.org/horizon-errors/not_found",
"title": "Resource Missing",
"status": 404,
"detail": "The resource at the url requested was not found.  This is usually occurs for one of two reasons:  The url requested is not valid, or no data in our database could be found with the parameters provided."
}

Let’s give some amount to the issuer now.

import requests
def fund_account(address):    r = requests.get('https://horizon-testnet.stellar.org/friendbot?addr=' + address)    return r.text
if __name__ == '__main__':    ISSUER_ADDRESS = 'GCYRKVRGM5OT3QINELNL7EOSW3RCRHVPMHWM4RKPVWLMVFYN2GCGLUD7'    ISSUER_SEED = 'SCHNS2RPCPBO74F367QKUJIU4L6U4L3HPQPJJW64V2MTKJSDQNAE576Q'    result = fund_account(ISSUER_ADDRESS)

I am using requests library to make a GET request for funding the account. If all goes well it will produce the result given below:

{  "_links": {    "transaction": {      "href": "https://horizon-testnet.stellar.org/transactions/eead4d417b9f6dc735216d9041d811baf042e3631c373e325070f33384707583"    }  },  "hash": "eead4d417b9f6dc735216d9041d811baf042e3631c373e325070f33384707583",  "ledger": 9054841,  "envelope_xdr": "AAAAABB90WssODNIgi6BHveqzxTRmIpvAFRyVNM+Hm2GVuCcAAAAZABiwhcAAd/kAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAsRVWJmddPcENItq/kdK24iier2HszkVPrZbKlw3RhGUAAAAXSHboAAAAAAAAAAABhlbgnAAAAEA9LreUnmBjv42x1PqUwLn+t6N1zQKJlPxztMUCy2iqpgdhyA+oWBjx08+83dFeNPAk/duMs5ZP1DQnBgI7J+gC",  "result_xdr": "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA=",  "result_meta_xdr": "AAAAAAAAAAEAAAADAAAAAACKKnkAAAAAAAAAALEVViZnXT3BDSLav5HStuIonq9h7M5FT62WypcN0YRlAAAAF0h26AAAiip5AAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAwCKKnkAAAAAAAAAABB90WssODNIgi6BHveqzxTRmIpvAFRyVNM+Hm2GVuCcAAfzKPoeba8AYsIXAAHf5AAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQCKKnkAAAAAAAAAABB90WssODNIgi6BHveqzxTRmIpvAFRyVNM+Hm2GVuCcAAfzEbGnha8AYsIXAAHf5AAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA"}

Stellar provides a neat tool to debug every operation output, called XDR Viewer. In this case, I am going to check envelope_xdr content. Upon pasting the value of envelope_xdr it generated a unique public URL, something like that. If you check the decoded output, you will see something like given below:

XDRViewer in action

The source account is actually the wallet address of the friend bot who is distributing 1K XLM on the creation of the account. The destination account is our issuer account. The startingBalance field tells the amount transferred to this account. Now, if you visit the API link for checking the balance of the issuer account, you will see something entirely different.

"balances": [    {      "balance": "10000.0000000",      "asset_type": "native"    }  ],

I just put the relevant chunk here. You can see the balance field tells the total amount and asset_type tells that it’s a native currency that is XLM.

Try to fund the account again and this time friend bot will remind you that:

{  "type": "https://stellar.org/horizon-errors/https://stellar.org/horizon-errors/transaction_failed",  "title": "Transaction Failed",  "status": 400,  "detail": "The transaction failed when submitted to the stellar network. The `extras.result_codes` field on this response contains further details.  Descriptions of each code can be found at: https://www.stellar.org/developers/learn/concepts/list-of-operations.html",  "extras": {    "envelope_xdr": "AAAAABB90WssODNIgi6BHveqzxTRmIpvAFRyVNM+Hm2GVuCcAAAAZABiwhcAAd/nAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAsRVWJmddPcENItq/kdK24iier2HszkVPrZbKlw3RhGUAAAAXSHboAAAAAAAAAAABhlbgnAAAAEC1+8qn2Uri6cJdn0h7DWDP/NpqZU1VfAjFmYNQ1RrASb35T5X4fm/ec2uedHIsladn8/x45ksXS4e+iearS/cG",    "result_codes": {      "transaction": "tx_failed",      "operations": [        "op_already_exists"      ]    },    "result_xdr": "AAAAAAAAAGT/////AAAAAQAAAAAAAAAA/////AAAAAA="  }}

So the moral lesson is: don’t test your friend bot just for sake of testing.

Alright, so the issuer account is created and funded. Now it’s time to create our own asset that is, BIRYANI token.

Asset Creation

Creating an asset on Stellar is not difficult, you call up the relevant method and it’s done.

from stellar_base.asset import Asset
def create_asset(code, issuer_address):    asset = Asset(code, issuer_address)    return asset
if __name__ == '__main__':    ISSUER_ADDRESS = 'GCYRKVRGM5OT3QINELNL7EOSW3RCRHVPMHWM4RKPVWLMVFYN2GCGLUD7'    ISSUER_SEED = 'SCHNS2RPCPBO74F367QKUJIU4L6U4L3HPQPJJW64V2MTKJSDQNAE576Q'    ASSET_CODE = 'BIRYANI'
new_asset = create_asset(ASSET_CODE,ISSUER_ADDRESS)    print(new_asset)

create_asset takes two parameters: The asset name or code and the issuer address. Asset creation will always depend on these two things as they are paired. This is why someone else can also create BIRYANI token because there will be a different issuer at that time. Don’t forget that Stellar holds a credit on behalf of the issuer. Two entities could have USD and they could issue it to their friends/customers. Since Stellar provides a facilitating channel for the transaction between two parties, it does not enforce it’s own rules when it comes to creating asset on the network.You can learn more about assets by visiting this link.

Establish Trustline

OK, so the next step is establishing the trustline between the issuer of assets. That is, a trustline between the issuer account and the distributor account, the account which will eventually hold newly created BIRYANI coins. In order to perform this step, we should have a distributor account. We will repeat the process of get_address() and fund_account() to create a new Stellar wallet and then the trustline will be established via code. After repeating the processes I got the following customer account details:

CUSTOMER_ADDRESS = 'GCDBCPDOTXTJMJVKPUBJO7Y7WF6RLT275INXSYAFSLGKRC5H66FYC25I'CUSTOMER_SEED = 'SCKO6DZU3FHF4DDOVL6ZXNHGDII5GXWA5VHIFKYBPOBJGPU35Y2MHZO3'

The distributor/customer account is ready, next up is establishing the trustline and for that, we will have to perform Change Trust operation. Since this operation will be performed on Horizon server, we also have to connect to horizon server to carry out this task.

from stellar_base.horizon import horizon_testnet
def connect_horizon():    horizon = horizon_testnet()    return horizon

Since I am on TestNet server so I imported onlyhorizon_testnet(). The other option is horizon_livenet() which is used for production. For Change Trust operation you will need an asset object, the receiver account for establishing trust and the limit. Then, you will fetch the next sequence. All Stellar transactions need a sequence number. Sequence Number is kind of an AUTO_INCREMENT field the account that makes sure that each transaction could be identified by a unique id. When you request the sequence number, it will always be 1+ of the last one.

def opr_change_trust(asset_object, receiver_address, receiver_seed, horizon):    # Operation Object    op = ChangeTrust({        'source': receiver_address,        'asset': asset_object,        'limit': '5000'    })    # get reference of horizon server
# Getting the next sequence of the address    sequence = horizon.account(receiver_address).get('sequence')    print(sequence)

If I run this code I get the sequence number 38912811723653120

No matter how many time I run this code, as long as some other transaction is not being performed on this account it will always be the same number. It is just like getting the max() of an ID in MySQL and add 1to it. Even if you access the account details via REST API you will find the same sequence number there

Now we are going to create the Transaction object. Before that, I am going to create a Text Memo. Text Memo is nothing but adding a small message to the transaction. In my case it’d be

msg = TextMemo('Change Trust Operation')

It is not necessary but it is very useful if you are integrating with some other other external system and want to store the reference of external system in your transaction. For instance, you want to store the UserID from your users or InvoiceID after each transaction for audit purpose.

Let’s move forward.

# construct Tx    tx = Transaction(        source=receiving_account.address().decode(),        opts={            'sequence': sequence,            'memo': msg,            'operations': [                op,            ],        },    )

OK, now we have to build the envelope of the transaction, sign it by the receiver and then submit to the Horizon server. Also, we need object reference of the receiver and we do that by passing seed value or private key value in from_seed() method of the Keypair.

from stellar_base.transaction_envelope import TransactionEnvelope as Te# Get the reference of receiving accountreceiving_account = Keypair.from_seed(receiver_seed)# build envelopeenvelope = Te(tx=tx, opts={"network_id": "TESTNET"})envelope.sign(receiving_account)

So what’s happening here? you get the account object who will sign this transaction. You need to sign all transactions and for that, you will need a private key of that account.

Now, it’s time to submit this transaction to Horizon server.

xdr_envelope = envelope.xdr()    # Submit the signed transaction to Horizon server.    response = horizon.submit(xdr_envelope)
if 'result_xdr' in response:        print('Successful')    else:        print('Things go Fishy')

Seems it worked but how do I verify? well, it’s easy. Before establishing a trustline, balances appear like below:

"balances": [    {      "balance": "10000.0000000",      "asset_type": "native"    }  ],

That is, only XLMs were in the wallet which was given by the friend bot. If a trustline is established successfully then balances gets another entry like below:

balances": [    {      "balance": "0.0000000",      "limit": "5000.0000000",      "asset_type": "credit_alphanum12",      "asset_code": "BIRYANI",      "asset_issuer": "GCYRKVRGM5OT3QINELNL7EOSW3RCRHVPMHWM4RKPVWLMVFYN2GCGLUD7"    },    {      "balance": "9999.9999900",      "asset_type": "native"    }  ],

Yay! I can see the entries of our BIRYANI coin. Everything is self-explanatory here, No?

You may also check it programmatically by calling horizon.account(receiver_address)['balances']

and it will return a listof all balances. In my cases it is displayed as:

[  {    'balance': '0.0000000',    'limit': '5000.0000000',    'asset_type': 'credit_alphanum12',    'asset_code': 'BIRYANI',    'asset_issuer': 'GCYRKVRGM5OT3QINELNL7EOSW3RCRHVPMHWM4RKPVWLMVFYN2GCGLUD7'  },  {    'balance': '9999.9999900',    'asset_type': 'native'  }]

OK so trustline is also established, mapping this to non-digital and conventional way, it means that I reached to the counter, gave the cash for the 1 plate of Biryani and the counter guy made up his mind that he’s gonna issue a token to me. This process could be called establishing a trustline between me and the business representative, that is, the counter guy!

Establishing the trustline is not enough. The customer has paid cash and he needs tokens in his wallet. The next step is transferring tokens in customer wallet by the issuer.

def transfer_fund(amount, asset_object, customer_account, issuer_account, issuer_seed, horizon):    print('Transferring fund to {}'.format(customer_account))
# Now create Payment Op    op = Payment({        'source': issuer_account,        'destination': customer_account,        'asset': asset_object,        'amount': str(amount)    })
msg = TextMemo('Your first Payment !')    # Getting the next sequence of the address    sequence = horizon.account(issuer_account).get('sequence')
# construct Tx    tx = Transaction(        source=issuer_account,        opts={            'sequence': sequence,            'memo': msg,            'operations': [                op,            ],        },    )
# Get reference of the issuer account    issuer_account1 = Keypair.from_seed(issuer_seed)
# build envelope    envelope = Te(tx=tx, opts={"network_id": "TESTNET"})    envelope.sign(issuer_account1)
xdr_envelope = envelope.xdr()
# Submit the signed transaction to Horizon server.    response = horizon.submit(xdr_envelope)
if 'result_xdr' in response:        print('Successful Transfer')    else:        print('Things go Fishy')

This is very similar to ChangeTrust operation. You set source and destination account. Since the amount is being sent by the issuer so the transaction will be signed by him as he was the initiator.

Now if you check the customer account you will see 1 BIRYANI token in his wallet.

"balances": [    {      "balance": "1.0000000",      "limit": "5000.0000000",      "asset_type": "credit_alphanum12",      "asset_code": "BIRYANI",      "asset_issuer": "GCYRKVRGM5OT3QINELNL7EOSW3RCRHVPMHWM4RKPVWLMVFYN2GCGLUD7"    },    {      "balance": "9999.9999900",      "asset_type": "native"    }  ],

n case you wonder why different currencies in a single wallet, ask yourself, don’t you keep multiple currencies in your own leather wallet?

OK, so far so good, now I, a customer, got a token in hand and now I am in the queue, waiting to get the parcel of Biryani. In my turn, I will return that token to the parcel boy and he will hand me the shopper of my favorite Biryani. The digital version of this workflow would be nothing but returning the token back to the issuer. If you check the customer account again, the balance will be zero. There will be no effect on the issuer account because he does not need to maintain the wallet neither he has an issue of token limit. He can issue millions of tokens with the same account. However, Stellar gives you an option to restrict the issuer account distributing infinite tokens by locking it. It’s done by setting the weight to 0. Since this is not our scenario so we don’t have to worry about it.

OK so the token is returned and I got my parcel ready!

Biryani Parcel

The management of Madni Biryani is very pleased to see the entire workflow of digitizing their Biryani delivery via Blockchain, in our case, on Stellar Network. They got so happy that besides my payment check they also gifted me a Biryani Deg. Life is so beautiful!

The management also discussed the integration of this new technology with their existing ordering system and with a promise of another Deg and of course, a check, I got agreed to help them out. Though I covered online integration in a previous post but will try to make a sequel to this post, provided if get time.

Conclusion

So in this post, I covered how you can automate the process of creating a custom token for the Stellar Network. You may also use Stellar Lab interface to create a custom token, in case you are not a programmer. You can also use this awesome command line toolto create a custom token or make payments.

The use-case I discussed is one of the many use-cases of using the Stellar network for custom tokens. The other use-case could be using the Stellar token as a Loyalty pointsfor your business where your customers earn points by doing certain activities. They could transfer loyalty tokens to other members. Blockchain’s immutable nature makes it easy to track everything easily. You may establish your own currency exchange and create custom token pairs for other currencies by installing your own private Stellar nodes that across branches of your company in different countries. Since they will be on an isolated network, you can have a confidentiality and more control over transactions. Image one of your exchange branch is in the US, others in Asia and Europe. You have 3 nodes setup and customers can transact money in the very short span of time. It is also very cost-effective for both customers and the business. All are required to make a transaction from branch A and it’s handled at branch B. Once verified, the exchange guy gives your USD cash on time and you return back to your home happy. Possibilities are endless.

Credit: Thanks to the entire Stellar community for coming up with awesome docs and answering my dumb questions that helped me to learn a lot about this awesome project. Specially @dzham,@mikefair, and @sacarlson for answering my stupid and childish questions again and again and answering with patience and professionalism :-)

This article was originally published here

Click here to subscribe my newsletter for future posts.