Skip to content

Chia-Mine/clvm-js

Repository files navigation

clvm

Javascript implementation of CLVM (Chia Lisp VM)

Install

npm install clvm
# or
yarn add clvm

Test

clvm-js passes all the test equivalent to Python's original clvm.
You can compare test files for Javascript and Python
To run the test, execute the following command.

git clone https://github.com/Chia-Mine/clvm-js
cd clvm-js

npm install
npm run test
# or
yarn
yarn test

If you find something not compatible with Python's clvm, please report it to GitHub issues.

Compatibility

This code is compatible with:

Example

// in nodejs context
async function main(){
  const clvm = require("clvm");

  // `await clvm.initializeClvmWasm()` is now always necessary.
  // This loads clvm_wasm_bg.wasm.
  // If you have a strong reason to use features of bls-signatures,
  // you need to do `await clvm.initialize()` instead.
  // `clvm.initialize()` loads both blsjs.wasm and clvm_wasm_bg.wasm.
  await clvm.initializeClvmWasm();
  
  const {SExp, KEYWORD_TO_ATOM, h, t, run_chia_program, Flag} = clvm;
  const plus = h(KEYWORD_TO_ATOM["+"]); // byte representation of '+' operator
  const q = h(KEYWORD_TO_ATOM["q"]); // byte representation of 'q' operator
  const program = SExp.to([plus, 1, t(q, 175)]).as_bin().raw(); // (+ . (1 . (q . 175)))
  const env = SExp.to(25).as_bin().raw();
  const max_cost = BigInt(10000000);
  const [cost, lazyNode] = run_chia_program(program, env, max_cost, Flag.allow_backrefs());
  const result = new SExp(lazyNode);
  let isEqual = result.equal_to(SExp.to(25 + 175));
  console.log(`isEqual: ${isEqual}`); // 'isEqual: true'
  isEqual = result.as_int() === (25 + 175);
  console.log(`isEqual: ${isEqual}`); // 'isEqual: true'
}

main().catch(e => console.error(e));

More example with clvm_wasm

See this test case for clvm_wasm

Use in browser

If you'd like to run some javascript code which depends on clvm on browser,
you need to put clvm_wasm_bg.wasm and optionally blsjs.wasm to the same directory as the code who loads clvm.
Because most of BLS operations are now performed inside clvm_wasm_bg.wasm, in most cases you don't need blsjs.wasm.

├── ...
├── main.js      # js file which clvm is compiled into
├── clvm_wasm_bg.wasm   # copy it from npm_modules/clvm/browser/clvm_wasm_bg.wasm
└── (Optional) blsjs.wasm   # copy it from npm_modules/clvm/browser/blsjs.wasm

If you use React with CRA(create-react-app), copy blsjs.wasm and clvm_wasm_bg.wasm into <react-project-root>/public/static/js/ folder. It automatically copies wasm file next to main js file.

If you use React with vite, copy blsjs.wasm and clvm_wasm_bg.wasm into <react-project-root>/public/assets/ folder.

IMPORTANT NOTE
When your code is loaded as a module, such as with <script type='module'/> (common in React with Vite), there is a path restriction for loading the WebAssembly (WASM) module.
See Known Issues. Also see code comment here

Note1
Don't forget to wait clvm.initializeClvmWasm().
clvm.initializeClvmWasm() only loads clvm_wasm_bg.wasm.
If you have a strong reason to use features of bls-signatures inside clvm-js, you need to wait clvm.initialize() instead, since clvm.initialize() loads both blsjs.wasm and clvm_wasm_bg.wasm.
Note2
Redistributing your project with bundled blsjs.wasm and/or clvm_wasm_bg.wasm must be compliant with Apache2.0 License provided by Chia-Network
Note3 You may need blsjs.wasm if you want to run run_program, which has been deprecated as of [email protected].

Browser compatibility

clvm-js uses BigInt. So if runtime environment does not support BigInt, clvm-js doesn't work as well.
If you transpile code using babel or something which uses babel (like create-react-app), you need to tell the transpiler to optimize code only for the target browsers.
Just copy and paste below to your package.json and you can avoid a lot of runtime incompatibility issues.

"browserslist": [
  "edge >= 79",
  "firefox >= 68",
  "chrome >= 67",
  "safari > 14",
  "opera >= 54",
  "ios_saf >= 14.4",
  "android >= 67",
  "op_mob >= 48",
  "and_chr >= 67",
  "and_ff >= 68",
  "samsung >= 9.2",
  "node >= 10.4.0",
  "electron >= 4.0.0"
]

Differences with Python's clvm

Although I try hard to make it look like Python's clvm, there are things users should be aware of.
I put the code which absorbs language incompatibility into src/__type_compaibility__.ts, so if you're interested take a look at it.

There are no build-in Tuple type in Javascript

When you want to create a tuple, you need to write like this:

const {t} = require("clvm"); 
const aTuple = t(1, 2);

// Tuple is Array-Like object
aTuple[0] === 1; // true
aTuple[1] === 2; // true

// Tuple content cannot be changed
aTuple[0] = 99;
aTuple[0] === 99; // false
aTuple[0] === 1; // true

// Tuple accepts only 2 elements.
const tuple2 = t(1, 2, 3);
tuple2; // (1, 2)

// You can check if a variable is a tuple
const {isTuple, isList} = require("clvm");
isTuple([1, 2]); // false
isTuple(t(1, 2)); // true
isList([1, 2]); // true
isList(t(1, 2)); // false

Just add t in front of tuple parenthesis (1, 2) and you get a tuple.

There are no build-in bytes type in JavaScript

This is the most notable difference with Python.
I used to be a JavaScript developer for several years, and sometimes I heard Python is slow and JavaScript is fast.
But working on the project, I truly surprised that Python can handle byte data really well in ways:

  • Python's bytes is immutable and can be used as a dict key.
  • Python's bytes is fast and easy to write.
    b'aaa' + b'bbb' == b'aaabbb', b'a' * 3 == b'aaa'
  • Python's bytes comparison is FAST.
    If you are interested, compare the performance of test_very_long_blobs in tests/serialize_test.[ts|py]
    See more details here
const {b} = require("clvm");
// Turns string to UTF-8 byte array.
b("abc"); // will be { Uint8Array(3) [97,98,99] }
b("あ"); // will be { Uint8Array(3) [227,129,130] }

// If you want to do Byte comparison, use equal_to method.
b("abc").equal_to(b("abc")); // true
b("abc") === b("abc"); // false. Because it compares reference of Bytes instance.

// Initialize Bytes instance with hex string
const {h} = require("clvm");
h("0x616263"); // will be { Uint8Array(3) [97,98,99] }
h("616263"); // You can omit '0x' prefix
h("0x616263").equal_to(b("abc")); // true

// +: Bytes concat
b("a").concat(b("b")); // === b("ab")
// *: Bytes repeat
b("a").repeat(3); // === b("aaa")

// Bytes initialization
const {Bytes} = require("clvm");
uint8 = new Uint8Array([97, 98, 99]);
b1 = Bytes.from(uint8);
b1.equal_to(b("abc")); // true
// Initializing by Bytes.from copies value and cut reference apart.
uint8[0] = 0;
b1.at(0); // 97
// Initializing by new Bytes() just stores value and keep reference, for better performance
uint8 = new Uint8Array([97, 98, 99]);
b2 = new Bytes(uint8);
b2.equal_to(b("abc")); // true
uint8[0] = 0;
b2.at(0); // 0

// Check byte instance
const {isBytes} = require("clvm");
isBytes(b('aaa')); // true
isBytes(h('0xfe')); // true
isBytes(new Bytes()); // true

Python's str(x) is x.toString() in Javascript

If you want to stringify SExp or Bytes, just call x.toString() method.

const {b, SExp, str} = require("clvm");
b("あ").toString(); // "b'\\xe3\\x81\\x82'"
SExp.to([1, [2, 3]]).toString(); // 'ff01ffff02ff038080'
str(SExp.to([1, [2, 3]])); // You can use str() function as well as Python by the way.

Calculation of division/modulo against negative number is different

Python JavaScript
-8 // 5 == -2 -8n / 5n === -1n
-8 % 5 == 2 -8n % 5n === -3n

clvm license

clvm-js is based on clvm with the Apache license 2.0

clvm_wasm license

clvm_wasm is used and redistributed under the Apache license 2.0

bls-signatures license

bls-signatures is used and redistributed under the Apache license 2.0

jscrypto license

jscrypto is used under the MIT license