Tokens

From RunWiki
Revision as of 22:08, 28 April 2023 by Zhell (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Using jigs, you can create tokens similar to ERC-20. These can be used for stablecoins, in-game currencies, tradable reward points, shares of stock, and more. Unlike ERC-20 however, where different users interact through a smart contract, in Run each user owns their own tokens. This makes them more like native bitcoin.

In Run, tokens are just jigs that are fungible, meaning that you can divide them into smaller pieces and combine them back together, like cash. Tokens can be minted, owned, sent, and redeemed. And while you could write your own token class to implement this functionality, Run provides a base Token standard for your convenience. You extend from Token to create your own fungible token. As the owner of the class, only you will be able to mint new coins for your users.

Here we will guide you through a few use cases. For more, see API Reference: Token.

Defining

You can define your own tradable token by creating a class that extends from Token. Your extension uniquely identifies your token and it will have its own name and properties. You don't need to add any methods either — all of the most common functionality for tokens is already in the base class. You will however probably want to set a few properties: like symbol, decimals, metadata, and anything particular to your token. Make sure to check out the Standard Metadata section for metadata used by wallets and exchanges.

Next, deploy your class. To do this, call run.deploy() in a script. Be sure to write down its location afterward, and make sure you've deployed it using an appropriate owner. You mint new tokens through the class, so its owner should be very secure. You may assign your token class to a private key or a more complex ownership script. Consider assigning your class to a Group lock to require multiple parties to sign off for mints.

Deploy a new token class:

const run = new Run({ owner })

// Define the token class
class USDCoin extends Token { }
USDCoin.decimals = 6
USDCoin.symbol = 'USDC'
USDCoin.metadata = { emoji: '💵' }
USDCoin.backingBank = 'HSBC'

// Deploy
run.deploy(USDCoin)
await run.sync()

// Write this down
console.log(USDCoin.location)

Minting

Minting tokens is as simple as calling mint() on your token class. You pass in the amount of tokens to mint along with the address to mint to. If you don't specify an address, you will mint tokens to the class owner.

Every time you mint, your token class is spent and the class receives a new location. One best practice is to the save the location for your token class after every mint. This way, Run can load it quickly the next time. When you load the exact latest location of the class, Run can get it from its cache. When you load from an older location, sync() will take some time to catch up.

You can mint to several users in a single transaction. Just wrap all your mint() calls inside of a run.transaction().

// Load and sync your token class
const USDCoin = await run.load(tokenClassLocation)
await USDCoin.sync()

// Mint new coins to users. Only USDCoin.owner may do this
const mintedCoin = USDCoin.mint(100, address)
await mintedCoin.sync()

console.log(mintedCoin.amount) // 100

Sending

Users that own your tokens can freely send them to others. To send a token, simply call send() and pass in the new owner and the amount to send. If you don't specify an amount, then the whole token amount will be sent. The return value of send() is the new coin that was sent. The change will remain in the current token.

You can send to several users in a single transaction by wrapping all your send() calls inside of a run.transaction().

Send some of a coin and keep the change:

const sentCoin = coinToSend.send(pubkey, 30)

Send to two users in the same transaction

run.transaction(() => {
  coinToSend.send(savanna, 10)
  coinToSend.send(margot, 20)
})

await run.sync()

Combining

After users have received several tokens, each of the same class, they can convert them into a single token having the combined amount using the combine() method. This is like taking five twenty-dollar bills to the bank to receive one one-hundred dollar bill. To combine tokens, call the combine() method on one token and pass in the other tokens that should be merged into it.

It is best if apps and wallets combine tokens regularly to minimize the number of objects that need to be loaded. One best practice to to combine before every send. You can even combine and send in a single transaction, as seen over there. ⇥

Combine three tokens together:

firstToken.combine(secondToken, thirdToken)

Combine and send in a single transaction:

const tokens = run.inventory.jigs.filter(jig => jig instanceof USDCoin)

const tx = new Run.Transaction()
tx.update(() => tokens[0].combine(...tokens.slice(1)))
tx.update(() => tokens[0].send(amount, address))
await tx.publish()

Redeeming

As an issuer, you may need to redeem tokens back for their underlying asset. For example, a stablecoin might be redeemed into its backing currency. Every token has a sender property that stores the last owner of that particular token. You can use this to determine who to redeem to.

Usually, you'd run a server that would monitor a redeem address. Users would send their tokens to that address, and when you receive a token, you'd use the sender property on it to send the underlying asset. Then you can destroy that token so that it is no longer in circulation.

Redeem a token as bitcoin:

const sender = token.sender
const satoshiValue = tokensToSatoshis(token.amount)

class Payment extends Jig {
  init(owner, satoshis) {
    this.owner = owner
    this.satoshis = satoshis
  }
}

run.transaction(() => {
  token.destroy()
  new Payment(sender, satoshiValue)
})

await run.sync()

Limiting Supply

You may want to limit the supply of a token. You can call destroy() on your token class after you've minted all tokens. This will prevent any further mints. It's a good idea for when you don't know in advance how many tokens you will mint. Calling destroy also prevents new tokens from being created in your name even if your private owner key were to get hacked.

Another approach is to set a max supply for a token by overriding the mint() method to track the number of coins minted, as seen to the right. Notice that the upgradable property is set to false; this prevents upgrades to your class that would to remove this check. This creates a provably limited supply, and is a good idea for when you want to limit the supply in advance.

class CollectableCoin extends Token {
  static mint(amount, to) {
    if (CollectableCoin.supply + amount > CollectableCoin.total) {
      throw new Error('Cannot mint: Max supply reached')
    }

    return super.mint(amount, to)
  }
}

CollectableCoin.total = 10000
CollectableCoin.upgradable = false

Blacklisting

You may need to blacklist individual tokens. For example, a user may lose their keys and request that you reissue their tokens. Or law enforcement may come to you to request blacklisting if your tokens are being used illegally. In both cases, the recommended solution is to keep a list of token locations that are blacklisted and not redeem any token that is on that blacklist. You will need to track descendant UTXOs too and blacklist them as well. But stay tuned! A simpler solution is coming.


Tokenkit

Run also offers a standalone library called Tokenkit that makes the process of deploying, minting and working with tokens breath-takingly simple. To find out more head over the to tokenkit github repo.

Where to go from here

Now that you know anything there is to know about creating tokens (almost), let's explore Advanced Usage