# Tutorial

# Write a Smart Contract

Smart contracts are Javascript or Typescript classes that extend from Contract. For example, a smart contract for a simple chat is

import { Contract } from '@bitcoin-computer/lib'

class Chat extends Contract {
  constructor(greeting) {
    super({ messages: [greeting] })
  }

  post(message) {
    this.messages.push(message)
  }
}

Note that it is not possible to assign to this in constructors. Instead you can initialize a smart object by passing an argument into super as shown above.

# Create a Computer Object

You need to create an instance of the Computer class in oder to deploy smart contracts and create smart objects.

import { Computer } from '@bitcoin-computer/lib'

const computer = new Computer({ mnemonic: 'replace this seed' })

You can pass in a BIP39 mnemonic to initialize the wallet in the computer object. To securely generate a random mnemonic leave the mnemonic key undefined. You can find more configuration options here.

# Create a Smart Object

The computer.new function creates a smart object from a smart contract.

const chat = await computer.new(Chat, ['hello'])

The object chat that is returned has the properties defined in the class and five extra properties _id, _rev, _root, _owners and _amount.

expect(chat).to.deep.equal({
  messages: ['hello'],
  _id: '667c...2357:0',
  _rev: '667c...2357:0',
  _root: '667c...2357:0',
  _owners: ['03...'],
  _amount: 5820
})

The property _owners is an array of public keys that are able to spend that UTXO and the property _amount is the amount of satoshis in that UTXO. The meaning of the other properties are explained below.

The properties _id, _rev, _root reference a transaction that is broadcast when the expression await computer.new(Chat, ['hello']) is evaluated. This transaction encodes the expression

class Chat extends Contract {
  constructor(greeting) {
    super({ messages: [greeting] })
  }

  post(message) {
    this.messages.push(message)
  }
}

new Chat('hello')

Another user can download this transaction and evaluate this expression to obtain a copy of the smart object. This can be done using the computer.sync function described in the next section.

# Read a Smart Object

The computer.sync function computes the state of smart object. For example, synchronizing to 667c...2357:0 will return an object with the same value as chat.

expect(await computer.sync('667c...2357:0')).to.deep.equal(chat)

You can find more details about how this works here.

# Update a Smart Object

A Smart object can be updated by calling one of it's functions. Note that you have to await on all function calls to read the latest state.

await chat.post('world')

expect(chat).to.deep.equal({
  messages: ['hello', 'world'],
  _id: '667c...2357:0',
  _rev: 'de43...818a:0',
  _root: '667c...2357:0',
  _owners: ['03...'],
  _amount: 5820
})

When a function is called, a transaction is broadcast that inscribes the expression of the function call. In the example, the transaction with id de43...818a inscribes the expression

chat.post('world')

Note that the _rev property of the chat object has been updated to de43...818a:0. Every time a smart object is updated a new revision is created. The corresponding transaction id and output number is assigned to the _rev property. The _id property is never updated and is a unique identifier for each smart object. You can find more details about updating smart object here.

The computer.sync function maps revisions to historical states. It can be called with any historical revision. It will return the current state for the latest revision.

const oldChat = await computer.sync(chat._id)
expect(oldChat.messages).to.deep.equal(['hello'])

const newChat = await computer.sync(chat._rev)
expect(newChat.messages).to.deep.equal(['hello', 'world'])

# Find a Smart Object

The computer.query function returns the latest revision of a smart objects. It can be called with many different kinds of arguments, for example object one or more ids:

const [rev] = await computer.query({ ids: ['667c...2357:0']})
expect(rev).to.equal('de43...818a:0')

A basic pattern for many applications is to identify a smart object by its id, look up the object's latest revision using computer.query, and then to compute its latest state using computer.sync. For example, imagine a chat app where the url would contain the id of a specific chat. The app could compute the latest state of the chat as follows:

const urlToId = (url) => ...
const id = urlToId(window.location)
const [rev] = await computer.query({ ids: [id] })
const obj = await computer.sync(rev)

# Data Ownership

Every smart object can have up to three owners. Only an owner can update the object. The owners can be set by assigning string encoded public keys to the _owners property of a smart object. If the _owners property is not assigned in a smart contract it defaults to the public key of the computer object that created the smart object.

In the chat example the initial owner is the public key of the computer object on which computer.new function was called. Thus only a user with the private key for that public key will be able to post to the chat. We can add a function invite to update the owners array to allow more users to post.

class Chat extends Contract {
  ... // like above

  invite(pubKeyString) {
    this._owners.push(pubKeyString)
  }
}

# Privacy

By default, the state of all smart objects is public in the sense that any user can call the computer.sync function on an object's revision. However, you can restrict read access to an object by setting its _readers property to an array of public keys. If _readers is assigned, the meta-data on the transaction is encrypted using a combination of AES and ECIES. Only the specified readers can decrypt an encrypted object using the computer.sync function.

For example, if we want to ensure that only people invited to the chat can read the messages, we can update our example code as follows:

class Chat extends Contract {
  constructor(greeting, owner) {
    super({
      messages: [greeting],
      _readers: [owner]
    })
  }

  invite(pubKey) {
    this._owners.push(pubKey)
    this._readers.push(pubKey)
  }
}

A user can (only) read the state of a smart object if they have read access to the current and all previous versions of the object. It is, therefore, not possible to revoke read access to a revision after it has been granted. However, it is possible to remove a user's ability to read the state of a smart object from a point in time forwards.

When smart objects are encrypted the flow of cryptocurrency is not obfuscated.

# Off-Chain Storage

Not all data needs to be stored on the blockchain. For example, personal information should never be stored on chain, not even encrypted.

When the property _url of a smart object is set to the URL of a Bitcoin Computer Node, the metadata of the current function call is stored on the specified Bitcoin Computer Node. The blockchain contains a hash of the meta data and a link to where it can be retrieved.

For example, if we want to allow users to send images that are too large to be stored on chain to the chat, we can make use of the off-chain solution:

class Chat extends Contract {
  // ... as above

  post(message) {
    this._url = null
    this.messages.push(message)
  }

  postPic(picBuf) {
    this._url = 'https://my.bc.node.example.com'
    this.messages.push(picBuf)
  }
}

# Cryptocurrency

Each smart object can store an amount of cryptocurrency. By default a smart object stores a minimal (non-dust) amount. If the _amount property of a smart object is set to a number, the output storing that smart object will contain that number of satoshis. For example, consider the class Payment below.

class Payment extends Contract {
  constructor(amount: number) {
    super({ _amount })
  }

  cashOut() {
    this._amount = 546 // minimal non-dust amount
  }
}

If a user A wants to send 210000 satoshis to a user B, the user A can setup the payment as follows:

const computerA = new Computer({ mnemonic: <A's mnemonic> })
const payment = computerA.new(Payment, [210000])

When the payment smart object is created, the wallet inside the computerA object funds the 210000 satoshi that are stored in the payment object. Once user B becomes aware of the payment, he can withdraw by syncing against the object and calling the cashOut function.

const computerB = new Computer({ seed: <B's mnemonic> })
const paymentB = await computerB.sync(payment._rev)
await paymentB.cashOut()

One more transaction is broadcast for which user B pays the fees. This transaction has two outputs: one that records that the cashOut function was called with 546 satoshi and another that spends the remaining satoshi to user B's address.