├── examples ├── testing-suite │ ├── src │ │ └── index.ts │ ├── contracts │ │ └── example.cash │ ├── tsconfig.json │ ├── vitest.config.ts │ ├── tsconfig.build.json │ ├── tasks │ │ └── index.ts │ ├── package.json │ ├── artifacts │ │ ├── example.json │ │ └── example.artifact.ts │ └── test │ │ └── example.test.ts ├── p2pkh.cash ├── transfer_with_timeout.cash ├── package.json ├── PriceOracle.ts ├── README.md ├── common-js.js ├── announcement.cash ├── hodl_vault.cash └── mecenas.cash ├── .npmignore ├── packages ├── cashc │ ├── src │ │ ├── optimisations │ │ │ └── .gitkeep │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── ast │ │ │ ├── Pragma.ts │ │ │ ├── ThrowingErrorListener.ts │ │ │ ├── Operator.ts │ │ │ ├── Location.ts │ │ │ └── SymbolTable.ts │ │ ├── artifact │ │ │ └── Artifact.ts │ │ ├── grammar │ │ │ ├── CashScript.tokens │ │ │ └── CashScriptLexer.tokens │ │ └── semantic │ │ │ └── EnsureFinalRequireTraversal.ts │ ├── .npmignore │ ├── test │ │ ├── compiler │ │ │ ├── ParseError │ │ │ │ ├── unnamed_contract.cash │ │ │ │ ├── contract_without_args.cash │ │ │ │ ├── contract_with_unnamed_parameters.cash │ │ │ │ ├── misspelled_contract_definition.cash │ │ │ │ ├── function_with_type_name.cash │ │ │ │ ├── require_too_many_args.cash │ │ │ │ ├── incorrect_scientific_notation.cash │ │ │ │ ├── incorrect_underscore_in_number_2.cash │ │ │ │ ├── incorrect_underscore_in_number_3.cash │ │ │ │ ├── expression_in_console_log.cash │ │ │ │ ├── incorrect_underscore_in_number_1.cash │ │ │ │ ├── single_type_destructuring.cash │ │ │ │ ├── unary_plus.cash │ │ │ │ ├── nonvoid_function_call.cash │ │ │ │ ├── bytes0.cash │ │ │ │ ├── function_without_args.cash │ │ │ │ ├── array_parameter.cash │ │ │ │ ├── unterminated_member.cash │ │ │ │ ├── misspelled_covenant.cash │ │ │ │ ├── unterminated_array.cash │ │ │ │ ├── non_existent_date.cash │ │ │ │ ├── use_byte_type_as_variable_name.cash │ │ │ │ ├── full_iso_date.cash │ │ │ │ ├── invalid_date.cash │ │ │ │ ├── misspelled_reverse.cash │ │ │ │ ├── invalid_iso_date.cash │ │ │ │ ├── tuple_index_string.cash │ │ │ │ ├── exponentiation.cash │ │ │ │ ├── slice_single_parameter.cash │ │ │ │ ├── incomplete_pragma_version.cash │ │ │ │ ├── incorrect_pragma.cash │ │ │ │ ├── assign_in_if_statement.cash │ │ │ │ ├── missing_semicolon.cash │ │ │ │ ├── multiline_string.cash │ │ │ │ ├── empty_list_trailing_comma.cash │ │ │ │ ├── incorrect_nested_comment.cash │ │ │ │ ├── unterminated_comment.cash │ │ │ │ └── multiple_trailing_comma.cash │ │ │ ├── EmptyContractError │ │ │ │ └── empty_contract.cash │ │ │ ├── UnsupportedTypeError │ │ │ │ ├── div_bytes.cash │ │ │ │ ├── not_bytes.cash │ │ │ │ ├── plus_bool.cash │ │ │ │ ├── unary_minus_bool.cash │ │ │ │ ├── lessthan_string.cash │ │ │ │ ├── plus_array.cash │ │ │ │ ├── splice_bool.cash │ │ │ │ ├── and_string.cash │ │ │ │ ├── bitwise_int.cash │ │ │ │ ├── reverse_int.cash │ │ │ │ ├── tx_time_bool.cash │ │ │ │ ├── splice_index_string.cash │ │ │ │ ├── input_index_bytes.cash │ │ │ │ ├── size_int.cash │ │ │ │ ├── slice_ints.cash │ │ │ │ ├── tuple_index_bytes.cash │ │ │ │ └── splice_tuple.cash │ │ │ ├── CastSizeError │ │ │ │ ├── bool_cast_size.cash │ │ │ │ └── bounded_bytes_cast_size.cash │ │ │ ├── InvalidParameterTypeError │ │ │ │ ├── require_string.cash │ │ │ │ ├── require_array.cash │ │ │ │ ├── ripemd160_too_many_args.cash │ │ │ │ ├── checkdatasig_string.cash │ │ │ │ ├── implicit_cast_to_sig.cash │ │ │ │ ├── locking_bytecode_nulldata_string.cash │ │ │ │ └── locking_bytecode_p2pkh_bytes32.cash │ │ │ ├── InvalidSymbolTypeError │ │ │ │ ├── id_as_function.cash │ │ │ │ ├── function_name_as_class.cash │ │ │ │ ├── class_name_as_id.cash │ │ │ │ └── class_name_as_function.cash │ │ │ ├── UnequalTypeError │ │ │ │ ├── and_string_bool.cash │ │ │ │ ├── eq_bool_bytes.cash │ │ │ │ ├── equal_cast_bool_int.cash │ │ │ │ ├── mod_int_bool.cash │ │ │ │ ├── covenant_version_bytes.cash │ │ │ │ ├── mul_int_string.cash │ │ │ │ ├── add_string_int.cash │ │ │ │ ├── unequal_bitwise.cash │ │ │ │ ├── input_value_equals_bytes.cash │ │ │ │ ├── add_string_array.cash │ │ │ │ └── input_locking_bytecode_equals_string.cash │ │ │ ├── CastTypeError │ │ │ │ ├── cast_array.cash │ │ │ │ ├── cast_bytes_to_string.cash │ │ │ │ ├── cast_sig_to_pubkey.cash │ │ │ │ ├── cast_string_to_sig.cash │ │ │ │ ├── cast_sig_to_datasig.cash │ │ │ │ └── cast_to_smaller_bytes.cash │ │ │ ├── EmptyFunctionError │ │ │ │ └── empty_functions.cash │ │ │ ├── UndefinedReferenceError │ │ │ │ ├── undefined_assign.cash │ │ │ │ ├── undefined_identifier.cash │ │ │ │ ├── undefined_variable_in_console_log.cash │ │ │ │ ├── variable_used_in_own_declaration.cash │ │ │ │ ├── id_with_function_name.cash │ │ │ │ ├── function_call_with_function_name.cash │ │ │ │ ├── function_name_incorrect_case.cash │ │ │ │ ├── undefined_function.cash │ │ │ │ ├── variable_from_different_function.cash │ │ │ │ ├── variable_out_of_scope.cash │ │ │ │ └── misspelled_function.cash │ │ │ ├── ArrayElementError │ │ │ │ ├── empty_array.cash │ │ │ │ ├── nested_array.cash │ │ │ │ ├── multitype_array.cash │ │ │ │ ├── more_sigs_multisig.cash │ │ │ │ └── tuple_in_array.cash │ │ │ ├── AssignTypeError │ │ │ │ ├── assign_array.cash │ │ │ │ ├── assign_bool_to_int.cash │ │ │ │ ├── define_bool_as_bytes.cash │ │ │ │ ├── assign_bytes_to_string.cash │ │ │ │ ├── define_pubkey_as_bytes.cash │ │ │ │ ├── split_wrong_type.cash │ │ │ │ ├── assign_bytes2_to_bytes3.cash │ │ │ │ ├── assign_too_large_byte.cash │ │ │ │ ├── slice_invalid_bounded_assign_type.cash │ │ │ │ ├── split_wrong_type_bounded_bytes.cash │ │ │ │ ├── tuple_unpacking_wrong_type.cash │ │ │ │ ├── tuple_unpacking_wrong_type_bounded_bytes.cash │ │ │ │ ├── assign_tuple.cash │ │ │ │ ├── assign_bytes2_to_sig.cash │ │ │ │ └── assign_bytes_to_sig.cash │ │ │ ├── UnusedVariableError │ │ │ │ ├── unused_contract_parameter.cash │ │ │ │ ├── unused_function_parameter.cash │ │ │ │ ├── unused_variable.cash │ │ │ │ ├── unused_scope_variable.cash │ │ │ │ └── final_variable_definition.cash │ │ │ ├── ConstantModificationError │ │ │ │ └── mutate_constant.cash │ │ │ ├── IndexOutOfBoundsError │ │ │ │ ├── slice_index_negative.cash │ │ │ │ ├── split_index_negative.cash │ │ │ │ ├── split_index_out_of_bounds.cash │ │ │ │ ├── slice_index_end_out_of_bounds.cash │ │ │ │ ├── slice_index_start_end_reversed.cash │ │ │ │ ├── tuple_index_negative.cash │ │ │ │ └── tuple_index_too_high.cash │ │ │ ├── RedefinitionError │ │ │ │ ├── function_param_redefines_contract_param.cash │ │ │ │ ├── duplicate_variable_definition.cash │ │ │ │ ├── variable_redefines_function_param.cash │ │ │ │ ├── variable_with_function_name.cash │ │ │ │ ├── duplicate_function.cash │ │ │ │ ├── duplicate_function_parameters.cash │ │ │ │ ├── duplicate_contract_parameters.cash │ │ │ │ └── scoped_variable_redefines_upper_scope_variable.cash │ │ │ ├── TypeError │ │ │ │ ├── slice_string_parameter.cash │ │ │ │ └── string_if_condition.cash │ │ │ ├── TupleAssignmentError │ │ │ │ └── unpack_not_tuple.cash │ │ │ ├── VersionError │ │ │ │ ├── caret_too_low.cash │ │ │ │ ├── tilde_too_low.cash │ │ │ │ ├── caret_too_high.cash │ │ │ │ ├── exact_version_wrong.cash │ │ │ │ ├── tilde_too_high.cash │ │ │ │ ├── out_of_range_1.cash │ │ │ │ └── out_of_range_2.cash │ │ │ ├── FinalRequireStatementError │ │ │ │ ├── final_assign.cash │ │ │ │ ├── final_assign_nested.cash │ │ │ │ ├── final_assign_nested_else.cash │ │ │ │ └── final_assign_deeply_nested.cash │ │ │ └── compiler.test.ts │ │ ├── valid-contract-files │ │ │ ├── simple_covenant.cash │ │ │ ├── bitwise.cash │ │ │ ├── bounded_bytes.cash │ │ │ ├── token_category_comparison.cash │ │ │ ├── num2bin.cash │ │ │ ├── simple_constant.cash │ │ │ ├── string_concatenation.cash │ │ │ ├── int_to_byte.cash │ │ │ ├── split_typed.cash │ │ │ ├── p2palindrome.cash │ │ │ ├── simple_checkdatasig.cash │ │ │ ├── simple_multisig.cash │ │ │ ├── bytes1_equals_byte.cash │ │ │ ├── tuple_unpacking_parameter.cash │ │ │ ├── num2bin_variable.cash │ │ │ ├── p2pkh.cash │ │ │ ├── slice_optimised.cash │ │ │ ├── tuple_unpacking_single_side_type.cash │ │ │ ├── 2_of_3_multisig.cash │ │ │ ├── simple_splice.cash │ │ │ ├── covenant.cash │ │ │ ├── split_size.cash │ │ │ ├── date_literal.cash │ │ │ ├── slice.cash │ │ │ ├── cast_hash_checksig.cash │ │ │ ├── multiplication.cash │ │ │ ├── p2pkh-logs.cash │ │ │ ├── simple_functions.cash │ │ │ ├── bigint.cash │ │ │ ├── p2pkh_with_cast.cash │ │ │ ├── tuple_unpacking.cash │ │ │ ├── double_split.cash │ │ │ ├── force_cast_smaller_bytes.cash │ │ │ ├── slice_variable_parameter.cash │ │ │ ├── correct_pragma.cash │ │ │ ├── debug_messages.cash │ │ │ ├── p2pkh_with_assignment.cash │ │ │ ├── multiline_array_multisig.cash │ │ │ ├── split_or_slice_signature.cash │ │ │ ├── if_statement_number_units.cash │ │ │ ├── simple_if_statement.cash │ │ │ ├── deep_replace.cash │ │ │ ├── log_intermediate_results.cash │ │ │ ├── if_statement_number_units-logs.cash │ │ │ ├── multifunction.cash │ │ │ ├── string_with_escaped_characters.cash │ │ │ ├── if_statement.cash │ │ │ ├── simple_variables.cash │ │ │ ├── reassignment.cash │ │ │ ├── simple_cast.cash │ │ │ ├── integer_formatting.cash │ │ │ ├── multiline_statements.cash │ │ │ ├── trailing_comma.cash │ │ │ ├── multifunction_if_statements.cash │ │ │ ├── comments.cash │ │ │ ├── deeply_nested.cash │ │ │ ├── everything.cash │ │ │ ├── deeply_nested-logs.cash │ │ │ ├── mecenas.cash │ │ │ ├── covenant_all_fields.cash │ │ │ ├── announcement.cash │ │ │ └── hodl_vault.cash │ │ ├── debugging │ │ │ ├── fixtures.ts │ │ │ └── debugging.test.ts │ │ ├── cashproof │ │ │ └── slice.equiv │ │ ├── test-utils.ts │ │ └── generation │ │ │ └── generation.test.ts │ ├── vitest.setup.ts │ ├── tsconfig.build.json │ ├── vitest.config.ts │ └── LICENSE ├── utils │ ├── .npmignore │ ├── tsconfig.build.json │ ├── src │ │ ├── index.ts │ │ ├── hash.ts │ │ ├── data.ts │ │ └── bitauth-script.ts │ ├── vitest.config.ts │ ├── test │ │ ├── source-map.test.ts │ │ ├── script.fixture.ts │ │ ├── hash.test.ts │ │ └── bitauth-script.test.ts │ ├── LICENSE │ ├── README.md │ └── package.json └── cashscript │ ├── src │ ├── external-types │ │ └── @psf │ │ │ └── bch-js.d.ts │ ├── constants.ts │ ├── network │ │ ├── index.ts │ │ ├── NetworkProvider.ts │ │ ├── BitcoinRpcNetworkProvider.ts │ │ └── FullStackNetworkProvider.ts │ └── index.ts │ ├── .npmignore │ ├── vitest.setup.ts │ ├── jest │ └── package.json │ ├── vitest │ └── package.json │ ├── test │ ├── fixture │ │ ├── bounded_bytes.cash │ │ ├── token_category_comparison.cash │ │ ├── p2palindrome.cash │ │ ├── bigint.cash │ │ ├── p2pkh.cash │ │ ├── p2pkh-invalid.cash │ │ ├── Foo.cash │ │ ├── Bar.cash │ │ ├── update_fixtures.sh │ │ ├── transfer_with_timeout.cash │ │ ├── SiblingIntrospection.cash │ │ ├── token_category_comparison.json │ │ ├── token_category_comparison.artifact.ts │ │ ├── p2palindrome.artifact.ts │ │ ├── p2palindrome.json │ │ ├── bounded_bytes.artifact.ts │ │ ├── bounded_bytes.json │ │ ├── PriceOracle.ts │ │ ├── p2pkh-invalid.json │ │ ├── p2pkh.artifact.ts │ │ ├── p2pkh.json │ │ ├── bigint.artifact.ts │ │ ├── bigint.json │ │ ├── announcement.cash │ │ ├── hodl_vault.cash │ │ ├── deprecated │ │ │ └── mecenas-v0.6.0.cash │ │ ├── Foo.artifact.ts │ │ ├── Foo.json │ │ ├── debugging │ │ │ └── multi_contract_debugging_contracts.ts │ │ ├── mecenas.cash │ │ ├── transfer_with_timeout.artifact.ts │ │ ├── transfer_with_timeout.json │ │ ├── Bar.artifact.ts │ │ └── Bar.json │ └── libauth-template │ │ ├── LibauthTemplate.test.ts │ │ └── LibauthTemplateMultiContract.test.ts │ ├── tsconfig.build.json │ ├── vitest.config.ts │ ├── LICENSE │ └── package.json ├── .gitattributes ├── website ├── static │ └── img │ │ ├── moria.png │ │ ├── badgers.png │ │ ├── bchguru.jpg │ │ ├── bitcats.png │ │ ├── favicon.ico │ │ ├── favicon.png │ │ ├── fundme.png │ │ ├── paytaca.png │ │ ├── tapswap.png │ │ ├── bch-pump.jpg │ │ ├── cashninjas.jpg │ │ ├── unspent_phi.png │ │ ├── cashscript-hero.png │ │ ├── cashtokens-studio.png │ │ └── general-protocols.png ├── .gitignore ├── src │ └── pages │ │ └── index.module.css ├── tsconfig.json ├── package.json ├── docs │ └── basics │ │ └── about.md └── sidebars.ts ├── .eslintignore ├── lerna.json ├── vitest.config.ts ├── .codecov.yml ├── tsconfig.json ├── update-version.ts ├── tsconfig.build.json ├── LICENSE ├── .github └── workflows │ └── github-actions.yml ├── package.json └── .gitignore /examples/testing-suite/src/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/** 3 | !index.* 4 | -------------------------------------------------------------------------------- /packages/cashc/src/optimisations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.cash linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /packages/cashc/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/** 3 | *.map 4 | -------------------------------------------------------------------------------- /packages/utils/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/** 3 | *.map 4 | -------------------------------------------------------------------------------- /packages/cashc/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const MAX_INPUT_BYTESIZE = 1650; 2 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/unnamed_contract.cash: -------------------------------------------------------------------------------- 1 | contract () { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /packages/cashscript/src/external-types/@psf/bch-js.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@psf/bch-js'; 2 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/contract_without_args.cash: -------------------------------------------------------------------------------- 1 | contract Test { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/EmptyContractError/empty_contract.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /packages/cashc/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import { inspect } from 'util'; 2 | inspect.defaultOptions.depth = 10; 3 | -------------------------------------------------------------------------------- /packages/cashscript/.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !dist/** 3 | !vitest/package.json 4 | !jest/package.json 5 | *.map 6 | -------------------------------------------------------------------------------- /website/static/img/moria.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/moria.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/ 2 | **/node_modules/ 3 | **/grammar/ 4 | /website/ 5 | *.js 6 | *.cjs 7 | examples/webapp 8 | -------------------------------------------------------------------------------- /website/static/img/badgers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/badgers.png -------------------------------------------------------------------------------- /website/static/img/bchguru.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/bchguru.jpg -------------------------------------------------------------------------------- /website/static/img/bitcats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/bitcats.png -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/favicon.png -------------------------------------------------------------------------------- /website/static/img/fundme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/fundme.png -------------------------------------------------------------------------------- /website/static/img/paytaca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/paytaca.png -------------------------------------------------------------------------------- /website/static/img/tapswap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/tapswap.png -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/contract_with_unnamed_parameters.cash: -------------------------------------------------------------------------------- 1 | contract Test(int, string) { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /website/static/img/bch-pump.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/bch-pump.jpg -------------------------------------------------------------------------------- /website/static/img/cashninjas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/cashninjas.jpg -------------------------------------------------------------------------------- /website/static/img/unspent_phi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/unspent_phi.png -------------------------------------------------------------------------------- /website/static/img/cashscript-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/cashscript-hero.png -------------------------------------------------------------------------------- /website/static/img/cashtokens-studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/cashtokens-studio.png -------------------------------------------------------------------------------- /website/static/img/general-protocols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CashScript/cashscript/HEAD/website/static/img/general-protocols.png -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/misspelled_contract_definition.cash: -------------------------------------------------------------------------------- 1 | // cspell:disable-next-line 2 | contrat Test(int argument) { 3 | 4 | } 5 | -------------------------------------------------------------------------------- /packages/cashscript/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import { inspect } from 'util'; 2 | import './src/test/TestExtensions.js'; 3 | 4 | inspect.defaultOptions.depth = 10; 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "packages": [ 5 | "packages/*" 6 | ], 7 | "version": "independent" 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/div_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(0x50 / 0x40); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/function_with_type_name.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function bytes(sig s, pubkey pk) { 3 | } 4 | } 5 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/require_too_many_args.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(bool b) { 3 | require(true, b); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/not_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(!!0x40 == 0x50); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/plus_bool.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(bool b) { 3 | require(true + b); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/unary_minus_bool.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(-true); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_covenant.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function covenant() { 3 | require(tx.version == 2); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/CastSizeError/bool_cast_size.cash: -------------------------------------------------------------------------------- 1 | contract Test(int y) { 2 | function hello() { 3 | require(bool(y, 2) == true); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidParameterTypeError/require_string.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(string s) { 3 | require(s); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidSymbolTypeError/id_as_function.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(int a) { 3 | require(a(0) == 0); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/and_string_bool.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(string s) { 3 | require(s && true); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/eq_bool_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s) { 3 | require(bytes(s) == true); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/equal_cast_bool_int.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(bool(1) == 1); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/mod_int_bool.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello(int t) { 3 | require(x % true > t); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/lessthan_string.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require("hi" < "hello"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/plus_array.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(bool b) { 3 | require([true] + [b]); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/splice_bool.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | require(true.split(x)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/bitwise.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 x, bytes8 y) { 2 | function hello() { 3 | require((x | y) == y); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashscript/jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "types": "../dist/test/TestExtensions.d.ts", 4 | "main": "../dist/test/TestExtensions.js" 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashscript/vitest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "types": "../dist/test/TestExtensions.d.ts", 4 | "main": "../dist/test/TestExtensions.js" 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/CastTypeError/cast_array.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes y) { 2 | function hello() { 3 | require(bytes(['world']) == y); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/EmptyFunctionError/empty_functions.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | } 4 | 5 | function world() { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidParameterTypeError/require_array.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(bool s) { 3 | require([s, true]); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/incorrect_scientific_notation.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(1000 == 1e-3); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/incorrect_underscore_in_number_2.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(1000 == _1000); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/incorrect_underscore_in_number_3.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(1000 == 1000_); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/undefined_assign.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function unlock(int a, int b) { 3 | c = a + b; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/and_string.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(string who) { 3 | require(who && "hello"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/bitwise_int.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, int y) { 2 | function hello() { 3 | require((x ^ y) == x); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/reverse_int.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | require(x.reverse() == x); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/tx_time_bool.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | require(tx.time >= bool(x)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashscript/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const VERSION_SIZE = 4; 2 | export const LOCKTIME_SIZE = 4; 3 | export const P2PKH_INPUT_SIZE = 32 + 4 + 1 + 1 + 65 + 1 + 33 + 4; 4 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/bounded_bytes.cash: -------------------------------------------------------------------------------- 1 | contract BoundedBytes() { 2 | function spend(bytes4 b, int i) { 3 | require(b == bytes4(i)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ArrayElementError/empty_array.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | int x = []; 4 | require(x > 1); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/assign_array.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | int x = [1]; 4 | require(x > 1); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/assign_bool_to_int.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | int x = 1; 4 | x = false; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/CastTypeError/cast_bytes_to_string.cash: -------------------------------------------------------------------------------- 1 | contract Test(string y) { 2 | function hello() { 3 | require(string(0x4801) == y); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/expression_in_console_log.cash: -------------------------------------------------------------------------------- 1 | contract DebugMessages() { 2 | function spend(int value) { 3 | console.log(value + 2); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/incorrect_underscore_in_number_1.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(1000 == 1__000); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/single_type_destructuring.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function test1(string s) { 3 | string x, y = s.split(5); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/unary_plus.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(int a) { 3 | int b = 10 - +4; 4 | require(b == a); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/covenant_version_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function covenant() { 3 | require(tx.version == 0x02); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/mul_int_string.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, int y) { 2 | function hello() { 3 | require((x * "50") == y); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/splice_index_string.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require("hello".split("e")); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/token_category_comparison.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function send() { 3 | require(tx.inputs[1].tokenCategory == 0x); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/define_bool_as_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | bool b = 0x00; 4 | require(b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/CastSizeError/bounded_bytes_cast_size.cash: -------------------------------------------------------------------------------- 1 | contract Test(int y) { 2 | function hello() { 3 | require(bytes2(y, 2) == 0x0000); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/CastTypeError/cast_sig_to_pubkey.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | require(pubkey(s) == pk); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidSymbolTypeError/function_name_as_class.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(new min(0, 1) == 0); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/nonvoid_function_call.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(true); 4 | ripemd160(true); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/undefined_identifier.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function unlock(int a, int b) { 3 | int c = 0 + a + d; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/add_string_int.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello() { 3 | require((y + x).length > 50); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/unequal_bitwise.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes4 x, bytes8 y) { 2 | function hello() { 3 | require((x & y) == y); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/input_index_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function spend() { 3 | require(tx.inputs[0x01].value != 0); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/size_int.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello() { 3 | require(x.length > y.length); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/bounded_bytes.cash: -------------------------------------------------------------------------------- 1 | contract BoundedBytes() { 2 | function spend(bytes4 b, int i) { 3 | require(b == bytes4(i)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ArrayElementError/nested_array.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | int x = [1, 2, [3, 4]]; 4 | require(x > 1); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/CastTypeError/cast_string_to_sig.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(string s, pubkey pk) { 3 | require(checkSig(sig(s), pk)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/bytes0.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | bytes0 byte = bytes0(10); 4 | require(int(byte) == 10); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/function_without_args.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello { 3 | } 4 | 5 | function world(int a) { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/undefined_variable_in_console_log.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | console.log(a, 'hello'); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/input_value_equals_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function spend() { 3 | require(tx.inputs[0].value != 0x01); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/token_category_comparison.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function send() { 3 | require(tx.inputs[1].tokenCategory == 0x); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ArrayElementError/multitype_array.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | int x = [1, 2, 'hello']; 4 | require(x > 1); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidSymbolTypeError/class_name_as_id.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(LockingBytecodeP2PKH == bytes34(0)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/array_parameter.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello(int[] a) { 3 | } 4 | 5 | function world(int a) { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/unterminated_member.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(bytes a) { 3 | bytes b = a.reverse( 4 | require(b == a); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/add_string_array.cash: -------------------------------------------------------------------------------- 1 | contract Test(string x, string y) { 2 | function hello() { 3 | require(([y] + x).length > 50); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/num2bin.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | bytes2 byte_ = bytes2(10); 4 | require(int(byte_) == 10); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_constant.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | string constant m = "hello"; 4 | require(m == "hello"); 5 | } 6 | } -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/string_concatenation.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(string who) { 3 | require(("hello " + who).length + 2 > 5); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/p2palindrome.cash: -------------------------------------------------------------------------------- 1 | contract P2Palindrome() { 2 | function spend(string palindrome) { 3 | require(palindrome.reverse() == palindrome); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/assign_bytes_to_string.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | string y = "Hello"; 4 | y = 0x000000fefe00; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidParameterTypeError/ripemd160_too_many_args.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(bool b) { 3 | require(ripemd160(true, b) == 0x0); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/variable_used_in_own_declaration.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function unlock(int a, int b) { 3 | int c = 0 + a + b + c; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/int_to_byte.cash: -------------------------------------------------------------------------------- 1 | contract IntToByte() { 2 | function hello(int a, byte b) { 3 | byte c = byte(a); 4 | require(b == c); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/split_typed.cash: -------------------------------------------------------------------------------- 1 | contract SplitTyped(bytes b) { 2 | function spend() { 3 | bytes4 x = b.split(4)[0]; 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/testing-suite/contracts/example.cash: -------------------------------------------------------------------------------- 1 | contract Example() { 2 | function test(int value) { 3 | console.log(value, "test"); 4 | require(value == 1, "Wrong value passed"); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ArrayElementError/more_sigs_multisig.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function cms(sig s, pubkey pk) { 3 | require(checkMultiSig([s, sig(0x00)], [pk])); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ArrayElementError/tuple_in_array.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | int x = [1, 2, 0x0010.split(1)]; 4 | require(x > 1); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/define_pubkey_as_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | pubkey pk = 0x00; 4 | require(bytes(pk) == 0x00); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/split_wrong_type.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes2 x = b.split(4)[0]; 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnusedVariableError/unused_contract_parameter.cash: -------------------------------------------------------------------------------- 1 | contract Test(string x) { 2 | function hello(sig s, pubkey pk) { 3 | require(checkSig(s, pk)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnusedVariableError/unused_function_parameter.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk, string x) { 3 | require(checkSig(s, pk)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/p2palindrome.cash: -------------------------------------------------------------------------------- 1 | contract P2Palindrome() { 2 | function spend(string palindrome) { 3 | require(palindrome.reverse() == palindrome); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_checkdatasig.cash: -------------------------------------------------------------------------------- 1 | contract Test(datasig s, pubkey pk) { 2 | function cds(bytes data) { 3 | require(checkDataSig(s, data, pk)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/assign_bytes2_to_bytes3.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | bytes3 byte_ = 0x1234; 4 | require(byte_.length == 3); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/assign_too_large_byte.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | bytes1 byte_ = 0x1234; 4 | require(byte_.length == 1); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/CastTypeError/cast_sig_to_datasig.cash: -------------------------------------------------------------------------------- 1 | contract Test(sig s, pubkey pk) { 2 | function cds(bytes data) { 3 | require(checkDataSig(datasig(s), data, pk)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/CastTypeError/cast_to_smaller_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | bytes1 b = bytes1(0x1234); 4 | require(b.length == 1); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidParameterTypeError/checkdatasig_string.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey p) { 3 | require(checkDataSig(s, "Hello", p)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidSymbolTypeError/class_name_as_function.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(LockingBytecodeP2PKH(bytes20(0)) == bytes34(0)); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/misspelled_covenant.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function covenant() { 3 | // cspell:disable-next-line 4 | require(tx.verson == 2); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/unterminated_array.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function cms(sig s, pubkey pk) { 3 | require(checkMultiSig([s, sig(0x00)], [pk, pubkey(0x00))); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnequalTypeError/input_locking_bytecode_equals_string.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function spend() { 3 | require(tx.inputs[0].lockingBytecode == "0x01"); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_multisig.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function cms(sig s, pubkey pk) { 3 | require(checkMultiSig([s, sig(0x00)], [pk, pubkey(0x00)])); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | projects: ['packages/**/vitest.config.ts'], 6 | globals: true, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/cashc/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Errors.js'; 2 | export * as utils from '@cashscript/utils'; 3 | export { compileFile, compileString } from './compiler.js'; 4 | 5 | export const version = '0.12.1'; 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ConstantModificationError/mutate_constant.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function test(){ 3 | int constant m = 5; 4 | m = m + 5; 5 | require(m > 2); 6 | } 7 | } -------------------------------------------------------------------------------- /packages/cashc/test/compiler/IndexOutOfBoundsError/slice_index_negative.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 b) { 2 | function spend() { 3 | bytes x = b.slice(4, -4); 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/IndexOutOfBoundsError/split_index_negative.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 b) { 2 | function spend() { 3 | bytes x = b.split(-4)[0]; 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/bytes1_equals_byte.cash: -------------------------------------------------------------------------------- 1 | contract Bytes1EqualsByte() { 2 | function hello(int a, byte b) { 3 | bytes1 c = byte(a); 4 | require(b == c); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/tuple_unpacking_parameter.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function split(bytes32 b) { 3 | bytes16 x, bytes16 y = b.split(16); 4 | require(x == y); 5 | } 6 | } -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/slice_invalid_bounded_assign_type.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 b) { 2 | function spend() { 3 | bytes2 x = b.slice(2, 6); 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/split_wrong_type_bounded_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 b) { 2 | function spend() { 3 | bytes2 x = b.split(4)[1]; 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/tuple_unpacking_wrong_type.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes4 x, bytes4 y = b.split(4); 4 | require(x != y); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/IndexOutOfBoundsError/split_index_out_of_bounds.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 b) { 2 | function spend() { 3 | bytes x = b.split(12)[0]; 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/non_existent_date.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract Test() { 4 | function test() { 5 | require(tx.time >= date("2021-50-03T05:30:00")); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/use_byte_type_as_variable_name.cash: -------------------------------------------------------------------------------- 1 | contract IntToByte() { 2 | function hello(int a, byte b) { 3 | byte byte = byte(a); 4 | require(b == byte); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnusedVariableError/unused_variable.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | string x = 'Hello'; 4 | require(checkSig(s, pk)); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/num2bin_variable.cash: -------------------------------------------------------------------------------- 1 | contract Num2Bin() { 2 | function spend(int size) { 3 | bytes byte_ = bytes(10, size); 4 | require(int(byte_) == 10); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/p2pkh.cash: -------------------------------------------------------------------------------- 1 | contract P2PKH(bytes20 pkh) { 2 | function spend(pubkey pk, sig s) { 3 | require(hash160(pk) == pkh); 4 | require(checkSig(s, pk)); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/testing-suite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | 4 | "compilerOptions": { 5 | "outDir": "./dist", 6 | }, 7 | 8 | "include": [ 9 | "src/**/*", 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/IndexOutOfBoundsError/slice_index_end_out_of_bounds.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 b) { 2 | function spend() { 3 | bytes x = b.slice(6, 12); 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/IndexOutOfBoundsError/slice_index_start_end_reversed.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 b) { 2 | function spend() { 3 | bytes x = b.slice(6, 2); 4 | require(x != b); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/full_iso_date.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract Test() { 4 | function test() { 5 | require(tx.time >= date("2021-03-03T05:30:00.000Z")); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/invalid_date.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract Test() { 4 | function test() { 5 | int d = date("not a date"); 6 | require(d == 0); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/slice_optimised.cash: -------------------------------------------------------------------------------- 1 | contract Slice(bytes32 data) { 2 | function spend() { 3 | bytes20 pkh = data.slice(0, 20); 4 | require(pkh == bytes20(0)); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/tuple_unpacking_single_side_type.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function split(bytes b) { 3 | bytes16 x, bytes y = b.split(16); 4 | require(x == y); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/2_of_3_multisig.cash: -------------------------------------------------------------------------------- 1 | contract MultiSig(pubkey pk1, pubkey pk2, pubkey pk3) { 2 | function spend(sig s1, sig s2) { 3 | require(checkMultiSig([s1, s2], [pk1, pk2, pk3])); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/tuple_unpacking_wrong_type_bounded_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes8 b) { 2 | function spend() { 3 | bytes2 x, bytes4 y = b.split(4); 4 | require(x != y); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/misspelled_reverse.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(bytes a) { 3 | // cspell:disable-next-lines 4 | bytes b = a.revese() 5 | require(b == a); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/assign_tuple.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes x = b.split(5); 4 | require(x != b); 5 | require (b.split(4)[0] != x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/invalid_iso_date.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract Test() { 4 | function test() { 5 | int d = date("02-16-2021 05:30:00 PM"); 6 | require(d == 0); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/slice_ints.cash: -------------------------------------------------------------------------------- 1 | contract Slice(bytes20 pkh) { 2 | function spend() { 3 | int actualPkh = 1921739821792419.slice(1, 3); 4 | require(pkh == actualPkh); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/tuple_index_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes x = b[1]; 4 | require(x != b); 5 | require (b.split(4)[0] != x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnusedVariableError/unused_scope_variable.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | if (checkSig(s, pk)) { 4 | string x = 'Hello'; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_splice.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes x = b.split(5)[1]; 4 | require(x != b); 5 | require (b.split(4)[0] != x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/assign_bytes2_to_sig.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | bytes2 byte_ = 0x1234; 4 | sig s = 0x1234; 5 | require(byte_.length == s.length); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/tuple_index_string.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes x = b.split(5)["1"]; 4 | require(x != b); 5 | require (b.split(4)[0] != x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/covenant.cash: -------------------------------------------------------------------------------- 1 | contract Covenant(int requiredVersion) { 2 | function spend() { 3 | require(tx.version == requiredVersion); 4 | require(this.activeBytecode == 0x00); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/split_size.cash: -------------------------------------------------------------------------------- 1 | contract SplitSize(bytes b) { 2 | function spend() { 3 | bytes x = b.split(b.length / 2)[1]; 4 | require(x != b); 5 | require(b.split(4)[0] != x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - packages/cashc/src/grammar/ 3 | - '**/test/' 4 | - examples/ 5 | coverage: 6 | status: 7 | project: 8 | default: 9 | target: 85% 10 | patch: 11 | default: 12 | target: 90% 13 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/AssignTypeError/assign_bytes_to_sig.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | bytes3 byte_ = 0x1234; 4 | sig s = bytes(0x1234); 5 | require(byte_.length == bytes(s).length); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/IndexOutOfBoundsError/tuple_index_negative.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes x = b.split(5)[-1]; 4 | require(x != b); 5 | require (b.split(4)[0] != x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/IndexOutOfBoundsError/tuple_index_too_high.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes x = b.split(5)[20]; 4 | require(x != b); 5 | require (b.split(4)[0] != x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/RedefinitionError/function_param_redefines_contract_param.cash: -------------------------------------------------------------------------------- 1 | contract Test(string s) { 2 | function hello(sig s, pubkey pk) { 3 | string v = 'hello'; 4 | 5 | require(checkSig(s, pk)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnsupportedTypeError/splice_tuple.cash: -------------------------------------------------------------------------------- 1 | contract Test(bytes b) { 2 | function spend() { 3 | bytes x = b.split(5).split(1)[1]; 4 | require(x != b); 5 | require (b.split(4)[0] != x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/date_literal.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract Test() { 4 | function test() { 5 | int d = date("2021-02-17T01:30:00"); //YYYY-MM-DDThh:mm:ss 6 | require(d == 0); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/RedefinitionError/duplicate_variable_definition.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | int v = 10; 4 | string v = 'hello'; 5 | 6 | require(checkSig(s, pk)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/id_with_function_name.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | require(world); 4 | } 5 | 6 | function world(int a) { 7 | require(a + 5 == 10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/slice.cash: -------------------------------------------------------------------------------- 1 | contract Slice(bytes20 pkh) { 2 | function spend() { 3 | bytes actualPkh = tx.inputs[this.activeInputIndex].lockingBytecode.slice(3, 23); 4 | require(pkh == actualPkh); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/exponentiation.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | int myVariable = 10 - 4; 4 | int myOtherVariable = 20 ** myVariable % 2; 5 | require(myOtherVariable > x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/RedefinitionError/variable_redefines_function_param.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | int s = 10; 4 | string v = 'hello'; 5 | 6 | require(checkSig(s, pk)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/cast_hash_checksig.cash: -------------------------------------------------------------------------------- 1 | contract CastHashChecksig() { 2 | function hello(pubkey pk, sig s) { 3 | require((ripemd160(bytes(pk)) == hash160(0x) == !true)); 4 | require(checkSig(s, pk)); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/multiplication.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | int myVariable = 10 - 4; 4 | int myOtherVariable = 20 * myVariable % 2; 5 | require(myOtherVariable > x); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/p2pkh-logs.cash: -------------------------------------------------------------------------------- 1 | contract P2PKH(bytes20 pkh) { 2 | function spend(pubkey pk, sig s) { 3 | require(hash160(pk) == pkh); 4 | console.log(pk, pkh, s); 5 | require(checkSig(s, pk)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | 4 | "compilerOptions": { 5 | "outDir": "./dist", 6 | "types": ["vitest/globals"], 7 | }, 8 | 9 | "include": [ 10 | "src/**/*", 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/slice_single_parameter.cash: -------------------------------------------------------------------------------- 1 | contract Slice(bytes20 pkh) { 2 | function spend() { 3 | bytes actualPkh = tx.inputs[this.activeInputIndex].lockingBytecode.slice(23); 4 | require(pkh == actualPkh); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_functions.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | require(checkSig(s, pk)); 4 | } 5 | 6 | function world(int a) { 7 | require(a + 5 == 10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/RedefinitionError/variable_with_function_name.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | int v = 10; 4 | string ripemd160 = 'hello'; 5 | 6 | require(checkSig(s, pk) && v < 10); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/TypeError/slice_string_parameter.cash: -------------------------------------------------------------------------------- 1 | contract Slice(bytes20 pkh) { 2 | function spend() { 3 | bytes actualPkh = tx.inputs[this.activeInputIndex].lockingBytecode.slice("3", 23); 4 | require(pkh == actualPkh); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/bigint.cash: -------------------------------------------------------------------------------- 1 | contract BigInt() { 2 | function proofOfBigInt(int x, int y) { 3 | int maxInt32PlusOne = 2147483648; 4 | require(x >= maxInt32PlusOne); 5 | require(x * y >= maxInt32PlusOne); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/p2pkh_with_cast.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract P2PKH(bytes20 pkh) { 4 | function spend(pubkey pk, bytes65 s) { 5 | require(hash160(pk) == pkh); 6 | require(checkSig(sig(s), pk)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/bigint.cash: -------------------------------------------------------------------------------- 1 | contract BigInt() { 2 | function proofOfBigInt(int x, int y) { 3 | int maxInt64PlusOne = 9223372036854775808; 4 | require(x >= maxInt64PlusOne); 5 | require(x * y >= maxInt64PlusOne); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/RedefinitionError/duplicate_function.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | require(checkSig(s, pk)); 4 | } 5 | 6 | function hello(int a) { 7 | require(a + 5 == 10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/function_call_with_function_name.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | require(world(x)); 4 | } 5 | 6 | function world(int a) { 7 | require(a + 5 == 10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/tuple_unpacking.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function split() { 3 | string s1 = "hello"; 4 | string s2 = "there"; 5 | string hello, string there = (s1+s2).split(5); 6 | require(hello == there); 7 | } 8 | } -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/p2pkh.cash: -------------------------------------------------------------------------------- 1 | contract P2PKH(bytes20 pkh) { 2 | // Require pk to match stored pkh and signature to match 3 | function spend(pubkey pk, sig s) { 4 | require(hash160(pk) == pkh); 5 | require(checkSig(s, pk)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/function_name_incorrect_case.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | require(Within(1, 0, 2)); 4 | } 5 | 6 | function world(int a) { 7 | require(a + 5 == 10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/undefined_function.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | require(sigCheck(s, pk)); 4 | } 5 | 6 | function world(int a) { 7 | require(a + 5 == 10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/double_split.cash: -------------------------------------------------------------------------------- 1 | contract DoubleSplit(bytes20 pkh) { 2 | function spend() { 3 | bytes actualPkh = tx.inputs[this.activeInputIndex].lockingBytecode.split(23)[0].split(3)[1]; 4 | require(pkh == actualPkh); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/force_cast_smaller_bytes.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello() { 3 | // Have to know what you're doing to force cast 4 | bytes3 byte_ = bytes3(bytes(0x1234)); 5 | require(byte_.length == 1); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/p2pkh-invalid.cash: -------------------------------------------------------------------------------- 1 | contract P2PKH(bytes20 pkh) { 2 | // Require pk to match stored pkh and signature to match 3 | function spend(pubkey pk, sig s) { 4 | require(hash160(pk) == s); 5 | require(checkSig(s, pk)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidParameterTypeError/implicit_cast_to_sig.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract P2PKH(bytes20 pkh) { 4 | function spend(pubkey pk, bytes65 s) { 5 | require(hash160(pk) == pkh); 6 | require(checkSig(s, pk)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/RedefinitionError/duplicate_function_parameters.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey s) { 3 | require(checkSig(s, s)); 4 | } 5 | 6 | function world(int a) { 7 | require(a + 5 == 10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/variable_from_different_function.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function unlock(int a, int b) { 3 | require(a == c); 4 | } 5 | 6 | function spend(int c, int d) { 7 | require(c == d); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "types": [ 6 | "node", 7 | "vitest/globals" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*", 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidParameterTypeError/locking_bytecode_nulldata_string.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract P2PKH() { 4 | function spend() { 5 | require(tx.outputs[0].lockingBytecode == new LockingBytecodeNullData(["Hello world"])); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/TupleAssignmentError/unpack_not_tuple.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function test1(string s) { 3 | string x, string y = s.split(5)[0]; 4 | string a, string b = s + "hello"; 5 | require(x == y); 6 | require(a -- b); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/variable_out_of_scope.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function unlock(int a, int b) { 3 | if (a == b) { 4 | int c = a; 5 | require (c == a); 6 | } 7 | require(c == b); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/p2pkh.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ^0.12.0; 2 | 3 | contract P2PKH(bytes20 pkh) { 4 | // Require pk to match stored pkh and signature to match 5 | function spend(pubkey pk, sig s) { 6 | require(hash160(pk) == pkh); 7 | require(checkSig(s, pk)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/slice_variable_parameter.cash: -------------------------------------------------------------------------------- 1 | contract Slice(bytes20 pkh) { 2 | function spend() { 3 | int x = 3; 4 | bytes actualPkh = tx.inputs[this.activeInputIndex].lockingBytecode.slice(x, 23); 5 | require(pkh == actualPkh); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './artifact.js'; 2 | export * from './bip68.js'; 3 | export * from './bitauth-script.js'; 4 | export * from './data.js'; 5 | export * from './hash.js'; 6 | export * from './script.js'; 7 | export * from './source-map.js'; 8 | export * from './types.js'; 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/RedefinitionError/duplicate_contract_parameters.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string x) { 2 | function hello(sig s, pubkey pk) { 3 | require(checkSig(s, pk)); 4 | } 5 | 6 | function world(int a) { 7 | require(a + 5 == 10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/VersionError/caret_too_low.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ^0.1.0; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/VersionError/tilde_too_low.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ~0.1.0; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/correct_pragma.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | 4 | "compilerOptions": { 5 | "baseUrl": "./packages", 6 | "paths": { 7 | "cashc": ["cashc/src"], 8 | "cashscript": ["cashscript/src"], 9 | "@cashscript/utils": ["utils/src"], 10 | }, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/incomplete_pragma_version.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >1; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/VersionError/caret_too_high.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ^20.0.0; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/VersionError/exact_version_wrong.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript 0.2.5; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/VersionError/tilde_too_high.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ~20.1.0; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UndefinedReferenceError/misspelled_function.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | // cspell:disable-next-line 4 | require(withn(1, 0, 2)); 5 | } 6 | 7 | function world(int a) { 8 | require(a + 5 == 10); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/VersionError/out_of_range_1.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.20.0 <0.21.0; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/VersionError/out_of_range_2.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >0.2.0 <=0.3.0; 2 | 3 | contract Test() { 4 | function hello(sig s, pubkey pk) { 5 | require(checkSig(s, pk)); 6 | } 7 | 8 | function world(int a) { 9 | require(a + 5 == 10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/debug_messages.cash: -------------------------------------------------------------------------------- 1 | contract DebugMessages() { 2 | function spend(int value) { 3 | require(value == 1, "Wrong value passed"); 4 | console.log(value, "test"); 5 | console.log(value, "test2"); 6 | require(value + 1 == 2, "Sum doesn't work"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/p2pkh_with_assignment.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract P2PKH(bytes20 pkh) { 4 | function spend(pubkey pk, sig s) { 5 | bytes20 passedPkh = hash160(pk); 6 | require(passedPkh == pkh); 7 | require(checkSig(s, pk)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashscript/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "types": [ 6 | "vitest/globals" 7 | ] 8 | }, 9 | "include": [ 10 | "src/**/*", 11 | ], 12 | "exclude": [ 13 | "test/fixture" 14 | ], 15 | } 16 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/incorrect_pragma.cash: -------------------------------------------------------------------------------- 1 | // cspell:disable-next-line 2 | pragma cashscrip 0.2.0; 3 | 4 | contract Test() { 5 | function hello(sig s, pubkey pk) { 6 | require(checkSig(s, pk)); 7 | } 8 | 9 | function world(int a) { 10 | require(a + 5 == 10); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/multiline_array_multisig.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function cms(sig s, pubkey pk) { 3 | require(checkMultiSig( 4 | [ 5 | s, sig(0x00) 6 | ], [ 7 | pk, pubkey(0x00) 8 | ] 9 | )); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/InvalidParameterTypeError/locking_bytecode_p2pkh_bytes32.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract P2PKH(bytes20 pkh) { 4 | function spend(pubkey pk, sig s) { 5 | require(checkSig(s, pk)); 6 | require(tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(sha256(pkh))); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/RedefinitionError/scoped_variable_redefines_upper_scope_variable.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(sig s, pubkey pk) { 3 | string v = 'hello'; 4 | 5 | if (v == 'hello') { 6 | int v = 10; 7 | require(v > 1); 8 | } 9 | 10 | require(checkSig(s, pk)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/TypeError/string_if_condition.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello(int a, string b) { 3 | if (a == x - 2) { 4 | require(false); 5 | } else if (b + y) 6 | require(y == "World"); 7 | else { 8 | require(true == !!!false); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/split_or_slice_signature.cash: -------------------------------------------------------------------------------- 1 | contract Test(sig signature) { 2 | function spend() { 3 | // Assume Schnorr 4 | bytes hashtype1 = signature.split(64)[1]; 5 | bytes1 hashtype2 = signature.slice(64, 65); 6 | require(hashtype1 == 0x01); 7 | require(hashtype2 == 0x01); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/if_statement_number_units.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(int a, int b) { 3 | if (a == b - 2 minutes) { 4 | require(false); 5 | } else if (b == 2 weeks) 6 | require(a == 20 seconds); 7 | else { 8 | require(true == !!!false); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_if_statement.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello(int a, string b) { 3 | if (a == x - 2) { 4 | require(false); 5 | } else if (b == "Hello " + y) 6 | require(y == "World"); 7 | else { 8 | require(true == !!!false); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/Foo.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.10.2; 2 | 3 | contract Foo(bytes20 pkh_foo) { 4 | // Require pk to match stored pkh and signature to match 5 | function execute(pubkey pk, sig s) { 6 | console.log("Foo 'execute' function called."); 7 | require(hash160(pk) == pkh_foo); 8 | require(checkSig(s, pk)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/assign_in_if_statement.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello(int a, string b) { 3 | if (a == x - 2) { 4 | require(false); 5 | } else if (b = "Hello " + y) { 6 | require(y == "World"); 7 | } else { 8 | require(true == !!!false); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/testing-suite/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | root: './test', 5 | test: { 6 | environment: 'node', 7 | globals: true, 8 | testTimeout: 50000, 9 | silent: 'passed-only', 10 | coverage: { 11 | provider: 'v8', 12 | reporter: ['json-summary'], 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/missing_semicolon.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello(sig s, pubkey pk) { 3 | int myVariable = 10 - 4; 4 | int myOtherVariable = 20 + myVariable % 2; 5 | require(myOtherVariable > x); 6 | 7 | string hw = "Hello World" 8 | require(ripemd160(pk) == ripemd160(hw)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/deep_replace.cash: -------------------------------------------------------------------------------- 1 | contract DeepReplace() { 2 | function hello() { 3 | int a = 1; 4 | int b = 2; 5 | int c = 3; 6 | int d = 4; 7 | int e = 5; 8 | int f = 6; 9 | 10 | if (a < 3) { 11 | a = 3; 12 | } 13 | 14 | require(a > b + c + d + e + f); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/log_intermediate_results.cash: -------------------------------------------------------------------------------- 1 | contract LogIntermediateResults(pubkey owner) { 2 | function test_log_intermediate_result() { 3 | bytes32 singleHash = sha256(owner); 4 | console.log(singleHash); 5 | bytes32 doubleHash = sha256(singleHash); 6 | require(doubleHash.length == 32, "doubleHash should be 32 bytes"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/multiline_string.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello(sig s, pubkey pk) { 3 | int myVariable = 10 - 4; 4 | int myOtherVariable = 20 + myVariable % 2; 5 | require(myOtherVariable > x); 6 | 7 | string hw = "Hello 8 | World"; 9 | require(ripemd160(pk) == ripemd160(hw)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/if_statement_number_units-logs.cash: -------------------------------------------------------------------------------- 1 | contract Test() { 2 | function hello(int a, int b) { 3 | if (a == b - 2 minutes) { 4 | require(false); 5 | } else if (b == 2 weeks) 6 | require(a == 20 seconds); 7 | else { 8 | require(true == !!!false); 9 | } 10 | 11 | console.log(a, b); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/utils/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'node', 6 | include: ['test/**/*.{test,spec}.?(c|m)[jt]s?(x)'], 7 | exclude: ['**/types/**'], 8 | globals: true, 9 | silent: 'passed-only', 10 | coverage: { 11 | provider: 'v8', 12 | include: ['src/**/*.ts'], 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/multifunction.cash: -------------------------------------------------------------------------------- 1 | contract MultiFunction( 2 | pubkey sender, 3 | pubkey recipient, 4 | int timeout 5 | ) { 6 | function transfer(sig recipientSig) { 7 | require(checkSig(recipientSig, recipient)); 8 | } 9 | 10 | function timeout(sig senderSig) { 11 | require(checkSig(senderSig, sender)); 12 | require(tx.time >= timeout); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/string_with_escaped_characters.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x) { 2 | function hello() { 3 | int myVariable = 10 - 4; 4 | int myOtherVariable = 20 + myVariable % 2; 5 | require(myOtherVariable > x); 6 | 7 | string x1 = "Hello \n \\ ' '' \" World"; 8 | string x2 = 'Hello \n \\ " " \' World'; 9 | require(ripemd160(x1) == hash160(x2)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/if_statement.cash: -------------------------------------------------------------------------------- 1 | contract IfStatement(int x, int y) { 2 | function hello(int a, int b) { 3 | int d = a + b; 4 | d = d - a; 5 | if (d == x - 2) { 6 | int c = d + b; 7 | d = a + c; 8 | require(c > d); 9 | } else { 10 | require(d == a); 11 | } 12 | d = d + a; 13 | require(d == y); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/cashscript/src/network/index.ts: -------------------------------------------------------------------------------- 1 | export type { default as NetworkProvider } from './NetworkProvider.js'; 2 | export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvider.js'; 3 | export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js'; 4 | export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js'; 5 | export { default as MockNetworkProvider } from './MockNetworkProvider.js'; 6 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_variables.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello(sig s, pubkey pk) { 3 | int myVariable = 10 - 4; 4 | int myOtherVariable = 20 + myVariable % 2; 5 | require(myOtherVariable > x); 6 | 7 | string hw = "Hello World"; 8 | hw = hw + y; 9 | 10 | require(ripemd160(pk) == ripemd160(hw)); 11 | require(checkSig(s, pk)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/Bar.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.10.2; 2 | 3 | contract Bar(bytes20 pkh_bar) { 4 | function funcA() { 5 | require(2==2); 6 | } 7 | 8 | function funcB() { 9 | require(2==2); 10 | } 11 | 12 | function execute(pubkey pk, sig s) { 13 | console.log("Bar 'execute' function called."); 14 | require(hash160(pk) == pkh_bar); 15 | require(checkSig(s, pk)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/reassignment.cash: -------------------------------------------------------------------------------- 1 | contract Reassignment(int x, string y) { 2 | function hello(pubkey pk, sig s) { 3 | int myVariable = 10 - 4; 4 | int myOtherVariable = 20 + myVariable % 2; 5 | require(myOtherVariable > x); 6 | 7 | string hw = "Hello World"; 8 | hw = hw + y; 9 | 10 | require(ripemd160(pk) == ripemd160(hw)); 11 | require(checkSig(s, pk)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/cashc/test/debugging/fixtures.ts: -------------------------------------------------------------------------------- 1 | interface Fixture { 2 | fn: string, 3 | fnWithLogs: string, 4 | } 5 | 6 | export const fixtures: Fixture[] = [ 7 | { 8 | fn: 'p2pkh.cash', 9 | fnWithLogs: 'p2pkh-logs.cash', 10 | }, 11 | { 12 | fn: 'if_statement_number_units.cash', 13 | fnWithLogs: 'if_statement_number_units-logs.cash', 14 | }, 15 | { 16 | fn: 'deeply_nested.cash', 17 | fnWithLogs: 'deeply_nested-logs.cash', 18 | }, 19 | ]; 20 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/simple_cast.cash: -------------------------------------------------------------------------------- 1 | contract Test(int x, string y) { 2 | function hello(sig s, pubkey pk) { 3 | int myVariable = 10 - int(true); 4 | int myOtherVariable = 20 + myVariable % 2; 5 | require(myOtherVariable > x); 6 | 7 | string hw = "Hello World"; 8 | hw = hw + y; 9 | 10 | require(ripemd160(pk) == ripemd160(bytes(hw) + bytes(pk))); 11 | require(checkSig(s, pk)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/cashc/test/cashproof/slice.equiv: -------------------------------------------------------------------------------- 1 | !full_script=True; 2 | 3 | # We are unable to run cashproof any more due to Python ecosystem issues, but we're including this file for reference. 4 | 5 | # x.slice(10, 25) & x.split(25)[0].split(10)[1] 6 | 25 OP_SPLIT OP_DROP OP_10 OP_SPLIT OP_NIP 7 | <=> 8 | # x.split(10)[1].split(15)[0] 9 | OP_10 OP_SPLIT OP_NIP OP_15 OP_SPLIT OP_DROP 10 | ; 11 | 12 | # Slice optimisation 13 | OP_0 OP_SPLIT OP_NIP <=> ; 14 | OP_SIZE OP_SPLIT OP_DROP <=> ; 15 | -------------------------------------------------------------------------------- /packages/cashscript/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'node', 6 | include: ['test/**/*.{test,spec}.?(c|m)[jt]s?(x)'], 7 | exclude: ['**/types/**'], 8 | globals: true, 9 | silent: 'passed-only', 10 | coverage: { 11 | provider: 'v8', 12 | include: ['src/**/*.ts'], 13 | }, 14 | setupFiles: ['./vitest.setup.ts'], 15 | testTimeout: 50000, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /packages/cashc/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: 'node', 6 | include: ['test/**/*.{test,spec}.?(c|m)[jt]s?(x)'], 7 | exclude: ['**/types/**'], 8 | globals: true, 9 | silent: 'passed-only', 10 | coverage: { 11 | provider: 'v8', 12 | include: ['src/**/*.ts'], 13 | exclude: ['src/grammar/**/*.ts'], 14 | }, 15 | setupFiles: ['./vitest.setup.ts'], 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/update_fixtures.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | DIR=$(dirname "$0") 4 | cd $DIR 5 | 6 | CASHC="../../../cashc/dist/cashc-cli.js" 7 | 8 | find . -maxdepth 1 -name "*.cash" | while read fn; do 9 | echo node "$CASHC" "$fn" -o "$(basename "$fn" .cash).json" 10 | node "$CASHC" "$fn" -o "$(basename "$fn" .cash).json" 11 | echo node "$CASHC" "$fn" -o "$(basename "$fn" .cash).artifact.ts" -f ts 12 | node "$CASHC" "$fn" -o "$(basename "$fn" .cash).artifact.ts" -f ts 13 | done 14 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/integer_formatting.cash: -------------------------------------------------------------------------------- 1 | contract IntegerFormatting() { 2 | function test() { 3 | int scientific1 = 1e12; 4 | int scientific2 = 1E12; 5 | int underscores = 1_000_000_000_000; 6 | int combined = 1_0e1_1; 7 | 8 | int regular = 1000000000000; 9 | 10 | require(scientific1 == regular); 11 | require(scientific2 == regular); 12 | require(underscores == regular); 13 | require(combined == regular); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/multiline_statements.cash: -------------------------------------------------------------------------------- 1 | contract MultilineStatements( 2 | int x, 3 | string y 4 | ) { 5 | function spend( 6 | int a, 7 | string b 8 | ) { 9 | if (a == x - 2 10 | && b == y) { 11 | require(false); 12 | } else if (b == "Hello " 13 | + y) { 14 | require( 15 | y == "World" 16 | ); 17 | } else { 18 | require(true == !!!false); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/transfer_with_timeout.cash: -------------------------------------------------------------------------------- 1 | contract TransferWithTimeout( 2 | pubkey sender, 3 | pubkey recipient, 4 | int timeout 5 | ) { 6 | // Require recipient's signature to match 7 | function transfer(sig recipientSig) { 8 | require(checkSig(recipientSig, recipient)); 9 | } 10 | 11 | // Require timeout time to be reached and sender's signature to match 12 | function timeout(sig senderSig) { 13 | require(checkSig(senderSig, sender)); 14 | require(tx.time >= timeout); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/transfer_with_timeout.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ^0.12.0; 2 | 3 | contract TransferWithTimeout( 4 | pubkey sender, 5 | pubkey recipient, 6 | int timeout 7 | ) { 8 | // Require recipient's signature to match 9 | function transfer(sig recipientSig) { 10 | require(checkSig(recipientSig, recipient)); 11 | } 12 | 13 | // Require timeout time to be reached and sender's signature to match 14 | function timeout(sig senderSig) { 15 | require(checkSig(senderSig, sender)); 16 | require(tx.time >= timeout); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/empty_list_trailing_comma.cash: -------------------------------------------------------------------------------- 1 | contract Contract( 2 | pubkey ownerPk, 3 | pubkey oraclePk, 4 | ) { 5 | function spend(,) { 6 | bytes oracleMessage = bytes('Spend') + bytes(12, 10,); 7 | require(checkDataSig( 8 | oracleMsgSig, 9 | oracleMessage, 10 | oraclePk, 11 | )); 12 | require(checkMultiSig([ 13 | ownerSig, 14 | oracleTxSig, 15 | ], [ 16 | ownerPk, 17 | oraclePk, 18 | ])); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/cashscript/test/libauth-template/LibauthTemplate.test.ts: -------------------------------------------------------------------------------- 1 | import { fixtures } from '../fixture/libauth-template/fixtures.js'; 2 | 3 | describe('Libauth Template generation tests (single-contract)', () => { 4 | it.each(fixtures)('should generate a valid libauth template for $name', (fixture) => { 5 | const generatedTemplate = fixture.transaction.getLibauthTemplate(); 6 | // console.warn(JSON.stringify(generatedTemplate, null, 2)); 7 | // console.warn(fixture.transaction.bitauthUri()); 8 | expect(generatedTemplate).toEqual(fixture.template); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/FinalRequireStatementError/final_assign.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract TransferWithTimeout( 4 | pubkey sender, 5 | pubkey recipient, 6 | int timeout 7 | ) { 8 | // Require recipient's signature to match 9 | function transfer(sig recipientSig) { 10 | require(checkSig(recipientSig, recipient)); 11 | } 12 | 13 | // Require timeout time to be reached and sender's signature to match 14 | function timeout(sig senderSig) { 15 | require(checkSig(senderSig, sender)); 16 | timeout = 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/UnusedVariableError/final_variable_definition.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract TransferWithTimeout( 4 | pubkey sender, 5 | pubkey recipient, 6 | int timeout 7 | ) { 8 | // Require recipient's signature to match 9 | function transfer(sig recipientSig) { 10 | require(checkSig(recipientSig, recipient)); 11 | } 12 | 13 | // Require timeout time to be reached and sender's signature to match 14 | function timeout(sig senderSig) { 15 | require(checkSig(senderSig, sender)); 16 | int x = 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /update-version.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { URL } from 'url'; 4 | 5 | // USAGE: 6 | // $ yarn run update-version 'X.X.X' 7 | 8 | const version = process.argv[2]; 9 | execSync(`yarn lerna version --no-push --no-git-tag-version --force-publish --yes ${version}`); 10 | 11 | const indexFilePath = new URL('packages/cashc/src/index.ts', import.meta.url); 12 | const data = fs.readFileSync(indexFilePath, 'utf8'); 13 | const updatedData = data.replace(/export const version = .*\n/, `export const version = '${version}';\n`); 14 | fs.writeFileSync(indexFilePath, updatedData); 15 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cashscript-examples", 3 | "private": true, 4 | "version": "0.12.1", 5 | "description": "Usage examples of the CashScript SDK", 6 | "main": "p2pkh.js", 7 | "type": "module", 8 | "author": "Rosco Kalis ", 9 | "license": "MIT", 10 | "scripts": { 11 | "lint": "eslint . --ext .ts --ignore-path ../.eslintignore" 12 | }, 13 | "dependencies": { 14 | "@bitauth/libauth": "^3.1.0-next.8", 15 | "@types/node": "^22.17.0", 16 | "cashc": "^0.12.1", 17 | "cashscript": "^0.12.1", 18 | "eslint": "^8.56.0", 19 | "typescript": "^5.9.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/cashc/src/ast/Pragma.ts: -------------------------------------------------------------------------------- 1 | import type { VersionOperatorContext } from '../grammar/CashScriptParser.js'; 2 | 3 | export enum PragmaName { 4 | CASHSCRIPT = 'cashscript', 5 | } 6 | 7 | export enum VersionOp { 8 | CARET = '^', 9 | TILDE = '~', 10 | GE = '>=', 11 | GT = '>', 12 | LT = '<', 13 | LE = '<=', 14 | EQ = '=', 15 | } 16 | 17 | export function getPragmaName(name: string): PragmaName { 18 | return PragmaName[name.toUpperCase() as keyof typeof PragmaName]; 19 | } 20 | 21 | export function getVersionOpFromCtx(ctx?: VersionOperatorContext): VersionOp { 22 | return (ctx ? ctx.getText() : '='); 23 | } 24 | -------------------------------------------------------------------------------- /packages/cashc/test/test-utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { URL } from 'url'; 3 | import urlJoin from 'url-join'; 4 | 5 | export function getSubdirectories(directory: URL): string[] { 6 | return fs.readdirSync(directory) 7 | .filter((fn) => fs.statSync(new URL(urlJoin(directory.toString(), fn))).isDirectory()); 8 | } 9 | 10 | export function readCashFiles(directory: URL): { fn: string, contents: string }[] { 11 | return fs.readdirSync(directory) 12 | .filter((fn) => fn.endsWith('.cash')) 13 | .map((fn) => ({ fn, contents: fs.readFileSync(new URL(urlJoin(directory.toString(), fn)), { encoding: 'utf-8' }) })); 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "esnext", 5 | "declaration": true, 6 | "lib": [ 7 | "es2022", 8 | "dom" 9 | ], 10 | "types": [ 11 | "node", 12 | "vitest/globals" 13 | ], 14 | "sourceMap": true, 15 | "strict": true, 16 | "strictPropertyInitialization": false, 17 | "experimentalDecorators": true, 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "esModuleInterop": true, 21 | "skipLibCheck": true, 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "dist", 26 | ], 27 | } 28 | -------------------------------------------------------------------------------- /packages/cashscript/test/libauth-template/LibauthTemplateMultiContract.test.ts: -------------------------------------------------------------------------------- 1 | import { fixtures } from '../fixture/libauth-template/multi-contract-fixtures.js'; 2 | 3 | describe('Libauth Template generation tests (multi-contract)', () => { 4 | it.each(fixtures)('should generate a valid libauth template for $name', async (fixture) => { 5 | const builder = await fixture.transaction; 6 | const generatedTemplate = builder.getLibauthTemplate(); 7 | // console.warn(JSON.stringify(generatedTemplate, null, 2)); 8 | // console.warn(builder.bitauthUri()); 9 | expect(generatedTemplate).toEqual(fixture.template); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/FinalRequireStatementError/final_assign_nested.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract TransferWithTimeout( 4 | pubkey sender, 5 | pubkey recipient, 6 | int timeout 7 | ) { 8 | // Require recipient's signature to match 9 | function transfer(sig recipientSig) { 10 | require(checkSig(recipientSig, recipient)); 11 | } 12 | 13 | // Require timeout time to be reached and sender's signature to match 14 | function timeout(sig senderSig) { 15 | require(checkSig(senderSig, sender)); 16 | if (timeout > 0) { 17 | timeout = 0; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/SiblingIntrospection.cash: -------------------------------------------------------------------------------- 1 | contract SiblingIntrospection(bytes expectedLockingBytecode) { 2 | function spend() { 3 | require(this.activeInputIndex == 0); 4 | 5 | bytes outputBytecode = tx.outputs[1].lockingBytecode; 6 | console.log("outputBytecode:", outputBytecode); 7 | require(outputBytecode == expectedLockingBytecode, 'output bytecode should match'); 8 | 9 | bytes inputBytecode = tx.inputs[1].lockingBytecode; 10 | console.log("inputBytecode:", inputBytecode); 11 | require(inputBytecode == expectedLockingBytecode, 'input bytecode should match'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/incorrect_nested_comment.cash: -------------------------------------------------------------------------------- 1 | /* This is a contract with some comments 2 | /* It has an incorrect nested comment */ 3 | */ 4 | 5 | contract Test(int x, string y) { 6 | // Line comments are a thing 7 | function hello(sig s, pubkey pk) { 8 | int i = 400 + x; 9 | bytes b = 0x07364897987fe87 + bytes(x); 10 | 11 | int myVariable = 10 - 4; // they can go at the end of the line 12 | int myOtherVariable = i + myVariable % 2; 13 | require(myOtherVariable /* And comments can be included within lines */ > i); 14 | 15 | require(ripemd160(b) == ripemd160(y)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/trailing_comma.cash: -------------------------------------------------------------------------------- 1 | contract Contract( 2 | pubkey ownerPk, 3 | pubkey oraclePk, 4 | ) { 5 | function spend( 6 | sig ownerSig, 7 | datasig oracleMsgSig, 8 | sig oracleTxSig, 9 | ) { 10 | bytes oracleMessage = bytes('Spend') + bytes(12, 10,); 11 | require(checkDataSig( 12 | oracleMsgSig, 13 | oracleMessage, 14 | oraclePk, 15 | )); 16 | require(checkMultiSig([ 17 | ownerSig, 18 | oracleTxSig, 19 | ], [ 20 | ownerPk, 21 | oraclePk, 22 | ])); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/unterminated_comment.cash: -------------------------------------------------------------------------------- 1 | /* This is a contract with some comments 2 | * 3 | */ 4 | 5 | contract Test(int x, string y) { 6 | // Line comments are a thing 7 | function hello(sig s, pubkey pk) { 8 | int i = 400 + x; 9 | bytes b = 0x07364897987fe87 + bytes(x); 10 | 11 | int myVariable = 10 - 4; // they can go at the end of the line 12 | int myOtherVariable = i + myVariable % 2; 13 | require(myOtherVariable /* And comments can be included within lines */ > i); 14 | /* Comments do need to be terminated though 15 | require(ripemd160(b) == ripemd160(y)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/ParseError/multiple_trailing_comma.cash: -------------------------------------------------------------------------------- 1 | contract Contract( 2 | pubkey ownerPk, 3 | pubkey oraclePk, 4 | ) { 5 | function spend( 6 | sig ownerSig, 7 | datasig oracleMsgSig, 8 | sig oracleTxSig, 9 | ) { 10 | bytes oracleMessage = bytes('Spend') + bytes(12, 10,); 11 | require(checkDataSig( 12 | oracleMsgSig, 13 | oracleMessage, 14 | oraclePk,,, 15 | )); 16 | require(checkMultiSig([ 17 | ownerSig, 18 | oracleTxSig, 19 | ], [ 20 | ownerPk, 21 | oraclePk, 22 | ])); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/testing-suite/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "esnext", 5 | "declaration": true, 6 | "lib": [ 7 | "es2022", 8 | "dom" 9 | ], 10 | "types": [ 11 | "node", 12 | "vitest/globals" 13 | ], 14 | "sourceMap": true, 15 | "strict": true, 16 | "strictPropertyInitialization": false, 17 | "experimentalDecorators": true, 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "esModuleInterop": true, 21 | "skipLibCheck": true, 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "dist", 26 | "taksks", 27 | ], 28 | } 29 | -------------------------------------------------------------------------------- /website/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .banner { 7 | padding: 3rem; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 966px) { 14 | .banner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | 25 | .features { 26 | display: flex; 27 | align-items: center; 28 | padding: 2rem; 29 | width: 100%; 30 | } 31 | 32 | .featureImage { 33 | height: 150px; 34 | width: 250px; 35 | } 36 | -------------------------------------------------------------------------------- /packages/utils/src/hash.ts: -------------------------------------------------------------------------------- 1 | import { sha256 as sha256Lib, ripemd160 as ripemd160Lib, sha512 as sha512Lib } from '@bitauth/libauth'; 2 | 3 | export function sha512(payload: Uint8Array): Uint8Array { 4 | return sha512Lib.hash(payload); 5 | } 6 | 7 | export function sha256(payload: Uint8Array): Uint8Array { 8 | return sha256Lib.hash(payload); 9 | } 10 | 11 | export function ripemd160(payload: Uint8Array): Uint8Array { 12 | return ripemd160Lib.hash(payload); 13 | } 14 | 15 | export function hash160(payload: Uint8Array): Uint8Array { 16 | return ripemd160(sha256(payload)); 17 | } 18 | 19 | export function hash256(payload: Uint8Array): Uint8Array { 20 | return sha256(sha256(payload)); 21 | } 22 | -------------------------------------------------------------------------------- /packages/cashc/test/generation/generation.test.ts: -------------------------------------------------------------------------------- 1 | /* generation.test.ts 2 | * 3 | * - This file is used to test the IR and target code generation 4 | */ 5 | 6 | import { URL } from 'url'; 7 | import { compileFile } from '../../src/index.js'; 8 | import { fixtures } from './fixtures.js'; 9 | 10 | describe('Code generation & target code optimisation', () => { 11 | fixtures.forEach((fixture) => { 12 | it(`should compile ${fixture.fn} to correct Script and artifact`, () => { 13 | const artifact = compileFile(new URL(`../valid-contract-files/${fixture.fn}`, import.meta.url)); 14 | expect(artifact).toEqual({ ...fixture.artifact, updatedAt: expect.any(String) }); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/FinalRequireStatementError/final_assign_nested_else.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract TransferWithTimeout( 4 | pubkey sender, 5 | pubkey recipient, 6 | int timeout 7 | ) { 8 | // Require recipient's signature to match 9 | function transfer(sig recipientSig) { 10 | require(checkSig(recipientSig, recipient)); 11 | } 12 | 13 | // Require timeout time to be reached and sender's signature to match 14 | function timeout(sig senderSig) { 15 | require(checkSig(senderSig, sender)); 16 | if (timeout > 0) { 17 | timeout = 0; 18 | } else { 19 | require(timeout == 0); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/multifunction_if_statements.cash: -------------------------------------------------------------------------------- 1 | contract MultiFunctionIfStatements(int x, int y) { 2 | function transfer(int a, int b) { 3 | int d = a + b; 4 | d = d - a; 5 | if (d == x) { 6 | int c = d + b; 7 | d = a + c; 8 | require(c > d); 9 | } else { 10 | d = a; 11 | } 12 | d = d + a; 13 | require(d == y); 14 | } 15 | 16 | function timeout(int b) { 17 | int d = b; 18 | d = d + 2; 19 | if (d == x) { 20 | int c = d + b; 21 | d = c + d; 22 | require(c > d); 23 | } 24 | d = b; 25 | require(d == y); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/utils/test/source-map.test.ts: -------------------------------------------------------------------------------- 1 | import { generateSourceMap, sourceMapToLocationData } from '../src/index.js'; 2 | import { fixtures } from './fixtures/source-map.fixture.js'; 3 | 4 | describe('Source map generation', () => { 5 | fixtures.forEach((fixture) => { 6 | describe(fixture.name, () => { 7 | it('should map from location data to source map', () => { 8 | const sourceMap = generateSourceMap(fixture.locationData); 9 | expect(sourceMap).toBe(fixture.sourceMap); 10 | }); 11 | 12 | it('should map from source map to location data', () => { 13 | const locationData = sourceMapToLocationData(fixture.sourceMap); 14 | expect(locationData).toEqual(fixture.locationData); 15 | }); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/token_category_comparison.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "Test", 3 | "constructorInputs": [], 4 | "abi": [ 5 | { 6 | "name": "send", 7 | "inputs": [] 8 | } 9 | ], 10 | "bytecode": "OP_1 OP_UTXOTOKENCATEGORY OP_0 OP_EQUAL", 11 | "source": "contract Test() {\n function send() {\n require(tx.inputs[1].tokenCategory == 0x);\n }\n}\n", 12 | "debug": { 13 | "bytecode": "51ce0087", 14 | "sourceMap": "3:26:3:27;:16::42:1;:46::48:0;:8::50:1", 15 | "logs": [], 16 | "requires": [ 17 | { 18 | "ip": 4, 19 | "line": 3 20 | } 21 | ] 22 | }, 23 | "compiler": { 24 | "name": "cashc", 25 | "version": "0.11.0" 26 | }, 27 | "updatedAt": "2025-06-16T15:05:57.130Z" 28 | } 29 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/token_category_comparison.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'Test', 3 | constructorInputs: [], 4 | abi: [ 5 | { 6 | name: 'send', 7 | inputs: [], 8 | }, 9 | ], 10 | bytecode: 'OP_1 OP_UTXOTOKENCATEGORY OP_0 OP_EQUAL', 11 | source: 'contract Test() {\n function send() {\n require(tx.inputs[1].tokenCategory == 0x);\n }\n}\n', 12 | debug: { 13 | bytecode: '51ce0087', 14 | sourceMap: '3:26:3:27;:16::42:1;:46::48:0;:8::50:1', 15 | logs: [], 16 | requires: [ 17 | { 18 | ip: 4, 19 | line: 3, 20 | }, 21 | ], 22 | }, 23 | compiler: { 24 | name: 'cashc', 25 | version: '0.11.0', 26 | }, 27 | updatedAt: '2025-06-16T15:05:57.354Z', 28 | } as const; 29 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/comments.cash: -------------------------------------------------------------------------------- 1 | /* This is a contract with some comments 2 | /* It has some comment characters inside the comment too for some reason 3 | // line comments too 4 | */ 5 | 6 | contract Test(int x) { 7 | // Line comments are a thing 8 | function hello(sig s, pubkey pk) { 9 | int i = 400 + x; 10 | bytes b = 0x07364897987fe87 + bytes(x); 11 | 12 | int myVariable = 10 - 4; // they can go at the end of the line 13 | int myOtherVariable = i + myVariable % 2; 14 | require(myOtherVariable /* And comments can be included within lines */ > i); 15 | 16 | if (x > 10) { 17 | require(i < 20); 18 | require(checkSig(s, pk)); 19 | } else 20 | require(b == 0x01); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/cashc/test/debugging/debugging.test.ts: -------------------------------------------------------------------------------- 1 | /* debugging.test.ts 2 | * 3 | * - This file is used to test the debugging functionality (console.log and require messages) 4 | */ 5 | 6 | import { URL } from 'url'; 7 | import { compileFile } from '../../src/index.js'; 8 | import { fixtures } from './fixtures.js'; 9 | 10 | describe('Debugging', () => { 11 | fixtures.forEach((fixture) => { 12 | it(`should compile ${fixture.fn} to the same bytecode as ${fixture.fnWithLogs}`, () => { 13 | const artifact = compileFile(new URL(`../valid-contract-files/${fixture.fn}`, import.meta.url)); 14 | const artifactWithLogs = compileFile(new URL(`../valid-contract-files/${fixture.fnWithLogs}`, import.meta.url)); 15 | expect(artifactWithLogs.bytecode).toEqual(artifact.bytecode); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/deeply_nested.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract TransferWithTimeout( 4 | pubkey sender, 5 | pubkey recipient, 6 | int timeout 7 | ) { 8 | // Require recipient's signature to match 9 | function transfer(sig recipientSig) { 10 | require(checkSig(recipientSig, recipient)); 11 | } 12 | 13 | // Require timeout time to be reached and sender's signature to match 14 | function timeout(sig senderSig) { 15 | require(checkSig(senderSig, sender)); 16 | if (timeout > 0) { 17 | if (timeout < 10) { 18 | require(timeout == 5); 19 | } else { 20 | require(timeout == 15); 21 | } 22 | } else { 23 | require(timeout == 0); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@docusaurus/tsconfig", 3 | "compilerOptions": { 4 | "target": "es2020", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext", 9 | "es2021" 10 | ], 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "strict": true, 14 | "esModuleInterop": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "allowSyntheticDefaultImports": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | "baseUrl": ".", 24 | }, 25 | "include": [ 26 | "**/*.ts", 27 | "**/*.tsx", 28 | "**/*.js", 29 | "**/*.jsx", 30 | ], 31 | "exclude": [ 32 | "**/node_modules" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/FinalRequireStatementError/final_assign_deeply_nested.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract TransferWithTimeout( 4 | pubkey sender, 5 | pubkey recipient, 6 | int timeout 7 | ) { 8 | // Require recipient's signature to match 9 | function transfer(sig recipientSig) { 10 | require(checkSig(recipientSig, recipient)); 11 | } 12 | 13 | // Require timeout time to be reached and sender's signature to match 14 | function timeout(sig senderSig) { 15 | require(checkSig(senderSig, sender)); 16 | if (timeout > 0) { 17 | if (timeout < 10) { 18 | require(timeout == 5); 19 | } else { 20 | timeout = 0; 21 | } 22 | } else { 23 | require(timeout == 0); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/p2palindrome.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'P2Palindrome', 3 | constructorInputs: [], 4 | abi: [ 5 | { 6 | name: 'spend', 7 | inputs: [ 8 | { 9 | name: 'palindrome', 10 | type: 'string', 11 | }, 12 | ], 13 | }, 14 | ], 15 | bytecode: 'OP_DUP OP_REVERSEBYTES OP_EQUAL', 16 | source: 'contract P2Palindrome() {\n function spend(string palindrome) {\n require(palindrome.reverse() == palindrome);\n }\n}\n', 17 | debug: { 18 | bytecode: '76bc87', 19 | sourceMap: '3:16:3:26;:::36:1;:8::52', 20 | logs: [], 21 | requires: [ 22 | { 23 | ip: 3, 24 | line: 3, 25 | }, 26 | ], 27 | }, 28 | compiler: { 29 | name: 'cashc', 30 | version: '0.11.0', 31 | }, 32 | updatedAt: '2025-06-16T15:05:55.718Z', 33 | } as const; 34 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/p2palindrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "P2Palindrome", 3 | "constructorInputs": [], 4 | "abi": [ 5 | { 6 | "name": "spend", 7 | "inputs": [ 8 | { 9 | "name": "palindrome", 10 | "type": "string" 11 | } 12 | ] 13 | } 14 | ], 15 | "bytecode": "OP_DUP OP_REVERSEBYTES OP_EQUAL", 16 | "source": "contract P2Palindrome() {\n function spend(string palindrome) {\n require(palindrome.reverse() == palindrome);\n }\n}\n", 17 | "debug": { 18 | "bytecode": "76bc87", 19 | "sourceMap": "3:16:3:26;:::36:1;:8::52", 20 | "logs": [], 21 | "requires": [ 22 | { 23 | "ip": 3, 24 | "line": 3 25 | } 26 | ] 27 | }, 28 | "compiler": { 29 | "name": "cashc", 30 | "version": "0.11.0" 31 | }, 32 | "updatedAt": "2025-06-16T15:05:55.464Z" 33 | } 34 | -------------------------------------------------------------------------------- /examples/testing-suite/tasks/index.ts: -------------------------------------------------------------------------------- 1 | import { compileString } from 'cashc'; 2 | import fs from 'fs'; 3 | import { URL } from 'url'; 4 | import urlJoin from 'url-join'; 5 | 6 | export const compile = (): void => { 7 | const directory = new URL('../contracts', import.meta.url); 8 | const result = fs.readdirSync(directory) 9 | .filter((fn) => fn.endsWith('.cash')) 10 | .map((fn) => ({ fn, contents: fs.readFileSync(new URL(urlJoin(directory.toString(), fn)), { encoding: 'utf-8' }) })); 11 | 12 | result.forEach(({ fn, contents }) => { 13 | const artifact = compileString(contents); 14 | 15 | fs.writeFileSync(new URL(`../artifacts/${fn.replace('.cash', '.json')}`, import.meta.url), JSON.stringify(artifact, null, 2)); 16 | }); 17 | }; 18 | 19 | switch (process.argv[2]) { 20 | case 'compile': 21 | compile(); 22 | break; 23 | default: 24 | console.log('Unknown task'); 25 | break; 26 | } 27 | -------------------------------------------------------------------------------- /packages/cashscript/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SignatureTemplate } from './SignatureTemplate.js'; 2 | export { Contract } from './Contract.js'; 3 | export { TransactionBuilder } from './TransactionBuilder.js'; 4 | export { 5 | type ConstructorArgument, 6 | type FunctionArgument, 7 | type EncodedConstructorArgument, 8 | type EncodedFunctionArgument, 9 | encodeFunctionArgument, 10 | } from './Argument.js'; 11 | export type { Artifact, AbiFunction, AbiInput } from '@cashscript/utils'; 12 | export * as utils from '@cashscript/utils'; 13 | export * from './interfaces.js'; 14 | export * from './Errors.js'; 15 | export { 16 | type NetworkProvider, 17 | BitcoinRpcNetworkProvider, 18 | ElectrumNetworkProvider, 19 | FullStackNetworkProvider, 20 | MockNetworkProvider, 21 | } from './network/index.js'; 22 | export { randomUtxo, randomToken, randomNFT } from './utils.js'; 23 | export * from './walletconnect-utils.js'; 24 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/bounded_bytes.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'BoundedBytes', 3 | constructorInputs: [], 4 | abi: [ 5 | { 6 | name: 'spend', 7 | inputs: [ 8 | { 9 | name: 'b', 10 | type: 'bytes4', 11 | }, 12 | { 13 | name: 'i', 14 | type: 'int', 15 | }, 16 | ], 17 | }, 18 | ], 19 | bytecode: 'OP_SWAP OP_4 OP_NUM2BIN OP_EQUAL', 20 | source: 'contract BoundedBytes() {\n function spend(bytes4 b, int i) {\n require(b == bytes4(i));\n }\n}\n', 21 | debug: { 22 | bytecode: '7c548087', 23 | sourceMap: '3:28:3:29;:21::30:1;;:8::32', 24 | logs: [], 25 | requires: [ 26 | { 27 | ip: 4, 28 | line: 3, 29 | }, 30 | ], 31 | }, 32 | compiler: { 33 | name: 'cashc', 34 | version: '0.11.0', 35 | }, 36 | updatedAt: '2025-06-16T15:05:56.281Z', 37 | } as const; 38 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/bounded_bytes.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "BoundedBytes", 3 | "constructorInputs": [], 4 | "abi": [ 5 | { 6 | "name": "spend", 7 | "inputs": [ 8 | { 9 | "name": "b", 10 | "type": "bytes4" 11 | }, 12 | { 13 | "name": "i", 14 | "type": "int" 15 | } 16 | ] 17 | } 18 | ], 19 | "bytecode": "OP_SWAP OP_4 OP_NUM2BIN OP_EQUAL", 20 | "source": "contract BoundedBytes() {\n function spend(bytes4 b, int i) {\n require(b == bytes4(i));\n }\n}\n", 21 | "debug": { 22 | "bytecode": "7c548087", 23 | "sourceMap": "3:28:3:29;:21::30:1;;:8::32", 24 | "logs": [], 25 | "requires": [ 26 | { 27 | "ip": 4, 28 | "line": 3 29 | } 30 | ] 31 | }, 32 | "compiler": { 33 | "name": "cashc", 34 | "version": "0.11.0" 35 | }, 36 | "updatedAt": "2025-06-16T15:05:56.024Z" 37 | } 38 | -------------------------------------------------------------------------------- /packages/cashc/src/ast/ThrowingErrorListener.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { ErrorListener, RecognitionException, Recognizer } from 'antlr4'; 3 | import { ParseError } from '../Errors.js'; 4 | import { Point } from './Location.js'; 5 | 6 | /** 7 | * ANTLR Error Listener that immediately throws on error. This is used so that 8 | * ANTLR doesn't attempt any error recovery during lexing/parsing and fails early. 9 | */ 10 | export default class ThrowingErrorListener extends ErrorListener { 11 | static readonly INSTANCE = new ThrowingErrorListener(); 12 | 13 | syntaxError( 14 | recognizer: Recognizer, 15 | offendingSymbol: TSymbol, 16 | line: number, 17 | charPositionInLine: number, 18 | message: string, 19 | e?: RecognitionException, 20 | ): void { 21 | const capitalisedMessage = message.charAt(0).toUpperCase() + message.slice(1); 22 | throw new ParseError(capitalisedMessage, new Point(line, charPositionInLine)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Rosco Kalis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cashscript.org", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "docusaurus start", 7 | "build": "docusaurus build", 8 | "swizzle": "docusaurus swizzle", 9 | "deploy": "docusaurus deploy" 10 | }, 11 | "dependencies": { 12 | "@branchup/docusaurus-plugin-simple-analytics": "^1.1.0", 13 | "@docusaurus/core": "^3.9.2", 14 | "@docusaurus/plugin-client-redirects": "^3.9.2", 15 | "@docusaurus/preset-classic": "^3.9.2", 16 | "classnames": "^2.5.1", 17 | "prism-react-renderer": "^2.4.1", 18 | "react": "^19.2.3", 19 | "react-dom": "^19.2.3" 20 | }, 21 | "devDependencies": { 22 | "@docusaurus/tsconfig": "^3.9.2", 23 | "typescript": "^5.9.2" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/cashc/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Rosco Kalis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /packages/utils/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Rosco Kalis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /packages/cashscript/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Rosco Kalis 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /examples/PriceOracle.ts: -------------------------------------------------------------------------------- 1 | import { padMinimallyEncodedVmNumber, flattenBinArray } from '@bitauth/libauth'; 2 | import { encodeInt, sha256 } from '@cashscript/utils'; 3 | import { SignatureAlgorithm, SignatureTemplate } from 'cashscript'; 4 | 5 | export class PriceOracle { 6 | constructor(public privateKey: Uint8Array) { } 7 | 8 | // Encode a blockHeight and bchUsdPrice into a byte sequence of 8 bytes (4 bytes per value) 9 | createMessage(blockHeight: bigint, bchUsdPrice: bigint): Uint8Array { 10 | const encodedBlockHeight = padMinimallyEncodedVmNumber(encodeInt(blockHeight), 4); 11 | const encodedBchUsdPrice = padMinimallyEncodedVmNumber(encodeInt(bchUsdPrice), 4); 12 | 13 | return flattenBinArray([encodedBlockHeight, encodedBchUsdPrice]); 14 | } 15 | 16 | signMessage(message: Uint8Array, signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.SCHNORR): Uint8Array { 17 | const signatureTemplate = new SignatureTemplate(this.privateKey, undefined, signatureAlgorithm); 18 | return signatureTemplate.signMessageHash(sha256(message)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # CashScript examples 2 | 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. 3 | 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). 5 | 6 | ## Running the examples 7 | 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. 8 | 9 | ```bash 10 | git clone git@github.com:CashScript/cashscript.git 11 | cd cashscript/examples 12 | yarn 13 | ``` 14 | 15 | All `.ts` files can then be executed with `tsx`. 16 | 17 | ```bash 18 | npm install -g tsx 19 | tsx p2pkh.ts 20 | ``` 21 | 22 | All `.js` files can be executed with `node`. 23 | 24 | ```bash 25 | node p2pkh.js 26 | ``` 27 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/PriceOracle.ts: -------------------------------------------------------------------------------- 1 | import { padMinimallyEncodedVmNumber, flattenBinArray } from '@bitauth/libauth'; 2 | import { encodeInt, sha256 } from '@cashscript/utils'; 3 | import { SignatureAlgorithm, SignatureTemplate } from '../../src/index.js'; 4 | 5 | export class PriceOracle { 6 | constructor(public privateKey: Uint8Array) { } 7 | 8 | // Encode a blockHeight and bchUsdPrice into a byte sequence of 8 bytes (4 bytes per value) 9 | createMessage(blockHeight: bigint, bchUsdPrice: bigint): Uint8Array { 10 | const encodedBlockHeight = padMinimallyEncodedVmNumber(encodeInt(blockHeight), 4); 11 | const encodedBchUsdPrice = padMinimallyEncodedVmNumber(encodeInt(bchUsdPrice), 4); 12 | 13 | return flattenBinArray([encodedBlockHeight, encodedBchUsdPrice]); 14 | } 15 | 16 | signMessage(message: Uint8Array, signatureAlgorithm: SignatureAlgorithm = SignatureAlgorithm.SCHNORR): Uint8Array { 17 | const signatureTemplate = new SignatureTemplate(this.privateKey, undefined, signatureAlgorithm); 18 | return signatureTemplate.signMessageHash(sha256(message)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/testing-suite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testing-suite", 3 | "version": "0.12.1", 4 | "description": "Example project to develop and test CashScript contracts", 5 | "main": "index.js", 6 | "type": "module", 7 | "author": "mainnet-pat", 8 | "license": "MIT", 9 | "private": true, 10 | "directories": { 11 | "lib": "src", 12 | "test": "test" 13 | }, 14 | "scripts": { 15 | "build": "yarn clean && yarn compile", 16 | "clean": "rm -rf ./dist", 17 | "compile": "tsc -p tsconfig.json && yarn run task:compile", 18 | "lint": "eslint . --ext .ts --ignore-path ../../.eslintignore", 19 | "prepare": "yarn build", 20 | "prepublishOnly": "yarn test && yarn lint", 21 | "task:compile": "tsx tasks/index.ts compile", 22 | "pretest": "yarn run task:compile", 23 | "test": "vitest run" 24 | }, 25 | "dependencies": { 26 | "@bitauth/libauth": "^3.1.0-next.8", 27 | "cashc": "^0.12.1", 28 | "cashscript": "^0.12.1", 29 | "url-join": "^5.0.0" 30 | }, 31 | "devDependencies": { 32 | "tsx": "^4.20.3", 33 | "typescript": "^5.9.2", 34 | "vitest": "^4.0.15" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/p2pkh-invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "P2PKH", 3 | "constructorInputs": [ 4 | { 5 | "name": "pkh", 6 | "type": "bytes20" 7 | } 8 | ], 9 | "abi": [ 10 | { 11 | "name": "spend", 12 | "inputs": [ 13 | { 14 | "name": "pk", 15 | "type": "pubkey" 16 | }, 17 | { 18 | "name": "s", 19 | "type": "sig" 20 | } 21 | ] 22 | } 23 | ], 24 | "source": "contract P2PKH(bytes20 pkh) {\n // Require pk to match stored pkh and signature to match\n function spend(pubkey pk, sig s) {\n require(hash160(pk) == pkh);\n require(checkSig(s, pk));\n }\n}\n", 25 | "networks": { 26 | "testnet": { 27 | "bchtest:ppzllwzk775qk86zfskzyzae7pa9h4dvzcfezpsdkl": "ffd7616dccb66109ac840cf9484e870ecac5e7fe OP_1 OP_PICK OP_HASH160 OP_1 OP_PICK OP_EQUAL OP_VERIFY OP_2 OP_PICK OP_2 OP_PICK OP_CHECKSIG OP_VERIFY OP_DROP OP_DROP OP_DROP OP_1" 28 | } 29 | }, 30 | "compiler": { 31 | "name": "cashc", 32 | "version": "0.3.0" 33 | }, 34 | "updatedAt": "2019-06-11T08:06:29.010Z" 35 | } 36 | -------------------------------------------------------------------------------- /examples/testing-suite/artifacts/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "Example", 3 | "constructorInputs": [], 4 | "abi": [ 5 | { 6 | "name": "test", 7 | "inputs": [ 8 | { 9 | "name": "value", 10 | "type": "int" 11 | } 12 | ] 13 | } 14 | ], 15 | "bytecode": "OP_1 OP_NUMEQUAL", 16 | "source": "contract Example() {\n function test(int value) {\n console.log(value, \"test\");\n require(value == 1, \"Wrong value passed\");\n }\n}\n", 17 | "debug": { 18 | "bytecode": "519c", 19 | "sourceMap": "4:21:4:22;:4::46:1", 20 | "logs": [ 21 | { 22 | "ip": 0, 23 | "line": 3, 24 | "data": [ 25 | { 26 | "stackIndex": 0, 27 | "type": "int", 28 | "ip": 0 29 | }, 30 | "test" 31 | ] 32 | } 33 | ], 34 | "requires": [ 35 | { 36 | "ip": 2, 37 | "line": 4, 38 | "message": "Wrong value passed" 39 | } 40 | ] 41 | }, 42 | "compiler": { 43 | "name": "cashc", 44 | "version": "0.12.0" 45 | }, 46 | "updatedAt": "2025-12-09T10:19:09.338Z" 47 | } -------------------------------------------------------------------------------- /examples/testing-suite/artifacts/example.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'Example', 3 | constructorInputs: [], 4 | abi: [ 5 | { 6 | name: 'test', 7 | inputs: [ 8 | { 9 | name: 'value', 10 | type: 'int', 11 | }, 12 | ], 13 | }, 14 | ], 15 | bytecode: 'OP_1 OP_NUMEQUAL', 16 | source: 'contract Example() {\n function test(int value) {\n console.log(value, \'test\');\n require(value == 1, \'Wrong value passed\');\n }\n}\n', 17 | debug: { 18 | bytecode: '007a519c', 19 | sourceMap: '4:12:4:17;;:21::22;:12:::1', 20 | logs: [ 21 | { 22 | ip: 0, 23 | line: 3, 24 | data: [ 25 | { 26 | stackIndex: 0, 27 | type: 'int', 28 | ip: 0, 29 | }, 30 | 'test', 31 | ], 32 | }, 33 | ], 34 | requires: [ 35 | { 36 | ip: 4, 37 | line: 4, 38 | message: 'Wrong value passed', 39 | }, 40 | ], 41 | }, 42 | compiler: { 43 | name: 'cashc', 44 | version: '0.11.0', 45 | }, 46 | updatedAt: '2025-04-11T09:08:09.750Z', 47 | } as const; 48 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/everything.cash: -------------------------------------------------------------------------------- 1 | /* This is a contract with some comments 2 | /* It has some comment characters inside the comment too for some reason 3 | // line comments too 4 | */ 5 | 6 | contract Test(int x, string y) { 7 | // Line comments are a thing 8 | function hello(sig s, pubkey pk) { 9 | int i = 400 + x; 10 | bytes b = 0x07364897987fe87 + bytes(y); 11 | 12 | int myVariable = 10 - int(false); // they can go at the end of the line 13 | int myOtherVariable = (i + myVariable) % 2; 14 | require(myOtherVariable /* And comments can be included within lines */ > i); 15 | 16 | myOtherVariable = i; 17 | myVariable = 10; 18 | 19 | require(ripemd160(b) == ripemd160(bytes(myVariable))); 20 | require(this.age >= 500); 21 | require(y.length < -10); 22 | 23 | if (i > 400) { 24 | i = 400; 25 | } 26 | 27 | if (x > 10) { 28 | require(i < 20); 29 | require(checkSig(s, pk)); 30 | } else if (x < 5) { 31 | require(false); 32 | } else 33 | require(myVariable == 1); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | # @cashscript/utils 2 | 3 | [![Build Status](https://github.com/CashScript/cashscript/actions/workflows/github-actions.yml/badge.svg)](https://github.com/CashScript/cashscript/actions/workflows/github-actions.yml) 4 | [![Coverage Status](https://img.shields.io/codecov/c/github/CashScript/cashscript.svg)](https://codecov.io/gh/CashScript/cashscript) 5 | [![NPM Version](https://img.shields.io/npm/v/@cashscript/utils.svg)](https://www.npmjs.com/package/@cashscript/utils) 6 | [![NPM Monthly Downloads](https://img.shields.io/npm/dm/@cashscript/utils.svg)](hhttps://www.npmjs.com/package/@cashscript/utils) 7 | [![NPM License](https://img.shields.io/npm/l/@cashscript/utils.svg)](https://www.npmjs.com/package/@cashscript/utils) 8 | 9 | `@cashscript/utils` is a package containing a number of utilities and types used by both the CashScript compiler and the TypeScript SDK. This package can be used by other applications as well, but it should be considered an unstable API and will not necessarily follow semantic versioning rules. So if you want to use `@cashscript/utils` as a standalone package, it is recommended to specify a specific version (instead of a range) in your `package.json`. 10 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/deeply_nested-logs.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | contract TransferWithTimeout( 4 | pubkey sender, 5 | pubkey recipient, 6 | int timeout 7 | ) { 8 | // Require recipient's signature to match 9 | function transfer(sig recipientSig) { 10 | require(checkSig(recipientSig, recipient)); 11 | console.log('recipientSig is', recipientSig); 12 | } 13 | 14 | // Require timeout time to be reached and sender's signature to match 15 | function timeout(sig senderSig) { 16 | require(checkSig(senderSig, sender)); 17 | if (timeout > 0) { 18 | if (timeout < 10) { 19 | require(timeout == 5); 20 | console.log('timeout is', timeout); 21 | console.log('senderSig is', senderSig); 22 | } else { 23 | console.log('timeout is', timeout); 24 | require(timeout == 15); 25 | } 26 | } else { 27 | require(timeout == 0); 28 | console.log('timeout is', timeout); 29 | } 30 | 31 | console.log('timeout is', timeout, 'and senderSig is', senderSig); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/github-actions.yml: -------------------------------------------------------------------------------- 1 | # Simple automation of tests on pushes to master branch 2 | name: tests 3 | 4 | on: 5 | push: 6 | branches: 7 | - 'master' 8 | # - 'next' 9 | 10 | pull_request: 11 | branches: 12 | - '*' 13 | 14 | # Allows you to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | 17 | jobs: 18 | # single job for testing, linting and spellcheck 19 | testing: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | 26 | - name: Setup node 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: 20 30 | 31 | - name: Install dependencies 32 | run: yarn 33 | 34 | - name: Run tests 35 | run: yarn test -- -- --coverage --coverage.provider=v8 36 | 37 | - name: Run linter 38 | run: yarn lint 39 | 40 | - name: Run spellchecker 41 | run: yarn spellcheck 42 | 43 | - name: Upload coverage reports to Codecov 44 | uses: codecov/codecov-action@v5 45 | with: 46 | token: ${{ secrets.CODECOV_TOKEN }} 47 | slug: CashScript/cashscript 48 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/p2pkh.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'P2PKH', 3 | constructorInputs: [ 4 | { 5 | name: 'pkh', 6 | type: 'bytes20', 7 | }, 8 | ], 9 | abi: [ 10 | { 11 | name: 'spend', 12 | inputs: [ 13 | { 14 | name: 'pk', 15 | type: 'pubkey', 16 | }, 17 | { 18 | name: 's', 19 | type: 'sig', 20 | }, 21 | ], 22 | }, 23 | ], 24 | bytecode: 'OP_OVER OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG', 25 | source: 'contract P2PKH(bytes20 pkh) {\n // Require pk to match stored pkh and signature to match\n function spend(pubkey pk, sig s) {\n require(hash160(pk) == pkh);\n require(checkSig(s, pk));\n }\n}\n', 26 | debug: { 27 | bytecode: '78a988ac', 28 | sourceMap: '4:24:4:26;:16::27:1;:8::36;5::5:33', 29 | logs: [], 30 | requires: [ 31 | { 32 | ip: 3, 33 | line: 4, 34 | }, 35 | { 36 | ip: 5, 37 | line: 5, 38 | }, 39 | ], 40 | }, 41 | compiler: { 42 | name: 'cashc', 43 | version: '0.11.0', 44 | }, 45 | updatedAt: '2025-06-16T15:05:57.831Z', 46 | } as const; 47 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/p2pkh.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "P2PKH", 3 | "constructorInputs": [ 4 | { 5 | "name": "pkh", 6 | "type": "bytes20" 7 | } 8 | ], 9 | "abi": [ 10 | { 11 | "name": "spend", 12 | "inputs": [ 13 | { 14 | "name": "pk", 15 | "type": "pubkey" 16 | }, 17 | { 18 | "name": "s", 19 | "type": "sig" 20 | } 21 | ] 22 | } 23 | ], 24 | "bytecode": "OP_OVER OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG", 25 | "source": "contract P2PKH(bytes20 pkh) {\n // Require pk to match stored pkh and signature to match\n function spend(pubkey pk, sig s) {\n require(hash160(pk) == pkh);\n require(checkSig(s, pk));\n }\n}\n", 26 | "debug": { 27 | "bytecode": "78a988ac", 28 | "sourceMap": "4:24:4:26;:16::27:1;:8::36;5::5:33", 29 | "logs": [], 30 | "requires": [ 31 | { 32 | "ip": 3, 33 | "line": 4 34 | }, 35 | { 36 | "ip": 5, 37 | "line": 5 38 | } 39 | ] 40 | }, 41 | "compiler": { 42 | "name": "cashc", 43 | "version": "0.11.0" 44 | }, 45 | "updatedAt": "2025-06-16T15:05:57.597Z" 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "type": "module", 5 | "workspaces": [ 6 | "packages/*", 7 | "examples", 8 | "examples/testing-suite" 9 | ], 10 | "devDependencies": { 11 | "@types/node": "^22.17.0", 12 | "@typescript-eslint/eslint-plugin": "^7.0.0", 13 | "@typescript-eslint/parser": "^7.0.0", 14 | "cspell": "^9.2.0", 15 | "eslint": "^8.54.0", 16 | "eslint-config-airbnb-typescript": "^18.0.0", 17 | "eslint-plugin-import": "^2.31.0", 18 | "lerna": "^3.22.1", 19 | "tsx": "^4.20.3", 20 | "typescript": "^5.9.2" 21 | }, 22 | "scripts": { 23 | "test": "lerna run test --ignore cashscript-examples --ignore testing-suite", 24 | "lint": "lerna run lint --ignore cashscript-examples --ignore testing-suite", 25 | "examples": "tsx examples/p2pkh.ts && tsx examples/transfer_with_timeout.ts && tsx examples/hodl_vault.ts", 26 | "postinstall": "lerna bootstrap && yarn build", 27 | "spellcheck": "cspell lint '**' --no-progress --must-find-files", 28 | "update-version": "tsx update-version.ts", 29 | "publish-all": "lerna publish from-package", 30 | "build": "lerna run build --ignore cashscript-examples --ignore testing-suite" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/bigint.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'BigInt', 3 | constructorInputs: [], 4 | abi: [ 5 | { 6 | name: 'proofOfBigInt', 7 | inputs: [ 8 | { 9 | name: 'x', 10 | type: 'int', 11 | }, 12 | { 13 | name: 'y', 14 | type: 'int', 15 | }, 16 | ], 17 | }, 18 | ], 19 | bytecode: '000000000000008000 OP_2DUP OP_GREATERTHANOREQUAL OP_VERIFY OP_SWAP OP_ROT OP_MUL OP_LESSTHANOREQUAL', 20 | source: 'contract BigInt() {\n function proofOfBigInt(int x, int y) {\n int maxInt64PlusOne = 9223372036854775808;\n require(x >= maxInt64PlusOne);\n require(x * y >= maxInt64PlusOne);\n }\n}\n', 21 | debug: { 22 | bytecode: '090000000000000080006ea2697c7b95a1', 23 | sourceMap: '3:30:3:49;4:16:4:36;::::1;:8::38;5:16:5:17:0;:20::21;:16:::1;:8::42', 24 | logs: [], 25 | requires: [ 26 | { 27 | ip: 3, 28 | line: 4, 29 | }, 30 | { 31 | ip: 8, 32 | line: 5, 33 | }, 34 | ], 35 | }, 36 | compiler: { 37 | name: 'cashc', 38 | version: '0.11.0', 39 | }, 40 | updatedAt: '2025-06-16T15:05:52.713Z', 41 | } as const; 42 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/bigint.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "BigInt", 3 | "constructorInputs": [], 4 | "abi": [ 5 | { 6 | "name": "proofOfBigInt", 7 | "inputs": [ 8 | { 9 | "name": "x", 10 | "type": "int" 11 | }, 12 | { 13 | "name": "y", 14 | "type": "int" 15 | } 16 | ] 17 | } 18 | ], 19 | "bytecode": "000000000000008000 OP_2DUP OP_GREATERTHANOREQUAL OP_VERIFY OP_SWAP OP_ROT OP_MUL OP_LESSTHANOREQUAL", 20 | "source": "contract BigInt() {\n function proofOfBigInt(int x, int y) {\n int maxInt64PlusOne = 9223372036854775808;\n require(x >= maxInt64PlusOne);\n require(x * y >= maxInt64PlusOne);\n }\n}\n", 21 | "debug": { 22 | "bytecode": "090000000000000080006ea2697c7b95a1", 23 | "sourceMap": "3:30:3:49;4:16:4:36;::::1;:8::38;5:16:5:17:0;:20::21;:16:::1;:8::42", 24 | "logs": [], 25 | "requires": [ 26 | { 27 | "ip": 3, 28 | "line": 4 29 | }, 30 | { 31 | "ip": 8, 32 | "line": 5 33 | } 34 | ] 35 | }, 36 | "compiler": { 37 | "name": "cashc", 38 | "version": "0.11.0" 39 | }, 40 | "updatedAt": "2025-06-16T15:05:52.485Z" 41 | } 42 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/mecenas.cash: -------------------------------------------------------------------------------- 1 | contract Mecenas(bytes20 recipient, bytes20 funder, int pledge, int period) { 2 | function receive() { 3 | require(this.age >= period); 4 | 5 | // Check that the first output sends to the recipient 6 | require(tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(recipient)); 7 | 8 | int minerFee = 1000; 9 | int currentValue = tx.inputs[this.activeInputIndex].value; 10 | int changeValue = currentValue - pledge - minerFee; 11 | 12 | // If there is not enough left for *another* pledge after this one, we send the remainder to the recipient 13 | // Otherwise we send the remainder to the recipient and the change back to the contract 14 | if (changeValue <= pledge + minerFee) { 15 | require(tx.outputs[0].value == currentValue - minerFee); 16 | } else { 17 | require(tx.outputs[0].value == pledge); 18 | require(tx.outputs[1].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode); 19 | require(tx.outputs[1].value == changeValue); 20 | } 21 | } 22 | 23 | function reclaim(pubkey pk, sig s) { 24 | require(hash160(pk) == funder); 25 | require(checkSig(s, pk)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/utils/test/script.fixture.ts: -------------------------------------------------------------------------------- 1 | import { hexToBin } from '@bitauth/libauth'; 2 | import { Op, Script } from '../src/index.js'; 3 | 4 | export interface Fixture { 5 | name: string; 6 | script: Script; 7 | asm: string; 8 | bytecode: Uint8Array; 9 | bytesize: number; 10 | opcount: number; 11 | } 12 | 13 | export const fixtures: Fixture[] = [ 14 | { 15 | name: 'simple addition', 16 | script: [Op.OP_1, Op.OP_1, Op.OP_ADD], 17 | asm: 'OP_1 OP_1 OP_ADD', 18 | bytecode: hexToBin('515193'), 19 | bytesize: 3, 20 | opcount: 1, 21 | }, 22 | { 23 | name: 'P2PKH', 24 | script: [Op.OP_DUP, Op.OP_HASH160, hexToBin('d627b2c3ede2aa85ca5869328873be1d8400bbcb'), Op.OP_EQUALVERIFY, Op.OP_CHECKSIG], 25 | asm: 'OP_DUP OP_HASH160 d627b2c3ede2aa85ca5869328873be1d8400bbcb OP_EQUALVERIFY OP_CHECKSIG', 26 | bytecode: hexToBin('76a914d627b2c3ede2aa85ca5869328873be1d8400bbcb88ac'), 27 | bytesize: 25, 28 | opcount: 4, 29 | }, 30 | { 31 | name: 'simple covenant', 32 | script: [Op.OP_TXVERSION, Op.OP_NUMEQUALVERIFY, Op.OP_ACTIVEBYTECODE, hexToBin('00'), Op.OP_EQUAL], 33 | asm: 'OP_TXVERSION OP_NUMEQUALVERIFY OP_ACTIVEBYTECODE 00 OP_EQUAL', 34 | bytecode: hexToBin('c29dc1010087'), 35 | bytesize: 6, 36 | opcount: 4, 37 | }, 38 | ]; 39 | -------------------------------------------------------------------------------- /examples/testing-suite/test/example.test.ts: -------------------------------------------------------------------------------- 1 | import artifact from '../artifacts/example.artifact.js'; 2 | import { Contract, MockNetworkProvider, TransactionBuilder, randomUtxo } from 'cashscript'; 3 | import 'cashscript/vitest'; 4 | 5 | describe('test example contract functions', () => { 6 | it('should check for output logs and error messages', async () => { 7 | const provider = new MockNetworkProvider(); 8 | const contract = new Contract(artifact, [], { provider }); 9 | 10 | // Create a contract Utxo 11 | const contractUtxo = randomUtxo(); 12 | provider.addUtxo(contract.address, contractUtxo); 13 | 14 | const transactionWrongValuePassed = new TransactionBuilder({ provider }) 15 | .addInput(contractUtxo, contract.unlock.test(0n)) 16 | .addOutput({ to: contract.address, amount: 10000n }); 17 | 18 | expect(transactionWrongValuePassed).toLog(/0 test/); 19 | expect(transactionWrongValuePassed).toFailRequireWith(/Wrong value passed/); 20 | 21 | const transactionRightValuePassed = new TransactionBuilder({ provider }) 22 | .addInput(contractUtxo, contract.unlock.test(1n)) 23 | .addOutput({ to: contract.address, amount: 10000n }) 24 | .send(); 25 | 26 | await expect(transactionRightValuePassed).resolves.not.toThrow(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/cashc/src/artifact/Artifact.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Artifact, DebugInformation, Script, scriptToAsm, 3 | } from '@cashscript/utils'; 4 | import { version } from '../index.js'; 5 | import { Ast } from '../ast/AST.js'; 6 | 7 | export function generateArtifact(ast: Ast, script: Script, source: string, debug: DebugInformation): Artifact { 8 | const { contract } = ast; 9 | 10 | const constructorInputs = contract.parameters 11 | .map((parameter) => ({ name: parameter.name, type: parameter.type.toString() })); 12 | 13 | const abi = contract.functions.map((func) => ({ 14 | name: func.name, 15 | inputs: func.parameters.map((parameter) => ({ 16 | name: parameter.name, 17 | type: parameter.type.toString(), 18 | })), 19 | })); 20 | 21 | const bytecode = scriptToAsm(script); 22 | 23 | return { 24 | contractName: contract.name, 25 | constructorInputs, 26 | abi, 27 | bytecode, 28 | source, 29 | debug, 30 | compiler: { 31 | name: 'cashc', 32 | version, 33 | }, 34 | updatedAt: new Date().toISOString(), 35 | }; 36 | } 37 | 38 | // strip verbose debug info from artifact for production use 39 | export function stripArtifact(artifact: Artifact): Omit { 40 | delete artifact.debug; 41 | return artifact; 42 | } 43 | -------------------------------------------------------------------------------- /examples/common-js.js: -------------------------------------------------------------------------------- 1 | import { hash160 } from '@cashscript/utils'; 2 | import { 3 | deriveHdPrivateNodeFromSeed, 4 | deriveHdPath, 5 | secp256k1, 6 | encodeCashAddress, 7 | deriveSeedFromBip39Mnemonic, 8 | } from '@bitauth/libauth'; 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 = deriveSeedFromBip39Mnemonic('CashScript Examples'); 14 | const rootNode = deriveHdPrivateNodeFromSeed(seed, { assumeValidity: true, throwErrors: 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({ prefix: 'bchtest', type: 'p2pkh', payload: alicePkh, throwErrors: true }).address; 24 | export const aliceTokenAddress = encodeCashAddress({ prefix: 'bchtest', type: 'p2pkhWithTokens', payload: alicePkh, throwErrors: true }).address; 25 | -------------------------------------------------------------------------------- /examples/announcement.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ^0.12.0; 2 | 3 | /* This is a contract showcasing covenants outside of regular transactional use. 4 | * It enforces the contract to make an "announcement" on Memo.cash, and send the 5 | * remainder of contract funds back to the contract. 6 | */ 7 | contract Announcement() { 8 | function announce() { 9 | // Create the memo.cash announcement output 10 | bytes announcement = new LockingBytecodeNullData([ 11 | 0x6d02, 12 | bytes('A contract may not injure a human being or, through inaction, allow a human being to come to harm.') 13 | ]); 14 | 15 | // Check that the first tx output matches the announcement 16 | require(tx.outputs[0].value == 0); 17 | require(tx.outputs[0].lockingBytecode == announcement); 18 | 19 | // Calculate leftover money after fee (1000 sats) 20 | // Check that the second tx output sends the change back if there's enough leftover for another announcement 21 | int minerFee = 1000; 22 | int changeAmount = tx.inputs[this.activeInputIndex].value - minerFee; 23 | if (changeAmount >= minerFee) { 24 | require(tx.outputs[1].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode); 25 | require(tx.outputs[1].value == changeAmount); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/cashscript/src/network/NetworkProvider.ts: -------------------------------------------------------------------------------- 1 | import { Utxo, Network } from '../interfaces.js'; 2 | 3 | export default interface NetworkProvider { 4 | /** 5 | * Variable indicating the network that this provider connects to. 6 | */ 7 | network: Network; 8 | 9 | /** 10 | * Retrieve all UTXOs (confirmed and unconfirmed) for a given address. 11 | * @param address The CashAddress for which we wish to retrieve UTXOs. 12 | * @returns List of UTXOs spendable by the provided address. 13 | */ 14 | getUtxos(address: string): Promise; 15 | 16 | /** 17 | * @returns The current block height. 18 | */ 19 | getBlockHeight(): Promise; 20 | 21 | /** 22 | * Retrieve the Hex transaction details for a given transaction ID. 23 | * @param txid Hex transaction ID. 24 | * @throws {Error} If the transaction does not exist 25 | * @returns The full hex transaction for the provided transaction ID. 26 | */ 27 | getRawTransaction(txid: string): Promise; 28 | 29 | /** 30 | * Broadcast a raw hex transaction to the Bitcoin Cash network. 31 | * @param txHex The raw transaction hex to be broadcast. 32 | * @throws {Error} If the transaction was not accepted by the network. 33 | * @returns The transaction ID corresponding to the broadcast transaction. 34 | */ 35 | sendRawTransaction(txHex: string): Promise; 36 | } 37 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/covenant_all_fields.cash: -------------------------------------------------------------------------------- 1 | contract Covenant() { 2 | function spend() { 3 | require(tx.version == 2); 4 | require(tx.locktime == 0); 5 | require(tx.inputs.length == 1); 6 | require(tx.outputs.length == 1); 7 | require(this.activeInputIndex == 0); 8 | require(this.activeBytecode.length == 300); 9 | require(tx.inputs[0].value == 10000); 10 | require(tx.inputs[0].lockingBytecode.length == 10000); 11 | require(tx.inputs[0].outpointTransactionHash == 0x000000000000000000000000000000000000000000000000000000000000000); 12 | require(tx.inputs[0].outpointIndex == 0); 13 | require(tx.inputs[0].unlockingBytecode.length == 100); 14 | require(tx.inputs[0].sequenceNumber == 0); 15 | require(tx.outputs[0].value == 10000); 16 | require(tx.outputs[0].lockingBytecode.length == 100); 17 | require(tx.inputs[0].tokenCategory == 0x000000000000000000000000000000000000000000000000000000000000000); 18 | require(tx.inputs[0].nftCommitment == 0x00); 19 | require(tx.inputs[0].tokenAmount == 100); 20 | require(tx.outputs[0].tokenCategory == 0x000000000000000000000000000000000000000000000000000000000000000); 21 | require(tx.outputs[0].nftCommitment == 0x00); 22 | require(tx.outputs[0].tokenAmount == 100); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/announcement.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | /* This is a contract showcasing covenants outside of regular transactional use. 4 | * It enforces the contract to make an "announcement" on Memo.cash, and send the 5 | * remainder of contract funds back to the contract. 6 | */ 7 | contract Announcement() { 8 | function announce() { 9 | // Create the memo.cash announcement output 10 | bytes announcement = new LockingBytecodeNullData([ 11 | 0x6d02, 12 | bytes('A contract may not injure a human being or, through inaction, allow a human being to come to harm.') 13 | ]); 14 | 15 | // Check that the first tx output matches the announcement 16 | require(tx.outputs[0].value == 0); 17 | require(tx.outputs[0].lockingBytecode == announcement); 18 | 19 | // Calculate leftover money after fee (1000 sats) 20 | // Check that the second tx output sends the change back if there's enough leftover for another announcement 21 | int minerFee = 1000; 22 | int changeAmount = tx.inputs[this.activeInputIndex].value - minerFee; 23 | if (changeAmount >= minerFee) { 24 | require(tx.outputs[1].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode); 25 | require(tx.outputs[1].value == changeAmount); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/announcement.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | /* This is a contract showcasing covenants outside of regular transactional use. 4 | * It enforces the contract to make an "announcement" on Memo.cash, and send the 5 | * remainder of contract funds back to the contract. 6 | */ 7 | contract Announcement() { 8 | function announce() { 9 | // Create the memo.cash announcement output 10 | bytes announcement = new LockingBytecodeNullData([ 11 | 0x6d02, 12 | bytes('A contract may not injure a human being or, through inaction, allow a human being to come to harm.') 13 | ]); 14 | 15 | // Check that the first tx output matches the announcement 16 | require(tx.outputs[0].value == 0); 17 | require(tx.outputs[0].lockingBytecode == announcement); 18 | 19 | // Calculate leftover money after fee (1000 sats) 20 | // Check that the second tx output sends the change back if there's enough leftover for another announcement 21 | int minerFee = 1000; 22 | int changeAmount = tx.inputs[this.activeInputIndex].value - minerFee; 23 | if (changeAmount >= minerFee) { 24 | require(tx.outputs[1].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode); 25 | require(tx.outputs[1].value == changeAmount); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/hodl_vault.cash: -------------------------------------------------------------------------------- 1 | // This contract forces HODLing until a certain price target has been reached 2 | // A minimum block is provided to ensure that oracle price entries from before this block are disregarded 3 | // i.e. when the BCH price was $1000 in the past, an oracle entry with the old block number and price can not be used. 4 | // Instead, a message with a block number and price from after the minBlock needs to be passed. 5 | // This contract serves as a simple example for checkDataSig-based contracts. 6 | contract HodlVault( 7 | pubkey ownerPk, 8 | pubkey oraclePk, 9 | int minBlock, 10 | int priceTarget 11 | ) { 12 | function spend(sig ownerSig, datasig oracleSig, bytes8 oracleMessage) { 13 | // message: { blockHeight, price } 14 | bytes4 blockHeightBin, bytes4 priceBin = oracleMessage.split(4); 15 | int blockHeight = int(blockHeightBin); 16 | int price = int(priceBin); 17 | 18 | // Check that blockHeight is after minBlock and not in the future 19 | require(blockHeight >= minBlock); 20 | require(tx.time >= blockHeight); 21 | 22 | // Check that current price is at least priceTarget 23 | require(price >= priceTarget); 24 | 25 | // Handle necessary signature checks 26 | require(checkDataSig(oracleSig, oracleMessage, oraclePk)); 27 | require(checkSig(ownerSig, ownerPk)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/hodl_vault.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ^0.12.0; 2 | 3 | // This contract forces HODLing until a certain price target has been reached 4 | // A minimum block is provided to ensure that oracle price entries from before this block are disregarded 5 | // i.e. when the BCH price was $1000 in the past, an oracle entry with the old block number and price can not be used. 6 | // Instead, a message with a block number and price from after the minBlock needs to be passed. 7 | // This contract serves as a simple example for checkDataSig-based contracts. 8 | contract HodlVault( 9 | pubkey ownerPk, 10 | pubkey oraclePk, 11 | int minBlock, 12 | int priceTarget 13 | ) { 14 | function spend(sig ownerSig, datasig oracleSig, bytes oracleMessage) { 15 | // message: { blockHeight, price } 16 | bytes4 blockHeightBin, bytes4 priceBin = oracleMessage.split(4); 17 | int blockHeight = int(blockHeightBin); 18 | int price = int(priceBin); 19 | 20 | // Check that blockHeight is after minBlock and not in the future 21 | require(blockHeight >= minBlock); 22 | require(tx.time >= blockHeight); 23 | 24 | // Check that current price is at least priceTarget 25 | require(price >= priceTarget); 26 | 27 | // Handle necessary signature checks 28 | require(checkDataSig(oracleSig, oracleMessage, oraclePk)); 29 | require(checkSig(ownerSig, ownerPk)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/deprecated/mecenas-v0.6.0.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ^0.6.0; 2 | 3 | /* This is an unofficial CashScript port of Licho's Mecenas contract. It is 4 | * not compatible with Licho's EC plugin, but rather meant as a demonstration 5 | * of covenants in CashScript. 6 | * The time checking has been removed so it can be tested without time requirements. 7 | */ 8 | contract Mecenas(bytes20 recipient, bytes20 funder, int pledge/*, int period*/) { 9 | function receive(pubkey pk, sig s) { 10 | require(checkSig(s, pk)); 11 | 12 | // require(tx.age >= period); 13 | 14 | int minerFee = 1000; 15 | int intValue = int(bytes(tx.value)); 16 | 17 | if (intValue <= pledge + minerFee) { 18 | bytes8 amount1 = bytes8(intValue - minerFee); 19 | bytes34 out1 = new OutputP2PKH(amount1, recipient); 20 | require(hash256(out1) == tx.hashOutputs); 21 | } else { 22 | bytes8 amount1 = bytes8(pledge); 23 | bytes8 amount2 = bytes8(intValue - pledge - minerFee); 24 | bytes34 out1 = new OutputP2PKH(amount1, recipient); 25 | bytes32 out2 = new OutputP2SH(amount2, hash160(tx.bytecode)); 26 | require(hash256(out1 + out2) == tx.hashOutputs); 27 | } 28 | } 29 | 30 | function reclaim(pubkey pk, sig s) { 31 | require(hash160(pk) == funder); 32 | require(checkSig(s, pk)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/Foo.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'Foo', 3 | constructorInputs: [ 4 | { 5 | name: 'pkh_foo', 6 | type: 'bytes20', 7 | }, 8 | ], 9 | abi: [ 10 | { 11 | name: 'execute', 12 | inputs: [ 13 | { 14 | name: 'pk', 15 | type: 'pubkey', 16 | }, 17 | { 18 | name: 's', 19 | type: 'sig', 20 | }, 21 | ], 22 | }, 23 | ], 24 | bytecode: 'OP_OVER OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG', 25 | source: 'pragma cashscript >=0.10.2;\n\ncontract Foo(bytes20 pkh_foo) {\n // Require pk to match stored pkh and signature to match\n function execute(pubkey pk, sig s) {\n console.log("Foo \'execute\' function called.");\n require(hash160(pk) == pkh_foo);\n require(checkSig(s, pk));\n }\n}\n', 26 | debug: { 27 | bytecode: '78a988ac', 28 | sourceMap: '7:24:7:26;:16::27:1;:8::40;8::8:33', 29 | logs: [ 30 | { 31 | ip: 1, 32 | line: 6, 33 | data: [ 34 | 'Foo \'execute\' function called.', 35 | ], 36 | }, 37 | ], 38 | requires: [ 39 | { 40 | ip: 3, 41 | line: 7, 42 | }, 43 | { 44 | ip: 5, 45 | line: 8, 46 | }, 47 | ], 48 | }, 49 | compiler: { 50 | name: 'cashc', 51 | version: '0.11.0', 52 | }, 53 | updatedAt: '2025-06-16T15:05:54.732Z', 54 | } as const; 55 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/Foo.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "Foo", 3 | "constructorInputs": [ 4 | { 5 | "name": "pkh_foo", 6 | "type": "bytes20" 7 | } 8 | ], 9 | "abi": [ 10 | { 11 | "name": "execute", 12 | "inputs": [ 13 | { 14 | "name": "pk", 15 | "type": "pubkey" 16 | }, 17 | { 18 | "name": "s", 19 | "type": "sig" 20 | } 21 | ] 22 | } 23 | ], 24 | "bytecode": "OP_OVER OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG", 25 | "source": "pragma cashscript >=0.10.2;\n\ncontract Foo(bytes20 pkh_foo) {\n // Require pk to match stored pkh and signature to match\n function execute(pubkey pk, sig s) {\n console.log(\"Foo 'execute' function called.\");\n require(hash160(pk) == pkh_foo);\n require(checkSig(s, pk));\n }\n}\n", 26 | "debug": { 27 | "bytecode": "78a988ac", 28 | "sourceMap": "7:24:7:26;:16::27:1;:8::40;8::8:33", 29 | "logs": [ 30 | { 31 | "ip": 1, 32 | "line": 6, 33 | "data": [ 34 | "Foo 'execute' function called." 35 | ] 36 | } 37 | ], 38 | "requires": [ 39 | { 40 | "ip": 3, 41 | "line": 7 42 | }, 43 | { 44 | "ip": 5, 45 | "line": 8 46 | } 47 | ] 48 | }, 49 | "compiler": { 50 | "name": "cashc", 51 | "version": "0.11.0" 52 | }, 53 | "updatedAt": "2025-06-16T15:05:54.487Z" 54 | } 55 | -------------------------------------------------------------------------------- /packages/utils/src/data.ts: -------------------------------------------------------------------------------- 1 | import { 2 | bigIntToVmNumber, 3 | utf8ToBin, 4 | binToUtf8, 5 | vmNumberToBigInt, 6 | isVmNumberError, 7 | } from '@bitauth/libauth'; 8 | 9 | export function encodeBool(bool: boolean): Uint8Array { 10 | return bool ? encodeInt(1n) : encodeInt(0n); 11 | } 12 | 13 | export function decodeBool(encodedBool: Uint8Array): boolean { 14 | // Any encoding of 0 is false, else true 15 | for (let i = 0; i < encodedBool.byteLength; i += 1) { 16 | if (encodedBool[i] !== 0) { 17 | // Can be negative zero 18 | if (i === encodedBool.byteLength - 1 && encodedBool[i] === 0x80) return false; 19 | return true; 20 | } 21 | } 22 | return false; 23 | } 24 | 25 | export function encodeInt(int: bigint): Uint8Array { 26 | return bigIntToVmNumber(int); 27 | } 28 | 29 | export function decodeInt(encodedInt: Uint8Array, maxLength: number = Infinity): bigint { 30 | const options = { maximumVmNumberByteLength: maxLength }; 31 | const result = vmNumberToBigInt(encodedInt, options); 32 | 33 | if (isVmNumberError(result)) { 34 | throw new Error(result); 35 | } 36 | 37 | return result; 38 | } 39 | 40 | export function encodeString(str: string): Uint8Array { 41 | return utf8ToBin(str); 42 | } 43 | 44 | export function decodeString(encodedString: Uint8Array): string { 45 | return binToUtf8(encodedString); 46 | } 47 | 48 | export function placeholder(size: number): Uint8Array { 49 | return new Uint8Array(size).fill(0); 50 | } 51 | -------------------------------------------------------------------------------- /packages/cashc/test/valid-contract-files/hodl_vault.cash: -------------------------------------------------------------------------------- 1 | // This contract forces HODLing until a certain price target has been reached 2 | // A minimum block is provided to ensure that oracle price entries from before this block are disregarded 3 | // i.e. when the BCH price was $1000 in the past, an oracle entry with the old block number and price can not be used. 4 | // Instead, a message with a block number and price from after the minBlock needs to be passed. 5 | // This contract serves as a simple example for checkDataSig-based contracts. 6 | contract HodlVault( 7 | pubkey ownerPk, 8 | pubkey oraclePk, 9 | int minBlock, 10 | int priceTarget 11 | ) { 12 | function spend(sig ownerSig, datasig oracleSig, bytes8 oracleMessage) { 13 | // message: { blockHeight, price } 14 | bytes4 blockHeightBin, bytes4 priceBin = oracleMessage.split(4); 15 | int blockHeight = int(blockHeightBin); 16 | int price = int(priceBin); 17 | 18 | // Check that blockHeight is after minBlock and not in the future 19 | require(blockHeight >= minBlock); 20 | require(tx.time >= blockHeight); 21 | 22 | // Check that current price is at least priceTarget 23 | require(price >= priceTarget); 24 | 25 | // Handle necessary signature checks 26 | require(checkDataSig( 27 | oracleSig, 28 | oracleMessage, 29 | oraclePk, 30 | )); 31 | require(checkSig(ownerSig, ownerPk)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/debugging/multi_contract_debugging_contracts.ts: -------------------------------------------------------------------------------- 1 | import { compileString } from 'cashc'; 2 | 3 | const SAME_NAME_DIFFERENT_PATH = ` 4 | contract SameNameDifferentPath(int a) { 5 | function function_1(int b) { 6 | if (a == 0) { 7 | console.log("a is 0"); 8 | require(b == 0, "b should be 0"); 9 | } else { 10 | console.log("a is not 0"); 11 | require(b != 0, "b should not be 0"); 12 | } 13 | } 14 | } 15 | `; 16 | 17 | const NAME_COLLISION = ` 18 | contract NameCollision(int a) { 19 | function name_collision(int b) { 20 | require(a == 0, "a should be 0"); 21 | require(b == 0, "b should be 0"); 22 | } 23 | } 24 | `; 25 | 26 | const CONTRACT_NAME_COLLISION = ` 27 | contract NameCollision(int a) { 28 | function name_collision(int b) { 29 | require(b == 1, "b should be 1"); 30 | require(a == 1, "a should be 1"); 31 | } 32 | } 33 | `; 34 | 35 | const FUNCTION_NAME_COLLISION = ` 36 | contract FunctionNameCollision(int a) { 37 | function name_collision(int b) { 38 | require(b == 1, "b should be 1"); 39 | require(a == 1, "a should be 1"); 40 | } 41 | } 42 | `; 43 | 44 | export const ARTIFACT_SAME_NAME_DIFFERENT_PATH = compileString(SAME_NAME_DIFFERENT_PATH); 45 | export const ARTIFACT_NAME_COLLISION = compileString(NAME_COLLISION); 46 | export const ARTIFACT_CONTRACT_NAME_COLLISION = compileString(CONTRACT_NAME_COLLISION); 47 | export const ARTIFACT_FUNCTION_NAME_COLLISION = compileString(FUNCTION_NAME_COLLISION); 48 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cashscript/utils", 3 | "version": "0.12.1", 4 | "description": "CashScript utilities and types", 5 | "keywords": [ 6 | "bitcoin cash", 7 | "cashscript", 8 | "sdk", 9 | "smart contracts", 10 | "cashtokens" 11 | ], 12 | "homepage": "https://cashscript.org", 13 | "bugs": { 14 | "url": "https://github.com/CashScript/cashscript/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/CashScript/cashscript.git" 19 | }, 20 | "license": "MIT", 21 | "author": "Rosco Kalis ", 22 | "contributors": [ 23 | "Mathieu Geukens " 24 | ], 25 | "main": "dist/index.js", 26 | "types": "dist/index.d.ts", 27 | "type": "module", 28 | "sideEffects": false, 29 | "directories": { 30 | "lib": "src", 31 | "test": "test" 32 | }, 33 | "scripts": { 34 | "build": "yarn clean && yarn compile", 35 | "clean": "rm -rf ./dist", 36 | "compile": "tsc -p tsconfig.build.json", 37 | "lint": "eslint . --ext .ts --ignore-path ../../.eslintignore", 38 | "prepare": "yarn build", 39 | "prepublishOnly": "yarn test && yarn lint", 40 | "test": "vitest run" 41 | }, 42 | "dependencies": { 43 | "@bitauth/libauth": "^3.1.0-next.8" 44 | }, 45 | "devDependencies": { 46 | "@vitest/coverage-v8": "^4.0.15", 47 | "eslint": "^8.54.0", 48 | "typescript": "^5.9.2", 49 | "vitest": "^4.0.15" 50 | }, 51 | "gitHead": "bf02a4b641d5d03c035d052247a545109c17b708" 52 | } 53 | -------------------------------------------------------------------------------- /examples/mecenas.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript ^0.12.0; 2 | 3 | /* This is an unofficial CashScript port of Licho's Mecenas contract. It is 4 | * not compatible with Licho's EC plugin, but rather meant as a demonstration 5 | * of covenants in CashScript. 6 | * The time checking has been removed so it can be tested without time requirements. 7 | */ 8 | contract Mecenas(bytes20 recipient, bytes20 funder, int pledge/*, int period */) { 9 | function receive() { 10 | // require(this.age >= period); 11 | 12 | // Check that the first output sends to the recipient 13 | require(tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(recipient)); 14 | 15 | int minerFee = 1000; 16 | int currentValue = tx.inputs[this.activeInputIndex].value; 17 | int changeValue = currentValue - pledge - minerFee; 18 | 19 | // If there is not enough left for *another* pledge after this one, we send the remainder to the recipient 20 | // Otherwise we send the remainder to the recipient and the change back to the contract 21 | if (changeValue <= pledge + minerFee) { 22 | require(tx.outputs[0].value == currentValue - minerFee); 23 | } else { 24 | require(tx.outputs[0].value == pledge); 25 | require(tx.outputs[1].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode); 26 | require(tx.outputs[1].value == changeValue); 27 | } 28 | } 29 | 30 | function reclaim(pubkey pk, sig s) { 31 | require(hash160(pk) == funder); 32 | require(checkSig(s, pk)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/cashc/src/ast/Operator.ts: -------------------------------------------------------------------------------- 1 | export enum NullaryOperator { 2 | INPUT_INDEX = 'this.activeInputIndex', 3 | BYTECODE = 'this.activeBytecode', 4 | INPUT_COUNT = 'tx.inputs.length', 5 | OUTPUT_COUNT = 'tx.outputs.length', 6 | VERSION = 'tx.version', 7 | LOCKTIME = 'tx.locktime', 8 | } 9 | 10 | export enum UnaryOperator { 11 | NOT = '!', 12 | NEGATE = '-', 13 | SIZE = '.length', 14 | REVERSE = '.reverse()', 15 | INPUT_VALUE = 'tx.inputs[i].value', 16 | INPUT_LOCKING_BYTECODE = 'tx.inputs[i].lockingBytecode', 17 | INPUT_OUTPOINT_HASH = 'tx.inputs[i].outpointTransactionHash', 18 | INPUT_OUTPOINT_INDEX = 'tx.inputs[i].outpointIndex', 19 | INPUT_UNLOCKING_BYTECODE = 'tx.inputs[i].unlockingBytecode', 20 | INPUT_SEQUENCE_NUMBER = 'tx.inputs[i].sequenceNumber', 21 | OUTPUT_VALUE = 'tx.outputs[i].value', 22 | OUTPUT_LOCKING_BYTECODE = 'tx.outputs[i].lockingBytecode', 23 | INPUT_TOKEN_CATEGORY = 'tx.inputs[i].tokenCategory', 24 | INPUT_NFT_COMMITMENT = 'tx.inputs[i].nftCommitment', 25 | INPUT_TOKEN_AMOUNT = 'tx.inputs[i].tokenAmount', 26 | OUTPUT_TOKEN_CATEGORY = 'tx.outputs[i].tokenCategory', 27 | OUTPUT_NFT_COMMITMENT = 'tx.outputs[i].nftCommitment', 28 | OUTPUT_TOKEN_AMOUNT = 'tx.outputs[i].tokenAmount', 29 | } 30 | 31 | export enum BinaryOperator { 32 | MUL = '*', 33 | DIV = '/', 34 | MOD = '%', 35 | PLUS = '+', 36 | MINUS = '-', 37 | LT = '<', 38 | LE = '<=', 39 | GT = '>', 40 | GE = '>=', 41 | EQ = '==', 42 | NE = '!=', 43 | BIT_AND = '&', 44 | BIT_XOR = '^', 45 | BIT_OR = '|', 46 | AND = '&&', 47 | OR = '||', 48 | SPLIT = '.split', 49 | } 50 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/mecenas.cash: -------------------------------------------------------------------------------- 1 | pragma cashscript >=0.8.0; 2 | 3 | /* This is an unofficial CashScript port of Licho's Mecenas contract. It is 4 | * not compatible with Licho's EC plugin, but rather meant as a demonstration 5 | * of covenants in CashScript. 6 | * The time checking has been removed so it can be tested without time requirements. 7 | */ 8 | contract Mecenas(bytes20 recipient, bytes20 funder, int pledge/*, int period */) { 9 | function receive() { 10 | // require(this.age >= period); 11 | 12 | // Check that the first output sends to the recipient 13 | require(tx.outputs[0].lockingBytecode == new LockingBytecodeP2PKH(recipient)); 14 | 15 | int minerFee = 1000; 16 | int currentValue = tx.inputs[this.activeInputIndex].value; 17 | int changeValue = currentValue - pledge - minerFee; 18 | 19 | // If there is not enough left for *another* pledge after this one, we send the remainder to the recipient 20 | // Otherwise we send the remainder to the recipient and the change back to the contract 21 | if (changeValue <= pledge + minerFee) { 22 | require(tx.outputs[0].value == currentValue - minerFee); 23 | } else { 24 | require(tx.outputs[0].value == pledge); 25 | require(tx.outputs[1].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode); 26 | require(tx.outputs[1].value == changeValue); 27 | } 28 | } 29 | 30 | function reclaim(pubkey pk, sig s) { 31 | require(hash160(pk) == funder); 32 | require(checkSig(s, pk)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /website/docs/basics/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What is CashScript? 3 | sidebar_label: About CashScript 4 | --- 5 | 6 | CashScript is a high-level programming language for smart contracts on Bitcoin Cash. It offers a strong abstraction layer over Bitcoin Cash' native virtual machine, BCH Script. CashScript was created in 2019 and has seen major upgrades over the years, thereby supporting new script functionalities enabled with the different Bitcoin Cash network upgrades — including native introspection and CashTokens. 7 | 8 | The CashScript syntax is based on Ethereum's smart contract language Solidity, but its functionality is very different since smart contracts on Bitcoin Cash differ greatly from smart contracts on Ethereum. You can read more on these differences on the ['About Bitcoin Cash' page](./about-bch.md). 9 | 10 | CashScript comes with a powerful TypeScript SDK for creating, debugging and testing smart contract transactions on BCH. The SDK has an integrated networkProvider API to fetch data from the BCH blockchain. With all this functionality, the Typescript SDK makes it easy to use CashScript contracts in applications in the browser or on the server in a type-safe way. 11 | 12 | CashScript aims to make building smart contract apps on Bitcoin Cash accessible to a wide range of developers and to power the next-generation of UTXO smart contract applications. 13 | 14 | :::tip 15 | To see what kind of things can be built with CashScript, you can look at the [Showcase](/docs/showcase) or [Examples](/docs/language/examples). 16 | 17 | To just dive into CashScript, refer to the [Getting Started page](/docs/basics/getting-started). 18 | ::: 19 | -------------------------------------------------------------------------------- /packages/cashscript/src/network/BitcoinRpcNetworkProvider.ts: -------------------------------------------------------------------------------- 1 | import { Utxo, Network } from '../interfaces.js'; 2 | import NetworkProvider from './NetworkProvider.js'; 3 | import { 4 | BchnRpcClient, 5 | type GetBlockCount, 6 | type GetRawTransactionVerbosity0, 7 | type ListUnspent, 8 | type SendRawTransaction, 9 | } from '@mr-zwets/bchn-api-wrapper'; 10 | 11 | export default class BitcoinRpcNetworkProvider implements NetworkProvider { 12 | private rpcClient: BchnRpcClient; 13 | 14 | constructor( 15 | public network: Network, 16 | url: string, 17 | opts: { 18 | rpcUser: string; 19 | rpcPassword: string; 20 | }, 21 | ) { 22 | this.rpcClient = new BchnRpcClient({ url, ...opts }); 23 | } 24 | 25 | async getUtxos(address: string): Promise { 26 | const result = await this.rpcClient.request('listunspent', 0, 9999999, [address]); 27 | 28 | const utxos = result.map((utxo) => ({ 29 | txid: utxo.txid, 30 | vout: utxo.vout, 31 | satoshis: BigInt(utxo.amount * 1e8), 32 | })); 33 | 34 | return utxos; 35 | } 36 | 37 | async getBlockHeight(): Promise { 38 | return this.rpcClient.request('getblockcount'); 39 | } 40 | 41 | async getRawTransaction(txid: string): Promise { 42 | return this.rpcClient.request('getrawtransaction', txid, 0); 43 | } 44 | 45 | async sendRawTransaction(txHex: string): Promise { 46 | return this.rpcClient.request('sendrawtransaction', txHex); 47 | } 48 | 49 | getClient(): BchnRpcClient { 50 | return this.rpcClient; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/utils/test/hash.test.ts: -------------------------------------------------------------------------------- 1 | import { hexToBin } from '@bitauth/libauth'; 2 | import { 3 | hash160, 4 | hash256, 5 | ripemd160, 6 | sha256, 7 | sha512, 8 | } from '../src/index.js'; 9 | 10 | describe('hashing functions', () => { 11 | describe('sha512()', () => { 12 | it('should perform a sha512 hash', () => { 13 | expect(sha512(hexToBin('000000000000000000'))) 14 | .toEqual(hexToBin('a85de39409374651002c84cba7c928eb96fb82f88acc2aeac392985def3e1389346122b82c9c47b743a5c2764bb0e3f5c309af7202dbe723e69a5193e84fcbc4')); 15 | }); 16 | }); 17 | 18 | describe('sha256()', () => { 19 | it('should perform a sha256 hash', () => { 20 | expect(sha256(hexToBin('000000000000000000'))) 21 | .toEqual(hexToBin('3e7077fd2f66d689e0cee6a7cf5b37bf2dca7c979af356d0a31cbc5c85605c7d')); 22 | }); 23 | }); 24 | 25 | describe('ripemd160()', () => { 26 | it('should perform a ripemd160 hash', () => { 27 | expect(ripemd160(hexToBin('000000000000000000'))) 28 | .toEqual(hexToBin('f4b7fe5e9c0898fb14bf52365feece17d5047199')); 29 | }); 30 | }); 31 | 32 | describe('hash160()', () => { 33 | it('should perform a sha256 hash, then a ripemd160 hash on the result', () => { 34 | expect(hash160(hexToBin('000000000000000000'))) 35 | .toEqual(hexToBin('85179ad383f90555e7b98bfa4e9a560ab6eba1e6')); 36 | }); 37 | }); 38 | 39 | describe('hash256()', () => { 40 | it('should perform a sha256 hash, then a sha256 hash on the result', () => { 41 | expect(hash256(hexToBin('000000000000000000'))) 42 | .toEqual(hexToBin('edb908054ac1409be5f77d5369c6e03490b2f6676d68d0b3370f8159e0fdadf9')); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/utils/test/bitauth-script.test.ts: -------------------------------------------------------------------------------- 1 | import { asmToScript } from '../src/script.js'; 2 | import { buildLineToOpcodesMap, buildLineToAsmMap, formatBitAuthScript } from '../src/bitauth-script.js'; 3 | import { fixtures } from './fixtures/bitauth-script.fixture.js'; 4 | import { compileString } from 'cashc'; 5 | 6 | describe('Libauth Script formatting', () => { 7 | fixtures.forEach((fixture) => { 8 | describe(fixture.name, () => { 9 | const scriptBytecode = asmToScript(fixture.asmBytecode); 10 | 11 | // Note: this also tests the compiler (but the compiler is tested much more thoroughly in its own test suite) 12 | it('should generate a correct source map and bytecode from freshly compiled source code', () => { 13 | const artifact = compileString(fixture.sourceCode); 14 | expect(artifact.debug?.sourceMap).toEqual(fixture.sourceMap); 15 | expect(artifact.bytecode).toEqual(fixture.asmBytecode); 16 | }); 17 | 18 | it('should build a line-to-opcodes map', () => { 19 | expect(buildLineToOpcodesMap(scriptBytecode, fixture.sourceMap)).toEqual(fixture.expectedLineToOpcodeMap); 20 | }); 21 | 22 | it('should build a line-to-asm map', () => { 23 | expect(buildLineToAsmMap(scriptBytecode, fixture.sourceMap)).toEqual(fixture.expectedLineToAsmMap); 24 | }); 25 | 26 | it('should format script as debugging output for BitAuth IDE', () => { 27 | const expectedBitAuthScript = fixture.expectedBitAuthScript.replace(/^\n+/, '').replace(/\n+$/, ''); 28 | expect(formatBitAuthScript(scriptBytecode, fixture.sourceMap, fixture.sourceCode)).toBe(expectedBitAuthScript); 29 | }); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/cashc/test/compiler/compiler.test.ts: -------------------------------------------------------------------------------- 1 | /* Compiler.test.ts 2 | * 3 | * - This file is used to test the overall functioning of the compiler. 4 | * - It tests successful compilation using fixture .cash files in ../valid-contract-files. 5 | * - It tests compile errors using fixture .cash files in respective Error directories. 6 | */ 7 | 8 | import { URL } from 'url'; 9 | import { getSubdirectories, readCashFiles } from '../test-utils.js'; 10 | import * as Errors from '../../src/Errors.js'; 11 | import { compileString } from '../../src/index.js'; 12 | 13 | describe('Compiler', () => { 14 | describe('Successful compilation', () => { 15 | readCashFiles(new URL('../valid-contract-files', import.meta.url)).forEach((file) => { 16 | it(`${file.fn} should succeed`, () => { 17 | expect(() => compileString(file.contents)).not.toThrow(); 18 | }); 19 | }); 20 | }); 21 | 22 | describe('Compilation errors', () => { 23 | const errorTypes = getSubdirectories(new URL('.', import.meta.url)); 24 | 25 | errorTypes.forEach((errorType) => { 26 | describe(errorType.toString(), () => { 27 | readCashFiles(new URL(errorType, import.meta.url)).forEach((file) => { 28 | it(`${file.fn} should throw ${errorType}`, () => { 29 | // Retrieve the correct Error constructor from the Errors.ts file 30 | const expectedError = Errors[errorType as keyof typeof Errors]; 31 | 32 | if (!expectedError) throw new Error(`Invalid test configuration: error ${errorType} does not exist`); 33 | 34 | expect(() => compileString(file.contents)).toThrow(expectedError); 35 | }); 36 | }); 37 | }); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/cashc/src/ast/Location.ts: -------------------------------------------------------------------------------- 1 | import type { ParserRuleContext, Token } from 'antlr4'; 2 | import { LocationI } from '@cashscript/utils'; 3 | 4 | export class Location implements LocationI { 5 | constructor(public start: Point, public end: Point) {} 6 | 7 | static fromCtx(ctx: ParserRuleContext): Location { 8 | const stop = ctx.stop?.text ? ctx.stop : ctx.start; 9 | const textLength = (stop.text ?? '').length; 10 | 11 | const start = new Point(ctx.start.line, ctx.start.column); 12 | const end = new Point(stop.line, stop.column + textLength); 13 | 14 | return new Location(start, end); 15 | } 16 | 17 | static fromToken(token: Token): Location { 18 | const textLength = (token.text ?? '').length; 19 | 20 | const start = new Point(token.line, token.column); 21 | const end = new Point(token.line, token.column + textLength); 22 | 23 | return new Location(start, end); 24 | } 25 | 26 | static fromObject(object: LocationI): Location { 27 | const start = new Point(object.start.line, object.start.column); 28 | const end = new Point(object.end.line, object.end.column); 29 | 30 | return new Location(start, end); 31 | } 32 | 33 | text(code: string): string { 34 | return code.slice(this.start.offset(code), this.end.offset(code)); 35 | } 36 | } 37 | 38 | export class Point { 39 | constructor(public line: number, public column: number) {} 40 | 41 | offset(code: string): number { 42 | const lines = code.split('\n'); 43 | const newLines = this.line - 1; 44 | const lineOffset = lines.slice(0, newLines).reduce((acc, curr) => acc + curr.length, 0); 45 | return lineOffset + newLines + this.column; 46 | } 47 | 48 | toString(): string { 49 | return `Line ${this.line}, Column ${this.column}`; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/cashscript/src/network/FullStackNetworkProvider.ts: -------------------------------------------------------------------------------- 1 | import { Utxo, Network } from '../interfaces.js'; 2 | import NetworkProvider from './NetworkProvider.js'; 3 | 4 | export default class FullStackNetworkProvider implements NetworkProvider { 5 | /** 6 | * @example 7 | * const BCHJS = require("@psf/bch-js") 8 | * let bchjs = new BCHJS({ 9 | * restURL: 'https://api.fullstack.cash/v3/', 10 | * apiToken: 'eyJhbGciO...' // Your JWT token here. 11 | * }) 12 | */ 13 | constructor( 14 | public network: Network, 15 | private bchjs: BCHJS, 16 | ) {} 17 | 18 | async getUtxos(address: string): Promise { 19 | const result = await this.bchjs.Electrumx.utxo(address); 20 | 21 | const utxos = (result.utxos ?? []).map((utxo: ElectrumUtxo) => ({ 22 | txid: utxo.tx_hash, 23 | vout: utxo.tx_pos, 24 | satoshis: BigInt(utxo.value), 25 | })); 26 | 27 | return utxos; 28 | } 29 | 30 | async getBlockHeight(): Promise { 31 | return this.bchjs.Blockchain.getBlockCount(); 32 | } 33 | 34 | async getRawTransaction(txid: string): Promise { 35 | return this.bchjs.RawTransactions.getRawTransaction(txid); 36 | } 37 | 38 | async sendRawTransaction(txHex: string): Promise { 39 | return this.bchjs.RawTransactions.sendRawTransaction(txHex); 40 | } 41 | } 42 | 43 | interface ElectrumUtxo { 44 | tx_pos: number; 45 | value: number; 46 | tx_hash: string; 47 | height: number; 48 | } 49 | 50 | interface BCHJS { 51 | Electrumx: { 52 | utxo(address: string): Promise<{ utxos: ElectrumUtxo[] }>; 53 | }; 54 | Blockchain: { 55 | getBlockCount(): Promise; 56 | }; 57 | RawTransactions: { 58 | getRawTransaction(txid: string): Promise; 59 | sendRawTransaction(txHex: string): Promise; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /packages/cashscript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cashscript", 3 | "version": "0.12.1", 4 | "description": "Easily write and interact with Bitcoin Cash contracts", 5 | "keywords": [ 6 | "bitcoin cash", 7 | "cashscript", 8 | "sdk", 9 | "smart contracts", 10 | "cashtokens" 11 | ], 12 | "homepage": "https://cashscript.org", 13 | "bugs": { 14 | "url": "https://github.com/CashScript/cashscript/issues" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/CashScript/cashscript.git" 19 | }, 20 | "license": "MIT", 21 | "author": "Rosco Kalis ", 22 | "contributors": [ 23 | "Mathieu Geukens ", 24 | "Gabriel Cardona " 25 | ], 26 | "main": "dist/index.js", 27 | "types": "dist/index.d.ts", 28 | "type": "module", 29 | "sideEffects": false, 30 | "directories": { 31 | "lib": "src", 32 | "test": "test" 33 | }, 34 | "scripts": { 35 | "build": "yarn clean && yarn compile", 36 | "clean": "rm -rf ./dist", 37 | "compile": "tsc -p tsconfig.build.json", 38 | "lint": "eslint . --ext .ts --ignore-path ../../.eslintignore", 39 | "prepare": "yarn build", 40 | "prepublishOnly": "yarn test && yarn lint", 41 | "test": "vitest run" 42 | }, 43 | "dependencies": { 44 | "@bitauth/libauth": "^3.1.0-next.8", 45 | "@cashscript/utils": "^0.12.1", 46 | "@electrum-cash/network": "^4.1.3", 47 | "@mr-zwets/bchn-api-wrapper": "^1.0.1", 48 | "pako": "^2.1.0", 49 | "semver": "^7.7.2" 50 | }, 51 | "devDependencies": { 52 | "@psf/bch-js": "^6.8.0", 53 | "@types/pako": "^2.0.3", 54 | "@types/semver": "^7.5.8", 55 | "@vitest/coverage-v8": "^4.0.15", 56 | "eslint": "^8.54.0", 57 | "typescript": "^5.9.2", 58 | "vitest": "^4.0.15" 59 | }, 60 | "gitHead": "bf02a4b641d5d03c035d052247a545109c17b708" 61 | } 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lerna-debug.log 2 | npm-debug.log 3 | **/dist/ 4 | **/node_modules/ 5 | 6 | **/.DS_Store 7 | /**/.project 8 | /**/.classpath 9 | /**/.settings/ 10 | /**/target 11 | /**/bin 12 | 13 | # Visual Studio Generated 14 | .antlr 15 | .fetch_time_cache 16 | /.vs 17 | .vscode/ 18 | .gradle/ 19 | 20 | #IntelliJ Idea project files 21 | .idea/ 22 | *.iml 23 | /**/gen 24 | 25 | # Logs 26 | logs 27 | *.log 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | lerna-debug.log* 32 | 33 | # Diagnostic reports (https://nodejs.org/api/report.html) 34 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 35 | 36 | # Runtime data 37 | pids 38 | *.pid 39 | *.seed 40 | *.pid.lock 41 | 42 | # Directory for instrumented libs generated by jscoverage/JSCover 43 | lib-cov 44 | 45 | # Coverage directory used by tools like istanbul 46 | coverage 47 | .nyc_output 48 | 49 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 50 | .grunt 51 | 52 | # Bower dependency directory (https://bower.io/) 53 | bower_components 54 | 55 | # node-waf configuration 56 | .lock-wscript 57 | 58 | # Compiled binary addons (https://nodejs.org/api/addons.html) 59 | build/Release 60 | 61 | # Dependency directories 62 | node_modules/ 63 | jspm_packages/ 64 | 65 | # TypeScript v1 declaration files 66 | typings/ 67 | 68 | # Optional npm cache directory 69 | .npm 70 | 71 | # Optional eslint cache 72 | .eslintcache 73 | 74 | # Optional REPL history 75 | .node_repl_history 76 | 77 | # Output of 'npm pack' 78 | *.tgz 79 | 80 | # Yarn Integrity file 81 | .yarn-integrity 82 | 83 | # dotenv environment variables file 84 | .env 85 | .env.test 86 | 87 | # parcel-bundler cache (https://parceljs.org/) 88 | .cache 89 | 90 | # next.js build output 91 | .next 92 | 93 | # nuxt.js build output 94 | .nuxt 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | manual-test.ts 109 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/transfer_with_timeout.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'TransferWithTimeout', 3 | constructorInputs: [ 4 | { 5 | name: 'sender', 6 | type: 'pubkey', 7 | }, 8 | { 9 | name: 'recipient', 10 | type: 'pubkey', 11 | }, 12 | { 13 | name: 'timeout', 14 | type: 'int', 15 | }, 16 | ], 17 | abi: [ 18 | { 19 | name: 'transfer', 20 | inputs: [ 21 | { 22 | name: 'recipientSig', 23 | type: 'sig', 24 | }, 25 | ], 26 | }, 27 | { 28 | name: 'timeout', 29 | inputs: [ 30 | { 31 | name: 'senderSig', 32 | type: 'sig', 33 | }, 34 | ], 35 | }, 36 | ], 37 | bytecode: 'OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_4 OP_ROLL OP_ROT OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_1 OP_NUMEQUALVERIFY OP_3 OP_ROLL OP_SWAP OP_CHECKSIGVERIFY OP_SWAP OP_CHECKLOCKTIMEVERIFY OP_2DROP OP_1 OP_ENDIF', 38 | source: 'contract TransferWithTimeout(\n pubkey sender,\n pubkey recipient,\n int timeout\n) {\n // Require recipient\'s signature to match\n function transfer(sig recipientSig) {\n require(checkSig(recipientSig, recipient));\n }\n\n // Require timeout time to be reached and sender\'s signature to match\n function timeout(sig senderSig) {\n require(checkSig(senderSig, sender));\n require(tx.time >= timeout);\n }\n}\n', 39 | debug: { 40 | bytecode: '5379009c63547a7bac77777767537a519d537a7cad7cb16d5168', 41 | sourceMap: '7:4:9:5;;;;;8:25:8:37;;:39::48;:8::51:1;7:4:9:5;;;;12::15::0;;;;13:25:13:34;;:36::42;:8::45:1;14:27:14:34:0;:8::36:1;12:4:15:5;;1:0:16:1', 42 | logs: [], 43 | requires: [ 44 | { 45 | ip: 12, 46 | line: 8, 47 | }, 48 | { 49 | ip: 23, 50 | line: 13, 51 | }, 52 | { 53 | ip: 25, 54 | line: 14, 55 | }, 56 | ], 57 | }, 58 | compiler: { 59 | name: 'cashc', 60 | version: '0.11.0', 61 | }, 62 | updatedAt: '2025-06-16T15:05:56.888Z', 63 | } as const; 64 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/transfer_with_timeout.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "TransferWithTimeout", 3 | "constructorInputs": [ 4 | { 5 | "name": "sender", 6 | "type": "pubkey" 7 | }, 8 | { 9 | "name": "recipient", 10 | "type": "pubkey" 11 | }, 12 | { 13 | "name": "timeout", 14 | "type": "int" 15 | } 16 | ], 17 | "abi": [ 18 | { 19 | "name": "transfer", 20 | "inputs": [ 21 | { 22 | "name": "recipientSig", 23 | "type": "sig" 24 | } 25 | ] 26 | }, 27 | { 28 | "name": "timeout", 29 | "inputs": [ 30 | { 31 | "name": "senderSig", 32 | "type": "sig" 33 | } 34 | ] 35 | } 36 | ], 37 | "bytecode": "OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_4 OP_ROLL OP_ROT OP_CHECKSIG OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_1 OP_NUMEQUALVERIFY OP_3 OP_ROLL OP_SWAP OP_CHECKSIGVERIFY OP_SWAP OP_CHECKLOCKTIMEVERIFY OP_2DROP OP_1 OP_ENDIF", 38 | "source": "contract TransferWithTimeout(\n pubkey sender,\n pubkey recipient,\n int timeout\n) {\n // Require recipient's signature to match\n function transfer(sig recipientSig) {\n require(checkSig(recipientSig, recipient));\n }\n\n // Require timeout time to be reached and sender's signature to match\n function timeout(sig senderSig) {\n require(checkSig(senderSig, sender));\n require(tx.time >= timeout);\n }\n}\n", 39 | "debug": { 40 | "bytecode": "5379009c63547a7bac77777767537a519d537a7cad7cb16d5168", 41 | "sourceMap": "7:4:9:5;;;;;8:25:8:37;;:39::48;:8::51:1;7:4:9:5;;;;12::15::0;;;;13:25:13:34;;:36::42;:8::45:1;14:27:14:34:0;:8::36:1;12:4:15:5;;1:0:16:1", 42 | "logs": [], 43 | "requires": [ 44 | { 45 | "ip": 12, 46 | "line": 8 47 | }, 48 | { 49 | "ip": 23, 50 | "line": 13 51 | }, 52 | { 53 | "ip": 25, 54 | "line": 14 55 | } 56 | ] 57 | }, 58 | "compiler": { 59 | "name": "cashc", 60 | "version": "0.11.0" 61 | }, 62 | "updatedAt": "2025-06-16T15:05:56.646Z" 63 | } 64 | -------------------------------------------------------------------------------- /website/sidebars.ts: -------------------------------------------------------------------------------- 1 | import type { SidebarsConfig } from '@docusaurus/plugin-content-docs'; 2 | 3 | const sidebars: SidebarsConfig = { 4 | docs: [ 5 | { 6 | type: 'category', 7 | label: 'Basics', 8 | items: [ 9 | 'basics/about', 10 | 'basics/about-bch', 11 | 'basics/getting-started', 12 | ], 13 | }, 14 | { 15 | type: 'category', 16 | label: 'Language Description', 17 | items: [ 18 | 'language/contracts', 19 | 'language/types', 20 | 'language/functions', 21 | 'language/globals', 22 | 'language/examples', 23 | 'language/syntax-highlighting' 24 | ], 25 | }, 26 | { 27 | type: 'category', 28 | label: 'Compiler', 29 | items: [ 30 | 'compiler/compiler', 31 | 'compiler/script-limits', 32 | 'compiler/artifacts', 33 | 'compiler/grammar', 34 | ], 35 | }, 36 | { 37 | type: 'category', 38 | label: 'TypeScript SDK', 39 | items: [ 40 | 'sdk/instantiation', 41 | 'sdk/transaction-builder', 42 | { 43 | type: 'category', 44 | label: 'Network Providers', 45 | items: [ 46 | 'sdk/network-provider', 47 | 'sdk/electrum-network-provider', 48 | 'sdk/other-network-providers', 49 | ], 50 | }, 51 | 'sdk/signature-templates', 52 | 'sdk/testing-setup', 53 | 'sdk/examples', 54 | ], 55 | }, 56 | { 57 | type: 'category', 58 | label: 'Guides', 59 | items: [ 60 | 'guides/covenants', 61 | 'guides/lifecycle', 62 | 'guides/cashtokens', 63 | 'guides/infrastructure', 64 | 'guides/walletconnect', 65 | 'guides/debugging', 66 | 'guides/optimization', 67 | 'guides/adversarial', 68 | ], 69 | }, 70 | { 71 | type: 'category', 72 | label: 'Releases', 73 | items: [ 74 | 'releases/release-notes', 75 | 'releases/migration-notes', 76 | ], 77 | }, 78 | 'showcase', 79 | ], 80 | }; 81 | 82 | export default sidebars; 83 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/Bar.artifact.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | contractName: 'Bar', 3 | constructorInputs: [ 4 | { 5 | name: 'pkh_bar', 6 | type: 'bytes20', 7 | }, 8 | ], 9 | abi: [ 10 | { 11 | name: 'funcA', 12 | inputs: [], 13 | }, 14 | { 15 | name: 'funcB', 16 | inputs: [], 17 | }, 18 | { 19 | name: 'execute', 20 | inputs: [ 21 | { 22 | name: 'pk', 23 | type: 'pubkey', 24 | }, 25 | { 26 | name: 's', 27 | type: 'sig', 28 | }, 29 | ], 30 | }, 31 | ], 32 | bytecode: 'OP_OVER OP_0 OP_NUMEQUAL OP_IF OP_2 OP_2 OP_NUMEQUAL OP_NIP OP_NIP OP_ELSE OP_OVER OP_1 OP_NUMEQUAL OP_IF OP_2 OP_2 OP_NUMEQUAL OP_NIP OP_NIP OP_ELSE OP_SWAP OP_2 OP_NUMEQUALVERIFY OP_OVER OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF OP_ENDIF', 33 | source: 'pragma cashscript >=0.10.2;\n\ncontract Bar(bytes20 pkh_bar) {\n function funcA() {\n require(2==2);\n }\n\n function funcB() {\n require(2==2);\n }\n\n function execute(pubkey pk, sig s) {\n console.log("Bar \'execute\' function called.");\n require(hash160(pk) == pkh_bar);\n require(checkSig(s, pk));\n }\n}\n', 34 | debug: { 35 | bytecode: '78009c6352529c77776778519c6352529c7777677c529d78a988ac6868', 36 | sourceMap: '4:4:6:5;;;;5:16:5:17;:19::20;:8::22:1;4:4:6:5;;;8::10::0;;;;9:16:9:17;:19::20;:8::22:1;8:4:10:5;;;12::16::0;;;14:24:14:26;:16::27:1;:8::40;15::15:33;3:0:17:1;', 37 | logs: [ 38 | { 39 | ip: 24, 40 | line: 13, 41 | data: [ 42 | 'Bar \'execute\' function called.', 43 | ], 44 | }, 45 | ], 46 | requires: [ 47 | { 48 | ip: 8, 49 | line: 5, 50 | }, 51 | { 52 | ip: 18, 53 | line: 9, 54 | }, 55 | { 56 | ip: 26, 57 | line: 14, 58 | }, 59 | { 60 | ip: 28, 61 | line: 15, 62 | }, 63 | ], 64 | }, 65 | compiler: { 66 | name: 'cashc', 67 | version: '0.11.0', 68 | }, 69 | updatedAt: '2025-06-16T15:05:54.204Z', 70 | } as const; 71 | -------------------------------------------------------------------------------- /packages/cashc/src/grammar/CashScript.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | T__10=11 12 | T__11=12 13 | T__12=13 14 | T__13=14 15 | T__14=15 16 | T__15=16 17 | T__16=17 18 | T__17=18 19 | T__18=19 20 | T__19=20 21 | T__20=21 22 | T__21=22 23 | T__22=23 24 | T__23=24 25 | T__24=25 26 | T__25=26 27 | T__26=27 28 | T__27=28 29 | T__28=29 30 | T__29=30 31 | T__30=31 32 | T__31=32 33 | T__32=33 34 | T__33=34 35 | T__34=35 36 | T__35=36 37 | T__36=37 38 | T__37=38 39 | T__38=39 40 | T__39=40 41 | T__40=41 42 | T__41=42 43 | T__42=43 44 | T__43=44 45 | T__44=45 46 | T__45=46 47 | T__46=47 48 | T__47=48 49 | T__48=49 50 | T__49=50 51 | T__50=51 52 | T__51=52 53 | T__52=53 54 | T__53=54 55 | T__54=55 56 | T__55=56 57 | T__56=57 58 | T__57=58 59 | VersionLiteral=59 60 | BooleanLiteral=60 61 | NumberUnit=61 62 | NumberLiteral=62 63 | NumberPart=63 64 | ExponentPart=64 65 | Bytes=65 66 | Bound=66 67 | StringLiteral=67 68 | DateLiteral=68 69 | HexLiteral=69 70 | TxVar=70 71 | NullaryOp=71 72 | Identifier=72 73 | WHITESPACE=73 74 | COMMENT=74 75 | LINE_COMMENT=75 76 | 'pragma'=1 77 | ';'=2 78 | 'cashscript'=3 79 | '^'=4 80 | '~'=5 81 | '>='=6 82 | '>'=7 83 | '<'=8 84 | '<='=9 85 | '='=10 86 | 'contract'=11 87 | '{'=12 88 | '}'=13 89 | 'function'=14 90 | '('=15 91 | ','=16 92 | ')'=17 93 | 'require'=18 94 | 'if'=19 95 | 'else'=20 96 | 'console.log'=21 97 | 'new'=22 98 | '['=23 99 | ']'=24 100 | 'tx.outputs'=25 101 | '.value'=26 102 | '.lockingBytecode'=27 103 | '.tokenCategory'=28 104 | '.nftCommitment'=29 105 | '.tokenAmount'=30 106 | 'tx.inputs'=31 107 | '.outpointTransactionHash'=32 108 | '.outpointIndex'=33 109 | '.unlockingBytecode'=34 110 | '.sequenceNumber'=35 111 | '.reverse()'=36 112 | '.length'=37 113 | '.split'=38 114 | '.slice'=39 115 | '!'=40 116 | '-'=41 117 | '*'=42 118 | '/'=43 119 | '%'=44 120 | '+'=45 121 | '=='=46 122 | '!='=47 123 | '&'=48 124 | '|'=49 125 | '&&'=50 126 | '||'=51 127 | 'constant'=52 128 | 'int'=53 129 | 'bool'=54 130 | 'string'=55 131 | 'pubkey'=56 132 | 'sig'=57 133 | 'datasig'=58 134 | -------------------------------------------------------------------------------- /packages/utils/src/bitauth-script.ts: -------------------------------------------------------------------------------- 1 | import { Script, scriptToBitAuthAsm } from './script.js'; 2 | import { sourceMapToLocationData } from './source-map.js'; 3 | import { PositionHint } from './types.js'; 4 | 5 | export type LineToOpcodesMap = Record; 6 | export type LineToAsmMap = Record; 7 | 8 | export function buildLineToOpcodesMap(bytecode: Script, sourceMap: string): LineToOpcodesMap { 9 | const locationData = sourceMapToLocationData(sourceMap); 10 | 11 | return locationData.reduce((lineToOpcodeMap, { location, positionHint }, index) => { 12 | const opcode = bytecode[index]; 13 | const line = positionHint === PositionHint.END ? location?.end.line : location?.start.line; 14 | 15 | return { 16 | ...lineToOpcodeMap, 17 | [line]: [...(lineToOpcodeMap[line] || []), opcode], 18 | }; 19 | }, {}); 20 | } 21 | 22 | export function buildLineToAsmMap(bytecode: Script, sourceMap: string): LineToAsmMap { 23 | const lineToOpcodesMap = buildLineToOpcodesMap(bytecode, sourceMap); 24 | 25 | return Object.fromEntries( 26 | Object.entries(lineToOpcodesMap).map(([lineNumber, opcodeList]) => [lineNumber, scriptToBitAuthAsm(opcodeList)]), 27 | ); 28 | } 29 | 30 | export function formatBitAuthScript(bytecode: Script, sourceMap: string, sourceCode: string): string { 31 | const lineToAsmMap = buildLineToAsmMap(bytecode, sourceMap); 32 | 33 | const escapedSourceCode = sourceCode.replaceAll('/*', '\\/*').replaceAll('*/', '*\\/'); 34 | const sourceCodeLines = escapedSourceCode.split('\n'); 35 | 36 | const sourceCodeLineLengths = sourceCodeLines.map((line) => line.length); 37 | const bytecodeLineLengths = Object.values(lineToAsmMap).map((line) => line.length); 38 | 39 | const maxSourceCodeLength = Math.max(...sourceCodeLineLengths); 40 | const maxBytecodeLength = Math.max(...bytecodeLineLengths); 41 | 42 | const annotatedAsmLines = sourceCodeLines.map((line, index) => { 43 | const lineAsm = lineToAsmMap[index + 1]; 44 | return `${(lineAsm || '').padEnd(maxBytecodeLength)} /* ${line.padEnd(maxSourceCodeLength)} */`; 45 | }); 46 | 47 | return annotatedAsmLines.join('\n'); 48 | } 49 | -------------------------------------------------------------------------------- /packages/cashc/src/grammar/CashScriptLexer.tokens: -------------------------------------------------------------------------------- 1 | T__0=1 2 | T__1=2 3 | T__2=3 4 | T__3=4 5 | T__4=5 6 | T__5=6 7 | T__6=7 8 | T__7=8 9 | T__8=9 10 | T__9=10 11 | T__10=11 12 | T__11=12 13 | T__12=13 14 | T__13=14 15 | T__14=15 16 | T__15=16 17 | T__16=17 18 | T__17=18 19 | T__18=19 20 | T__19=20 21 | T__20=21 22 | T__21=22 23 | T__22=23 24 | T__23=24 25 | T__24=25 26 | T__25=26 27 | T__26=27 28 | T__27=28 29 | T__28=29 30 | T__29=30 31 | T__30=31 32 | T__31=32 33 | T__32=33 34 | T__33=34 35 | T__34=35 36 | T__35=36 37 | T__36=37 38 | T__37=38 39 | T__38=39 40 | T__39=40 41 | T__40=41 42 | T__41=42 43 | T__42=43 44 | T__43=44 45 | T__44=45 46 | T__45=46 47 | T__46=47 48 | T__47=48 49 | T__48=49 50 | T__49=50 51 | T__50=51 52 | T__51=52 53 | T__52=53 54 | T__53=54 55 | T__54=55 56 | T__55=56 57 | T__56=57 58 | T__57=58 59 | VersionLiteral=59 60 | BooleanLiteral=60 61 | NumberUnit=61 62 | NumberLiteral=62 63 | NumberPart=63 64 | ExponentPart=64 65 | Bytes=65 66 | Bound=66 67 | StringLiteral=67 68 | DateLiteral=68 69 | HexLiteral=69 70 | TxVar=70 71 | NullaryOp=71 72 | Identifier=72 73 | WHITESPACE=73 74 | COMMENT=74 75 | LINE_COMMENT=75 76 | 'pragma'=1 77 | ';'=2 78 | 'cashscript'=3 79 | '^'=4 80 | '~'=5 81 | '>='=6 82 | '>'=7 83 | '<'=8 84 | '<='=9 85 | '='=10 86 | 'contract'=11 87 | '{'=12 88 | '}'=13 89 | 'function'=14 90 | '('=15 91 | ','=16 92 | ')'=17 93 | 'require'=18 94 | 'if'=19 95 | 'else'=20 96 | 'console.log'=21 97 | 'new'=22 98 | '['=23 99 | ']'=24 100 | 'tx.outputs'=25 101 | '.value'=26 102 | '.lockingBytecode'=27 103 | '.tokenCategory'=28 104 | '.nftCommitment'=29 105 | '.tokenAmount'=30 106 | 'tx.inputs'=31 107 | '.outpointTransactionHash'=32 108 | '.outpointIndex'=33 109 | '.unlockingBytecode'=34 110 | '.sequenceNumber'=35 111 | '.reverse()'=36 112 | '.length'=37 113 | '.split'=38 114 | '.slice'=39 115 | '!'=40 116 | '-'=41 117 | '*'=42 118 | '/'=43 119 | '%'=44 120 | '+'=45 121 | '=='=46 122 | '!='=47 123 | '&'=48 124 | '|'=49 125 | '&&'=50 126 | '||'=51 127 | 'constant'=52 128 | 'int'=53 129 | 'bool'=54 130 | 'string'=55 131 | 'pubkey'=56 132 | 'sig'=57 133 | 'datasig'=58 134 | -------------------------------------------------------------------------------- /packages/cashscript/test/fixture/Bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "Bar", 3 | "constructorInputs": [ 4 | { 5 | "name": "pkh_bar", 6 | "type": "bytes20" 7 | } 8 | ], 9 | "abi": [ 10 | { 11 | "name": "funcA", 12 | "inputs": [] 13 | }, 14 | { 15 | "name": "funcB", 16 | "inputs": [] 17 | }, 18 | { 19 | "name": "execute", 20 | "inputs": [ 21 | { 22 | "name": "pk", 23 | "type": "pubkey" 24 | }, 25 | { 26 | "name": "s", 27 | "type": "sig" 28 | } 29 | ] 30 | } 31 | ], 32 | "bytecode": "OP_OVER OP_0 OP_NUMEQUAL OP_IF OP_2 OP_2 OP_NUMEQUAL OP_NIP OP_NIP OP_ELSE OP_OVER OP_1 OP_NUMEQUAL OP_IF OP_2 OP_2 OP_NUMEQUAL OP_NIP OP_NIP OP_ELSE OP_SWAP OP_2 OP_NUMEQUALVERIFY OP_OVER OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF OP_ENDIF", 33 | "source": "pragma cashscript >=0.10.2;\n\ncontract Bar(bytes20 pkh_bar) {\n function funcA() {\n require(2==2);\n }\n\n function funcB() {\n require(2==2);\n }\n\n function execute(pubkey pk, sig s) {\n console.log(\"Bar 'execute' function called.\");\n require(hash160(pk) == pkh_bar);\n require(checkSig(s, pk));\n }\n}\n", 34 | "debug": { 35 | "bytecode": "78009c6352529c77776778519c6352529c7777677c529d78a988ac6868", 36 | "sourceMap": "4:4:6:5;;;;5:16:5:17;:19::20;:8::22:1;4:4:6:5;;;8::10::0;;;;9:16:9:17;:19::20;:8::22:1;8:4:10:5;;;12::16::0;;;14:24:14:26;:16::27:1;:8::40;15::15:33;3:0:17:1;", 37 | "logs": [ 38 | { 39 | "ip": 24, 40 | "line": 13, 41 | "data": [ 42 | "Bar 'execute' function called." 43 | ] 44 | } 45 | ], 46 | "requires": [ 47 | { 48 | "ip": 8, 49 | "line": 5 50 | }, 51 | { 52 | "ip": 18, 53 | "line": 9 54 | }, 55 | { 56 | "ip": 26, 57 | "line": 14 58 | }, 59 | { 60 | "ip": 28, 61 | "line": 15 62 | } 63 | ] 64 | }, 65 | "compiler": { 66 | "name": "cashc", 67 | "version": "0.11.0" 68 | }, 69 | "updatedAt": "2025-06-16T15:05:53.940Z" 70 | } 71 | -------------------------------------------------------------------------------- /packages/cashc/src/semantic/EnsureFinalRequireTraversal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ContractNode, 3 | ParameterNode, 4 | FunctionDefinitionNode, 5 | RequireNode, 6 | StatementNode, 7 | TimeOpNode, 8 | BranchNode, 9 | ConsoleStatementNode, 10 | } from '../ast/AST.js'; 11 | import AstTraversal from '../ast/AstTraversal.js'; 12 | import { EmptyContractError, EmptyFunctionError, FinalRequireStatementError } from '../Errors.js'; 13 | 14 | export default class EnsureFinalRequireTraversal extends AstTraversal { 15 | visitContract(node: ContractNode): ContractNode { 16 | node.parameters = this.visitList(node.parameters) as ParameterNode[]; 17 | node.functions = this.visitList(node.functions) as FunctionDefinitionNode[]; 18 | 19 | if (node.functions.length === 0) { 20 | throw new EmptyContractError(node); 21 | } 22 | 23 | return node; 24 | } 25 | 26 | visitFunctionDefinition(node: FunctionDefinitionNode): FunctionDefinitionNode { 27 | node.parameters = this.visitList(node.parameters) as ParameterNode[]; 28 | node.body = this.visit(node.body); 29 | 30 | if (node.body.statements === undefined || node.body.statements.length === 0) { 31 | throw new EmptyFunctionError(node); 32 | } 33 | 34 | ensureFinalStatementIsRequire(node.body.statements); 35 | 36 | return node; 37 | } 38 | } 39 | 40 | function ensureFinalStatementIsRequire(statements: StatementNode[] = []): void { 41 | const statementsWithoutLogs = statements.filter((statement) => !(statement instanceof ConsoleStatementNode)); 42 | const finalStatement = statementsWithoutLogs[statements.length - 1]; 43 | 44 | if (!finalStatement) return; 45 | 46 | // If the final statement is a branch node, then both branches need to end with a require() 47 | if (finalStatement instanceof BranchNode) { 48 | ensureFinalStatementIsRequire(finalStatement.ifBlock.statements); 49 | ensureFinalStatementIsRequire(finalStatement.elseBlock?.statements); 50 | return; 51 | } 52 | 53 | // The final statement needs to be a require() 54 | if (!(finalStatement instanceof RequireNode || finalStatement instanceof TimeOpNode)) { 55 | throw new FinalRequireStatementError(finalStatement); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/cashc/src/ast/SymbolTable.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@cashscript/utils'; 2 | import { 3 | VariableDefinitionNode, 4 | ParameterNode, 5 | IdentifierNode, 6 | Node, 7 | } from './AST.js'; 8 | 9 | export class Symbol { 10 | references: IdentifierNode[] = []; 11 | private constructor( 12 | public name: string, 13 | public type: Type, 14 | public symbolType: SymbolType, 15 | public definition?: Node, 16 | public parameters?: Type[], 17 | ) {} 18 | 19 | static variable(node: VariableDefinitionNode | ParameterNode): Symbol { 20 | return new Symbol(node.name, node.type, SymbolType.VARIABLE, node); 21 | } 22 | 23 | static global(name: string, type: Type): Symbol { 24 | return new Symbol(name, type, SymbolType.VARIABLE); 25 | } 26 | 27 | static function(name: string, type: Type, parameters: Type[]): Symbol { 28 | return new Symbol(name, type, SymbolType.FUNCTION, undefined, parameters); 29 | } 30 | 31 | static class(name: string, type: Type, parameters: Type[]): Symbol { 32 | return new Symbol(name, type, SymbolType.CLASS, undefined, parameters); 33 | } 34 | 35 | toString(): string { 36 | let str = `${this.type} ${this.name}`; 37 | if (this.parameters) { 38 | str += ` (${this.parameters})`; 39 | } 40 | return str; 41 | } 42 | } 43 | 44 | export enum SymbolType { 45 | VARIABLE = 'variable', 46 | FUNCTION = 'function', 47 | CLASS = 'class', 48 | } 49 | 50 | export class SymbolTable { 51 | symbols: Map = new Map(); 52 | 53 | constructor( 54 | public parent?: SymbolTable, 55 | ) {} 56 | 57 | set(symbol: Symbol): void { 58 | this.symbols.set(symbol.name, symbol); 59 | } 60 | 61 | get(name: string): Symbol | undefined { 62 | return this.symbols.get(name) ?? this.parent?.get(name); 63 | } 64 | 65 | getFromThis(name: string): Symbol | undefined { 66 | return this.symbols.get(name); 67 | } 68 | 69 | toString(): string { 70 | return `[${Array.from(this.symbols).map((e) => e[1])}]`; 71 | } 72 | 73 | unusedSymbols(): Symbol[] { 74 | return Array.from(this.symbols) 75 | .map((e) => e[1]) 76 | .filter((s) => s.references.length === 0); 77 | } 78 | } 79 | --------------------------------------------------------------------------------