Skip to content
Merged

Token #260

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ba82be5
Cra template: refactor .env.example
ClemensLey Aug 12, 2024
02ec2eb
Cra template: refactor navbar
ClemensLey Aug 13, 2024
ed81093
Cra template: add deploy flow
ClemensLey Aug 13, 2024
4274f4b
Swap: fix tests for payment
ClemensLey Aug 13, 2024
638dace
Swap: fix test for sale
ClemensLey Aug 13, 2024
ddc155f
Cra template: add instructions for adding a new package to readme file
ClemensLey Aug 13, 2024
210564b
Docs: fix example for sales
ClemensLey Aug 13, 2024
082397a
Initial commit for ft2
ClemensLey Aug 13, 2024
a2f74b7
TBC20: fix configuration
ClemensLey Aug 13, 2024
fac39d5
Swap: refactor tests for sale
ClemensLey Aug 13, 2024
22429ab
TBC20: refactor smart contract and test
ClemensLey Aug 13, 2024
c6e2ab8
FT2: use tbc20 in deploy script
ClemensLey Aug 13, 2024
c9052c8
FT2: use module specifier from deploy file
ClemensLey Aug 13, 2024
15276bc
FT2: add tbc20 to package.json file
ClemensLey Aug 13, 2024
b92b264
TBC20: fix import for contract
ClemensLey Aug 13, 2024
add39f2
FT2: make it possible to mint tbc20 contracts
ClemensLey Aug 13, 2024
27a480a
Swap: build files
ClemensLey Aug 14, 2024
e15b2b2
FT2: make is possible to create sell orders
ClemensLey Aug 14, 2024
3ef22a5
FT2: enable creating sell orders
ClemensLey Aug 14, 2024
54bd547
FT2: show sell orders
ClemensLey Aug 15, 2024
dda0dbe
FT2: close sell orders
ClemensLey Aug 15, 2024
11c80ea
Swaps: make Token contract compatible with Swap contract
ClemensLey Aug 16, 2024
b2e9d39
Swap: add buy contract
ClemensLey Aug 16, 2024
0602644
Swaps: refine buy contract and add tests
ClemensLey Aug 22, 2024
d194112
Swap: fix deploy script to support buy orders
ClemensLey Aug 22, 2024
54fc4d8
FT2: move code for sell orders to its own file
ClemensLey Aug 22, 2024
3893024
Swap: use staticSwapHelper in BuyHelper
ClemensLey Aug 22, 2024
c1cb2e9
FT2: add buy orders
ClemensLey Aug 22, 2024
9a6df4a
Components: show location data for smart objects again
ClemensLey Aug 22, 2024
ab20811
Refine dockerfile
ClemensLey Aug 22, 2024
a0ddbdc
FT2: Add reload buttons
ClemensLey Aug 23, 2024
4805333
FT2: fix dark mode
ClemensLey Aug 23, 2024
e7cb680
CRA-template: fix dark mode
ClemensLey Aug 23, 2024
4faf0a9
FT2: refactoring
ClemensLey Aug 23, 2024
bb84895
Simplify root level Dockerfile
ClemensLey Aug 23, 2024
bd73bba
Merge branch 'main' into token
ClemensLey Aug 23, 2024
964c73c
Swap: bug fix
ClemensLey Aug 26, 2024
55537de
BTC20: rename token.tokens to token.amount
ClemensLey Aug 26, 2024
e60992d
FT2: refactoring
ClemensLey Aug 27, 2024
8cb0eb9
FT2: remove unneeded <hr /> from orders page
ClemensLey Aug 30, 2024
a611fcb
FT2: Start work on hiding buy orders when they are closed
ClemensLey Aug 30, 2024
98c340e
SWAP: improve typing for buy contract
ClemensLey Aug 30, 2024
d64eb41
Dockerfile: install all dependencies and build from source
ClemensLey Aug 30, 2024
347721c
Fix version number
ClemensLey Aug 30, 2024
f8375e8
FT2: only show open sell orders
ClemensLey Aug 31, 2024
60b3eb2
Fix version numbers in package.json
ClemensLey Aug 31, 2024
280bfa4
FT2: refactor sell order
ClemensLey Sep 4, 2024
957480c
FT2: Make buy orders work
ClemensLey Sep 4, 2024
f5f5586
FT2: fix balance in deploy script
ClemensLey Sep 4, 2024
4e27be3
FT2: refactor orders
ClemensLey Sep 4, 2024
04786f5
Docs: more work on images
ClemensLey Sep 4, 2024
a9e892c
FT2: add package to dockerignore
ClemensLey Sep 4, 2024
deb3cc9
Merge branch 'main' into token
ClemensLey Sep 4, 2024
149301e
Swap: bug fix
ClemensLey Sep 4, 2024
1629f5f
Merge branch 'token' of https://github.com/bitcoin-computer/monorepo …
ClemensLey Sep 4, 2024
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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ packages/docs
packages/explorer
packages/nft
packages/ft
packages/ft2
packages/nodejs-template
packages/swap/
packages/TBC*/
Expand Down
16 changes: 12 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# We are using the alpine distribution with Long Term Support (LTS) as of 11/04/2020.
# We are using the alpine distribution with Node.js 20 (LTS).
FROM node:20-alpine

# install dependencies to run cmake
RUN apk add --no-cache python3 cmake make g++ curl
RUN apk add --no-cache cmake make gcc g++ python3 libstdc++ libgcc curl bash zeromq zeromq-dev

# Install node-gyp and node-gyp-build globally to avoid issues during build
RUN npm install -g node-gyp node-gyp-build

# Set the working directory inside the container
WORKDIR /dist
Expand All @@ -13,12 +16,17 @@ COPY . /dist
# Remove the existing node_modules directory
RUN rm -rf node_modules

# Install dependencies for the monorepo
RUN npm install
# Install dependencies
RUN npm install --build-from-source

# Ensure that the necessary binaries are in the PATH
ENV PATH="/dist/node_modules/.bin:${PATH}"

# Set the working directory to "monorepo/packages/node"
WORKDIR /dist/packages/node

# Expose the necessary port
EXPOSE 1031

# Define the command to run when the container starts
CMD ["npm", "start"]
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bitcoin-computer",
"version": "0.20.1-beta.0",
"version": "0.21.0-beta.0",
"description": "Lightweight Smart Contracts for Bitcoin and Litecoin",
"contributors": [
"Clemens Ley",
Expand Down
6 changes: 4 additions & 2 deletions packages/TBC20/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{
"name": "@bitcoin-computer/TBC20",
"version": "0.21.0-beta.0",
"description": "Minimalistic boilerplate to quick-start Bitcoin Computer development.",
"description": "Standard for fungible tokens on Bitcoin",
"private": true,
"type": "module",
"main": "./build/src/token.js",
"types": "./build/src/token.d.ts",
"devDependencies": {
"@types/chai": "^4.3.12",
"@types/expect": "^24.3.0",
Expand All @@ -20,7 +22,7 @@
"typescript": "~5.3.3"
},
"scripts": {
"build": "tsc -p tsconfig.release.json",
"build": "rm -rf build && tsc -p tsconfig.release.json",
"build:watch": "tsc -w -p tsconfig.release.json",
"clean": "rimraf coverage build tmp",
"clean-logs": "rm -f *.log 2> /dev/null",
Expand Down
91 changes: 52 additions & 39 deletions packages/TBC20/src/token.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,92 @@
/* eslint-disable max-classes-per-file */

import { Contract } from '@bitcoin-computer/lib'
const { Contract } = await import('@bitcoin-computer/lib')

export class Token extends Contract {
tokens: number
amount: number
name: string
symbol: string
_owners: string[]

constructor(to: string, tokens: number, name: string, symbol = '') {
super({ _owners: [to], tokens, name, symbol })
constructor(to: string, amount: number, name: string, symbol = '') {
super({ _owners: [to], amount, name, symbol })
}

transfer(to: string, amount: number): Token {
if (this.tokens < amount) throw new Error()
this.tokens -= amount
return new Token(to, amount, this.name, this.symbol)
transfer(to: string, amount?: number): Token | null {
// Send entire amount
if (typeof amount === 'undefined') {
this._owners = [to]
return null
}
// Send partial amount in a new object
if (this.amount >= amount) {
this.amount -= amount
return new Token(to, amount, this.name, this.symbol)
}

throw new Error('Insufficient funds')
}
}

export interface ITBC20 {
mint(publicKey: string, amount: number): Promise<string>
totalSupply(): Promise<number>
balanceOf(publicKey: string): Promise<number>
transfer(to: string, amount: number): Promise<void>
deploy(): Promise<string>
mint(publicKey: string, amount: number, name: string, symbol: string): Promise<string>
totalSupply(root: string): Promise<number>
balanceOf(publicKey: string, root: string): Promise<number>
transfer(to: string, amount: number, root: string): Promise<void>
}

export class TBC20 implements ITBC20 {
name: string
symbol: string
computer: any
mintId: string
mod: string

constructor(name: string, symbol: string, computer: any, mintId?: string) {
this.name = name
this.symbol = symbol
constructor(computer: any, mod?: string) {
this.computer = computer
this.mintId = mintId
this.mod = mod
}

async deploy() {
this.mod = await this.computer.deploy(`export ${Token}`)
return this.mod
}

async mint(publicKey: string, amount: number): Promise<string> {
const args = [publicKey, amount, this.name, this.symbol]
const token = await this.computer.new(Token, args)
this.mintId = token._root
return this.mintId
async mint(
publicKey: string,
amount: number,
name: string,
symbol: string
): Promise<string | undefined> {
const args = [publicKey, amount, name, symbol]
const token = await this.computer.new(Token, args, this.mod)
return token._root
}

async totalSupply(): Promise<number> {
if (!this.mintId) throw new Error('Please set a mint id.')
const rootBag = await this.computer.sync(this.mintId)
return rootBag.tokens
async totalSupply(root: string): Promise<number> {
const rootBag = await this.computer.sync(root)
return rootBag.amount
}

private async getBags(publicKey): Promise<Token[]> {
if (!this.mintId) throw new Error('Please set a mint id.')
const revs = await this.computer.query({ publicKey })
private async getBags(publicKey: string, root: string): Promise<Token[]> {
const revs = await this.computer.query({ publicKey, mod: this.mod })
const bags = await Promise.all(revs.map(async (rev: string) => this.computer.sync(rev)))
return bags.flatMap((bag: Token & { _root: string }) =>
bag._root === this.mintId ? [bag] : []
)
return bags.flatMap((bag: Token & { _root: string }) => (bag._root === root ? [bag] : []))
}

async balanceOf(publicKey: string): Promise<number> {
const bags = await this.getBags(publicKey)
return bags.reduce((prev, curr) => prev + curr.tokens, 0)
async balanceOf(publicKey: string, root: string): Promise<number> {
if (typeof root === 'undefined') throw new Error('Please pass a root into balanceOf.')
const bags = await this.getBags(publicKey, root)
return bags.reduce((prev, curr) => prev + curr.amount, 0)
}

async transfer(to: string, amount: number): Promise<void> {
async transfer(to: string, amount: number, root: string): Promise<void> {
let _amount = amount
const owner = this.computer.getPublicKey()
const bags = await this.getBags(owner)
const bags = await this.getBags(owner, root)
const results = []
while (_amount > 0 && bags.length > 0) {
const [bag] = bags.splice(0, 1)
const available = Math.min(_amount, bag.tokens)
const available = Math.min(_amount, bag.amount)
// eslint-disable-next-line no-await-in-loop
results.push(await bag.transfer(to, available))
_amount -= available
Expand Down
88 changes: 44 additions & 44 deletions packages/TBC20/test/token.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
/* eslint-disable no-unused-expressions */
// eslint-disable-next-line import/no-extraneous-dependencies
import { expect } from 'chai'
import { Computer } from '@bitcoin-computer/lib'
import dotenv from 'dotenv'
import { TBC20, Token } from '../src/token'

// If you want to connect to your local Bitcoin Computer Node, create a .env file
// in the monorepo root level and add the following line:
// BCN_URL=http://localhost:1031

dotenv.config({ path: '../../.env' })

const url = process.env.BCN_URL
Expand Down Expand Up @@ -36,7 +31,7 @@ describe('Token', () => {
})

it('The meta data should be set', async () => {
expect(token.tokens).to.eq(3)
expect(token.amount).to.eq(3)
expect(token._owners).deep.equal([sender.getPublicKey()])
expect(token.name).to.eq('test')
expect(token.symbol).to.eq('')
Expand All @@ -54,7 +49,7 @@ describe('Token', () => {
})

it('The meta data of token should be set correctly', () => {
expect(token.tokens).to.eq(2)
expect(token.amount).to.eq(2)
expect(token._owners).deep.equal([sender.getPublicKey()])
expect(token.name).to.eq('test')
expect(token.symbol).to.eq('')
Expand All @@ -64,7 +59,7 @@ describe('Token', () => {
})

it('The meta data of newToken should be set correctly', () => {
expect(newToken.tokens).to.eq(1)
expect(newToken.amount).to.eq(1)
expect(newToken._owners).deep.equal([receiver.getPublicKey()])
expect(newToken.name).to.eq('test')
expect(newToken.symbol).to.eq('')
Expand All @@ -77,101 +72,106 @@ describe('Token', () => {

describe('Using fungible tokens with a helper class', () => {
describe('mint', () => {
const tbc20 = new TBC20('test', 'TST', sender)
let mintId: string
const tbc20 = new TBC20(sender)
let root: string
it('Should create the tbc20 object', async () => {
const publicKey = tbc20.computer.getPublicKey()
mintId = await tbc20.mint(publicKey, 200)
expect(mintId).not.to.be.undefined
expect(typeof mintId).to.eq('string')
expect(mintId.length).to.be.greaterThan(64)
root = await tbc20.mint(publicKey, 200, 'test', 'TST')
expect(root).not.to.be.undefined
expect(typeof root).to.eq('string')
expect(root.length).to.be.greaterThan(64)
})

it('Should mint a root token', async () => {
const rootToken: any = await sender.sync(mintId)
const rootToken: any = await sender.sync(root)
expect(rootToken).not.to.be.undefined
expect(rootToken._id).to.eq(mintId)
expect(rootToken._rev).to.eq(mintId)
expect(rootToken._root).to.eq(mintId)
expect(rootToken.tokens).to.eq(200)
expect(rootToken._id).to.eq(root)
expect(rootToken._rev).to.eq(root)
expect(rootToken._root).to.eq(root)
expect(rootToken.amount).to.eq(200)
expect(rootToken.name).to.eq('test')
expect(rootToken.symbol).to.eq('TST')
})
})

describe('totalSupply', () => {
it('Should return the supply of tokens', async () => {
const tbc20 = new TBC20('test', 'TST', sender)
const tbc20 = new TBC20(sender)
const publicKey = tbc20.computer.getPublicKey()
await tbc20.mint(publicKey, 200)
const supply = await tbc20.totalSupply()
const root = await tbc20.mint(publicKey, 200, 'test', 'TST')
const supply = await tbc20.totalSupply(root)
expect(supply).to.eq(200)
})
})

describe('balanceOf', () => {
it('Should throw an error if the mint id is not set', async () => {
it('Should throw an error if the root is not set', async () => {
const publicKeyString = sender.getPublicKey()

const tbc20 = new TBC20('test', 'TST', sender)
const tbc20 = new TBC20(sender)
expect(tbc20).not.to.be.undefined
try {
await tbc20.balanceOf(publicKeyString)
expect(true).to.eq('false')
await tbc20.balanceOf(publicKeyString, undefined)
expect(true).to.eq(false)
} catch (err) {
expect(err.message).to.eq('Please set a mint id.')
expect(err.message).to.eq('Please pass a root into balanceOf.')
}
})

it('Should compute the balance', async () => {
const tbc20 = new TBC20('test', 'TST', sender)
const tbc20 = new TBC20(sender)
const publicKey = tbc20.computer.getPublicKey()
await tbc20.mint(publicKey, 200)
const root = await tbc20.mint(publicKey, 200, 'test', 'TST')
await sleep(200)
const res = await tbc20.balanceOf(publicKey)
const res = await tbc20.balanceOf(publicKey, root)
expect(res).to.eq(200)
})
})

describe('transfer', () => {
it('Should transfer a token', async () => {
const computer2 = new Computer()
const tbc20 = new TBC20('test', 'TST', sender)
const tbc20 = new TBC20(sender)
const publicKey = tbc20.computer.getPublicKey()
await tbc20.mint(publicKey, 200)
const root = await tbc20.mint(publicKey, 200, 'test', 'TST')
await sleep(200)
await tbc20.transfer(computer2.getPublicKey(), 20)
await tbc20.transfer(computer2.getPublicKey(), 20, root)
await sleep(200)
const res = await tbc20.balanceOf(publicKey)
const res = await tbc20.balanceOf(publicKey, root)
expect(res).to.eq(180)
})

it('Should transfer random amounts to different people', async () => {
const computer2 = new Computer()
const computer3 = new Computer()
const tbc20 = new TBC20('multiple', 'MULT', sender)
const tbc20 = new TBC20(sender)
const publicKey = tbc20.computer.getPublicKey()
await tbc20.mint(publicKey, 200)
const root = await tbc20.mint(publicKey, 200, 'multiple', 'MULT')
const amount2 = Math.floor(Math.random() * 100)
const amount3 = Math.floor(Math.random() * 100)
await sleep(200)
await tbc20.transfer(computer2.getPublicKey(), amount2)
await tbc20.transfer(computer2.getPublicKey(), amount2, root)
await sleep(200)
await tbc20.transfer(computer3.getPublicKey(), amount3)
await tbc20.transfer(computer3.getPublicKey(), amount3, root)
await sleep(200)
const res = await tbc20.balanceOf(publicKey)
const res = await tbc20.balanceOf(publicKey, root)
expect(res).to.eq(200 - amount2 - amount3)

const res2 = await tbc20.balanceOf(computer2.getPublicKey())
const res2 = await tbc20.balanceOf(computer2.getPublicKey(), root)
expect(res2).to.eq(amount2)

const res3 = await tbc20.balanceOf(computer3.getPublicKey())
const res3 = await tbc20.balanceOf(computer3.getPublicKey(), root)
expect(res3).to.eq(amount3)
})

it('Should fail if the amount is greater than the balance', async () => {
const computer2 = new Computer()
const tbc20 = new TBC20('test', 'TST', sender)
const tbc20 = new TBC20(sender)
const publicKey = tbc20.computer.getPublicKey()
await tbc20.mint(publicKey, 200)
const root = await tbc20.mint(publicKey, 200, 'test', 'TST')
await sleep(200)
try {
await tbc20.transfer(computer2.getPublicKey(), 201)
await tbc20.transfer(computer2.getPublicKey(), 201, root)
expect(true).to.eq('false')
} catch (err) {
expect(err.message).to.eq('Could not send entire amount')
Expand Down
Loading