Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,164 changes: 1,841 additions & 323 deletions package-lock.json

Large diffs are not rendered by default.

23 changes: 6 additions & 17 deletions packages/docs/Examples/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ icon: comment-discussion

## Smart Contract

A chat is just a smart object with a property `messages` of type `string[]`. Like all smart objects it has an `_owners` property set to the current data owner. The [`_readers`](./how-it-works.md#keyword-properties-control-the-transaction-being-built) property can be used to restrict read access.
The smart contract below creates a chat that can be initialized with an array of participants. All messages are encrypted so that only the members can read the messages. It is not possible to add a new participant, but any current participant can remove another participant from the chat.

```ts
class Chat extends Contract {
Expand All @@ -28,8 +28,9 @@ class Chat extends Contract {
}

remove(publicKey: string) {
this._readers = this._readers.filter((o) => o !== publicKey)
this._owners_ = this._owners_.filter((o) => o !== publicKey)
const isNotPublicKey = (o) => o !== publicKey
this._readers = this._readers.filter(isNotPublicKey)
this._owners = this._owners.filter(isNotPublicKey)
}
}
```
Expand All @@ -40,37 +41,25 @@ A new chat can be created using the [`new`](./API/new.md) function. Note that Bo

Later, Alice called the `remove` function removing Bob's public key from these arrays. After this point Bob cannot read or write anymore.

Eve was never part of the `_readers` array so she cannot read the content of the chat, nor write to it.

```ts
// Create and fund wallets
const alice = new Computer()
const bob = new Computer()
const eve = new Computer()
await alice.faucet(0.01e8)
await bob.faucet(0.01e8)

// Alice creates a chat with Bob and posts a message
const publicKeys = [alice.getPublicKey(), bob.getPublicKey()].sort()
const publicKeys = [alice.getPublicKey(), bob.getPublicKey()]
const alicesChat = await alice.new(Chat, [publicKeys])

// Alice can post to the chat
await alicesChat.post('Hi')

// Bob can read the current state of the chat and post a message
const bobsChat = (await bob.sync(alicesChat._rev)) as Chat
const bobsChat = await bob.sync(alicesChat._rev)
await bobsChat.post('Yo')
expect(bobsChat.messages).deep.eq(['Hi', 'Yo'])

// Eve was not invited and can neither read nor write
try {
// This line throws an error
await eve.sync(alicesChat._rev)
expect(true).eq(false)
} catch (err) {
expect(err.message).not.undefined
}

// Alice removes Bob's public key from the _readers array
await alicesChat.remove(bob.getPublicKey())

Expand Down
15 changes: 7 additions & 8 deletions packages/docs/Examples/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ order: -40
# Examples

{.compact}
| Method | Description |
| Method | Description |
|-----------------------------------------------------|-----------------------------------------------------|
| [Non Fungible Token (NFT)](./non-fungible-token.md) | A non fungible token similar to ERC721 |
| [Fungible Token](./fungible-token.md) | A fungible token similar to ERC20 |
| [Encrypted Chat](./chat.md) | An encrypted chat |
| [Swap](./swap.md) | Trustlessly exchange two smart object |
| [Sale](./sale.md) | Trustlessly sell a smart object to an unknown buyer |


| [Non Fungible Token (NFT)](./non-fungible-token.md) | A non fungible token similar to ERC721 |
| [Fungible Token](./fungible-token.md) | A fungible token similar to ERC20 |
| [Encrypted Chat](./chat.md) | An encrypted chat |
| [Swap](./swap.md) | Trustlessly exchange two smart object |
| [Sale](./sale.md) | Trustlessly sell a smart object to an unknown buyer |
| [Ordinal Sale](./ordinal-sale.md) | Trustlessly sell an ordinal to an unknown buyer |
18 changes: 12 additions & 6 deletions packages/docs/Examples/fungible-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ icon: circle

## Smart Contract

A fungible token has three properties, a `amount` indicating the number of tokens stored in the current smart object, a property `symbol` that stores the identifier of the tokens, and an `_owners` property set to the current owner of the smart object.
A fungible token is stored and transferred in a similar way to how satoshis are stored and transferred in Bitcoin. Its current state is stored in a set of UTXOs. When tokens are transferred a utxo is spent and two new UTXOs are created: one containing the amount sent and one containing the change.

The `transfer` function takes two arguments, the public key of the recipient and an `amount` to be sent. This function first checks if the current smart object contains sufficient supply and throws an error if it does not. If the supply is sufficient the supply of the current smart object is reduced by the amount to be sent. A new smart object is created that is owned by recipient and that contains the amount to be sent. This object is returned from the function call to create a new smart object.
The `transfer` function checks if the current on-chain object contains a sufficient number of tokens and throws an error if not. If sufficient, the supply of the current on-chain object is reduced by the amount to be sent. A new on-chain object, owned by the recipient and containing the sent amount, is created and returned.

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

```javascript
class Token extends Contract {
amount: number
symbol: string
Expand All @@ -23,6 +25,7 @@ class Token extends Contract {

transfer(recipient: string, amount: number) {
if (this.amount < amount) throw new Error()

this.amount -= amount
return new Token(recipient, amount, this.symbol)
}
Expand All @@ -42,11 +45,14 @@ const receiver = new Computer()
await sender.faucet(0.001e8)

// Mint new fungible token with total supply of 10
const token = await sender.new(Token, [sender.getPublicKey(), 10, 'MY-TOKEN'])
const token = await sender.new(Token, [sender.getPublicKey(), 10, 'SYM'])

// Send 2 tokens to receiver, sentToken will have supply of 2 and
// token will have a supply of 8.
// Send 2 tokens to receiver
const sentToken = await token.transfer(receiver.getPublicKey(), 2)

// SentToken will have supply of 2 and token will have a supply of 8
expect(token.amount).eq(8)
expect(sentToken.amount).eq(2)
```

## Code
Expand Down
61 changes: 43 additions & 18 deletions packages/docs/Examples/non-fungible-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ icon: image

## Smart Contract

Our example class for a non-fungible token only has two properties `name` and `symbol`. It has one function `transfer` that updates the `_owners` property.
A smart contract for an NFT is a class with two properties and one function to update the `_owners` property.

```ts
class NFT extends Contract {
import { Contract } from '@bitcoin-computer/lib'

export class NFT extends Contract {
constructor(name = '', symbol = '') {
super({ name, symbol })
}
Expand All @@ -23,11 +25,15 @@ class NFT extends Contract {

## Usage

To create a non-fungible token you can call the [`new`](./API/new.md) function as shown below. The [`faucet`](./API/faucet.md) function funds the `sender` object when the `sender` object is configured to `regtest`. The `sender.new` function mints a new NFT and the `transfer` function send the NFT to another user.
To create an on-chain object of class `NFT`, you can use the [`new`](./API/new.md) function of the `Computer` class. The [`faucet`](./API/faucet.md) function funds the `sender` object on `regtest`. The `sender.new` function mints a new NFT and the `transfer` function send the NFT to another user.

```ts
// Create the sender wallet
import { Computer } from '@bitcoin-computer/lib'
import { NFT } from './nft.js'

// Create the wallets
const sender = new Computer()
const receiver = new Computer()

// Fund the senders wallet
await sender.faucet(0.001e8)
Expand All @@ -36,31 +42,50 @@ await sender.faucet(0.001e8)
const nft = await sender.new(NFT, ['name', 'symbol'])

// Send the NFT
await nft.transfer(new Computer().getPublicKey())
await nft.transfer(receiver.getPublicKey())
```

If more than one NFT are broadcast one can save transaction fees by broadcasting a module containing the NFT smart contract first. The class `TCB721` is a helper class for that purpose.
The transaction that is broadcast when `sender.new` is called contains the expression below.

```js
class NFT extends Contract {
constructor(name = '', symbol = '') {
super({ name, symbol })
}

transfer(to: string) {
this._owners = [to]
}
}
new NFT('name', 'symbol')
```

### The Module System

If many NFTs are created, it is wasteful to store the same Javascript class in the blockchain multiple times. In this case, it is possible to use the module system to store the smart contract one time and refer to it multiple times.

```ts
// Create wallet
const sender = new Computer()
import { Computer } from '@bitcoin-computer/lib'

// Fund wallet
// Create and fund wallet
const sender = new Computer()
await sender.faucet(0.001e8)

// Create helper object
const tokenHelper = new TokenHelper(sender)

// Deploy smart contract
await tokenHelper.deploy()
const mod = await sender.deploy(`export ${NFT}`)

// Mint nfts
const nft1 = await sender.new(NFT, ['name1', 'symbol'], mod)
const nft2 = await sender.new(NFT, ['name1', 'symbol'], mod)
...
```

// Mint nft
nft = await tokenHelper.mint('name', 'symbol')
In this case, each transaction encoding the minting of an NFT contains the module specifier (a transaction id and an output number) and the following expression.

// Transfer NFT
await tokenHelper.transfer(new Computer().getPublicKey(), nft._id)
```js
new NFT('name1', 'symbol')
```

## Code

You can find the code [here](https://github.com/bitcoin-computer/monorepo/tree/main/packages/TBC721#readme).
You can find a slightly more elaborate implementation [here](https://github.com/bitcoin-computer/monorepo/tree/main/packages/TBC721#readme).
124 changes: 124 additions & 0 deletions packages/docs/Examples/ordinal-sale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---
order: -45
icon: number
---

# Ordinal Sale

The [`Sale`](./sale.md) smart contract is not safe to use with ordinals because the smart objects have different ordinal ranges before and after the call. To preserve the ordinal ranges the expression must not use the `_amount` keyword and must not return an object or an array containing an object.

Building a sale contract for ordinals is more complicated than for smart objects. A very clever construction was proposed [here](https://github.com/ordinals/ord/issues/802) and later [refined](https://github.com/ordinals/ord/issues/802#issuecomment-1498030294). Our smart contract below implements this exact idea.

!!!success
This code preserves ordinal ranges and is safe to use this in smart objects that contain ordinals.
!!!

## Smart Contract

The `exec` function of the `OrdSale` class swaps the owners just like in the `Sale` class. However it then proceeds to double the amount of `b1` and return `[b1, t, p, b2]`.

```ts
class OrdSale extends Contract {
static exec(b1: Payment, b2: Payment, n: NFT, p: Payment) {
const [ownerT] = n._owners
const [ownerP] = p._owners
n.transfer(ownerP)
p.transfer(ownerT)

b1.setAmount(b1._amount + b2._amount)
return [b1, n, p, b2]
}
}
```

When the `exec` function is evaluated, a transaction of the following form is built:

```
Inputs: b1, b2, t, p
Outputs: b1, t, p, b2
```

The amount of `b1` after the call is the sum of the amounts of `b1` and `b2` before combined. The smart object `b1` therefore absorbs the entire ordinals range of `b2`. The objects `n` and `p` do not change their amounts during the call, these objects preserve their ordinal ranges.

We now explain the whole process of minting an NFT, listing it for sale, and processing a purchase. We skip the step for minting as it was described above.

## Usage

### Building the Sales Transaction

In order to build the sale transaction, Seller first needs to create the objects `b1`, `b2`, and `p`:

```ts
const paymentMock = new PaymentMock(7860)
const b1Mock = new PaymentMock()
const b2Mock = new PaymentMock()
```

Next Seller can build the sales transaction by executing the following code:

```ts
const { tx } = await seller.encode({
exp: `${OrdSale} OrdSale.exec(b1, b2, nft, payment)`,
env: { b1: b1Mock._rev, b2: b2Mock._rev, nft: nft._rev, payment: paymentMock._rev },
mocks: { b1: b1Mock, b2: b2Mock, payment: paymentMock },
sighashType: SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,
inputIndex: 2,
fund: false,
})
```

Conceptually this is very similar to the use of `encode` above. Note however, that Seller signs the third input output pair this time. This is because the NFT is spent by the third input and the payment that Seller wants to obtain is in the third output.

### Buying the Ordinal NFT

This process is similar to the case of a smart object sale above. See the example blow.

### Full Example

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

// Create and fund wallets
const seller = new Computer(RLTC)
const buyer = new Computer(RLTC)
await seller.faucet(1e8)
await buyer.faucet(2e8)

// Seller mints an NFT
const nft = await seller.new(NFT, ['name', 'symbol'])

// Seller creates partially signed swap as a sale offer
const paymentMock = new PaymentMock(7860)
const b1Mock = new PaymentMock()
const b2Mock = new PaymentMock()

const { SIGHASH_SINGLE, SIGHASH_ANYONECANPAY } = Transaction
const { tx } = await seller.encode({
exp: `${OrdSale} OrdSale.exec(b1, b2, nft, payment)`,
env: { b1: b1Mock._rev, b2: b2Mock._rev, nft: nft._rev, payment: paymentMock._rev },
mocks: { b1: b1Mock, b2: b2Mock, payment: paymentMock },
// eslint-disable-next-line no-bitwise
sighashType: SIGHASH_SINGLE | SIGHASH_ANYONECANPAY,
inputIndex: 2,
fund: false,
})

// Buyer creates a payment object with the asking price
const payment = await buyer.new(Payment, [1e8])
const [paymentTxId, paymentIndex] = payment._rev.split(':')

// Buyer set's the payment object as the second input of the swap tx
tx.updateInput(1, { txId: paymentTxId, index: parseInt(paymentIndex, 10) })

// Buyer updates the second output of the swap tx to receive the NFT
tx.updateOutput(1, { scriptPubKey: buyer.toScriptPubKey() })

// Buyer funds, signs, and broadcasts to execute the sale
await buyer.fund(tx)
await buyer.sign(tx)
await buyer.broadcast(tx)
```

## Code

You can find the code [here](https://github.com/bitcoin-computer/monorepo/tree/main/packages/swap#readme).
Loading