Skip to content

Commit 711756d

Browse files
committed
Move to ESM-only + Libauth v2 (#142)
1 parent bac5967 commit 711756d

File tree

111 files changed

+2617
-3304
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+2617
-3304
lines changed

.cspell.json

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"bchjs",
1515
"bchreg",
1616
"bchtest",
17+
"bigints",
1718
"bitauth",
1819
"bitbox",
1920
"bitbox's",
@@ -225,6 +226,7 @@
225226
"**.pdf",
226227
// Do not spellcheck generated code
227228
"**/dist/**",
229+
"**/dist-test/**",
228230
"**/coverage/**",
229231
"**/build/**",
230232
"**/.docusaurus/**",

.eslintignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
**/dist/
2+
**/dist-test/
23
**/node_modules/
34
**/grammar/
45
/website/
56
*.js
7+
*.cjs
68
examples/webapp

.eslintrc.js renamed to .eslintrc.cjs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
const path = require('path');
2+
13
module.exports = {
24
root: true,
35
parser: '@typescript-eslint/parser',
46
plugins: ['@typescript-eslint'],
57
extends: ['airbnb-typescript/base'],
68
parserOptions: {
7-
project: './tsconfig.json',
9+
project: path.join(__dirname, 'tsconfig.json'),
810
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
911
sourceType: 'module', // Allows for the use of imports
12+
extraFileExtensions: ['.cjs'],
1013
},
1114
env: {
1215
'jest': true,
@@ -43,6 +46,6 @@ module.exports = {
4346
'max-classes-per-file': 0, // Multiple classes in one file are allowed (e.g. Errors)
4447
'@typescript-eslint/no-redeclare': 0, // I sometimes name variables an types the same
4548
'linebreak-style': 0, // Ignore linebreak lints https://stackoverflow.com/a/43008668/1129108
46-
'import/extensions': ['error', 'always'], // ESM requires file extensins
49+
'import/extensions': ['error', 'always'], // ESM requires file extensions
4750
},
4851
}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
lerna-debug.log
22
npm-debug.log
33
**/dist/
4+
**/dist-test/
45
**/node_modules/
56

67
**/.DS_Store

README.md

+1-5
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,12 @@ npm install cashscript
4848
import { Contract, ... } from 'cashscript';
4949
```
5050

51-
```js
52-
const { Contract, ... } = require('cashscript');
53-
```
54-
5551
Using the CashScript SDK, you can import contract artifact files, create new instances of these contracts, and interact with these instances:
5652

5753
```ts
5854
...
5955
// Import the P2PKH artifact
60-
const P2PKH = require('./p2pkh-artifact.json');
56+
import P2PKH from './p2pkh-artifact.json' assert { type: 'json' };
6157

6258
// Instantiate a network provider for CashScript's network operations
6359
const provider = new ElectrumNetworkProvider('mainnet');

examples/PriceOracle.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import { Script, Crypto } from 'bitbox-sdk';
2-
import { ECPair } from 'bitcoincashjs-lib';
3-
import { SignatureAlgorithm } from 'cashscript';
1+
import { padMinimallyEncodedVmNumber, flattenBinArray, secp256k1 } from '@bitauth/libauth';
2+
import { encodeInt, sha256 } from '@cashscript/utils';
43

54
export class PriceOracle {
6-
constructor(public keypair: ECPair) {}
5+
constructor(public privateKey: Uint8Array) {}
76

87
// Encode a blockHeight and bchUsdPrice into a byte sequence of 8 bytes (4 bytes per value)
9-
createMessage(blockHeight: number, bchUsdPrice: number): Buffer {
10-
const lhs = Buffer.alloc(4, 0);
11-
const rhs = Buffer.alloc(4, 0);
12-
new Script().encodeNumber(blockHeight).copy(lhs);
13-
new Script().encodeNumber(bchUsdPrice).copy(rhs);
14-
return Buffer.concat([lhs, rhs]);
8+
createMessage(blockHeight: bigint, bchUsdPrice: bigint): Uint8Array {
9+
const encodedBlockHeight = padMinimallyEncodedVmNumber(encodeInt(blockHeight), 4);
10+
const encodedBchUsdPrice = padMinimallyEncodedVmNumber(encodeInt(bchUsdPrice), 4);
11+
12+
return flattenBinArray([encodedBlockHeight, encodedBchUsdPrice]);
1513
}
1614

17-
signMessage(message: Buffer): Buffer {
18-
return this.keypair.sign(new Crypto().sha256(message), SignatureAlgorithm.SCHNORR).toRSBuffer();
15+
signMessage(message: Uint8Array): Uint8Array {
16+
const signature = secp256k1.signMessageHashSchnorr(this.privateKey, sha256(message));
17+
if (typeof signature === 'string') throw new Error();
18+
return signature;
1919
}
2020
}

examples/README.md

+4-14
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,26 @@
11
# CashScript examples
22
This folder contains a number of example CashScript contracts to show off its functionality and usage. The `.cash` files contain example contracts, and the `.ts`/`.js` files contain example usage of the CashScript SDK with these contracts.
33

4-
The "Hello World" of cash contracts is defining the P2PKH pattern inside a cash contract, which can be found under [`p2pkh.cash`](/examples/p2pkh.cash). Its usage can be found under [`p2pkh.ts`](/examples/p2pkh.ts). To showcase web usage of the CashScript SDK, the `p2pkh.cash` contract has also been integrated into a very simple React webapp under [`webapp`](/examples/webapp/).
4+
The "Hello World" of cash contracts is defining the P2PKH pattern inside a cash contract, which can be found under [`p2pkh.cash`](/examples/p2pkh.cash). Its usage can be found under [`p2pkh.ts`](/examples/p2pkh.ts).
55

66
## Running the examples
77
To run the examples, clone this repository and navigate to the `examples/` directory. Since the examples depend on the SDK, be sure to run `yarn` inside the `examples/` directory, which installs all required packages.
88

99
```bash
10-
git clone [email protected]:Bitcoin-com/cashscript.git
10+
git clone [email protected]:CashScript/cashscript.git
1111
cd cashscript/examples
1212
yarn
1313
```
1414

15-
All `.ts` files can then be executed with `ts-node`.
15+
All `.ts` files can then be executed with `ts-node-esm`.
1616

1717
```bash
1818
npm install -g ts-node
19-
ts-node p2pkh.ts
19+
ts-node-esm p2pkh.ts
2020
```
2121

2222
All `.js` files can be executed with `node`.
2323

2424
```bash
2525
node p2pkh.js
2626
```
27-
28-
## Running the webapp example
29-
The webapp example can be run from the `webapp/` folder.
30-
31-
```bash
32-
yarn
33-
yarn start
34-
```
35-
36-
This will display a simple webapp that allows you to create a P2PKH contract from a specific seed phrase and use the contract to send BCH. The code can be found in [`webapp/src/App.ts`](/examples/webapp/src/App.ts). Note that the `.cash` file has to be read, and then passed into the CashScript SDK, rather than passing just the file path into the SDK.

examples/announcement.ts

+25-32
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,34 @@
11
import { Contract, ElectrumNetworkProvider } from 'cashscript';
22
import { compileFile } from 'cashc';
3-
import path from 'path';
43
import { stringify } from '@bitauth/libauth';
4+
import { URL } from 'url';
55

6-
run();
7-
export async function run(): Promise<void> {
8-
try {
9-
// Compile the Announcement contract to an artifact object
10-
const artifact = compileFile(path.join(__dirname, 'announcement.cash'));
6+
// Compile the Announcement contract to an artifact object
7+
const artifact = compileFile(new URL('announcement.cash', import.meta.url));
118

12-
// Initialise a network provider for network operations on MAINNET
13-
const provider = new ElectrumNetworkProvider();
9+
// Initialise a network provider for network operations on MAINNET
10+
const provider = new ElectrumNetworkProvider();
1411

15-
// Instantiate a new contract using the compiled artifact and network provider
16-
// AND providing the constructor parameters (none)
17-
const contract = new Contract(artifact, [], provider);
12+
// Instantiate a new contract using the compiled artifact and network provider
13+
// AND providing the constructor parameters (none)
14+
const contract = new Contract(artifact, [], provider);
1815

19-
// Get contract balance & output address + balance
20-
console.log('contract address:', contract.address);
21-
console.log('contract balance:', await contract.getBalance());
22-
console.log('contract opcount:', contract.opcount);
23-
console.log('contract bytesize:', contract.bytesize);
16+
// Get contract balance & output address + balance
17+
console.log('contract address:', contract.address);
18+
console.log('contract balance:', await contract.getBalance());
19+
console.log('contract opcount:', contract.opcount);
20+
console.log('contract bytesize:', contract.bytesize);
2421

25-
// Send the announcement. Trying to send any other announcement will fail because
26-
// the contract's covenant logic. Uses a hardcoded fee and minChange so that
27-
// change is only sent back to the contract if there's enough leftover
28-
// for another announcement.
29-
const str = 'A contract may not injure a human being or, through inaction, allow a human being to come to harm.';
30-
const tx = await contract.functions
31-
.announce()
32-
.withOpReturn(['0x6d02', str])
33-
.withHardcodedFee(1000)
34-
.withMinChange(1000)
35-
.send();
22+
// Send the announcement. Trying to send any other announcement will fail because
23+
// the contract's covenant logic. Uses a hardcoded fee and minChange so that
24+
// change is only sent back to the contract if there's enough leftover
25+
// for another announcement.
26+
const str = 'A contract may not injure a human being or, through inaction, allow a human being to come to harm.';
27+
const tx = await contract.functions
28+
.announce()
29+
.withOpReturn(['0x6d02', str])
30+
.withHardcodedFee(1000n)
31+
.withMinChange(1000n)
32+
.send();
3633

37-
console.log('transaction details:', stringify(tx));
38-
} catch (e) {
39-
console.log(e);
40-
}
41-
}
34+
console.log('transaction details:', stringify(tx));

examples/common-js.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { hash160 } from '@cashscript/utils';
2+
import {
3+
deriveHdPrivateNodeFromSeed,
4+
deriveHdPath,
5+
secp256k1,
6+
encodeCashAddress,
7+
} from '@bitauth/libauth';
8+
import bip39 from 'bip39';
9+
10+
// This is duplicated from common.ts because it is not possible to import from a .ts file in p2pkh.js
11+
12+
// Generate entropy from BIP39 mnemonic phrase and initialise a root HD-wallet node
13+
const seed = await bip39.mnemonicToSeed('CashScript Examples');
14+
const rootNode = deriveHdPrivateNodeFromSeed(seed, true);
15+
const baseDerivationPath = "m/44'/145'/0'/0";
16+
17+
// Derive Alice's private key, public key, public key hash and address
18+
const aliceNode = deriveHdPath(rootNode, `${baseDerivationPath}/0`);
19+
if (typeof aliceNode === 'string') throw new Error();
20+
export const alicePub = secp256k1.derivePublicKeyCompressed(aliceNode.privateKey);
21+
export const alicePriv = aliceNode.privateKey;
22+
export const alicePkh = hash160(alicePub);
23+
export const aliceAddress = encodeCashAddress('bchtest', 'p2pkh', alicePkh);

examples/common.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { hash160 } from '@cashscript/utils';
2+
import {
3+
deriveHdPrivateNodeFromSeed,
4+
deriveHdPath,
5+
secp256k1,
6+
encodeCashAddress,
7+
} from '@bitauth/libauth';
8+
import bip39 from 'bip39';
9+
import { PriceOracle } from './PriceOracle.js';
10+
11+
// Generate entropy from BIP39 mnemonic phrase and initialise a root HD-wallet node
12+
const seed = await bip39.mnemonicToSeed('CashScript Examples');
13+
const rootNode = deriveHdPrivateNodeFromSeed(seed, true);
14+
const baseDerivationPath = "m/44'/145'/0'/0";
15+
16+
// Derive Alice's private key, public key, public key hash and address
17+
const aliceNode = deriveHdPath(rootNode, `${baseDerivationPath}/0`);
18+
if (typeof aliceNode === 'string') throw new Error();
19+
export const alicePub = secp256k1.derivePublicKeyCompressed(aliceNode.privateKey) as Uint8Array;
20+
export const alicePriv = aliceNode.privateKey;
21+
export const alicePkh = hash160(alicePub);
22+
export const aliceAddress = encodeCashAddress('bchtest', 'p2pkh', alicePkh);
23+
24+
// Derive Bob's private key, public key, public key hash and address
25+
const bobNode = deriveHdPath(rootNode, `${baseDerivationPath}/1`);
26+
if (typeof bobNode === 'string') throw new Error();
27+
export const bobPub = secp256k1.derivePublicKeyCompressed(bobNode.privateKey) as Uint8Array;
28+
export const bobPriv = bobNode.privateKey;
29+
export const bobPkh = hash160(bobPub);
30+
export const bobAddress = encodeCashAddress('bchtest', 'p2pkh', bobPkh);
31+
32+
// Initialise a price oracle with a private key
33+
const oracleNode = deriveHdPath(rootNode, `${baseDerivationPath}/2`);
34+
if (typeof oracleNode === 'string') throw new Error();
35+
export const oraclePub = secp256k1.derivePublicKeyCompressed(oracleNode.privateKey) as Uint8Array;
36+
export const oraclePriv = oracleNode.privateKey;
37+
export const oracle = new PriceOracle(oracleNode.privateKey);
38+
export const oraclePkh = hash160(oraclePub);
39+
export const oracleAddress = encodeCashAddress('bchtest', 'p2pkh', oraclePkh);

examples/hodl_vault.ts

+36-48
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,39 @@
11
import { stringify } from '@bitauth/libauth';
2-
import { BITBOX } from 'bitbox-sdk';
32
import { Contract, SignatureTemplate, ElectrumNetworkProvider } from 'cashscript';
43
import { compileFile } from 'cashc';
5-
import path from 'path';
6-
import { PriceOracle } from './PriceOracle.js';
7-
8-
run();
9-
async function run(): Promise<void> {
10-
// Initialise BITBOX
11-
const bitbox = new BITBOX();
12-
13-
// Initialise HD node and owner's keypair
14-
const rootSeed = bitbox.Mnemonic.toSeed('CashScript');
15-
const hdNode = bitbox.HDNode.fromSeed(rootSeed);
16-
17-
const owner = bitbox.HDNode.toKeyPair(bitbox.HDNode.derive(hdNode, 0));
18-
const ownerPk = bitbox.ECPair.toPublicKey(owner);
19-
20-
// Initialise price oracle with a keypair
21-
const oracleKeypair = bitbox.HDNode.toKeyPair(bitbox.HDNode.derive(hdNode, 1));
22-
const oraclePk = bitbox.ECPair.toPublicKey(oracleKeypair);
23-
const oracle = new PriceOracle(oracleKeypair);
24-
25-
// Compile the HodlVault contract to an artifact object
26-
const artifact = compileFile(path.join(__dirname, 'hodl_vault.cash'));
27-
28-
// Initialise a network provider for network operations on TESTNET3
29-
const provider = new ElectrumNetworkProvider('testnet3');
30-
31-
// Instantiate a new contract using the compiled artifact and network provider
32-
// AND providing the constructor parameters
33-
const parameters = [ownerPk, oraclePk, 597000, 30000];
34-
const contract = new Contract(artifact, parameters, provider);
35-
36-
// Get contract balance & output address + balance
37-
console.log('contract address:', contract.address);
38-
console.log('contract balance:', await contract.getBalance());
39-
40-
// Produce new oracle message and signature
41-
const oracleMessage = oracle.createMessage(597000, 30000);
42-
const oracleSignature = oracle.signMessage(oracleMessage);
43-
44-
// Spend from the vault
45-
const tx = await contract.functions
46-
.spend(new SignatureTemplate(owner), oracleSignature, oracleMessage)
47-
.to(contract.address, 1000)
48-
.send();
49-
50-
console.log(stringify(tx));
51-
}
4+
import { URL } from 'url';
5+
6+
// Import keys and price oracle from common.ts
7+
import {
8+
alicePriv,
9+
alicePub,
10+
oracle,
11+
oraclePub,
12+
} from './common.js';
13+
14+
// Compile the HodlVault contract to an artifact object
15+
const artifact = compileFile(new URL('hodl_vault.cash', import.meta.url));
16+
17+
// Initialise a network provider for network operations on TESTNET4
18+
const provider = new ElectrumNetworkProvider('testnet4');
19+
20+
// Instantiate a new contract using the compiled artifact and network provider
21+
// AND providing the constructor parameters
22+
const parameters = [alicePub, oraclePub, 100000n, 30000n];
23+
const contract = new Contract(artifact, parameters, provider);
24+
25+
// Get contract balance & output address + balance
26+
console.log('contract address:', contract.address);
27+
console.log('contract balance:', await contract.getBalance());
28+
29+
// Produce new oracle message and signature
30+
const oracleMessage = oracle.createMessage(100000n, 30000n);
31+
const oracleSignature = oracle.signMessage(oracleMessage);
32+
33+
// Spend from the vault
34+
const tx = await contract.functions
35+
.spend(new SignatureTemplate(alicePriv), oracleSignature, oracleMessage)
36+
.to(contract.address, 1000n)
37+
.send();
38+
39+
console.log(stringify(tx));

0 commit comments

Comments
 (0)