Introduction
RUN is a platform to build apps and tokens on Bitcoin. Developers create applications using interactive objects, called jigs. Jigs allow you to build almost anything you can dream up: tokens, contracts, artwork, elections, games, digital pets, and more — all run on Bitcoin.
RUN supports full object-oriented programming and arbitrary code today. It is secure and performant for real applications. These docs will help you get started!
Getting Started
Installation
Load both
bsv
andrun
in the browser
<script src="bsv.browser.min.js"></script>
<script src="run.browser.min.js"></script>
Load
run
in Node.js
const Run = require('run-sdk')
If you're new to RUN, let the tutorial series guide your journey to get acquainted. You can write code without installing anything. The web browser Console will be your playground.
The details written here in the Docs have example code in the sidebar on the right-hand side. Anything you read in paragraphs, you can preview in-action over there. ⇥
RUN works everywhere including all major browsers, on desktop and mobile, as well as Node.js 10+ on servers. The RUN SDK is written in JavaScript ES6 and uses the bsv library to build and sign transactions. To get started:
- For a webpage: Add
bsv.browser.min.js
andrun.browser.min.js
to the<head>
tag. - For Node.js: Run
npm install run-sdk
to install both the run and bsv libraries
And that's it. All your code and jigs will be saved on-chain and RUN will use public APIs to interact with the Bitcoin network. You don't need to deploy any servers to use RUN. All the logic works client-side.
Setup
const run = new Run({ network: 'mock' })
A Run
instance manages your communication with the Bitcoin network. The default network is main
(Mainnet), but for development and testing, we recommend mock
. Mock is an in-memory simulation blockchain that does not require funds to use. We like to call it the mockchain. For more configuration options, see API Reference: Run.
Creating Jigs
class SimpleStore extends Jig {
set(value) {
this.value = value
}
}
const jig = new SimpleStore()
jig.set('Satoshi Nakamoto')
await jig.sync()
console.log(jig.owner)
console.log(jig.location)
console.log(jig.origin)
Let's begin with a basic jig that stores a value in a variable. Create a jig called SimpleStore
. By extending from Jig
, the instances of your class will automatically sync to the blockchain. Every jig has an owner
. Any code may read jigs but only its owner
can update it. The owner
is typically a Bitcoin address, and the private key is required to update it.
In addition to owner
, every jig has a location
. A jig's location
is the pairing of a Bitcoin transaction ID and an output index, and it represents a particular state in time of an object or class on Bitcoin. When you check the location
property of a jig, you get its current location. If you wish to get the location where the jig was first deployed, that is called its origin
. The origin
is unique and will not change, however location
changes with every update. After a method call, your jig will have a new location
pointing to a Bitcoin transaction containing the last method call.
Loading Jigs
Loading all jigs
await run.inventory.sync()
const simpleStore = run.inventory.jigs.find(x => x instanceof SimpleStore)
simpleStore.set(123)
Loading a specific jig
const specificJig = await run.load(simpleStore.location)
specificJig.set('abc')
// Fast-forward the jig to its latest state
await specificJig.sync()
The simplest way to load your jigs is to call run.inventory.sync()
and then access run.inventory.jigs
. run.inventory.sync()
will find and load all objects owned by run.owner
and place them in the jigs
array. Once loaded, you may call methods and use them normally. Alternatively, you may wish to load a particular jig or load a jig in a historical state. To do either, pass the location
of the jig you wish to load into run.load()
.
If you've loaded a historical location so that your jig is in a previous state, you'll need to first catch up to the latest state before you'll be allowed to make a method call. sync()
will handily fast-forward a jig to its latest state on the blockchain without triggering a Bitcoin transaction. In the example on the sidebar, if specificJig
was in a historical state, you would call specificJig.sync()
and then call specificJig.set('abc')
. When you accidentally try to update a jig without the jig being in its latest state, RUN will safely abort before publishing a Bitcoin transaction and inform you with an error. In that case, you'll just need to add the preceding sync()
call and execute your code again. The best practice is to write code in such a way that jigs are always up-to-date in their latest state. RUN manages the heavy lifting for you.
sync()
also acts as a debugging tool since it surfaces any errors your jigs have after you've made changes to them. If you notice your app acting funny, a well-placed preceding specificJig.sync()
can help you uncover the error. You may also call sync()
on your Run
instance, like this: run.sync()
. That'll help you search out any errors from your entire app in all of the jigs you own.
Jigs
Jigs are interactive objects on Bitcoin. You define a jig with a JavaScript class and that class determines exactly what the jig can do. All of your jigs are unique. Each one has an owner and only that owner can update the jig. How is that secured? Bitcoin! Let's explore how you create a jig.
Creating
class Post extends Jig {
init(message) {
this.message = message
}
}
new Post('Hello, world')
Let's create a new class of jigs called Post
to represent comments on a message board. In JavaScript, your class initializer is called constructor
, but for jigs, this method is called init
. Think of them the same way. If init
throws an exception, the jig will never be created, just like constructors. You create jigs with the new
keyword, just as you would with normal JavaScript objects, and they get deployed onto the Bitcoin network. Pretty cool.
Updating
class EditablePost extends Post {
edit(message) {
this.message = message
}
}
const editablePost = new EditablePost('Hello, world')
editablePost.edit('Hello, BitCoin')
Jigs are updated by calling methods. In fact, this is the only way to update jigs. Your jig class defines the ways that your jig instances may evolve, so be sure to think ahead. When you call a method, RUN publishes a Bitcoin transaction with data in an op_return
that includes the method name and its arguments. The state may be recomputed simply by playing back every update one-by-one. For more information about how it works, see How It Works.
Sending
class Dragon extends Jig {
send(to) {
this.owner = to
}
}
new Dragon().send(address)
Jigs may be sent to someone else by changing the owner
property of a jig in a method. You can set the owner to a Bitcoin address, a public key, or a custom Lock. The new owner will be able to update the jig starting in the next transaction.
Syncing
Wait for updates to complete
class LoyaltyCard extends Jig {
init() { this.stamps = 0 }
stamp() { this.stamps +=1 }
}
const card = new LoyaltyCard()
await card.sync()
card.stamp()
await card.sync()
Sync a jig from its origin to its latest state
const card2 = await run.load(card.origin)
await card2.sync()
RUN is asynchronous. When you create or update jigs, RUN creates Bitcoin transactions for you in the background and broadcasts them to the network. As with any network request, the request may sometimes fail. Your connection may go down, or a node on the network may reject your transaction.
Every jig has sync()
available to ensure your local state is the same as that on the network. Calling await jig.sync()
returns when:
- All pending local transactions have been published successfully, and
- The jig has been caught up with any new transactions from the network.
Any errors in validation or network requests will throw an error.
When testing ideas out in the browser, you won't need to call sync()
very much. RUN will happily execute method calls on jigs in their latest state. However, in a production app, the best practice is to call sync()
after every method call to ensure that you catch errors early and handle them gracefully. Calling sync()
will force any error to show up at that particular line in the code.
RUN updates your jig's state with each method call you make. However, it does not continuously update your jigs with the blockchain. Because if it did, as a developer you'd feel like you were on shifting sand. Instead, you've got a reliable and steady state for each jig that you're working with. Therefore, depending on the app you've built and actions of the owner of the jig, you might need to catch up a jig to its latest state because it could have been modified outside of your app.
Interactivity
class Event extends Jig {
createTicket() { return new Ticket(this) }
}
class Ticket extends Jig {
init(event) { this.event = event }
}
Event.deps = { Ticket }
Jigs are designed to interact together. Jigs may create other jigs, call methods on other jigs, read properties on other jigs, and even store other jigs inside them. These are just a few of the types of interactions that RUN supports.
Calling new
to construct a jig within a method of another jig will create a new output in the transaction. Sometimes you will create new jigs that are of the same class, but other times you will want to create jigs that are of a different class. Because Jig code runs in a sandbox without access to other code, you set the deps
property on your jig class to tell RUN about any other classes you use. These dependencies will be made available as globals to your jig.
Jigs may store other jigs as properties too. Think of these as standard JavaScript object references that you may read or write. However, if stored jigs are updated by calling methods, then their owners must also sign the Bitcoin transaction that updates their state. For more information about when owners need to approve, see the Ownership Rules.
Destroying
Destroy a jig
giftCard.destroy()
Override destroy to finalize a jig
class GiftCard extends Jig {
init(value) {
this.value = value
}
destroy() {
super.destroy()
this.value = 0
}
}
Not all jigs will live forever. You may need to clean up old jigs in your inventory, and or you may need to have one jig consume another jig to make an update. Calling destroy()
on a jig creates a transaction that removes the jig's output and makes it un-updatable. To do this, a jig may be destroyed. When a jig is destroyed, it no longer has an unspent output on the blockchain, and it will not appear in your inventory.
Destroyed jigs still have unique locations that identify their final state. You can even still load their final state using run.load()
. However, destroyed jigs are marked as being destroyed with a null
owner and they are forever locked in their final state.
By default, every jig has a destroy method. If you wish to prevent a jig from being destroyed, override the destroy method and throw an error in it. You can call destroy from outside the jig or from another method. You can also override destroy()
to put the jig into its final state.
Backing
class Tip extends Jig {
init(message, pubkey, amount) {
this.message = message
this.owner = pubkey
this.satoshis = amount
}
withdraw() {
this.satoshis = 0
}
}
new Tip('I like your videos', pubkey, 100000)
Jigs may be backed by bitcoins. When a jig is backed, it means that the output associated with that jig has a non-dust value in it. Backed jigs let users send money between each other and provide a baseline value for items. To back a jig, set the satoshis
property to a number in a method. Your purse will automatically deposit that amount into the jig. When the satoshis
property is later decreased, those Bitcoins will be withdrawn to the active purse.
It is important to remember that backed jigs may be melted at any time by spending the jig output outside of RUN. This will destroy the jig, and the owner will keep the satoshis.
Checking Parameters
Attaching an item
class Hat extends Jig { }
class Person extends Jig {
wear(item) {
expect(item).toBeInstanceOf(Hat)
this.item = item
}
}
Person.deps = { Hat, expect: Run.extra.expect }
You should always check the parameters passed to your methods. If you are expecting a number, make sure it is really a number. If you are expecting a jig, then make sure you have a jig of the right class. This prevents others from mis-using your jigs.
For your convenience, RUN provides the expect
sidekick. expect
lets you make assertions similar to those you might be familiar with in Jest or Chai. You can to check many things, including if a jig is a certain class, if a number is greater than another number, or if a parameter is of a certain type. To get started, add expect
as a dependency to your jig class. Then, check out the expect documention to find the right assertion.
Sometimes you may wish to check if a class is part of a changing set. For example, a game may have a list of item classes that may change over time. This is a little more advanced. See the Dynamic Whitelists section.
Adding an Icon
const emoji = '🦖'
const image = await Run.extra.B.loadWithMetadata('55e2c09672355e009e4727c2365fb61d12c69add91215ee3e9f50aa76c808536', {
title: 'T Rex skull icon',
license: '[CC BY 3.0](http://creativecommons.org/licenses/by/3.0)',
author: '[Delapouite](https://delapouite.com/)',
source: '[game-icons.net](https://game-icons.net/1x1/delapouite/t-rex-skull.html)'
})
class DinoPet extends Jig { }
DinoPet.metadata = { emoji, image }
new DinoPet()
You may optionally set an icon onto a jig using an emoji, a custom image, or both. These icons will show in explorers, exchanges, and wallets, whenever your jig is displayed. To attach an icon, you set the metadata
property on your jig class.
Setting an emoji: To add an emoji, set the emoji
field on metadata
to a single emoji character. You can find an appropriate emoji on Emojipedia.
Setting an image: To set an image, first upload your image on-chain in the B:// protocol format. Bitcoin Files is an excellent tool for this. The image data you uploaded can then be loaded and set as a custom image on your jig using the B berry class. Simply set the berry loaded to the image
field on the metadata
object. Currently RUN supports SVG and PNG formats. SVG is recommended in most cases because it scales up better on large displays and loads quicker due to its smaller file size.
Code
With RUN, you can own code, like classes and functions, just as easily as you can own objects. This may sound a little meta at first, but don't worry — you've been doing it all along! Every jig you created was linked to a jig class which you owned too. That jig class lived in its own output and had its own location.
When you own a piece of code, it means you have control over that behavior. For example, if you own a jig class, you get to decide what its jig instances are able to do. You can update or destroy that class and even call methods on it. RUN calls classes and functions that people own Code with a capital C. Using Code, you can define new kinds of jigs, create reusable helper functions, share your server logic for auditability, discover algorithms from other apps, and much more.
There are two kinds of Code that RUN supports:
Jig classes: Jig classes are classes that extend from Jig and behave like jigs. You already know these. You can use them to create jig objects as we've already seen, but you can also call methods on them to update their properties over time. Jig classes evolve according to same Ownership Rules as jig objects.
Sidekicks: Sidekicks are your helpers. They are your classes or functions that don't extend from
Jig
which you use more for supporting roles. You might use sidekicks to create a custom lock, to define a berry, to make a shared helper like expect, or even to store your server code on-chain. When you call a method on a sidekick, nothing gets recorded to Bitcoin. But you can call sidekicks from inside jigs and even from your own app. There are no restrictions on what kinds of parameters can be passed into or returned from sidekicks.
Using jig classes and sidekicks, you have a powerful repertoire to build extensible apps.
Deploying
Deploy a sidekick function
function sha256(data) { ... }
const sha256Code = run.deploy(sha256)
await sha256Code.sync()
You deploy your classes and functions to Bitcoin. Deploying uploads your code in a Bitcoin transaction and creates a new output for it that you own. To deploy, you call run.deploy()
and pass in your local class or function. RUN will publish the transaction in the background and make you the owner of the new Code. Being the owner allows you to perform actions on it. Code is not necessarily static, as we'll see.
run.deploy()
returns to you a new copy of your class or function that you should use going forward. This copy is special: unlike your original code, this copy is linked to your new Bitcoin output. It is also securely sandboxed and acquires several special Code methods that you can use to update it. We suggest adding the word Code to the name of the copy to differentiate it from your local class. You can call sync()
on it right away to make sure that it deployed successfully.
We recommend you deploy all of your code in advance and load your load when your app starts. This way, your app and your users have a stable library to work with. You can do this by writing a script outside of your app.
Loading
Load a Data class
const Data = await run.load('<class-location-goes-here>')
const data = new Data('abc')
Find a class you own in the inventory
await run.inventory.sync()
const MyClass = run.inventory.code.find(C => C.origin === myClassOrigin)
You will want to load the code your deployed when your app starts. run.load()
, in addition to loading jigs, is able to download, install and sandbox your classes and functions from the blockchain too. Just pass in the location of the code you wish to download.
Alternatively, you can use the inventory to find your code. run.inventory.sync()
will load all the code that you own and place it in the run.inventory.code
array. You can filter that array by origin to find the code you plan to use.
Linking
Deploy a jig class with a dependency
class Reward extends Jig { ... }
class LootBox extends Jig {
init() { this.reward = new Reward() }
}
LootBox.deps = { Reward }
run.deploy(LootBox)
You can create code on Bitcoin that uses other code on Bitcoin, just like you would in your application. To do this, you have to explicitely tell RUN that you are using other classes or functions, because RUN needs to be able to link the code together.
You tell RUN about other code you use by setting the deps
property on your class or function. The deps
property should be set to an object having all of the code you use inside. Those dependencies stored in deps
will be made available as globals inside your class or function.
Extending
class EditablePost extends Post {
edit(message) {
this.message = message
}
}
You can create hierarchies of jig classes using RUN. This is often useful for adding or changing some base class behavior. For example, think of a game where a base Vehicle class is extended to create many types of vehicles, like planes and trains.
To extend a class, you use the extends
keyword as you normally would in JavaScript. When you deploy an extension class, RUN will automatically create separate outputs for both the parent and the child and link them together. You do not need to add the parent class to the deps
object on the child. RUN is smart enough to automatically find the parent.
Inside methods on the child class, you can call the parent class's methods by using the super
keyword in JavaScript. This is useful when you want to override a method on the parent to add behavior but you still want to call the parent method inside.
By default, extending a class requires the parent class's approval. This ensures the instanceof
keyword is secure, because if RUN didn't require parent class approval, third-parties could extend from a class to trick your instanceof
checks into passing. Sometimes, however, you will want third-parties to extend your classes, and in those cases, set the sealed
property on your class to false
before you deploy so that anyone can create extensions.
Syncing
Load a class at its origin and fast-forward it to its latest state
const DigitalPet = await run.load('<digital-pet-class-origin>')
await DigitalPet.sync()
When you call MyClass.sync()
, it works just like myJig.sync()
. If there are pending transactions, they will be published to the blockchain, and if there are network updates you haven't received, RUN will download and apply them. We always recommend calling sync()
after every code update to catch errors too, just like jigs. If you load a class by its origin, it is best to call sync()
right after to make sure you have the latest state. For more about syncing, see Jigs: Syncing.
Updating
class Weapon extends Jig {
static setPower(power) {
this.power = power
}
}
const WeaponCode = run.deploy(Weapon)
WeaponCode.setPower(100)
// WeaponCode.power === 100
You can call methods on jig classes just like jig objects. Class methods can change properties, call other class methods, and create instances. You can even send the code to someone else by changing its owner!
To write a jig class method, put static
before the method name and it will apply to the class itself and not instances. When you call a class method, RUN publishes a Bitcoin transaction with the update and assigns the code to a new location. Other jigs that use the code will see the updates when they sync.
You should think of code as living objects with their own history. Although only jig classes can have static methods that update their state, all code including sidekicks can be upgraded, destroyed, and authed.
Upgrading
const OldWeapon = await run.load('<weapon-class-location>')
const NewWeapon = class Weapon extends Jig {
...
}
// Copy over existing state
Object.assign(NewWeapon, OldWeapon)
// Except the bindings, which are set by Run
['origin', 'location', 'nonce', 'owner', 'satoshis'].forEach(x => { delete NewWeapon[x] })
OldWeapon.upgrade(NewWeapon)
await OldWeapon.sync()
After your code is deployed, you may discover that it is missing an important feature or perhaps has a bug. On other blockchains, this is a terminal failure, but in RUN, you can call upgrade()
on your code and pass in a replacement class or function. That replacement will become the new behavior. Existing jigs that were deployed using your old class will get the upgrade when they sync or when they interact with another jig that already has the upgrade.
To upgrade a class:
- Load the old class
- Write the new class, including all existing methods
- Copy over existing state to the new class, except the bindings
- Call
OldClass.upgrade(NewClass)
and sync
We recommend writing a script to upgrade, similar to deploying code. It is important when you upgrade to copy over every property and method to the new class, because you are defining a full replacement. The example on the right shows a safe way to copy over state.
After you upgrade a jig class, you'll want to sync its jig instances when you load them in your app to make sure they get the upgrade. One way to do this is to call jig.constructor.sync()
after loading any jig, but if there are many updates this might not be very efficient. Instead, we recommend calling Run.util.unify(jig, LatestClass)
. This will instantly upgrade the jig to use the latest version of a class that you already know about.
If you wish to provably prevent upgrades, you can set upgradable
to false on the class. This is useful if you wish to show others that your code will not fundamentally change.
With great power comes great responsibility! Be careful.
Trusting
// Trust the class
run.trust('<your-class-origin-txid>')
// Trust an upgrade
run.trust('<your-class-upgrade-location-txid>')
Code needs to be trusted in order for RUN to load it. To load a jig, its class has to be trusted too, since that defines its behavior. This keeps your application secure by ensuring you do not accidentally load harmful code. All code that RUN executes, includes deployed classes, upgraded classes, and any linked code you used in deps
, needs to be trusted.
To trust code, you call run.trust()
and pass in the transaction ID where the code was deployed or upgraded. Be careful to just pass the transaction ID and not the code's location. If you try to load a jig but not all of its code is trusted, you may see an error saying "Cannot load untrusted code". You can get the untrusted txid
out of the error object, trust it if it's safe, and retry the load again.
It is possible to trust any code in your Cache
by passing "cache"
into run.trust()
. This is a good idea when you are building the cache yourself because it will lead to better performance.
It is also possible to trust all code by passing "*"
into run.trust()
. This is strongly discouraged for production applications, but it can be useful during development and testing.
Tokens
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
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)
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. You may use ether the Deploy Tool or 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.
Minting
// 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
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()
.
Sending
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()
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()
.
Combining
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()
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. ⇥
Redeeming
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()
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.
Limiting Supply
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
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.
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.
Advanced Usage
Standard Metadata
class DigitalPet extends Jig { }
DigitalPet.metadata = {
author: 'Maximus',
license: 'MIT',
emoji: '🐉'
}
run.deploy(DigitalPet)
Wallets, explorers, and exchanges will want to show your jigs. You can add special metadata to any creation to help these services do so. This metadata is stored as an object called metadata
on your jig, code, or berry, and it contains properties that assist apps. The following properties are considered standard metadata:
Presentation
Property | Type | Description |
---|---|---|
name |
string | String name to use in place of the class or function name |
description |
string | Short sentence, less than 80 characters, that describes the jig for users |
emoji |
string | Single UTF-8 emoji character to represent the jig |
image |
B instance | Reference to an SVG or PNG image stored using B:// |
Attribution and Licensing
Property | Type | Description |
---|---|---|
author |
string markdown | Name of the creator for the code or content |
title |
string markdown | Title of the content |
source |
string markdown | URL where the content was found |
license |
string markdown | License for the code or content |
You can start by setting these properties on your jig classes, as seen to the right. By convention, jig instances will automatically use the metadata from their class. However, jig instances may also have their own metadata that overrides its class metadata. You can put any information in metadata
you deem important, even properties that are not listed above. More will become standard over time.
Batch Transactions
Use two jigs separately in one transaction
run.transaction(() => {
new SomeJig()
anotherJig.send(address)
})
await run.sync()
Multiple updates may be batched together in a single Bitcoin transaction. Batching makes the actions atomic, meaning all of them happen or none of them happen. This is great when two users want to exchange jigs, for example. If any errors occur while publishing the updates, all of the jig changes in the transaction will be reverted. Besides making updates atomic, batching also reduces fees.
Any number of updates may be batched together from different jigs. Call run.transaction()
and pass in a callback function that includes all of the actions that you want batched together. Then call run.sync()
to wait for the updates to publish. If you need more advanced transaction control, see the Transaction API
Owners must still sign off on any updates to a jig, so if you just created a jig in a new output, you can't yet call a method on it because its owner can't sign. If you just sent a jig to someone, they also can't use it until the next transaction.
Caller Variable
Storing the parent jig
class Child extends Jig {
init() {
this.parent = caller
}
}
Enforcing a method may only be called by certain classes
class Database extends Jig {
init(rootUser) {
this.rootUser = rootUser
}
deleteAll() {
if (caller !== this.rootUser) throw new Error('only the root user may delete the database')
}
}
class User extends Jig {
deleteDatabase(database) {
database.deleteAll()
}
}
Within code, there is a special caller
variable that is available. This variable is always set to the jig or jig class that called the current method, or null
if the current method was invoked from application code. Sidekicks are transparent and will never be a caller.
You can use caller
to store a jig's creator, enforce that a jig may only be created by a specific parent, or create administrator jigs that uniquely can call special methods.
Limiting Interactivity
Create a token that can only interact with itself
class MoneyBucks extends Token { }
MoneyBucks.interactive = false
run.deploy(MoneyBucks)
By default, any jig may interact with any other jig in a transaction. Jigs may be atomically swapped, stored on each other, created from each other, and more. However, the downside is that when jigs interact with other jigs, all of the code for those other jigs needs to be trusted by anyone who wishes to load those jigs later. RUN maintains a global trust list, but apps and token issuers may want to avoid the risk that there jigs may be un-loadable by restricting their code and jig instances so that they can only interact with certain other jigs. This removes the risk that a jig might not be redeemable by a service after it interacts with other code that the service doesn't yet trust.
To limit interactivity, you can set the interactive
property on your class or function to false
. When code is non-interactive, it can only interact with itself, its instances, and any references it has to other code. RUN enforces this.
Private Properties
class TradingCard extends Jig {
send(to) {
this._checkNotBlacklisted(to)
this.owner = to
}
_checkNotBlacklisted(owner) {
if (TradingCard.blacklist.includes(owner)) {
throw new Error('blacklisted')
}
}
}
TradingCard.blacklist = [cheaterAddress]
Jigs may have private methods and private properties.
Private methods are useful when you have internal helper methods that shouldn't be called from outside. To make a method private, you put an underscore before the method name. When a method is private, it can only be called by the current jig, a jig of the same class, or the jig class itself. If a different jig attempts to call a private method, RUN will throw an error.
RUN supports private properties too. Like methods, you put an underscore before the property name to make it private. You can use private properties to hide state that shouldn't be used by other jigs. Private properties may only be read from inside the current jig, from a jig of the same class, or from the class itself. If you try to access a private property from another class, RUN will throw an error.
Auth Method
Limit the creation of jig instances by forcing the class to auth
class SingleTonMonster extends Jig {
init() {
SingleTonMonster.auth()
}
}
Every jig and every code has an auth()
method on it. To auth means to spend a jig, forcing its owner to approve of any actions performed. Auth also ensures that the jig's state is the latest. You may use auth()
for many purposes, such as to have a class approve of the creation of instances, as seen to the right. You may also use it to get approval from several jigs before performing some action. For example, you may require that several players in a game auth using their characters for some shared action to take place, like moving on to the next round. Authing is a fundamental action that will become more useful to you over time.
Code Presets
class Dragon extends Jig {
set(name) {
this.name = name
}
}
// Use the deploy tool to deploy the Dragon class and generate these presets
Dragon.presets = {
main: {
origin: 'bee45c75c37a289517f33ebfa051601c9610ccc56fbddfbabc44413db5b0bc1b_o1',
location: 'bee45c75c37a289517f33ebfa051601c9610ccc56fbddfbabc44413db5b0bc1b_o1',
nonce: 1,
owner: '13amCautaFqwbWV6MoC86xrh96W4fXGfDV',
satoshis: 0
}
}
When you wish to create an instance of a jig from a class you've already deployed, you have two options. Either you can load the class from the blockchain via run.load()
and then create an instance, or you can create an instance from a local class that has presets applied. When a local class has presets, RUN doesn't have to download the code from the blockchain. Presets are a great way to share jig classes in NPM libraries.
You can use the Deploy Tool to generate presets. However, you can also create them manually. To do this, upload your code to each Bitcoin network you wish to support using run.deploy()
in a script. The presets are origin
, location
, nonce
, owner
, and satoshis
set onto a presets object for the given network. RUN will detect these presets and automatically use the origins and locations on the appropriate network.
Berries
Load a metanet node
const txid = '2f24d7edb8de0ef534d8e0bc2413eddda451b4accc481519a1647d7af79d8e88'
const node = await MetanetNode.load(txid)
Define the MetanetNode Berry
class MetanetNode extends Berry {
// Its constructor can only be called by its pluck function. RUN guarantees this.
init(pnode, parent, data) {
this.pnode = pnode
this.parent = parent
this.data = data
}
// RUN calls this pluck function when you call run.load with a Berry class.
static async pluck(location, fetch) {
const data = txo(await fetch(location))
if (data.out[0].s1 === 'meta') {
const pnode = data.out[0].s2
const txidParent = data.out[0].s3
const data = data.out[0].s4
if (data === 'METANET_ROOT') {
return new MetanetNode(pnode, null, data)
} else {
const parentNode = await MetanetNode.load(txidParent)
return new MetanetNode(pnode, parentNode, data)
}
}
}
}
MetanetNode.deps = { txo: Run.extra.txo }
Third-party protocols, like Twetch, B, and Metanet, may be used within RUN as berries. Berries are JavaScript objects that deterministically capture data on the blockchain that is outside RUN. If you can write code to parse an OP_RETURN, you can create Berries. Berries are more than data though. They are typed objects with methods, and they may be passed into jigs, stored as properties, cached, and more.
To load berries using an existing Berry class, you call BerryClass.load()
and pass in the path to load, which is often a transaction ID. RUN ships with one berry class, B, that may be used to load B:// protocol data.
To define a new Berry protocol, you create a class that extends from the base Berry class and implement the static pluck()
method and init()
method. RUN calls your pluck()
method to load data for that path into a JavaScript object. The second parameter to pluck
is a method that allows you to fetch transactions from the blockchain. RUN fetches transaction data in raw hex format, but you can parse it using the txo or Tx helpers. In pluck()
, you instantiate a berry, which will call your init()
method.
Once loaded, berries have locations just like jigs. Berry locations are slightly more advanced though and include the class location, the path, and more, in the form: <berry_class_location>?berry=<path>&hash=<state-hash>&version=<protocol>
. This is a unique identifier that allows them to be safely referenced and cached.
Locks
Send a token to a P2PK output script
class PubkeyLock {
constructor(pubkey) { this.pubkey = pubkey }
script() { return asm(`${this.pubkey} OP_CHECKSIG`) }
domain() { return 74 }
}
PubkeyLock.deps = { asm }
token.send(new PubkeyLock(pubkey))
A provably unspendable lock
class Unspendable {
script() { return asm('OP_RETURN') }
domain() { return 0 }
}
Unspendable.deps = { asm }
The default owner for a jig is a Bitcoin address. This is a great default, but sometimes you'll want more advanced ownership. For example, you may want a group to own a jig, so that no one party has exclusive control. Or you may wish for a jig to be provably owned by nobody. Both of these and more are possible with locks.
A lock is an object that produces a Bitcoin output script. You can set one as the owner
on your jigs. RUN ships with a Group lock class for multi-sig ownership, and you may also create your own locks by implementing the Lock API. Your lock class will have two methods: script()
and domain()
. script()
returns the bitcoin output script in hex format, and should be safe to call with any state. domain()
returns the maximum size of the unlocking script for this lock. After implementing these methods, you set the owner of a jig to an instance of your lock, and RUN will build its transaction output.
You'll probably also want to unlock your jigs to update them. To do that, create a corresponding key by implementing the Owner API. Your implementation of the Owner
API requires two methods: sign()
and nextOwner()
.
Locks
are deterministic, typed, and run in a sandbox environment. This means they can be safely used inside jigs. But this also means they can't use the bsv
library or similar external code. RUN provides an asm helper function to fill the gap when building custom scripts inside Lock classes.
Tips and Tricks
Limiting Supply
class Weapon extends Jig {
init(owner) {
// Only the Weapon class can mint Weapon jigs
expect(caller).toBe(Weapon)
this.owner = owner
}
static mint() {
if (this.supply >= 10) throw new Error('too many weapons')
// The class keeps track of the number of mints
this.supply++
return new Weapon()
}
}
Weapon.supply = 0
Weapon.deps = { expect: Run.extra.expect }
const weapon = Weapon.mint()
The supply of a jig class may be limited through the use of a minter. The minter is a separate jig or jig class that regulates the number of mintee jigs produced. In the example on the right, the Weapon
jig class is the minter for its own instances. We use the special caller variable to enforce that Weapon jigs may only be created through the class's mint()
method. If a player tried to create a Weapon
instance using new Weapon()
outside of mint()
, RUN would throw an error because the caller
property would be null
.
Random Numbers
Use an oracle to provide random values
class RandomValue {
init() {
this.owner = ORACLE_ADDRESS
this.satoshis = 1000
}
set(value) {
this.value = value
this.satoshis = 0
this.destroy()
}
}
class DigitalPet {
init() {
this.random = new RandomValue()
}
setup() {
this.size = this.random.value % 10
this.agility = this.random.value % 100
this.color = this.random.value % 5
}
}
DigitalPet.deps = { RandomValue }
Many applications use random numbers to perform updates. For example, you might wish to randomly generate the properties of a digital pet when it is created, or you may wish to determine the winner of a game using a statistical calculation. Generating random numbers inside jigs can be challenging because blockchains have to be deterministic. Run disables the Math.random()
function within jigs for this reason.
It may be tempting to use the location
or origin
properties of a jig as random numbers. Certainly, they appear random. However, this is a bad idea because a motivated user would be able to make alterations to their Bitcoin transactions, either during signing or payment, until they get the location
or origin
that leads to the random value they want.
Instead, we recommended using an oracle to provide randomness. Jigs that require random values would send a paid request to an oracle service. This oracle service would set the random number on the request jigs it receives, and when the original jig that requires randomness is synced, it will get the random value.
This same idea also applies to other oracle data, including dates, prices, and more.
Dynamic Whitelists
A dynamic whitelist to support new tokens over time
class SupportedClasses {
init() { this.classes = new Set() }
add(T) { this.classes.add(T) }
}
const whitelist = new SupportedClasses()
class Game {
init() { this.items = new Set() }
use(jig) {
expect(Game.whitelist.classes.has(jig.constructor)).toBe(true)
this.items.add(jig)
}
}
Account.whitelist = whitelist
An app may want to allow third-party developers to create new classes of jigs. Those jigs can freely interact with existing jigs once approved. For this, you can create a dynamic whitelist of approved classes that jigs can check. See the example to the right 🠮
Atomic Swaps
Atomically swapping two items with different owners
Machine 1
const mine = await run.load(myLocation)
const theirs = await run.load(theirLocation)
const me = mine.owner
const them = theirs.owner
const tx = new Run.Transaction()
tx.update(() => mine.send(them))
tx.update(() => theirs.send(me))
const rawtx = await tx.export()
Machine 2
const tx = await run.import(rawtx)
if (tx.outputs.length) {
// TODO: Inspect the transaction to be sure it atomically swaps
}
await tx.publish({ pay: false })
RUN supports atomically updating jigs with different owners using the Transaction API. One use case for this is atomic swaps, where two jigs owned by different users are exchanged in a single transaction.
The general process for an atomic swap is for one user to start a RUN transaction by calling new Run.Transaction()
. Then, this user performs all updates inside an update()
call on the transaction, including calling methods on jigs they don't currently own. RUN allows this user to build the transaction even if they won't be able to sign for every jig. Finally, this first user calls tx.export()
to export the transaction, which will pay and sign for it in the process.
The transaction is now built and must now be handed to other parties for them to sign. The other party then calls run.import()
to load the transaction they received. They may then want to check the transaction by inspecting tx.outputs
and Run.util.metadata
. If they approve, then they may call tx.publish()
to sign and publish. If more signatures are needed, they can export the transaction instead of publishing.
The Transaction API may also be used to simulate state channels and propose changes to other jigs. See the Transaction API for more information.
Using RUN on a Server
You may want to use RUN on your server. For example, to:
- Issue tokens to users who send you BSV
- Index user jigs to allow searching in your app
- Take actions automatically as in a game server
- Gather statistics about your user's behaviors
- Perform administrative tasks like managing a blacklist
RUN is designed to work in a Node.js server as easily as a web browser. You might create a REST API using express
that allows users to make queries. Or you might deploy a serverless function that responds to events. Or you might listen to relevant transactions using MatterCloud's SSE events to perform actions automatically. RUN supports all of these architectures.
RUN requires at least version 10 of Node. You can check which version you are using with node --version
. If you are using Google Functions, be sure to set { "engine": 10 }
in your package.json
since it defaults to Node 8.
Here are a few more tips:
Expand the In-Memory Cache
Increase the state cache size
run.cache.sizeMB = 1000
The LocalCache
stores jig state and blockchain transactions in memory. It is the default cache used by Run when running in Node. By default, it caches 10MB of data in memory. You can increase this by setting run.cache.sizeMB
. The local in-memory cache is useful and compliments a persisted cache.
Increase Node Memory Limits
You may need to increase Node's memory limits if you have many jigs loaded or cached at once. If you launch your server by running a node process, you can increase its memory via: node --max-old-space-size=8192
.
Persist the Cache
Save state into a Firestore collection
class Firestore {
async get(key) {
// Firestore does not allow forward slashes in keys
key = key.split('://').join('-')
const entry = await db.collection('state').doc(key).get()
if (entry.exists) return entry.data()
}
async set(key, value) {
// Firestore does not allow forward slashes in keys
key = key.split('://').join('-')
return db.collection('state').doc(key).set(value)
}
}
const run = new Run({ cache: new Firestore() })
It is a very good idea to persist your cache in a database so your backend never needs to load the same jig twice. It is even more important when using multiple servers because you can share this database. Simply implement the Cache API.
RUN will automatically call get
on your Cache
implementation when values are needed, and set
when values are ready to be cached. Cached values will never change for a given key. Just implement these two methods to load and save the cache into your preferred key-value database: Redis, Firestore, DynamoDB, etc.
To the right is an example of persisting in Google Firestore. You may want to modify it so that it also checks an in-memory local cache first and then goes to Firestore if that misses.
Avoid Race Conditions
If you are seeing server errors about Missing inputs
or txn-mempool-conflict
, it is likely that you have encountered a race condition. Two pieces of code are likely attempting to update the same jig at the same time, and are attemping to spend the same Bitcoin outputs. The network can only accept one. To solve this, here are a few recommendations:
Load jigs once at the start: If you have jigs you are reused across many request handlers, like a class that you mint from, load those jigs or code once upon starting your server rather than each time they are used. This will let RUN track the output correctly between asyncronous calls, and successive updates on that object will be performed in the right order.
Separate server, separate purses: Every Run
instance has its own internal update queue, so it is unlikely that the purse will be double-spent on a single server. But on multiple servers, if a purse key is shared, then it is very likely that double-spend errors will occur. To avoid this, use different purse keys on different servers.
Sync after every update: Call await jig.sync()
on jigs after you call a method to be sure that the updates were applied before continuing on.
Serialize shared updates: If a request handler makes updates to multiple jigs, and these updates can conflict if interleaved across multiple users, then consider serializing these updates. Within a single request handler, you can bundle all jig updates together in a batch, so that the operation is all-or-nothing. Across multiple request handlers, you can put updates into a task queue like p-limit
.
Add retry logic: If the above changes do not fix a race condition, consider adding retry logic. Run will retry network calls, but race conditions require manual handling by your server.
Load Only Safe Code
Even with sandboxing, arbitrary code has the potential for infinite loops and infinite memory attacks that can take down your server. Avoid running code that you do not trust. You should only add transaction IDs to your trusted set that you know are safe. Also, it is generally not recommended to call run.inventory.sync()
on servers, because that will load UTXOs you receive from others.
Debugging
Sometimes you will be faced with errors you don't understand. Don't fret! Here are some tips:
- Grab the latest RUN SDK from NPM or run.network
- Use Node 10+ or a modern web browser like Chrome, Firefox, Edge, or Safari
- Use the mockchain to check that it is not a connection or server-side error
- Enable RUN's internal logs by passing
{ logger: console }
into theRun
constructor
Writing Unit Tests
It is always a good idea to write unit tests for your jigs. We recommend using a framework like mocha
or jest
, and running your tests using the mockchain. The mockchain will be faster and will isolate your Jig logic from any network errors.
Getting Help
Please reach out to @runonbitcoin on Twitter or Twetch, or @niv in the BSV slack channels. We are happy to help you debug and fix any issues you encounter. If you can, see if you can reproduce the issue in the browser's web console.
How It Works
Overview
A jig is a JavaScript object that RUN syncs with the blockchain. All of its code and method calls are stored on-chain in op_return
metadata. RUN can reconstruct the state of jigs by loading the code and replaying every method call. Every jig is paired with a Bitcoin output and only the owner of that output can make changes to the jig. All updates taken together form a transaction chain that enables consensus through user verification. This design is similar to other Layer-2 UTXO-based token systems because miners don't verify the JavaScript code. If anyone publishes an incorrect update, it not only destroys the jig but also leaves an immutable record.
Transaction
Sample code for a transaction
class Dragon extends Jig { }
const dragon = new Dragon()
A transaction in RUN is an atomic update to jigs or code.
Inputs -> Computation -> Outputs
Inputs are the jigs and code which will be updated. Computation is stored as executable statements in an op_return
. The RUN Virtual Machine executes these statements and produces outputs which are the jigs and code updated or created fresh. The data for these jigs are not stored in Bitcoin outputs, but instead are stored in an off-chain cache and able to be recomputed by others from the blockchain data. In this way RUN transactions are kept small. There may also be payment inputs and outputs too that are not used by RUN but part of the transaction. Here is the transaction format for the example to the right:
Inputs | Outputs |
---|---|
1. payment | 1. op_return: RUN metadata |
2. p2pkh: Dragon Class | |
3. p2pkh: dragon jig instance | |
4. change output for payment |
Inspecting RUN metadata
const rawtx = await run.blockchain.fetch(txid)
const metadata = Run.util.metadata(rawtx)
console.log(metadata)
The op_return
data is made up of both push data and a JSON payload. It has the following structure:
[op_false] [op_return] 'run' <version> '<app-id>' '<json-payload>'
- Each data field starts with an
op_push
specifying its length. - The protocol version is currently 0x05.
- The
app-id
field enables applications to identify their RUN transactions. - Strings are UTF-8 encoded.
[op_false] [op_return]
is the standard prefix for metadata on Bitcoin SV since the Quasar hard fork.
You can easily identify RUN transactions and its outputs using the op_return
metadata.
JSON Payload
Sample JSON payload
{
"in": 0,
"ref": [
"native://Jig"
],
"out": [
"e494cd3d0c33615620c22f44cddf85f2bf613fd608dbfc53822664581205d198",
"9a99596f417e8925cb25f2acf99abe28f014aaad47ce93c427ee3afd3bcc5084"
],
"del": [],
"cre": [
"mhhHzeLjRTD4cjuygJFjqmCHCFpDKGrp75",
"mhhHzeLjRTD4cjuygJFjqmCHCFpDKGrp75"
],
"exec": [
{
"op": "DEPLOY",
"data": ["class Dragon extends Jig { }", { "deps": { "Jig": { "$jig": 0 } } }]
},
{
"op": "NEW",
"data": [{ "$jig": 1 }, []]
}
]
}
The JSON payload stores RUN-specific metadata. To the right is a JSON payload for the example above. Its fields are:
Name | Description |
---|---|
in | Number of jig and code inputs |
ref | Array of references to jigs and code used by not spent |
out | State hashes of jigs and code in transaction outputs |
del | State hashes of jigs and code deleted |
cre | New owners of jigs and code created |
exec | Statements to execute on the jigs |
The exec
field in particular contains the statements for RUN to execute. There are 4 opcodes supported by RUN in 0.6:
Opcode | Description | Data Format |
---|---|---|
DEPLOY | Upload new code | [<src1>, <props1>, <src2>, <props2>, ...] |
NEW | Instantiate a jig | [<jig class>, <args>] |
CALL | Call a method on a jig | [<jig>, <method>, <args>] |
UPGRADE | Replace code with new code | [<code>, <src>, <props>] |
The state hashes used in the out
and del
arrays are calculated by taking the sha-256 of the JSON serialization of the jigs. This state cache format will also be documented soon. Finally, the RUN VM provides certain native classes, like Jig
and Code
, that are built-in to its virtual machine. Eventually this virtual machine will be stored on-chain too ensuring total trustlessness.
The protocol will be documented more in a coming spec. Reach out for any questions!
Serialization
Pass a sidekick object in by value
class Person {
constructor(name, creationDate, profilePicture) {
this.name = name
this.creationDate = creationDate
this.profilePicture = profilePicture
}
valid() { ... }
}
class SocialGroup extends Jig {
constructor() { this.members = [] }
add(person) {
expect(person).toBeInstanceOf(Person)
expect(person.valid()).toBe(true)
this.members.push(person)
}
}
SocialGroup.deps = { Person, expect }
const group = new SocialGroup()
group.add(new Person('Li', new Date(), null))
Every jig, every argument to a method, and every static property of a class, must be serialized so that it can be restored later. RUN uses a custom serialization format that is similar to JSON. The following data types are supported:
- Numbers (IEEE-754)
- Strings (UTF-8)
- Booleans
- Objects (Basic and Sidekicks)
- Null and Undefined
- Arrays
- Uint8Arrays
- Maps
- Sets
- Jigs
- Code
- Berries
All JavaScript IEEE 754 numbers types are serializable, including Infinity, NaN, and -0. Properties may be safely set on Sets, Maps, and Arrays, including any of the above types. Circular references are OK.
Symbols are not however serializable and will not be supported. Other JavaScript objects that are not currently supported include Date, WeakMap, Promise, Error, Float32Array, and BigInt.
Sidekick objects are JavaScript objects whose constructor is a sidekick class. Whereas jigs have blockchain locations and owners, and RUN enforces that their state progresses from their method calls, sidekick objects are much simpler. They are stored by-value in their current state. A lock instance is an example of a sidekick object. In effect, they are data blobs with methods from on-chain code.
Sandboxing
All Code is sandboxed so that it does not have access to your application code, including your private keys! This provides security and also determinism when replaying a jig's history because a jig should evaluate the same in every environment. RUN uses Agoric SES, the gold standard in secure JavaScript evalution, combined with the vm
library in node and hidden iframes
in the browser, to perform this sandboxing. Despite this, it is recommended you avoid running untrusted code.
Safety Checks
RUN also protects you from performing updates on your jigs that will not be valid by others. For example, RUN will throw an error if you try to change a property on a jig directly rather than call a method, or if you attempt to set a function as a property on a jig. Only top-level method calls may change a jig's state which is also enforced by RUN. In addition, every RUN transaction is pre-verified before it is published on your local machine.
Ownership Rules
Jigs are special objects that have ownership rules which RUN enforces. These include:
- Ownership grants the right to change the jig’s state through an action.
- Ownership grants the right to create new jigs from the jig, where those new jigs have the same owner as the creator jig.
- Ownership grants the right to enact actions on other jigs from the jig that change the states of those other jigs, as long as the owners of those changed jigs approve.
Likewise, without ownership, the above actions cannot be performed. Proving ownership requires spending a Bitcoin output, so when ownership changes on a jig, further updates to that jig must occur in a new transaction. There is also a fourth rule of ownership that is a right granted to all jigs:
- A jig may reference or read the state of another jig with or without its owner’s approval.
Privacy
All jigs and jig code are stored on-chain and are open for everyone to read. This enables anyone to reconstruct the state of any jig. Privacy is achieved similar to Bitcoin UTXOs. For maximum privacy, public keys should not be re-used. In the future, we will introduce private jigs that are visible only to the current owners, but by their nature, they will not work as well with explorers, analytics and other services.
Backwards Compatibility
Starting in RUN 0.6, which uses protocol 0x05, all jigs will be backwards-compatible. This means jigs you launch today will always be supported into the future by the RUN SDK. This is our promise. To keep this promise, every jig is connected to its protocol version, so as RUN is updated, older jigs can be kept operational with their original behavior. In addition, we run tests against on-chain jigs with every update to ensure that your jigs continue to work.
API Reference
Run
class Run { ... }
A Run
instance coordinates activity between the application, its jigs and other creations, and the blockchain. You always create a Run
instance when you use the RUN SDK. This stores your settings, creates and signs transactions, loads jigs and code, connects to the blockchain network, securely sandboxes code, keeps an inventory of jigs, keeps a cache for fast loads, and more.
constructor(options)
Create a
Run
instance on Mocknet. The purse will be auto-funded.
const run = new Run({ network: 'mock' })
Create a run instance on Testnet
// Create a run instance on testnet
const run = new Run({
network: 'test',
owner: 'cTMHbJULREfbsUFsuJLMrNbJ7VrASgWeYfFF6EgJMy49ARnNed3d',
purse: 'cQP1h2zumWrCr2zxciuNeho61QUGtQ4zBKWFauk7WEhFb8kvjRTh'
})
constructor(options: object): Run
Creates a new Run
instance.
The constructor takes many possible options, but the three most important options are network
, owner
, and purse
. The network indicates which Bitcoin network to connect to. The owner
and the purse
are generally both private keys. The owner
stores the user's jigs. Each live jig has a UTXO associated with it assigned to the owner's address. The purse
is a separate private key used to pay the mining fees for RUN transactions. It is best if the owner
and purse
are separate accounts so that jigs and money are kept in different wallets.
Creating a new Run
instance will automatically activate it. See run.activate()
for more information.
Options
Property | Type | Description | Default |
---|---|---|---|
api |
string | One of 'run', 'mattercloud', or 'whatsonchain' | 'run' |
apiKey |
string | Blockchain API key for MatterCloud or WhatsOnChain. | undefined |
app |
string | Application id to distinguish RUN transactions. See How It Works. | Empty string |
blockchain |
Blockchain |
Blockchain implementation for interacting with the Bitcoin network. If specified, then network , api , and apiKey are ignored. |
RunConnect |
cache |
Cache |
Cache API implementation that RUN will use instead of the default | LocalCache or BrowserCache |
client |
boolean | Whether to only load jigs from the cache and not replay transactions | false |
logger |
Logger |
Logging object for internal run messages. If logger is null, then nothing will be logged. You may pass console as the logger to log everything. |
Logs warnings and errors to the console |
network |
string | Bitcoin network, either main , test , stn , or mock . |
main |
networkTimeout |
number | Timeout for network requests in milliseconds | 10000 |
networkRetries |
number | Number of times to retry network requests | 2 |
owner |
string or Owner |
Private key, public key, address, or custom lock used to own jigs and sign transactions | Randomly generated LocalOwner |
purse |
string or Purse |
Private key or Purse API used to pay for transactions. On the mock network, the purse will be funded automatically. For other networks, the user must fund the purse. |
Randomly generated LocalPurse |
timeout |
number | Timeout for all RUN actions in milliseconds | 30000 |
trust |
Array<string> or '*' |
IDs of transactions whose code is known not to be malicious | Empty array |
wallet |
Owner and Purse |
A shorthand for a single object that implements both Owner and Purse . If specified, then owner and purse are ignored. |
undefined |
activate()
activate()
Sets this instance to Run.instance
, the active Run
instance. All jig instantiations, updates, and class deployments will occur on the active run instance. The owner of this instance will sign transactions and its blockchain and purse are the ones which will be used. activate()
will also assign bsv.Networks.defaultNetwork
to be the active Run
's network configuration.
deploy(code)
Upload a BigInteger class to the blockchain
class BigInteger {
// ...
}
run.deploy(BigInteger)
await run.sync()
deploy(T: function): Code
Uploads code to the blockchain. Once deployment completes, the class or function will be assigned a location
so that it may be downloaded later. It will also be assigned an origin
and owner
. deploy
also sandboxes the class or function and returns it as Code with special Code
methods. Code uploaded does not necessarily need to be a Jig class — any class or function will work.
import(rawtx, options)
Import a RUN transaction to update it
const tx = await run.import(rawtx)
tx.update(() => run.deploy(AnotherClass))
const updated = await tx.export()
import(rawtx: string, options: ?object): Promise<Transaction>
Imports a Bitcoin transaction into a Run.Transaction object that can be updated.
Options
Property | Type | Description | Default |
---|---|---|---|
trust |
boolean | Whether to automatically trust the transaction being imported | false |
txid |
string | Optional transaction ID for the raw tx | undefined |
load(location, options)
Load a particular jig
const ticketLocation = 'afc557ef2970af0b5fb8bc1a70a320af425c7a45ca5d40eac78475109563c5f8_o1'
const ticket = await run.load(ticketLocation)
Load a code creation
const classLocation = 'e4a9618d3a187448feeb95ff30f7ec6381a025316cdcae2cdf388ad3de7fab6f_o2'
const MyClass = await run.load(classLocation)
const object = new MyClass()
load(location: string, options: ?object): Promise<Creation>
Universal load method to load any creation including jigs objects, jig classes, sidekick code, berries, and more. You pass in an on-chain location
. The location
is generally a transaction ID and output index pair. This string should usually be the jig's or class's last known location
property. However, it is also possible to load jigs at any state in the past using other location
s, although these will be read-only. Downloaded code will be safely sandboxed before being returned.
Options
Property | Type | Description | Default |
---|---|---|---|
trust |
boolean | Whether to automatically trust the transaction being imported | false |
sync()
Wait for all jigs to deploy and classes to upload before continuing
class MyClass { }
run.deploy(MyClass)
class MyObject extends Jig { }
const jig = new MyObject()
await run.sync()
console.log('jig origin', jig.origin)
console.log('class origin', MyClass.origin)
sync(): Promise<void>
Returns a promise that completes when all pending transactions are published. After this completes, all jigs and classes will be assigned location
s. Note: This method will not update the inventory with newly received UTXOs. For that, call run.inventory.sync()
.
transaction(f)
Send two tokens and deploy a class all in the same transaction
run.transaction(() => {
token1.send(address)
token2.send(address)
run.deploy(Trophy)
})
transaction(f: function): any
Shorthand for creating a new transaction, calling update with the function passed, and then calling publish.
trust(txid)
Trust a third-party transaction before loading its code
const codeTxId = codeLocation.slice(0, 64)
run.trust(codeTxId)
await run.load(codeLocation)
trust(txid: string)
Adds a transaction to the trusted set. All transactions that deploy or upgrade code must be trusted in order to load their jigs or code. By default, code that is not in the trusted set cannot be loaded. Make sure to pass a transaction ID and not a location.
There are two special values you can provide to trust()
. If you pass "cache"
, then any code in the RUN cache is trusted. This will lead to performance improvements. If you pass "*"
, then all code is trusted regardless of where it comes from. This is dangerous for production but may make testing and development easier.
app
app: string
An ID to distinguish your RUN transactions from other applications' transactions. Developers may set this in Run
's constructor in order to find their specific transactions in third-party tools and analytics, like trends.cash or WhatsOnChain. This string will be UTF-8 encoded and stored in the 5th chunk of the op_return
script. For more details about the protocol, see How It Works.
blockchain
Download a transaction from the blockchain
const txid = 'afc557ef2970af0b5fb8bc1a70a320af425c7a45ca5d40eac78475109563c5f8'
const tx = await run.blockchain.fetch(txid)
blockchain: Blockchain
Blockchain API to access the bitcoin network. See the Blockchain API. You may change the blockchain anytime.
cache
cache: Cache
Cache API used to save and load existing state. You may change the cache anytime.
client
client: boolean
In client mode, jig and code may only be loaded if they are in the cache and RUN will never execute transactions. This is useful if your cache is being populated via an external process that pre-loads relevant jigs. It adds a layer of protection and improves performance. The default value is false.
inventory
inventory: Inventory
Inventory that tracks creations owned by the current owner. This is reset whenever the owner
changes.
logger
logger: Logger
Logger implementation that prints out RUN information. You may change the logger anytime.
networkRetries
networkRetries: number
Number of times to retry network requests if they fail for some intermittent reason. The default value is 2.
networkTimeout
networkTimeout: number
Timeout for all network requests in milliseconds. If set to Infinity, then there is no network timeout. The default value is 10000.
owner
owner: Owner
The owner object used to update jigs and approve transactions. See the Owner API. You may change the owner anytime.
purse
purse: Purse
The purse used to pay for transactions. You may change the purse anytime.
timeout
timeout: number
Timeout for all RUN actions in milliseconds. This applies to load()
, sync()
, import()
and the inventory's sync()
. If set to Infinity, then there is no timeout. The default value is 10000.
static Jig
Create a custom jig that extends the Jig base class
class MyObject extends Run.Jig {
// ...
}
static Jig: Jig
Jig
class that is common for all Run
instances. It is set as Jig
at the global scope. You must extend from this base class to create a jig. See Jig.
static Code
static Code: Code
Code
base class that all deployed classes or functions inherit from. See Code.
static Berry
static Berry: Berry
Berry
base class that defines a method for loading third-party data and its structure. See Berry.
static Creation
static Creation: Creation
Creation
base class that jigs, code, and berries all share. See Creation.
static Transaction
static Transaction: Transaction
Transaction
class used to manually create, update, and inspect RUN transactions. See Transaction.
static plugins
static plugins: object
Contains the plugins that ship with the RUN SDK and may be used to configure RUN. See Plugins
static extra
static extra: object
Contains the standard library of code jigs that ship with the RUN SDK. See Extras.
static api
static api: object
An object that contains all plugin APIs that a user may implement. See APIs.
static util
static util: object
Various utility functions and classes. See Util
static configure(env, network)
Configure RUN from the command line
Run.configure(process.env)
static configure(env: object, network: ?string)
Configures RUN's defaults with the provided environment object. This is typically process.env
. This provides an easy way for apps to setup RUN from the shell. When Run
is instantiated, it will use these settings as defaults.
The network parameter is optional. If specified, it overrides the value in the environment variables. RUN will pick the PURSE
and OWNER
keys for that specific network.
Environment variables
Name | Description | Possible Values | Default |
---|---|---|---|
APP | App identifier used in RUN transactions | your app string | '' |
LOGGER | Whether to log internal messages to the console | true , false , debug |
false |
NETWORK | Network string | mock , main , test , stn |
mock |
PURSE | Purse key used | your string privkey | undefined |
PURSE_[network] | Purse key used on a specific network | your string privkey | undefined |
OWNER | Owner key used | your string privkey | undefined |
OWNER_[network] | Owner key used on a specific network | your string privkey | undefined |
API | Blockchain API when using mainnet or testnet | run , mattercloud , whatsonchain |
run |
APIKEY | API key for the blockchain API | your string api key | undefined |
APIKEY_[api] | API key used with a specific API | your string api key | undefined |
static protocol
static version: number
The version number of the RUN protocol. Jigs that you deploy will be tied to a particular protocol. Jigs and other creations deployed using protocol v5 and above and guaranteed to be supported in the future.
static version
static version: string
Semantic version of the RUN SDK.
static instance
static instance: Run
The currently active Run
instance. All jig instantiations, updates, and class deployments will occur on the active run instance. The owner of that instance will sign transactions and its blockchain and purse are the ones which will be used. You may change the active Run
instance either by calling run.activate()
or by creating a new Run
instance.
Jig
class Jig extends Creation { ... }
Base class for objects and code that can be owned on the blockchain and updated by calling methods. Jigs have various internal rules that ensure they are able to be safely composed together and called from each other. All classes that extend from Jig
are themselves jigs as well as Code. Jigs are also Creations and have location
, origin
, and nonce
properties.
init()
Simple jig that takes two parameters in its constructor
class SimpleStore extends Jig {
init(a, b) {
this.a = a
this.b = b
}
}
const store = new SimpleStore(1, 2)
console.log(store.b) // prints 2
init()
The initialization method for jig objects. init()
is to jigs as constructor()
is to normal classes. RUN will call init automatically when the class is instantiated, just like constructors, and init()
cannot be called directly by users. You may pass parameters into init()
and also call super.init()
to call its parent initializer.
sync(options)
sync(options: ?object): Promise<Jig>
Synchronizes the jig object with the blockchain. If there are any pending transactions for this jig to publish, this method will wait for them to broadcast. If there are no pending transactions, then this method will update the jig and any internally-referenced jigs to their latest states on the blockchain.
This method supports two options: forward
and inner
. If you pass { forward: false }
, then only pending transactions will be published and new updates will not be downloaded. This may be useful for performance reasons or if the Blockchain API does not support forward syncing, as is the case with WhatsOnChain. If you pass { inner: false }
, then only the current jig will be updated and any internally-referenced creations will not be explicitly updated.
Options
Property | Description | Default |
---|---|---|
forward |
Whether to sync the jig forward to its latest on-chain state | undefined |
inner |
Whether to also sync any referenced creations | undefined |
destroy()
destroy(): Jig
Destroys the jig. The destroyed jig will acquire a final location that ends with _dN
, where N is the index of the destroyed jig in its final transaction. This location is uniquely identifiable and the jig may still be referenced. However, it can no longer be updated. You may override the destroy()
method to perform custom destroy behavior.
auth()
auth(): Jig
Proves ownership over the current jig by spending its output in a transaction. You may override the auth()
method.
static load(location)
Load a jig
const msg = await Message.load('4e146ac161324ef0b388798462867c29ad681ef4624ea4e3f7c775561af3ddd0_o1')
static load(location: string): Promise<Berry>
Loads a jig object that is an instance of the particular jig class. This is an alternative to run.load()
for when you know the type of jig to load. This method is accessible when loading berries unlike run.load()
.
Code
class Code extends Creation { ... }
Code
is the base class for classes and functions deployed on-chain. Unlike Jig
however, you do not need to extend from Code yourself. When you call run.deploy
on a class or function, the returned sandboxed code is a Code
instance that gets all the functions and properties below. This lets you to upgrade, send and destroy code just like object jigs! All code are also Creations and have location
, origin
, and nonce
properties.
There are two kinds of Code
: jig code and sidekick code. Jig code are classes that extend from Jig
and behave just like jig objects, meaning their methods are protected and static function calls can update their state. Sidekick code are functions or classes that do not extend from Jig, and cannot be updated in that way over time. Sidekick code has no special rules unlike jigs and so is better suited for helper code. The Code base class is available via Run.Code
. Both jig code and sidekick code however may be upgraded, destroyed, and signed.
sync(options)
sync(options: ?object): Promise<Code>
Synchronizes code with the blockchain. If there are any pending transactions to publish, this method will wait for them to broadcast. If there are no pending transactions, then this method will update the code and any internally-referenced creations to their latest states on the blockchain.
This method supports two options: forward
and inner
. If you pass { forward: false }
, then only pending transactions will be published and new updates will not be downloaded. This may be useful for performance reasons or if the Blockchain API does not support forward syncing, as is the case with WhatsOnChain. If you pass { inner: false }
, then only the current jig will be updated and any internally-referenced creations will not be explicitily updated.
Options
Property | Description | Default |
---|---|---|
forward |
Whether to sync the code forward to its latest on-chain state | undefined |
inner |
Whether to also sync any referenced creations | undefined |
destroy()
Destroy a token class during deploy to stop future mints
class GiftCard extends Token { }
run.transaction(() => {
GiftCard.mint(1000)
GiftCard.destroy()
})
destroy(): Code
Destroys the code so that it cannot be updated in the future. The destroyed code will acquire a final location that ends with _dN
, where N is the index of the destroyed code in its final transaction. This location is uniquely identifiable and the code may still be referenced and instantiated. However, it may no longer be updated.
auth()
Enforce that only the owner of a jig class may create instances
class Coin extends Jig {
init() {
Coin.auth()
}
}
auth(): Code
Proves ownership over the current code by spending its output in a transaction.
upgrade(T)
Upgrade a jig class to add a new method
const DragonCode = await run.load(classLocation)
class Dragon extends Jig {
setName(name) { this.name = name }
setAge(age) { this.age = age }
}
DragonCode.upgrade(Dragon)
await DragonCode.sync()
upgrade(T: function)
Replaces the code with new behavior. With great power comes great responsibility. This method can completely change the behavior not just of the class but of any instances. It is best to use this feature to make bug fixes but not major changes. Once you are confident in the security of the code, you can set the upgradable
property to false to prevent further upgrades. Jig objects, as instances of jig code, will be automatically upgraded to the new behavior when they call sync()
and their class reference gets upgraded.
deps
static deps: ?object
The dependencies of this code. These must be manually specified in order for RUN to load them into the code sandbox. And properties on deps are added as globals within the sandbox.
interactive
interactive: boolean
Whether the code is allowed to openly interact with any other code. The default value is true. However, you may wish to set this value to false if you only want your class and its jigs to interact with itself and any of its code references. You can use this create a code family where only code, and jigs created from that code, in the group are allowed to interact together.
sealed
sealed: true | false | 'owner'
The sealed
property defines the rules for how a class can be extended. If true
, then no extensions are allowed. If false
, then anyone may extend from the class. If 'owner'
, then the class's owner must authorize of any extensions to the class. The default value is 'owner'
so that a class hierarchy can be trusted and the instanceof
keyword is safe by default.
upgradable
upgradable: boolean
Whether the code is allowed to be upgraded. You may wish to set this value to false to prove that the behavior will not change. The default value is true.
Berry
Define a berry class
class TwetchPost extends Berry {
init(text) {
this.text = text
}
static async pluck(location, fetch) {
const data = txo(await fetch(location))
if (data.out[0].s2 === '19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut') { // B protocol
return new TwetchPost(data.out[0].s3)
}
}
}
TwetchPost.deps = { txo }
class Berry extends Creation { ... }
Berry
is a base class for defining how data from third-party protocols should be loaded. Each class that extends from Berry defines a type of data. Berry instances, or simply berries, are read-only objects that represent this parsed data. Once loaded, but they may be used within jigs. All berries are also Creations and have location
, origin
, and nonce
properties.
init()
init()
The initialization method for berry instances. You may pass parameters into init()
and use those parameters to setup the berry. The pluck()
method must create a new instance of the berry which will call init()
.
static pluck(path, fetch)
pluck(path: string, fetch: function): Promise<Berry>
Plucks a berry from the blockchain. You will override this method to load a berry instance. The berry class should fetch the transaction using async fetch()
. This will return a raw transaction in hex format. The Berry should parse the transaction, perhaps using Tx or txo, and then instantiate a new Berry of the appropriate type with the data parsed out. If additional berries are required to build this one, they may be plucked recursively using Berry.load()
.
static load(path)
Load a berry
const post = await TwetchPost.load('4e146ac161324ef0b388798462867c29ad681ef4624ea4e3f7c775561af3ddd0')
static load(path: string): Promise<Berry>
Loads a berry of a particular type. RUN internally calls the berry class's pluck()
method to load the berry. load()
may be also called recursively inside of Berry.pluck()
to load sub-berries. You cannot override load()
.
Creation
Check that objects are indeed creations
Token2 instanceof Creation
// true
new Dragon() instanceof Creation
// true
twetchPostBerry instanceof Creation
// true
class Creation { ... }
A JavaScript object that RUN can be loaded from the blockchain. This includes jigs, code, and berries. The Creation
class, available via Run.Creation
is the effective base class for all creations. All creations have the location bindings location
, origin
, and nonce
as well as UTXO bindings owner
and satoshis
. They can be passed into jig functions and referenced as properties. To load a creation, call run.load
with its corresponding location.
origin
Two different tickets have different origins
class Ticket extends Jig { }
const ticket1 = new Ticket()
const ticket2 = new Ticket()
await run.inventory.sync()
// ticket1.origin !== ticket2.origin
origin: string
The unique blockchain location where the creation was initially deployed or loaded that distinguishes it from all other creations. The format for origin
is generally TXID_oN
where TXID
is the bitcoin transaction ID where the creation was first deployed. o
is shorthand for output and N
is the zero-based index representing this creation. Deleted creations, instead of ending in _oN
, end in _dN
instead. Berries have additional query parameters in their origin. In any case, the origin is set automatically by RUN after deploying or loading the creation and it becomes read-only thereafter. Jig methods may internally read the origin anytime after it is initially published.
location
The value of
location
changes with every update
class Weapon extends Jig {
upgrade() {
this.upgraded = true
}
}
const weapon = new Weapon()
await weapon.sync()
// weapon.origin === weapon.location
weapon.upgrade()
await weapon.sync()
// weapon.origin !== weapon.location
location: string
The blockchain location that uniquely identifies the current state of the creation. This is in the same form as origin
. However, unlike origin
, location
will update with every state change on the creation. When there is a pending change, reading location
will throw an error. This is because the Bitcoin transaction ID is not yet known so its location is undetermined. To avoid this error, be sure to call sync
before reading the location. Finally, note that location, like origin, is a read-only property that RUN updates for you.
nonce
nonce: number
The number of transactions this creation was updated in. This may be used to determine if one creation state is newer than another. Like origin
and location
, this is a read-only property that RUN updates for you.
owner
Transferring ownership of a code jig
class FrequentFlyerRewards extends Jig {
static transfer(to) {
this.owner = to
}
}
run.deploy(FrequentFlyerRewards).transfer(address)
owner: string|Lock
The owner is either a Bitcoin address string, a public key in hex, or a custom Lock that creates an output script. A jig may change this property to change owners, but it is read-only from outside the creation.
satoshis
Backing a jig with Bitcoin
class Item extends Jig {
init() {
this.satoshis = 10000
}
}
satoshis: number
This number represents the amount of Bitcoin in satoshis that are backing this creation. It may be increased or decreased but it may not be set below zero. Increasing this value will deposit Bitcoin into the output for the jig from the RUN purse. Decreasing it will withdraw Bitcoin to the RUN purse.
Transaction
class Store extends Jig {
set(value) { this.value = value }
}
const store = new Store()
const tx = new Run.Transaction()
tx.update(() => {
store.set('Hello, world')
})
await tx.publish()
class Transaction { ... }
The Transaction class gives you to finer control over the current Bitcoin transaction being built by RUN. This class is available via Run.Transaction
. It allows you to:
- Batch many jig updates together
- Load and inspect transactions
- Export transactions for others to co-sign
- Sign transactions involving your jigs
- Propose atomic swaps and other updates that involve multiple parties
Normally, when you update a jig that you own, RUN will begin a transaction for you automatically. However, you can take control of this process by creating a new Transaction
and calling update()
on the transaction to perform the update yourself. When you are finished, you can call publish()
to broadcast the transaction to the blockchain or export()
to save the transaction locally. Alternatively, you may cancel the updates by calling rollback
. Other parties may load your exported transaction by calling run.import
and then inspecting its contents using outputs
and deletes
arrays. If they approve of the updates to their jigs, they may call sign
and then publish
to broadcast the transaction.
update(callback)
Deploy multiple jig classes in a single transaction
const tx = new Run.Transaction()
tx.update(() => run.deploy(Picasso))
tx.update(() => run.deploy(Monet))
tx.update(() => run.deploy(VanGogh))
await tx.publish()
update(callback: function)
Adds additional updates to the transaction. This may include calling a jig method, deploying code, upgrading code, destroying jigs, and more. The callback function passed in will be called to record the new changes.
publish(options)
publish(options: ?object): Promise<string>
Finalizes the transaction and publishes it to the blockchain. Returns the txid as a hex string.
Options
Property | Description | Default |
---|---|---|
pay | Whether to pay for the transaction | true |
sign | Whether to sign the transaction | true |
export(options)
export(options: ?object): Promise<string>
Exports the in-progress transaction as a raw hex string. This transaction may be imported or broadcast afterward.
Options
Property | Description | Default |
---|---|---|
pay | Whether to pay for the transaction | true |
sign | Whether to sign the transaction | true |
cache()
Import and cache a transaction from the blockchain
const tx = await run.import(rawtx, { txid })
await tx.cache()
cache(): Promise<void>
Caches the jig and code states for the current transaction in the Cache
. After calling cache()
, the transaction is locked and there may be no more changes. This method may be useful for indexing transactions from the blockchain, or caching the state of jigs that will be broadcast by a third-party.
pay()
pay(): Promise<void>
Pays for the current transaction. This is performed automatically when publish()
or export()
is called, but you may wish to call it manually if you are paying with multiple purses or co-signing the transaction with others. If any additional updates are made to the transaction, including calling jig methods or deploying new code, then the transaction must be paid for again.
sign()
sign(): Promise<void>
Signs all inputs involved in the current transaction using the active Run
owner. The transaction should only be signed after all updates are added and the transaction has been paid for via pay()
. After all required signatures are added, then the transaction may be broadcasted, and if the transaction requires signatures from multiple different owners, then it must be exported and imported by other parties to be signed. If any additional updates are made to the transaction, including calling jig methods or deploying new code, then the signatures are reset and the transaction must be paid for again and re-signed by all parties.
rollback()
class Store extends Jig {
set(value) { this.value = value }
}
const store = new Store()
const tx = new Run.Transaction()
tx.update(() => {
store.set('Hello, world')
})
tx.rollback()
// store.value is undefined again
rollback()
Abandons the current transaction and reverts all changes to jigs and code. Any classes to be deployed will have their origins and locations cleared, and any jigs to be updated will revert to their prior state. Any jigs that are deployed for their first time will be reverted to an invalid state where they may no longer be used.
outputs
outputs: Array<Creation>
Jigs or code outputted from the transaction in their end state.
deletes
deletes: Array<Creation>
Jigs or code deleted within the transaction in their end state.
base
base: string
The starting Bitcoin transaction before any RUN inputs or outputs are added, stored as a hex string. This may be used to set a custom nLockTime or to add custom outputs before the RUN metadata. This is the only supported way to add custom outputs to a transaction before the RUN metadata. Custom inputs however are not yet supported. By default, the base
property stores an empty transaction.
Plugins
This section describes various classes that ship with the RUN SDK that may be used to manually configure RUN. Each plugin is available under Run.plugins
.
BrowserCache
Create a BrowserCache with a higher in-memory cache size
const { BrowserCache } = Run.plugins
const cache = new BrowserCache({ maxMemorySizeMB: 100 })
const run = new Run({ cache })
class BrowserCache implements Cache { ... }
A multi-level Cache implementation designed for web browsers that persists values across sessions.
BrowserCache
is the default implementation of the Cache
API setup by RUN when using browsers. BrowserCache
is a wrapper around the LocalCache
and an IndexedDB store. It uses the LocalCache
to quickly store and access values from local memory and the IndexedDb store to persist values across browser sessions. When a value is written, it is written both to the LocalCache
and to the IndexedDB store.
BrowserCache
may be accessed via Run.plugins.BrowserCache
.
constructor(options)
constructor(options: ?object)
Creates the browser cache. The options object may be used to configure the cache.
Options
Property | Type | Description | Default |
---|---|---|---|
maxMemorySizeMB |
number | Max size in megabytes (MB) of the cached data stored in memory | 10 (10MB) |
dbName |
string | Database name | 'run-browser-cache' |
dbVersion |
number | Database version | 1 |
dbStore |
string | Object storage name | 'run-objects' |
maxMemorySizeMB
maxMemorySizeMB: number
Size in megabytes (MB) of the in-memory cache. This value may be changed at runtime.
Inventory
Tracks jigs assigned to the current owner. An Inventory is automatically created with a new Run
instance and assigned to Run.inventory
.
jigs
Load all jigs that are tickets
// Update the inventory's jigs
await run.inventory.sync()
// Find the ticket
const ticketClassOrigin = 'e4a9618d3a187448feeb95ff30f7ec6381a025316cdcae2cdf388ad3de7fab6f_o1'
const ticket = run.inventory.jigs.find(jig.constructor.origin === ticketClassOrigin)
jigs: Array<Jig>
Returns an array of all jig objects owned by the RUN owner. This array is cached. To update it, call run.inventory.sync()
.
code
Load the user's classes
// Update the inventory's code
await run.inventory.sync()
// Find all classes we known that extend from a known origin
const itemBaseClassOrigin = 'e4a9618d3a187448feeb95ff30f7ec6381a025316cdcae2cdf388ad3de7fab6f_o2'
const ItemBase = await run.load(itemBaseClassOrigin)
const itemClasses = run.inventory.code.find(T => Object.getPrototypeOf(T) === ItemBase)
code: Array<function>
An array of all jig classes and functions owned by the RUN owner. This array is cached. To update it, call run.inventory.sync()
.
It is best to identify code using origins as seen on the right. Identifying classes by their names is not secure.
sync()
sync(): Promise<void>
Updates the local jig objects and code with the latest UTXO set.
LocalCache
class LocalCache implements Cache { ... }
An in-memory LRU Cache implementation with a maximum size. LocalCache
is the default implementation of the Cache
API when using Node. The Run
class will create one automatically if no cache is provided.
LocalCache
may be accessed via Run.plugins.LocalCache
.
constructor(options)
constructor(options: ?object)
Creates the local cache. The options object may be used to configure the cache.
Options
Property | Type | Description | Default |
---|---|---|---|
maxSizeMB |
number | Max size in megabytes (MB) of the cached data stored in memory | 10 (10MB) |
maxSizeMB
maxSizeMB: number
The maximum amount of data stored in the state cache in megabytes.
If this value is less than the current size, the cache will be shrunk to fit.
LocalOwner
class LocalOwner implements Owner { ... }
The default Owner implementation that uses a local private key to sign transactions. It is able to sign both standard locks (including addresses and public key strings) as well as group locks. Jigs created will all be assigned to the same address.
LocalOwner
may be accessed via Run.plugins.LocalOwner
.
constructor(options)
constructor(options: object): LocalOwner
Creates a LocalOwner
with the provided configuration.
Options
Property | Type | Description | Default |
---|---|---|---|
privkey |
string or bsv.PrivateKey | Private key used to own jigs and other resources | Randomly generated |
blockchain |
Blockchain |
Blockchain used to query UTXOs. If none is specified, then the inventory will not be synced. | None |
privkey
privkey: string
Hex private key string used to sign jig updates. This is read-only.
address
address: string
Address used to assign to new jigs. This is read-only.
LocalPurse
class LocalPurse implements Purse { ... }
The Purse implementation that RUN uses by default to pay for transactions using a local wallet. This is an implementation of the Purse
API.
LocalPurse
may be accessed via Run.plugins.LocalPurse
.
constructor(options)
constructor(options: object): LocalPurse
Creates a LocalPurse
with the provided configuration.
Options
Property | Type | Description | Default |
---|---|---|---|
privkey |
string or bsv.PrivateKey | Private key used to own jigs and other resources | Required |
blockchain |
Blockchain |
Blockchain used to query UTXOs | Required |
splits |
number | Number of UTXO splits to reduce mempool chain issues | 1 |
feePerKb |
number | Transaction fee in satoshis per kilobyte | 500 |
privkey
privkey: string
Private key used to sign the transaction.
address
address: string
Address used to find UTXOs and receive payments.
splits
Change the number of purse UTXO splits
run.purse.splits = 100
splits: number
The minimum number of UTXOs that the purse must have. If the number of UTXOs is less than this value, then RUN will automatically split your UTXOs the next time a RUN transaction is generated. The default value for splits
is 1.
Increasing this value may avoid the too-long-mempool-chain
error. For more information, see Debugging Tips.
balance()
Querying the current balance
console.log('Satoshis', await run.purse.balance())
balance(): Promise<number>
Returns a promise that resolves to the current balance in satoshis of this purse. This is the sum of all satoshis in this purse's unspent outputs.
utxos()
Building a transaction using the purse
const utxos = await run.purse.utxos()
const tx = new bsv.Transaction().from(utxos).change(run.purse.address).sign(run.purse.privateKey)
utxos(): Promise<{txid: string, vout: number, script: bsv.Script, satoshis: number}>
Returns a promise that resolves to the current UTXOs of this purse.
MatterCloud
Connect to MatterCloud Blockchain API with the given API key
const blockchain = new Run.plugins.MatterCloud({ apiKey: '...' })
const run = new Run({ blockchain })
class MatterCloud implements Blockchain { ... }
A Blockchain implementation that connects to the MatterCloud API. Only mainnet is supported.
MatterCloud
may be accessed via Run.plugins.MatterCloud
.
constructor(options)
constructor(options: object): MatterCloud
Creates a MatterCloud
instance with the provided configuration.
Options
Property | Type | Description | Default |
---|---|---|---|
apiKey |
string | API key to use. Currently, only MatterCloud API supports this option. | undefined |
Mockchain
Getting the mockchain from a run instance
const run = new Run({ network: 'mock' })
const mockchain = run.blockchain
Creating a mockchain manually and passing it into
Run
const mockchain = new Run.plugins.Mockchain()
const run = new Run({ blockchain: mockchain })
class Mockchain implements Blockchain { ... }
The mockchain is a local simulation of a blockchain that stored entirely in memory. It implements the Blockchain API. It accepts and validates real Bitcoin transactions using testnet settings. The mockchain simulates many aspects of a real Bitcoin API, including the 25 chained mempool limit, but everything happens locally.
We recommend using the mockchain during development and unit testing. The mockchain lets you simulate RUN without requiring network connectivity and without spending real bitcoins. Note that when the program closes however, all mockchain data is lost, so it is not a substitute for a real network.
The class is accessible via Run.plugins.Mockchain
, or as an instance in run.blockchain
after setting the network to mock
.
constructor()
constructor()
Creates a new mockchain. There are no parameters.
fund(address, satoshis)
fund(address: string, satoshis: number)
Directly funds an address with an amount of satoshis without requiring the spending of any inputs. The Run
class will automatically fund the purse, but you may fund additional addresses using this method.
block()
block()
Creates a new simulated block. This is useful when the 25 chained mempool limit is hit.
PayServer
Instantiate
Run
and use the testnet pay server
const apiKey = '<your-api-key>'
const payServer = new Run.plugins.PayServer(apiKey)
const run = new Run({ network: 'test', purse: payServer })
class PayServer implements Purse { ... }
A Purse implementation that uses the RUN Pay Server to pay for transactions.
PayServer
may be accessed via Run.plugins.PayServer
.
constructor(apiKey)
constructor(apiKey: string)
Creates the PayServer with a given API key. The API key may be generated via https://api.run.network/v1/test/pay/generate
. Only testnet is supported today.
RunConnect
Use to RUN Connect Blockchain API
const blockchain = new Run.plugins.RunConnect()
const run = new Run({ blockchain })
class RunConnect implements Blockchain { ... }
A Blockchain implementation that connects to the RUN Connect Blockchain Server.
RunConnect
may be accessed via Run.plugins.RunConnect
.
constructor(options)
constructor(options: object): RunConnect
Creates a RunConnect
instance with the provided configuration.
Options
Property | Type | Description | Default |
---|---|---|---|
network |
string | Either main or test | main |
Viewer
Load the jigs owned by another user
const viewer = new Run.plugins.Viewer(customLock)
const run = new Run({ owner: viewer })
await run.inventory.sync()
// run.inventory.jigs will contain their jigs
class Viewer implements Owner
An Owner implementation for loading another user's jigs.
These resources will be read-only and the Viewer
will not be able to sign for any updates.
Viewer
may be accessed via Run.plugins.Viewer
.
constructor(owner)
constructor(owner: string|Lock)
Creates a new viewer.
WhatsOnChain
Connect to WhatsOnChain API on testnet
const blockchain = new Run.plugins.WhatsOnChain({ network: 'test' })
const run = new Run({ blockchain })
class WhatsOnChain implements Blockchain { ... }
A Blockchain implementation that connects to the WhatsOnChain API.
WhatsOnChain
may be accessed via Run.plugins.WhatsOnChain
.
constructor(options)
constructor(options: object): WhatsOnChain
Creates a WhatsOnChain
with the provided configuration.
Options
Property | Type | Description | Default |
---|---|---|---|
network |
string | Either main or test | main |
Extras
The RUN SDK ships with several built-in Code creations that are part of its standard library. These include the standard Token2
class for numerical tokens and several other helpers. Each Code below is predeployed and available under Run.extra
.
asm
Create a custom R-puzzle output script
asm(`OP_DUP OP_3 OP_SPLIT OP_NIP OP_1 OP_SPLIT OP_SWAP OP_SPLIT OP_DROP OP_HASH160 ${rhash} OP_EQUALVERIFY OP_OVER OP_CHECKSIGVERIFY OP_CHECKSIG`)
function asm(s: string): string
Converts a Bitcoin script string in ASM notation into its hex string format. This code is deterministic and safe to use within Jigs. It is often useful when creating custom owner locks.
It is available via Run.extra.asm
and predeployed at the following locations:
Network | Location |
---|---|
main | 61e1265acb3d93f1bf24a593d70b2a6b1c650ec1df90ddece8d6954ae3cdd915_o1 |
test | 1f0abf8d94477b1cb57629d861376616f6e1d7b78aba23a19da3e6169caf489e_o1 |
B
class B extends Berry { ... }
The B
class loads file data stored on the blockchain into a JavaScript object. You may use B
to load images, 3D models, CSS styles, and more, to attach to your jigs, to display in your apps, or for any other purpose. The B:// data format is well-established as a method for storing files on-chain, and services like Bitcoin Files allow you to upload and view them easily. RUN uses B data to represent images for jigs. See Standard Metadata for more.
To load a B
berry, call either B.load()
or B.loadWithMetadata()
. B
will load the first output containing B data in the transaction.
It is available via Run.extra.B
and predeployed at the following locations:
Network | Location |
---|---|
main | 6fe169894d313b44bd54154f88e1f78634c7f5a23863d1713342526b86a39b8b_o1 |
test | 5435ae2760dc35f4329501c61c42e24f6a744861c22f8e0f04735637c20ce987_o1 |
load()
Load B:// data into an img element
B.load('2f3492ef5401d887a93ca09820dff952f355431cea306841a70d163e32b2acad').then(b => {
imgElement.src = `data:${b.mediaType};base64, ${b.base64Data}`
})
load(txid: string): Promise<B>
Loads B:// data from the transaction with the ID passed.
loadWithMetadata()
Load B:// data with attribution metadata
const metadata = {
title: 'T Rex skull icon',
license: '[CC BY 3.0](http://creativecommons.org/licenses/by/3.0)',
author: '[Delapouite](https://delapouite.com/)',
source: '[game-icons.net](https://game-icons.net/1x1/delapouite/t-rex-skull.html)'
}
await B.loadWithMetadata('55e2c09672355e009e4727c2365fb61d12c69add91215ee3e9f50aa76c808536', metadata)
loadWithMetadata(txid: string, metadata: ?object): Promise<B>
Loads B:// data and then attaches the provided metadata to the berry.
This is useful for adding attribution information to data that lacks it. See the Standard Metadata for information on metadata fields.
base64Data
base64Data: string
The data stored using B:// represented in base 64.
filename
filename: string
The filename as declared by the B:// protocol data.
mediaType
mediaType: string
The W3 registered media type. Examples include image/svg+xml
and image/png
.
Base58
class Base58 { ... }
It is available via Run.extra.Base58
and predeployed at the following locations:
Network | Location |
---|---|
main | 81bcef29b0e4ed745f3422c0b764a33c76d0368af2d2e7dd139db8e00ee3d8a6_o1 |
test | 424abf066be56b9dd5203ed81cf1f536375351d29726d664507fdc30eb589988_o1 |
decode()
decode(s: string): Array<number>
Decodes a Base58Check string into an array of bytes. This may be used to parse the public key hash data out of an address within a custom lock.
expect
class Post extends Jig {
init(message) {
expect(message).toBeString()
this.message = message
}
}
Post.deps = { expect: Run.extra.expect }
function expect(subject) { ... }
The expect
function is arbitrary code to help you check with parameters in Jigs. It is similar to Jest or Chai assertions. expect
takes a single argument, subject, and then lets you execute one of its assertion methods on ithe subject. If the assertion passes, then nothing happens, but if it fails, an Error is thrown.
Each method takes an optional last parameter message that is the error message to throw. If none is specified, then a default error message will be created.
It is available via Run.extra.expect
and predeployed at the following locations:
Network | Location |
---|---|
main | 71fba386341b932380ec5bfedc3a40bce43d4974decdc94c419a94a8ce5dfc23_o1 |
test | f97d4ac2a3d6f5ed09fad4a4f341619dc5a3773d9844ff95c99c5d4f8388de2f_o1 |
not
expect(name).not.toBeNumber()
not
The not
property reverses the condition of any methods that follow.
toBe()
expect(power > 9999).toBe(true)
toBe(value: any, message: ?string)
Checks that the subject is equal to the value, using the javascript === operator. This does not, however, deeply compare values of an object, so for deep object comparison we recommend toEqual
.
toEqual()
expect(names).toBe(['Stevie', 'Wonder'])
toEqual(value: any, message: ?string)
Deeply compares the subject to a value, which involves recursively traversing through every sub-property of objects to compare whether they are the same primitive values.
toBeInstanceOf()
expect(dragon).toBeInstanceOf(Dragon)
toBeInstanceOf(Class: Class, message: ?string)
Checks that an object is an instance of a class or one of its parents.
toBeDefined()
expect(message).toBeDefined()
toBeDefined(message: ?string)
Checks that a value is not undefined.
toBeNull()
expect(hat).not.toBeNull()
toBeNull(message: ?string)
Checks that a value is null. Often this is paired with not
to check that a value is not null, as seen on the right.
toBeNumber()
expect(health).toBeNumber()
toBeNumber(message: ?string)
Checks that a value is a numerical type. Numbers including all integers, floating point numbers, NaN, and Infinity.
toBeInteger()
expect(amount).toBeInteger()
toBeInteger(message: ?string)
Checks that a value is an integer number and does not have a decimal point.
toBeLessThan()
expect(damage).toBeLessThan(50)
toBeLessThan(value: number, message: ?string)
Checks that a value is a number less than a particular number.
toBeLessThanOrEqualTo()
toBeLessThanOrEqualTo(value: number, message: ?string)
Checks that a value is a number less than or equal to a particular number.
toBeGreaterThan()
expect(name.length).toBeGreaterThan(3)
toBeGreaterThan(value: number, message: ?string)
Checks that a value is a number greater than a particular number.
toBeGreaterThanOrEqualTo()
toBeGreaterThanOrEqualTo(value: number, message: ?string)
Checks that a value is a number greater than or equal to a particular number.
toBeBoolean()
expect(weaponsEnabled).toBeBoolean()
toBeBoolean(message: ?string)
Checks that a value is either true or false.
toBeString()
expect(name).toBeString()
toBeString(message: ?string)
Checks that a value is a string. It may still be the empty string however.
toBeObject()
expect(properties).toBeObject()
toBeObject(message: ?string)
Checks that a value is a non-null object or an Array.
toBeArray()
expect(tokens).toBeArray()
toBeArray(message: ?string)
Checks that a value is an array. It may be empty.
toBeSet()
expect(whitelist).toBeSet()
toBeSet(message: ?string)
Checks that a value is a Set instance. It may be empty.
toBeMap()
expect(users).toBeMap()
toBeMap(message: ?string)
Checks that a value is Map instance. It may be empty.
toBeUint8Array()
expect(buffer).toBeUint8Array()
toBeUint8Array(message: ?string)
Checks that a value is a Uint8Array instance. It may be empty.
toBeClass()
expect(CustomLock).toBeClass()
toBeClass(message: ?string)
Checks that a type is a class.
toBeFunction()
expect(calculateDamage).toBeFunction()
toBeFunction(message: ?string)
Checks that a type is a function. Classes are not considered functions for this assert.
toBeJigClass()
expect(Dragon).toBeJigClass()
toBeJigClass(message: ?string)
Checks that a type is a class that extends from Jig
.
toExtendFrom()
expect(MyToken).toExtendFrom(Token2)
toExtendFrom(T: function)
Checks that a class extends from another class.
Group
Create and then sign a 2-3 multi-sig
// Create a token owned by a 2-3 multi-sig
token.send(new Group([pubkey1, pubkey2, pubkey3], 2))
// Begin a transaction to spend it and sign with key #1
const run = new Run({ owner: privkey1 })
const tx = new Run.Transaction()
tx.update(() => token.send(address, 100))
await tx.pay()
await tx.sign()
// Co-sign with key #2
run.owner = privkey2
await tx.sign()
await tx.publish()
class Group implements Lock { ... }
A group lock is a m-of-n multi-sig output used to have more than one user own a resource. Combined with the LocalOwner
and TransactionAPI
, RUN is able to sign group locks using its default LocalOwner
in any order. See the example to the right.
A Group
lock is as secure as bitcoins themselves. If some parties can update a jig, they can also destroy the jig.
It is available via Run.extra.Group
and predeployed at the following locations:
Network | Location |
---|---|
main | 780ab8919cb89323707338070323c24ce42cdec2f57d749bd7aceef6635e7a4d_o1 |
test | 63e0e1268d8ab021d1c578afb8eaa0828ccbba431ffffd9309d04b78ebeb6e56_o1 |
constructor(pubkeys, required)
constructor(pubkeys: Array<string>, required: number): Group
Creates a m-of-n Group
for the set of public keys.
The maximum length of pubkeys
is 16. If required
is not specified, then it is the number of pubkeys.
add(pubkey)
add(pubkey: string)
Adds a pubkey to the pubkey list if it does not already exist.
pubkeys: Array
Array of public keys that are partial owners. There can be no more than 16 and they must be hex strings.
required: number
Number of signatures required to unlock the output. This is the m in m-of-n multi-sig.
Hex
It is available via Run.extra.Hex
and predeployed at the following locations:
Network | Location |
---|---|
main | 727e7b423b7ee40c0b5be87fba7fa5673ea2d20a74259040a7295d9c32a90011_o1 |
test | 1f0abf8d94477b1cb57629d861376616f6e1d7b78aba23a19da3e6169caf489e_o2 |
static stringToBytes
static bytesToString(b: Array<number>): string
Converts a array of bytes to a hex string. This method will throw if any entries in the array are not bytes.
static stringToBytes(s: string): Array<number>
Converts a hex string into an array of bytes. This method will throw if the string is not a a valid hex string.
sha256
function sha256(data: Array<number>): Array<number>
Performs a SHA256 hash on arbitrary data. This may be used within jigs or custom locks.
It is available via Run.extra.sha256
and predeployed at the following locations:
Network | Location |
---|---|
main | 3b7ef411185bbe3d01caeadbe6f115b0103a546c4ef0ac7474aa6fbb71aff208_o1 |
test | 4a1929527605577a6b30710e6001b9379400421d8089d34bb0404dd558529417_o1 |
Token
class Token extends Jig { ... }
Token
is a standard base class for fungible tokens similar to ERC-20 or SLP. It may be used for shares, loyalty points, gift cards, and more. Each Token
instance defines a numerical amount held by its owner. You extend from Token
to define a new kind of token. As the owner of this new token class, only you can mint new token instances. The token instances you mint though may be sent, owned, traded, and combined together with others. Like bitcoins, they are permissionless and only require their owner's approval to use.
Token
supports integer amounts like ERC-20 in its amount
field. In practice, you will often wish to display the token's amount with a decimal, like 1.50
. The static decimals
property may be used to declare how many digits the amount is to be shifted when the amount is displayed. In the previous example, amount
would be 150 and decimals
would be 2.
It is available via Run.extra.Token
and predeployed at the following locations:
Network | Location |
---|---|
main | 72a61eb990ffdb6b38e5f955e194fed5ff6b014f75ac6823539ce5613aea0be8_o1 |
test | 7d14c868fe39439edffe6982b669e7b4d3eb2729eee7c262ec2494ee3e310e99_o1 |
static mint(amount, to)
Minting a new token
class MyCustomToken extends Token { }
const token = MyCustomToken.mint(100)
mint(amount: number, to: ?(string|Lock)): Token
mint()
issues a new token with a specified amount. Only the owner of the extended token class is able to call this method. The newly minted token will be assigned to the to
address if specified, or to to the token class's owner if not.
send(to, amount)
const token = new MyCustomToken(100)
const sent = token.send(pubkey, 20)
console.log(sent.amount) // 20
console.log(token.amount) // 80
send(to: string|Lock, amount: ?number): Token
Sends amounts from this token to another user, creating a new Token in the process. The returned token will have the amount
specified and the current token will have its amount
decreased accordingly. When its amount
becomes zero, the token is automatically destroyed. If amount
is not specified, when the entire token amount is sent.
combine(...tokens)
Combining three tokens newly minted
const token1 = new MyCustomToken(10)
const token2 = new MyCustomToken(20)
const token3 = new MyCustomToken(30)
const combined = token1.combine(token2, token3)
console.log(combine.amount) // 60
Combining and sending tokens in a single transaction
const tokens = run.inventory.jigs.filter(jig => jig instanceof MyCustomToken)
run.transaction(() => {
const combined = tokens[0].combine(...tokens.slice(1))
combined.send(pubkey, amount)
})
combine(...tokens: Token): Token
combine
will merge the multiple tokens into a single token that has the combined amount. The tokens to combine must all be the same kind and the returned token object will be the same token that was called.
Tokens are like Bitcoin outputs. Every time you send some tokens, you split off an amount into a new output. Just like Bitcoins, you may later want to merge them back together. It is often useful to do this right before sending your tokens so that you can send their full amount. The example on the right shows how to combine and send tokens in a single transaction.
amount
amount: number
The integer value held within this token. The meaning of amount
depends on the token.
static decimals
class USDToken extends Token { }
USDToken.decimals = 2
const dollars = new USDToken(100)
let displayAmount = dollars.amount
while (let i = 0; i < USDToken.decimals. i++) {
displayAmount /= 10
}
console.log(displayAmount) // 1.00
static decimals: number
The number of decimal places to shift the amount
when displaying it. Token
uses integers by default for precision, just like ERC-20, but often times you will want to specify a decimals value to indicate a unit for display to the user. For example, a US Dollar coin might set decimals to 2 so that the base amount is cents but the display value is dollars.
Tx
Load an OP_RETURN into a berry
class OpReturn extends Berry {
async static pluck(txid, fetch) {
const rawtx = await fetch(txid)
const tx = new Tx(rawtx)
const script = tx.outputs[0].script
if (!script.startsWith('6a')) throw new Error('No an OP_RETURN')
return new OpReturn(tx.outputs[0].script)
}
init(script) { this.script = script }
}
OpReturn.deps = { Tx: Run.extra.Tx }
class Tx { ... }
Parses a raw hex transactions into an object that can be inspected. This is often useful when creating berries from transactions.
It is available via Run.extra.Tx
and predeployed at the following locations:
Network | Location |
---|---|
main | 312985bd960ae4c59856b3089b04017ede66506ea181333eec7c9bb88b11c490_o2 |
test | 33e78fa7c43b6d7a60c271d783295fa180b7e9fce07d41ff1b52686936b3e6ae_o2 |
constructor(rawtx)
constructor(rawtx: string): Tx
Parses the raw transaction into a Tx
object. The constructor will throw an error if rawtx
is not a valid Bitcoin transaction.
inputs
inputs: Array<{prevTxId: string, outputIndex: number, script: string, sequenceNumber: number}>
Transaction inputs array.
outputs
outputs: Array<{satoshis: number, script: string}
Transaction outputs array.
version
version: number
The version number of the transaction.
nLockTime
nLockTime: number
The lock time of the transaction.
txo
Parse a Twetch transaction into a berry
const { txo } = Run.extra
class TwetchPost extends Berry {
init (text) {
this.text = text
}
static async pluck (txid, fetch) {
const data = txo(await fetch(txid))
if (data.out[0].s2 === '19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut') {
return new TwetchPost(data.out[0].s3)
}
}
}
TwetchPost.deps = { txo }
function txo(rawtx: string): object
Parses a raw hex transaction into a TXO data structure. This is often an easy way to interpret a transaction for a Berry.
The following fields are supported:
- bN (base58)
- hN (hex)
- sN (utf8)
- bN.op (opcode num)
- e.h (txid)
- e.i (output index)
- e.v (satoshis)
- seq
- i
It is available via Run.extra.txo
and predeployed at the following locations:
Network | Location |
---|---|
main | 312985bd960ae4c59856b3089b04017ede66506ea181333eec7c9bb88b11c490_o1 |
test | 33e78fa7c43b6d7a60c271d783295fa180b7e9fce07d41ff1b52686936b3e6ae_o1 |
APIs
Check if a custom owner implementation will be accepted by RUN
class MyOwner { /* implementation */ }
console.log(new MyOwner() instanceof Run.api.Owner)
This section describes APIs that may be implemented by the developer to customize RUN. Each API is available under Run.api
and some commonly-used implementations of these APIs are provided via the built-in Plugins. You may check that your implementation conforms to the interface by using instanceof
on instances. You do not have to extend from the API classes for an implementation to be considered valid.
Blockchain
class Blockchain { ... }
The interface RUN uses to communicate with the Bitcoin network. The RUN SDK ships with several implementations: RunConnect, MatterCloud, WhatsOnChain, and the Mockchain. Developers may implement Blockchain
to connect to the Bitcoin network in custom ways.
network
network: string
A friendly network string. This is usually one of main
, test
, stn
, or mock
, however it may be any string. If the network starts with 'main', the RUN SDK will use mainnet settings wherever it matters. For all other networks, RUN will use testnet settings.
broadcast(rawtx)
Create and broadcast a simple transaction
const rawtx = new bsv.Transaction()
.from(utxo)
.to(address, amount)
.sign(privateKey)
.toString('hex')
await run.blockchain.broadcast(rawtx)
broadcast(rawtx: string): Promise<string>
Submits a raw transaction in hex format to the network. A promise is returned that must resolve with the transaction ID if the transaction was accepted. If the network did not accept the transaction, then the promise should be rejected with the error. This method should resolve successfully for broadcasts of transactions which are already in the mempool.
fetch(txid)
Downloads a transaction from the network
const txid = 'afc557ef2970af0b5fb8bc1a70a320af425c7a45ca5d40eac78475109563c5f8'
const rawtx = await run.blockchain.fetch(txid)
const tx = new bsv.Transaction(rawtx)
fetch(txid: string): Promise<string>
Downloads a transaction from the network. A promise is returned that will resolve with the raw hex transaction or reject with the error if the transaction could not be retrieved.
utxos(script)
Downloads the current UTXOs for a given address
const address = 'mpBU73vq9ajhkXknP1sNGe6wjXH7QtwmWm'
const script = Script.fromAddress(address).toHex()
const utxos = await run.blockchain.utxos(script)
const tx = new bsv.Transaction().from(utxos).change(address)
utxos(script: string): Promise<Array<{txid: string, vout: number, script: string, satoshis: number}>>
Returns the unspent outputs for a given address. A promise is returned that will resolve with an array of UTXOs, which may be empty, or reject with an error. Each returned UTXO may be converted to a bsv.Transaction.UnspentOutput
. The output script passed into utxos
will be in hex format.
Usually, implementations will index UTXOs by the script's hash rather than the script itself. The utxos
method takes a full script however to support partial compatibility for certain script patterns like P2PKH that often have dedicated query APIs. To calculate a script hash from a script using the bsv library, use the following: sha256(new Script(script).toBuffer()).reverse().toString('hex')
.
time(txid)
time(txid: string): Promise<number>
Returns the block time the transaction was confirmed, or the mempool acceptance time if not yet in a block, in milliseconds since the unix epoch.
spends(txid, vout)
spends(txid: string, vout: number): Promise<?string>
Returns the ID of the transaction that spends the given output, or null if the output is unspent.
If the Blockchain API does not support spends
, it may return a Run.error.NotImplementedError
, however, RUN will only partially work in this case.
Cache
A cache implementation that stores data in the browser's local storage
class LocalStorageCache {
async get(key) {
const x = localStorage.getItem(key)
if (x) return JSON.parse(x)
}
async set(key, value) {
localStorage.setItem(key, JSON.stringify(value))
}
}
class Cache { ... }
The interface RUN uses to cache jig state, transactions, and other data needed for fast use. The Cache
API also enables third-party state servers to exist and provide the intermediate state of jigs. All values stored are immutable and JSON-serializable. They should not be modified or created by hand.
The following keys will be set by RUN:
Key | Description |
---|---|
jig://<location> |
Serialized state for jigs and code |
berry://<location> |
Serialized state for berries |
tx://<txid> |
Raw transaction hex |
time://<txid> |
Transaction time in milliseconds since the unix epoch |
spend://<location> |
Transaction ID that spends the output |
trust://<txid> |
Whether a transaction is trusted (true) or not (false) |
ban://<location> |
Jig location that is known to be unloadable |
get(key)
get(key: string): Promise<?object>
Gets the value for a particular key. If this is an LRU cache, get() should also bump the key to the front.
RUN will call this when loading jigs to see if it can short-circuit its loading. If a value exists and is valid state, RUN will use it. This method is asyncronous to allow for third-party network calls.
set(key, value)
set(key: string, value: object): Promise<void>
Caches a value for a particular key.
RUN will call this whenever a new transaction is published and also when a jig is loaded. This method is asyncronous to allow for third-party network calls.
Lock
Send a token to a P2PK output script
const { asm } = Run.extra
class PayToPublicKeyLock {
constructor(pubkey) { this.pubkey = pubkey }
script() { return asm(`${this.pubkey} OP_CHECKSIG`) }
domain() { return 74 }
}
PayToPublicKeyLock.deps = { asm }
token.send(new PayToPublicKeyLock(pubkey))
class Lock { ... }
Locks are custom output scripts that may be assigned as owners to jigs. Every lock is an instance of a lock class that is deployed on-chain. The lock class's script()
method defines how the output script is built from the lock instance's properties. Because the lock class is also deployed on-chain, jig code can use logic that relies on strongly typed owners. For example, perhaps a jig must be held by 3 people in a Group for it to be used.
script()
script(): string
Returns the output script for the current lock as a hex string.
RUN will call this method when building your transactions. RUN provides the asm utility function to more easily build these scripts. For security reasons, this function should always rebuild the script when it's invoked and never cache the results.
domain()
domain(): number
Returns the maximum size of this lock's unlocking script in bytes. RUN uses this value to improve fee estimation for signatures exist by creating placeholders in unlocking scripts that are of size domain()
.
Calculating the domain
may depend on various properties of the lock. As a rule of thumb, Bitcoin signatures are at most 74 bytes in script, and public keys when compressed are 34 bytes in script. It is best to err on the conservative side.
Logger
Write info, warn, and error messages to a file
class FileLogger {
constructor(path) { this.stream = fs.createWriteStream(path, { flags: 'a' }) }
info(...messages) { this.log('INFO', ...messages) }
warn(...messages) { this.log('WARN', ...messages) }
error(...messages) { this.log('ERROR', ...messages) }
log(type, ...messages) { this.stream.write(`${type} ${messages.join(' ')} \n`) }
}
const run = new Run({ logger: new FileLogger('log.txt') })
class Logger { ... }
A custom logger API that may be used to intercept RUN logs and send them to a more appropriate place. By default, RUN will log errors and warnings to the console, but you may wish to log them to a custom file, or enable the info and debug logs too. It is not necessary to implement all methods below; whichever methods are implemented, RUN will call.
info(...messages)
info(...messages)
Called when RUN is performing a major action, such as loading a jig, or querying a REST API.
error(...messages)
error(...messages)
Called when RUN detects an error. These are recommended for production logs.
warn(...messages)
warn(...messages)
Called when RUN detects a possible error. These are recommended for production logs.
debug(...messages)
debug(...messages)
Called when RUN creates debug messages. These will be more far verbose than you would typically expect in production logs.
Owner
An owner that runs on a REST server
class RemoteWallet {
constructor(host, address) {
this.host = host
this.address = address
}
async nextOwner() { return this.address }
async sign(tx, parents, locks) {
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: { tx, parents }
}
const rawtx = await fetch('/sign', options).then(r => r.json())
return rawtx
}
}
class Owner { ... }
An owner of jigs and other resources that is run.owner
.
RUN calls the Owner API
to create new jigs and sign transactions for the user. It is different from the purse which is strictly for paying for transactions. This API supports both local and remote ownership via the async
methods.
For a complete guide on implementing the Owner API
for a third-party wallet, please reach out to @niv in the Atlantis slack.
nextOwner()
nextOwner(): Promise<string|Lock>
The owner address, pubkey, or lock, used to assign to new jigs.
sign(rawtx, parents, locks)
Sign all resources that are P2PK outputs
async sign(rawtx, parents, locks) {
const tx = new bsv.Transaction(rawtx)
for (let i = 0; i < tx.inputs.length; i++) {
if (locks[i] instanceof PayToPublicKeyLock) {
const sig = generateSignature(tx, i, parents[i], this.privateKey)
tx.inputs[i].setScript(sig.toString('hex'))
}
}
return tx.toString('hex')
}
sign(rawtx: string, parents: Array<?{satoshis: number, script: string}>, locks: Array<?Lock>): Promise<string>
Signs the transaction for the current jig owner.
The parents array is the same size as the transaction's inputs and contains a list of the parent outputs that are inputs in this transaction. This information may be needed to generate sighash values.
The third parameter, locks
, are the jig owners for each jig input. This is a higher-level representation of the parent output scripts that allows the sign
method to more easily determine which inputs to sign. If an input is not a jig, then its value in the array will be undefined. If the jig's owner is an address or public key, then the lock will be a CommonLock.
Purse
class Purse { ... }
An interface to pay for transactions that is the purse in RUN. RUN's default Purse
implementation is LocalPurse
For a complete guide on implementing the Purse API
for a third-party wallet, please reach out to @niv in the Atlantis slack.
pay(rawtx, parents)
Pay for a transaction using specific UTXOs
class PayWithUtxos {
constructor(utxos, privateKey) {
this.utxos = utxos
this.privateKey = privateKey
}
async pay(rawtx) {
const tx = new bsv.Transaction(rawtx)
tx.from(this.utxos)
tx.change(this.privateKey.toAddress())
tx.sign(this.privateKey)
return tx.toString('hex')
}
}
const run = new Run({ purse: new PayWithUtxos(utxos, privateKey) })
pay(rawtx: string, parents: Array<{satoshis: number, script: string}>): Promise<string>
Pays for a transaction so that it is acceptable to miners.
The transaction is passed in raw hex format. Raw transaction do not have information about their parent outputs, so the second parameter is a parents array that is a 1-1 mapping with the inputs of the transaction. This method should return a paid transaction in raw hex format.
To pay for a transaction, the method should add inputs and outputs so that its fee is raised high enough to be accepted by miners, and then the purse should sign the inputs it adds. As of May 2020, an appropriate miner fee is 0.5 satoshis per byte. The transaction passed will include placeholder signatures for any jig inputs to help with fee estimation.
This method should not assume that all inputs and outputs are dust because there may be backed jigs that were updated or created. A third-party implementation can check that there are no backed jigs by looking for any non-dust inputs or outputs. Otherwise, the purse should be prepared to pay more than the miner fee to back jigs and receive change from unbacked jigs.
pay()
is an asynchronous call that returns a promise. If an error is thrown, then the state of all jigs in the transaction will be reverted and the transaction will not be broadcasted. It is up to the purse to add retry logic if needed.
broadcast(rawtx)
broadcast(rawtx: string): Promise<>
A notification for purses when a transaction they paid for is being broadcast.
This method is optional for purses. It is designed for wallets to update their UTXOs. Wallets may also choose to broadcast the transaction themselves but if they do they should expect that the sometimes transaction might already be received by the network.
Util
This section describes various helper functionality made available via Run.util
.
CommonLock
Send a token to a P2PKH address
token.send(new CommonLock(address))
class CommonLock implements Lock { ... }
The CommonLock
generates a standard P2PKH output script for an address. It is created internally and automatically by RUN whenever an address or public key is set as a jig owner. CommonLock
instances are also passed into the locks
array in the Owner API's sign
method, and they may be created and used on their own as seen on the right. CommonLock
may be accessed via Run.util.CommonLock
.
constructor(address, testnet)
constructor(address: string, testnet: ?boolean): CommonLock
Creates a CommonLock
for a specific address.
address
address: string
The address string for this lock.
testnet
testnet: ?boolean
Whether this address is intended for testnet (true
), mainnet (false
), or an unspecified network (undefined
).
install(T)
Preinstall a class with presets to share it in a library
class Token extends Jig { ... }
Token.presets = {
main: {
location: 'b17a9af70ab0f46809f908b2e900e395ba40996000bf4f00e3b27a1e93280cf1_o1',
origin: 'b17a9af70ab0f46809f908b2e900e395ba40996000bf4f00e3b27a1e93280cf1_o1',
nonce: 1,
owner: '1PytriYokKN3GpKw84L4vvrGBwUvTYzCpx',
satoshis: 0
}
}
module.exports = Run.util.install(Token)
install (T: function): Code
Installs a class or function as Code but does not deploy it nor require a RUN instance to be created. If the resulting code is synced or referenced by another creation, it will automatically be deployed in the next transaction. This method is sometimes useful for providing sandboxed code to third-parties without requiring a RUN instance to be created. For example, the built-in extras that ship with RUN are pre-installed code.
metadata(rawtx)
Print out the RUN transaction metadata
Run.util.metadata(rawtx)
{
"app": "",
"version": 5,
"in": 0,
"ref": [
"native://Jig"
],
"out": [
"e494cd3d0c33615620c22f44cddf85f2bf613fd608dbfc53822664581205d198",
"9a99596f417e8925cb25f2acf99abe28f014aaad47ce93c427ee3afd3bcc5084"
],
"del": [],
"cre": [
"mhhHzeLjRTD4cjuygJFjqmCHCFpDKGrp75",
"mhhHzeLjRTD4cjuygJFjqmCHCFpDKGrp75"
],
"exec": [
{
"op": "DEPLOY",
"data": ["class A extends Jig { }", { "deps": { "Jig": { "$jig": 0 } } }]
},
{
"op": "NEW",
"data": [{ "$jig": 1 }, []]
}
]
}
metadata(rawtx: string): object
Extracts all Layer-2 metadata from a RUN transaction. The metadata
function will throw an error if not passed a valid RUN transaction in hex format. This method may be useful for debugging purposes and to determine whether a transaction is a RUN transaction. The metadata returned is not intended to be executed outside RUN. This metadata is stored in the first output using OP_RETURN
. For more about this data structure, see How It Works. The metadata
function may be accessed via Run.util.metadata
.
sha256(hex)
Use the optimized SHA-256
Run.util.sha256 = data => crypto.subtle.digest('SHA-256', Uint8Array.from(data))
sha256(data: Array<number>): Array<number>
The function that RUN calls to perform SHA256 hashing internally. By default, this runs in the same process as RUN, but it is possible to set a custom function that performs SHA256 in a background process. This may be useful in the browser to reduce UI stutter, or to used a more optimized SHA-256 implementation. The function takes and returns arrays of bytes.
unify(...creations)
Unify a token with its latest class
Run.util.unify(token, LatestTokenClass)
Unify dependencies so that two classes can be deployed together
run.transaction(() => {
Run.util.unify(Axe, Sword)
run.deploy(Axe)
run.deploy(Sword)
})
unify(...creations: Array<Creation>)
Synchronizes the creations passed so that they all refer to the same objects in the same state. RUN will prefer newer states over older states, so this is a quick way sync a jig to use its latest class if there is an upgrade. It may also be necessary to unify creations when using a Transaction.
Then RUN protocol requires that creations be unified within a transaction. This is usually performed automatically by RUN before each method is executed, but only for the creations that interact in that method.
Tools
Explorer
RUN has an explorer that you may use to view jigs and code. It is an excellent way to test that your objects are working. To give it a go, open run.network and click Explorer. Then, paste any location, address, or transaction ID into the search box.
Deploy
Generate keys and deploy code to testnet
> ./deploy --test lib/dragon.js
No purse keys found. Generate new keys? (y/n) y
Success!
Your private keys are in this .env file. Please keep it safe.
/home/yolanda/myproject/.env
Now send some funds to your testnet address
mims9NN2AEdEQ8ULcTPdgU8TdE34oojky9
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
█ ▄▄▄▄▄ █▄▀ ▀ ██▄▀▀█ █ ▄▄▄▄▄ █
█ █ █ █ █▀ ▀▀█▄ ▀█ █ █ █
█ █▄▄▄█ █▄█▀ ▄▄ ▄██▀ █ █▄▄▄█ █
█▄▄▄▄▄▄▄█▄█ █ █▄▀▄▀▄█ █▄▄▄▄▄▄▄█
█▄▄ █▀▄█ █ ▄ ▀▄██▄ ▄ ▄▀█ ▄▄▀ █
█ ▄ █▄▀▄▄█ ▄▄ ▄█▄█▀▀▀▀ █▄▄█▄▀█
█▄ ▄█▀▄ █ ▄▀ ▄▀▀▀████▄█▀▄▀▀█
██▄▀ █ ▄▄▀ ▀ ██▀▀ █ ▄▀ ▀ █▀▄▄▄█
█▄▄█▀██▄▀▄▄▀█▄▄▀▀█▄ █ ▄▀ ▄▀▀█▄█
█▄█▀▀ ▄█ ▄█▀███▄▀▀▄█▄█ ▀█▀█
██▄█▄██▄▄▀▄███▀▀▀▀▄ ▄ ▄▄▄ ▀ █
█ ▄▄▄▄▄ ██▀▀▀▀▀ █▀▄▄█ █▄█ ▀█▄██
█ █ █ █▀█▄ █▀▀▀▄ █▀ ▄▄ ▄▄▄ █
█ █▄▄▄█ █ █ ▀▄▀▀▄▄▄█ █ ▀ ▄▀▄█
█▄▄▄▄▄▄▄█▄▄█▄████▄▄▄▄▄█▄█▄██▄██
Waiting for funds to be sent...
Success! Received 3000000 satoshis
Deploying code to test
Dragon.presets.test.location = 'd9f9b15d73298bb807c3b22f62cb1f0b096d2a1c13678a9d29836a6fb538f901_o6'
Dragon.presets.test.origin = 'd9f9b15d73298bb807c3b22f62cb1f0b096d2a1c13678a9d29836a6fb538f901_o6'
Dragon.presets.test.nonce = 1
Dragon.presets.test.owner = 'mjs1rSdsm6tGAKdm79cUQ4rYXY1kvho34v'
Dragon.presets.test.satoshis = 0
Success! Presets saved.
Deploying your jig classes ahead of time is a great best practice. It allows code to be re-used across jigs, and jigs to safely rely on types. For example, an event may only accept tickets that are instances of a SpecificTicket
class. Pre-deployed classes make this possible, and save blockchain space and fees too! 🌎
Deploying code yourself is possible, but to help, the RUN SDK ships with a deploy
tool. deploy
is a command-line program to deploy your jig classes and update their presets. It can be found as dist/deploy
and it is a self-contained app.
To use deploy
, you'll specify:
- files - which files you wish to deploy (ex.
my-jigs/**
) - network - which networks to deploy to (ex.
main
) - purse - which purses to use (ex.
<private key>
)
deploy
will find which classes to deploy based on your module's exports, deploy them to each blockchain, print out your new presets, and then update your source code to save the presets.
To setup your purse keys, it is recommended that you create a .env
file to persist them. If you run deploy
without a purse, it'll prompt you to create a new .env
file and fund your new keys, as seen to the right. You can also pass a pre-existing purse on the command line. For example: deploy --purse=<my key> --main my-jigs.js
.
For more details, see deploy --help
.