diff --git a/.dockerignore b/.dockerignore index 8c9c81fa2..20e964ed8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,6 +19,7 @@ packages/docs packages/explorer packages/nft packages/ft +packages/ft2 packages/nodejs-template packages/swap/ packages/TBC*/ diff --git a/Dockerfile b/Dockerfile index fb614f189..f1fea7cec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 @@ -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"] diff --git a/package.json b/package.json index c75b4e2b4..3db5b81e9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/TBC20/package.json b/packages/TBC20/package.json index 920118b88..0fbc6fa31 100644 --- a/packages/TBC20/package.json +++ b/packages/TBC20/package.json @@ -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", @@ -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", diff --git a/packages/TBC20/src/token.ts b/packages/TBC20/src/token.ts index 780221f91..0439c765c 100644 --- a/packages/TBC20/src/token.ts +++ b/packages/TBC20/src/token.ts @@ -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 - totalSupply(): Promise - balanceOf(publicKey: string): Promise - transfer(to: string, amount: number): Promise + deploy(): Promise + mint(publicKey: string, amount: number, name: string, symbol: string): Promise + totalSupply(root: string): Promise + balanceOf(publicKey: string, root: string): Promise + transfer(to: string, amount: number, root: string): Promise } 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 { - 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 { + const args = [publicKey, amount, name, symbol] + const token = await this.computer.new(Token, args, this.mod) + return token._root } - async totalSupply(): Promise { - 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 { + const rootBag = await this.computer.sync(root) + return rootBag.amount } - private async getBags(publicKey): Promise { - 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 { + 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 { - const bags = await this.getBags(publicKey) - return bags.reduce((prev, curr) => prev + curr.tokens, 0) + async balanceOf(publicKey: string, root: string): Promise { + 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 { + async transfer(to: string, amount: number, root: string): Promise { 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 diff --git a/packages/TBC20/test/token.test.ts b/packages/TBC20/test/token.test.ts index 6939a2482..c76ef2fc7 100644 --- a/packages/TBC20/test/token.test.ts +++ b/packages/TBC20/test/token.test.ts @@ -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 @@ -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('') @@ -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('') @@ -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('') @@ -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') diff --git a/packages/TBC20/tsconfig.json b/packages/TBC20/tsconfig.json index d25092993..085679305 100644 --- a/packages/TBC20/tsconfig.json +++ b/packages/TBC20/tsconfig.json @@ -1,19 +1,13 @@ { "compilerOptions": { - "target": "ES6", - "module": "esnext", + "target": "ES2017", + "module": "ESNEXT", "resolveJsonModule": true, "moduleResolution": "node", "esModuleInterop": true, "experimentalDecorators": true, "skipLibCheck": true, - "lib": [ - "esnext", - "DOM" - ] + "lib": ["esnext", "DOM"] }, - "include": [ - "src/**/*", - "test/**/*" - ] + "include": ["src/**/*", "test/**/*"] } diff --git a/packages/TBC20/tsconfig.release.json b/packages/TBC20/tsconfig.release.json index a7ed494c9..736826cab 100644 --- a/packages/TBC20/tsconfig.release.json +++ b/packages/TBC20/tsconfig.release.json @@ -3,9 +3,8 @@ "compilerOptions": { "rootDir": ".", "outDir": "build", - "removeComments": true + "removeComments": true, + "declaration": true }, - "include": [ - "src/**/*" - ] + "include": ["src/**/*"] } diff --git a/packages/components/built/SmartObject.js b/packages/components/built/SmartObject.js index 3d0d792b4..7abac5131 100644 --- a/packages/components/built/SmartObject.js +++ b/packages/components/built/SmartObject.js @@ -82,105 +82,11 @@ var SmartObjectValues = function (_a) { return (_jsxs("div", { children: [_jsx("h3", __assign({ className: "mt-2 text-xl font-bold dark:text-white" }, { children: capitalizeFirstLetter(key) })), _jsx(ObjectValueCard, { content: toObject(value || "") })] }, i)); }) })); }; -// const revToId = (rev: string) => rev?.split(":")[0] -// const MetaData = ({ smartObject }: any) => ( -// <> -//

Meta Data

-// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -//
-// Key -// -// Short -// -// Value -//
Identity -//
_id
-//
-// -// {smartObject?._id} -// -//
Revision -//
_rev
-//
-// -// {smartObject?._rev} -// -//
Root -//
_root
-//
-// -// {smartObject?._root} -// -//
Owners -//
_owners
-//
-// -// {smartObject?._owners} -// -//
Amount -//
_amount
-//
-// -// {smartObject?._amount} -// -//
Transaction -// -// {revToId(smartObject?._rev)} -// -//
-// -// ) +var revToId = function (rev) { return rev === null || rev === void 0 ? void 0 : rev.split(":")[0]; }; +var MetaData = function (_a) { + var smartObject = _a.smartObject; + return (_jsxs(_Fragment, { children: [_jsx("h2", __assign({ className: "mb-2 text-4xl font-bold dark:text-white" }, { children: "Meta Data" })), _jsxs("table", __assign({ className: "w-full mt-4 mb-8 text-sm text-left text-gray-500 dark:text-gray-400" }, { children: [_jsx("thead", __assign({ className: "text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400" }, { children: _jsxs("tr", { children: [_jsx("th", __assign({ scope: "col", className: "px-6 py-3" }, { children: "Key" })), _jsx("th", __assign({ scope: "col", className: "px-6 py-3" }, { children: "Short" })), _jsx("th", __assign({ scope: "col", className: "px-6 py-3" }, { children: "Value" }))] }) })), _jsxs("tbody", { children: [_jsxs("tr", __assign({ className: "bg-white border-b dark:bg-gray-800 dark:border-gray-700" }, { children: [_jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: "Identity" })), _jsx("td", __assign({ className: "px-6 py-4 break-all text-sm" }, { children: _jsx("pre", { children: "_id" }) })), _jsx("td", __assign({ className: "px-6 py-4" }, { children: _jsx(Link, __assign({ to: "/objects/".concat(smartObject === null || smartObject === void 0 ? void 0 : smartObject._id), className: "font-medium text-blue-600 dark:text-blue-500 hover:underline" }, { children: smartObject === null || smartObject === void 0 ? void 0 : smartObject._id })) }))] })), _jsxs("tr", __assign({ className: "bg-white border-b dark:bg-gray-800 dark:border-gray-700" }, { children: [_jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: "Revision" })), _jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: _jsx("pre", { children: "_rev" }) })), _jsx("td", __assign({ className: "px-6 py-4" }, { children: _jsx(Link, __assign({ to: "/objects/".concat(smartObject === null || smartObject === void 0 ? void 0 : smartObject._rev), className: "font-medium text-blue-600 dark:text-blue-500 hover:underline" }, { children: smartObject === null || smartObject === void 0 ? void 0 : smartObject._rev })) }))] })), _jsxs("tr", __assign({ className: "bg-white border-b dark:bg-gray-800 dark:border-gray-700" }, { children: [_jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: "Root" })), _jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: _jsx("pre", { children: "_root" }) })), _jsx("td", __assign({ className: "px-6 py-4" }, { children: _jsx(Link, __assign({ to: "/objects/".concat(smartObject === null || smartObject === void 0 ? void 0 : smartObject._root), className: "font-medium text-blue-600 dark:text-blue-500 hover:underline" }, { children: smartObject === null || smartObject === void 0 ? void 0 : smartObject._root })) }))] })), _jsxs("tr", __assign({ className: "bg-white border-b dark:bg-gray-800 dark:border-gray-700" }, { children: [_jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: "Owners" })), _jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: _jsx("pre", { children: "_owners" }) })), _jsx("td", __assign({ className: "px-6 py-4" }, { children: _jsx("span", __assign({ className: "font-medium text-gray-900 dark:text-white" }, { children: smartObject === null || smartObject === void 0 ? void 0 : smartObject._owners })) }))] })), _jsxs("tr", __assign({ className: "bg-white border-b dark:bg-gray-800 dark:border-gray-700" }, { children: [_jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: "Amount" })), _jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: _jsx("pre", { children: "_amount" }) })), _jsx("td", __assign({ className: "px-6 py-4" }, { children: _jsx("span", __assign({ className: "font-medium text-gray-900 dark:text-white" }, { children: smartObject === null || smartObject === void 0 ? void 0 : smartObject._amount })) }))] })), _jsxs("tr", __assign({ className: "bg-white border-b dark:bg-gray-800 dark:border-gray-700" }, { children: [_jsx("td", __assign({ className: "px-6 py-4 break-all" }, { children: "Transaction" })), _jsx("td", { className: "px-6 py-4 break-all" }), _jsx("td", __assign({ className: "px-6 py-4" }, { children: _jsx(Link, __assign({ to: "/transactions/".concat(revToId(smartObject === null || smartObject === void 0 ? void 0 : smartObject._rev)), className: "font-medium text-blue-600 dark:text-blue-500 hover:underline" }, { children: revToId(smartObject === null || smartObject === void 0 ? void 0 : smartObject._rev) })) }))] }))] })] }))] })); +}; function Component() { var _this = this; var location = useLocation(); @@ -239,7 +145,7 @@ function Component() { setFunctionsExist(funcExist); }, [smartObject]); var _e = rev.split(":"), txId = _e[0], outNum = _e[1]; - return (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("h1", __assign({ className: "mb-2 text-5xl font-extrabold dark:text-white" }, { children: "Object" })), _jsxs("p", __assign({ className: "mb-6 text-lg font-normal text-gray-500 lg:text-xl dark:text-gray-400" }, { children: [_jsx(Link, __assign({ to: "/transactions/".concat(txId), className: "font-medium text-blue-600 dark:text-blue-500 hover:underline" }, { children: txId })), ":", outNum] })), _jsx(SmartObjectValues, { smartObject: smartObject }), _jsx(SmartObjectFunction, { smartObject: smartObject, functionsExist: functionsExist, options: options, setFunctionResult: setFunctionResult, setShow: setShow, setModalTitle: setModalTitle })] }), _jsx(Modal.Component, { title: modalTitle, content: FunctionResultModalContent, contentData: { functionResult: functionResult }, id: modalId })] })); + return (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("h1", __assign({ className: "mb-2 text-5xl font-extrabold dark:text-white" }, { children: "Object" })), _jsxs("p", __assign({ className: "mb-6 text-lg font-normal text-gray-500 lg:text-xl dark:text-gray-400" }, { children: [_jsx(Link, __assign({ to: "/transactions/".concat(txId), className: "font-medium text-blue-600 dark:text-blue-500 hover:underline" }, { children: txId })), ":", outNum] })), _jsx(SmartObjectValues, { smartObject: smartObject }), _jsx(SmartObjectFunction, { smartObject: smartObject, functionsExist: functionsExist, options: options, setFunctionResult: setFunctionResult, setShow: setShow, setModalTitle: setModalTitle }), _jsx(MetaData, { smartObject: smartObject })] }), _jsx(Modal.Component, { title: modalTitle, content: FunctionResultModalContent, contentData: { functionResult: functionResult }, id: modalId })] })); } export var SmartObject = { Component: Component diff --git a/packages/components/src/SmartObject.tsx b/packages/components/src/SmartObject.tsx index e2575490e..373daa3dc 100644 --- a/packages/components/src/SmartObject.tsx +++ b/packages/components/src/SmartObject.tsx @@ -48,111 +48,111 @@ const SmartObjectValues = ({ smartObject }: any) => { ) } -// const revToId = (rev: string) => rev?.split(":")[0] - -// const MetaData = ({ smartObject }: any) => ( -// <> -//

Meta Data

-// -// -// -// -// -// -// -// -// -// -// -// -// -// - -// -// -// -// -// - -// -// -// -// -// - -// -// -// -// -// - -// -// -// -// -// - -// -// -// -// -// -// -//
-// Key -// -// Short -// -// Value -//
Identity -//
_id
-//
-// -// {smartObject?._id} -// -//
Revision -//
_rev
-//
-// -// {smartObject?._rev} -// -//
Root -//
_root
-//
-// -// {smartObject?._root} -// -//
Owners -//
_owners
-//
-// -// {smartObject?._owners} -// -//
Amount -//
_amount
-//
-// -// {smartObject?._amount} -// -//
Transaction -// -// {revToId(smartObject?._rev)} -// -//
-// -// ) +const revToId = (rev: string) => rev?.split(":")[0] + +const MetaData = ({ smartObject }: any) => ( + <> +

Meta Data

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Key + + Short + + Value +
Identity +
_id
+
+ + {smartObject?._id} + +
Revision +
_rev
+
+ + {smartObject?._rev} + +
Root +
_root
+
+ + {smartObject?._root} + +
Owners +
_owners
+
+ + {smartObject?._owners} + +
Amount +
_amount
+
+ + {smartObject?._amount} + +
Transaction + + {revToId(smartObject?._rev)} + +
+ +) function Component() { const location = useLocation() @@ -234,7 +234,7 @@ function Component() { setModalTitle={setModalTitle} /> - {/* */} + @@ -39,9 +42,6 @@ To start the application run the command below and open [http://localhost:3000]( ```bash -# Move to the package -cd packages/cra-template - # Install the dependencies npm install @@ -54,6 +54,10 @@ npm run start +### Create a New Package + +Copy this folder into the packages folder and change the name in the `package.json` file. You can change the name of the app in Navbar.tsx. + ## Documentation Have a look at the [docs](https://docs.bitcoincomputer.io/) for the Bitcoin Computer. diff --git a/packages/cra-template/package.json b/packages/cra-template/package.json index fe35d36cb..bf9d2d83e 100644 --- a/packages/cra-template/package.json +++ b/packages/cra-template/package.json @@ -2,6 +2,7 @@ "name": "@bitcoin-computer/cra-template", "version": "0.21.0-beta.0", "private": true, + "type": "module", "dependencies": { "@bitcoin-computer/components": "^0.21.0-beta.0", "@bitcoin-computer/lib": "^0.21.0-beta.0", @@ -39,6 +40,7 @@ }, "scripts": { "start": "react-scripts start", + "deploy": "node --experimental-modules scripts/deploy.mjs", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", diff --git a/packages/cra-template/scripts/deploy.mjs b/packages/cra-template/scripts/deploy.mjs new file mode 100644 index 000000000..ee59dc83d --- /dev/null +++ b/packages/cra-template/scripts/deploy.mjs @@ -0,0 +1,60 @@ +import { config } from "dotenv" +import * as readline from 'node:readline/promises' +import { stdin as input, stdout as output } from 'node:process' +import { Contract } from "@bitcoin-computer/lib" + +// todo: import from file instead of defining the class here +// import { Counter } from "../src/contracts/counter" +export class Counter extends Contract { + constructor() { + super({ count: 0 }) + } + + inc() { + this.count += 1 + } +} + +const { Computer } = await import("@bitcoin-computer/lib") + +config() + +const rl = readline.createInterface({ input, output }) + +const { REACT_APP_CHAIN: chain, REACT_APP_NETWORK: network, REACT_APP_URL: url, REACT_APP_MNEMONIC: mnemonic } = process.env + +const computer = new Computer({ chain, network, mnemonic, url }) +await computer.faucet(2e8) +const balance = await computer.wallet.getBalance() + +// Summary +console.log(`Chain \x1b[2m${chain}\x1b[0m +Network \x1b[2m${network}\x1b[0m +Node Url \x1b[2m${url}\x1b[0m +Address \x1b[2m${computer.wallet.address}\x1b[0m +Mnemonic \x1b[2m${mnemonic}\x1b[0m +Balance \x1b[2m${balance / 1e8}\x1b[0m`) + +const answer = await rl.question('\nDo you want to deploy the contracts? \x1b[2m(y/n)\x1b[0m') +if (answer === 'n') { + console.log("\n Aborting...\n") +} else { + console.log("\n * Deploying counter contract...") + const counterModSpec = await computer.deploy(Counter) + + console.log(` +Successfully deployed smart contracts. + +----------------- + ACTION REQUIRED +----------------- + +(1) Update the following row in your .env file. + +REACT_APP_COUNTER_MOD_SPEC\x1b[2m=${counterModSpec}\x1b[0m + +(2) Run 'npm start' to start the application. +`) +} + +rl.close() diff --git a/packages/cra-template/src/components/Assets.tsx b/packages/cra-template/src/components/Assets.tsx index 9262b73ab..2ed608c6e 100644 --- a/packages/cra-template/src/components/Assets.tsx +++ b/packages/cra-template/src/components/Assets.tsx @@ -1,14 +1,12 @@ import { Auth, Gallery } from "@bitcoin-computer/components" -import { Counter } from "../contracts/counter" const publicKey = Auth.getComputer().getPublicKey() -const contract = { class: Counter } export function MyAssets() { return ( <>

My Counters

- + ) } @@ -17,7 +15,7 @@ export function AllAssets() { return ( <>

All Counters

- + ) } diff --git a/packages/cra-template/src/components/Mint.tsx b/packages/cra-template/src/components/Mint.tsx index 0d18fb76c..8a8d1bd53 100644 --- a/packages/cra-template/src/components/Mint.tsx +++ b/packages/cra-template/src/components/Mint.tsx @@ -65,7 +65,7 @@ export default function Mint() { const onSubmit = async (e: React.SyntheticEvent) => { e.preventDefault() try { - const counter = await computer.new(Counter) + const counter = await computer.new(Counter, [], process.env.REACT_APP_COUNTER_MOD_SPEC) setSuccessRev(counter._id) Modal.showModal("success-modal") } catch (err) { diff --git a/packages/cra-template/src/components/Navbar.tsx b/packages/cra-template/src/components/Navbar.tsx index 3eaa5da11..ccddce309 100644 --- a/packages/cra-template/src/components/Navbar.tsx +++ b/packages/cra-template/src/components/Navbar.tsx @@ -1,246 +1,41 @@ import { Link } from "react-router-dom" -import { Modal, Auth, UtilsContext, Drawer } from "@bitcoin-computer/components" -import { useEffect, useState } from "react" +import { Modal, Auth, Drawer } from "@bitcoin-computer/components" +import { useEffect } from "react" import { initFlowbite } from "flowbite" -import { Chain, Network } from "../types/common" - -const modalTitle = "Connect to Node" -const modalId = "unsupported-config-modal" - -function formatChainAndNetwork(chain: Chain, network: Network) { - const map = { - mainnet: "", - testnet: "t", - regtest: "r" - } - const prefix = map[network] - return `${prefix}${chain}` -} - -function ModalContent() { - const [url, setUrl] = useState("") - function setNetwork(e: React.SyntheticEvent) { - e.preventDefault() - localStorage.setItem("URL", url) - } - - function closeModal() { - Modal.get(modalId).hide() - } - - return ( -
-
-
- - - setUrl(e.target.value)} - value={url} - type="text" - name="url" - id="url" - className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" - placeholder="http://127.0.0.1:1031" - required - /> - - -
-
- -
- - -
-
- ) -} - -function SignInItem() { - return ( -
  • - -
  • - ) -} - -export function NotLoggedMenu() { - const [dropDownLabel, setDropDownLabel] = useState("LTC") - const { showSnackBar } = UtilsContext.useUtilsComponents() - - useEffect(() => { - initFlowbite() - - const { chain, network } = Auth.defaultConfiguration() - setDropDownLabel(formatChainAndNetwork(chain, network)) - }, []) - - const setChainAndNetwork = (chain: Chain, network: Network) => { - try { - localStorage.setItem("CHAIN", chain) - localStorage.setItem("NETWORK", network) - setDropDownLabel(formatChainAndNetwork(chain, network)) - window.location.href = "/" - } catch (err) { - showSnackBar("Error setting chain and network", false) - Modal.get(modalId).show() - } - } - - function CoinSelectionItem({ chain, network }: { chain: Chain; network: Network }) { - return ( -
  • -
    setChainAndNetwork(chain, network)} - className="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white" - > - {chain} {network} -
    -
  • - ) - } - - return ( - <> - -
      -
    • - - -
    • - - -
    - - ) -} - -function WalletItem() { - return ( -
  • - -
  • - ) -} - -const capitalizeFirstLetter = (s: string) => s.charAt(0).toUpperCase() + s.slice(1) function Item({ dest }: { dest: string }) { return ( - {capitalizeFirstLetter(dest)} + {dest.charAt(0).toUpperCase() + dest.slice(1)} ) } -export function LoggedInMenu() { +export function NotLoggedMenu() { return (
      - - - +
    • + +
    ) } -function NavbarDropdownButton() { +export function LoggedInMenu() { return ( - +
      + + +
    • + +
    • +
    ) } @@ -265,7 +60,6 @@ export function Navbar() {