Stellar Cryptocurrency Tutorial Series: Part 2 — Issuing Assets with Go/Stellar

Alexander Leon
6 min readFeb 13, 2018

You just bought an awesome plot of land somewhere exotic and you have enough oranges to start a business. Being the tech-savvy person you are, you want to sell your oranges online.

You consider setting up a database to manage your business, but you soon realize if you need a database, you need to hire an IT department to then manage the database. Oh, and what if the database goes down, it better be a good IT department. Wait, and we’ll have to come up with a good infrastructure to track our full history. Hold on, don’t forget to make numerous replicas of the database in case something happens to one of them. You look up businesses and cloud services for ideas, but it’s starting to look like there are too many obstacles.

Meanwhile, your friend Tom has been raving about something called Stella. Is that right? No, Stellar. That’s the one.

Enter the blockchain

What if there was a way to use an existing database-like public service on which you could record your orange sales? You could access this machine at any time, create your transactions right on it, and view all your past and pending orders. If that sounds interesting to you, continue reading. By the end of this tutorial we’ll have set up the basic infrastructure for your orange business.

Before moving on, if you haven’t yet done so, I recommend checking out this previous tutorial from the series regarding making regular (lumen) payments: https://medium.com/@alexanderleon/stellar-tutorial-making-a-payment-with-go-on-the-testnet-4d0ab2ba0887

Plan of Action

  1. We’re going to create a mock account for ourselves and our customer
  2. We’re going to create our asset, the orange
  3. The customers will request oranges in exchange for lumens, the Stellar native currency
  4. We’ll accept the trades

PSA: It is perfectly feasible to exchange oranges for U.S. dollars. For the sake of simplicity though, we’ll exchange oranges for lumens in this tutorial.

So what’s an asset?

For starters, let’s differentiate the two types of assets that Stellar supports: Native and Credit assets. Native assets are lumens; Stellar’s native currency. What about credit assets? Well, these are assets that are credited to your account but you don’t actually hold yet. When someone sends us money in exchange for oranges, what we’ll actually be giving the customer is credit to said oranges. It is then our duty as a legitimate business to give the customer the oranges in the real world in exchange for the orange credit assets being returned to us in the Stellar blockchain.

Sure, it’s a trust-based system, but remember that when you make a purchase on Amazon, you’re trusting the company to send you the product you requested.

Roll up your sleeves

Enough theory and concepts, let’s build this thing! We’ll start with creating our mock accounts and filling them up with lumens. This is similar to the last tutorial, so I’ll give you all of it in one big chunk:

// oranges.gopackage mainimport (
"github.com/stellar/go/clients/horizon"
"github.com/stellar/go/keypair"
"log"
"net/http"
)
func fillAccounts(addresses []string) {
for _, address := range addresses {
friendBotResp, err := http.Get("https://horizon-testnet.stellar.org/friendbot?addr=" + address)
if err != nil {
log.Fatal(err)
}
defer friendBotResp.Body.Close()
}
}
func logBalances(addresses []string) {
for _, address := range addresses {
account, err := horizon.DefaultTestNetClient.LoadAccount(address)
if err != nil {
log.Fatal(err)
}
log.Println("Balances for address:", address)
for _, balance := range account.Balances {
log.Println(balance)
}
}
}
func main() {
seller, err := keypair.Random()
if err != nil { log.Fatal(err) }
buyer, err := keypair.Random()
if err != nil { log.Fatal(err) }
addresses := []string{seller.Address(), buyer.Address()}
fillAccounts(addresses)
logBalances(addresses)
}

Let’s run it in our terminal:

go get
go run oranges.go

You gotta trust me

So Stellar is smart and doesn’t let you trade any asset for any asset by default. You have to first declare that you trust a specific asset before exchanging something you own for it. To do so, we’re going to set up a trustline to the seller’s orange asset.

Let’s make a trust function like so:

func trust(asset build.Asset, limit string, address string, seed string) {
tx, err := build.Transaction(
build.SourceAccount{address},
build.AutoSequence{SequenceProvider: horizon.DefaultTestNetClient},
build.TestNetwork,
build.Trust(asset.Code, asset.Issuer, build.Limit(limit)),
)
if err != nil { log.Fatal(err) }
signAndSubmit(tx, seed, "Create orange trustline")
}

Since we have to sign and submit a couple other actions down the line, why not have a signAndSubmit function that’ll complete that process for us? Here it is:

func signAndSubmit(tx *build.TransactionBuilder, seed string, privateMemo string) {
txe, err := tx.Sign(seed)
if err != nil { log.Fatal(err) }
txeB64, err := txe.Base64()
if err != nil { log.Fatal(err) }
_, err = horizon.DefaultTestNetClient.SubmitTransaction(txeB64)
if err != nil {
log.Fatal(err)
}
log.Println("Transaction successfully submitted:", privateMemo)
}

Now in our main function, we’re going to declare the orange credit asset and have our customer trust it. A credit asset is composed of an asset code and the issuer’s address. Since the issuer’s address is part of the asset, it’s easy as a customer to verify and ensure that you’re purchasing that asset, an orange, from that specific seller. Add the missing imports:

"github.com/stellar/go/clients/horizon"
"github.com/stellar/go/build"

Here’s the code to add to main():

OrangeCreditAsset := build.CreditAsset("Orange", seller.Address())

trust(OrangeCreditAsset, "500", buyer.Address(), buyer.Seed())

What’s up with “500” you ask? Well, it’s a statement saying that we want to trust that asset, but not more than 500 units of it. Put another way, I don’t necessarily trust that the seller has 5,000,000 oranges. I only trust that the issuer has 500 units so at most my account can ever hold credit to 500 oranges. It’s fraud protection.

Let’s buy some oranges on the blockchain

Pretty epic title, huh? Well, let’s do it.

Here are the functions to to commit purchase and sell offers:

func makeOrangePurchaseOffer(rate build.Rate, amount build.Amount, address string, seed string) {
tx, err := build.Transaction(
build.SourceAccount{address},
build.AutoSequence{SequenceProvider: horizon.DefaultTestNetClient},
build.TestNetwork,
build.CreateOffer(rate, amount),
)
if err != nil { log.Fatal(err) }
signAndSubmit(tx, seed, "Make purchase offer")
}

func makeOrangeSellOffer(rate build.Rate, amount build.Amount, address string, seed string) {
tx, err := build.Transaction(
build.SourceAccount{address},
build.AutoSequence{SequenceProvider: horizon.DefaultTestNetClient},
build.TestNetwork,
build.CreateOffer(rate, amount),
)
if err != nil { log.Fatal(err) }
signAndSubmit(tx, seed, "Make sell offer")
}

Now in our main function, let’s call these functions:

sellOrangeRate := build.Rate{Selling: OrangeCreditAsset, Buying: build.NativeAsset(), Price: "0.5"}
buyOrangeRate := build.Rate{Selling: build.NativeAsset(), Buying: OrangeCreditAsset, Price: "2"}
makeOrangeSellOffer(sellOrangeRate, "20", seller.Address(), seller.Seed())
makeOrangePurchaseOffer(buyOrangeRate, "20", buyer.Address(), buyer.Seed())
logBalances(addresses)

To understand the rates, sellOrangeRate is saying: Exchange two OrangeCreditAssets for one NativeAsset. buyOrangeRate is saying: Exchange one NativeAsset for two Oranges. The rates have to coincide in order for the transaction to go through.

Quick thought: alternatively, the buyer could try to barter a lower price by submitting an offer at a lower rate and the seller could choose to accept it.

Anyways, if you run this function, you should see that the account balances look different. The seller has more lumens now, and the buyer has less lumens and credit to twenty oranges.

So do those twenty orange credits stay in the buyer’s account forever?

They shouldn’t. My proposal is that when the buyer receives the physical product, she should ‘sign’ something (it could be as simple/easy as a button on a phone app or something) stating that she received said oranges. Upon signing, those assets get nullified. If you have a better mechanism to ensure the buyer cancels out those credit assets, I’d love to hear it.

Here’s the code for my proposal. Let’s add a confirmPayment function:

func confirmPayment(asset build.Asset, amount string, destinationAddress string, seed string) {
tx, err := build.Transaction(
build.SourceAccount{seed},
build.TestNetwork,
build.AutoSequence{SequenceProvider: horizon.DefaultTestNetClient},
build.Payment(
build.Destination{AddressOrSeed: destinationAddress},
build.CreditAmount{asset.Code, asset.Issuer, amount},
),
)
signAndSubmit(tx, seed, "Oranges arrived")
}

Call it from the main function:

confirmPayment(OrangeCreditAsset, "20", seller.Address(), buyer.Seed())
logBalances(addresses)

If you run the file now, you should see that at the end, the buyer will be holding zero credit assets. They got their oranges after all!

Unrelated PSA: Looking for a new high paying software development job? Send me your resume to alexleondeveloper@gmail.com and I’ll get back to you!

Well, that’s all folks! Once again, here’s the source code: https://github.com/alien35/stellar-tutorial/blob/master/stellar_assets.go

--

--

Alexander Leon

I help developers with easy-to-understand technical articles encompassing System Design, Algorithms, Cutting-Edge tech, and misc.