From e2a2808dba879de604000c7b7f4c79b34bdaf232 Mon Sep 17 00:00:00 2001 From: sudoshreyansh Date: Sat, 27 Apr 2024 00:34:38 +0530 Subject: [PATCH 01/49] update management canister interface --- canisters/management/canister_management.ts | 38 +++++++++++++++++++++ canisters/management/index.ts | 12 ++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/canisters/management/canister_management.ts b/canisters/management/canister_management.ts index c1c2b8e5e9..2a925e82bf 100644 --- a/canisters/management/canister_management.ts +++ b/canisters/management/canister_management.ts @@ -35,6 +35,11 @@ export const CanisterSettings = Record({ }); export type CanisterSettings = typeof CanisterSettings.tsType; +export const ChunkHash = Record({ + hash: blob +}); +export type ChunkHash = typeof ChunkHash.tsType; + /** * The arguments to provide to the management canister's create_canister * method @@ -89,6 +94,28 @@ export const UpdateSettingsArgs = Record({ }); export type UpdateSettingsArgs = typeof UpdateSettingsArgs.tsType; +export const UploadChunkArgs = Record({ + canister_id: CanisterId, + chunk: blob +}); +export type UploadChunkArgs = typeof UploadChunkArgs.tsType; + +export const UploadChunkResult = ChunkHash; +export type UploadChunkResult = typeof UploadChunkResult.tsType; + +export const ClearChunkStoreArgs = Record({ + canister_id: CanisterId +}); +export type ClearChunkStoreArgs = typeof ClearChunkStoreArgs.tsType; + +export const StoredChunksArgs = Record({ + canister_id: CanisterId +}); +export type StoredChunksArgs = typeof StoredChunksArgs.tsType; + +export const StoredChunksResult = Vec(ChunkHash); +export type StoredChunksResult = typeof StoredChunksResult.tsType; + export const InstallCodeMode = Variant({ install: Null, reinstall: Null, @@ -105,6 +132,17 @@ export const InstallCodeArgs = Record({ }); export type InstallCodeArgs = typeof InstallCodeArgs.tsType; +export const InstallChunkedCodeArgs = Record({ + mode: InstallCodeMode, + target_canister: CanisterId, + storage_canister: Opt(CanisterId), + chunk_hashes_list: Vec(blob), + wasm_module_hash: blob, + arg: blob, + sender_canister_version: Opt(nat64) +}); +export type InstallChunkedCodeArgs = typeof InstallChunkedCodeArgs.tsType; + export const UninstallCodeArgs = Record({ canister_id: CanisterId, sender_canister_version: Opt(nat64) diff --git a/canisters/management/index.ts b/canisters/management/index.ts index 4251283cda..20a747d244 100644 --- a/canisters/management/index.ts +++ b/canisters/management/index.ts @@ -18,18 +18,24 @@ import { CanisterInfoArgs, CanisterInfoResult } from './canister_info'; import { CanisterStatusArgs, CanisterStatusResult, + ClearChunkStoreArgs, CreateCanisterArgs, CreateCanisterResult, DeleteCanisterArgs, DepositCyclesArgs, + InstallChunkedCodeArgs, InstallCodeArgs, ProvisionalCreateCanisterWithCyclesArgs, ProvisionalCreateCanisterWithCyclesResult, ProvisionalTopUpCanisterArgs, StartCanisterArgs, StopCanisterArgs, + StoredChunksArgs, + StoredChunksResult, UninstallCodeArgs, - UpdateSettingsArgs + UpdateSettingsArgs, + UploadChunkArgs, + UploadChunkResult } from './canister_management'; import { HttpRequestArgs, HttpResponse } from './http_request'; import { @@ -57,7 +63,11 @@ export const managementCanister = Canister({ // canister management create_canister: update([CreateCanisterArgs], CreateCanisterResult), update_settings: update([UpdateSettingsArgs], Void), + upload_chunk: update([UploadChunkArgs], UploadChunkResult), + clear_chunk_store: update([ClearChunkStoreArgs], Void), + stored_chunks: update([StoredChunksArgs], StoredChunksResult), install_code: update([InstallCodeArgs], Void), + install_chunked_code: update([InstallChunkedCodeArgs], Void), uninstall_code: update([UninstallCodeArgs], Void), start_canister: update([StartCanisterArgs], Void), stop_canister: update([StopCanisterArgs], Void), From e5602a5142d5fffe83145eed08fb47a42f2c9968 Mon Sep 17 00:00:00 2001 From: sudoshreyansh Date: Sat, 27 Apr 2024 01:24:56 +0530 Subject: [PATCH 02/49] update interface according to updated spec --- canisters/management/canister_management.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canisters/management/canister_management.ts b/canisters/management/canister_management.ts index 2a925e82bf..da6c20b966 100644 --- a/canisters/management/canister_management.ts +++ b/canisters/management/canister_management.ts @@ -135,7 +135,7 @@ export type InstallCodeArgs = typeof InstallCodeArgs.tsType; export const InstallChunkedCodeArgs = Record({ mode: InstallCodeMode, target_canister: CanisterId, - storage_canister: Opt(CanisterId), + store_canister: Opt(CanisterId), chunk_hashes_list: Vec(blob), wasm_module_hash: blob, arg: blob, From ae1f033bfbf75be77665c7862a8302a81d0c2000 Mon Sep 17 00:00:00 2001 From: sudoshreyansh Date: Sat, 27 Apr 2024 01:37:59 +0530 Subject: [PATCH 03/49] add examples and tests --- .github/workflows/test.yml | 132 +----- canisters/management/canister_management.ts | 2 +- canisters/management/ic.did | 433 +++++++++++++------- examples/management_canister/src/index.ts | 130 +++++- examples/management_canister/test/tests.ts | 126 +++++- 5 files changed, 509 insertions(+), 314 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 27d2c1b7e3..c49aa37349 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,129 +71,7 @@ jobs: run: | EXAMPLE_DIRECTORIES=$(cat << END [ - "examples/apollo_server", - "examples/async_await", - "examples/audio_and_video", - "examples/audio_recorder", - "examples/autoreload", - "examples/bitcoin", - "examples/bitcoinjs-lib", - "examples/bitcore-lib", - "examples/blob_array", - "examples/bytes", - "examples/call_raw", - "examples/candid_encoding", - "examples/candid_keywords", - "examples/canister", - "examples/ckbtc", - "examples/complex_init", - "examples/complex_types", - "examples/composite_queries", - "examples/counter", - "examples/cross_canister_calls", - "examples/cycles", - "examples/date", - "examples/ethereum_json_rpc", - "examples/ethers", - "examples/ethers_base", - "examples/express", - "examples/fetch_ic", - "examples/file_protocol", - "examples/fs", - "examples/func_types", - "examples/guard_functions", - "examples/heartbeat", - "examples/hello_world", - "examples/http_outcall_fetch", - "examples/hybrid_canister", - "examples/ic_api", - "examples/ic_evm_rpc", - "examples/icrc", - "examples/imports", - "examples/init", - "examples/inspect_message", - "examples/internet_identity", - "examples/key_value_store", - "examples/large_files", - "examples/ledger_canister", - "examples/list_of_lists", - "examples/management_canister", - "examples/manual_reply", - "examples/motoko_examples/calc", - "examples/motoko_examples/counter", - "examples/motoko_examples/echo", - "examples/motoko_examples/factorial", - "examples/motoko_examples/hello", - "examples/motoko_examples/hello-world", - "examples/motoko_examples/http_counter", - "examples/motoko_examples/minimal-counter-dapp", - "examples/motoko_examples/persistent-storage", - "examples/motoko_examples/phone-book", - "examples/motoko_examples/quicksort", - "examples/motoko_examples/simple-to-do", - "examples/motoko_examples/superheroes", - "examples/motoko_examples/threshold_ecdsa", - "examples/motoko_examples/whoami", - "examples/new", - "examples/notify_raw", - "examples/null_example", - "examples/optional_types", - "examples/outgoing_http_requests", - "examples/pre_and_post_upgrade", - "examples/primitive_types", - "examples/principal", - "examples/query", - "examples/randomness", - "examples/recursion", - "examples/rejections", - "examples/robust_imports", - "examples/simple_erc20", - "examples/simple_user_accounts", - "examples/sqlite", - "examples/stable_b_tree_map_instruction_threshold", - "examples/stable_memory", - "examples/stable_structures", - "examples/tfjs", - "examples/timers", - "examples/tuple_types", - "examples/update", - "examples/vanilla_js", - "examples/web_assembly", - "property_tests/tests/blob", - "property_tests/tests/bool", - "property_tests/tests/canister_methods/http_request", - "property_tests/tests/canister_methods/http_request_update", - "property_tests/tests/canister_methods/init", - "property_tests/tests/canister_methods/inspect_message", - "property_tests/tests/canister_methods/post_upgrade", - "property_tests/tests/canister_methods/pre_upgrade", - "property_tests/tests/canister_methods/query", - "property_tests/tests/canister_methods/update", - "property_tests/tests/float32", - "property_tests/tests/float64", - "property_tests/tests/func", - "property_tests/tests/int", - "property_tests/tests/int8", - "property_tests/tests/int16", - "property_tests/tests/int32", - "property_tests/tests/int64", - "property_tests/tests/nat", - "property_tests/tests/nat8", - "property_tests/tests/nat16", - "property_tests/tests/nat32", - "property_tests/tests/nat64", - "property_tests/tests/null", - "property_tests/tests/opt", - "property_tests/tests/principal", - "property_tests/tests/record", - "property_tests/tests/recursive", - "property_tests/tests/service", - "property_tests/tests/stable_b_tree_map", - "property_tests/tests/text", - "property_tests/tests/tuple", - "property_tests/tests/variant", - "property_tests/tests/vec" - ] + "examples/management_canister" ] END ) EXAMPLE_DIRECTORIES="${EXAMPLE_DIRECTORIES//'%'/'%25'}" @@ -317,14 +195,6 @@ jobs: shell: bash -l {0} working-directory: ${{ matrix.example_directories }} run: AZLE_PROPTEST_NUM_RUNS=5 AZLE_PROPTEST_VERBOSE=true npm test - - if: ${{ needs.release-candidate-deploy.outputs.should_run_tests && contains(github.head_ref, 'release--') && !(github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'demergent-labs/release--')) }} - shell: bash -l {0} - working-directory: ${{ matrix.example_directories }} - run: AZLE_PROPTEST_NUM_RUNS=10 AZLE_PROPTEST_VERBOSE=true npm test - - if: ${{ needs.release-candidate-deploy.outputs.should_run_tests && (github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'demergent-labs/release--')) }} - shell: bash -l {0} - working-directory: ${{ matrix.example_directories }} - run: AZLE_PROPTEST_NUM_RUNS=100 AZLE_PROPTEST_VERBOSE=true npm test - if: ${{ needs.release-candidate-deploy.outputs.should_run_tests && matrix.example_directories != 'examples/new' }} shell: bash -l {0} working-directory: ${{ matrix.example_directories }} diff --git a/canisters/management/canister_management.ts b/canisters/management/canister_management.ts index da6c20b966..026116d93c 100644 --- a/canisters/management/canister_management.ts +++ b/canisters/management/canister_management.ts @@ -136,7 +136,7 @@ export const InstallChunkedCodeArgs = Record({ mode: InstallCodeMode, target_canister: CanisterId, store_canister: Opt(CanisterId), - chunk_hashes_list: Vec(blob), + chunk_hashes_list: Vec(ChunkHash), wasm_module_hash: blob, arg: blob, sender_canister_version: Opt(nat64) diff --git a/canisters/management/ic.did b/canisters/management/ic.did index 7eab9045b0..3aae0f5f20 100644 --- a/canisters/management/ic.did +++ b/canisters/management/ic.did @@ -1,4 +1,4 @@ -// Taken from: https://github.com/dfinity/interface-spec/blob/d04e0dd12956d7b7f52ea01e1bba24479deffa00/spec/_attachments/ic.did +// Taken from: https://github.com/dfinity/interface-spec/blob/6e00887946b612a1169964fc75ee14f57c0ce65d/spec/_attachments/ic.did // Copyright 2021 DFINITY Foundation // @@ -18,70 +18,76 @@ type canister_id = principal; type wasm_module = blob; type canister_settings = record { - controllers : opt vec principal; - compute_allocation : opt nat; - memory_allocation : opt nat; - freezing_threshold : opt nat; - reserved_cycles_limit : opt nat; + controllers : opt vec principal; + compute_allocation : opt nat; + memory_allocation : opt nat; + freezing_threshold : opt nat; + reserved_cycles_limit : opt nat; }; type definite_canister_settings = record { - controllers : vec principal; - compute_allocation : nat; - memory_allocation : nat; - freezing_threshold : nat; - reserved_cycles_limit : nat; + controllers : vec principal; + compute_allocation : nat; + memory_allocation : nat; + freezing_threshold : nat; + reserved_cycles_limit : nat; }; type change_origin = variant { - from_user : record { - user_id : principal; - }; - from_canister : record { - canister_id : principal; - canister_version : opt nat64; - }; + from_user : record { + user_id : principal; + }; + from_canister : record { + canister_id : principal; + canister_version : opt nat64; + }; }; type change_details = variant { - creation : record { - controllers : vec principal; - }; - code_uninstall; - code_deployment : record { - mode : variant {install; reinstall; upgrade}; - module_hash : blob; - }; - controllers_change : record { - controllers : vec principal; - }; + creation : record { + controllers : vec principal; + }; + code_uninstall; + code_deployment : record { + mode : variant { install; reinstall; upgrade }; + module_hash : blob; + }; + controllers_change : record { + controllers : vec principal; + }; }; type change = record { - timestamp_nanos : nat64; - canister_version : nat64; - origin : change_origin; - details : change_details; + timestamp_nanos : nat64; + canister_version : nat64; + origin : change_origin; + details : change_details; }; -type chunk_hash = blob; +type chunk_hash = record { + hash : blob; +}; -type http_header = record { name: text; value: text }; +type http_header = record { + name : text; + value : text; +}; -type http_response = record { - status: nat; - headers: vec http_header; - body: blob; +type http_request_result = record { + status : nat; + headers : vec http_header; + body : blob; }; -type ecdsa_curve = variant { secp256k1; }; +type ecdsa_curve = variant { + secp256k1; +}; type satoshi = nat64; type bitcoin_network = variant { - mainnet; - testnet; - regtest; + mainnet; + testnet; }; type bitcoin_address = text; @@ -89,45 +95,67 @@ type bitcoin_address = text; type block_hash = blob; type outpoint = record { - txid : blob; - vout : nat32 + txid : blob; + vout : nat32; }; type utxo = record { - outpoint: outpoint; - value: satoshi; - height: nat32; + outpoint : outpoint; + value : satoshi; + height : nat32; }; -type get_utxos_request = record { - address : bitcoin_address; - network: bitcoin_network; - filter: opt variant { - min_confirmations: nat32; - page: blob; - }; +type bitcoin_get_utxos_args = record { + address : bitcoin_address; + network : bitcoin_network; + filter : opt variant { + min_confirmations : nat32; + page : blob; + }; +}; + +type bitcoin_get_utxos_query_args = record { + address : bitcoin_address; + network : bitcoin_network; + filter : opt variant { + min_confirmations : nat32; + page : blob; + }; +}; + +type bitcoin_get_current_fee_percentiles_args = record { + network : bitcoin_network; }; -type get_current_fee_percentiles_request = record { - network: bitcoin_network; +type bitcoin_get_utxos_result = record { + utxos : vec utxo; + tip_block_hash : block_hash; + tip_height : nat32; + next_page : opt blob; }; -type get_utxos_response = record { - utxos: vec utxo; - tip_block_hash: block_hash; - tip_height: nat32; - next_page: opt blob; +type bitcoin_get_utxos_query_result = record { + utxos : vec utxo; + tip_block_hash : block_hash; + tip_height : nat32; + next_page : opt blob; }; -type get_balance_request = record { - address : bitcoin_address; - network: bitcoin_network; - min_confirmations: opt nat32; +type bitcoin_get_balance_args = record { + address : bitcoin_address; + network : bitcoin_network; + min_confirmations : opt nat32; }; -type send_transaction_request = record { - transaction: blob; - network: bitcoin_network; +type bitcoin_get_balance_query_args = record { + address : bitcoin_address; + network : bitcoin_network; + min_confirmations : opt nat32; +}; + +type bitcoin_send_transaction_args = record { + transaction : blob; + network : bitcoin_network; }; type millisatoshi_per_byte = nat64; @@ -138,125 +166,212 @@ type node_metrics = record { num_block_failures_total : nat64; }; -service ic : { - create_canister : (record { +type create_canister_args = record { settings : opt canister_settings; sender_canister_version : opt nat64; - }) -> (record {canister_id : canister_id}); - update_settings : (record { +}; + +type create_canister_result = record { + canister_id : canister_id; +}; + +type update_settings_args = record { canister_id : principal; settings : canister_settings; sender_canister_version : opt nat64; - }) -> (); - upload_chunk : (record { +}; + +type upload_chunk_args = record { canister_id : principal; chunk : blob; - }) -> (chunk_hash); - clear_chunk_store: (record {canister_id : canister_id}) -> (); - stored_chunks: (record {canister_id : canister_id}) -> (vec chunk_hash); - install_code : (record { - mode : variant { - install; - reinstall; - upgrade : opt record { - skip_pre_upgrade: opt bool; - } +}; + +type clear_chunk_store_args = record { + canister_id : canister_id; +}; + +type stored_chunks_args = record { + canister_id : canister_id; +}; + +type canister_install_mode = variant { + install; + reinstall; + upgrade : opt record { + skip_pre_upgrade : opt bool; }; +}; + +type install_code_args = record { + mode : canister_install_mode; canister_id : canister_id; wasm_module : wasm_module; arg : blob; sender_canister_version : opt nat64; - }) -> (); - install_chunked_code: (record { - mode : variant { - install; - reinstall; - upgrade : opt record { - skip_pre_upgrade: opt bool; - }; - }; - target_canister: canister_id; - storage_canister: opt canister_id; - chunk_hashes_list: vec chunk_hash; - wasm_module_hash: blob; +}; + +type install_chunked_code_args = record { + mode : canister_install_mode; + target_canister : canister_id; + store_canister : opt canister_id; + chunk_hashes_list : vec chunk_hash; + wasm_module_hash : blob; arg : blob; sender_canister_version : opt nat64; - }) -> (); - uninstall_code : (record { +}; + +type uninstall_code_args = record { canister_id : canister_id; sender_canister_version : opt nat64; - }) -> (); - start_canister : (record {canister_id : canister_id}) -> (); - stop_canister : (record {canister_id : canister_id}) -> (); - canister_status : (record {canister_id : canister_id}) -> (record { - status : variant { running; stopping; stopped }; - settings: definite_canister_settings; - module_hash: opt blob; - memory_size: nat; - cycles: nat; - reserved_cycles: nat; - idle_cycles_burned_per_day: nat; - }); - canister_info : (record { - canister_id : canister_id; - num_requested_changes : opt nat64; - }) -> (record { - total_num_changes : nat64; - recent_changes : vec change; - module_hash : opt blob; - controllers : vec principal; - }); - delete_canister : (record {canister_id : canister_id}) -> (); - deposit_cycles : (record {canister_id : canister_id}) -> (); - raw_rand : () -> (blob); - http_request : (record { +}; + +type start_canister_args = record { + canister_id : canister_id; +}; + +type stop_canister_args = record { + canister_id : canister_id; +}; + +type canister_status_args = record { + canister_id : canister_id; +}; + +type canister_status_result = record { + status : variant { running; stopping; stopped }; + settings : definite_canister_settings; + module_hash : opt blob; + memory_size : nat; + cycles : nat; + reserved_cycles : nat; + idle_cycles_burned_per_day : nat; +}; + +type canister_info_args = record { + canister_id : canister_id; + num_requested_changes : opt nat64; +}; + +type canister_info_result = record { + total_num_changes : nat64; + recent_changes : vec change; + module_hash : opt blob; + controllers : vec principal; +}; + +type delete_canister_args = record { + canister_id : canister_id; +}; + +type deposit_cycles_args = record { + canister_id : canister_id; +}; + +type http_request_args = record { url : text; - max_response_bytes: opt nat64; + max_response_bytes : opt nat64; method : variant { get; head; post }; - headers: vec http_header; + headers : vec http_header; body : opt blob; transform : opt record { - function : func (record {response : http_response; context : blob}) -> (http_response) query; - context : blob + function : func(record { response : http_request_result; context : blob }) -> (http_request_result) query; + context : blob; }; - }) -> (http_response); +}; - // Threshold ECDSA signature - ecdsa_public_key : (record { +type ecdsa_public_key_args = record { canister_id : opt canister_id; derivation_path : vec blob; - key_id : record { curve: ecdsa_curve; name: text }; - }) -> (record { public_key : blob; chain_code : blob; }); - sign_with_ecdsa : (record { + key_id : record { curve : ecdsa_curve; name : text }; +}; + +type ecdsa_public_key_result = record { + public_key : blob; + chain_code : blob; +}; + +type sign_with_ecdsa_args = record { message_hash : blob; derivation_path : vec blob; - key_id : record { curve: ecdsa_curve; name: text }; - }) -> (record { signature : blob }); - - // bitcoin interface - bitcoin_get_balance: (get_balance_request) -> (satoshi); - bitcoin_get_balance_query: (get_balance_request) -> (satoshi) query; - bitcoin_get_utxos: (get_utxos_request) -> (get_utxos_response); - bitcoin_get_utxos_query: (get_utxos_request) -> (get_utxos_response) query; - bitcoin_send_transaction: (send_transaction_request) -> (); - bitcoin_get_current_fee_percentiles: (get_current_fee_percentiles_request) -> (vec millisatoshi_per_byte); - - // metrics interface - node_metrics_history : (record { + key_id : record { curve : ecdsa_curve; name : text }; +}; + +type sign_with_ecdsa_result = record { + signature : blob; +}; + +type node_metrics_history_args = record { subnet_id : principal; - start_at_timestamp_nanos: nat64; - }) -> (vec record { + start_at_timestamp_nanos : nat64; +}; + +type node_metrics_history_result = vec record { timestamp_nanos : nat64; node_metrics : vec node_metrics; - }); +}; - // provisional interfaces for the pre-ledger world - provisional_create_canister_with_cycles : (record { - amount: opt nat; +type provisional_create_canister_with_cycles_args = record { + amount : opt nat; settings : opt canister_settings; - specified_id: opt canister_id; + specified_id : opt canister_id; sender_canister_version : opt nat64; - }) -> (record {canister_id : canister_id}); - provisional_top_up_canister : - (record { canister_id: canister_id; amount: nat }) -> (); -} +}; + +type provisional_create_canister_with_cycles_result = record { + canister_id : canister_id; +}; + +type provisional_top_up_canister_args = record { + canister_id : canister_id; + amount : nat; +}; + +type raw_rand_result = blob; + +type stored_chunks_result = vec chunk_hash; + +type upload_chunk_result = chunk_hash; + +type bitcoin_get_balance_result = satoshi; + +type bitcoin_get_balance_query_result = satoshi; + +type bitcoin_get_current_fee_percentiles_result = vec millisatoshi_per_byte; + +service ic : { + create_canister : (create_canister_args) -> (create_canister_result); + update_settings : (update_settings_args) -> (); + upload_chunk : (upload_chunk_args) -> (upload_chunk_result); + clear_chunk_store : (clear_chunk_store_args) -> (); + stored_chunks : (stored_chunks_args) -> (stored_chunks_result); + install_code : (install_code_args) -> (); + install_chunked_code : (install_chunked_code_args) -> (); + uninstall_code : (uninstall_code_args) -> (); + start_canister : (start_canister_args) -> (); + stop_canister : (stop_canister_args) -> (); + canister_status : (canister_status_args) -> (canister_status_result); + canister_info : (canister_info_args) -> (canister_info_result); + delete_canister : (delete_canister_args) -> (); + deposit_cycles : (deposit_cycles_args) -> (); + raw_rand : () -> (raw_rand_result); + http_request : (http_request_args) -> (http_request_result); + + // Threshold ECDSA signature + ecdsa_public_key : (ecdsa_public_key_args) -> (ecdsa_public_key_result); + sign_with_ecdsa : (sign_with_ecdsa_args) -> (sign_with_ecdsa_result); + + // bitcoin interface + bitcoin_get_balance : (bitcoin_get_balance_args) -> (bitcoin_get_balance_result); + bitcoin_get_balance_query : (bitcoin_get_balance_query_args) -> (bitcoin_get_balance_query_result) query; + bitcoin_get_utxos : (bitcoin_get_utxos_args) -> (bitcoin_get_utxos_result); + bitcoin_get_utxos_query : (bitcoin_get_utxos_query_args) -> (bitcoin_get_utxos_query_result) query; + bitcoin_send_transaction : (bitcoin_send_transaction_args) -> (); + bitcoin_get_current_fee_percentiles : (bitcoin_get_current_fee_percentiles_args) -> (bitcoin_get_current_fee_percentiles_result); + + // metrics interface + node_metrics_history : (node_metrics_history_args) -> (node_metrics_history_result); + + // provisional interfaces for the pre-ledger world + provisional_create_canister_with_cycles : (provisional_create_canister_with_cycles_args) -> (provisional_create_canister_with_cycles_result); + provisional_top_up_canister : (provisional_top_up_canister_args) -> (); +}; \ No newline at end of file diff --git a/examples/management_canister/src/index.ts b/examples/management_canister/src/index.ts index 2a714e1f32..ff3700bcd3 100644 --- a/examples/management_canister/src/index.ts +++ b/examples/management_canister/src/index.ts @@ -12,16 +12,19 @@ import { query, serialize, Some, - update + update, + Vec } from 'azle'; import { CanisterInfoArgs, CanisterInfoResult, CanisterStatusArgs, CanisterStatusResult, + ChunkHash, CreateCanisterResult, managementCanister, - ProvisionalCreateCanisterWithCyclesResult + ProvisionalCreateCanisterWithCyclesResult, + StoredChunksResult } from 'azle/canisters/management'; type State = { @@ -79,6 +82,84 @@ export default Canister({ return true; }), + executeUploadChunk: update( + [Principal, blob], + ChunkHash, + async (canisterId, chunk): Promise => { + if (process.env.AZLE_TEST_FETCH === 'true' || false) { + const response = await fetch(`icp://aaaaa-aa/upload_chunk`, { + body: serialize({ + args: [ + { + canister_id: canisterId, + chunk: chunk + } + ] + }) + }); + + return await response.json(); + } else { + return await ic.call(managementCanister.upload_chunk, { + args: [ + { + canister_id: canisterId, + chunk: chunk + } + ] + }); + } + } + ), + executeClearChunkStore: update([Principal], bool, async (canisterId) => { + if (process.env.AZLE_TEST_FETCH === 'true' || false) { + await fetch(`icp://aaaaa-aa/clear_chunk_store`, { + body: serialize({ + args: [ + { + canister_id: canisterId + } + ] + }) + }); + } else { + await ic.call(managementCanister.clear_chunk_store, { + args: [ + { + canister_id: canisterId + } + ] + }); + } + + return true; + }), + getStoredChunks: update( + [Principal], + StoredChunksResult, + async (canisterId) => { + if (process.env.AZLE_TEST_FETCH === 'true') { + const response = await fetch(`icp://aaaaa-aa/stored_chunks`, { + body: serialize({ + args: [ + { + canister_id: canisterId + } + ] + }) + }); + return await response.json(); + } else { + return await ic.call(managementCanister.stored_chunks, { + args: [ + { + canister_id: canisterId + } + ] + }); + } + } + ), executeInstallCode: update( [Principal, blob], bool, @@ -120,6 +201,51 @@ export default Canister({ return true; } ), + executeInstallChunkedCode: update( + [Principal, Vec(ChunkHash), blob], + bool, + async (canisterId, chunkHashes, wasmModuleHash) => { + if (process.env.AZLE_TEST_FETCH === 'true') { + await fetch(`icp://aaaaa-aa/install_chunked_code`, { + body: serialize({ + args: [ + { + mode: { + install: null + }, + target_canister: canisterId, + store_canister: [], + chunk_hashes_list: chunkHashes, + wasm_module_hash: wasmModuleHash, + arg: Uint8Array.from([]), + sender_canister_version: [] + } + ], + cycles: 100_000_000_000n + }) + }); + } else { + await ic.call(managementCanister.install_chunked_code, { + args: [ + { + mode: { + install: null + }, + target_canister: canisterId, + store_canister: None, + chunk_hashes_list: chunkHashes, + wasm_module_hash: wasmModuleHash, + arg: Uint8Array.from([]), + sender_canister_version: None + } + ], + cycles: 100_000_000_000n + }); + } + + return true; + } + ), executeUninstallCode: update([Principal], bool, async (canisterId) => { if (process.env.AZLE_TEST_FETCH === 'true') { await fetch(`icp://aaaaa-aa/uninstall_code`, { diff --git a/examples/management_canister/test/tests.ts b/examples/management_canister/test/tests.ts index 4cb35e3ea8..c575f05b29 100644 --- a/examples/management_canister/test/tests.ts +++ b/examples/management_canister/test/tests.ts @@ -1,5 +1,6 @@ import { ActorSubclass } from '@dfinity/agent'; import { Test } from 'azle/test'; +import { createHash } from 'crypto'; import { readFileSync } from 'fs'; import { _SERVICE } from './dfx_generated/management_canister/management_canister.did'; @@ -61,49 +62,132 @@ export function getTests(managementCanister: ActorSubclass<_SERVICE>): Test[] { } }, { - name: 'executeDepositCycles', + name: 'executeUninstallCode', test: async () => { const canisterId = await managementCanister.getCreatedCanisterId(); - const statusBefore = await managementCanister.getCanisterStatus( - { + await managementCanister.executeUninstallCode(canisterId); + + const getCanisterStatusResult = + await managementCanister.getCanisterStatus({ canister_id: canisterId - } - ); + }); - const cyclesBefore = statusBefore.cycles; + const canisterStatus = getCanisterStatusResult; - await managementCanister.executeDepositCycles(canisterId); + return { + Ok: canisterStatus.module_hash.length === 0 + }; + } + }, + { + name: 'executeUploadChunk ', + test: async () => { + const canisterId = + await managementCanister.getCreatedCanisterId(); - const statusAfter = await managementCanister.getCanisterStatus({ - canister_id: canisterId - }); + const wasmModule = Array.from(readFileSync('src/test.wasm')); - const cyclesAfter = statusAfter.cycles; + const chunkUploadResult = + await managementCanister.executeUploadChunk( + canisterId, + wasmModule as any + ); return { - Ok: cyclesAfter > cyclesBefore + Ok: chunkUploadResult.hash.length === 32 }; } }, { - name: 'executeUninstallCode', + name: 'getStoredChunks', + test: async () => { + const canisterId = + await managementCanister.getCreatedCanisterId(); + + const storedChunks = + await managementCanister.getStoredChunks(canisterId); + + const wasmModule = Uint8Array.from( + readFileSync('src/test.wasm') + ); + const wasmHash = createHash('sha256') + .update(wasmModule) + .digest(); + + return { + Ok: + storedChunks.length === 1 && + Uint8Array.from(wasmHash).toString() === + storedChunks[0].hash.toString() + }; + } + }, + { + name: 'executeInstallChunkedCode', test: async () => { const canisterId = await managementCanister.getCreatedCanisterId(); + const storedChunks = + await managementCanister.getStoredChunks(canisterId); + + const result = + await managementCanister.executeInstallChunkedCode( + canisterId, + storedChunks, + storedChunks[0].hash + ); + await managementCanister.executeUninstallCode(canisterId); - const getCanisterStatusResult = - await managementCanister.getCanisterStatus({ + return { + Ok: result + }; + } + }, + { + name: 'executeClearChunkStore', + test: async () => { + const canisterId = + await managementCanister.getCreatedCanisterId(); + + const result = + await managementCanister.executeClearChunkStore(canisterId); + + const storedChunks = + await managementCanister.getStoredChunks(canisterId); + + return { + Ok: result && storedChunks.length === 0 + }; + } + }, + { + name: 'executeDepositCycles', + test: async () => { + const canisterId = + await managementCanister.getCreatedCanisterId(); + + const statusBefore = await managementCanister.getCanisterStatus( + { canister_id: canisterId - }); + } + ); - const canisterStatus = getCanisterStatusResult; + const cyclesBefore = statusBefore.cycles; + + await managementCanister.executeDepositCycles(canisterId); + + const statusAfter = await managementCanister.getCanisterStatus({ + canister_id: canisterId + }); + + const cyclesAfter = statusAfter.cycles; return { - Ok: canisterStatus.module_hash.length === 0 + Ok: cyclesAfter > cyclesBefore }; } }, @@ -176,7 +260,7 @@ export function getTests(managementCanister: ActorSubclass<_SERVICE>): Test[] { return { Ok: 'running' in canisterStatus.status && - canisterStatus.memory_size === 342n && + canisterStatus.memory_size === 550n && canisterStatus.cycles >= 800_000_000_000n && canisterStatus.settings.freezing_threshold === 2_000_000n && @@ -201,8 +285,8 @@ export function getTests(managementCanister: ActorSubclass<_SERVICE>): Test[] { return { Ok: - canisterInfo.total_num_changes === 3n && - canisterInfo.recent_changes.length === 3 && + canisterInfo.total_num_changes === 5n && + canisterInfo.recent_changes.length === 5 && canisterInfo.module_hash.length === 0 && canisterInfo.controllers.length === 1 }; From 84ac178309d216a1516404adf28e98b5b0544d25 Mon Sep 17 00:00:00 2001 From: Shreyansh Jain Date: Fri, 3 May 2024 17:33:40 +0530 Subject: [PATCH 04/49] Update LICENSE_EXTENSION.md --- LICENSE_EXTENSION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE_EXTENSION.md b/LICENSE_EXTENSION.md index 7e140fc22b..37be6bf349 100644 --- a/LICENSE_EXTENSION.md +++ b/LICENSE_EXTENSION.md @@ -39,3 +39,4 @@ Your electronic signature will be considered final once the commit is included i | Tarek Mouawad | tarek-eg | Germany | March 31, 2022 | | Quint Daenen | di-wu | Belgium | May 21, 2022 | | Ryan Vandersmith | rvanasa | United States | September 3, 2022 | +| Shreyansh Jain | sudoshreyansh | India | May 3, 2024 | From 61ef962a98dc81fe27c0d498e9d7af93943fd687 Mon Sep 17 00:00:00 2001 From: sudoshreyansh Date: Fri, 3 May 2024 19:01:20 +0530 Subject: [PATCH 05/49] add chunk methods in azle book --- .../management_canister/clear_chunk_store.md | 26 +++++++++++++ .../install_chunked_code.md | 39 +++++++++++++++++++ .../management_canister/stored_chunks.md | 32 +++++++++++++++ .../management_canister/upload_chunk.md | 33 ++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 the_azle_book/src/reference/management_canister/clear_chunk_store.md create mode 100644 the_azle_book/src/reference/management_canister/install_chunked_code.md create mode 100644 the_azle_book/src/reference/management_canister/stored_chunks.md create mode 100644 the_azle_book/src/reference/management_canister/upload_chunk.md diff --git a/the_azle_book/src/reference/management_canister/clear_chunk_store.md b/the_azle_book/src/reference/management_canister/clear_chunk_store.md new file mode 100644 index 0000000000..4fa9fb12be --- /dev/null +++ b/the_azle_book/src/reference/management_canister/clear_chunk_store.md @@ -0,0 +1,26 @@ +# clear_chunk_store + +This section is a work in progress. + +Examples: + +- [management_canister](https://github.com/demergent-labs/azle/tree/main/examples/management_canister) + +```typescript +import { Canister, ic, update, Principal } from 'azle'; +import { managementCanister } from 'azle/canisters/management'; + +export default Canister({ + executeClearChunkStore: update([Principal], bool, async (canisterId) => { + await ic.call(managementCanister.clear_chunk_store, { + args: [ + { + canister_id: canisterId + } + ] + }); + + return true; + }) +}); +``` diff --git a/the_azle_book/src/reference/management_canister/install_chunked_code.md b/the_azle_book/src/reference/management_canister/install_chunked_code.md new file mode 100644 index 0000000000..6af52da329 --- /dev/null +++ b/the_azle_book/src/reference/management_canister/install_chunked_code.md @@ -0,0 +1,39 @@ +# install_chunked_code + +This section is a work in progress. + +Examples: + +- [management_canister](https://github.com/demergent-labs/azle/tree/main/examples/management_canister) + +```typescript +import { Vec, blob, bool, Canister, ic, Principal, update, None } from 'azle'; +import { managementCanister } from 'azle/canisters/management'; + +export default Canister({ + executeInstallChunkedCode: update( + [Principal, blob], + bool, + async (canisterId, wasmModuleHash) => { + await ic.call(managementCanister.install_chunked_code, { + args: [ + { + mode: { + install: null + }, + target_canister: canisterId, + store_canister: None, + chunk_hashes_list: [{ hash: wasmModuleHash }], + wasm_module_hash: wasmModuleHash, + arg: Uint8Array.from([]), + sender_canister_version: None + } + ], + cycles: 100_000_000_000n + }); + + return true; + } + ) +}); +``` diff --git a/the_azle_book/src/reference/management_canister/stored_chunks.md b/the_azle_book/src/reference/management_canister/stored_chunks.md new file mode 100644 index 0000000000..d1c4557f6a --- /dev/null +++ b/the_azle_book/src/reference/management_canister/stored_chunks.md @@ -0,0 +1,32 @@ +# stored_chunks + +This section is a work in progress. + +Examples: + +- [management_canister](https://github.com/demergent-labs/azle/tree/main/examples/management_canister) + +```typescript +import { Canister, ic, update } from 'azle'; +import { + StoredChunksArgs, + StoredChunksResult, + managementCanister +} from 'azle/canisters/management'; + +export default Canister({ + getStoredChunks: query( + [Principal], + StoredChunksResult, + async (canisterId) => { + return await ic.call(managementCanister.stored_chunks, { + args: [ + { + canister_id: canisterId + } + ] + }); + } + ) +}); +``` diff --git a/the_azle_book/src/reference/management_canister/upload_chunk.md b/the_azle_book/src/reference/management_canister/upload_chunk.md new file mode 100644 index 0000000000..3aa1e0613c --- /dev/null +++ b/the_azle_book/src/reference/management_canister/upload_chunk.md @@ -0,0 +1,33 @@ +# upload_chunk + +This section is a work in progress. + +Examples: + +- [management_canister](https://github.com/demergent-labs/azle/tree/main/examples/management_canister) + +```typescript +import { Canister, ic, update } from 'azle'; +import { + UploadChunkArgs, + UploadChunkResult, + managementCanister +} from 'azle/canisters/management'; + +export default Canister({ + executeUploadChunk: query( + [Principal, blob], + UploadChunkResult, + async (canisterId, wasmChunk) => { + return await ic.call(managementCanister.upload_chunk, { + args: [ + { + canister_id: canisterId, + chunk: wasmChunk + } + ] + }); + } + ) +}); +``` From f1cfc00f311b03b24ebf9a403b502e2fb93f945d Mon Sep 17 00:00:00 2001 From: sudoshreyansh Date: Fri, 3 May 2024 19:03:32 +0530 Subject: [PATCH 06/49] undo workflow changes --- .github/workflows/test.yml | 132 ++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c49aa37349..27d2c1b7e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,129 @@ jobs: run: | EXAMPLE_DIRECTORIES=$(cat << END [ - "examples/management_canister" ] + "examples/apollo_server", + "examples/async_await", + "examples/audio_and_video", + "examples/audio_recorder", + "examples/autoreload", + "examples/bitcoin", + "examples/bitcoinjs-lib", + "examples/bitcore-lib", + "examples/blob_array", + "examples/bytes", + "examples/call_raw", + "examples/candid_encoding", + "examples/candid_keywords", + "examples/canister", + "examples/ckbtc", + "examples/complex_init", + "examples/complex_types", + "examples/composite_queries", + "examples/counter", + "examples/cross_canister_calls", + "examples/cycles", + "examples/date", + "examples/ethereum_json_rpc", + "examples/ethers", + "examples/ethers_base", + "examples/express", + "examples/fetch_ic", + "examples/file_protocol", + "examples/fs", + "examples/func_types", + "examples/guard_functions", + "examples/heartbeat", + "examples/hello_world", + "examples/http_outcall_fetch", + "examples/hybrid_canister", + "examples/ic_api", + "examples/ic_evm_rpc", + "examples/icrc", + "examples/imports", + "examples/init", + "examples/inspect_message", + "examples/internet_identity", + "examples/key_value_store", + "examples/large_files", + "examples/ledger_canister", + "examples/list_of_lists", + "examples/management_canister", + "examples/manual_reply", + "examples/motoko_examples/calc", + "examples/motoko_examples/counter", + "examples/motoko_examples/echo", + "examples/motoko_examples/factorial", + "examples/motoko_examples/hello", + "examples/motoko_examples/hello-world", + "examples/motoko_examples/http_counter", + "examples/motoko_examples/minimal-counter-dapp", + "examples/motoko_examples/persistent-storage", + "examples/motoko_examples/phone-book", + "examples/motoko_examples/quicksort", + "examples/motoko_examples/simple-to-do", + "examples/motoko_examples/superheroes", + "examples/motoko_examples/threshold_ecdsa", + "examples/motoko_examples/whoami", + "examples/new", + "examples/notify_raw", + "examples/null_example", + "examples/optional_types", + "examples/outgoing_http_requests", + "examples/pre_and_post_upgrade", + "examples/primitive_types", + "examples/principal", + "examples/query", + "examples/randomness", + "examples/recursion", + "examples/rejections", + "examples/robust_imports", + "examples/simple_erc20", + "examples/simple_user_accounts", + "examples/sqlite", + "examples/stable_b_tree_map_instruction_threshold", + "examples/stable_memory", + "examples/stable_structures", + "examples/tfjs", + "examples/timers", + "examples/tuple_types", + "examples/update", + "examples/vanilla_js", + "examples/web_assembly", + "property_tests/tests/blob", + "property_tests/tests/bool", + "property_tests/tests/canister_methods/http_request", + "property_tests/tests/canister_methods/http_request_update", + "property_tests/tests/canister_methods/init", + "property_tests/tests/canister_methods/inspect_message", + "property_tests/tests/canister_methods/post_upgrade", + "property_tests/tests/canister_methods/pre_upgrade", + "property_tests/tests/canister_methods/query", + "property_tests/tests/canister_methods/update", + "property_tests/tests/float32", + "property_tests/tests/float64", + "property_tests/tests/func", + "property_tests/tests/int", + "property_tests/tests/int8", + "property_tests/tests/int16", + "property_tests/tests/int32", + "property_tests/tests/int64", + "property_tests/tests/nat", + "property_tests/tests/nat8", + "property_tests/tests/nat16", + "property_tests/tests/nat32", + "property_tests/tests/nat64", + "property_tests/tests/null", + "property_tests/tests/opt", + "property_tests/tests/principal", + "property_tests/tests/record", + "property_tests/tests/recursive", + "property_tests/tests/service", + "property_tests/tests/stable_b_tree_map", + "property_tests/tests/text", + "property_tests/tests/tuple", + "property_tests/tests/variant", + "property_tests/tests/vec" + ] END ) EXAMPLE_DIRECTORIES="${EXAMPLE_DIRECTORIES//'%'/'%25'}" @@ -195,6 +317,14 @@ jobs: shell: bash -l {0} working-directory: ${{ matrix.example_directories }} run: AZLE_PROPTEST_NUM_RUNS=5 AZLE_PROPTEST_VERBOSE=true npm test + - if: ${{ needs.release-candidate-deploy.outputs.should_run_tests && contains(github.head_ref, 'release--') && !(github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'demergent-labs/release--')) }} + shell: bash -l {0} + working-directory: ${{ matrix.example_directories }} + run: AZLE_PROPTEST_NUM_RUNS=10 AZLE_PROPTEST_VERBOSE=true npm test + - if: ${{ needs.release-candidate-deploy.outputs.should_run_tests && (github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'Merge pull request') && contains(github.event.head_commit.message, 'demergent-labs/release--')) }} + shell: bash -l {0} + working-directory: ${{ matrix.example_directories }} + run: AZLE_PROPTEST_NUM_RUNS=100 AZLE_PROPTEST_VERBOSE=true npm test - if: ${{ needs.release-candidate-deploy.outputs.should_run_tests && matrix.example_directories != 'examples/new' }} shell: bash -l {0} working-directory: ${{ matrix.example_directories }} From a717caebd4945cbb60d1284c7ccd7aab34b7ace4 Mon Sep 17 00:00:00 2001 From: Jordan Last Date: Tue, 7 May 2024 09:57:46 -0500 Subject: [PATCH 07/49] address many comments from PR --- src/compiler/compile_typescript_code.ts | 23 +++++++++++-------- src/compiler/custom_js_modules/acorn/acorn.ts | 4 ++++ src/compiler/custom_js_modules/acorn/walk.ts | 4 ++++ src/compiler/custom_js_modules/async_hooks.ts | 2 ++ src/compiler/custom_js_modules/https.ts | 3 +++ src/compiler/custom_js_modules/perf_hooks.ts | 2 ++ src/compiler/custom_js_modules/zlib.ts | 4 ++++ 7 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 src/compiler/custom_js_modules/acorn/acorn.ts create mode 100644 src/compiler/custom_js_modules/acorn/walk.ts create mode 100644 src/compiler/custom_js_modules/zlib.ts diff --git a/src/compiler/compile_typescript_code.ts b/src/compiler/compile_typescript_code.ts index c55d8ea241..c62640fb77 100644 --- a/src/compiler/compile_typescript_code.ts +++ b/src/compiler/compile_typescript_code.ts @@ -100,7 +100,7 @@ export async function bundleFromString( const externalNotImplementedAzle: string[] = []; // These are modules that should not be included in the build from the developer side - // These are specified in the dfx.json canister object npm_external property + // These are specified in the dfx.json canister object esm_externals property const externalNotImplementedDev = esmExternals; // These will cause runtime errors if their functionality is dependend upon @@ -140,12 +140,18 @@ export async function bundleFromString( encoding: `${finalWasmedgeQuickJsPath}/modules/encoding.js`, http: `${finalWasmedgeQuickJsPath}/modules/http.js`, os: `${finalWasmedgeQuickJsPath}/modules/os.js`, - // crypto: `${finalWasmedgeQuickJsPath}/modules/crypto.js`, - crypto: 'crypto-browserify', - zlib: 'crypto-browserify', // TODO wrong of course - 'internal/deps/acorn/acorn/dist/acorn': `crypto-browserify`, // TODO this is a bug, wasmedge-quickjs should probably add these files - 'internal/deps/acorn/acorn-walk/dist/walk': `crypto-browserify`, // TODO this is a bug, wasmedge-quickjs should probably add these files - perf_hooks: path.join(__dirname, 'custom_js_modules/perf_hooks.ts'), // TODO will this work across all operating systems? Might need to test on Mac + // crypto: `${finalWasmedgeQuickJsPath}/modules/crypto.js`, // TODO waiting on wasi-crypto + crypto: 'crypto-browserify', // TODO we really want the wasmedge-quickjs version once wasi-crypto is working + zlib: path.join(__dirname, 'custom_js_modules/zlib.ts'), + 'internal/deps/acorn/acorn/dist/acorn': path.join( + __dirname, + 'custom_js_modules/acorn/acorn.ts' + ), // TODO acorn stuff is a bug, wasmedge-quickjs should probably add these files + 'internal/deps/acorn/acorn-walk/dist/walk': path.join( + __dirname, + 'custom_js_modules/acorn/walk.ts' + ), // TODO acorn stuff is a bug, wasmedge-quickjs should probably add these files + perf_hooks: path.join(__dirname, 'custom_js_modules/perf_hooks.ts'), async_hooks: path.join( __dirname, 'custom_js_modules/async_hooks.ts' @@ -155,10 +161,9 @@ export async function bundleFromString( }, external: [...externalImplemented, ...externalNotImplemented], plugins: [esbuildPluginTsc()] - // tsconfig: path.join(__dirname, './esbuild_tsconfig.json') // TODO tsconfig was here to attempt to set importsNotUsedAsValues to true to force Principal to always be bundled // TODO now we always bundle Principal for all code, but I am keeping this here in case we run into the problem elsewhere - // tsconfig: path.join( __dirname, './esbuild-tsconfig.json') // TODO this path resolution may cause problems on non-Linux systems, beware...might not be necessary now that we are using stdin + // tsconfig: path.join( __dirname, './esbuild-tsconfig.json') }); const bundleArray = buildResult.outputFiles[0].contents; diff --git a/src/compiler/custom_js_modules/acorn/acorn.ts b/src/compiler/custom_js_modules/acorn/acorn.ts new file mode 100644 index 0000000000..449471cbc1 --- /dev/null +++ b/src/compiler/custom_js_modules/acorn/acorn.ts @@ -0,0 +1,4 @@ +// TODO this is just a placeholder module so that bundling won't break +// TODO eventually we would want a real implementation of this in Azle or wasmedge-quickjs or Waden + +export {}; diff --git a/src/compiler/custom_js_modules/acorn/walk.ts b/src/compiler/custom_js_modules/acorn/walk.ts new file mode 100644 index 0000000000..449471cbc1 --- /dev/null +++ b/src/compiler/custom_js_modules/acorn/walk.ts @@ -0,0 +1,4 @@ +// TODO this is just a placeholder module so that bundling won't break +// TODO eventually we would want a real implementation of this in Azle or wasmedge-quickjs or Waden + +export {}; diff --git a/src/compiler/custom_js_modules/async_hooks.ts b/src/compiler/custom_js_modules/async_hooks.ts index 926d4015fa..f2407f2511 100644 --- a/src/compiler/custom_js_modules/async_hooks.ts +++ b/src/compiler/custom_js_modules/async_hooks.ts @@ -1,3 +1,5 @@ +// TODO this is just a placeholder module so that bundling won't break +// TODO eventually we would want a real implementation of this in Azle or wasmedge-quickjs or Waden // TODO it seems async_hooks is impossible or very difficult to polyfill in the browser // TODO I am not sure what it would take to do it in Wasm/QuickJS // TODO It might be a bit tricky diff --git a/src/compiler/custom_js_modules/https.ts b/src/compiler/custom_js_modules/https.ts index cb0ff5c3b5..449471cbc1 100644 --- a/src/compiler/custom_js_modules/https.ts +++ b/src/compiler/custom_js_modules/https.ts @@ -1 +1,4 @@ +// TODO this is just a placeholder module so that bundling won't break +// TODO eventually we would want a real implementation of this in Azle or wasmedge-quickjs or Waden + export {}; diff --git a/src/compiler/custom_js_modules/perf_hooks.ts b/src/compiler/custom_js_modules/perf_hooks.ts index 2079141ac1..6a6b372e08 100644 --- a/src/compiler/custom_js_modules/perf_hooks.ts +++ b/src/compiler/custom_js_modules/perf_hooks.ts @@ -1,3 +1,5 @@ +// TODO this is just a placeholder module so that bundling won't break +// TODO eventually we would want a real implementation of this in Azle or wasmedge-quickjs or Waden // TODO we should PR this implementation into https://github.com/WasmEdge/WasmEdge/issues/1535 // TODO it should be pretty easy and a good way to get started in the repo diff --git a/src/compiler/custom_js_modules/zlib.ts b/src/compiler/custom_js_modules/zlib.ts new file mode 100644 index 0000000000..449471cbc1 --- /dev/null +++ b/src/compiler/custom_js_modules/zlib.ts @@ -0,0 +1,4 @@ +// TODO this is just a placeholder module so that bundling won't break +// TODO eventually we would want a real implementation of this in Azle or wasmedge-quickjs or Waden + +export {}; From bb157d830bcc76c6c1b998d5375d969849764bea Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 6 May 2024 13:12:36 -0600 Subject: [PATCH 08/49] update example to use bitcoinjs-lib --- examples/basic_bitcoin/.bitcoin.conf | 3 + examples/basic_bitcoin/.gitignore | 3 +- examples/basic_bitcoin/dfx.json | 35 +- examples/basic_bitcoin/package-lock.json | 3443 ++++++++++++----- examples/basic_bitcoin/package.json | 30 +- examples/basic_bitcoin/scripts/install.sh | 18 + .../src/{ => backend}/bitcoin_api.ts | 104 +- .../src/backend/bitcoin_wallet.ts | 415 ++ .../basic_bitcoin/src/backend/ecdsa_api.ts | 53 + examples/basic_bitcoin/src/backend/index.did | 4 + examples/basic_bitcoin/src/backend/index.ts | 155 + examples/basic_bitcoin/src/backend/types.ts | 18 + .../src/bitcoin_plugin/Cargo.toml | 3 - .../basic_bitcoin/src/bitcoin_plugin/index.ts | 172 - .../src/bitcoin_plugin/src/lib.rs | 852 ---- examples/basic_bitcoin/src/bitcoin_wallet.ts | 340 -- examples/basic_bitcoin/src/ecdsa_api.ts | 55 - examples/basic_bitcoin/src/index.did | 20 - examples/basic_bitcoin/src/index.ts | 93 - examples/basic_bitcoin/src/types.ts | 8 - examples/basic_bitcoin/test/pretest.ts | 13 + examples/basic_bitcoin/test/test.ts | 45 + examples/basic_bitcoin/test/tests.ts | 361 ++ examples/basic_bitcoin/tsconfig.json | 4 +- 24 files changed, 3725 insertions(+), 2522 deletions(-) create mode 100755 examples/basic_bitcoin/scripts/install.sh rename examples/basic_bitcoin/src/{ => backend}/bitcoin_api.ts (57%) create mode 100644 examples/basic_bitcoin/src/backend/bitcoin_wallet.ts create mode 100644 examples/basic_bitcoin/src/backend/ecdsa_api.ts create mode 100644 examples/basic_bitcoin/src/backend/index.did create mode 100644 examples/basic_bitcoin/src/backend/index.ts create mode 100644 examples/basic_bitcoin/src/backend/types.ts delete mode 100644 examples/basic_bitcoin/src/bitcoin_plugin/Cargo.toml delete mode 100644 examples/basic_bitcoin/src/bitcoin_plugin/index.ts delete mode 100644 examples/basic_bitcoin/src/bitcoin_plugin/src/lib.rs delete mode 100644 examples/basic_bitcoin/src/bitcoin_wallet.ts delete mode 100644 examples/basic_bitcoin/src/ecdsa_api.ts delete mode 100644 examples/basic_bitcoin/src/index.did delete mode 100644 examples/basic_bitcoin/src/index.ts delete mode 100644 examples/basic_bitcoin/src/types.ts create mode 100644 examples/basic_bitcoin/test/pretest.ts create mode 100644 examples/basic_bitcoin/test/test.ts create mode 100644 examples/basic_bitcoin/test/tests.ts diff --git a/examples/basic_bitcoin/.bitcoin.conf b/examples/basic_bitcoin/.bitcoin.conf index d2cf90b26f..c84674df72 100644 --- a/examples/basic_bitcoin/.bitcoin.conf +++ b/examples/basic_bitcoin/.bitcoin.conf @@ -5,3 +5,6 @@ regtest=1 rpcuser=ic-btc-integration rpcpassword=QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E= rpcauth=ic-btc-integration:cdf2741387f3a12438f69092f0fdad8e$62081498c98bee09a0dce2b30671123fa561932992ce377585e8e08bb0c11dfa + +# Enable indexing so we can look up transactions by their ids +txindex=1 diff --git a/examples/basic_bitcoin/.gitignore b/examples/basic_bitcoin/.gitignore index 8699a2441c..53dcaef0ee 100644 --- a/examples/basic_bitcoin/.gitignore +++ b/examples/basic_bitcoin/.gitignore @@ -1,6 +1,7 @@ .azle .bitcoin .dfx +*.wasm +*.wasm.gz dfx_generated node_modules - diff --git a/examples/basic_bitcoin/dfx.json b/examples/basic_bitcoin/dfx.json index c622c383d8..b74cb5cdfd 100644 --- a/examples/basic_bitcoin/dfx.json +++ b/examples/basic_bitcoin/dfx.json @@ -1,19 +1,18 @@ { "canisters": { - "basic_bitcoin": { + "backend": { "type": "custom", - "main": "src/index.ts", - "candid": "src/index.did", - "build": "npx azle basic_bitcoin", - "wasm": ".azle/basic_bitcoin/basic_bitcoin.wasm.gz", - "declarations": { - "output": "test/dfx_generated/basic_bitcoin", - "node_compatibility": true - }, + "main": "src/backend/index.ts", + "candid": "src/backend/index.did", + "init_arg": "(variant { regtest })", + "build": "npx azle backend", + "env": ["BITCOIN_NETWORK"], + "wasm": ".azle/backend/backend.wasm", + "gzip": true, "metadata": [ { "name": "candid:service", - "path": "src/index.did" + "path": "src/backend/index.did" }, { "name": "cdk:name", @@ -21,5 +20,21 @@ } ] } + }, + "networks": { + "local": { + "bind": "127.0.0.1:8000", + "type": "ephemeral", + "replica": { + "subnet_type": "system" + } + } + }, + "defaults": { + "bitcoin": { + "enabled": true, + "nodes": ["127.0.0.1:18444"], + "log_level": "info" + } } } diff --git a/examples/basic_bitcoin/package-lock.json b/examples/basic_bitcoin/package-lock.json index e37ea6042a..a63a63721f 100644 --- a/examples/basic_bitcoin/package-lock.json +++ b/examples/basic_bitcoin/package-lock.json @@ -4,21 +4,36 @@ "requires": true, "packages": { "": { + "hasInstallScript": true, "dependencies": { - "assert": "2.0.0", - "azle": "0.16.3", - "bs58": "5.0.0", - "events": "3.3.0", - "hex-array": "1.0.0", - "js-sha256": "0.9.0", - "ripemd160": "2.0.2" + "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", + "azle": "^0.21.1", + "bitcoinjs-lib": "^6.1.5", + "ecpair": "^2.1.0", + "express": "^4.18.2", + "tiny-secp256k1": "^2.2.3" }, "devDependencies": { - "@dfinity/agent": "0.11.1", - "@types/hex-array": "1.0.0", - "@types/ripemd160": "2.0.0", - "ts-node": "10.7.0", - "typescript": "4.6.3" + "@dfinity/agent": "^0.19.2", + "@types/express": "^4.17.21", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, + "node_modules/@bitcoin-js/tiny-secp256k1-asmjs": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@bitcoin-js/tiny-secp256k1-asmjs/-/tiny-secp256k1-asmjs-2.2.3.tgz", + "integrity": "sha512-arFPdEZi9RIiaG76OZswTnAU0KfuiLwGw2VNfD66LKhzlbfOnX1o1WI/GI3qm9UbjG/0QOzZu/KmTNvL79x/DQ==", + "dependencies": { + "uint8array-tools": "0.0.7" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/@cspotcode/source-map-consumer": { @@ -41,70 +56,117 @@ } }, "node_modules/@dfinity/agent": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.11.1.tgz", - "integrity": "sha512-Z1zw8l3d+AG3uu7d8G/Rd9Q5MWT9gB+Cori/Rqb6IjSEribRhL36ulCSkDYZJU/dhqSUp1VlvX5u51+wgv+MLg==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.19.3.tgz", + "integrity": "sha512-q410aNLoOA1ZkwdAMgSo6t++pjISn9TfSybRXhPRI5Ume7eG6+6qYr/rlvcXy7Nb2+Ar7LTsHNn34IULfjni7w==", "dev": true, "dependencies": { + "@noble/hashes": "^1.3.1", "base64-arraybuffer": "^0.2.0", - "bignumber.js": "^9.0.0", "borc": "^2.1.1", - "js-sha256": "0.9.0", "simple-cbor": "^0.4.1" }, "peerDependencies": { - "@dfinity/candid": "^0.11.1", - "@dfinity/principal": "^0.11.1" + "@dfinity/candid": "^0.19.3", + "@dfinity/principal": "^0.19.3" } }, "node_modules/@dfinity/candid": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.11.3.tgz", - "integrity": "sha512-xX7xNj2lLt7SIlvy0sqNp4fpcTD/xnwEu9APj0tnIF64cnsxIiS12T1Z8jl9g80jCQ1CbRPQf4cfsOfS3Cd2OA==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.19.3.tgz", + "integrity": "sha512-yXfbLSWTeRd4G0bLLxYoDqpXH3Jim0P+1PPZOoktXNC1X1hB+ea3yrZebX75t4GVoQK7123F7mxWHiPjuV2tQQ==", "dev": true, - "peer": true + "peer": true, + "peerDependencies": { + "@dfinity/principal": "^0.19.3" + } + }, + "node_modules/@dfinity/identity-secp256k1": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/identity-secp256k1/-/identity-secp256k1-1.2.0.tgz", + "integrity": "sha512-QAsVycTLY0HH5OS/Ub8G0A70WZb9nkJR9fzZywUIAKpFRY8ZXHiXrT/ifM6AqY9L/83l/ywrwuSKbHVngshpkw==", + "dependencies": { + "@dfinity/agent": "^1.2.0", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "asn1js": "^3.0.5", + "bip39": "^3.1.0", + "bs58check": "^3.0.1", + "hdkey": "^2.1.0" + } + }, + "node_modules/@dfinity/identity-secp256k1/node_modules/@dfinity/agent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-1.2.0.tgz", + "integrity": "sha512-i9mH0PO73nrhLc9lZv14SWr4muyMcs6uqqlG2SHQtRUFRXbqj4DOhKsU0Sm+kC8eWYCSu65WPKPYwwAR7YM6ug==", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "base64-arraybuffer": "^0.2.0", + "borc": "^2.1.1", + "buffer": "^6.0.3", + "simple-cbor": "^0.4.1" + }, + "peerDependencies": { + "@dfinity/candid": "^1.2.0", + "@dfinity/principal": "^1.2.0" + } + }, + "node_modules/@dfinity/identity-secp256k1/node_modules/@dfinity/candid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-1.2.0.tgz", + "integrity": "sha512-L6gV3ODIFC9qNenq3zuRvHrTwH36IM5utVH2wMS5f5eIUeG9fNe+avYLRPBUJwdeX7cM7xhvDgE/m/aN2cZvKQ==", + "peer": true, + "peerDependencies": { + "@dfinity/principal": "^1.2.0" + } + }, + "node_modules/@dfinity/identity-secp256k1/node_modules/@dfinity/principal": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-1.2.0.tgz", + "integrity": "sha512-7eurqPDL5ptlTTLMJPeiO75FAumXHsWEWDVQaN6XpA3aZtmofNK4Sb5g5Ne9syeuoCJcW3mFBbbFtFNxggxu+g==", + "peer": true, + "dependencies": { + "@noble/hashes": "^1.3.1" + } + }, + "node_modules/@dfinity/identity-secp256k1/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } }, "node_modules/@dfinity/principal": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.11.3.tgz", - "integrity": "sha512-+AJGDJ+RsveybSdxuTQFr2DPNZFpPfXnyixAOFWWdElVniSwnO/SwqQChR0AWvJdy/fKqoAXK+ZzyLG0uqSetA==", + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.19.3.tgz", + "integrity": "sha512-+nixVvdGt7ECxRvLXDXsvU9q9sSPssBtDQ4bXa149SK6gcYcmZ6lfWIi3DJNqj3tGROxILVBsguel9tECappsA==", "dev": true, - "peer": true + "peer": true, + "dependencies": { + "@noble/hashes": "^1.3.1" + } }, - "node_modules/@swc/core": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.2.151.tgz", - "integrity": "sha512-oHgqKwK/Djv765zUHPiGqfMCaKIxXTgQyyCUBKLBQfAJwe/7FVobQ2fghBp4FsZA/NE1LZBmMPpRZNQwlGjeHw==", - "bin": { - "swcx": "run_swcx.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-android-arm-eabi": "1.2.151", - "@swc/core-android-arm64": "1.2.151", - "@swc/core-darwin-arm64": "1.2.151", - "@swc/core-darwin-x64": "1.2.151", - "@swc/core-freebsd-x64": "1.2.151", - "@swc/core-linux-arm-gnueabihf": "1.2.151", - "@swc/core-linux-arm64-gnu": "1.2.151", - "@swc/core-linux-arm64-musl": "1.2.151", - "@swc/core-linux-x64-gnu": "1.2.151", - "@swc/core-linux-x64-musl": "1.2.151", - "@swc/core-win32-arm64-msvc": "1.2.151", - "@swc/core-win32-ia32-msvc": "1.2.151", - "@swc/core-win32-x64-msvc": "1.2.151" - } - }, - "node_modules/@swc/core-android-arm-eabi": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.151.tgz", - "integrity": "sha512-Suk3IcHdha33K4hq9tfBCwkXJsENh7kjXCseLqL8Yvy8QobqkXjf1fcoJxX9BdCmPwsKmIw0ZgCBYR+Hl83M2w==", + "node_modules/@esbuild/android-arm": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.4.tgz", + "integrity": "sha512-uBIbiYMeSsy2U0XQoOGVVcpIktjLMEKa7ryz2RLr7L/vTnANNEsPVAh4xOv7ondGz6ac1zVb0F8Jx20rQikffQ==", "cpu": [ "arm" ], @@ -113,13 +175,13 @@ "android" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-android-arm64": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-android-arm64/-/core-android-arm64-1.2.151.tgz", - "integrity": "sha512-HZVy69dVWT5RgrMJMRK5aiicPmhzkyCHAexApYAHYLgAIhsxL7uoAIPmuRKRkrKNJjrwsWL7H27bBH5bddRDvg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.4.tgz", + "integrity": "sha512-mRsi2vJsk4Bx/AFsNBqOH2fqedxn5L/moT58xgg51DjX1la64Z3Npicut2VbhvDFO26qjWtPMsVxCd80YTFVeg==", "cpu": [ "arm64" ], @@ -128,13 +190,28 @@ "android" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.151.tgz", - "integrity": "sha512-Ql7rXMu+IC76TemRtkt+opl5iSpX2ApAXVSfvf6afNVTrfTKLpDwiR3ySRRlG0FnNIv6TfOCJpHf655xp01S/g==", + "node_modules/@esbuild/android-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.4.tgz", + "integrity": "sha512-4iPufZ1TMOD3oBlGFqHXBpa3KFT46aLl6Vy7gwed0ZSYgHaZ/mihbYb4t7Z9etjkC9Al3ZYIoOaHrU60gcMy7g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.4.tgz", + "integrity": "sha512-Lviw8EzxsVQKpbS+rSt6/6zjn9ashUZ7Tbuvc2YENgRl0yZTktGlachZ9KMJUsVjZEGFVu336kl5lBgDN6PmpA==", "cpu": [ "arm64" ], @@ -143,13 +220,13 @@ "darwin" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.2.151.tgz", - "integrity": "sha512-N1OBIB7xatR5eybLo91ZhvMJMxT0zxRQURV/a9I8o5CyP4iLd1k8gmrYvBbtj08ohS8F9z7k/dFjxk/9ve5Drw==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.4.tgz", + "integrity": "sha512-YHbSFlLgDwglFn0lAO3Zsdrife9jcQXQhgRp77YiTDja23FrC2uwnhXMNkAucthsf+Psr7sTwYEryxz6FPAVqw==", "cpu": [ "x64" ], @@ -158,13 +235,28 @@ "darwin" ], "engines": { - "node": ">=10" + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.4.tgz", + "integrity": "sha512-vz59ijyrTG22Hshaj620e5yhs2dU1WJy723ofc+KUgxVCM6zxQESmWdMuVmUzxtGqtj5heHyB44PjV/HKsEmuQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/@swc/core-freebsd-x64": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.151.tgz", - "integrity": "sha512-WVIRiDzuz+/W7BMjVtg1Cmk1+zmDT18Qq+Ygr9J6aFQ1JQUkLEE1pvtkGD3JIEa6Jhz/VwM6AFHtY5o1CrZ21w==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.4.tgz", + "integrity": "sha512-3sRbQ6W5kAiVQRBWREGJNd1YE7OgzS0AmOGjDmX/qZZecq8NFlQsQH0IfXjjmD0XtUYqr64e0EKNFjMUlPL3Cw==", "cpu": [ "x64" ], @@ -173,13 +265,13 @@ "freebsd" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.151.tgz", - "integrity": "sha512-pfBrIUwu3cR/M7DzDCUJAw9jFKXvJ/Ge8auFk07lRb+JcDnPm0XxLyrLqGvNQWdcHgXeXfmnS4fMQxdb9GUN1w==", + "node_modules/@esbuild/linux-arm": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.4.tgz", + "integrity": "sha512-z/4ArqOo9EImzTi4b6Vq+pthLnepFzJ92BnofU1jgNlcVb+UqynVFdoXMCFreTK7FdhqAzH0vmdwW5373Hm9pg==", "cpu": [ "arm" ], @@ -188,13 +280,13 @@ "linux" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.151.tgz", - "integrity": "sha512-M+BTkTdPY7gteM+0dYz9wrU/j9taL4ccqPEHkDEKP21lS24y99UtuKsvdBLzDm/6ShBVLFAkgIBPu5cEb7y6ig==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.4.tgz", + "integrity": "sha512-ZWmWORaPbsPwmyu7eIEATFlaqm0QGt+joRE9sKcnVUG3oBbr/KYdNE2TnkzdQwX6EDRdg/x8Q4EZQTXoClUqqA==", "cpu": [ "arm64" ], @@ -203,43 +295,103 @@ "linux" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.151.tgz", - "integrity": "sha512-7A+yTtSvPJVwO8X1cxUbD/PVCx8G9MKn83G9pH/r+9sQMBXqxyw6/NR0DG6nMMiyOmJkmYWgh5mO47BN7WC4dQ==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.4.tgz", + "integrity": "sha512-EGc4vYM7i1GRUIMqRZNCTzJh25MHePYsnQfKDexD8uPTCm9mK56NIL04LUfX2aaJ+C9vyEp2fJ7jbqFEYgO9lQ==", "cpu": [ - "arm64" + "ia32" ], "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.151.tgz", - "integrity": "sha512-ORlbN3wf1w0IQGjGToYYC/hV/Vwfcs88Ohfxc4X+IQaw/VxKG6/XT65c0btK640F2TVhvhH1MbYFJJlsycsW7g==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.4.tgz", + "integrity": "sha512-WVhIKO26kmm8lPmNrUikxSpXcgd6HDog0cx12BUfA2PkmURHSgx9G6vA19lrlQOMw+UjMZ+l3PpbtzffCxFDRg==", "cpu": [ - "x64" + "loong64" ], "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.151.tgz", - "integrity": "sha512-r6odKE3+9+ReVdnNTZnICt5tscyFFtP4GFcmPQzBSlVoD9LZX6O4WeOlFXn77rVK/+205n2ag/KkKgZH+vdPuQ==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.4.tgz", + "integrity": "sha512-keYY+Hlj5w86hNp5JJPuZNbvW4jql7c1eXdBUHIJGTeN/+0QFutU3GrS+c27L+NTmzi73yhtojHk+lr2+502Mw==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.4.tgz", + "integrity": "sha512-tQ92n0WMXyEsCH4m32S21fND8VxNiVazUbU4IUGVXQpWiaAxOBvtOtbEt3cXIV3GEBydYsY8pyeRMJx9kn3rvw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.4.tgz", + "integrity": "sha512-tRRBey6fG9tqGH6V75xH3lFPpj9E8BH+N+zjSUCnFOX93kEzqS0WdyJHkta/mmJHn7MBaa++9P4ARiU4ykjhig==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.4.tgz", + "integrity": "sha512-152aLpQqKZYhThiJ+uAM4PcuLCAOxDsCekIbnGzPKVBRUDlgaaAfaUl5NYkB1hgY6WN4sPkejxKlANgVcGl9Qg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.4.tgz", + "integrity": "sha512-Mi4aNA3rz1BNFtB7aGadMD0MavmzuuXNTaYL6/uiYIs08U7YMPETpgNn5oue3ICr+inKwItOwSsJDYkrE9ekVg==", "cpu": [ "x64" ], @@ -248,13 +400,58 @@ "linux" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.151.tgz", - "integrity": "sha512-jnjJTNHpLhBaPwRgiKv1TdrMljL88ePqMCdVMantyd7yl4lP0D2e5/xR9ysR9S4EGcUnOyo9w8WUYhx/TioMZw==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.4.tgz", + "integrity": "sha512-9+Wxx1i5N/CYo505CTT7T+ix4lVzEdz0uCoYGxM5JDVlP2YdDC1Bdz+Khv6IbqmisT0Si928eAxbmGkcbiuM/A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.4.tgz", + "integrity": "sha512-MFsHleM5/rWRW9EivFssop+OulYVUoVcqkyOkjiynKBCGBj9Lihl7kh9IzrreDyXa4sNkquei5/DTP4uCk25xw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.4.tgz", + "integrity": "sha512-6Xq8SpK46yLvrGxjp6HftkDwPP49puU4OF0hEL4dTxqCbfx09LyrbUj/D7tmIRMj5D5FCUPksBbxyQhp8tmHzw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.4.tgz", + "integrity": "sha512-PkIl7Jq4mP6ke7QKwyg4fD4Xvn8PXisagV/+HntWoDEdmerB2LTukRZg728Yd1Fj+LuEX75t/hKXE2Ppk8Hh1w==", "cpu": [ "arm64" ], @@ -263,13 +460,13 @@ "win32" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.151.tgz", - "integrity": "sha512-hSCxAiyDDXKvdUExj4jSIhzWFePqoqak1qdNUjlhEhEinDG8T8PTRCLalyW6fqZDcLf6Tqde7H79AqbfhRlYGQ==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.4.tgz", + "integrity": "sha512-ga676Hnvw7/ycdKB53qPusvsKdwrWzEyJ+AtItHGoARszIqvjffTwaaW3b2L6l90i7MO9i+dlAW415INuRhSGg==", "cpu": [ "ia32" ], @@ -278,13 +475,13 @@ "win32" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.2.151", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.151.tgz", - "integrity": "sha512-HOkqcJWCChps83Maj0M5kifPDuZ2sGPqpLM67poawspTFkBh0QJ9TMmxW1doQw+74cqsTpRi1ewr/KhsN18i5g==", + "node_modules/@esbuild/win32-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.4.tgz", + "integrity": "sha512-HP0GDNla1T3ZL8Ko/SHAS2GgtjOg+VmWnnYLhuTksr++EnduYB0f3Y2LzHsUwb2iQ13JGoY6G3R8h6Du/WG6uA==", "cpu": [ "x64" ], @@ -293,957 +490,2111 @@ "win32" ], "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/@types/hex-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/hex-array/-/hex-array-1.0.0.tgz", - "integrity": "sha512-DiHkbvklZELsTQyuATAvrQW2CAHJX87VK8olXxQaREB+KGgYx/LiBERKRnhXC3T1lST4vuOwUrmrOyGloDS4gA==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, - "node_modules/@types/node": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz", - "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==" - }, - "node_modules/@types/ripemd160": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/ripemd160/-/ripemd160-2.0.0.tgz", - "integrity": "sha512-LD6AO/+8cAa1ghXax9NG9iPDLPUEGB2WWPjd//04KYfXxTwHvlDEfL0NRjrM5z9XWBi6WbKw75Are0rDyn3PSA==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "dependencies": { - "@types/node": "*" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "bin": { - "acorn": "bin/acorn" + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "engines": { - "node": ">=0.4.0" + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", "engines": { - "node": ">=0.4.0" + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "node_modules/@swc/core": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.91.tgz", + "integrity": "sha512-r950d0fdlZ8qbSDyvApn3HyCojiZE8xpgJzQvypeMi32dalYwugdJKWyLB55JIGMRGJ8+lmVvY4MPGkSR3kXgA==", + "hasInstallScript": true, + "optional": true, + "peer": true, "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/azle": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/azle/-/azle-0.16.3.tgz", - "integrity": "sha512-6JWTLCg261bKZACL0BiCZQ93gHhoB0dv5g6Tn1ixAkuIlWASyQjvpth4PJ/e9AkNh1kI3sltwjqVfrmtll48qg==", - "dependencies": { - "@dfinity/principal": "0.11.2", - "@swc/core": "1.2.151", - "azle-syn": "0.0.0", - "esbuild": "0.14.25", - "fs-extra": "10.0.1", - "js-sha256": "0.9.0", - "ts-node": "10.3.1", - "typescript": "4.4.4" - }, - "bin": { - "azle": "bin.js" - } - }, - "node_modules/azle-syn": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/azle-syn/-/azle-syn-0.0.0.tgz", - "integrity": "sha512-fWExJb5/hOEJOuBQ8hMMHRs9WryYeLLa9/ydqPWxbwjMEpE8RKdU1dTK6mdZtzNMhbeHdyne2pU1iVKiKmImGw==" - }, - "node_modules/azle/node_modules/@dfinity/principal": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.11.2.tgz", - "integrity": "sha512-vReWruqIl16yQeKOrCLDLDf2aTEJsXcKeW9qbwVfmV0kwLNE3B2Z6tbRjYbY7s+KwpysD5B1b48ZbIwI00BeyQ==" - }, - "node_modules/azle/node_modules/ts-node": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.1.tgz", - "integrity": "sha512-Yw3W2mYzhHfCHOICGNJqa0i+rbL0rAyg7ZIHxU+K4pgY8gd2Lh1j+XbHCusJMykbj6RZMJVOY0MlHVd+GOivcw==", - "dependencies": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "yn": "3.1.1" + "type": "opencollective", + "url": "https://opencollective.com/swc" }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.91", + "@swc/core-darwin-x64": "1.3.91", + "@swc/core-linux-arm-gnueabihf": "1.3.91", + "@swc/core-linux-arm64-gnu": "1.3.91", + "@swc/core-linux-arm64-musl": "1.3.91", + "@swc/core-linux-x64-gnu": "1.3.91", + "@swc/core-linux-x64-musl": "1.3.91", + "@swc/core-win32-arm64-msvc": "1.3.91", + "@swc/core-win32-ia32-msvc": "1.3.91", + "@swc/core-win32-x64-msvc": "1.3.91" }, "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "@swc/helpers": "^0.5.0" }, "peerDependenciesMeta": { - "@swc/core": { + "@swc/helpers": { "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/azle/node_modules/typescript": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", - "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/base-x": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", - "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" - }, - "node_modules/base64-arraybuffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", - "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" } - ] - }, - "node_modules/bignumber.js": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", - "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/borc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", - "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", - "dev": true, - "dependencies": { - "bignumber.js": "^9.0.0", - "buffer": "^5.5.0", - "commander": "^2.15.0", - "ieee754": "^1.1.13", - "iso-url": "~0.4.7", - "json-text-sequence": "~0.1.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/bs58": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", - "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", - "dependencies": { - "base-x": "^4.0.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.91.tgz", + "integrity": "sha512-7kHGiQ1he5khcEeJuHDmLZPM3rRL/ith5OTmV6bOPsoHi46kLeixORW+ts1opC3tC9vu6xbk16xgX0QAJchc1w==", + "cpu": [ + "arm64" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "optional": true, + "os": [ + "darwin" ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delimit-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", - "integrity": "sha512-a02fiQ7poS5CnjiJBAsjGLPp5EwVoGHNeu9sziBd9huppRfsAFIpv5zNLv0V1gbop53ilngAf5Kf331AwcoRBQ==", - "dev": true - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "peer": true, "engines": { - "node": ">=0.3.1" + "node": ">=10" } }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==" - }, - "node_modules/esbuild": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.25.tgz", - "integrity": "sha512-4JHEIOMNFvK09ziiL+iVmldIhLbn49V4NAVo888tcGFKedEZY/Y8YapfStJ6zSE23tzYPKxqKwQBnQoIO0BI/Q==", - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "esbuild-android-64": "0.14.25", - "esbuild-android-arm64": "0.14.25", - "esbuild-darwin-64": "0.14.25", - "esbuild-darwin-arm64": "0.14.25", - "esbuild-freebsd-64": "0.14.25", - "esbuild-freebsd-arm64": "0.14.25", - "esbuild-linux-32": "0.14.25", - "esbuild-linux-64": "0.14.25", - "esbuild-linux-arm": "0.14.25", - "esbuild-linux-arm64": "0.14.25", - "esbuild-linux-mips64le": "0.14.25", - "esbuild-linux-ppc64le": "0.14.25", - "esbuild-linux-riscv64": "0.14.25", - "esbuild-linux-s390x": "0.14.25", - "esbuild-netbsd-64": "0.14.25", - "esbuild-openbsd-64": "0.14.25", - "esbuild-sunos-64": "0.14.25", - "esbuild-windows-32": "0.14.25", - "esbuild-windows-64": "0.14.25", - "esbuild-windows-arm64": "0.14.25" - } - }, - "node_modules/esbuild-android-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.25.tgz", - "integrity": "sha512-L5vCUk7TzFbBnoESNoXjU3x9+/+7TDIE/1mTfy/erAfvZAqC+S3sp/Qa9wkypFMcFvN9FzvESkTlpeQDolREtQ==", + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.91.tgz", + "integrity": "sha512-8SpU18FbFpZDVzsHsAwdI1thF/picQGxq9UFxa8W+T9SDnbsqwFJv/6RqKJeJoDV6qFdl2OLjuO0OL7xrp0qnQ==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ - "android" + "darwin" ], + "peer": true, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-android-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.25.tgz", - "integrity": "sha512-4jv5xPjM/qNm27T5j3ZEck0PvjgQtoMHnz4FzwF5zNP56PvY2CT0WStcAIl6jNlsuDdN63rk2HRBIsO6xFbcFw==", + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.91.tgz", + "integrity": "sha512-fOq4Cy8UbwX1yf0WB0d8hWZaIKCnPtPGguRqdXGLfwvhjZ9SIErT6PnmGTGRbQCNCIkOZWHKyTU0r8t2dN3haQ==", "cpu": [ - "arm64" + "arm" ], + "dev": true, "optional": true, "os": [ - "android" + "linux" ], + "peer": true, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-darwin-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.25.tgz", - "integrity": "sha512-TGp8tuudIxOyWd1+8aYPxQmC1ZQyvij/AfNBa35RubixD0zJ1vkKHVAzo0Zao1zcG6pNqiSyzfPto8vmg0s7oA==", + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.91.tgz", + "integrity": "sha512-fki4ioRP/Esy4vdp8T34RCV+V9dqkRmOt763pf74pdiyFV2dPLXa5lnw/XvR1RTfPGknrYgjEQLCfZlReTryRw==", "cpu": [ - "x64" + "arm64" ], + "dev": true, "optional": true, "os": [ - "darwin" + "linux" ], + "peer": true, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-darwin-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.25.tgz", - "integrity": "sha512-oTcDgdm0MDVEmw2DWu8BV68pYuImpFgvWREPErBZmNA4MYKGuBRaCiJqq6jZmBR1x+3y1DWCjez+5uLtuAm6mw==", + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.91.tgz", + "integrity": "sha512-XrG+DUUqNtfVLcJ20imby7fpBwQNG5VsEQBzQndSonPyUOa2YkTbBb60YDondfQGDABopuHH8gHN8o2H2/VCnQ==", "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ - "darwin" + "linux" ], + "peer": true, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-freebsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.25.tgz", - "integrity": "sha512-ueAqbnMZ8arnuLH8tHwTCQYeptnHOUV7vA6px6j4zjjQwDx7TdP7kACPf3TLZLdJQ3CAD1XCvQ2sPhX+8tacvQ==", + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.91.tgz", + "integrity": "sha512-d11bYhX+YPBr/Frcjc6eVn3C0LuS/9U1Li9EmQ+6s9EpYtYRl2ygSlC8eueLbaiazBnCVYFnc8bU4o0kc5B9sw==", "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ - "freebsd" + "linux" ], + "peer": true, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/esbuild-freebsd-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.25.tgz", - "integrity": "sha512-+ZVWud2HKh+Ob6k/qiJWjBtUg4KmJGGmbvEXXW1SNKS7hW7HU+Zq2ZCcE1akFxOPkVB+EhOty/sSek30tkCYug==", + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.91.tgz", + "integrity": "sha512-2SRp5Dke2P4jCQePkDx9trkkTstnRpZJVw5r3jvYdk0zeO6iC4+ZPvvoWXJLigqQv/fZnIiSUfJ6ssOoaEqTzQ==", "cpu": [ - "arm64" + "x64" ], + "dev": true, "optional": true, "os": [ - "freebsd" + "linux" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.91.tgz", + "integrity": "sha512-l9qKXikOxj42UIjbeZpz9xtBmr736jOMqInNP8mVF2/U+ws5sI8zJjcOFFtfis4ru7vWCXhB1wtltdlJYO2vGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.91.tgz", + "integrity": "sha512-+s+52O0QVPmzOgjEe/rcb0AK6q/J7EHKwAyJCu/FaYO9df5ovE0HJjSKP6HAF0dGPO5hkENrXuNGujofUH9vtQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.91.tgz", + "integrity": "sha512-7u9HDQhjUC3Gv43EFW84dZtduWCSa4MgltK+Sp9zEGti6WXqDPu/ESjvDsQEVYTBEMEvZs/xVAXPgLVHorV5nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "optional": true, + "peer": true + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz", + "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==" + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz", + "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/azle": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/azle/-/azle-0.21.1.tgz", + "integrity": "sha512-pPAMmgs4X7joasm4YnfZibnEEDgvtYwfpScfSqcOgRLYJnRwil+xgnkIns/F3iAaRoRmy84pPKPIN5J3ysHQRw==", + "dependencies": { + "@dfinity/agent": "^1.1.0", + "@dfinity/identity-secp256k1": "^1.1.0", + "@types/uuid": "^9.0.4", + "buffer": "^6.0.3", + "chokidar": "^3.6.0", + "crypto-browserify": "^3.12.0", + "esbuild": "^0.19.3", + "ethers": "^6.11.1", + "fs-extra": "10.0.1", + "hash-of-directory": "^1.0.1", + "http-message-parser": "^0.0.34", + "js-sha256": "0.9.0", + "net": "^1.0.2", + "pako": "^2.1.0", + "text-encoding": "0.7.0", + "ts-node": "10.3.1", + "typescript": "^5.2.2", + "uuid": "^9.0.1" + }, + "bin": { + "azle": "bin.js" + } + }, + "node_modules/azle/node_modules/@dfinity/agent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-1.2.0.tgz", + "integrity": "sha512-i9mH0PO73nrhLc9lZv14SWr4muyMcs6uqqlG2SHQtRUFRXbqj4DOhKsU0Sm+kC8eWYCSu65WPKPYwwAR7YM6ug==", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "base64-arraybuffer": "^0.2.0", + "borc": "^2.1.1", + "buffer": "^6.0.3", + "simple-cbor": "^0.4.1" + }, + "peerDependencies": { + "@dfinity/candid": "^1.2.0", + "@dfinity/principal": "^1.2.0" + } + }, + "node_modules/azle/node_modules/@dfinity/candid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-1.2.0.tgz", + "integrity": "sha512-L6gV3ODIFC9qNenq3zuRvHrTwH36IM5utVH2wMS5f5eIUeG9fNe+avYLRPBUJwdeX7cM7xhvDgE/m/aN2cZvKQ==", + "peer": true, + "peerDependencies": { + "@dfinity/principal": "^1.2.0" + } + }, + "node_modules/azle/node_modules/@dfinity/principal": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-1.2.0.tgz", + "integrity": "sha512-7eurqPDL5ptlTTLMJPeiO75FAumXHsWEWDVQaN6XpA3aZtmofNK4Sb5g5Ne9syeuoCJcW3mFBbbFtFNxggxu+g==", + "peer": true, + "dependencies": { + "@noble/hashes": "^1.3.1" + } + }, + "node_modules/azle/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/azle/node_modules/ts-node": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.1.tgz", + "integrity": "sha512-Yw3W2mYzhHfCHOICGNJqa0i+rbL0rAyg7ZIHxU+K4pgY8gd2Lh1j+XbHCusJMykbj6RZMJVOY0MlHVd+GOivcw==", + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, + "node_modules/bitcoinjs-lib": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz", + "integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/borc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", + "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", + "dependencies": { + "bignumber.js": "^9.0.0", + "buffer": "^5.5.0", + "commander": "^2.15.0", + "ieee754": "^1.1.13", + "iso-url": "~0.4.7", + "json-text-sequence": "~0.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.4", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delimit-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", + "integrity": "sha512-a02fiQ7poS5CnjiJBAsjGLPp5EwVoGHNeu9sziBd9huppRfsAFIpv5zNLv0V1gbop53ilngAf5Kf331AwcoRBQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/ecpair": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", + "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", + "dependencies": { + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.4.tgz", + "integrity": "sha512-x7jL0tbRRpv4QUyuDMjONtWFciygUxWaUM1kMX2zWxI0X2YWOt7MSA0g4UdeSiHM8fcYVzpQhKYOycZwxTdZkA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.4", + "@esbuild/android-arm64": "0.19.4", + "@esbuild/android-x64": "0.19.4", + "@esbuild/darwin-arm64": "0.19.4", + "@esbuild/darwin-x64": "0.19.4", + "@esbuild/freebsd-arm64": "0.19.4", + "@esbuild/freebsd-x64": "0.19.4", + "@esbuild/linux-arm": "0.19.4", + "@esbuild/linux-arm64": "0.19.4", + "@esbuild/linux-ia32": "0.19.4", + "@esbuild/linux-loong64": "0.19.4", + "@esbuild/linux-mips64el": "0.19.4", + "@esbuild/linux-ppc64": "0.19.4", + "@esbuild/linux-riscv64": "0.19.4", + "@esbuild/linux-s390x": "0.19.4", + "@esbuild/linux-x64": "0.19.4", + "@esbuild/netbsd-x64": "0.19.4", + "@esbuild/openbsd-x64": "0.19.4", + "@esbuild/sunos-x64": "0.19.4", + "@esbuild/win32-arm64": "0.19.4", + "@esbuild/win32-ia32": "0.19.4", + "@esbuild/win32-x64": "0.19.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ethers": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.11.1.tgz", + "integrity": "sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + }, + "node_modules/ethers/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" ], "engines": { - "node": ">=12" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-prop": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/get-prop/-/get-prop-0.0.10.tgz", + "integrity": "sha512-XRSGBgcIisSMLJ/dwe1y/eMm9yzLicEJKmEXA3ArBkDkCW2nyRroLOoKIz+SdxuG5SI7ym2QHaTU5ifCl7MTDg==" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-of-directory": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hash-of-directory/-/hash-of-directory-1.0.1.tgz", + "integrity": "sha512-PX6VaxD6JK8R4113ChdTtEnWIo2XA9mz4yrtGBuUGUKtWCj6iWWYj/qwjdfs3Zgm+FdiNj0Vmt4VwPlwxx8WHw==" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/esbuild-linux-32": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.25.tgz", - "integrity": "sha512-3OP/lwV3kCzEz45tobH9nj+uE4ubhGsfx+tn0L26WAGtUbmmcRpqy7XRG/qK7h1mClZ+eguIANcQntYMdYklfw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/hdkey": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hdkey/-/hdkey-2.1.0.tgz", + "integrity": "sha512-i9Wzi0Dy49bNS4tXXeGeu0vIcn86xXdPQUpEYg+SO1YiO8HtomjmmRMaRyqL0r59QfcD4PfVbSF3qmsWFwAemA==", + "dependencies": { + "bs58check": "^2.1.2", + "ripemd160": "^2.0.2", + "safe-buffer": "^5.1.1", + "secp256k1": "^4.0.0" } }, - "node_modules/esbuild-linux-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.25.tgz", - "integrity": "sha512-+aKHdHZmX9qwVlQmu5xYXh7GsBFf4TWrePgeJTalhXHOG7NNuUwoHmketGiZEoNsWyyqwH9rE5BC+iwcLY30Ug==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/hdkey/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" } }, - "node_modules/esbuild-linux-arm": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.25.tgz", - "integrity": "sha512-aTLcE2VBoLydL943REcAcgnDi3bHtmULSXWLbjtBdtykRatJVSxKMjK9YlBXUZC4/YcNQfH7AxwVeQr9fNxPhw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/hdkey/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" } }, - "node_modules/esbuild-linux-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.25.tgz", - "integrity": "sha512-UxfenPx/wSZx55gScCImPtXekvZQLI2GW3qe5dtlmU7luiqhp5GWPzGeQEbD3yN3xg/pHc671m5bma5Ns7lBHw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/hdkey/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" } }, - "node_modules/esbuild-linux-mips64le": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.25.tgz", - "integrity": "sha512-wLWYyqVfYx9Ur6eU5RT92yJVsaBGi5RdkoWqRHOqcJ38Kn60QMlcghsKeWfe9jcYut8LangYZ98xO1LxIoSXrQ==", - "cpu": [ - "mips64el" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" } }, - "node_modules/esbuild-linux-ppc64le": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.25.tgz", - "integrity": "sha512-0dR6Csl6Zas3g4p9ULckEl8Mo8IInJh33VCJ3eaV1hj9+MHGdmDOakYMN8MZP9/5nl+NU/0ygpd14cWgy8uqRw==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ], + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, "engines": { - "node": ">=12" + "node": ">= 0.8" } }, - "node_modules/esbuild-linux-riscv64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.25.tgz", - "integrity": "sha512-J4d20HDmTrgvhR0bdkDhvvJGaikH3LzXQnNaseo8rcw9Yqby9A90gKUmWpfwqLVNRILvNnAmKLfBjCKU9ajg8w==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ], + "node_modules/http-message-parser": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/http-message-parser/-/http-message-parser-0.0.34.tgz", + "integrity": "sha512-KABKXT347AYvQoaMZg9/K+/GqW6gfB4pKCiTyMUYnosfkdkaBkrXE/cWGSLk5jvD5tiDeLFlYSHLhhPhQKbRrA==", + "dependencies": { + "buffer": "^4.9.1", + "concat-stream": "^1.5.1", + "get-prop": "0.0.10", + "minimist": "^1.2.0", + "stream-buffers": "^3.0.0" + }, + "bin": { + "http-message-parser": "bin/http-message-parser.js" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/esbuild-linux-s390x": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.25.tgz", - "integrity": "sha512-YI2d5V6nTE73ZnhEKQD7MtsPs1EtUZJ3obS21oxQxGbbRw1G+PtJKjNyur+3t6nzHP9oTg6GHQ3S3hOLLmbDIQ==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "node_modules/http-message-parser/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, - "node_modules/esbuild-netbsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.25.tgz", - "integrity": "sha512-TKIVgNWLUOkr+Exrye70XTEE1lJjdQXdM4tAXRzfHE9iBA7LXWcNtVIuSnphTqpanPzTDFarF0yqq4kpbC6miA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "netbsd" - ], + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/esbuild-openbsd-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.25.tgz", - "integrity": "sha512-QgFJ37A15D7NIXBTYEqz29+uw3nNBOIyog+3kFidANn6kjw0GHZ0lEYQn+cwjyzu94WobR+fes7cTl/ZYlHb1A==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "engines": { - "node": ">=12" + "node": ">= 0.10" } }, - "node_modules/esbuild-sunos-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.25.tgz", - "integrity": "sha512-rmWfjUItYIVlqr5EnTH1+GCxXiBOC42WBZ3w++qh7n2cS9Xo0lO5pGSG2N+huOU2fX5L+6YUuJ78/vOYvefeFw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "sunos" - ], + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/esbuild-windows-32": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.25.tgz", - "integrity": "sha512-HGAxVUofl3iUIz9W10Y9XKtD0bNsK9fBXv1D55N/ljNvkrAYcGB8YCm0v7DjlwtyS6ws3dkdQyXadbxkbzaKOA==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/esbuild-windows-64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.25.tgz", - "integrity": "sha512-TirEohRkfWU9hXLgoDxzhMQD1g8I2mOqvdQF2RS9E/wbkORTAqJHyh7wqGRCQAwNzdNXdg3JAyhQ9/177AadWA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/esbuild-windows-arm64": { - "version": "0.14.25", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.25.tgz", - "integrity": "sha512-4ype9ERiI45rSh+R8qUoBtaj6kJvUOI7oVLhKqPEpcF4Pa5PpT3hm/mXAyotJHREkHpM87PAJcA442mLnbtlNA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "engines": { - "node": ">=12" + "node": ">=0.12.0" } }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/iso-url": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", + "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", "engines": { - "node": ">=0.8.x" + "node": ">=10" } }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "node_modules/json-text-sequence": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", + "integrity": "sha512-L3mEegEWHRekSHjc7+sc8eJhba9Clq1PZ8kMkzf8OxElhXc8O4TS5MwcVlj9aEbm5dr81N90WHC5nAz3UO971w==", "dependencies": { - "is-callable": "^1.1.3" + "delimit-stream": "0.1.0" } }, - "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, - "engines": { - "node": ">=12" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, - "node_modules/gopd": { + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dependencies": { - "get-intrinsic": "^1.1.3" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "miller-rabin": "bin/miller-rabin" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" }, "engines": { - "node": ">= 0.4.0" + "node": ">=4" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "get-intrinsic": "^1.1.1" + "mime-db": "1.52.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.6" } }, - "node_modules/has-proto": { + "node_modules/minimalistic-assert": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" - }, + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/net": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz", + "integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==" + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "ee-first": "1.1.1" }, "engines": { - "node": ">=4" + "node": ">= 0.8" } }, - "node_modules/hex-array": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hex-array/-/hex-array-1.0.0.tgz", - "integrity": "sha512-dbjcApZ+RBr2DYCqs2gab5So6q4rM5VBqFg8oeOPc/s8PzrD48pf4w9e7DC4G+Gq4CEkHYQxqn7uYAQZKVWo8w==" + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.12" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "engines": { - "node": ">= 0.4" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": { - "has-tostringtag": "^1.0.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.10" } }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6.0.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "side-channel": "^1.0.4" }, "engines": { - "node": ">= 0.4" + "node": ">=0.6" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/iso-url": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", - "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/js-sha256": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" - }, - "node_modules/json-text-sequence": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", - "integrity": "sha512-L3mEegEWHRekSHjc7+sc8eJhba9Clq1PZ8kMkzf8OxElhXc8O4TS5MwcVlj9aEbm5dr81N90WHC5nAz3UO971w==", - "dev": true, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dependencies": { - "delimit-stream": "0.1.0" + "safe-buffer": "^5.1.0" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8" } }, "node_modules/readable-stream": { @@ -1259,6 +2610,17 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -1287,11 +2649,137 @@ } ] }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/simple-cbor": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", - "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==", - "dev": true + "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-buffers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", + "engines": { + "node": ">= 0.10.0" + } }, "node_modules/string_decoder": { "version": "1.3.0", @@ -1301,13 +2789,49 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/text-encoding": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", + "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", + "deprecated": "no longer maintained" + }, + "node_modules/tiny-secp256k1": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz", + "integrity": "sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==", + "dependencies": { + "uint8array-tools": "0.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, "dependencies": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -1318,7 +2842,7 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { @@ -1344,17 +2868,63 @@ } } }, - "node_modules/typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "node_modules/ts-node/node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" + } + }, + "node_modules/uint8array-tools": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", + "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", + "engines": { + "node": ">=14.0.0" } }, "node_modules/universalify": { @@ -1365,16 +2935,12 @@ "node": ">= 10.0.0" } }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" } }, "node_modules/util-deprecate": { @@ -1382,29 +2948,100 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, + "safe-buffer": "^5.1.1" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "engines": { - "node": ">= 0.4" + "node": ">= 0.8" + } + }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/wif/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/wif/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/wif/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "engines": { + "node": ">=10.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/yn": { diff --git a/examples/basic_bitcoin/package.json b/examples/basic_bitcoin/package.json index cda9784ae6..27c2262223 100644 --- a/examples/basic_bitcoin/package.json +++ b/examples/basic_bitcoin/package.json @@ -1,22 +1,24 @@ { "scripts": { - "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", - "test": "ts-node --transpile-only --ignore=false test/test.ts" + "bitcoin": ".bitcoin/bin/bitcoind -conf=$(pwd)/.bitcoin.conf -datadir=$(pwd)/.bitcoin/data --port=18444", + "ic": "dfx start --clean --host 127.0.0.1:8000 --enable-bitcoin", + "install": "./scripts/install.sh", + "deploy": "dfx deploy backend --specified-id bkyz2-fmaaa-aaaaa-qaaaq-cai", + "pre_tests": "ts-node --transpile-only --ignore=false test/pretest.ts", + "test": "BITCOIN_NETWORK=regtest npm run pre_tests && ts-node --transpile-only --ignore=false test/test.ts" }, "dependencies": { - "assert": "2.0.0", - "azle": "0.16.3", - "bs58": "5.0.0", - "events": "3.3.0", - "hex-array": "1.0.0", - "js-sha256": "0.9.0", - "ripemd160": "2.0.2" + "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", + "azle": "^0.21.1", + "bitcoinjs-lib": "^6.1.5", + "ecpair": "^2.1.0", + "express": "^4.18.2", + "tiny-secp256k1": "^2.2.3" }, "devDependencies": { - "@dfinity/agent": "0.11.1", - "@types/hex-array": "1.0.0", - "@types/ripemd160": "2.0.0", - "ts-node": "10.7.0", - "typescript": "4.6.3" + "@dfinity/agent": "^0.19.2", + "@types/express": "^4.17.21", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" } } diff --git a/examples/basic_bitcoin/scripts/install.sh b/examples/basic_bitcoin/scripts/install.sh new file mode 100755 index 0000000000..0bcdbfe762 --- /dev/null +++ b/examples/basic_bitcoin/scripts/install.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Check if .bitcoin directory exists; if not, create it +if [ ! -d ".bitcoin" ]; then + mkdir .bitcoin +fi + +# Check if .bitcoin/data directory exists; if not, create it +if [ ! -d ".bitcoin/data" ]; then + mkdir .bitcoin/data +fi + +# Check if bitcoind executable exists; if not, download and extract it +if [ ! -f ".bitcoin/bin/bitcoind" ]; then + curl -o bitcoin.tar.gz https://bitcoincore.org/bin/bitcoin-core-23.0/bitcoin-23.0-x86_64-linux-gnu.tar.gz + tar xzf bitcoin.tar.gz --overwrite --strip-components=1 --directory=.bitcoin/ bitcoin-23.0/bin/ + rm -rf bitcoin.tar.gz +fi diff --git a/examples/basic_bitcoin/src/bitcoin_api.ts b/examples/basic_bitcoin/src/backend/bitcoin_api.ts similarity index 57% rename from examples/basic_bitcoin/src/bitcoin_api.ts rename to examples/basic_bitcoin/src/backend/bitcoin_api.ts index 6697e3a8ee..45869f74a9 100644 --- a/examples/basic_bitcoin/src/bitcoin_api.ts +++ b/examples/basic_bitcoin/src/backend/bitcoin_api.ts @@ -1,7 +1,4 @@ -// @ts-nocheck - -import { blob, ic, match, nat64, Opt, Vec } from 'azle'; -import { managementCanister } from 'azle/canisters/management'; +import { blob, nat64, serialize, Vec } from 'azle'; import { BitcoinNetwork, GetUtxosResult, @@ -23,19 +20,19 @@ export async function getBalance( network: BitcoinNetwork, address: string ): Promise { - const balanceRes = await managementCanister - .bitcoin_get_balance({ - address, - network, - min_confirmations: Opt.None + const response = await fetch(`icp://aaaaa-aa/bitcoin_get_balance`, { + body: serialize({ + args: [ + { + address, + min_confirmations: [], + network + } + ], + cycles: GET_BALANCE_COST_CYCLES }) - .cycles(GET_BALANCE_COST_CYCLES) - .call(); - - return match(balanceRes, { - Ok: (ok) => ok, - Err: (err) => ic.trap(err) }); + return await response.json(); } /// Returns the UTXOs of the given bitcoin address. @@ -48,19 +45,19 @@ export async function getUtxos( network: BitcoinNetwork, address: string ): Promise { - const utxosRes = await managementCanister - .bitcoin_get_utxos({ - address, - network, - filter: Opt.None + const response = await fetch(`icp://aaaaa-aa/bitcoin_get_utxos`, { + body: serialize({ + args: [ + { + address, + filter: [], + network + } + ], + cycles: GET_UTXOS_COST_CYCLES }) - .cycles(GET_UTXOS_COST_CYCLES) - .call(); - - return match(utxosRes, { - Ok: (ok) => ok, - Err: (err) => ic.trap(err) }); + return await response.json(); } /// Returns the 100 fee percentiles measured in millisatoshi/byte. @@ -71,17 +68,20 @@ export async function getUtxos( export async function getCurrentFeePercentiles( network: BitcoinNetwork ): Promise> { - const res = await managementCanister - .bitcoin_get_current_fee_percentiles({ - network - }) - .cycles(GET_CURRENT_FEE_PERCENTILES_CYCLES) - .call(); - - return match(res, { - Ok: (ok) => ok, - Err: (err) => ic.trap(err) - }); + const response = await fetch( + `icp://aaaaa-aa/bitcoin_get_current_fee_percentiles`, + { + body: serialize({ + args: [ + { + network + } + ], + cycles: GET_CURRENT_FEE_PERCENTILES_CYCLES + }) + } + ); + return await response.json(); } /// Sends a (signed) transaction to the bitcoin network. @@ -96,16 +96,22 @@ export async function sendTransaction( SEND_TRANSACTION_BASE_CYCLES + BigInt(transaction.length) * SEND_TRANSACTION_PER_BYTE_CYCLES; - const res = await managementCanister - .bitcoin_send_transaction({ - network, - transaction - }) - .cycles(transactionFee) - .call(); - - return match(res, { - Ok: (ok) => ok, - Err: (err) => ic.trap(err) - }); + try { + await fetch(`icp://aaaaa-aa/bitcoin_send_transaction`, { + body: serialize({ + args: [ + { + transaction, + network + } + ], + cycles: transactionFee + }) + }); + } catch (err: any) { + console.log('There was an error sending the transaction'); + console.log(err.message); + console.log(err); + throw err; + } } diff --git a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts new file mode 100644 index 0000000000..b974a8aa2b --- /dev/null +++ b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts @@ -0,0 +1,415 @@ +//! A demo of a very bare-bones bitcoin "wallet". +//! +//! The wallet here showcases how bitcoin addresses can be be computed +//! and how bitcoin transactions can be signed. It is missing several +//! pieces that any production-grade wallet would have, including: +//! +//! * Support for address types that aren't P2PKH. +//! * Caching spent UTXOs so that they are not reused in future transactions. +//! * Option to set the fee. +import { blob, nat64, Principal, Vec } from 'azle'; +import { + BitcoinNetwork, + MillisatoshiPerByte, + Satoshi, + Utxo +} from 'azle/canisters/management'; +import * as bitcoin from 'bitcoinjs-lib'; +import { networks, Psbt, Transaction } from 'bitcoinjs-lib'; +import { ValidateSigFunction } from 'bitcoinjs-lib/src/psbt'; +import { Buffer } from 'buffer'; + +import * as bitcoinApi from './bitcoin_api'; +import * as ecdsaApi from './ecdsa_api'; +import { BitcoinTxid, DerivationPath } from './types'; +export const ECDSA = 'ecdsa'; +export const SCHNORR = 'schnorr'; + +export type ThresholdKeyInfo = { + derivationPath: Uint8Array[]; + canisterId?: Principal | string; + keyId?: { + curve: 'secp256k1'; + name: 'dfx_test_key' | 'test_key_1' | 'key_1'; + }; +}; + +/// Returns the P2PKH address of this canister at the given derivation path. +export async function getAddress( + network: BitcoinNetwork, + keyName: string, + derivationPath: DerivationPath +): Promise { + const pubkey = await getPublicKey(keyName, derivationPath); + const { address } = bitcoin.payments.p2pkh({ + pubkey, + network: determineNetwork(network) + }); + if (address === undefined) { + throw new Error('Unable to get address from the canister'); + } + return address; +} + +export async function getPublicKey( + keyName: string, + derivationPath: DerivationPath +): Promise { + // Fetch the public key of the given derivation path. + return Buffer.from( + await ecdsaApi.ecdsaPublicKey(keyName, derivationPath), + 'hex' + ); +} + +/// Sends a transaction to the network that transfers the given amount to the +/// given destination, where the source of the funds is the canister itself +/// at the given derivation path. +export async function send( + network: BitcoinNetwork, + derivationPath: Vec, + keyName: string, + dstAddressString: string, + amount: Satoshi, + transactionHashes: TransactionHashes +): Promise { + const signedTransaction = await createSignedTransaction( + network, + derivationPath, + keyName, + dstAddressString, + amount, + transactionHashes + ); + const signedTransactionBytes = signedTransaction.toBuffer(); + + await bitcoinApi.sendTransaction(network, signedTransactionBytes); + + return signedTransaction.getId(); +} + +type TransactionHashes = { + [txid: string]: string; +}; + +export async function createSignedTransaction( + network: BitcoinNetwork, + derivationPath: Vec, + keyName: string, + dstAddress: string, + amount: Satoshi, + transactionHashes: TransactionHashes +): Promise { + const ownAddress = await getAddress(network, keyName, derivationPath); + + const ownUtxos = (await bitcoinApi.getUtxos(network, ownAddress.toString())) + .utxos; + + // Build the transaction that sends `amount` to the destination address. + const transaction = await buildTransaction( + ownAddress, + ownUtxos, + dstAddress, + amount, + await getFeePerByte(network), + keyName, + derivationPath, + determineNetwork(network), + transactionHashes + ); + + // Sign the transaction. + const result = await signTransaction(transaction, keyName, derivationPath); + console.log('bw: createSignedTransaction: by golly I think it worked!!'); + return result; +} + +async function getFeePerByte(network: BitcoinNetwork): Promise { + // Get fee percentiles from previous transactions to estimate our own fee. + const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); + + return feePercentiles.length === 0 + ? // There are no fee percentiles. This case can only happen on a regtest + // network where there are no non-coinbase transactions. In this case, + // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) + 2_000n + : // Choose the 50th percentile for sending fees. + feePercentiles[49]; +} + +// Builds a transaction to send the given `amount` of satoshis to the +// destination address. +async function buildTransaction( + ownAddress: string, + ownUtxos: Vec, + dstAddress: string, + amount: Satoshi, + feePerByte: MillisatoshiPerByte, + keyName: string, + derivationPath: DerivationPath, + network: bitcoin.Network, + transactionHashes: TransactionHashes +): Promise { + // We have a chicken-and-egg problem where we need to know the length + // of the transaction in order to compute its proper fee, but we need + // to know the proper fee in order to figure out the inputs needed for + // the transaction. + // + // We solve this problem iteratively. We start with a fee of zero, build + // and sign a transaction, see what its size is, and then update the fee, + // rebuild the transaction, until the fee is set to the correct amount. + // + // However zero is too small a fee when you serialize the transaction you + // will get the following error while it's checking the transaction: + // 'Fee is too small: expected more than 128 but got 0' + // So I'm going to start at 130 instead + console.log('bw: buildTransaction: Building transaction...'); + let totalFee = 130n; + // eslint-disable-next-line no-constant-condition + while (true) { + let transaction = buildTransactionWithFee( + ownUtxos, + ownAddress, + dstAddress, + amount, + totalFee, + network, + transactionHashes + ); + + console.log('bw: buildTransaction: start signing'); + + // Sign the transaction. In this case, we only care about the size + // of the signed transaction. + const signedTransaction = await signTransaction( + transaction.clone(), + keyName, + derivationPath + ); + + console.log('bw: buildTransaction: end signing'); + + const signedTxBytesLen = BigInt(signedTransaction.byteLength()); + + if ((signedTxBytesLen * feePerByte) / 1_000n === totalFee) { + console.log( + `bw: buildTransaction: Transaction built with fee ${totalFee}.` + ); + return transaction; + } else { + totalFee = (signedTxBytesLen * feePerByte) / 1_000n; + } + } +} + +function buildTransactionWithFee( + ownUtxos: Utxo[], + ownAddress: string, // TDO what about that ownAddress?? Would we only need it for the signing? + destAddress: string, + amount: bigint, + fee: bigint, + network: bitcoin.Network, + transactionHashes: TransactionHashes +): Psbt { + // Select which UTXOs to spend. We naively spend the oldest available UTXOs, + // even if they were previously spent in a transaction. This isn't a + // problem as long as at most one transaction is created per block and + // we're using min_confirmations of 1. + let utxosToSpend: Vec = []; + let totalSpent = 0n; + console.log('bw: buildTransactionWithFee: start'); + for (const utxo of [...ownUtxos].reverse()) { + if (utxosToSpend.includes(utxo)) { + console.log( + "bw: buildTransactionWithFee: CHEEKY THING it's trying to double add!!" + ); + continue; + } + totalSpent += utxo.value; + utxosToSpend.push(utxo); + if (totalSpent >= amount + fee) { + // We have enough inputs to cover the amount we want to spend. + break; + } + } + console.log('bw: buildTransactionWithFee: collected utxos'); + + if (totalSpent < amount + fee) { + throw new Error( + `Insufficient balance: ${totalSpent}, trying to transfer ${amount} satoshi with fee ${fee}` + ); + } + + let transaction = new Psbt({ network }); + transaction.setVersion(2); + + console.log('bw: buildTransactionWithFee: building psbt'); + + try { + console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); + console.log( + `bw: buildTransactionWithFee: Adding ${utxosToSpend.length} inputs to the transaction` + ); + console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); + for (let utxo of utxosToSpend) { + const hash = Buffer.from(utxo.outpoint.txid).reverse(); + const txid = hash.toString('hex'); + console.log( + `bw: buildTransactionWithFee: Adding ${txid}:${utxo.outpoint.vout}` + ); + const nonWitnessUtxo = Buffer.from(transactionHashes[txid], 'hex'); + transaction.addInput({ + hash: Buffer.from(utxo.outpoint.txid), // TOOD if this works clean it up + index: utxo.outpoint.vout, + nonWitnessUtxo + }); + } + } catch (err: any) { + console.log('bw: buildTransactionWithFee: Error:'); + console.log(err.message); + console.log(err); + throw err; + } + + console.log('bw: buildTransactionWithFee: inputs added'); + + transaction.addOutput({ address: destAddress, value: Number(amount) }); + transaction.addOutput({ + address: ownAddress, + value: Number(totalSpent - (amount + fee)) + }); + + console.log('bw: buildTransactionWithFee: transaction built'); + + return transaction; +} + +// function getNonWitnessUtxos(txid: Uint8Array): Buffer { +// const txidHex = Buffer.from(txid).toString('hex'); +// // TODO implement this +// console.log( +// `We need to find a way to get the tx data from this txid: ${txidHex}` +// ); +// throw ''; +// } + +async function signTransaction( + transaction: Psbt, + keyName: string, + derivationPath: DerivationPath +): Promise { + console.log('bw: signTransaction: getPublicKey'); + const publicKey = await getPublicKey(keyName, derivationPath); + const signer: bitcoin.SignerAsync = { + sign: async (hashBuffer) => { + const sec1 = await ecdsaApi.signWithECDSA( + keyName, + derivationPath, + Uint8Array.from(hashBuffer) + ); + console.log( + `bw: signTransaction: signer: got signature back from IC. ${sec1.length}` + ); + const der = sec1ToDer(sec1); + console.log( + `bw: signTransaction: signer: We may have to convert to der. ${der.length}` + ); + return Buffer.from(sec1); // TODO if this works get rid of DER, why did we need this? If it doesn't work why does DER make it longer and what can we do to get it back to 64? + }, + publicKey + }; + const validator: ValidateSigFunction = ( + pubkey: Buffer, + msghash: Buffer, + signature: Buffer + ): boolean => { + // TODO I think if we pass along the ECPair we can validate with that. See the bitcoinjs-lib /create-psbt + console.log( + `bw: signTransaction: validator: pubkey: ${pubkey.toString('hex')}` + ); + console.log( + `bw: signTransaction: validator: msghash: ${msghash.toString( + 'hex' + )}` + ); + console.log( + `bw: signTransaction: validator: sig: ${signature.toString('hex')}` + ); + return true; + }; + try { + console.log('bw: signTransaction: start signing all inputs'); + for (let i = 0; i < transaction.txInputs.length; i++) { + await transaction.signInputAsync(i, signer); + } + // await transaction.signAllInputsAsync(signer); // TODO I bet we could go back to this once we get the inputs figured out + console.log('bw: signTransaction: signed all inputs'); + transaction.validateSignaturesOfAllInputs(validator); + console.log('bw: signTransaction: validated all signatures'); + transaction.finalizeAllInputs(); // TODO what if we don't finalize it? + console.log('bw: signTransaction: finalized'); + try { + return transaction.extractTransaction(); + } catch (err: any) { + console.log( + 'bw: signTransaction: Error: extracting the transaction' + ); + console.log(err.message); + console.log(err); + throw err; + } + } catch (err: any) { + console.log( + 'bw: signTransaction: Error: signing all inputs (or maybe duplication of extracting the transaction)' + ); + console.log(err.message); + console.log(err); + throw err; + } +} + +function determineNetwork(network: BitcoinNetwork): networks.Network { + if (network.mainnet !== undefined) { + return networks.bitcoin; + } + if (network.testnet !== undefined) { + return networks.testnet; + } + if (network.regtest !== undefined) { + return networks.regtest; + } + throw new Error(`Unknown Network: ${network}`); +} + +// Converts a SEC1 ECDSA signature to the DER format. +function sec1ToDer(sec1Signature: Uint8Array): Uint8Array { + let r: Uint8Array; + + if ((sec1Signature[0] & 0x80) !== 0) { + // r is negative. Prepend a zero byte. + const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(0, 32)]); + r = tmp; + } else { + // r is positive. + r = sec1Signature.slice(0, 32); + } + + let s: Uint8Array; + + if ((sec1Signature[32] & 0x80) !== 0) { + // s is negative. Prepend a zero byte. + const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(32)]); + s = tmp; + } else { + // s is positive. + s = sec1Signature.slice(32); + } + + // Convert signature to DER. + return Uint8Array.from([ + ...[0x30, 4 + r.length + s.length, 0x02, r.length], + ...r, + ...[0x02, s.length], + ...s + ]); +} diff --git a/examples/basic_bitcoin/src/backend/ecdsa_api.ts b/examples/basic_bitcoin/src/backend/ecdsa_api.ts new file mode 100644 index 0000000000..2ba8eeb2da --- /dev/null +++ b/examples/basic_bitcoin/src/backend/ecdsa_api.ts @@ -0,0 +1,53 @@ +import { serialize } from 'azle'; + +import { DerivationPath } from './types'; + +/// Returns the ECDSA public key of this canister at the given derivation path. +export async function ecdsaPublicKey( + keyName: string, + derivationPath: DerivationPath +): Promise { + // Retrieve the public key of this canister at the given derivation path + // from the ECDSA API. + const response = await fetch('icp://aaaaa-aa/ecdsa_public_key', { + body: serialize({ + args: [ + { + canister_id: [], + derivation_path: derivationPath, + key_id: { + curve: { secp256k1: null }, + name: keyName + } + } + ] + }) + }); + const ecdsaPublicKey = await response.json(); + return Buffer.from(ecdsaPublicKey.public_key).toString('hex'); +} + +export async function signWithECDSA( + keyName: string, + derivationPath: DerivationPath, + messageHash: Uint8Array +): Promise { + const publicKeyResponse = await fetch(`icp://aaaaa-aa/sign_with_ecdsa`, { + body: serialize({ + args: [ + { + message_hash: messageHash, + derivation_path: derivationPath, + key_id: { + curve: { secp256k1: null }, + name: keyName + } + } + ], + cycles: 10_000_000_000n + }) + }); + const publicKeyResult = await publicKeyResponse.json(); + + return publicKeyResult.signature; +} diff --git a/examples/basic_bitcoin/src/backend/index.did b/examples/basic_bitcoin/src/backend/index.did new file mode 100644 index 0000000000..7bf084795e --- /dev/null +++ b/examples/basic_bitcoin/src/backend/index.did @@ -0,0 +1,4 @@ +service: () -> { + http_request: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}; certificate_version:opt nat16}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:vec nat8; callback:func (vec nat8) -> (opt record {token:opt vec nat8; body:vec nat8}) query}}; status_code:nat16}) query; + http_request_update: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:vec nat8; callback:func (vec nat8) -> (opt record {token:opt vec nat8; body:vec nat8}) query}}; status_code:nat16}); +} diff --git a/examples/basic_bitcoin/src/backend/index.ts b/examples/basic_bitcoin/src/backend/index.ts new file mode 100644 index 0000000000..a4f682cfef --- /dev/null +++ b/examples/basic_bitcoin/src/backend/index.ts @@ -0,0 +1,155 @@ +import { jsonStringify } from 'azle'; +import { BitcoinNetwork } from 'azle/canisters/management'; +import express, { Request } from 'express'; + +import * as bitcoinApi from './bitcoin_api'; +import * as bitcoinWallet from './bitcoin_wallet'; + +export type SendRequest = { + destinationAddress: string; + amountInSatoshi: bigint; + utxos: string[]; +}; + +const NETWORK: BitcoinNetwork = determineNetwork( + process.env.BITCOIN_NETWORK +) ?? { + testnet: null +}; + +// The derivation path to use for ECDSA secp256k1. +const DERIVATION_PATH: Uint8Array[] = []; + +// The ECDSA key name. +const KEY_NAME: string = determineKeyName(NETWORK); + +const app = express(); + +app.use(express.json()); + +app.get('/', (req, res) => { + res.send( + `Network: ${NETWORK}, KeyName: ${KEY_NAME}, DerivationPath: ${DERIVATION_PATH}` + ); +}); + +/// Returns the balance of the given bitcoin address. +app.post( + '/get-balance', + async (req: Request, res) => { + const balance = await bitcoinApi.getBalance(NETWORK, req.query.address); + + res.send(jsonStringify(balance)); + } +); + +/// Returns the UTXOs of the given bitcoin address. +app.post( + '/get-utxos', + async (req: Request, res) => { + const utxos = await bitcoinApi.getUtxos(NETWORK, req.query.address); + + res.send(jsonStringify(utxos)); + } +); + +/// Returns the 100 fee percentiles measured in millisatoshi/byte. +/// Percentiles are computed from the last 10,000 transactions (if available). +app.post('/get-current-fee-percentiles', async (req, res) => { + const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(NETWORK); + + res.send(jsonStringify(feePercentiles)); +}); + +/// Returns the P2PKH address of this canister at a specific derivation path. +app.post('/get-p2pkh-address', async (req, res) => { + const address = await bitcoinWallet.getAddress( + NETWORK, + KEY_NAME, + DERIVATION_PATH + ); + + res.send(address.toString()); +}); + +app.post('/send', async (req: Request, res) => { + const destAddress = req.query.destinationAddress; + const amount = BigInt(req.query.amountInSatoshi); + const transactions = req.body; + console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); + console.log(`>>> Dest: ${destAddress} amount: ${amount}`); + console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); + const txId = await bitcoinWallet.send( + NETWORK, + DERIVATION_PATH, + KEY_NAME, + destAddress, + amount, + transactions + ); + + res.send(txId); +}); + +app.post( + '/create-transaction', + async (req: Request, res) => { + const destAddress = req.query.destinationAddress; + const amount = BigInt(req.query.amountInSatoshi); + const transactions = req.body; + console.log('THESE ARE THE TRANSACTIONS!!!'); + console.log(transactions); + // const jsonTransactions = jsonParse(transactions); + console.log( + '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' + ); + console.log(`>>> Dest: ${destAddress} amount: ${amount}`); + console.log( + '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' + ); + const transaction = await bitcoinWallet.createSignedTransaction( + NETWORK, + DERIVATION_PATH, + KEY_NAME, + destAddress, + amount, + transactions + ); + + const result = { + transactionHex: transaction.toHex(), + transactionBuf: transaction.toBuffer().toString('hex') + }; + + res.send(result); + } +); + +app.listen(); + +function determineKeyName(network: BitcoinNetwork): string { + if (network.mainnet !== undefined) { + return 'test_key_1'; + } else if (network.testnet !== undefined) { + return 'test_key_1'; + } else if (network.regtest !== undefined) { + return 'dfx_test_key'; + } + throw new Error('Invalid Bitcoin Network'); +} + +function determineNetwork(networkName?: string): BitcoinNetwork | undefined { + if (networkName === undefined) { + return undefined; + } + if (networkName === 'mainnet') { + return { mainnet: null }; + } + if (networkName === 'testnet') { + return { testnet: null }; + } + if (networkName === 'regtest') { + return { regtest: null }; + } + throw new Error('Invalid Bitcoin Network'); +} diff --git a/examples/basic_bitcoin/src/backend/types.ts b/examples/basic_bitcoin/src/backend/types.ts new file mode 100644 index 0000000000..fd5d8373b1 --- /dev/null +++ b/examples/basic_bitcoin/src/backend/types.ts @@ -0,0 +1,18 @@ +import { nat64 } from 'azle'; + +export type SendRequest = { + destinationAddress: string; + amountInSatoshi: nat64; +}; + +// TODO decide if we are going to use this or not +export type ThresholdKeyInfo = { + derivationPath: DerivationPath; + keyId?: { + curve: 'secp256k1'; + name: 'dfx_test_key' | 'test_key_1' | 'key_1'; + }; +}; + +export type BitcoinTxid = string; +export type DerivationPath = Uint8Array[]; diff --git a/examples/basic_bitcoin/src/bitcoin_plugin/Cargo.toml b/examples/basic_bitcoin/src/bitcoin_plugin/Cargo.toml deleted file mode 100644 index 2c11138344..0000000000 --- a/examples/basic_bitcoin/src/bitcoin_plugin/Cargo.toml +++ /dev/null @@ -1,3 +0,0 @@ -[dependencies] -bitcoin = "0.28.1" -boa_gc = { git = "https://github.com/boa-dev/boa", rev = "08a72387ba5cd185f657e98d984ff2ce328da69e" } diff --git a/examples/basic_bitcoin/src/bitcoin_plugin/index.ts b/examples/basic_bitcoin/src/bitcoin_plugin/index.ts deleted file mode 100644 index 4620517d80..0000000000 --- a/examples/basic_bitcoin/src/bitcoin_plugin/index.ts +++ /dev/null @@ -1,172 +0,0 @@ -// @ts-nocheck - -import { blob, int32, nat32, nat64, registerPlugin, Vec } from 'azle'; - -export type BitcoinAddress = { - script_pubkey: () => BitcoinScript; -}; - -export const BitcoinAddress = { - from_str: (string: string) => { - return (globalThis as any).BitcoinPlugin.BitcoinAddress.from_str( - string - ) as BitcoinAddress; - } -}; - -export type BitcoinHash = { - from_slice: (_: blob) => BitcoinHash; -}; - -export const BitcoinHash = { - from_slice: (slice: blob) => { - return (globalThis as any).BitcoinPlugin.BitcoinHash.from_slice( - slice - ) as BitcoinHash; - } -}; - -export type BitcoinOutPoint = { - new: (txid: BitcoinTxid, vout: nat32) => BitcoinOutPoint; -}; - -export const BitcoinOutPoint = { - new: (txid: BitcoinTxid, vout: nat32) => { - return (globalThis as any).BitcoinPlugin.BitcoinOutPoint.new( - txid, - vout - ) as BitcoinOutPoint; - } -}; - -export type BitcoinSighash = { - to_vec: () => blob; -}; - -export type BitcoinScript = { - new: () => BitcoinScript; -}; - -export const BitcoinScript = { - new: () => { - return ( - globalThis as any - ).BitcoinPlugin.BitcoinScript.new() as BitcoinScript; - } -}; - -export type BitcoinScriptBuilder = { - into_script: () => BitcoinScript; - new: () => BitcoinScriptBuilder; - push_slice: (_: blob) => BitcoinScriptBuilder; -}; - -export const BitcoinScriptBuilder = { - new: () => { - return ( - globalThis as any - ).BitcoinPlugin.BitcoinScriptBuilder.new() as BitcoinScriptBuilder; - } -}; - -export type BitcoinTransaction = { - inputs: Vec; - lock_time: nat32; - version: int32; - outputs: Vec; - prepare_script_sig: (index: nat32, script: BitcoinScript) => void; - serialize: () => blob; - signature_hash: ( - input_index: nat32, - script_pubkey: BitcoinScript, - sighash_u32: nat32 - ) => BitcoinSighash; - txid: () => BitcoinTxid; -}; - -export const BitcoinTransaction = { - new: ( - inputs: BitcoinTransaction['inputs'], - lock_time: BitcoinTransaction['lock_time'], - version: BitcoinTransaction['version'], - outputs: BitcoinTransaction['outputs'] - ) => { - return (globalThis as any).BitcoinPlugin.BitcoinTransaction.new( - inputs, - lock_time, - version, - outputs - ) as BitcoinTransaction; - } -}; - -export type BitcoinTxid = { - from_hash: (hash: BitcoinHash) => BitcoinTxid; - to_string: () => string; -}; - -export const BitcoinTxid = { - from_hash: (hash: BitcoinHash) => { - return (globalThis as any).BitcoinPlugin.BitcoinTxid.from_hash( - hash - ) as BitcoinTxid; - } -}; - -export type BitcoinTxIn = { - witness: BitcoinWitness; // TODO we need these to be setters or accessors or something - new: ( - previous_output: BitcoinOutPoint, - sequence: nat32, - witness: BitcoinWitness, - script_sig: BitcoinScript - ) => BitcoinTxIn; - set_script_sig: (script_sig: BitcoinScript) => void; -}; - -export const BitcoinTxIn = { - new: ( - previous_output: BitcoinOutPoint, - script_sig: BitcoinScript, - sequence: nat32, - witness: BitcoinWitness - ) => { - return (globalThis as any).BitcoinPlugin.BitcoinTxIn.new( - previous_output, - script_sig, - sequence, - witness - ) as BitcoinTxIn; - } -}; - -export type BitcoinTxOut = { - new: (value: nat64, script_pubkey: BitcoinScript) => BitcoinTxOut; -}; - -export const BitcoinTxOut = { - new: (value: nat64, script_pubkey: BitcoinScript) => { - return (globalThis as any).BitcoinPlugin.BitcoinTxOut.new( - value, - script_pubkey - ) as BitcoinTxOut; - } -}; - -export type BitcoinWitness = { - clear: () => void; - new: () => BitcoinWitness; -}; - -export const BitcoinWitness = { - new: () => { - return ( - globalThis as any - ).BitcoinPlugin.BitcoinWitness.new() as BitcoinWitness; - } -}; - -registerPlugin({ - globalObjectName: 'BitcoinPlugin', - rustRegisterFunctionName: '_bitcoin_plugin_register' -}); diff --git a/examples/basic_bitcoin/src/bitcoin_plugin/src/lib.rs b/examples/basic_bitcoin/src/bitcoin_plugin/src/lib.rs deleted file mode 100644 index c2aabb5719..0000000000 --- a/examples/basic_bitcoin/src/bitcoin_plugin/src/lib.rs +++ /dev/null @@ -1,852 +0,0 @@ -use bitcoin::hashes::Hash; -use bitcoin::psbt::serialize::Serialize; -use boa_engine::object::NativeObject; - -fn _bitcoin_plugin_register(boa_context: &mut boa_engine::Context) { - let address = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_address_from_str), - "from_str", - 0, - ) - .build(); - - let hash = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_hash_from_slice), - "from_slice", - 0, - ) - .build(); - - let out_point = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_out_point_new), - "new", - 0, - ) - .build(); - - let script = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_script_new), - "new", - 0, - ) - .build(); - - let script_builder = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_script_builder_new), - "new", - 0, - ) - .build(); - - let transaction = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_transaction_new), - "new", - 0, - ) - .build(); - - let txid = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_txid_from_hash), - "from_hash", - 0, - ) - .build(); - - let tx_in = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_tx_in_new), - "new", - 0, - ) - .build(); - - let tx_out = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_tx_out_new), - "new", - 0, - ) - .build(); - - let witness = boa_engine::object::ObjectInitializer::new(boa_context) - .function( - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_witness_new), - "new", - 0, - ) - .build(); - - let bitcoin_plugin = boa_engine::object::ObjectInitializer::new(boa_context) - .property( - "BitcoinAddress", - address, - boa_engine::property::Attribute::all(), - ) - .property("BitcoinHash", hash, boa_engine::property::Attribute::all()) - .property( - "BitcoinOutPoint", - out_point, - boa_engine::property::Attribute::all(), - ) - .property( - "BitcoinScript", - script, - boa_engine::property::Attribute::all(), - ) - .property( - "BitcoinScriptBuilder", - script_builder, - boa_engine::property::Attribute::all(), - ) - .property( - "BitcoinTransaction", - transaction, - boa_engine::property::Attribute::all(), - ) - .property("BitcoinTxid", txid, boa_engine::property::Attribute::all()) - .property("BitcoinTxIn", tx_in, boa_engine::property::Attribute::all()) - .property( - "BitcoinTxOut", - tx_out, - boa_engine::property::Attribute::all(), - ) - .property( - "BitcoinWitness", - witness, - boa_engine::property::Attribute::all(), - ) - .build(); - - boa_context.register_global_property( - "BitcoinPlugin", - bitcoin_plugin, - boa_engine::property::Attribute::all(), - ); -} - -// address - -struct JsBitcoinAddress(bitcoin::Address); - -unsafe impl boa_gc::Trace for JsBitcoinAddress { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinAddress {} - -fn _bitcoin_plugin_address_from_str( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let arg0: String = aargs[0].clone().try_from_vm_value(&mut *context).unwrap(); - - let address = JsBitcoinAddress(bitcoin::Address::from_str(&arg0).unwrap()); - - let address_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(address), - ); - - address_js_object - .set( - "script_pubkey", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_address_script_pubkey), - ) - .build(), - false, - context, - ) - .unwrap(); - - Ok(address_js_object.into()) -} - -fn _bitcoin_plugin_address_script_pubkey( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let this_js_object = this.as_object().unwrap(); - let address = this_js_object.downcast_ref::().unwrap(); - - let bitcoin_script = JsBitcoinScript(address.0.script_pubkey()); - - let bitcoin_script_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(bitcoin_script), - ); - - Ok(bitcoin_script_js_object.into()) -} - -// hash -struct JsBitcoinHash(bitcoin::hashes::sha256d::Hash); - -unsafe impl boa_gc::Trace for JsBitcoinHash { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinHash {} - -fn _bitcoin_plugin_hash_from_slice( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let arg0: Vec = aargs[0].clone().try_from_vm_value(&mut *context).unwrap(); - - let hash = JsBitcoinHash(bitcoin::hashes::sha256d::Hash::from_slice(&arg0).unwrap()); - - let hash_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(hash), - ); - - Ok(hash_js_object.into()) -} - -// OutPoint -struct JsBitcoinOutPoint(bitcoin::OutPoint); - -unsafe impl boa_gc::Trace for JsBitcoinOutPoint { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinOutPoint {} - -fn _bitcoin_plugin_out_point_new( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let txid_js_object = aargs[0].as_object().unwrap(); - let txid = txid_js_object.downcast_ref::().unwrap(); - - let vout: u32 = aargs[1].clone().try_from_vm_value(&mut *context).unwrap(); - - let out_point = JsBitcoinOutPoint(bitcoin::OutPoint::new(txid.0, vout)); - - let out_point_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(out_point), - ); - - Ok(out_point_js_object.into()) -} - -// script -struct JsBitcoinScript(bitcoin::Script); - -unsafe impl boa_gc::Trace for JsBitcoinScript { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinScript {} - -fn _bitcoin_plugin_script_new( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let script = JsBitcoinScript(bitcoin::Script::new()); - - let script_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(script), - ); - - Ok(script_js_object.into()) -} - -// script_builder -struct JsBitcoinScriptBuilder(bitcoin::blockdata::script::Builder); - -unsafe impl boa_gc::Trace for JsBitcoinScriptBuilder { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinScriptBuilder {} - -fn _bitcoin_plugin_script_builder_new( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let script_builder = JsBitcoinScriptBuilder(bitcoin::blockdata::script::Builder::new()); - - let script_builder_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(script_builder), - ); - - script_builder_js_object - .set( - "into_script", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_script_builder_into_script), - ) - .build(), - false, - context, - ) - .unwrap(); - - script_builder_js_object - .set( - "push_slice", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_script_builder_push_slice), - ) - .build(), - false, - context, - ) - .unwrap(); - - Ok(script_builder_js_object.into()) -} - -fn _bitcoin_plugin_script_builder_into_script( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let this_js_object = this.as_object().unwrap(); - let script_builder = this_js_object - .downcast_ref::() - .unwrap(); - - let bitcoin_script = JsBitcoinScript(script_builder.0.clone().into_script()); - - let bitcoin_script_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(bitcoin_script), - ); - - Ok(bitcoin_script_js_object.into()) -} - -fn _bitcoin_plugin_script_builder_push_slice( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let this_js_object = this.as_object().unwrap(); - let script_builder = this_js_object - .downcast_ref::() - .unwrap(); - - let arg0: Vec = aargs[0].clone().try_from_vm_value(&mut *context).unwrap(); - - let bitcoin_script_builder = JsBitcoinScriptBuilder(script_builder.0.clone().push_slice(&arg0)); - - let bitcoin_script_builder_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(bitcoin_script_builder), - ); - - bitcoin_script_builder_js_object - .set( - "into_script", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_script_builder_into_script), - ) - .build(), - false, - context, - ) - .unwrap(); - - bitcoin_script_builder_js_object - .set( - "push_slice", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_script_builder_push_slice), - ) - .build(), - false, - context, - ) - .unwrap(); - - Ok(bitcoin_script_builder_js_object.into()) -} - -// Sighash -struct JsBitcoinSighash(bitcoin::Sighash); - -unsafe impl boa_gc::Trace for JsBitcoinSighash { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinSighash {} - -fn _bitcoin_plugin_sighash_to_vec( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let this_js_object = this.as_object().unwrap(); - let sighash = &this_js_object.downcast_ref::().unwrap().0; - - Ok(sighash.to_vec().try_into_vm_value(&mut *context).unwrap()) -} - -// transaction -struct JsBitcoinTransaction(bitcoin::Transaction); - -unsafe impl boa_gc::Trace for JsBitcoinTransaction { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinTransaction {} - -fn _bitcoin_plugin_transaction_new( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let input = get_transaction_input(aargs[0].clone(), context); - let lock_time: u32 = aargs[1].clone().try_from_vm_value(&mut *context).unwrap(); - let version: i32 = aargs[2].clone().try_from_vm_value(&mut *context).unwrap(); - let output = get_transaction_output(aargs[3].clone(), context); - - let transaction = JsBitcoinTransaction(bitcoin::Transaction { - input, - lock_time, - version, - output, - }); - - let transaction_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(transaction), - ); - - transaction_js_object - .set("inputs", aargs[0].clone(), false, context) - .unwrap(); - - transaction_js_object - .set( - "serialize", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_transaction_serialize), - ) - .build(), - false, - context, - ) - .unwrap(); - - transaction_js_object - .set( - "signature_hash", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_transaction_signature_hash), - ) - .build(), - false, - context, - ) - .unwrap(); - - transaction_js_object - .set( - "txid", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_transaction_txid), - ) - .build(), - false, - context, - ) - .unwrap(); - - transaction_js_object - .set( - "prepare_script_sig", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_copy_closure( - |this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context| { - let this_js_object = this.as_object().unwrap(); - let transaction = &mut this_js_object - .downcast_mut::() - .unwrap() - .0; - - let index: u32 = aargs[0].clone().try_from_vm_value(&mut *context).unwrap(); - - let script_js_object = aargs[1].as_object().unwrap(); - let script = &script_js_object - .downcast_ref::() - .unwrap() - .0; - - let mut input = &mut transaction.input[index as usize]; - - input.script_sig = script.clone(); - - input.witness.clear(); - - Ok(boa_engine::JsValue::Undefined) - }, - ), - ) - .build(), - false, - context, - ) - .unwrap(); - - Ok(transaction_js_object.into()) -} - -fn _bitcoin_plugin_transaction_serialize( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let this_js_object = this.as_object().unwrap(); - let transaction = &this_js_object - .downcast_ref::() - .unwrap() - .0; - - let serialized_transaction = transaction.serialize().try_into_vm_value(context).unwrap(); - - Ok(serialized_transaction) -} - -fn _bitcoin_plugin_transaction_signature_hash( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let this_js_object = this.as_object().unwrap(); - let transaction = &this_js_object - .downcast_ref::() - .unwrap() - .0; - - let input_index: u32 = aargs[0].clone().try_from_vm_value(&mut *context).unwrap(); - - let script_pubkey_js_object = aargs[1].as_object().unwrap(); - let script_pubkey = &script_pubkey_js_object - .downcast_ref::() - .unwrap() - .0; - - let sighash_u32: u32 = aargs[2].clone().try_from_vm_value(&mut *context).unwrap(); - - let sighash = JsBitcoinSighash(transaction.signature_hash( - input_index as usize, - &script_pubkey, - sighash_u32, - )); - - let sighash_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(sighash), - ); - - sighash_js_object - .set( - "to_vec", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_sighash_to_vec), - ) - .build(), - false, - context, - ) - .unwrap(); - - Ok(sighash_js_object.into()) -} - -fn _bitcoin_plugin_transaction_txid( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let this_js_object = this.as_object().unwrap(); - let transaction = &this_js_object - .downcast_ref::() - .unwrap() - .0; - - let txid = JsBitcoinTxid(transaction.txid()); - - let txid_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(txid), - ); - - txid_js_object - .set( - "to_string", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_copy_closure( - |this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context| { - let this_js_object = this.as_object().unwrap(); - let tx_id = &this_js_object.downcast_mut::().unwrap().0; - - let string = tx_id.to_string(); - - Ok(string.into()) - }, - ), - ) - .build(), - false, - context, - ) - .unwrap(); - - Ok(txid_js_object.into()) -} - -fn get_transaction_input( - input_js_value: boa_engine::JsValue, - context: &mut boa_engine::Context, -) -> Vec { - let input_js_object = input_js_value.as_object().unwrap(); - - let mut processing: bool = true; - let mut index: usize = 0; - - let mut result = vec![]; - - while processing == true { - let item_js_value = input_js_object.get(index, context).unwrap(); - - if item_js_value.is_undefined() { - processing = false; - } else { - let tx_in_js_object = item_js_value.as_object().unwrap(); - let tx_in = &tx_in_js_object.downcast_ref::().unwrap().0; - - result.push(tx_in.clone()); - index += 1; - } - } - - result -} - -fn get_transaction_output( - output_js_value: boa_engine::JsValue, - context: &mut boa_engine::Context, -) -> Vec { - let output_js_object = output_js_value.as_object().unwrap(); - - let mut processing: bool = true; - let mut index: usize = 0; - - let mut result = vec![]; - - while processing == true { - let item_js_value = output_js_object.get(index, context).unwrap(); - - if item_js_value.is_undefined() { - processing = false; - } else { - let tx_out_js_object = item_js_value.as_object().unwrap(); - let tx_out = &tx_out_js_object.downcast_ref::().unwrap().0; - - result.push(tx_out.clone()); - index += 1; - } - } - - result -} - -// txid -struct JsBitcoinTxid(bitcoin::Txid); - -unsafe impl boa_gc::Trace for JsBitcoinTxid { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinTxid {} - -fn _bitcoin_plugin_txid_from_hash( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let hash_js_object = aargs[0].as_object().unwrap(); - let hash = hash_js_object.downcast_ref::().unwrap(); - - let txid = JsBitcoinTxid(bitcoin::hash_types::Txid::from_hash(hash.0)); - - let txid_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(txid), - ); - - Ok(txid_js_object.into()) -} - -// txin -struct JsBitcoinTxIn(bitcoin::TxIn); - -unsafe impl boa_gc::Trace for JsBitcoinTxIn { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinTxIn {} - -fn _bitcoin_plugin_tx_in_new( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let previous_output_js_object = aargs[0].as_object().unwrap(); - let previous_output = previous_output_js_object - .downcast_ref::() - .unwrap() - .0; - - let script_sig_js_object = aargs[1].as_object().unwrap(); - let script_sig = script_sig_js_object - .downcast_ref::() - .unwrap() - .0 - .clone(); - - let sequence: u32 = aargs[2].clone().try_from_vm_value(&mut *context).unwrap(); - - let witness_js_object = aargs[3].as_object().unwrap(); - let witness = witness_js_object - .downcast_ref::() - .unwrap() - .0 - .clone(); - - let tx_in = JsBitcoinTxIn(bitcoin::TxIn { - previous_output, - script_sig, - sequence, - witness, - }); - - let tx_in_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(tx_in), - ); - - Ok(tx_in_js_object.into()) -} - -// txout -struct JsBitcoinTxOut(bitcoin::TxOut); - -unsafe impl boa_gc::Trace for JsBitcoinTxOut { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinTxOut {} - -fn _bitcoin_plugin_tx_out_new( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let value: u64 = aargs[0].clone().try_from_vm_value(&mut *context).unwrap(); - - let script_pubkey_js_object = aargs[1].as_object().unwrap(); - let script_pubkey = script_pubkey_js_object - .downcast_ref::() - .unwrap() - .0 - .clone(); - - let tx_out = JsBitcoinTxOut(bitcoin::TxOut { - value, - script_pubkey, - }); - - let tx_out_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(tx_out), - ); - - Ok(tx_out_js_object.into()) -} - -// witness -struct JsBitcoinWitness(bitcoin::blockdata::witness::Witness); - -unsafe impl boa_gc::Trace for JsBitcoinWitness { - boa_gc::empty_trace!(); -} - -impl boa_gc::Finalize for JsBitcoinWitness {} - -fn _bitcoin_plugin_witness_new( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let witness = JsBitcoinWitness(bitcoin::blockdata::witness::Witness::new()); - - let witness_js_object = boa_engine::object::JsObject::from_proto_and_data( - context.intrinsics().constructors().object().prototype(), - boa_engine::object::ObjectData::native_object(witness), - ); - - witness_js_object - .set( - "clear", - boa_engine::object::FunctionObjectBuilder::new( - context, - boa_engine::NativeFunction::from_fn_ptr(_bitcoin_plugin_witness_clear), - ) - .build(), - false, - context, - ) - .unwrap(); - - Ok(witness_js_object.into()) -} - -fn _bitcoin_plugin_witness_clear( - this: &boa_engine::JsValue, - aargs: &[boa_engine::JsValue], - context: &mut boa_engine::Context, -) -> boa_engine::JsResult { - let this_js_object = this.as_object().unwrap(); - let mut witness = &mut this_js_object.downcast_mut::().unwrap().0; - - witness.clear(); - - Ok(boa_engine::JsValue::Undefined) -} diff --git a/examples/basic_bitcoin/src/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts deleted file mode 100644 index 65d7d9bc28..0000000000 --- a/examples/basic_bitcoin/src/bitcoin_wallet.ts +++ /dev/null @@ -1,340 +0,0 @@ -// @ts-nocheck - -//! A demo of a very bare-bones bitcoin "wallet". -//! -//! The wallet here showcases how bitcoin addresses can be be computed -//! and how bitcoin transactions can be signed. It is missing several -//! pieces that any production-grade wallet would have, including: -//! -//! * Support for address types that aren't P2PKH. -//! * Caching spent UTXOs so that they are not reused in future transactions. -//! * Option to set the fee. -import { blob, ic, match, nat64, Result, Vec } from 'azle'; -import { - BitcoinNetwork, - MillisatoshiPerByte, - Satoshi, - Utxo -} from 'azle/canisters/management'; -import * as bs58 from 'bs58'; -import { Buffer } from 'buffer'; -import hexarray from 'hex-array'; -import { sha256 } from 'js-sha256'; -import RIPEMD160 from 'ripemd160'; - -import * as bitcoinApi from './bitcoin_api'; -import { - BitcoinAddress, - BitcoinHash, - BitcoinOutPoint, - BitcoinScript, - BitcoinScriptBuilder, - BitcoinTransaction, - BitcoinTxid, - BitcoinTxIn, - BitcoinTxOut, - BitcoinWitness -} from './bitcoin_plugin'; -import * as ecdsaApi from './ecdsa_api'; - -/// Returns the P2PKH address of this canister at the given derivation path. -export async function getP2PKHAddress( - network: BitcoinNetwork, - keyName: string, - derivationPath: Vec -): Promise { - // Fetch the public key of the given derivation path. - const publicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); - - // Compute the address. - return publicKeyToP2PKHAddress(network, publicKey); -} - -/// Sends a transaction to the network that transfers the given amount to the -/// given destination, where the source of the funds is the canister itself -/// at the given derivation path. -export async function send( - network: BitcoinNetwork, - derivationPath: Vec, - keyName: string, - dstAddressString: string, - amount: Satoshi -): Promise { - // Get fee percentiles from previous transactions to estimate our own fee. - const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); - - const feePerByte = - feePercentiles.length === 0 - ? // There are no fee percentiles. This case can only happen on a regtest - // network where there are no non-coinbase transactions. In this case, - // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) - 2_000n - : // Choose the 50th percentile for sending fees. - feePercentiles[49]; - - // Fetch our public key, P2PKH address, and UTXOs. - const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); - const ownAddressString = publicKeyToP2PKHAddress(network, ownPublicKey); - - console.log('Fetching UTXOs...'); - const ownUtxos = (await bitcoinApi.getUtxos(network, ownAddressString)) - .utxos; - - const ownAddress = BitcoinAddress.from_str(ownAddressString); - const dstAddress = BitcoinAddress.from_str(dstAddressString); - - // Build the transaction that sends `amount` to the destination address. - const transaction = await buildTransaction( - ownPublicKey, - ownAddress, - ownUtxos, - dstAddress, - amount, - feePerByte - ); - - const txBytes = transaction.serialize(); - console.log(`Transaction to sign: ${hexarray.toString(txBytes)}`); - - // Sign the transaction. - const signedTransaction = await signTransaction( - ownPublicKey, - ownAddress, - transaction, - keyName, - derivationPath, - ecdsaApi.signWithECDSA - ); - - const signedTransactionBytes = signedTransaction.serialize(); - console.log(` - "Signed transaction: ${hexarray.toString(signedTransactionBytes)}`); - - console.log('Sending transaction...'); - await bitcoinApi.sendTransaction(network, signedTransactionBytes); - console.log('Done'); - - return signedTransaction.txid(); -} - -// Builds a transaction to send the given `amount` of satoshis to the -// destination address. -async function buildTransaction( - ownPublicKey: blob, - ownAddress: BitcoinAddress, - ownUtxos: Vec, - dstAddress: BitcoinAddress, - amount: Satoshi, - feePerByte: MillisatoshiPerByte -): Promise { - // We have a chicken-and-egg problem where we need to know the length - // of the transaction in order to compute its proper fee, but we need - // to know the proper fee in order to figure out the inputs needed for - // the transaction. - // - // We solve this problem iteratively. We start with a fee of zero, build - // and sign a transaction, see what its size is, and then update the fee, - // rebuild the transaction, until the fee is set to the correct amount. - console.log('Building transaction...'); - let totalFee = 0n; - // eslint-disable-next-line no-constant-condition - while (true) { - const transaction = match( - buildTransactionWithFee( - ownUtxos, - ownAddress, - dstAddress, - amount, - totalFee - ), - { - Ok: (ok) => ok, - Err: () => ic.trap('Error building transaction.') - } - ); - - // Sign the transaction. In this case, we only care about the size - // of the signed transaction, so we use a mock signer here for efficiency. - const signedTransaction = await signTransaction( - ownPublicKey, - ownAddress, - transaction, - '', // mock key name - [], // mock derivation path - mockSigner - ); - - const signedTxBytesLen = BigInt(signedTransaction.serialize().length); - - if ((signedTxBytesLen * feePerByte) / 1_000n === totalFee) { - console.log(`Transaction built with fee ${totalFee}.`); - return transaction; - } else { - totalFee = (signedTxBytesLen * feePerByte) / 1_000n; - } - } -} - -function buildTransactionWithFee( - ownUtxos: Vec, - ownAddress: BitcoinAddress, - dstAddress: BitcoinAddress, - amount: nat64, - fee: nat64 -): Result { - // Assume that any amount below this threshold is dust. - const DUST_THRESHOLD: nat64 = 1_000n; - - // Select which UTXOs to spend. We naively spend the oldest available UTXOs, - // even if they were previously spent in a transaction. This isn't a - // problem as long as at most one transaction is created per block and - // we're using min_confirmations of 1. - let utxosToSpend: Vec = []; - let totalSpent = 0n; - for (const utxo of [...ownUtxos].reverse()) { - totalSpent += utxo.value; - utxosToSpend.push(utxo); - if (totalSpent >= amount + fee) { - // We have enough inputs to cover the amount we want to spend. - break; - } - } - - if (totalSpent < amount + fee) { - return Result.Err( - `Insufficient balance: ${totalSpent}, trying to transfer ${amount} satoshi with fee ${fee}` - ); - } - - const inputs: Vec = utxosToSpend.map((utxo) => { - const txid = BitcoinTxid.from_hash( - BitcoinHash.from_slice(utxo.outpoint.txid) - ); - const previousOutput = BitcoinOutPoint.new(txid, utxo.outpoint.vout); - const scriptSig = BitcoinScript.new(); - const sequence = 0xffffffff; - const witness = BitcoinWitness.new(); - - return BitcoinTxIn.new(previousOutput, scriptSig, sequence, witness); - }); - - let outputs: Vec = [ - BitcoinTxOut.new(amount, dstAddress.script_pubkey()) - ]; - - const remainingAmount = totalSpent - amount - fee; - - if (remainingAmount >= DUST_THRESHOLD) { - outputs.push( - BitcoinTxOut.new(remainingAmount, ownAddress.script_pubkey()) - ); - } - - return Result.Ok(BitcoinTransaction.new(inputs, 0, 1, outputs)); -} - -// Sign a bitcoin transaction. -// -// IMPORTANT: This method is for demonstration purposes only and it only -// supports signing transactions if: -// -// 1. All the inputs are referencing outpoints that are owned by `own_address`. -// 2. `own_address` is a P2PKH address. -async function signTransaction( - ownPublicKey: blob, - ownAddress: BitcoinAddress, - transaction: BitcoinTransaction, - keyName: string, - derivationPath: Vec, - signer: (_: string, __: Vec, ___: blob) => Promise -): Promise { - for (let index = 0; index < transaction.inputs.length; index++) { - const sighash = transaction.signature_hash( - index, - ownAddress.script_pubkey(), - 1 - ); - - const signature = await signer( - keyName, - derivationPath, - sighash.to_vec() - ); - - // Convert signature to DER. - const derSignature = sec1ToDer(signature); - - const sigWithHashtype = Uint8Array.from([...derSignature, 1]); - - transaction.prepare_script_sig( - index, - BitcoinScriptBuilder.new() - .push_slice(sigWithHashtype) - .push_slice(ownPublicKey) - .into_script() - ); - } - - return transaction; -} - -// Converts a public key to a P2PKH address. -function publicKeyToP2PKHAddress( - network: BitcoinNetwork, - publicKey: blob -): string { - // sha256 + ripmd160 - let hasher = new RIPEMD160(); - hasher.update(Buffer.from(sha256.digest(publicKey))); - const result = hasher.digest(); - const prefix = match(network, { - Mainnet: () => Buffer.from([0x00]), - _: () => Buffer.from([0x6f]) - }); - const dataWithPrefix = Buffer.concat([prefix, result]); - const checksum = sha256.digest(sha256.digest(dataWithPrefix)).slice(0, 4); - const fullAddress = Buffer.concat([dataWithPrefix, Buffer.from(checksum)]); - return bs58.encode(fullAddress); -} - -// A mock for rubber-stamping ECDSA signatures. -async function mockSigner( - _key_name: string, - _derivation_path: Vec, - _message_hash: blob -): Promise { - return new Uint8Array(64).fill(255); -} - -// Converts a SEC1 ECDSA signature to the DER format. -function sec1ToDer(sec1Signature: blob): blob { - let r: blob; - - if ((sec1Signature[0] & 0x80) !== 0) { - // r is negative. Prepend a zero byte. - const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(0, 32)]); - r = tmp; - } else { - // r is positive. - r = sec1Signature.slice(0, 32); - } - - let s: blob; - - if ((sec1Signature[32] & 0x80) !== 0) { - // s is negative. Prepend a zero byte. - const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(32)]); - s = tmp; - } else { - // s is positive. - s = sec1Signature.slice(32); - } - - // Convert signature to DER. - return Uint8Array.from([ - ...[0x30, 4 + r.length + s.length, 0x02, r.length], - ...r, - ...[0x02, s.length], - ...s - ]); -} diff --git a/examples/basic_bitcoin/src/ecdsa_api.ts b/examples/basic_bitcoin/src/ecdsa_api.ts deleted file mode 100644 index 24aa9aa607..0000000000 --- a/examples/basic_bitcoin/src/ecdsa_api.ts +++ /dev/null @@ -1,55 +0,0 @@ -// @ts-nocheck - -import { blob, ic, match, Opt, Vec } from 'azle'; -import { managementCanister } from 'azle/canisters/management'; - -/// Returns the ECDSA public key of this canister at the given derivation path. -export async function ecdsaPublicKey( - keyName: string, - derivationPath: Vec -): Promise { - // Retrieve the public key of this canister at the given derivation path - // from the ECDSA API. - const res = await managementCanister - .ecdsa_public_key({ - canister_id: Opt.None, - derivation_path: derivationPath, - key_id: { - curve: { - secp256k1: null - }, - name: keyName - } - }) - .call(); - - return match(res, { - Ok: (ok) => ok.public_key, - Err: (err) => ic.trap(err) - }); -} - -export async function signWithECDSA( - keyName: string, - derivationPath: Vec, - messageHash: blob -): Promise { - const res = await managementCanister - .sign_with_ecdsa({ - message_hash: messageHash, - derivation_path: derivationPath, - key_id: { - curve: { - secp256k1: null - }, - name: keyName - } - }) - .cycles(10_000_000_000n) - .call(); - - return match(res, { - Ok: (ok) => ok.signature, - Err: (err) => ic.trap(err) - }); -} diff --git a/examples/basic_bitcoin/src/index.did b/examples/basic_bitcoin/src/index.did deleted file mode 100644 index e3f4395746..0000000000 --- a/examples/basic_bitcoin/src/index.did +++ /dev/null @@ -1,20 +0,0 @@ -type BitcoinNetwork = variant { Mainnet; Regtest; Testnet }; -type ManualReply = record { - next_page : opt vec nat8; - tip_height : nat32; - tip_block_hash : vec nat8; - utxos : vec Utxo; -}; -type Outpoint = record { txid : vec nat8; vout : nat32 }; -type SendRequest = record { - amountInSatoshi : nat64; - destinationAddress : text; -}; -type Utxo = record { height : nat32; value : nat64; outpoint : Outpoint }; -service : (BitcoinNetwork) -> { - getBalance : (text) -> (nat64); - getCurrentFeePercentiles : () -> (vec nat64); - getP2PKHAddress : () -> (text); - getUtxos : (text) -> (ManualReply); - send : (SendRequest) -> (text); -} \ No newline at end of file diff --git a/examples/basic_bitcoin/src/index.ts b/examples/basic_bitcoin/src/index.ts deleted file mode 100644 index 0e0fceda3f..0000000000 --- a/examples/basic_bitcoin/src/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -// @ts-nocheck - -import { $init, $postUpgrade, $update, blob, match, nat64, Vec } from 'azle'; -import { - BitcoinNetwork, - GetUtxosResult, - MillisatoshiPerByte -} from 'azle/canisters/management'; - -import * as bitcoinApi from './bitcoin_api'; -import * as bitcoinWallet from './bitcoin_wallet'; -import { SendRequest } from './types'; - -// The bitcoin network to connect to. -// -// When developing locally this should be `Regtest`. -// When deploying to the IC this should be `Testnet`. -// `Mainnet` is currently unsupported. -let NETWORK: BitcoinNetwork = { - Testnet: null -}; - -// The derivation path to use for ECDSA secp256k1. -let DERIVATION_PATH: Vec = []; - -// The ECDSA key name. -let KEY_NAME: string = ''; - -$init; -export function init(network: BitcoinNetwork): void { - NETWORK = network; - - KEY_NAME = match(network, { - Mainnet: () => 'test_key_1', - Testnet: () => 'test_key_1', - Regtest: () => 'dfx_test_key' - }); -} - -$postUpgrade; -export function postUpgrade(network: BitcoinNetwork): void { - NETWORK = network; - - KEY_NAME = match(network, { - Mainnet: () => 'test_key_1', - Testnet: () => 'test_key_1', - Regtest: () => 'dfx_test_key' - }); -} - -/// Returns the balance of the given bitcoin address. -$update; -export async function getBalance(address: string): Promise { - return await bitcoinApi.getBalance(NETWORK, address); -} - -/// Returns the UTXOs of the given bitcoin address. -$update; -export async function getUtxos(address: string): Promise { - return await bitcoinApi.getUtxos(NETWORK, address); -} - -/// Returns the 100 fee percentiles measured in millisatoshi/byte. -/// Percentiles are computed from the last 10,000 transactions (if available). -$update; -export async function getCurrentFeePercentiles(): Promise< - Vec -> { - return await bitcoinApi.getCurrentFeePercentiles(NETWORK); -} - -/// Returns the P2PKH address of this canister at a specific derivation path. -$update; -export async function getP2PKHAddress(): Promise { - return await bitcoinWallet.getP2PKHAddress( - NETWORK, - KEY_NAME, - DERIVATION_PATH - ); -} - -$update; -export async function send(request: SendRequest): Promise { - const txId = await bitcoinWallet.send( - NETWORK, - DERIVATION_PATH, - KEY_NAME, - request.destinationAddress, - request.amountInSatoshi - ); - - return txId.to_string(); -} diff --git a/examples/basic_bitcoin/src/types.ts b/examples/basic_bitcoin/src/types.ts deleted file mode 100644 index 2f2e17321f..0000000000 --- a/examples/basic_bitcoin/src/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -// @ts-nocheck - -import { nat64, Record } from 'azle'; - -export type SendRequest = Record<{ - destinationAddress: string; - amountInSatoshi: nat64; -}>; diff --git a/examples/basic_bitcoin/test/pretest.ts b/examples/basic_bitcoin/test/pretest.ts new file mode 100644 index 0000000000..4fe51b706f --- /dev/null +++ b/examples/basic_bitcoin/test/pretest.ts @@ -0,0 +1,13 @@ +import { execSync } from 'child_process'; + +async function pretest() { + execSync(`dfx canister uninstall-code backend || true`, { + stdio: 'inherit' + }); + + execSync(`npm run deploy`, { + stdio: 'inherit' + }); +} + +pretest(); diff --git a/examples/basic_bitcoin/test/test.ts b/examples/basic_bitcoin/test/test.ts new file mode 100644 index 0000000000..80fcb0a282 --- /dev/null +++ b/examples/basic_bitcoin/test/test.ts @@ -0,0 +1,45 @@ +import { getCanisterId } from 'azle/dfx'; +import { runTests } from 'azle/test'; +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; +import { existsSync, rmSync } from 'fs-extra'; + +import { getTests } from './tests'; + +const canisterId = getCanisterId('backend'); + +export async function whileRunningBitcoinDaemon( + callback: () => Promise | void +) { + const bitcoinDaemon = await startBitcoinDaemon(); + await callback(); + bitcoinDaemon.kill(); +} + +async function startBitcoinDaemon(): Promise { + if (existsSync(`.bitcoin/regtest`)) { + rmSync('.bitcoin/regtest', { recursive: true, force: true }); + } + const bitcoinDaemon = spawn('.bitcoin/bin/bitcoind', [ + `-conf=${process.cwd()}/.bitcoin.conf`, + `-datadir=${process.cwd()}/.bitcoin`, + '--port=18444' + ]); + + process.on('uncaughtException', () => { + if (!bitcoinDaemon.killed) { + bitcoinDaemon.kill(); + } + }); + + process.on('exit', () => { + if (!bitcoinDaemon.killed) { + bitcoinDaemon.kill(); + } + }); + + console.log(`starting bitcoind...`); + await new Promise((resolve) => setTimeout(resolve, 5000)); + return bitcoinDaemon; +} + +whileRunningBitcoinDaemon(() => runTests(getTests(canisterId))); diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts new file mode 100644 index 0000000000..059a39a54a --- /dev/null +++ b/examples/basic_bitcoin/test/tests.ts @@ -0,0 +1,361 @@ +import * as dns from 'node:dns'; +dns.setDefaultResultOrder('ipv4first'); + +import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; +import { jsonParse, jsonStringify } from 'azle'; +import { GetUtxosResult, Outpoint, Satoshi } from 'azle/canisters/management'; +import { Test } from 'azle/test'; +import { networks, payments, Transaction } from 'bitcoinjs-lib'; +import { execSync } from 'child_process'; +import { ECPairFactory } from 'ecpair'; + +const SINGLE_BLOCK_REWARD = 5_000_000_000n; + +const ECPair = ECPairFactory(ecc); + +let lastTx = ''; + +// TODO adding HD wallets and showing how to use the Derivation path might be nice + +export function getTests(canisterId: string): Test[] { + const origin = `http://${canisterId}.localhost:8000`; + const canisterAddressForm = 'mhVmPSYFraAYnA4ZP6KUx41P3dKgAg27Cm'; // p2pkh-address on the regtest will generally be of this form, starting with m or n and this many characters. + return [ + { + name: '/get-current-fee-percentiles', + test: async () => { + const response = await fetch( + `${origin}/get-current-fee-percentiles`, + { method: 'POST' } + ); + + const feePercentiles = jsonParse(await response.text()); + + return { Ok: feePercentiles.length === 0 }; + } + }, + { + name: '/get-p2pkh-address', + test: async () => { + const address = await getAddress(origin); + + return { Ok: canisterAddressForm.length === address.length }; + } + }, + { + name: '/get-balance', + test: async () => { + const address = await getAddress(origin); + const balance = await getBalance(origin, address); + + return { Ok: balance === 0n }; + } + }, + { + name: 'mint BTC', + prep: async () => { + const address = await getAddress(origin); + + for (let i = 0; i < 101; i++) { + execSync(`npm run mint --address=${address}`); + } + } + }, + { name: 'wait for blocks to settle', wait: 60_000 }, + { + name: '/get-balance', + test: async () => { + const address = await getAddress(origin); + const balance = await getBalance(origin, address); + + return { Ok: balance === SINGLE_BLOCK_REWARD * 101n }; + } + }, + { + name: '/get-utxos', + test: async () => { + const address = await getAddress(origin); + + const response = await fetch( + `${origin}/get-utxos?address=${address}`, + { method: 'POST' } + ); + const utxosResult: GetUtxosResult = await jsonParse( + await response.text() + ); + + return { + Ok: + utxosResult.tip_height === 101 && + utxosResult.utxos.length === 101 && + checkUtxos(utxosResult.utxos) + }; + } + }, + { + name: '/get-current-fee-percentiles', + test: async () => { + const response = await fetch( + `${origin}/get-current-fee-percentiles`, + { method: 'POST' } + ); + + const feePercentiles = jsonParse(await response.text()); + + // Though blocks are mined no transaction have happened yet so the list should still be empty + return { Ok: feePercentiles.length === 0 }; + } + }, + { + name: 'toAddress sanity check', + test: async () => { + const address = getToAddress(); + return { Ok: address === 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD' }; + } + }, + { + name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + test: async () => { + const address = getToAddress(); + const balance = await getBalance(origin, address); + + return { Ok: balance === 0n }; + } + }, + { + name: '/create-transaction', + test: async () => { + const address = getToAddress(); + const body = jsonStringify(getUtxoHashes()); + const response = await fetch( + `${origin}/create-transaction?amountInSatoshi=${SINGLE_BLOCK_REWARD}&destinationAddress=${address}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + } + ); + const transactionHex = await response.text(); + console.log(transactionHex); + return { Ok: true }; + } + }, + { + name: '/send from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + test: async () => { + const toAddress = getToAddress(); + const body = jsonStringify(getUtxoHashes()); + const response = await fetch( + `${origin}/send?amountInSatoshi=${ + SINGLE_BLOCK_REWARD / 2n + }&destinationAddress=${toAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + } + ); + lastTx = await response.text(); + + // TODO is there anyway to deteremine if this actually worked or just by getting the balance bellow? + // TODO if that is the case then we should probably get rid of the create-transaction end point and call get balance as part of this one + return { Ok: true }; + } + }, + { + name: 'mint BTC', + prep: async () => { + const address = await getToAddress(); + generateToAddress(address, 3); + } + }, + { name: 'wait for blocks to settle', wait: 60_000 }, + { + name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + test: async () => { + const address = getToAddress(); + const balance = await getBalance(origin, address); + + const previousTransaction = Transaction.fromHex( + getTxHex(lastTx) + ); + + const outputValue = BigInt(getTotalOutput(previousTransaction)); + + const inputValue = SINGLE_BLOCK_REWARD; + + const fee = inputValue - outputValue; + const blockRewards = SINGLE_BLOCK_REWARD * 3n + fee; + const expectedBalance = SINGLE_BLOCK_REWARD / 2n + blockRewards; + + return { + Ok: balance === expectedBalance + }; + } + }, + { + name: '/get-balance', + test: async () => { + const address = await getAddress(origin); + const balance = await getBalance(origin, address); + + const previousTransaction = Transaction.fromHex( + getTxHex(lastTx) + ); + const outputValue = BigInt(getTotalOutput(previousTransaction)); + const amountSent = SINGLE_BLOCK_REWARD / 2n; + const inputValue = SINGLE_BLOCK_REWARD; + const fee = inputValue - outputValue; + const blockRewards = SINGLE_BLOCK_REWARD * 101n; + const expectedBalance = blockRewards - (amountSent + fee); + + return { Ok: balance === expectedBalance }; + } + } + ]; +} + +function getTotalOutput(tx: Transaction): number { + return tx.outs.reduce((total, output) => { + return total + output.value; + }, 0); +} + +async function getAddress(origin: string): Promise { + const response = await fetch(`${origin}/get-p2pkh-address`, { + method: 'POST' + }); + return await response.text(); +} + +function generateToAddress(address: string, blocks: number) { + for (let i = 0; i < blocks; i++) { + execSync(`npm run mint --address=${address}`); + } +} + +function getUtxoHashes(): TransactionHashes { + // TODO would range() be better here? + return Array.from({ length: 102 }).reduce((acc, _, blockHeight) => { + const blockHash = getblockhash(blockHeight); + const block = getBlock(blockHash); + const result = getTransactionHashAndIdFromBlock(block); + return { ...acc, ...result }; + }, {} as TransactionHashes); +} + +function getTxHex(txid: string): string { + return execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawtransaction ${txid}` + ) + .toString() + .trim(); +} + +function getblockhash(blockIndex: number): string { + return execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockhash ${blockIndex}` + ) + .toString() + .trim(); +} + +type Block = { + hash: string; + confirmations: number; + height: number; + version: number; + versionHex: string; + merkleroot: string; + time: number; + mediantime: number; + nonce: number; + bits: string; + difficulty: number; + chainwork: string; + nTx: number; + previousblockhash: string; + nextblockhash: string; + strippedsize: number; + size: number; + weight: number; + tx: Tx[]; +}; + +type Tx = { + txid: string; + hash: string; + version: number; + size: number; + vsize: number; + weight: number; + locktime: number; + vin: [ + { + coinbase: string; + txinwitness: string[]; + sequence: number; + } + ]; + vout: Vout[]; + hex: string; +}; + +type Vout = { + value: number; + n: number; + scriptPubKey: { + asm: string; + desc: string; + hex: string; + address: string; + type: string; + }; +}; + +function getBlock(hash: string): Block { + const getBlockResult = execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblock ${hash} 2` + ) + .toString() + .trim(); + + return jsonParse(getBlockResult); +} + +type TransactionHashes = { + [txid: string]: string; +}; + +function getTransactionHashAndIdFromBlock(block: Block): TransactionHashes { + return { [block.tx[0].txid]: block.tx[0].hex }; +} + +function getToAddress(): string { + const keyPair = ECPair.fromWIF( + 'L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv' + ); + const { address: address } = payments.p2pkh({ + pubkey: keyPair.publicKey, + network: networks.regtest + }); + if (address === undefined) { + throw new Error('To Address is undefined'); + } + return address; +} + +async function getBalance(origin: string, address: string): Promise { + const response = await fetch(`${origin}/get-balance?address=${address}`, { + method: 'POST' + }); + return jsonParse(await response.text()); +} + +type Utxo = { height: number; outpoint: Outpoint; value: Satoshi }; + +function checkUtxos(utxso: Utxo[]): boolean { + return utxso.every( + (utxo) => utxo.value === SINGLE_BLOCK_REWARD && utxo.outpoint.vout === 0 + ); +} diff --git a/examples/basic_bitcoin/tsconfig.json b/examples/basic_bitcoin/tsconfig.json index 552e5f0cbd..0817cb3fc1 100644 --- a/examples/basic_bitcoin/tsconfig.json +++ b/examples/basic_bitcoin/tsconfig.json @@ -4,7 +4,7 @@ "target": "ES2020", "moduleResolution": "node", "allowJs": true, - "allowSyntheticDefaultImports": true, - "outDir": "HACK_BECAUSE_OF_ALLOW_JS" + "outDir": "HACK_BECAUSE_OF_ALLOW_JS", + "allowSyntheticDefaultImports": true } } From 510616401cf51e9849a456309d5513b2cebdbecf Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 6 May 2024 13:18:30 -0600 Subject: [PATCH 09/49] clean up from debugging --- .../basic_bitcoin/src/backend/bitcoin_api.ts | 29 +-- .../src/backend/bitcoin_wallet.ts | 40 +--- .../basic_bitcoin/src/backend/ecdsa_api.ts | 2 - examples/basic_bitcoin/src/backend/index.ts | 37 ---- examples/basic_bitcoin/test/bitcoin.ts | 117 ++++++++++ examples/basic_bitcoin/test/tests.ts | 200 ++++-------------- 6 files changed, 185 insertions(+), 240 deletions(-) create mode 100644 examples/basic_bitcoin/test/bitcoin.ts diff --git a/examples/basic_bitcoin/src/backend/bitcoin_api.ts b/examples/basic_bitcoin/src/backend/bitcoin_api.ts index 45869f74a9..3babe85934 100644 --- a/examples/basic_bitcoin/src/backend/bitcoin_api.ts +++ b/examples/basic_bitcoin/src/backend/bitcoin_api.ts @@ -96,22 +96,15 @@ export async function sendTransaction( SEND_TRANSACTION_BASE_CYCLES + BigInt(transaction.length) * SEND_TRANSACTION_PER_BYTE_CYCLES; - try { - await fetch(`icp://aaaaa-aa/bitcoin_send_transaction`, { - body: serialize({ - args: [ - { - transaction, - network - } - ], - cycles: transactionFee - }) - }); - } catch (err: any) { - console.log('There was an error sending the transaction'); - console.log(err.message); - console.log(err); - throw err; - } + await fetch(`icp://aaaaa-aa/bitcoin_send_transaction`, { + body: serialize({ + args: [ + { + transaction, + network + } + ], + cycles: transactionFee + }) + }); } diff --git a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts index b974a8aa2b..c3c58acc37 100644 --- a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts @@ -34,6 +34,10 @@ export type ThresholdKeyInfo = { }; }; +type TransactionHashes = { + [txid: string]: string; +}; + /// Returns the P2PKH address of this canister at the given derivation path. export async function getAddress( network: BitcoinNetwork, @@ -88,11 +92,7 @@ export async function send( return signedTransaction.getId(); } -type TransactionHashes = { - [txid: string]: string; -}; - -export async function createSignedTransaction( +async function createSignedTransaction( network: BitcoinNetwork, derivationPath: Vec, keyName: string, @@ -119,9 +119,7 @@ export async function createSignedTransaction( ); // Sign the transaction. - const result = await signTransaction(transaction, keyName, derivationPath); - console.log('bw: createSignedTransaction: by golly I think it worked!!'); - return result; + return await signTransaction(transaction, keyName, derivationPath); } async function getFeePerByte(network: BitcoinNetwork): Promise { @@ -163,7 +161,6 @@ async function buildTransaction( // will get the following error while it's checking the transaction: // 'Fee is too small: expected more than 128 but got 0' // So I'm going to start at 130 instead - console.log('bw: buildTransaction: Building transaction...'); let totalFee = 130n; // eslint-disable-next-line no-constant-condition while (true) { @@ -177,8 +174,6 @@ async function buildTransaction( transactionHashes ); - console.log('bw: buildTransaction: start signing'); - // Sign the transaction. In this case, we only care about the size // of the signed transaction. const signedTransaction = await signTransaction( @@ -187,14 +182,9 @@ async function buildTransaction( derivationPath ); - console.log('bw: buildTransaction: end signing'); - const signedTxBytesLen = BigInt(signedTransaction.byteLength()); if ((signedTxBytesLen * feePerByte) / 1_000n === totalFee) { - console.log( - `bw: buildTransaction: Transaction built with fee ${totalFee}.` - ); return transaction; } else { totalFee = (signedTxBytesLen * feePerByte) / 1_000n; @@ -217,12 +207,8 @@ function buildTransactionWithFee( // we're using min_confirmations of 1. let utxosToSpend: Vec = []; let totalSpent = 0n; - console.log('bw: buildTransactionWithFee: start'); for (const utxo of [...ownUtxos].reverse()) { if (utxosToSpend.includes(utxo)) { - console.log( - "bw: buildTransactionWithFee: CHEEKY THING it's trying to double add!!" - ); continue; } totalSpent += utxo.value; @@ -232,7 +218,6 @@ function buildTransactionWithFee( break; } } - console.log('bw: buildTransactionWithFee: collected utxos'); if (totalSpent < amount + fee) { throw new Error( @@ -325,16 +310,11 @@ async function signTransaction( ): boolean => { // TODO I think if we pass along the ECPair we can validate with that. See the bitcoinjs-lib /create-psbt console.log( - `bw: signTransaction: validator: pubkey: ${pubkey.toString('hex')}` - ); - console.log( - `bw: signTransaction: validator: msghash: ${msghash.toString( - 'hex' - )}` - ); - console.log( - `bw: signTransaction: validator: sig: ${signature.toString('hex')}` + "Please visually inspect these to make sure they look right and that no one is trying to spend bitcoin they don't own" ); + console.log(`pubkey: ${pubkey.toString('hex')}`); + console.log(`msghash: ${msghash.toString('hex')}`); + console.log(`sig: ${signature.toString('hex')}`); return true; }; try { diff --git a/examples/basic_bitcoin/src/backend/ecdsa_api.ts b/examples/basic_bitcoin/src/backend/ecdsa_api.ts index 2ba8eeb2da..8fd7f9aeb3 100644 --- a/examples/basic_bitcoin/src/backend/ecdsa_api.ts +++ b/examples/basic_bitcoin/src/backend/ecdsa_api.ts @@ -7,8 +7,6 @@ export async function ecdsaPublicKey( keyName: string, derivationPath: DerivationPath ): Promise { - // Retrieve the public key of this canister at the given derivation path - // from the ECDSA API. const response = await fetch('icp://aaaaa-aa/ecdsa_public_key', { body: serialize({ args: [ diff --git a/examples/basic_bitcoin/src/backend/index.ts b/examples/basic_bitcoin/src/backend/index.ts index a4f682cfef..ada6b7062b 100644 --- a/examples/basic_bitcoin/src/backend/index.ts +++ b/examples/basic_bitcoin/src/backend/index.ts @@ -76,9 +76,6 @@ app.post('/send', async (req: Request, res) => { const destAddress = req.query.destinationAddress; const amount = BigInt(req.query.amountInSatoshi); const transactions = req.body; - console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); - console.log(`>>> Dest: ${destAddress} amount: ${amount}`); - console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); const txId = await bitcoinWallet.send( NETWORK, DERIVATION_PATH, @@ -91,40 +88,6 @@ app.post('/send', async (req: Request, res) => { res.send(txId); }); -app.post( - '/create-transaction', - async (req: Request, res) => { - const destAddress = req.query.destinationAddress; - const amount = BigInt(req.query.amountInSatoshi); - const transactions = req.body; - console.log('THESE ARE THE TRANSACTIONS!!!'); - console.log(transactions); - // const jsonTransactions = jsonParse(transactions); - console.log( - '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' - ); - console.log(`>>> Dest: ${destAddress} amount: ${amount}`); - console.log( - '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>' - ); - const transaction = await bitcoinWallet.createSignedTransaction( - NETWORK, - DERIVATION_PATH, - KEY_NAME, - destAddress, - amount, - transactions - ); - - const result = { - transactionHex: transaction.toHex(), - transactionBuf: transaction.toBuffer().toString('hex') - }; - - res.send(result); - } -); - app.listen(); function determineKeyName(network: BitcoinNetwork): string { diff --git a/examples/basic_bitcoin/test/bitcoin.ts b/examples/basic_bitcoin/test/bitcoin.ts new file mode 100644 index 0000000000..6829a7b452 --- /dev/null +++ b/examples/basic_bitcoin/test/bitcoin.ts @@ -0,0 +1,117 @@ +import { jsonParse } from 'azle'; +import { Transaction } from 'bitcoinjs-lib'; +import { execSync } from 'child_process'; + +type Block = { + hash: string; + confirmations: number; + height: number; + version: number; + versionHex: string; + merkleroot: string; + time: number; + mediantime: number; + nonce: number; + bits: string; + difficulty: number; + chainwork: string; + nTx: number; + previousblockhash: string; + nextblockhash: string; + strippedsize: number; + size: number; + weight: number; + tx: Tx[]; +}; + +type Tx = { + txid: string; + hash: string; + version: number; + size: number; + vsize: number; + weight: number; + locktime: number; + vin: [ + { + coinbase: string; + txinwitness: string[]; + sequence: number; + } + ]; + vout: Vout[]; + hex: string; +}; + +type Vout = { + value: number; + n: number; + scriptPubKey: { + asm: string; + desc: string; + hex: string; + address: string; + type: string; + }; +}; + +type TransactionHashes = { + [txid: string]: string; +}; + +export function getTotalOutput(tx: Transaction): number { + return tx.outs.reduce((total, output) => { + return total + output.value; + }, 0); +} + +export function generateToAddress(address: string, blocks: number) { + for (let i = 0; i < blocks; i++) { + execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf generatetoaddress 1 ${address}` + ); + } +} + +export function getUtxoHashes(): TransactionHashes { + return Array.from({ length: 102 }).reduce((acc, _, blockHeight) => { + const blockHash = getblockhash(blockHeight); + const block = getBlock(blockHash); + const result = getTransactionHashAndIdFromBlock(block); + return { ...acc, ...result }; + }, {} as TransactionHashes); +} + +function getBlock(hash: string): Block { + const getBlockResult = execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblock ${hash} 2` + ) + .toString() + .trim(); + + return jsonParse(getBlockResult); +} + +export function getTransaction(txid: string): Transaction { + return Transaction.fromHex(getTxHex(txid)); +} + +function getTxHex(txid: string): string { + return execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawtransaction ${txid}` + ) + .toString() + .trim(); +} + +function getblockhash(blockIndex: number): string { + return execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockhash ${blockIndex}` + ) + .toString() + .trim(); +} + +function getTransactionHashAndIdFromBlock(block: Block): TransactionHashes { + return { [block.tx[0].txid]: block.tx[0].hex }; +} diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 059a39a54a..18115dbd9c 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -5,11 +5,19 @@ import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; import { jsonParse, jsonStringify } from 'azle'; import { GetUtxosResult, Outpoint, Satoshi } from 'azle/canisters/management'; import { Test } from 'azle/test'; -import { networks, payments, Transaction } from 'bitcoinjs-lib'; -import { execSync } from 'child_process'; +import { networks, payments } from 'bitcoinjs-lib'; import { ECPairFactory } from 'ecpair'; +import { + generateToAddress, + getTotalOutput, + getTransaction, + getUtxoHashes +} from './bitcoin'; + const SINGLE_BLOCK_REWARD = 5_000_000_000n; +const FIRST_MINING_SESSION = 101; +const SECOND_MINING_SESSION = 3; const ECPair = ECPairFactory(ecc); @@ -55,10 +63,7 @@ export function getTests(canisterId: string): Test[] { name: 'mint BTC', prep: async () => { const address = await getAddress(origin); - - for (let i = 0; i < 101; i++) { - execSync(`npm run mint --address=${address}`); - } + generateToAddress(address, FIRST_MINING_SESSION); } }, { name: 'wait for blocks to settle', wait: 60_000 }, @@ -68,7 +73,11 @@ export function getTests(canisterId: string): Test[] { const address = await getAddress(origin); const balance = await getBalance(origin, address); - return { Ok: balance === SINGLE_BLOCK_REWARD * 101n }; + return { + Ok: + balance === + SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION) + }; } }, { @@ -86,8 +95,8 @@ export function getTests(canisterId: string): Test[] { return { Ok: - utxosResult.tip_height === 101 && - utxosResult.utxos.length === 101 && + utxosResult.tip_height === FIRST_MINING_SESSION && + utxosResult.utxos.length === FIRST_MINING_SESSION && checkUtxos(utxosResult.utxos) }; } @@ -122,27 +131,9 @@ export function getTests(canisterId: string): Test[] { return { Ok: balance === 0n }; } }, - { - name: '/create-transaction', - test: async () => { - const address = getToAddress(); - const body = jsonStringify(getUtxoHashes()); - const response = await fetch( - `${origin}/create-transaction?amountInSatoshi=${SINGLE_BLOCK_REWARD}&destinationAddress=${address}`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body - } - ); - const transactionHex = await response.text(); - console.log(transactionHex); - return { Ok: true }; - } - }, { name: '/send from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', - test: async () => { + prep: async () => { const toAddress = getToAddress(); const body = jsonStringify(getUtxoHashes()); const response = await fetch( @@ -156,71 +147,77 @@ export function getTests(canisterId: string): Test[] { } ); lastTx = await response.text(); - - // TODO is there anyway to deteremine if this actually worked or just by getting the balance bellow? - // TODO if that is the case then we should probably get rid of the create-transaction end point and call get balance as part of this one - return { Ok: true }; } }, { name: 'mint BTC', prep: async () => { - const address = await getToAddress(); - generateToAddress(address, 3); + const address = getToAddress(); + generateToAddress(address, SECOND_MINING_SESSION); } }, { name: 'wait for blocks to settle', wait: 60_000 }, { - name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv final', test: async () => { const address = getToAddress(); const balance = await getBalance(origin, address); - const previousTransaction = Transaction.fromHex( - getTxHex(lastTx) - ); + const previousTransaction = getTransaction(lastTx); const outputValue = BigInt(getTotalOutput(previousTransaction)); const inputValue = SINGLE_BLOCK_REWARD; const fee = inputValue - outputValue; - const blockRewards = SINGLE_BLOCK_REWARD * 3n + fee; + const blockRewards = + SINGLE_BLOCK_REWARD * BigInt(SECOND_MINING_SESSION) + fee; const expectedBalance = SINGLE_BLOCK_REWARD / 2n + blockRewards; + console.log(balance); + console.log(expectedBalance); + return { Ok: balance === expectedBalance }; } }, { - name: '/get-balance', + name: '/get-balance final', test: async () => { const address = await getAddress(origin); const balance = await getBalance(origin, address); - const previousTransaction = Transaction.fromHex( - getTxHex(lastTx) - ); + const previousTransaction = getTransaction(lastTx); const outputValue = BigInt(getTotalOutput(previousTransaction)); const amountSent = SINGLE_BLOCK_REWARD / 2n; const inputValue = SINGLE_BLOCK_REWARD; const fee = inputValue - outputValue; - const blockRewards = SINGLE_BLOCK_REWARD * 101n; + const blockRewards = + SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION); const expectedBalance = blockRewards - (amountSent + fee); return { Ok: balance === expectedBalance }; } + }, + { + name: '/get-current-fee-percentiles', + test: async () => { + const response = await fetch( + `${origin}/get-current-fee-percentiles`, + { method: 'POST' } + ); + + const feePercentiles = jsonParse(await response.text()); + console.log(feePercentiles); + console.log(feePercentiles.length); + + return { Ok: feePercentiles.length === 101 }; // TODO is that what we are expecting it to be? + } } ]; } -function getTotalOutput(tx: Transaction): number { - return tx.outs.reduce((total, output) => { - return total + output.value; - }, 0); -} - async function getAddress(origin: string): Promise { const response = await fetch(`${origin}/get-p2pkh-address`, { method: 'POST' @@ -228,109 +225,6 @@ async function getAddress(origin: string): Promise { return await response.text(); } -function generateToAddress(address: string, blocks: number) { - for (let i = 0; i < blocks; i++) { - execSync(`npm run mint --address=${address}`); - } -} - -function getUtxoHashes(): TransactionHashes { - // TODO would range() be better here? - return Array.from({ length: 102 }).reduce((acc, _, blockHeight) => { - const blockHash = getblockhash(blockHeight); - const block = getBlock(blockHash); - const result = getTransactionHashAndIdFromBlock(block); - return { ...acc, ...result }; - }, {} as TransactionHashes); -} - -function getTxHex(txid: string): string { - return execSync( - `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawtransaction ${txid}` - ) - .toString() - .trim(); -} - -function getblockhash(blockIndex: number): string { - return execSync( - `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockhash ${blockIndex}` - ) - .toString() - .trim(); -} - -type Block = { - hash: string; - confirmations: number; - height: number; - version: number; - versionHex: string; - merkleroot: string; - time: number; - mediantime: number; - nonce: number; - bits: string; - difficulty: number; - chainwork: string; - nTx: number; - previousblockhash: string; - nextblockhash: string; - strippedsize: number; - size: number; - weight: number; - tx: Tx[]; -}; - -type Tx = { - txid: string; - hash: string; - version: number; - size: number; - vsize: number; - weight: number; - locktime: number; - vin: [ - { - coinbase: string; - txinwitness: string[]; - sequence: number; - } - ]; - vout: Vout[]; - hex: string; -}; - -type Vout = { - value: number; - n: number; - scriptPubKey: { - asm: string; - desc: string; - hex: string; - address: string; - type: string; - }; -}; - -function getBlock(hash: string): Block { - const getBlockResult = execSync( - `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblock ${hash} 2` - ) - .toString() - .trim(); - - return jsonParse(getBlockResult); -} - -type TransactionHashes = { - [txid: string]: string; -}; - -function getTransactionHashAndIdFromBlock(block: Block): TransactionHashes { - return { [block.tx[0].txid]: block.tx[0].hex }; -} - function getToAddress(): string { const keyPair = ECPair.fromWIF( 'L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv' From 923dc052dddb90aa1b64e54aae37ebc74e607216 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 6 May 2024 13:21:25 -0600 Subject: [PATCH 10/49] unify index.ts with rust example --- examples/basic_bitcoin/src/backend/index.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/basic_bitcoin/src/backend/index.ts b/examples/basic_bitcoin/src/backend/index.ts index ada6b7062b..5aed090dbf 100644 --- a/examples/basic_bitcoin/src/backend/index.ts +++ b/examples/basic_bitcoin/src/backend/index.ts @@ -4,13 +4,13 @@ import express, { Request } from 'express'; import * as bitcoinApi from './bitcoin_api'; import * as bitcoinWallet from './bitcoin_wallet'; +import { SendRequest } from './types'; -export type SendRequest = { - destinationAddress: string; - amountInSatoshi: bigint; - utxos: string[]; -}; - +// The bitcoin network to connect to. +// +// When developing locally this should be `Regtest`. +// When deploying to the IC this should be `Testnet`. +// `Mainnet` is currently unsupported. const NETWORK: BitcoinNetwork = determineNetwork( process.env.BITCOIN_NETWORK ) ?? { @@ -73,15 +73,13 @@ app.post('/get-p2pkh-address', async (req, res) => { }); app.post('/send', async (req: Request, res) => { - const destAddress = req.query.destinationAddress; - const amount = BigInt(req.query.amountInSatoshi); const transactions = req.body; const txId = await bitcoinWallet.send( NETWORK, DERIVATION_PATH, KEY_NAME, - destAddress, - amount, + req.query.destinationAddress, + BigInt(req.query.amountInSatoshi), transactions ); From 1e2f48c1707fa1f1330b0f9508ac6d10defc564b Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 6 May 2024 13:26:08 -0600 Subject: [PATCH 11/49] unify ecdsa api --- examples/basic_bitcoin/src/backend/bitcoin_wallet.ts | 5 +---- examples/basic_bitcoin/src/backend/ecdsa_api.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts index c3c58acc37..0342e4dc26 100644 --- a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts @@ -60,10 +60,7 @@ export async function getPublicKey( derivationPath: DerivationPath ): Promise { // Fetch the public key of the given derivation path. - return Buffer.from( - await ecdsaApi.ecdsaPublicKey(keyName, derivationPath), - 'hex' - ); + return Buffer.from(await ecdsaApi.ecdsaPublicKey(keyName, derivationPath)); } /// Sends a transaction to the network that transfers the given amount to the diff --git a/examples/basic_bitcoin/src/backend/ecdsa_api.ts b/examples/basic_bitcoin/src/backend/ecdsa_api.ts index 8fd7f9aeb3..2b361b8ddd 100644 --- a/examples/basic_bitcoin/src/backend/ecdsa_api.ts +++ b/examples/basic_bitcoin/src/backend/ecdsa_api.ts @@ -6,7 +6,9 @@ import { DerivationPath } from './types'; export async function ecdsaPublicKey( keyName: string, derivationPath: DerivationPath -): Promise { +): Promise { + // Retrieve the public key of this canister at the given derivation path + // from the ECDSA API. const response = await fetch('icp://aaaaa-aa/ecdsa_public_key', { body: serialize({ args: [ @@ -21,8 +23,9 @@ export async function ecdsaPublicKey( ] }) }); - const ecdsaPublicKey = await response.json(); - return Buffer.from(ecdsaPublicKey.public_key).toString('hex'); + const ecdsaPublicKeyResult = await response.json(); + + return ecdsaPublicKeyResult.public_key; } export async function signWithECDSA( From 2372315c1919f4bf9f460d279d299e58d405fc7b Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 6 May 2024 13:28:40 -0600 Subject: [PATCH 12/49] unify bitcoin api --- .../basic_bitcoin/src/backend/bitcoin_api.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/basic_bitcoin/src/backend/bitcoin_api.ts b/examples/basic_bitcoin/src/backend/bitcoin_api.ts index 3babe85934..0bdeebfb4f 100644 --- a/examples/basic_bitcoin/src/backend/bitcoin_api.ts +++ b/examples/basic_bitcoin/src/backend/bitcoin_api.ts @@ -1,4 +1,4 @@ -import { blob, nat64, serialize, Vec } from 'azle'; +import { serialize } from 'azle'; import { BitcoinNetwork, GetUtxosResult, @@ -6,11 +6,11 @@ import { } from 'azle/canisters/management/bitcoin'; // The fees for the various bitcoin endpoints. -const GET_BALANCE_COST_CYCLES: nat64 = 100_000_000n; -const GET_UTXOS_COST_CYCLES: nat64 = 10_000_000_000n; -const GET_CURRENT_FEE_PERCENTILES_CYCLES: nat64 = 100_000_000n; -const SEND_TRANSACTION_BASE_CYCLES: nat64 = 5_000_000_000n; -const SEND_TRANSACTION_PER_BYTE_CYCLES: nat64 = 20_000_000n; +const GET_BALANCE_COST_CYCLES = 100_000_000n; +const GET_UTXOS_COST_CYCLES = 10_000_000_000n; +const GET_CURRENT_FEE_PERCENTILES_CYCLES = 100_000_000n; +const SEND_TRANSACTION_BASE_CYCLES = 5_000_000_000n; +const SEND_TRANSACTION_PER_BYTE_CYCLES = 20_000_000n; /// Returns the balance of the given bitcoin address. /// @@ -19,7 +19,7 @@ const SEND_TRANSACTION_PER_BYTE_CYCLES: nat64 = 20_000_000n; export async function getBalance( network: BitcoinNetwork, address: string -): Promise { +): Promise { const response = await fetch(`icp://aaaaa-aa/bitcoin_get_balance`, { body: serialize({ args: [ @@ -67,7 +67,7 @@ export async function getUtxos( /// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_current_fee_percentiles export async function getCurrentFeePercentiles( network: BitcoinNetwork -): Promise> { +): Promise { const response = await fetch( `icp://aaaaa-aa/bitcoin_get_current_fee_percentiles`, { @@ -90,7 +90,7 @@ export async function getCurrentFeePercentiles( /// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_send_transaction export async function sendTransaction( network: BitcoinNetwork, - transaction: blob + transaction: Uint8Array ): Promise { const transactionFee = SEND_TRANSACTION_BASE_CYCLES + From 636030583dc429b150deb10a66cb37d8a939dd93 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 6 May 2024 13:31:21 -0600 Subject: [PATCH 13/49] unify bitcoin wallet --- .../basic_bitcoin/src/backend/bitcoin_api.ts | 16 +- .../src/backend/bitcoin_wallet.ts | 277 ++++++------------ examples/basic_bitcoin/src/backend/index.ts | 4 +- examples/basic_bitcoin/src/backend/types.ts | 9 - examples/basic_bitcoin/test/tests.ts | 24 +- 5 files changed, 110 insertions(+), 220 deletions(-) diff --git a/examples/basic_bitcoin/src/backend/bitcoin_api.ts b/examples/basic_bitcoin/src/backend/bitcoin_api.ts index 0bdeebfb4f..d268a531d0 100644 --- a/examples/basic_bitcoin/src/backend/bitcoin_api.ts +++ b/examples/basic_bitcoin/src/backend/bitcoin_api.ts @@ -1,4 +1,4 @@ -import { serialize } from 'azle'; +import { blob, nat64, serialize, Vec } from 'azle'; import { BitcoinNetwork, GetUtxosResult, @@ -6,11 +6,11 @@ import { } from 'azle/canisters/management/bitcoin'; // The fees for the various bitcoin endpoints. -const GET_BALANCE_COST_CYCLES = 100_000_000n; -const GET_UTXOS_COST_CYCLES = 10_000_000_000n; -const GET_CURRENT_FEE_PERCENTILES_CYCLES = 100_000_000n; -const SEND_TRANSACTION_BASE_CYCLES = 5_000_000_000n; -const SEND_TRANSACTION_PER_BYTE_CYCLES = 20_000_000n; +const GET_BALANCE_COST_CYCLES: nat64 = 100_000_000n; +const GET_UTXOS_COST_CYCLES: nat64 = 10_000_000_000n; +const GET_CURRENT_FEE_PERCENTILES_CYCLES: nat64 = 100_000_000n; +const SEND_TRANSACTION_BASE_CYCLES: nat64 = 5_000_000_000n; +const SEND_TRANSACTION_PER_BYTE_CYCLES: nat64 = 20_000_000n; /// Returns the balance of the given bitcoin address. /// @@ -67,7 +67,7 @@ export async function getUtxos( /// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_current_fee_percentiles export async function getCurrentFeePercentiles( network: BitcoinNetwork -): Promise { +): Promise> { const response = await fetch( `icp://aaaaa-aa/bitcoin_get_current_fee_percentiles`, { @@ -90,7 +90,7 @@ export async function getCurrentFeePercentiles( /// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_send_transaction export async function sendTransaction( network: BitcoinNetwork, - transaction: Uint8Array + transaction: blob ): Promise { const transactionFee = SEND_TRANSACTION_BASE_CYCLES + diff --git a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts index 0342e4dc26..01585fba89 100644 --- a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts @@ -7,7 +7,6 @@ //! * Support for address types that aren't P2PKH. //! * Caching spent UTXOs so that they are not reused in future transactions. //! * Option to set the fee. -import { blob, nat64, Principal, Vec } from 'azle'; import { BitcoinNetwork, MillisatoshiPerByte, @@ -22,45 +21,22 @@ import { Buffer } from 'buffer'; import * as bitcoinApi from './bitcoin_api'; import * as ecdsaApi from './ecdsa_api'; import { BitcoinTxid, DerivationPath } from './types'; -export const ECDSA = 'ecdsa'; -export const SCHNORR = 'schnorr'; - -export type ThresholdKeyInfo = { - derivationPath: Uint8Array[]; - canisterId?: Principal | string; - keyId?: { - curve: 'secp256k1'; - name: 'dfx_test_key' | 'test_key_1' | 'key_1'; - }; -}; type TransactionHashes = { [txid: string]: string; }; /// Returns the P2PKH address of this canister at the given derivation path. -export async function getAddress( +export async function getP2pkhAddress( network: BitcoinNetwork, keyName: string, derivationPath: DerivationPath ): Promise { - const pubkey = await getPublicKey(keyName, derivationPath); - const { address } = bitcoin.payments.p2pkh({ - pubkey, - network: determineNetwork(network) - }); - if (address === undefined) { - throw new Error('Unable to get address from the canister'); - } - return address; -} - -export async function getPublicKey( - keyName: string, - derivationPath: DerivationPath -): Promise { // Fetch the public key of the given derivation path. - return Buffer.from(await ecdsaApi.ecdsaPublicKey(keyName, derivationPath)); + const publicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); + + // Compute the address. + return publicKeyToP2pkhAddress(network, publicKey); } /// Sends a transaction to the network that transfers the given amount to the @@ -68,47 +44,43 @@ export async function getPublicKey( /// at the given derivation path. export async function send( network: BitcoinNetwork, - derivationPath: Vec, - keyName: string, - dstAddressString: string, - amount: Satoshi, - transactionHashes: TransactionHashes -): Promise { - const signedTransaction = await createSignedTransaction( - network, - derivationPath, - keyName, - dstAddressString, - amount, - transactionHashes - ); - const signedTransactionBytes = signedTransaction.toBuffer(); - - await bitcoinApi.sendTransaction(network, signedTransactionBytes); - - return signedTransaction.getId(); -} - -async function createSignedTransaction( - network: BitcoinNetwork, - derivationPath: Vec, + derivationPath: DerivationPath, keyName: string, dstAddress: string, amount: Satoshi, transactionHashes: TransactionHashes -): Promise { - const ownAddress = await getAddress(network, keyName, derivationPath); +): Promise { + // Get fee percentiles from previous transactions to estimate our own fee. + const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); + const feePerByte = + feePercentiles.length === 0 + ? // There are no fee percentiles. This case can only happen on a regtest + // network where there are no non-coinbase transactions. In this case, + // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) + 2_000n + : // Choose the 50th percentile for sending fees. + feePercentiles[49]; + + // Fetch our public key, P2PKH address, and UTXOs. + const ownAddress = await getP2pkhAddress(network, keyName, derivationPath); + const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); + + console.log('Fetching UTXOs...'); + // Note that pagination may have to be used to get all UTXOs for the given address. + // For the sake of simplicity, it is assumed here that the `utxo` field in the response + // contains all UTXOs. const ownUtxos = (await bitcoinApi.getUtxos(network, ownAddress.toString())) .utxos; // Build the transaction that sends `amount` to the destination address. const transaction = await buildTransaction( + ownPublicKey, ownAddress, ownUtxos, dstAddress, amount, - await getFeePerByte(network), + feePerByte, keyName, derivationPath, determineNetwork(network), @@ -116,27 +88,27 @@ async function createSignedTransaction( ); // Sign the transaction. - return await signTransaction(transaction, keyName, derivationPath); -} + const signedTransaction = await signTransaction( + ownPublicKey, + transaction, + keyName, + derivationPath + ); + const signedTransactionBytes = signedTransaction.toBuffer(); -async function getFeePerByte(network: BitcoinNetwork): Promise { - // Get fee percentiles from previous transactions to estimate our own fee. - const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); + console.log('Sending transaction...'); + await bitcoinApi.sendTransaction(network, signedTransactionBytes); + console.log('Done.'); - return feePercentiles.length === 0 - ? // There are no fee percentiles. This case can only happen on a regtest - // network where there are no non-coinbase transactions. In this case, - // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) - 2_000n - : // Choose the 50th percentile for sending fees. - feePercentiles[49]; + return signedTransaction.getId(); } // Builds a transaction to send the given `amount` of satoshis to the // destination address. async function buildTransaction( + ownPublicKey: Uint8Array, ownAddress: string, - ownUtxos: Vec, + ownUtxos: Utxo[], dstAddress: string, amount: Satoshi, feePerByte: MillisatoshiPerByte, @@ -153,12 +125,7 @@ async function buildTransaction( // We solve this problem iteratively. We start with a fee of zero, build // and sign a transaction, see what its size is, and then update the fee, // rebuild the transaction, until the fee is set to the correct amount. - // - // However zero is too small a fee when you serialize the transaction you - // will get the following error while it's checking the transaction: - // 'Fee is too small: expected more than 128 but got 0' - // So I'm going to start at 130 instead - let totalFee = 130n; + let totalFee = 0n; // eslint-disable-next-line no-constant-condition while (true) { let transaction = buildTransactionWithFee( @@ -174,6 +141,7 @@ async function buildTransaction( // Sign the transaction. In this case, we only care about the size // of the signed transaction. const signedTransaction = await signTransaction( + ownPublicKey, transaction.clone(), keyName, derivationPath @@ -198,11 +166,14 @@ function buildTransactionWithFee( network: bitcoin.Network, transactionHashes: TransactionHashes ): Psbt { + // Assume that any amount below this threshold is dust. + const dustThreshold = 1_000n; + // Select which UTXOs to spend. We naively spend the oldest available UTXOs, // even if they were previously spent in a transaction. This isn't a // problem as long as at most one transaction is created per block and // we're using min_confirmations of 1. - let utxosToSpend: Vec = []; + let utxosToSpend: Utxo[] = []; let totalSpent = 0n; for (const utxo of [...ownUtxos].reverse()) { if (utxosToSpend.includes(utxo)) { @@ -225,63 +196,44 @@ function buildTransactionWithFee( let transaction = new Psbt({ network }); transaction.setVersion(2); - console.log('bw: buildTransactionWithFee: building psbt'); - - try { - console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); - console.log( - `bw: buildTransactionWithFee: Adding ${utxosToSpend.length} inputs to the transaction` - ); - console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); - for (let utxo of utxosToSpend) { - const hash = Buffer.from(utxo.outpoint.txid).reverse(); - const txid = hash.toString('hex'); - console.log( - `bw: buildTransactionWithFee: Adding ${txid}:${utxo.outpoint.vout}` - ); - const nonWitnessUtxo = Buffer.from(transactionHashes[txid], 'hex'); - transaction.addInput({ - hash: Buffer.from(utxo.outpoint.txid), // TOOD if this works clean it up - index: utxo.outpoint.vout, - nonWitnessUtxo - }); - } - } catch (err: any) { - console.log('bw: buildTransactionWithFee: Error:'); - console.log(err.message); - console.log(err); - throw err; + for (let utxo of utxosToSpend) { + const hash = Buffer.from(utxo.outpoint.txid).reverse(); + const txid = hash.toString('hex'); + const nonWitnessUtxo = Buffer.from(transactionHashes[txid], 'hex'); + transaction.addInput({ + hash: Buffer.from(utxo.outpoint.txid), // TOOD if this works clean it up + index: utxo.outpoint.vout, + nonWitnessUtxo + }); } - console.log('bw: buildTransactionWithFee: inputs added'); + const remainingAmount = totalSpent - (amount + fee); transaction.addOutput({ address: destAddress, value: Number(amount) }); - transaction.addOutput({ - address: ownAddress, - value: Number(totalSpent - (amount + fee)) - }); - console.log('bw: buildTransactionWithFee: transaction built'); + if (remainingAmount >= dustThreshold) { + transaction.addOutput({ + address: ownAddress, + value: Number(remainingAmount) + }); + } return transaction; } -// function getNonWitnessUtxos(txid: Uint8Array): Buffer { -// const txidHex = Buffer.from(txid).toString('hex'); -// // TODO implement this -// console.log( -// `We need to find a way to get the tx data from this txid: ${txidHex}` -// ); -// throw ''; -// } - +// Sign a bitcoin transaction. +// +// IMPORTANT: This method is for demonstration purposes only and it only +// supports signing transactions if: +// +// 1. All the inputs are referencing outpoints that are owned by `own_address`. +// 2. `own_address` is a P2PKH address. async function signTransaction( + ownPublicKey: Uint8Array, transaction: Psbt, keyName: string, derivationPath: DerivationPath ): Promise { - console.log('bw: signTransaction: getPublicKey'); - const publicKey = await getPublicKey(keyName, derivationPath); const signer: bitcoin.SignerAsync = { sign: async (hashBuffer) => { const sec1 = await ecdsaApi.signWithECDSA( @@ -289,16 +241,10 @@ async function signTransaction( derivationPath, Uint8Array.from(hashBuffer) ); - console.log( - `bw: signTransaction: signer: got signature back from IC. ${sec1.length}` - ); - const der = sec1ToDer(sec1); - console.log( - `bw: signTransaction: signer: We may have to convert to der. ${der.length}` - ); - return Buffer.from(sec1); // TODO if this works get rid of DER, why did we need this? If it doesn't work why does DER make it longer and what can we do to get it back to 64? + + return Buffer.from(sec1); }, - publicKey + publicKey: Buffer.from(ownPublicKey) }; const validator: ValidateSigFunction = ( pubkey: Buffer, @@ -314,35 +260,10 @@ async function signTransaction( console.log(`sig: ${signature.toString('hex')}`); return true; }; - try { - console.log('bw: signTransaction: start signing all inputs'); - for (let i = 0; i < transaction.txInputs.length; i++) { - await transaction.signInputAsync(i, signer); - } - // await transaction.signAllInputsAsync(signer); // TODO I bet we could go back to this once we get the inputs figured out - console.log('bw: signTransaction: signed all inputs'); - transaction.validateSignaturesOfAllInputs(validator); - console.log('bw: signTransaction: validated all signatures'); - transaction.finalizeAllInputs(); // TODO what if we don't finalize it? - console.log('bw: signTransaction: finalized'); - try { - return transaction.extractTransaction(); - } catch (err: any) { - console.log( - 'bw: signTransaction: Error: extracting the transaction' - ); - console.log(err.message); - console.log(err); - throw err; - } - } catch (err: any) { - console.log( - 'bw: signTransaction: Error: signing all inputs (or maybe duplication of extracting the transaction)' - ); - console.log(err.message); - console.log(err); - throw err; - } + await transaction.signAllInputsAsync(signer); + transaction.validateSignaturesOfAllInputs(validator); + transaction.finalizeAllInputs(); + return transaction.extractTransaction(); } function determineNetwork(network: BitcoinNetwork): networks.Network { @@ -358,35 +279,17 @@ function determineNetwork(network: BitcoinNetwork): networks.Network { throw new Error(`Unknown Network: ${network}`); } -// Converts a SEC1 ECDSA signature to the DER format. -function sec1ToDer(sec1Signature: Uint8Array): Uint8Array { - let r: Uint8Array; - - if ((sec1Signature[0] & 0x80) !== 0) { - // r is negative. Prepend a zero byte. - const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(0, 32)]); - r = tmp; - } else { - // r is positive. - r = sec1Signature.slice(0, 32); - } - - let s: Uint8Array; - - if ((sec1Signature[32] & 0x80) !== 0) { - // s is negative. Prepend a zero byte. - const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(32)]); - s = tmp; - } else { - // s is positive. - s = sec1Signature.slice(32); +// Converts a public key to a P2PKH address. +function publicKeyToP2pkhAddress( + network: BitcoinNetwork, + publicKey: Uint8Array +): string { + const { address } = bitcoin.payments.p2pkh({ + pubkey: Buffer.from(publicKey), + network: determineNetwork(network) + }); + if (address === undefined) { + throw new Error('Unable to get address from the canister'); } - - // Convert signature to DER. - return Uint8Array.from([ - ...[0x30, 4 + r.length + s.length, 0x02, r.length], - ...r, - ...[0x02, s.length], - ...s - ]); + return address; } diff --git a/examples/basic_bitcoin/src/backend/index.ts b/examples/basic_bitcoin/src/backend/index.ts index 5aed090dbf..bcd62986a1 100644 --- a/examples/basic_bitcoin/src/backend/index.ts +++ b/examples/basic_bitcoin/src/backend/index.ts @@ -63,13 +63,13 @@ app.post('/get-current-fee-percentiles', async (req, res) => { /// Returns the P2PKH address of this canister at a specific derivation path. app.post('/get-p2pkh-address', async (req, res) => { - const address = await bitcoinWallet.getAddress( + const address = await bitcoinWallet.getP2pkhAddress( NETWORK, KEY_NAME, DERIVATION_PATH ); - res.send(address.toString()); + res.send(address); }); app.post('/send', async (req: Request, res) => { diff --git a/examples/basic_bitcoin/src/backend/types.ts b/examples/basic_bitcoin/src/backend/types.ts index fd5d8373b1..d4ec231467 100644 --- a/examples/basic_bitcoin/src/backend/types.ts +++ b/examples/basic_bitcoin/src/backend/types.ts @@ -5,14 +5,5 @@ export type SendRequest = { amountInSatoshi: nat64; }; -// TODO decide if we are going to use this or not -export type ThresholdKeyInfo = { - derivationPath: DerivationPath; - keyId?: { - curve: 'secp256k1'; - name: 'dfx_test_key' | 'test_key_1' | 'key_1'; - }; -}; - export type BitcoinTxid = string; export type DerivationPath = Uint8Array[]; diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 18115dbd9c..91baa69ffd 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -17,7 +17,7 @@ import { const SINGLE_BLOCK_REWARD = 5_000_000_000n; const FIRST_MINING_SESSION = 101; -const SECOND_MINING_SESSION = 3; +const SECOND_MINING_SESSION = 10; const ECPair = ECPairFactory(ecc); @@ -45,7 +45,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-p2pkh-address', test: async () => { - const address = await getAddress(origin); + const address = await getP2pkhAddress(origin); return { Ok: canisterAddressForm.length === address.length }; } @@ -53,7 +53,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance', test: async () => { - const address = await getAddress(origin); + const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); return { Ok: balance === 0n }; @@ -62,7 +62,7 @@ export function getTests(canisterId: string): Test[] { { name: 'mint BTC', prep: async () => { - const address = await getAddress(origin); + const address = await getP2pkhAddress(origin); generateToAddress(address, FIRST_MINING_SESSION); } }, @@ -70,7 +70,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance', test: async () => { - const address = await getAddress(origin); + const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); return { @@ -83,7 +83,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-utxos', test: async () => { - const address = await getAddress(origin); + const address = await getP2pkhAddress(origin); const response = await fetch( `${origin}/get-utxos?address=${address}`, @@ -147,6 +147,7 @@ export function getTests(canisterId: string): Test[] { } ); lastTx = await response.text(); + console.log(lastTx); } }, { @@ -174,9 +175,6 @@ export function getTests(canisterId: string): Test[] { SINGLE_BLOCK_REWARD * BigInt(SECOND_MINING_SESSION) + fee; const expectedBalance = SINGLE_BLOCK_REWARD / 2n + blockRewards; - console.log(balance); - console.log(expectedBalance); - return { Ok: balance === expectedBalance }; @@ -185,7 +183,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance final', test: async () => { - const address = await getAddress(origin); + const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); const previousTransaction = getTransaction(lastTx); @@ -209,16 +207,14 @@ export function getTests(canisterId: string): Test[] { ); const feePercentiles = jsonParse(await response.text()); - console.log(feePercentiles); - console.log(feePercentiles.length); - return { Ok: feePercentiles.length === 101 }; // TODO is that what we are expecting it to be? + return { Ok: feePercentiles.length === 0 }; // TODO is that what we are expecting it to be? } } ]; } -async function getAddress(origin: string): Promise { +async function getP2pkhAddress(origin: string): Promise { const response = await fetch(`${origin}/get-p2pkh-address`, { method: 'POST' }); From ee19867b399f7b77e7c2b0204131c35e502dfb1d Mon Sep 17 00:00:00 2001 From: Jordan Last Date: Tue, 7 May 2024 10:49:57 -0500 Subject: [PATCH 14/49] move file uploader agent creation code up to a common location, use the uploader agent for reloadjs as well --- examples/large_files/test/tests.ts | 2 +- src/compiler/compile_typescript_code.ts | 2 +- src/compiler/custom_js_modules/zlib.ts | 4 ---- src/compiler/file_uploader/index.ts | 2 +- src/compiler/file_watcher/file_watcher.ts | 13 +++++++++---- src/compiler/file_watcher/setup_file_watcher.ts | 6 ++++-- src/compiler/index.ts | 3 ++- .../{file_uploader => }/uploader_identity.ts | 2 +- 8 files changed, 19 insertions(+), 15 deletions(-) delete mode 100644 src/compiler/custom_js_modules/zlib.ts rename src/compiler/{file_uploader => }/uploader_identity.ts (96%) diff --git a/examples/large_files/test/tests.ts b/examples/large_files/test/tests.ts index ca16b68b61..0bc8c15142 100644 --- a/examples/large_files/test/tests.ts +++ b/examples/large_files/test/tests.ts @@ -10,7 +10,7 @@ import { rm } from 'fs/promises'; import { join } from 'path'; import { v4 } from 'uuid'; -import { AZLE_UPLOADER_IDENTITY_NAME } from '../../../src/compiler/file_uploader/uploader_identity'; +import { AZLE_UPLOADER_IDENTITY_NAME } from '../../../src/compiler/uploader_identity'; import { generateTestFileOfSize } from './generateTestFiles'; export function getTests(canisterId: string): Test[] { diff --git a/src/compiler/compile_typescript_code.ts b/src/compiler/compile_typescript_code.ts index c62640fb77..c680581475 100644 --- a/src/compiler/compile_typescript_code.ts +++ b/src/compiler/compile_typescript_code.ts @@ -142,7 +142,7 @@ export async function bundleFromString( os: `${finalWasmedgeQuickJsPath}/modules/os.js`, // crypto: `${finalWasmedgeQuickJsPath}/modules/crypto.js`, // TODO waiting on wasi-crypto crypto: 'crypto-browserify', // TODO we really want the wasmedge-quickjs version once wasi-crypto is working - zlib: path.join(__dirname, 'custom_js_modules/zlib.ts'), + zlib: 'pako', 'internal/deps/acorn/acorn/dist/acorn': path.join( __dirname, 'custom_js_modules/acorn/acorn.ts' diff --git a/src/compiler/custom_js_modules/zlib.ts b/src/compiler/custom_js_modules/zlib.ts deleted file mode 100644 index 449471cbc1..0000000000 --- a/src/compiler/custom_js_modules/zlib.ts +++ /dev/null @@ -1,4 +0,0 @@ -// TODO this is just a placeholder module so that bundling won't break -// TODO eventually we would want a real implementation of this in Azle or wasmedge-quickjs or Waden - -export {}; diff --git a/src/compiler/file_uploader/index.ts b/src/compiler/file_uploader/index.ts index 2454821ecc..4f0d99d25f 100644 --- a/src/compiler/file_uploader/index.ts +++ b/src/compiler/file_uploader/index.ts @@ -1,9 +1,9 @@ import { getCanisterId } from '../../../dfx'; +import { generateUploaderIdentity } from '../uploader_identity'; import { expandPaths } from './expand_paths'; import { onBeforeExit } from './on_before_exit'; import { uploadFile } from './upload_file'; import { createActor } from './uploader_actor'; -import { generateUploaderIdentity } from './uploader_identity'; export type Src = string; export type Dest = string; diff --git a/src/compiler/file_watcher/file_watcher.ts b/src/compiler/file_watcher/file_watcher.ts index 6649bf9097..0912d11763 100644 --- a/src/compiler/file_watcher/file_watcher.ts +++ b/src/compiler/file_watcher/file_watcher.ts @@ -2,8 +2,9 @@ import { Actor, ActorMethod, ActorSubclass } from '@dfinity/agent'; import { watch } from 'chokidar'; import { writeFileSync } from 'fs'; -import { createAuthenticatedAgent, whoami } from '../../../dfx'; +import { createAuthenticatedAgent } from '../../../dfx'; import { getCanisterJavaScript } from '../get_canister_javascript'; +import { generateUploaderIdentity } from '../uploader_identity'; import { ok } from '../utils/result'; type ActorReloadJs = ActorSubclass<_SERVICE>; @@ -21,13 +22,14 @@ const mainPath = process.argv[4]; const wasmedgeQuickJsPath = process.argv[5]; const esmAliases = JSON.parse(process.argv[6]); const esmExternals = JSON.parse(process.argv[7]); +const canisterName = process.argv[8]; // TODO https://github.com/demergent-labs/azle/issues/1664 watch(process.cwd(), { ignored: ['**/.dfx/**', '**/.azle/**', '**/node_modules/**'] }).on('all', async (event, path) => { if (actor === undefined) { - actor = await createActorReloadJs(); + actor = await createActorReloadJs(canisterName); } if (process.env.AZLE_VERBOSE === 'true') { @@ -105,8 +107,11 @@ async function reloadJs( writeFileSync(reloadedJsPath, reloadedJs); } -async function createActorReloadJs(): Promise { - const agent = await createAuthenticatedAgent(whoami()); +async function createActorReloadJs( + canisterName: string +): Promise { + const identityName = generateUploaderIdentity(canisterName); + const agent = await createAuthenticatedAgent(identityName); return Actor.createActor( ({ IDL }) => { diff --git a/src/compiler/file_watcher/setup_file_watcher.ts b/src/compiler/file_watcher/setup_file_watcher.ts index b06cde8baa..c43ed35f82 100644 --- a/src/compiler/file_watcher/setup_file_watcher.ts +++ b/src/compiler/file_watcher/setup_file_watcher.ts @@ -9,7 +9,8 @@ export function setupFileWatcher( mainPath: string, wasmedgeQuickJsPath: string, esmAliases: Record, - esmExternals: string[] + esmExternals: string[], + canisterName: string ) { try { // TODO should we check that this was successful in killing @@ -35,7 +36,8 @@ export function setupFileWatcher( mainPath, wasmedgeQuickJsPath, JSON.stringify(esmAliases), - JSON.stringify(esmExternals) + JSON.stringify(esmExternals), + canisterName ], { detached: true, diff --git a/src/compiler/index.ts b/src/compiler/index.ts index 511a131707..b0e91b55d4 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -73,7 +73,8 @@ async function azle() { canisterConfig.main, wasmedgeQuickJsPath, esmAliases, - esmExternals + esmExternals, + canisterName ); await time( diff --git a/src/compiler/file_uploader/uploader_identity.ts b/src/compiler/uploader_identity.ts similarity index 96% rename from src/compiler/file_uploader/uploader_identity.ts rename to src/compiler/uploader_identity.ts index 203e9f90c3..c1d84d5d05 100644 --- a/src/compiler/file_uploader/uploader_identity.ts +++ b/src/compiler/uploader_identity.ts @@ -3,7 +3,7 @@ import { generateIdentity, getPrincipal, identityExists -} from '../../../dfx'; +} from '../../dfx'; export const AZLE_UPLOADER_IDENTITY_NAME = process.env.AZLE_UPLOADER_IDENTITY_NAME ?? '_azle_file_uploader_identity'; From 056ba3a993c43b3f84e39bdd0914942285d939b4 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 6 May 2024 14:33:32 -0600 Subject: [PATCH 15/49] add to test.yml --- .github/workflows/test.yml | 1 + examples/basic_bitcoin/test/tests.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 27d2c1b7e3..273ba99557 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -76,6 +76,7 @@ jobs: "examples/audio_and_video", "examples/audio_recorder", "examples/autoreload", + "examples/basic_bitcoin", "examples/bitcoin", "examples/bitcoinjs-lib", "examples/bitcore-lib", diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 91baa69ffd..3b9846fc51 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -1,6 +1,7 @@ import * as dns from 'node:dns'; dns.setDefaultResultOrder('ipv4first'); +// import * as ecc from 'tiny-secp256k1/lib/'; // TODO we should switch to this import as soon as we have wasm support import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; import { jsonParse, jsonStringify } from 'azle'; import { GetUtxosResult, Outpoint, Satoshi } from 'azle/canisters/management'; From 8bbc23e12029720e8ad80b8899037dba0a099883 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 7 May 2024 13:55:06 -0600 Subject: [PATCH 16/49] pr fixes WIP --- .../src/backend/bitcoin_wallet.ts | 19 ++++++++----------- examples/bitcoinjs-lib/src/index.ts | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts index 01585fba89..7a716cf36c 100644 --- a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts @@ -63,8 +63,8 @@ export async function send( feePercentiles[49]; // Fetch our public key, P2PKH address, and UTXOs. - const ownAddress = await getP2pkhAddress(network, keyName, derivationPath); const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); + const ownAddress = publicKeyToP2pkhAddress(network, ownPublicKey); console.log('Fetching UTXOs...'); // Note that pagination may have to be used to get all UTXOs for the given address. @@ -159,7 +159,7 @@ async function buildTransaction( function buildTransactionWithFee( ownUtxos: Utxo[], - ownAddress: string, // TDO what about that ownAddress?? Would we only need it for the signing? + ownAddress: string, destAddress: string, amount: bigint, fee: bigint, @@ -176,9 +176,6 @@ function buildTransactionWithFee( let utxosToSpend: Utxo[] = []; let totalSpent = 0n; for (const utxo of [...ownUtxos].reverse()) { - if (utxosToSpend.includes(utxo)) { - continue; - } totalSpent += utxo.value; utxosToSpend.push(utxo); if (totalSpent >= amount + fee) { @@ -194,20 +191,20 @@ function buildTransactionWithFee( } let transaction = new Psbt({ network }); - transaction.setVersion(2); + transaction.setVersion(2); // TODO check to see if we can use version 1 - for (let utxo of utxosToSpend) { - const hash = Buffer.from(utxo.outpoint.txid).reverse(); - const txid = hash.toString('hex'); + for (const utxo of utxosToSpend) { + const previousTxidHash = Buffer.from(utxo.outpoint.txid); + const txid = previousTxidHash.reverse().toString('hex'); const nonWitnessUtxo = Buffer.from(transactionHashes[txid], 'hex'); transaction.addInput({ - hash: Buffer.from(utxo.outpoint.txid), // TOOD if this works clean it up + hash: previousTxidHash, index: utxo.outpoint.vout, nonWitnessUtxo }); } - const remainingAmount = totalSpent - (amount + fee); + const remainingAmount = totalSpent - amount - fee; transaction.addOutput({ address: destAddress, value: Number(amount) }); diff --git a/examples/bitcoinjs-lib/src/index.ts b/examples/bitcoinjs-lib/src/index.ts index 82fc7a905d..8aa5be2a0b 100644 --- a/examples/bitcoinjs-lib/src/index.ts +++ b/examples/bitcoinjs-lib/src/index.ts @@ -187,7 +187,7 @@ app.post('/create-psbt', (req, res) => { const keyPair = ECPair.fromWIF( 'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr' ); - const psbt = new bitcoin.Psbt(); + let psbt = new bitcoin.Psbt(); psbt.addInput({ // if hash is string, txid, if hash is Buffer, is reversed compared to txid hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e', From dfa12fa766bbd7aa34dc5c70d14f8eb0a2d92b39 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 7 May 2024 16:36:07 -0600 Subject: [PATCH 17/49] move files out of backend --- examples/basic_bitcoin/.gitignore | 2 -- examples/basic_bitcoin/dfx.json | 23 ++++--------------- examples/basic_bitcoin/package.json | 2 +- .../src/{backend => }/bitcoin_api.ts | 0 .../src/{backend => }/bitcoin_wallet.ts | 2 +- .../src/{backend => }/ecdsa_api.ts | 0 .../basic_bitcoin/src/{backend => }/index.did | 0 .../basic_bitcoin/src/{backend => }/index.ts | 0 .../basic_bitcoin/src/{backend => }/types.ts | 0 examples/basic_bitcoin/test/pretest.ts | 2 +- examples/basic_bitcoin/test/test.ts | 2 +- 11 files changed, 8 insertions(+), 25 deletions(-) rename examples/basic_bitcoin/src/{backend => }/bitcoin_api.ts (100%) rename examples/basic_bitcoin/src/{backend => }/bitcoin_wallet.ts (99%) rename examples/basic_bitcoin/src/{backend => }/ecdsa_api.ts (100%) rename examples/basic_bitcoin/src/{backend => }/index.did (100%) rename examples/basic_bitcoin/src/{backend => }/index.ts (100%) rename examples/basic_bitcoin/src/{backend => }/types.ts (100%) diff --git a/examples/basic_bitcoin/.gitignore b/examples/basic_bitcoin/.gitignore index 53dcaef0ee..d85448bfd1 100644 --- a/examples/basic_bitcoin/.gitignore +++ b/examples/basic_bitcoin/.gitignore @@ -1,7 +1,5 @@ .azle .bitcoin .dfx -*.wasm -*.wasm.gz dfx_generated node_modules diff --git a/examples/basic_bitcoin/dfx.json b/examples/basic_bitcoin/dfx.json index b74cb5cdfd..3fcd94fb20 100644 --- a/examples/basic_bitcoin/dfx.json +++ b/examples/basic_bitcoin/dfx.json @@ -1,24 +1,9 @@ { "canisters": { - "backend": { - "type": "custom", - "main": "src/backend/index.ts", - "candid": "src/backend/index.did", - "init_arg": "(variant { regtest })", - "build": "npx azle backend", - "env": ["BITCOIN_NETWORK"], - "wasm": ".azle/backend/backend.wasm", - "gzip": true, - "metadata": [ - { - "name": "candid:service", - "path": "src/backend/index.did" - }, - { - "name": "cdk:name", - "content": "azle" - } - ] + "basic_bitcoin": { + "type": "azle", + "main": "src/index.ts", + "env": ["BITCOIN_NETWORK"] } }, "networks": { diff --git a/examples/basic_bitcoin/package.json b/examples/basic_bitcoin/package.json index 27c2262223..ff30c7702e 100644 --- a/examples/basic_bitcoin/package.json +++ b/examples/basic_bitcoin/package.json @@ -3,7 +3,7 @@ "bitcoin": ".bitcoin/bin/bitcoind -conf=$(pwd)/.bitcoin.conf -datadir=$(pwd)/.bitcoin/data --port=18444", "ic": "dfx start --clean --host 127.0.0.1:8000 --enable-bitcoin", "install": "./scripts/install.sh", - "deploy": "dfx deploy backend --specified-id bkyz2-fmaaa-aaaaa-qaaaq-cai", + "deploy": "dfx deploy basic_bitcoin --specified-id bkyz2-fmaaa-aaaaa-qaaaq-cai", "pre_tests": "ts-node --transpile-only --ignore=false test/pretest.ts", "test": "BITCOIN_NETWORK=regtest npm run pre_tests && ts-node --transpile-only --ignore=false test/test.ts" }, diff --git a/examples/basic_bitcoin/src/backend/bitcoin_api.ts b/examples/basic_bitcoin/src/bitcoin_api.ts similarity index 100% rename from examples/basic_bitcoin/src/backend/bitcoin_api.ts rename to examples/basic_bitcoin/src/bitcoin_api.ts diff --git a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts similarity index 99% rename from examples/basic_bitcoin/src/backend/bitcoin_wallet.ts rename to examples/basic_bitcoin/src/bitcoin_wallet.ts index 7a716cf36c..a592854cd4 100644 --- a/examples/basic_bitcoin/src/backend/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/bitcoin_wallet.ts @@ -198,7 +198,7 @@ function buildTransactionWithFee( const txid = previousTxidHash.reverse().toString('hex'); const nonWitnessUtxo = Buffer.from(transactionHashes[txid], 'hex'); transaction.addInput({ - hash: previousTxidHash, + hash: Buffer.from(utxo.outpoint.txid), index: utxo.outpoint.vout, nonWitnessUtxo }); diff --git a/examples/basic_bitcoin/src/backend/ecdsa_api.ts b/examples/basic_bitcoin/src/ecdsa_api.ts similarity index 100% rename from examples/basic_bitcoin/src/backend/ecdsa_api.ts rename to examples/basic_bitcoin/src/ecdsa_api.ts diff --git a/examples/basic_bitcoin/src/backend/index.did b/examples/basic_bitcoin/src/index.did similarity index 100% rename from examples/basic_bitcoin/src/backend/index.did rename to examples/basic_bitcoin/src/index.did diff --git a/examples/basic_bitcoin/src/backend/index.ts b/examples/basic_bitcoin/src/index.ts similarity index 100% rename from examples/basic_bitcoin/src/backend/index.ts rename to examples/basic_bitcoin/src/index.ts diff --git a/examples/basic_bitcoin/src/backend/types.ts b/examples/basic_bitcoin/src/types.ts similarity index 100% rename from examples/basic_bitcoin/src/backend/types.ts rename to examples/basic_bitcoin/src/types.ts diff --git a/examples/basic_bitcoin/test/pretest.ts b/examples/basic_bitcoin/test/pretest.ts index 4fe51b706f..27f9b07e60 100644 --- a/examples/basic_bitcoin/test/pretest.ts +++ b/examples/basic_bitcoin/test/pretest.ts @@ -1,7 +1,7 @@ import { execSync } from 'child_process'; async function pretest() { - execSync(`dfx canister uninstall-code backend || true`, { + execSync(`dfx canister uninstall-code basic_bitcoin || true`, { stdio: 'inherit' }); diff --git a/examples/basic_bitcoin/test/test.ts b/examples/basic_bitcoin/test/test.ts index 80fcb0a282..c22e1ff8fb 100644 --- a/examples/basic_bitcoin/test/test.ts +++ b/examples/basic_bitcoin/test/test.ts @@ -5,7 +5,7 @@ import { existsSync, rmSync } from 'fs-extra'; import { getTests } from './tests'; -const canisterId = getCanisterId('backend'); +const canisterId = getCanisterId('basic_bitcoin'); export async function whileRunningBitcoinDaemon( callback: () => Promise | void From 3bcd20589ac8b00983a9e9ea83da017c94ed3836 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Wed, 8 May 2024 15:42:14 -0600 Subject: [PATCH 18/49] pr fixes --- examples/basic_bitcoin/README.md | 20 +- examples/basic_bitcoin/package.json | 7 +- examples/basic_bitcoin/scripts/install.sh | 10 +- examples/basic_bitcoin/src/bitcoin_api.ts | 32 ++- examples/basic_bitcoin/src/bitcoin_wallet.ts | 252 ++++++++++++------- examples/basic_bitcoin/src/ecdsa_api.ts | 17 +- examples/basic_bitcoin/src/index.did | 4 - examples/basic_bitcoin/src/index.ts | 27 +- examples/basic_bitcoin/src/types.ts | 9 - examples/basic_bitcoin/test/bitcoin.ts | 20 +- examples/basic_bitcoin/test/pretest.ts | 2 +- examples/basic_bitcoin/test/test.ts | 8 +- examples/basic_bitcoin/test/tests.ts | 140 ++++++----- examples/ckbtc/scripts/install/bitcoin.sh | 10 +- 14 files changed, 316 insertions(+), 242 deletions(-) delete mode 100644 examples/basic_bitcoin/src/index.did delete mode 100644 examples/basic_bitcoin/src/types.ts diff --git a/examples/basic_bitcoin/README.md b/examples/basic_bitcoin/README.md index 46dbceedad..98f6cdd7c6 100644 --- a/examples/basic_bitcoin/README.md +++ b/examples/basic_bitcoin/README.md @@ -5,14 +5,14 @@ This is an implementation of the [basic_bitcoin example](https://github.com/dfin ## bitcoind ```bash -mkdir .bitcoin -mkdir .bitcoin/data - -curl https://bitcoincore.org/bin/bitcoin-core-23.0/bitcoin-23.0-x86_64-linux-gnu.tar.gz -o bitcoin.tar.gz - -tar xzf bitcoin.tar.gz --overwrite --strip-components=1 --directory=.bitcoin/ bitcoin-23.0/bin/ - -rm -rf bitcoin.tar.gz +mkdir -p .bitcoin/data + +# Check if bitcoind executable exists; if not, download and extract it +if [ ! -f ".bitcoin/bin/bitcoind" ]; then + curl -o bitcoin.tar.gz https://bitcoincore.org/bin/bitcoin-core-23.0/bitcoin-23.0-x86_64-linux-gnu.tar.gz + tar xzf bitcoin.tar.gz --overwrite --strip-components=1 --directory=.bitcoin/ bitcoin-23.0/bin/ + rm -rf bitcoin.tar.gz +fi ``` # Deployment @@ -28,13 +28,13 @@ rm -rf bitcoin.tar.gz ```bash # Do this in its own terminal -dfx start --clean --host 127.0.0.1:8000 --enable-bitcoin +dfx start --clean --host 127.0.0.1:8000 ``` ## basic_bitcion ```bash -dfx deploy --argument='(variant { Regtest })' +BITCOIN_NETWORK=regtest dfx deploy' ``` # Usage diff --git a/examples/basic_bitcoin/package.json b/examples/basic_bitcoin/package.json index ff30c7702e..bd90bf6faa 100644 --- a/examples/basic_bitcoin/package.json +++ b/examples/basic_bitcoin/package.json @@ -1,11 +1,8 @@ { "scripts": { - "bitcoin": ".bitcoin/bin/bitcoind -conf=$(pwd)/.bitcoin.conf -datadir=$(pwd)/.bitcoin/data --port=18444", - "ic": "dfx start --clean --host 127.0.0.1:8000 --enable-bitcoin", "install": "./scripts/install.sh", - "deploy": "dfx deploy basic_bitcoin --specified-id bkyz2-fmaaa-aaaaa-qaaaq-cai", - "pre_tests": "ts-node --transpile-only --ignore=false test/pretest.ts", - "test": "BITCOIN_NETWORK=regtest npm run pre_tests && ts-node --transpile-only --ignore=false test/test.ts" + "pretest": "BITCOIN_NETWORK=regtest ts-node --transpile-only --ignore=false test/pretest.ts", + "test": "ts-node --transpile-only --ignore=false test/test.ts" }, "dependencies": { "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", diff --git a/examples/basic_bitcoin/scripts/install.sh b/examples/basic_bitcoin/scripts/install.sh index 0bcdbfe762..a4d0f03526 100755 --- a/examples/basic_bitcoin/scripts/install.sh +++ b/examples/basic_bitcoin/scripts/install.sh @@ -1,14 +1,6 @@ #!/bin/bash -# Check if .bitcoin directory exists; if not, create it -if [ ! -d ".bitcoin" ]; then - mkdir .bitcoin -fi - -# Check if .bitcoin/data directory exists; if not, create it -if [ ! -d ".bitcoin/data" ]; then - mkdir .bitcoin/data -fi +mkdir -p .bitcoin/data # Check if bitcoind executable exists; if not, download and extract it if [ ! -f ".bitcoin/bin/bitcoind" ]; then diff --git a/examples/basic_bitcoin/src/bitcoin_api.ts b/examples/basic_bitcoin/src/bitcoin_api.ts index d268a531d0..7ae7a8f50f 100644 --- a/examples/basic_bitcoin/src/bitcoin_api.ts +++ b/examples/basic_bitcoin/src/bitcoin_api.ts @@ -1,4 +1,4 @@ -import { blob, nat64, serialize, Vec } from 'azle'; +import { serialize } from 'azle'; import { BitcoinNetwork, GetUtxosResult, @@ -6,11 +6,11 @@ import { } from 'azle/canisters/management/bitcoin'; // The fees for the various bitcoin endpoints. -const GET_BALANCE_COST_CYCLES: nat64 = 100_000_000n; -const GET_UTXOS_COST_CYCLES: nat64 = 10_000_000_000n; -const GET_CURRENT_FEE_PERCENTILES_CYCLES: nat64 = 100_000_000n; -const SEND_TRANSACTION_BASE_CYCLES: nat64 = 5_000_000_000n; -const SEND_TRANSACTION_PER_BYTE_CYCLES: nat64 = 20_000_000n; +const GET_BALANCE_COST_CYCLES: bigint = 100_000_000n; +const GET_UTXOS_COST_CYCLES: bigint = 10_000_000_000n; +const GET_CURRENT_FEE_PERCENTILES_CYCLES: bigint = 100_000_000n; +const SEND_TRANSACTION_BASE_CYCLES: bigint = 5_000_000_000n; +const SEND_TRANSACTION_PER_BYTE_CYCLES: bigint = 20_000_000n; /// Returns the balance of the given bitcoin address. /// @@ -20,7 +20,7 @@ export async function getBalance( network: BitcoinNetwork, address: string ): Promise { - const response = await fetch(`icp://aaaaa-aa/bitcoin_get_balance`, { + const balanceRes = await fetch(`icp://aaaaa-aa/bitcoin_get_balance`, { body: serialize({ args: [ { @@ -32,20 +32,18 @@ export async function getBalance( cycles: GET_BALANCE_COST_CYCLES }) }); - return await response.json(); + return await balanceRes.json(); } /// Returns the UTXOs of the given bitcoin address. /// -/// NOTE: Pagination is ignored in this example. If an address has many thousands -/// of UTXOs, then subsequent calls to `bitcoin_get_utxos` are required. -/// +/// NOTE: Relies on the `bitcoin_get_utxos` endpoint. /// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_utxos export async function getUtxos( network: BitcoinNetwork, address: string ): Promise { - const response = await fetch(`icp://aaaaa-aa/bitcoin_get_utxos`, { + const utxoRes = await fetch(`icp://aaaaa-aa/bitcoin_get_utxos`, { body: serialize({ args: [ { @@ -57,7 +55,7 @@ export async function getUtxos( cycles: GET_UTXOS_COST_CYCLES }) }); - return await response.json(); + return await utxoRes.json(); } /// Returns the 100 fee percentiles measured in millisatoshi/byte. @@ -67,8 +65,8 @@ export async function getUtxos( /// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_current_fee_percentiles export async function getCurrentFeePercentiles( network: BitcoinNetwork -): Promise> { - const response = await fetch( +): Promise { + const res = await fetch( `icp://aaaaa-aa/bitcoin_get_current_fee_percentiles`, { body: serialize({ @@ -81,7 +79,7 @@ export async function getCurrentFeePercentiles( }) } ); - return await response.json(); + return await res.json(); } /// Sends a (signed) transaction to the bitcoin network. @@ -90,7 +88,7 @@ export async function getCurrentFeePercentiles( /// See https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_send_transaction export async function sendTransaction( network: BitcoinNetwork, - transaction: blob + transaction: Uint8Array ): Promise { const transactionFee = SEND_TRANSACTION_BASE_CYCLES + diff --git a/examples/basic_bitcoin/src/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts index a592854cd4..62cbe6dede 100644 --- a/examples/basic_bitcoin/src/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/bitcoin_wallet.ts @@ -14,23 +14,25 @@ import { Utxo } from 'azle/canisters/management'; import * as bitcoin from 'bitcoinjs-lib'; -import { networks, Psbt, Transaction } from 'bitcoinjs-lib'; -import { ValidateSigFunction } from 'bitcoinjs-lib/src/psbt'; +import { Network, networks, Transaction } from 'bitcoinjs-lib'; import { Buffer } from 'buffer'; import * as bitcoinApi from './bitcoin_api'; import * as ecdsaApi from './ecdsa_api'; -import { BitcoinTxid, DerivationPath } from './types'; -type TransactionHashes = { - [txid: string]: string; -}; +type SignFun = ( + keyName: string, + derivationPath: Uint8Array[], + messageHash: Uint8Array +) => Promise | Uint8Array; + +const SIG_HASH_TYPE = Transaction.SIGHASH_ALL; /// Returns the P2PKH address of this canister at the given derivation path. export async function getP2pkhAddress( network: BitcoinNetwork, keyName: string, - derivationPath: DerivationPath + derivationPath: Uint8Array[] ): Promise { // Fetch the public key of the given derivation path. const publicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); @@ -44,12 +46,11 @@ export async function getP2pkhAddress( /// at the given derivation path. export async function send( network: BitcoinNetwork, - derivationPath: DerivationPath, + derivationPath: Uint8Array[], keyName: string, dstAddress: string, - amount: Satoshi, - transactionHashes: TransactionHashes -): Promise { + amount: Satoshi +): Promise { // Get fee percentiles from previous transactions to estimate our own fee. const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); @@ -60,13 +61,13 @@ export async function send( // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) 2_000n : // Choose the 50th percentile for sending fees. - feePercentiles[49]; + feePercentiles[50]; // Fetch our public key, P2PKH address, and UTXOs. const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); const ownAddress = publicKeyToP2pkhAddress(network, ownPublicKey); - console.log('Fetching UTXOs...'); + console.info('Fetching UTXOs...'); // Note that pagination may have to be used to get all UTXOs for the given address. // For the sake of simplicity, it is assumed here that the `utxo` field in the response // contains all UTXOs. @@ -81,24 +82,29 @@ export async function send( dstAddress, amount, feePerByte, - keyName, - derivationPath, - determineNetwork(network), - transactionHashes + network ); - // Sign the transaction. + console.info(`Transaction to sign: ${transaction.toHex()}`); + const signedTransaction = await signTransaction( ownPublicKey, + ownAddress, transaction, keyName, - derivationPath + derivationPath, + ecdsaApi.signWithECDSA, + network ); + const signedTransactionBytes = signedTransaction.toBuffer(); + console.info( + `Signed transaction: ${signedTransactionBytes.toString('hex')}` + ); - console.log('Sending transaction...'); + console.info('Sending transaction...'); await bitcoinApi.sendTransaction(network, signedTransactionBytes); - console.log('Done.'); + console.info('Done'); return signedTransaction.getId(); } @@ -112,11 +118,8 @@ async function buildTransaction( dstAddress: string, amount: Satoshi, feePerByte: MillisatoshiPerByte, - keyName: string, - derivationPath: DerivationPath, - network: bitcoin.Network, - transactionHashes: TransactionHashes -): Promise { + network: BitcoinNetwork +): Promise { // We have a chicken-and-egg problem where we need to know the length // of the transaction in order to compute its proper fee, but we need // to know the proper fee in order to figure out the inputs needed for @@ -125,6 +128,7 @@ async function buildTransaction( // We solve this problem iteratively. We start with a fee of zero, build // and sign a transaction, see what its size is, and then update the fee, // rebuild the transaction, until the fee is set to the correct amount. + console.info('Building transaction...'); let totalFee = 0n; // eslint-disable-next-line no-constant-condition while (true) { @@ -134,22 +138,25 @@ async function buildTransaction( dstAddress, amount, totalFee, - network, - transactionHashes + network ); // Sign the transaction. In this case, we only care about the size // of the signed transaction. const signedTransaction = await signTransaction( ownPublicKey, + ownAddress, transaction.clone(), - keyName, - derivationPath + '', // mock key name + [], // mock derivation path + mockSigner, + network ); const signedTxBytesLen = BigInt(signedTransaction.byteLength()); if ((signedTxBytesLen * feePerByte) / 1_000n === totalFee) { + console.info(`Transaction built with fee ${totalFee}.`); return transaction; } else { totalFee = (signedTxBytesLen * feePerByte) / 1_000n; @@ -163,9 +170,8 @@ function buildTransactionWithFee( destAddress: string, amount: bigint, fee: bigint, - network: bitcoin.Network, - transactionHashes: TransactionHashes -): Psbt { + network: BitcoinNetwork +): Transaction { // Assume that any amount below this threshold is dust. const dustThreshold = 1_000n; @@ -190,29 +196,28 @@ function buildTransactionWithFee( ); } - let transaction = new Psbt({ network }); - transaction.setVersion(2); // TODO check to see if we can use version 1 + let transaction = new Transaction(); + transaction.version = 1; for (const utxo of utxosToSpend) { - const previousTxidHash = Buffer.from(utxo.outpoint.txid); - const txid = previousTxidHash.reverse().toString('hex'); - const nonWitnessUtxo = Buffer.from(transactionHashes[txid], 'hex'); - transaction.addInput({ - hash: Buffer.from(utxo.outpoint.txid), - index: utxo.outpoint.vout, - nonWitnessUtxo - }); + transaction.addInput( + Buffer.from(utxo.outpoint.txid), + utxo.outpoint.vout + ); } const remainingAmount = totalSpent - amount - fee; - transaction.addOutput({ address: destAddress, value: Number(amount) }); + transaction.addOutput( + createScriptPubkey(destAddress, network), + Number(amount) + ); if (remainingAmount >= dustThreshold) { - transaction.addOutput({ - address: ownAddress, - value: Number(remainingAmount) - }); + transaction.addOutput( + createScriptPubkey(ownAddress, network), + Number(remainingAmount) + ); } return transaction; @@ -227,53 +232,50 @@ function buildTransactionWithFee( // 2. `own_address` is a P2PKH address. async function signTransaction( ownPublicKey: Uint8Array, - transaction: Psbt, + ownAddress: string, + transaction: Transaction, keyName: string, - derivationPath: DerivationPath + derivationPath: Uint8Array[], + signer: SignFun, + network: BitcoinNetwork ): Promise { - const signer: bitcoin.SignerAsync = { - sign: async (hashBuffer) => { - const sec1 = await ecdsaApi.signWithECDSA( - keyName, - derivationPath, - Uint8Array.from(hashBuffer) - ); - - return Buffer.from(sec1); - }, - publicKey: Buffer.from(ownPublicKey) - }; - const validator: ValidateSigFunction = ( - pubkey: Buffer, - msghash: Buffer, - signature: Buffer - ): boolean => { - // TODO I think if we pass along the ECPair we can validate with that. See the bitcoinjs-lib /create-psbt - console.log( - "Please visually inspect these to make sure they look right and that no one is trying to spend bitcoin they don't own" + const addressVersion = bitcoin.address.fromBase58Check(ownAddress).version; + if ( + addressVersion !== bitcoin.networks.bitcoin.pubKeyHash && + addressVersion !== bitcoin.networks.testnet.pubKeyHash && + addressVersion !== bitcoin.networks.regtest.pubKeyHash + ) { + throw new Error('This example supports signing p2pkh addresses only.'); + } + + for (let i = 0; i < transaction.ins.length; i++) { + const sighash = transaction.hashForSignature( + i, + createScriptPubkey(ownAddress, network), + SIG_HASH_TYPE ); - console.log(`pubkey: ${pubkey.toString('hex')}`); - console.log(`msghash: ${msghash.toString('hex')}`); - console.log(`sig: ${signature.toString('hex')}`); - return true; - }; - await transaction.signAllInputsAsync(signer); - transaction.validateSignaturesOfAllInputs(validator); - transaction.finalizeAllInputs(); - return transaction.extractTransaction(); -} -function determineNetwork(network: BitcoinNetwork): networks.Network { - if (network.mainnet !== undefined) { - return networks.bitcoin; - } - if (network.testnet !== undefined) { - return networks.testnet; - } - if (network.regtest !== undefined) { - return networks.regtest; + const signature = Uint8Array.from( + await signer(keyName, derivationPath, sighash) + ); + + // Convert signature to DER. + const derSignature = sec1ToDer(signature); + + const sigWithHashType = Uint8Array.from([ + ...derSignature, + SIG_HASH_TYPE + ]); + const scriptSig = Uint8Array.from([ + sigWithHashType.length, + ...sigWithHashType, + ownPublicKey.length, + ...ownPublicKey + ]); + transaction.setInputScript(i, Buffer.from(scriptSig)); } - throw new Error(`Unknown Network: ${network}`); + + return transaction; } // Converts a public key to a P2PKH address. @@ -290,3 +292,73 @@ function publicKeyToP2pkhAddress( } return address; } + +// A mock for rubber-stamping ECDSA signatures. +function mockSigner( + _keyName: string, + _derivationPath: Uint8Array[], + _messageHash: Uint8Array +): Uint8Array { + return new Uint8Array(64); +} + +// Converts a SEC1 ECDSA signature to the DER format. +function sec1ToDer(sec1Signature: Uint8Array): Uint8Array { + let r: Uint8Array; + + if ((sec1Signature[0] & 0x80) !== 0) { + // r is negative. Prepend a zero byte. + const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(0, 32)]); + r = tmp; + } else { + // r is positive. + r = sec1Signature.slice(0, 32); + } + + let s: Uint8Array; + + if ((sec1Signature[32] & 0x80) !== 0) { + // s is negative. Prepend a zero byte. + const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(32)]); + s = tmp; + } else { + // s is positive. + s = sec1Signature.slice(32); + } + + // Convert signature to DER. + return Uint8Array.from([ + ...[0x30, 4 + r.length + s.length, 0x02, r.length], + ...r, + ...[0x02, s.length], + ...s + ]); +} + +export function determineNetwork(network: BitcoinNetwork): Network { + if (network.mainnet === null) { + return networks.bitcoin; + } + if (network.testnet === null) { + return networks.testnet; + } + if (network.regtest === null) { + return networks.regtest; + } + throw new Error(`Unknown Network: ${network}`); +} + +function createScriptPubkey(address: string, network: BitcoinNetwork): Buffer { + const pubKeyHash = bitcoin.address + .toOutputScript(address, determineNetwork(network)) + .subarray(3, 23); + const result = bitcoin.script.compile([ + bitcoin.opcodes.OP_DUP, + bitcoin.opcodes.OP_HASH160, + pubKeyHash, + bitcoin.opcodes.OP_EQUALVERIFY, + bitcoin.opcodes.OP_CHECKSIG + ]); + + return result; +} diff --git a/examples/basic_bitcoin/src/ecdsa_api.ts b/examples/basic_bitcoin/src/ecdsa_api.ts index 2b361b8ddd..4bdfa080ae 100644 --- a/examples/basic_bitcoin/src/ecdsa_api.ts +++ b/examples/basic_bitcoin/src/ecdsa_api.ts @@ -1,11 +1,12 @@ import { serialize } from 'azle'; -import { DerivationPath } from './types'; +// The fee for the `sign_with_ecdsa` endpoint using the test key. +const SIGN_WITH_ECDSA_COST_CYCLES: bigint = 10_000_000_000n; /// Returns the ECDSA public key of this canister at the given derivation path. export async function ecdsaPublicKey( keyName: string, - derivationPath: DerivationPath + derivationPath: Uint8Array[] ): Promise { // Retrieve the public key of this canister at the given derivation path // from the ECDSA API. @@ -23,14 +24,14 @@ export async function ecdsaPublicKey( ] }) }); - const ecdsaPublicKeyResult = await response.json(); + const res = await response.json(); - return ecdsaPublicKeyResult.public_key; + return res.public_key; } export async function signWithECDSA( keyName: string, - derivationPath: DerivationPath, + derivationPath: Uint8Array[], messageHash: Uint8Array ): Promise { const publicKeyResponse = await fetch(`icp://aaaaa-aa/sign_with_ecdsa`, { @@ -45,10 +46,10 @@ export async function signWithECDSA( } } ], - cycles: 10_000_000_000n + cycles: SIGN_WITH_ECDSA_COST_CYCLES }) }); - const publicKeyResult = await publicKeyResponse.json(); + const res = await publicKeyResponse.json(); - return publicKeyResult.signature; + return res.signature; } diff --git a/examples/basic_bitcoin/src/index.did b/examples/basic_bitcoin/src/index.did deleted file mode 100644 index 7bf084795e..0000000000 --- a/examples/basic_bitcoin/src/index.did +++ /dev/null @@ -1,4 +0,0 @@ -service: () -> { - http_request: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}; certificate_version:opt nat16}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:vec nat8; callback:func (vec nat8) -> (opt record {token:opt vec nat8; body:vec nat8}) query}}; status_code:nat16}) query; - http_request_update: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:vec nat8; callback:func (vec nat8) -> (opt record {token:opt vec nat8; body:vec nat8}) query}}; status_code:nat16}); -} diff --git a/examples/basic_bitcoin/src/index.ts b/examples/basic_bitcoin/src/index.ts index bcd62986a1..0bf53db301 100644 --- a/examples/basic_bitcoin/src/index.ts +++ b/examples/basic_bitcoin/src/index.ts @@ -1,10 +1,9 @@ -import { jsonStringify } from 'azle'; +import { jsonParse, jsonStringify } from 'azle'; import { BitcoinNetwork } from 'azle/canisters/management'; import express, { Request } from 'express'; import * as bitcoinApi from './bitcoin_api'; import * as bitcoinWallet from './bitcoin_wallet'; -import { SendRequest } from './types'; // The bitcoin network to connect to. // @@ -34,7 +33,7 @@ app.get('/', (req, res) => { }); /// Returns the balance of the given bitcoin address. -app.post( +app.get( '/get-balance', async (req: Request, res) => { const balance = await bitcoinApi.getBalance(NETWORK, req.query.address); @@ -44,7 +43,7 @@ app.post( ); /// Returns the UTXOs of the given bitcoin address. -app.post( +app.get( '/get-utxos', async (req: Request, res) => { const utxos = await bitcoinApi.getUtxos(NETWORK, req.query.address); @@ -55,14 +54,14 @@ app.post( /// Returns the 100 fee percentiles measured in millisatoshi/byte. /// Percentiles are computed from the last 10,000 transactions (if available). -app.post('/get-current-fee-percentiles', async (req, res) => { +app.get('/get-current-fee-percentiles', async (req, res) => { const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(NETWORK); res.send(jsonStringify(feePercentiles)); }); /// Returns the P2PKH address of this canister at a specific derivation path. -app.post('/get-p2pkh-address', async (req, res) => { +app.get('/get-p2pkh-address', async (req, res) => { const address = await bitcoinWallet.getP2pkhAddress( NETWORK, KEY_NAME, @@ -72,15 +71,17 @@ app.post('/get-p2pkh-address', async (req, res) => { res.send(address); }); -app.post('/send', async (req: Request, res) => { - const transactions = req.body; +/// Sends the given amount of bitcoin from this canister to the given address. +/// Returns the transaction ID. +app.post('/send', async (req, res) => { + const { destinationAddress, amountInSatoshi } = req.body; + const txId = await bitcoinWallet.send( NETWORK, DERIVATION_PATH, KEY_NAME, - req.query.destinationAddress, - BigInt(req.query.amountInSatoshi), - transactions + destinationAddress, + BigInt(jsonParse(JSON.stringify(amountInSatoshi))) ); res.send(txId); @@ -99,7 +100,9 @@ function determineKeyName(network: BitcoinNetwork): string { throw new Error('Invalid Bitcoin Network'); } -function determineNetwork(networkName?: string): BitcoinNetwork | undefined { +export function determineNetwork( + networkName?: string +): BitcoinNetwork | undefined { if (networkName === undefined) { return undefined; } diff --git a/examples/basic_bitcoin/src/types.ts b/examples/basic_bitcoin/src/types.ts deleted file mode 100644 index d4ec231467..0000000000 --- a/examples/basic_bitcoin/src/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { nat64 } from 'azle'; - -export type SendRequest = { - destinationAddress: string; - amountInSatoshi: nat64; -}; - -export type BitcoinTxid = string; -export type DerivationPath = Uint8Array[]; diff --git a/examples/basic_bitcoin/test/bitcoin.ts b/examples/basic_bitcoin/test/bitcoin.ts index 6829a7b452..5afb6f8bc2 100644 --- a/examples/basic_bitcoin/test/bitcoin.ts +++ b/examples/basic_bitcoin/test/bitcoin.ts @@ -66,11 +66,21 @@ export function getTotalOutput(tx: Transaction): number { } export function generateToAddress(address: string, blocks: number) { - for (let i = 0; i < blocks; i++) { - execSync( - `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf generatetoaddress 1 ${address}` - ); - } + execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf generatetoaddress ${blocks} ${address}` + ); +} + +export function generate(blocks: number) { + execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf -generate ${blocks}` + ); +} + +export function createWallet(name: string) { + execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf createwallet ${name}` + ); } export function getUtxoHashes(): TransactionHashes { diff --git a/examples/basic_bitcoin/test/pretest.ts b/examples/basic_bitcoin/test/pretest.ts index 27f9b07e60..6088fdcab2 100644 --- a/examples/basic_bitcoin/test/pretest.ts +++ b/examples/basic_bitcoin/test/pretest.ts @@ -5,7 +5,7 @@ async function pretest() { stdio: 'inherit' }); - execSync(`npm run deploy`, { + execSync(`dfx deploy`, { stdio: 'inherit' }); } diff --git a/examples/basic_bitcoin/test/test.ts b/examples/basic_bitcoin/test/test.ts index c22e1ff8fb..a128fb5cb4 100644 --- a/examples/basic_bitcoin/test/test.ts +++ b/examples/basic_bitcoin/test/test.ts @@ -16,12 +16,12 @@ export async function whileRunningBitcoinDaemon( } async function startBitcoinDaemon(): Promise { - if (existsSync(`.bitcoin/regtest`)) { - rmSync('.bitcoin/regtest', { recursive: true, force: true }); + if (existsSync(`.bitcoin/data/regtest`)) { + rmSync('.bitcoin/data/regtest', { recursive: true, force: true }); } const bitcoinDaemon = spawn('.bitcoin/bin/bitcoind', [ `-conf=${process.cwd()}/.bitcoin.conf`, - `-datadir=${process.cwd()}/.bitcoin`, + `-datadir=${process.cwd()}/.bitcoin/data`, '--port=18444' ]); @@ -37,7 +37,7 @@ async function startBitcoinDaemon(): Promise { } }); - console.log(`starting bitcoind...`); + console.info(`starting bitcoind...`); await new Promise((resolve) => setTimeout(resolve, 5000)); return bitcoinDaemon; } diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 3b9846fc51..0268e9f7e0 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -4,12 +4,14 @@ dns.setDefaultResultOrder('ipv4first'); // import * as ecc from 'tiny-secp256k1/lib/'; // TODO we should switch to this import as soon as we have wasm support import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; import { jsonParse, jsonStringify } from 'azle'; -import { GetUtxosResult, Outpoint, Satoshi } from 'azle/canisters/management'; -import { Test } from 'azle/test'; +import { GetUtxosResult, Utxo } from 'azle/canisters/management'; +import { AzleResult, Test } from 'azle/test'; import { networks, payments } from 'bitcoinjs-lib'; import { ECPairFactory } from 'ecpair'; import { + createWallet, + generate, generateToAddress, getTotalOutput, getTransaction, @@ -18,7 +20,8 @@ import { const SINGLE_BLOCK_REWARD = 5_000_000_000n; const FIRST_MINING_SESSION = 101; -const SECOND_MINING_SESSION = 10; +const SECOND_MINING_SESSION = 40; +const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; const ECPair = ECPairFactory(ecc); @@ -30,12 +33,18 @@ export function getTests(canisterId: string): Test[] { const origin = `http://${canisterId}.localhost:8000`; const canisterAddressForm = 'mhVmPSYFraAYnA4ZP6KUx41P3dKgAg27Cm'; // p2pkh-address on the regtest will generally be of this form, starting with m or n and this many characters. return [ + { + name: 'Set up minting wallet', + prep: async () => { + createWallet('minty'); + } + }, { name: '/get-current-fee-percentiles', test: async () => { const response = await fetch( `${origin}/get-current-fee-percentiles`, - { method: 'POST' } + { headers: [['X-Ic-Force-Update', 'true']] } ); const feePercentiles = jsonParse(await response.text()); @@ -57,11 +66,11 @@ export function getTests(canisterId: string): Test[] { const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); - return { Ok: balance === 0n }; + return compareBalances(0n, balance); } }, { - name: 'mint BTC', + name: 'first mint BTC', prep: async () => { const address = await getP2pkhAddress(origin); generateToAddress(address, FIRST_MINING_SESSION); @@ -74,11 +83,10 @@ export function getTests(canisterId: string): Test[] { const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); - return { - Ok: - balance === - SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION) - }; + return compareBalances( + SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION), + balance + ); } }, { @@ -88,7 +96,7 @@ export function getTests(canisterId: string): Test[] { const response = await fetch( `${origin}/get-utxos?address=${address}`, - { method: 'POST' } + { headers: [['X-Ic-Force-Update', 'true']] } ); const utxosResult: GetUtxosResult = await jsonParse( await response.text() @@ -107,7 +115,7 @@ export function getTests(canisterId: string): Test[] { test: async () => { const response = await fetch( `${origin}/get-current-fee-percentiles`, - { method: 'POST' } + { headers: [['X-Ic-Force-Update', 'true']] } ); const feePercentiles = jsonParse(await response.text()); @@ -129,56 +137,44 @@ export function getTests(canisterId: string): Test[] { const address = getToAddress(); const balance = await getBalance(origin, address); - return { Ok: balance === 0n }; + return compareBalances(0n, balance); } }, { name: '/send from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', prep: async () => { const toAddress = getToAddress(); - const body = jsonStringify(getUtxoHashes()); - const response = await fetch( - `${origin}/send?amountInSatoshi=${ - SINGLE_BLOCK_REWARD / 2n - }&destinationAddress=${toAddress}`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body - } - ); + const body = jsonStringify({ + transactions: getUtxoHashes(), + amountInSatoshi: FIRST_AMOUNT_SENT, + destinationAddress: toAddress + }); + const response = await fetch(`${origin}/send`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + }); lastTx = await response.text(); - console.log(lastTx); + console.info(lastTx); } }, + // Wait to insure the transaction is in the mempool before generating a new block. + // TODO it might be nice to look at the mempool and wait for a transaction to show up before generating any blocks + { name: 'wait for blocks to settle', wait: 15_000 }, { - name: 'mint BTC', + name: 'second mint BTC', prep: async () => { - const address = getToAddress(); - generateToAddress(address, SECOND_MINING_SESSION); + generate(SECOND_MINING_SESSION); } }, - { name: 'wait for blocks to settle', wait: 60_000 }, + { name: 'wait for blocks to settle', wait: 15_000 }, { name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv final', test: async () => { const address = getToAddress(); const balance = await getBalance(origin, address); - const previousTransaction = getTransaction(lastTx); - - const outputValue = BigInt(getTotalOutput(previousTransaction)); - - const inputValue = SINGLE_BLOCK_REWARD; - - const fee = inputValue - outputValue; - const blockRewards = - SINGLE_BLOCK_REWARD * BigInt(SECOND_MINING_SESSION) + fee; - const expectedBalance = SINGLE_BLOCK_REWARD / 2n + blockRewards; - - return { - Ok: balance === expectedBalance - }; + return compareBalances(FIRST_AMOUNT_SENT, balance); } }, { @@ -187,16 +183,19 @@ export function getTests(canisterId: string): Test[] { const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); - const previousTransaction = getTransaction(lastTx); - const outputValue = BigInt(getTotalOutput(previousTransaction)); - const amountSent = SINGLE_BLOCK_REWARD / 2n; - const inputValue = SINGLE_BLOCK_REWARD; - const fee = inputValue - outputValue; + // The expected balance of the canister's wallet is: + // all of it's total block mining rewards - the amount sent in the first transaction - the fee + + // At the time this transaction was made, the canister only had utxos from block rewards. + // The amount sent and the fee was less than the reward from minting a single block so there will be only one input and it will have the value of that reward. + const fee = getFeeFromTransaction(lastTx, SINGLE_BLOCK_REWARD); + const blockRewards = SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION); - const expectedBalance = blockRewards - (amountSent + fee); - return { Ok: balance === expectedBalance }; + const expectedBalance = blockRewards - FIRST_AMOUNT_SENT - fee; + + return compareBalances(expectedBalance, balance); } }, { @@ -204,7 +203,7 @@ export function getTests(canisterId: string): Test[] { test: async () => { const response = await fetch( `${origin}/get-current-fee-percentiles`, - { method: 'POST' } + { headers: [['X-Ic-Force-Update', 'true']] } ); const feePercentiles = jsonParse(await response.text()); @@ -215,9 +214,34 @@ export function getTests(canisterId: string): Test[] { ]; } +function compareBalances( + expected: bigint, + actual: bigint +): AzleResult { + if (expected === actual) { + return { Ok: true }; + } else { + return { Err: `Expected: ${expected}, Received: ${actual}` }; + } +} + +/** + * The fee is determined by finding the difference between the total value of the inputs minus the total value of the outputs + * The value of the outputs is easily found by looking the transaction with the provided txid + * The value of the inputs is not easily calculated automatically and must be provided + * @param txid + * @param totalInputValue + * @returns + */ +function getFeeFromTransaction(txid: string, totalInputValue: bigint): bigint { + const previousTransaction = getTransaction(txid); + const outputValue = BigInt(getTotalOutput(previousTransaction)); + return totalInputValue - outputValue; +} + async function getP2pkhAddress(origin: string): Promise { const response = await fetch(`${origin}/get-p2pkh-address`, { - method: 'POST' + headers: [['X-Ic-Force-Update', 'true']] }); return await response.text(); } @@ -226,7 +250,7 @@ function getToAddress(): string { const keyPair = ECPair.fromWIF( 'L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv' ); - const { address: address } = payments.p2pkh({ + const { address } = payments.p2pkh({ pubkey: keyPair.publicKey, network: networks.regtest }); @@ -238,15 +262,13 @@ function getToAddress(): string { async function getBalance(origin: string, address: string): Promise { const response = await fetch(`${origin}/get-balance?address=${address}`, { - method: 'POST' + headers: [['X-Ic-Force-Update', 'true']] }); return jsonParse(await response.text()); } -type Utxo = { height: number; outpoint: Outpoint; value: Satoshi }; - -function checkUtxos(utxso: Utxo[]): boolean { - return utxso.every( +function checkUtxos(utxos: Utxo[]): boolean { + return utxos.every( (utxo) => utxo.value === SINGLE_BLOCK_REWARD && utxo.outpoint.vout === 0 ); } diff --git a/examples/ckbtc/scripts/install/bitcoin.sh b/examples/ckbtc/scripts/install/bitcoin.sh index 0bcdbfe762..a4d0f03526 100755 --- a/examples/ckbtc/scripts/install/bitcoin.sh +++ b/examples/ckbtc/scripts/install/bitcoin.sh @@ -1,14 +1,6 @@ #!/bin/bash -# Check if .bitcoin directory exists; if not, create it -if [ ! -d ".bitcoin" ]; then - mkdir .bitcoin -fi - -# Check if .bitcoin/data directory exists; if not, create it -if [ ! -d ".bitcoin/data" ]; then - mkdir .bitcoin/data -fi +mkdir -p .bitcoin/data # Check if bitcoind executable exists; if not, download and extract it if [ ! -f ".bitcoin/bin/bitcoind" ]; then From bd0fdfb3ace83ba6f63730ba8e102679d1b4de31 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 14 May 2024 15:47:44 -0600 Subject: [PATCH 19/49] update rust version --- src/compiler/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/Dockerfile b/src/compiler/Dockerfile index feef4a4b75..83dcf84ead 100644 --- a/src/compiler/Dockerfile +++ b/src/compiler/Dockerfile @@ -12,7 +12,7 @@ RUN apt-get install -y curl RUN apt-get install -y git ENV PATH="/root/.cargo/bin:${PATH}" -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain=1.73.0 --profile=minimal +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain=1.75.0 --profile=minimal RUN rustup target add wasm32-wasi RUN cargo install --git https://github.com/wasm-forge/wasi2ic --rev 806c3558aad24224852a9582f018178402cb3679 From 2eef95c630455e93f5c167211bd85385accb03e7 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 14 May 2024 22:05:13 -0600 Subject: [PATCH 20/49] final fixes for current review --- examples/basic_bitcoin/test/bitcoin.ts | 89 ----------------- examples/basic_bitcoin/test/tests.ts | 133 ++++++++++++++++++++----- examples/basic_bitcoin/tsconfig.json | 4 +- 3 files changed, 111 insertions(+), 115 deletions(-) diff --git a/examples/basic_bitcoin/test/bitcoin.ts b/examples/basic_bitcoin/test/bitcoin.ts index 5afb6f8bc2..d785779cff 100644 --- a/examples/basic_bitcoin/test/bitcoin.ts +++ b/examples/basic_bitcoin/test/bitcoin.ts @@ -1,64 +1,6 @@ -import { jsonParse } from 'azle'; import { Transaction } from 'bitcoinjs-lib'; import { execSync } from 'child_process'; -type Block = { - hash: string; - confirmations: number; - height: number; - version: number; - versionHex: string; - merkleroot: string; - time: number; - mediantime: number; - nonce: number; - bits: string; - difficulty: number; - chainwork: string; - nTx: number; - previousblockhash: string; - nextblockhash: string; - strippedsize: number; - size: number; - weight: number; - tx: Tx[]; -}; - -type Tx = { - txid: string; - hash: string; - version: number; - size: number; - vsize: number; - weight: number; - locktime: number; - vin: [ - { - coinbase: string; - txinwitness: string[]; - sequence: number; - } - ]; - vout: Vout[]; - hex: string; -}; - -type Vout = { - value: number; - n: number; - scriptPubKey: { - asm: string; - desc: string; - hex: string; - address: string; - type: string; - }; -}; - -type TransactionHashes = { - [txid: string]: string; -}; - export function getTotalOutput(tx: Transaction): number { return tx.outs.reduce((total, output) => { return total + output.value; @@ -83,25 +25,6 @@ export function createWallet(name: string) { ); } -export function getUtxoHashes(): TransactionHashes { - return Array.from({ length: 102 }).reduce((acc, _, blockHeight) => { - const blockHash = getblockhash(blockHeight); - const block = getBlock(blockHash); - const result = getTransactionHashAndIdFromBlock(block); - return { ...acc, ...result }; - }, {} as TransactionHashes); -} - -function getBlock(hash: string): Block { - const getBlockResult = execSync( - `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblock ${hash} 2` - ) - .toString() - .trim(); - - return jsonParse(getBlockResult); -} - export function getTransaction(txid: string): Transaction { return Transaction.fromHex(getTxHex(txid)); } @@ -113,15 +36,3 @@ function getTxHex(txid: string): string { .toString() .trim(); } - -function getblockhash(blockIndex: number): string { - return execSync( - `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockhash ${blockIndex}` - ) - .toString() - .trim(); -} - -function getTransactionHashAndIdFromBlock(block: Block): TransactionHashes { - return { [block.tx[0].txid]: block.tx[0].hex }; -} diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 0268e9f7e0..df0ec2abec 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -14,20 +14,19 @@ import { generate, generateToAddress, getTotalOutput, - getTransaction, - getUtxoHashes + getTransaction } from './bitcoin'; const SINGLE_BLOCK_REWARD = 5_000_000_000n; const FIRST_MINING_SESSION = 101; -const SECOND_MINING_SESSION = 40; const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; +const SECOND_AMOUNT_SENT = SINGLE_BLOCK_REWARD * 2n; const ECPair = ECPairFactory(ecc); let lastTx = ''; - -// TODO adding HD wallets and showing how to use the Derivation path might be nice +let toAddressPreviousBalance = 0n; +let canisterPreviousBalance = 0n; export function getTests(canisterId: string): Test[] { const origin = `http://${canisterId}.localhost:8000`; @@ -39,19 +38,6 @@ export function getTests(canisterId: string): Test[] { createWallet('minty'); } }, - { - name: '/get-current-fee-percentiles', - test: async () => { - const response = await fetch( - `${origin}/get-current-fee-percentiles`, - { headers: [['X-Ic-Force-Update', 'true']] } - ); - - const feePercentiles = jsonParse(await response.text()); - - return { Ok: feePercentiles.length === 0 }; - } - }, { name: '/get-p2pkh-address', test: async () => { @@ -145,7 +131,6 @@ export function getTests(canisterId: string): Test[] { prep: async () => { const toAddress = getToAddress(); const body = jsonStringify({ - transactions: getUtxoHashes(), amountInSatoshi: FIRST_AMOUNT_SENT, destinationAddress: toAddress }); @@ -158,13 +143,12 @@ export function getTests(canisterId: string): Test[] { console.info(lastTx); } }, - // Wait to insure the transaction is in the mempool before generating a new block. // TODO it might be nice to look at the mempool and wait for a transaction to show up before generating any blocks - { name: 'wait for blocks to settle', wait: 15_000 }, + { name: 'wait for transaction to appear in mempool', wait: 15_000 }, { - name: 'second mint BTC', + name: '', prep: async () => { - generate(SECOND_MINING_SESSION); + generate(1); } }, { name: 'wait for blocks to settle', wait: 15_000 }, @@ -173,6 +157,7 @@ export function getTests(canisterId: string): Test[] { test: async () => { const address = getToAddress(); const balance = await getBalance(origin, address); + toAddressPreviousBalance = balance; return compareBalances(FIRST_AMOUNT_SENT, balance); } @@ -182,6 +167,7 @@ export function getTests(canisterId: string): Test[] { test: async () => { const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); + canisterPreviousBalance = balance; // The expected balance of the canister's wallet is: // all of it's total block mining rewards - the amount sent in the first transaction - the fee @@ -208,7 +194,106 @@ export function getTests(canisterId: string): Test[] { const feePercentiles = jsonParse(await response.text()); - return { Ok: feePercentiles.length === 0 }; // TODO is that what we are expecting it to be? + return { + Ok: feePercentiles.length === 101 + }; + } + }, + { + name: 'mine a block for the latest transaction', + prep: async () => { + // Generate blocks to ensure that enough of the canisters block rewards are available to spend + generate(10); + } + }, + { + name: '/send big from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + prep: async () => { + const toAddress = getToAddress(); + const body = jsonStringify({ + amountInSatoshi: SECOND_AMOUNT_SENT, + destinationAddress: toAddress + }); + const response = await fetch(`${origin}/send`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + }); + lastTx = await response.text(); + console.info(lastTx); + } + }, + { name: 'wait for transaction to appear in mempool', wait: 15_000 }, + { + name: 'mine a block for the latest transaction', + prep: async () => { + generate(1); + } + }, + { name: 'wait for blocks to settle', wait: 15_000 }, + { + name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv big', + test: async () => { + const address = getToAddress(); + const balance = await getBalance(origin, address); + const expectedBalance = + toAddressPreviousBalance + SECOND_AMOUNT_SENT; + + return compareBalances(expectedBalance, balance); + } + }, + { + name: '/get-balance big', + test: async () => { + const address = await getP2pkhAddress(origin); + const balance = await getBalance(origin, address); + + const fee = getFeeFromTransaction( + lastTx, + SINGLE_BLOCK_REWARD * 3n + ); + + const expectedBalance = + canisterPreviousBalance - SECOND_AMOUNT_SENT - fee; + + return compareBalances(expectedBalance, balance); + } + }, + { + name: '/get-current-fee-percentiles', + test: async () => { + const response = await fetch( + `${origin}/get-current-fee-percentiles`, + { headers: [['X-Ic-Force-Update', 'true']] } + ); + + const feePercentiles = jsonParse(await response.text()); + + return { + Ok: feePercentiles.length === 101 + }; + } + }, + { + name: 'mine a block to see if resets the fee percentiles', + prep: async () => { + generate(1); + } + }, + { name: 'wait for blocks to settle', wait: 15_000 }, + { + name: '/get-current-fee-percentiles', + test: async () => { + const response = await fetch( + `${origin}/get-current-fee-percentiles`, + { headers: [['X-Ic-Force-Update', 'true']] } + ); + + const feePercentiles = jsonParse(await response.text()); + + return { + Ok: feePercentiles.length === 0 + }; } } ]; diff --git a/examples/basic_bitcoin/tsconfig.json b/examples/basic_bitcoin/tsconfig.json index 0817cb3fc1..552e5f0cbd 100644 --- a/examples/basic_bitcoin/tsconfig.json +++ b/examples/basic_bitcoin/tsconfig.json @@ -4,7 +4,7 @@ "target": "ES2020", "moduleResolution": "node", "allowJs": true, - "outDir": "HACK_BECAUSE_OF_ALLOW_JS", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "outDir": "HACK_BECAUSE_OF_ALLOW_JS" } } From e7a93ec5b9f2ee6cb98016f61775395c97c1d89f Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Wed, 15 May 2024 09:36:06 -0600 Subject: [PATCH 21/49] make static to address --- examples/basic_bitcoin/package-lock.json | 82 +----------------------- examples/basic_bitcoin/package.json | 5 +- examples/basic_bitcoin/test/tests.ts | 43 ++----------- 3 files changed, 8 insertions(+), 122 deletions(-) diff --git a/examples/basic_bitcoin/package-lock.json b/examples/basic_bitcoin/package-lock.json index a63a63721f..1aa664f77a 100644 --- a/examples/basic_bitcoin/package-lock.json +++ b/examples/basic_bitcoin/package-lock.json @@ -6,12 +6,9 @@ "": { "hasInstallScript": true, "dependencies": { - "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "azle": "^0.21.1", "bitcoinjs-lib": "^6.1.5", - "ecpair": "^2.1.0", - "express": "^4.18.2", - "tiny-secp256k1": "^2.2.3" + "express": "^4.18.2" }, "devDependencies": { "@dfinity/agent": "^0.19.2", @@ -25,17 +22,6 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" }, - "node_modules/@bitcoin-js/tiny-secp256k1-asmjs": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@bitcoin-js/tiny-secp256k1-asmjs/-/tiny-secp256k1-asmjs-2.2.3.tgz", - "integrity": "sha512-arFPdEZi9RIiaG76OZswTnAU0KfuiLwGw2VNfD66LKhzlbfOnX1o1WI/GI3qm9UbjG/0QOzZu/KmTNvL79x/DQ==", - "dependencies": { - "uint8array-tools": "0.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -1647,19 +1633,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "node_modules/ecpair": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", - "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", - "dependencies": { - "randombytes": "^2.1.0", - "typeforce": "^1.18.0", - "wif": "^2.0.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2795,17 +2768,6 @@ "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", "deprecated": "no longer maintained" }, - "node_modules/tiny-secp256k1": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz", - "integrity": "sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==", - "dependencies": { - "uint8array-tools": "0.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2919,14 +2881,6 @@ "node": ">=14.17" } }, - "node_modules/uint8array-tools": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", - "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -2990,40 +2944,6 @@ "node": ">= 0.8" } }, - "node_modules/wif": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", - "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", - "dependencies": { - "bs58check": "<3.0.0" - } - }, - "node_modules/wif/node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/wif/node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/wif/node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, "node_modules/ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", diff --git a/examples/basic_bitcoin/package.json b/examples/basic_bitcoin/package.json index bd90bf6faa..4b503226b5 100644 --- a/examples/basic_bitcoin/package.json +++ b/examples/basic_bitcoin/package.json @@ -5,12 +5,9 @@ "test": "ts-node --transpile-only --ignore=false test/test.ts" }, "dependencies": { - "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "azle": "^0.21.1", "bitcoinjs-lib": "^6.1.5", - "ecpair": "^2.1.0", - "express": "^4.18.2", - "tiny-secp256k1": "^2.2.3" + "express": "^4.18.2" }, "devDependencies": { "@dfinity/agent": "^0.19.2", diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index df0ec2abec..a64cd359b9 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -1,13 +1,9 @@ import * as dns from 'node:dns'; dns.setDefaultResultOrder('ipv4first'); -// import * as ecc from 'tiny-secp256k1/lib/'; // TODO we should switch to this import as soon as we have wasm support -import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; import { jsonParse, jsonStringify } from 'azle'; import { GetUtxosResult, Utxo } from 'azle/canisters/management'; import { AzleResult, Test } from 'azle/test'; -import { networks, payments } from 'bitcoinjs-lib'; -import { ECPairFactory } from 'ecpair'; import { createWallet, @@ -21,8 +17,7 @@ const SINGLE_BLOCK_REWARD = 5_000_000_000n; const FIRST_MINING_SESSION = 101; const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; const SECOND_AMOUNT_SENT = SINGLE_BLOCK_REWARD * 2n; - -const ECPair = ECPairFactory(ecc); +const TO_ADDRESS = 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD'; // Regtest address from this WIF L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv let lastTx = ''; let toAddressPreviousBalance = 0n; @@ -110,18 +105,10 @@ export function getTests(canisterId: string): Test[] { return { Ok: feePercentiles.length === 0 }; } }, - { - name: 'toAddress sanity check', - test: async () => { - const address = getToAddress(); - return { Ok: address === 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD' }; - } - }, { name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', test: async () => { - const address = getToAddress(); - const balance = await getBalance(origin, address); + const balance = await getBalance(origin, TO_ADDRESS); return compareBalances(0n, balance); } @@ -129,10 +116,9 @@ export function getTests(canisterId: string): Test[] { { name: '/send from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', prep: async () => { - const toAddress = getToAddress(); const body = jsonStringify({ amountInSatoshi: FIRST_AMOUNT_SENT, - destinationAddress: toAddress + destinationAddress: TO_ADDRESS }); const response = await fetch(`${origin}/send`, { method: 'POST', @@ -155,8 +141,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv final', test: async () => { - const address = getToAddress(); - const balance = await getBalance(origin, address); + const balance = await getBalance(origin, TO_ADDRESS); toAddressPreviousBalance = balance; return compareBalances(FIRST_AMOUNT_SENT, balance); @@ -209,10 +194,9 @@ export function getTests(canisterId: string): Test[] { { name: '/send big from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', prep: async () => { - const toAddress = getToAddress(); const body = jsonStringify({ amountInSatoshi: SECOND_AMOUNT_SENT, - destinationAddress: toAddress + destinationAddress: TO_ADDRESS }); const response = await fetch(`${origin}/send`, { method: 'POST', @@ -234,8 +218,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv big', test: async () => { - const address = getToAddress(); - const balance = await getBalance(origin, address); + const balance = await getBalance(origin, TO_ADDRESS); const expectedBalance = toAddressPreviousBalance + SECOND_AMOUNT_SENT; @@ -331,20 +314,6 @@ async function getP2pkhAddress(origin: string): Promise { return await response.text(); } -function getToAddress(): string { - const keyPair = ECPair.fromWIF( - 'L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv' - ); - const { address } = payments.p2pkh({ - pubkey: keyPair.publicKey, - network: networks.regtest - }); - if (address === undefined) { - throw new Error('To Address is undefined'); - } - return address; -} - async function getBalance(origin: string, address: string): Promise { const response = await fetch(`${origin}/get-balance?address=${address}`, { headers: [['X-Ic-Force-Update', 'true']] From 723b5fe785b67d7c029da2b3aa373cb2ba13dfa4 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Wed, 15 May 2024 14:15:15 -0600 Subject: [PATCH 22/49] prefer library functionality --- examples/basic_bitcoin/src/bitcoin_wallet.ts | 61 +++++--------------- examples/basic_bitcoin/test/bitcoin.ts | 14 +++++ examples/basic_bitcoin/test/tests.ts | 25 ++++++-- 3 files changed, 49 insertions(+), 51 deletions(-) diff --git a/examples/basic_bitcoin/src/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts index 62cbe6dede..42a09b6280 100644 --- a/examples/basic_bitcoin/src/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/bitcoin_wallet.ts @@ -255,23 +255,18 @@ async function signTransaction( SIG_HASH_TYPE ); - const signature = Uint8Array.from( - await signer(keyName, derivationPath, sighash) - ); - - // Convert signature to DER. - const derSignature = sec1ToDer(signature); + const signature = await signer(keyName, derivationPath, sighash); - const sigWithHashType = Uint8Array.from([ - ...derSignature, + const encodedSig = bitcoin.script.signature.encode( + Buffer.from(signature), SIG_HASH_TYPE + ); + + const scriptSig = bitcoin.script.compile([ + encodedSig, + Buffer.from(ownPublicKey) ]); - const scriptSig = Uint8Array.from([ - sigWithHashType.length, - ...sigWithHashType, - ownPublicKey.length, - ...ownPublicKey - ]); + transaction.setInputScript(i, Buffer.from(scriptSig)); } @@ -299,40 +294,12 @@ function mockSigner( _derivationPath: Uint8Array[], _messageHash: Uint8Array ): Uint8Array { - return new Uint8Array(64); -} - -// Converts a SEC1 ECDSA signature to the DER format. -function sec1ToDer(sec1Signature: Uint8Array): Uint8Array { - let r: Uint8Array; - - if ((sec1Signature[0] & 0x80) !== 0) { - // r is negative. Prepend a zero byte. - const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(0, 32)]); - r = tmp; - } else { - // r is positive. - r = sec1Signature.slice(0, 32); - } - - let s: Uint8Array; - - if ((sec1Signature[32] & 0x80) !== 0) { - // s is negative. Prepend a zero byte. - const tmp = Uint8Array.from([0x00, ...sec1Signature.slice(32)]); - s = tmp; - } else { - // s is positive. - s = sec1Signature.slice(32); + let array = new Uint8Array(64); + // bitcoin.script.signature.encode threw away most of the signature when it was all 0's so we need to fill it up with something + for (let i = 0; i < 64; i++) { + array[i] = i + 1; } - - // Convert signature to DER. - return Uint8Array.from([ - ...[0x30, 4 + r.length + s.length, 0x02, r.length], - ...r, - ...[0x02, s.length], - ...s - ]); + return array; } export function determineNetwork(network: BitcoinNetwork): Network { diff --git a/examples/basic_bitcoin/test/bitcoin.ts b/examples/basic_bitcoin/test/bitcoin.ts index d785779cff..154ea6e0e0 100644 --- a/examples/basic_bitcoin/test/bitcoin.ts +++ b/examples/basic_bitcoin/test/bitcoin.ts @@ -25,6 +25,20 @@ export function createWallet(name: string) { ); } +export function getMempoolCount(): number { + const mempool = execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawmempool true` + ) + .toString() + .trim(); + + const mempoolObj = JSON.parse(mempool); + + const transactionCount = Object.keys(mempoolObj).length; + + return transactionCount; +} + export function getTransaction(txid: string): Transaction { return Transaction.fromHex(getTxHex(txid)); } diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index a64cd359b9..0b488c6328 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -9,6 +9,7 @@ import { createWallet, generate, generateToAddress, + getMempoolCount, getTotalOutput, getTransaction } from './bitcoin'; @@ -129,10 +130,12 @@ export function getTests(canisterId: string): Test[] { console.info(lastTx); } }, - // TODO it might be nice to look at the mempool and wait for a transaction to show up before generating any blocks - { name: 'wait for transaction to appear in mempool', wait: 15_000 }, { - name: '', + name: 'wait for transaction to appear in mempool', + prep: waitForMempool + }, + { + name: 'mine a block with the latest transaction', prep: async () => { generate(1); } @@ -207,7 +210,10 @@ export function getTests(canisterId: string): Test[] { console.info(lastTx); } }, - { name: 'wait for transaction to appear in mempool', wait: 15_000 }, + { + name: 'wait for transaction to appear in mempool', + prep: waitForMempool + }, { name: 'mine a block for the latest transaction', prep: async () => { @@ -326,3 +332,14 @@ function checkUtxos(utxos: Utxo[]): boolean { (utxo) => utxo.value === SINGLE_BLOCK_REWARD && utxo.outpoint.vout === 0 ); } + +async function waitForMempool() { + for (let i = 0; i < 60; i++) { + if (getMempoolCount() > 0) { + console.info('done waiting'); + return; + } + await new Promise((resolve) => setTimeout(resolve, 1_000)); + } + throw new Error('Timeout: Transaction was not added to the mempool'); +} From 6f8b0f925e7422750e26c29c2e4fb51c64d11808 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 16 May 2024 13:26:08 -0600 Subject: [PATCH 23/49] pr fixes --- examples/basic_bitcoin/package.json | 2 +- examples/basic_bitcoin/src/bitcoin_wallet.ts | 14 ++---- examples/basic_bitcoin/src/index.ts | 12 ++--- examples/basic_bitcoin/test/bitcoin.ts | 6 --- examples/basic_bitcoin/test/pretest.ts | 2 +- examples/basic_bitcoin/test/test.ts | 1 + examples/basic_bitcoin/test/tests.ts | 53 +++++++++++--------- 7 files changed, 41 insertions(+), 49 deletions(-) diff --git a/examples/basic_bitcoin/package.json b/examples/basic_bitcoin/package.json index 4b503226b5..21d48d64a1 100644 --- a/examples/basic_bitcoin/package.json +++ b/examples/basic_bitcoin/package.json @@ -1,7 +1,7 @@ { "scripts": { "install": "./scripts/install.sh", - "pretest": "BITCOIN_NETWORK=regtest ts-node --transpile-only --ignore=false test/pretest.ts", + "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", "test": "ts-node --transpile-only --ignore=false test/test.ts" }, "dependencies": { diff --git a/examples/basic_bitcoin/src/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts index 42a09b6280..8b6aa9a1f0 100644 --- a/examples/basic_bitcoin/src/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/bitcoin_wallet.ts @@ -132,7 +132,7 @@ async function buildTransaction( let totalFee = 0n; // eslint-disable-next-line no-constant-condition while (true) { - let transaction = buildTransactionWithFee( + const transaction = buildTransactionWithFee( ownUtxos, ownAddress, dstAddress, @@ -206,13 +206,13 @@ function buildTransactionWithFee( ); } - const remainingAmount = totalSpent - amount - fee; - transaction.addOutput( createScriptPubkey(destAddress, network), Number(amount) ); + const remainingAmount = totalSpent - amount - fee; + if (remainingAmount >= dustThreshold) { transaction.addOutput( createScriptPubkey(ownAddress, network), @@ -294,12 +294,8 @@ function mockSigner( _derivationPath: Uint8Array[], _messageHash: Uint8Array ): Uint8Array { - let array = new Uint8Array(64); - // bitcoin.script.signature.encode threw away most of the signature when it was all 0's so we need to fill it up with something - for (let i = 0; i < 64; i++) { - array[i] = i + 1; - } - return array; + // bitcoin.script.signature.encode threw away most of the signature when it was all 0's so we need to fill it up with anything besides just 0s + return Uint8Array.from(new Array(64).fill(1)); } export function determineNetwork(network: BitcoinNetwork): Network { diff --git a/examples/basic_bitcoin/src/index.ts b/examples/basic_bitcoin/src/index.ts index 0bf53db301..b93888f068 100644 --- a/examples/basic_bitcoin/src/index.ts +++ b/examples/basic_bitcoin/src/index.ts @@ -26,12 +26,6 @@ const app = express(); app.use(express.json()); -app.get('/', (req, res) => { - res.send( - `Network: ${NETWORK}, KeyName: ${KEY_NAME}, DerivationPath: ${DERIVATION_PATH}` - ); -}); - /// Returns the balance of the given bitcoin address. app.get( '/get-balance', @@ -90,11 +84,11 @@ app.post('/send', async (req, res) => { app.listen(); function determineKeyName(network: BitcoinNetwork): string { - if (network.mainnet !== undefined) { + if (network.mainnet === null) { return 'test_key_1'; - } else if (network.testnet !== undefined) { + } else if (network.testnet === null) { return 'test_key_1'; - } else if (network.regtest !== undefined) { + } else if (network.regtest === null) { return 'dfx_test_key'; } throw new Error('Invalid Bitcoin Network'); diff --git a/examples/basic_bitcoin/test/bitcoin.ts b/examples/basic_bitcoin/test/bitcoin.ts index 154ea6e0e0..bf7d119c11 100644 --- a/examples/basic_bitcoin/test/bitcoin.ts +++ b/examples/basic_bitcoin/test/bitcoin.ts @@ -1,12 +1,6 @@ import { Transaction } from 'bitcoinjs-lib'; import { execSync } from 'child_process'; -export function getTotalOutput(tx: Transaction): number { - return tx.outs.reduce((total, output) => { - return total + output.value; - }, 0); -} - export function generateToAddress(address: string, blocks: number) { execSync( `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf generatetoaddress ${blocks} ${address}` diff --git a/examples/basic_bitcoin/test/pretest.ts b/examples/basic_bitcoin/test/pretest.ts index 6088fdcab2..40ccad9e6e 100644 --- a/examples/basic_bitcoin/test/pretest.ts +++ b/examples/basic_bitcoin/test/pretest.ts @@ -5,7 +5,7 @@ async function pretest() { stdio: 'inherit' }); - execSync(`dfx deploy`, { + execSync(`BITCOIN_NETWORK=regtest dfx deploy`, { stdio: 'inherit' }); } diff --git a/examples/basic_bitcoin/test/test.ts b/examples/basic_bitcoin/test/test.ts index a128fb5cb4..34b64e1d11 100644 --- a/examples/basic_bitcoin/test/test.ts +++ b/examples/basic_bitcoin/test/test.ts @@ -38,6 +38,7 @@ async function startBitcoinDaemon(): Promise { }); console.info(`starting bitcoind...`); + // This await is necessary to ensure the daemon is running await new Promise((resolve) => setTimeout(resolve, 5000)); return bitcoinDaemon; } diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 0b488c6328..f02feed48c 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -4,13 +4,13 @@ dns.setDefaultResultOrder('ipv4first'); import { jsonParse, jsonStringify } from 'azle'; import { GetUtxosResult, Utxo } from 'azle/canisters/management'; import { AzleResult, Test } from 'azle/test'; +import { Transaction } from 'bitcoinjs-lib'; import { createWallet, generate, generateToAddress, getMempoolCount, - getTotalOutput, getTransaction } from './bitcoin'; @@ -18,9 +18,9 @@ const SINGLE_BLOCK_REWARD = 5_000_000_000n; const FIRST_MINING_SESSION = 101; const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; const SECOND_AMOUNT_SENT = SINGLE_BLOCK_REWARD * 2n; -const TO_ADDRESS = 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD'; // Regtest address from this WIF L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv +const TO_ADDRESS = 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD'; -let lastTx = ''; +let lastTxid = ''; let toAddressPreviousBalance = 0n; let canisterPreviousBalance = 0n; @@ -58,7 +58,7 @@ export function getTests(canisterId: string): Test[] { generateToAddress(address, FIRST_MINING_SESSION); } }, - { name: 'wait for blocks to settle', wait: 60_000 }, + { name: 'wait for blocks to settle', wait: 30_000 }, { name: '/get-balance', test: async () => { @@ -102,12 +102,12 @@ export function getTests(canisterId: string): Test[] { const feePercentiles = jsonParse(await response.text()); - // Though blocks are mined no transaction have happened yet so the list should still be empty + // Though blocks are mined no transactions have happened yet so the list should still be empty return { Ok: feePercentiles.length === 0 }; } }, { - name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + name: `/get-balance of ${TO_ADDRESS}`, test: async () => { const balance = await getBalance(origin, TO_ADDRESS); @@ -115,7 +115,7 @@ export function getTests(canisterId: string): Test[] { } }, { - name: '/send from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + name: `/send from canister to ${TO_ADDRESS}`, prep: async () => { const body = jsonStringify({ amountInSatoshi: FIRST_AMOUNT_SENT, @@ -126,8 +126,8 @@ export function getTests(canisterId: string): Test[] { headers: { 'Content-Type': 'application/json' }, body }); - lastTx = await response.text(); - console.info(lastTx); + lastTxid = await response.text(); + console.info(lastTxid); } }, { @@ -142,7 +142,7 @@ export function getTests(canisterId: string): Test[] { }, { name: 'wait for blocks to settle', wait: 15_000 }, { - name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv final', + name: `/get-balance of ${TO_ADDRESS} final`, test: async () => { const balance = await getBalance(origin, TO_ADDRESS); toAddressPreviousBalance = balance; @@ -157,12 +157,12 @@ export function getTests(canisterId: string): Test[] { const balance = await getBalance(origin, address); canisterPreviousBalance = balance; - // The expected balance of the canister's wallet is: - // all of it's total block mining rewards - the amount sent in the first transaction - the fee - // At the time this transaction was made, the canister only had utxos from block rewards. // The amount sent and the fee was less than the reward from minting a single block so there will be only one input and it will have the value of that reward. - const fee = getFeeFromTransaction(lastTx, SINGLE_BLOCK_REWARD); + const fee = getFeeFromTransaction( + lastTxid, + SINGLE_BLOCK_REWARD + ); const blockRewards = SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION); @@ -188,14 +188,13 @@ export function getTests(canisterId: string): Test[] { } }, { - name: 'mine a block for the latest transaction', + name: 'Generate blocks to ensure that enough of the canisters block rewards are available to spend', prep: async () => { - // Generate blocks to ensure that enough of the canisters block rewards are available to spend generate(10); } }, { - name: '/send big from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + name: `/send big from canister to ${TO_ADDRESS}`, prep: async () => { const body = jsonStringify({ amountInSatoshi: SECOND_AMOUNT_SENT, @@ -206,8 +205,8 @@ export function getTests(canisterId: string): Test[] { headers: { 'Content-Type': 'application/json' }, body }); - lastTx = await response.text(); - console.info(lastTx); + lastTxid = await response.text(); + console.info(lastTxid); } }, { @@ -222,7 +221,7 @@ export function getTests(canisterId: string): Test[] { }, { name: 'wait for blocks to settle', wait: 15_000 }, { - name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv big', + name: `/get-balance of ${TO_ADDRESS} big`, test: async () => { const balance = await getBalance(origin, TO_ADDRESS); const expectedBalance = @@ -237,8 +236,10 @@ export function getTests(canisterId: string): Test[] { const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); + // At the time this transaction was made, the next utxos to use will be from block rewards. + // The amount sent and the fee were more than the reward from minting two blocks so there will be three inputs and it will have the value of those rewards. const fee = getFeeFromTransaction( - lastTx, + lastTxid, SINGLE_BLOCK_REWARD * 3n ); @@ -264,7 +265,7 @@ export function getTests(canisterId: string): Test[] { } }, { - name: 'mine a block to see if resets the fee percentiles', + name: 'mine a block to see if it resets the fee percentiles', prep: async () => { generate(1); } @@ -301,7 +302,7 @@ function compareBalances( /** * The fee is determined by finding the difference between the total value of the inputs minus the total value of the outputs - * The value of the outputs is easily found by looking the transaction with the provided txid + * The value of the outputs is easily found by looking at the transaction with the provided txid * The value of the inputs is not easily calculated automatically and must be provided * @param txid * @param totalInputValue @@ -313,6 +314,12 @@ function getFeeFromTransaction(txid: string, totalInputValue: bigint): bigint { return totalInputValue - outputValue; } +function getTotalOutput(tx: Transaction): number { + return tx.outs.reduce((total, output) => { + return total + output.value; + }, 0); +} + async function getP2pkhAddress(origin: string): Promise { const response = await fetch(`${origin}/get-p2pkh-address`, { headers: [['X-Ic-Force-Update', 'true']] From 4938b8e3c18cebb034047d668bc188cb8e1dcfd3 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Fri, 17 May 2024 09:15:42 -0600 Subject: [PATCH 24/49] spelling fixes --- examples/basic_bitcoin/src/bitcoin_wallet.ts | 2 +- examples/basic_bitcoin/test/tests.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/basic_bitcoin/src/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts index 8b6aa9a1f0..2629faa63e 100644 --- a/examples/basic_bitcoin/src/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/bitcoin_wallet.ts @@ -294,7 +294,7 @@ function mockSigner( _derivationPath: Uint8Array[], _messageHash: Uint8Array ): Uint8Array { - // bitcoin.script.signature.encode threw away most of the signature when it was all 0's so we need to fill it up with anything besides just 0s + // bitcoin.script.signature.encode threw away most of the signature when it was all 0s so we need to fill it up with anything besides just 0s return Uint8Array.from(new Array(64).fill(1)); } diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index f02feed48c..d951af542e 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -188,7 +188,7 @@ export function getTests(canisterId: string): Test[] { } }, { - name: 'Generate blocks to ensure that enough of the canisters block rewards are available to spend', + name: "Generate blocks to ensure that enough of the canister's block rewards are available to spend", prep: async () => { generate(10); } From d7eb218d29ff3e270809e411a233fb299919b9c4 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 16 May 2024 09:35:06 -0600 Subject: [PATCH 25/49] add psbt example --- .github/workflows/test.yml | 1 + examples/basic_bitcoin/src/bitcoin_wallet.ts | 2 +- examples/basic_bitcoin/src/index.ts | 2 +- examples/basic_bitcoin/test/tests.ts | 18 +- examples/bitcoin_psbt/.bitcoin.conf | 10 + examples/bitcoin_psbt/.gitignore | 5 + examples/bitcoin_psbt/LICENSE | 208 ++ examples/bitcoin_psbt/README.md | 67 + examples/bitcoin_psbt/dfx.json | 25 + examples/bitcoin_psbt/package-lock.json | 3056 +++++++++++++++++ examples/bitcoin_psbt/package.json | 34 + .../scripts/bitcoin/getallblockhashes.sh | 9 + .../scripts/bitcoin/transactionCount.js | 29 + examples/bitcoin_psbt/scripts/install.sh | 10 + examples/bitcoin_psbt/src/bitcoin_psbt.ts | 278 ++ examples/bitcoin_psbt/src/index.ts | 75 + examples/bitcoin_psbt/test/bitcoin.ts | 90 + examples/bitcoin_psbt/test/manual_tests.ts | 12 + examples/bitcoin_psbt/test/pretest.ts | 13 + examples/bitcoin_psbt/test/test.ts | 45 + examples/bitcoin_psbt/test/tests.ts | 100 + examples/bitcoin_psbt/tsconfig.json | 10 + 22 files changed, 4091 insertions(+), 8 deletions(-) create mode 100644 examples/bitcoin_psbt/.bitcoin.conf create mode 100644 examples/bitcoin_psbt/.gitignore create mode 100644 examples/bitcoin_psbt/LICENSE create mode 100644 examples/bitcoin_psbt/README.md create mode 100644 examples/bitcoin_psbt/dfx.json create mode 100644 examples/bitcoin_psbt/package-lock.json create mode 100644 examples/bitcoin_psbt/package.json create mode 100755 examples/bitcoin_psbt/scripts/bitcoin/getallblockhashes.sh create mode 100644 examples/bitcoin_psbt/scripts/bitcoin/transactionCount.js create mode 100755 examples/bitcoin_psbt/scripts/install.sh create mode 100644 examples/bitcoin_psbt/src/bitcoin_psbt.ts create mode 100644 examples/bitcoin_psbt/src/index.ts create mode 100644 examples/bitcoin_psbt/test/bitcoin.ts create mode 100644 examples/bitcoin_psbt/test/manual_tests.ts create mode 100644 examples/bitcoin_psbt/test/pretest.ts create mode 100644 examples/bitcoin_psbt/test/test.ts create mode 100644 examples/bitcoin_psbt/test/tests.ts create mode 100644 examples/bitcoin_psbt/tsconfig.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 364a2a5ce2..709966cf37 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,7 @@ jobs: "examples/autoreload", "examples/basic_bitcoin", "examples/bitcoin", + "examples/bitcoin_psbt", "examples/bitcoinjs-lib", "examples/bitcore-lib", "examples/blob_array", diff --git a/examples/basic_bitcoin/src/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts index 2629faa63e..0402cc31c1 100644 --- a/examples/basic_bitcoin/src/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/bitcoin_wallet.ts @@ -274,7 +274,7 @@ async function signTransaction( } // Converts a public key to a P2PKH address. -function publicKeyToP2pkhAddress( +export function publicKeyToP2pkhAddress( network: BitcoinNetwork, publicKey: Uint8Array ): string { diff --git a/examples/basic_bitcoin/src/index.ts b/examples/basic_bitcoin/src/index.ts index b93888f068..a833c9b0ab 100644 --- a/examples/basic_bitcoin/src/index.ts +++ b/examples/basic_bitcoin/src/index.ts @@ -83,7 +83,7 @@ app.post('/send', async (req, res) => { app.listen(); -function determineKeyName(network: BitcoinNetwork): string { +export function determineKeyName(network: BitcoinNetwork): string { if (network.mainnet === null) { return 'test_key_1'; } else if (network.testnet === null) { diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index d951af542e..e8c77cc7ec 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -289,7 +289,7 @@ export function getTests(canisterId: string): Test[] { ]; } -function compareBalances( +export function compareBalances( expected: bigint, actual: bigint ): AzleResult { @@ -308,7 +308,10 @@ function compareBalances( * @param totalInputValue * @returns */ -function getFeeFromTransaction(txid: string, totalInputValue: bigint): bigint { +export function getFeeFromTransaction( + txid: string, + totalInputValue: bigint +): bigint { const previousTransaction = getTransaction(txid); const outputValue = BigInt(getTotalOutput(previousTransaction)); return totalInputValue - outputValue; @@ -320,27 +323,30 @@ function getTotalOutput(tx: Transaction): number { }, 0); } -async function getP2pkhAddress(origin: string): Promise { +export async function getP2pkhAddress(origin: string): Promise { const response = await fetch(`${origin}/get-p2pkh-address`, { headers: [['X-Ic-Force-Update', 'true']] }); return await response.text(); } -async function getBalance(origin: string, address: string): Promise { +export async function getBalance( + origin: string, + address: string +): Promise { const response = await fetch(`${origin}/get-balance?address=${address}`, { headers: [['X-Ic-Force-Update', 'true']] }); return jsonParse(await response.text()); } -function checkUtxos(utxos: Utxo[]): boolean { +export function checkUtxos(utxos: Utxo[]): boolean { return utxos.every( (utxo) => utxo.value === SINGLE_BLOCK_REWARD && utxo.outpoint.vout === 0 ); } -async function waitForMempool() { +export async function waitForMempool() { for (let i = 0; i < 60; i++) { if (getMempoolCount() > 0) { console.info('done waiting'); diff --git a/examples/bitcoin_psbt/.bitcoin.conf b/examples/bitcoin_psbt/.bitcoin.conf new file mode 100644 index 0000000000..c84674df72 --- /dev/null +++ b/examples/bitcoin_psbt/.bitcoin.conf @@ -0,0 +1,10 @@ +# Enable regtest mode. This is required to setup a private bitcoin network. +regtest=1 + +# Dummy credentials that are required by `bitcoin-cli`. +rpcuser=ic-btc-integration +rpcpassword=QPQiNaph19FqUsCrBRN0FII7lyM26B51fAMeBQzCb-E= +rpcauth=ic-btc-integration:cdf2741387f3a12438f69092f0fdad8e$62081498c98bee09a0dce2b30671123fa561932992ce377585e8e08bb0c11dfa + +# Enable indexing so we can look up transactions by their ids +txindex=1 diff --git a/examples/bitcoin_psbt/.gitignore b/examples/bitcoin_psbt/.gitignore new file mode 100644 index 0000000000..d85448bfd1 --- /dev/null +++ b/examples/bitcoin_psbt/.gitignore @@ -0,0 +1,5 @@ +.azle +.bitcoin +.dfx +dfx_generated +node_modules diff --git a/examples/bitcoin_psbt/LICENSE b/examples/bitcoin_psbt/LICENSE new file mode 100644 index 0000000000..d5dadbd59b --- /dev/null +++ b/examples/bitcoin_psbt/LICENSE @@ -0,0 +1,208 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and + distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the + copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other + entities that control, are controlled by, or are under common control with + that entity. For the purposes of this definition, "control" means (i) the + power, direct or indirect, to cause the direction or management of such + entity, whether by contract or otherwise, or (ii) ownership of fifty percent + (50%) or more of the outstanding shares, or (iii) beneficial ownership of + such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising + permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation source, and + configuration files. + + "Object" form shall mean any form resulting from mechanical transformation + or translation of a Source form, including but not limited to compiled + object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, + made available under the License, as indicated by a copyright notice that is + included in or attached to the work (an example is provided in the Appendix + below). + + "Derivative Works" shall mean any work, whether in Source or Object form, + that is based on (or derived from) the Work and for which the editorial + revisions, annotations, elaborations, or other modifications represent, as a + whole, an original work of authorship. For the purposes of this License, + Derivative Works shall not include works that remain separable from, or + merely link (or bind by name) to the interfaces of, the Work and Derivative + Works thereof. + + "Contribution" shall mean any work of authorship, including the original + version of the Work and any modifications or additions to that Work or + Derivative Works thereof, that is intentionally submitted to Licensor for + inclusion in the Work by the copyright owner or by an individual or Legal + Entity authorized to submit on behalf of the copyright owner. For the + purposes of this definition, "submitted" means any form of electronic, + verbal, or written communication sent to the Licensor or its + representatives, including but not limited to communication on electronic + mailing lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, the Licensor for the purpose of discussing + and improving the Work, but excluding communication that is conspicuously + marked or otherwise designated in writing by the copyright owner as "Not a + Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on + behalf of whom a Contribution has been received by Licensor and subsequently + incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this + License, each Contributor hereby grants to You a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable copyright license to + reproduce, prepare Derivative Works of, publicly display, publicly perform, + sublicense, and distribute the Work and such Derivative Works in Source or + Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this + License, each Contributor hereby grants to You a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable (except as stated in + this section) patent license to make, have made, use, offer to sell, sell, + import, and otherwise transfer the Work, where such license applies only to + those patent claims licensable by such Contributor that are necessarily + infringed by their Contribution(s) alone or by combination of their + Contribution(s) with the Work to which such Contribution(s) was submitted. + If You institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work or a + Contribution incorporated within the Work constitutes direct or contributory + patent infringement, then any patent licenses granted to You under this + License for that Work shall terminate as of the date such litigation is + filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or + Derivative Works thereof in any medium, with or without modifications, and + in Source or Object form, provided that You meet the following conditions: + + a. You must give any other recipients of the Work or Derivative Works a + copy of this License; and + + b. You must cause any modified files to carry prominent notices stating + that You changed the files; and + + c. You must retain, in the Source form of any Derivative Works that You + distribute, all copyright, patent, trademark, and attribution notices + from the Source form of the Work, excluding those notices that do not + pertain to any part of the Derivative Works; and + + d. If the Work includes a "NOTICE" text file as part of its distribution, + then any Derivative Works that You distribute must include a readable + copy of the attribution notices contained within such NOTICE file, + excluding those notices that do not pertain to any part of the Derivative + Works, in at least one of the following places: within a NOTICE text file + distributed as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, within a + display generated by the Derivative Works, if and wherever such + third-party notices normally appear. The contents of the NOTICE file are + for informational purposes only and do not modify the License. You may + add Your own attribution notices within Derivative Works that You + distribute, alongside or as an addendum to the NOTICE text from the Work, + provided that such additional attribution notices cannot be construed as + modifying the License. + + You may add Your own copyright statement to Your modifications and may + provide additional or different license terms and conditions for use, + reproduction, or distribution of Your modifications, or for any such + Derivative Works as a whole, provided Your use, reproduction, and + distribution of the Work otherwise complies with the conditions stated in + this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any + Contribution intentionally submitted for inclusion in the Work by You to the + Licensor shall be under the terms and conditions of this License, without + any additional terms or conditions. Notwithstanding the above, nothing + herein shall supersede or modify the terms of any separate license agreement + you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, + trademarks, service marks, or product names of the Licensor, except as + required for reasonable and customary use in describing the origin of the + Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in + writing, Licensor provides the Work (and each Contributor provides its + Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied, including, without limitation, any + warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or + FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining + the appropriateness of using or redistributing the Work and assume any risks + associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in + tort (including negligence), contract, or otherwise, unless required by + applicable law (such as deliberate and grossly negligent acts) or agreed to + in writing, shall any Contributor be liable to You for damages, including + any direct, indirect, special, incidental, or consequential damages of any + character arising as a result of this License or out of the use or inability + to use the Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all other + commercial damages or losses), even if such Contributor has been advised of + the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or + Derivative Works thereof, You may choose to offer, and charge a fee for, + acceptance of support, warranty, indemnity, or other liability obligations + and/or rights consistent with this License. However, in accepting such + obligations, You may act only on Your own behalf and on Your sole + responsibility, not on behalf of any other Contributor, and only if You + agree to indemnify, defend, and hold each Contributor harmless for any + liability incurred by, or claims asserted against, such Contributor by + reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +LLVM EXCEPTIONS TO THE APACHE 2.0 LICENSE + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you may +redistribute such embedded portions in such Object form without complying with +the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a court +of competent jurisdiction determines that the patent provision (Section 3), the +indemnity provision (Section 9) or other Section of the License conflicts with +the conditions of the GPLv2, you may retroactively and prospectively choose to +deem waived or otherwise exclude such Section(s) of the License, but only in +their entirety and only with respect to the Combined Software. + +END OF LLVM EXCEPTIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification +within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +END OF APPENDIX diff --git a/examples/bitcoin_psbt/README.md b/examples/bitcoin_psbt/README.md new file mode 100644 index 0000000000..98f6cdd7c6 --- /dev/null +++ b/examples/bitcoin_psbt/README.md @@ -0,0 +1,67 @@ +# Installation + +This is an implementation of the [basic_bitcoin example](https://github.com/dfinity/examples/tree/master/rust/basic_bitcoin) written in TypeScript with Azle. It's focused on showing you basic ICP/Bitcoin concepts in a local development environment. + +## bitcoind + +```bash +mkdir -p .bitcoin/data + +# Check if bitcoind executable exists; if not, download and extract it +if [ ! -f ".bitcoin/bin/bitcoind" ]; then + curl -o bitcoin.tar.gz https://bitcoincore.org/bin/bitcoin-core-23.0/bitcoin-23.0-x86_64-linux-gnu.tar.gz + tar xzf bitcoin.tar.gz --overwrite --strip-components=1 --directory=.bitcoin/ bitcoin-23.0/bin/ + rm -rf bitcoin.tar.gz +fi +``` + +# Deployment + +## bitcoind + +```bash +# Do this in its own terminal +.bitcoin/bin/bitcoind -conf=$(pwd)/.bitcoin.conf -datadir=$(pwd)/.bitcoin/data --port=18444 +``` + +## dfx + +```bash +# Do this in its own terminal +dfx start --clean --host 127.0.0.1:8000 +``` + +## basic_bitcion + +```bash +BITCOIN_NETWORK=regtest dfx deploy' +``` + +# Usage + +After setting up your local Bitcoin node and deploying the canister locally, you can interact with your canister from the command-line or from the browser. To interact with your canister from the browser, use the URL displayed in the terminal after deploy, which will look something like this: `http://127.0.0.1:8000/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai` + +This example provides 5 functions to call: `getBalance`, `getUtxos`, `getCurrentFeePercentiles`, `getP2PKHAddress`, and `send`. + +To create Bitcoin that can be used to test the `send` functionality, first mine 101 blocks to the address you obtain from calling `getP2PKHAddress`. You must mine 101 blocks because there is a rule in Bitcoin that block rewards cannot be spent until 100 blocks have been mined on top: + +```bash +.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf generatetoaddress 101 +``` + +Give this process a minute or two before expecting the balance of your address to update properly. + +You can now use the `send` function to try sending some BTC to another address, for example to the address `n2dcQfuwFw7M2UYzLfM6P7DwewsQaygb8S`. After calling the `send` function you should receive a transaction hash such as `44fdde20b88027e6a8786689739b0fec74f9e6c86cad3f4d5c05e43a51d97445`. To make sure the transaction is included into the Bitcoin blockchain, you must mine a new block: + +```bash +.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf generatetoaddress 1 +``` + +You should see some output such as `2023-05-30T20:33:25Z CreateNewBlock(): block weight: 1804 txs: 1 fees: 454 sigops 408` in your Bitcoin node's terminal indicating that your transaction was included in the block. + +Now if you call the functions with the `n2dcQfuwFw7M2UYzLfM6P7DwewsQaygb8S` address you should see a new balance, utxos, and fee percentiles. + +If you need more information you can view the following: + +- [Developing Bitcoin dapps locally]()https://internetcomputer.org/docs/current/developer-docs/integrations/bitcoin/local-development +- [Deploying your first Bitcoin dapp](https://internetcomputer.org/docs/current/samples/deploying-your-first-bitcoin-dapp) diff --git a/examples/bitcoin_psbt/dfx.json b/examples/bitcoin_psbt/dfx.json new file mode 100644 index 0000000000..3fcd94fb20 --- /dev/null +++ b/examples/bitcoin_psbt/dfx.json @@ -0,0 +1,25 @@ +{ + "canisters": { + "basic_bitcoin": { + "type": "azle", + "main": "src/index.ts", + "env": ["BITCOIN_NETWORK"] + } + }, + "networks": { + "local": { + "bind": "127.0.0.1:8000", + "type": "ephemeral", + "replica": { + "subnet_type": "system" + } + } + }, + "defaults": { + "bitcoin": { + "enabled": true, + "nodes": ["127.0.0.1:18444"], + "log_level": "info" + } + } +} diff --git a/examples/bitcoin_psbt/package-lock.json b/examples/bitcoin_psbt/package-lock.json new file mode 100644 index 0000000000..a63a63721f --- /dev/null +++ b/examples/bitcoin_psbt/package-lock.json @@ -0,0 +1,3056 @@ +{ + "name": "basic_bitcoin", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "hasInstallScript": true, + "dependencies": { + "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", + "azle": "^0.21.1", + "bitcoinjs-lib": "^6.1.5", + "ecpair": "^2.1.0", + "express": "^4.18.2", + "tiny-secp256k1": "^2.2.3" + }, + "devDependencies": { + "@dfinity/agent": "^0.19.2", + "@types/express": "^4.17.21", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, + "node_modules/@bitcoin-js/tiny-secp256k1-asmjs": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@bitcoin-js/tiny-secp256k1-asmjs/-/tiny-secp256k1-asmjs-2.2.3.tgz", + "integrity": "sha512-arFPdEZi9RIiaG76OZswTnAU0KfuiLwGw2VNfD66LKhzlbfOnX1o1WI/GI3qm9UbjG/0QOzZu/KmTNvL79x/DQ==", + "dependencies": { + "uint8array-tools": "0.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@dfinity/agent": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-0.19.3.tgz", + "integrity": "sha512-q410aNLoOA1ZkwdAMgSo6t++pjISn9TfSybRXhPRI5Ume7eG6+6qYr/rlvcXy7Nb2+Ar7LTsHNn34IULfjni7w==", + "dev": true, + "dependencies": { + "@noble/hashes": "^1.3.1", + "base64-arraybuffer": "^0.2.0", + "borc": "^2.1.1", + "simple-cbor": "^0.4.1" + }, + "peerDependencies": { + "@dfinity/candid": "^0.19.3", + "@dfinity/principal": "^0.19.3" + } + }, + "node_modules/@dfinity/candid": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-0.19.3.tgz", + "integrity": "sha512-yXfbLSWTeRd4G0bLLxYoDqpXH3Jim0P+1PPZOoktXNC1X1hB+ea3yrZebX75t4GVoQK7123F7mxWHiPjuV2tQQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "@dfinity/principal": "^0.19.3" + } + }, + "node_modules/@dfinity/identity-secp256k1": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/identity-secp256k1/-/identity-secp256k1-1.2.0.tgz", + "integrity": "sha512-QAsVycTLY0HH5OS/Ub8G0A70WZb9nkJR9fzZywUIAKpFRY8ZXHiXrT/ifM6AqY9L/83l/ywrwuSKbHVngshpkw==", + "dependencies": { + "@dfinity/agent": "^1.2.0", + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "asn1js": "^3.0.5", + "bip39": "^3.1.0", + "bs58check": "^3.0.1", + "hdkey": "^2.1.0" + } + }, + "node_modules/@dfinity/identity-secp256k1/node_modules/@dfinity/agent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-1.2.0.tgz", + "integrity": "sha512-i9mH0PO73nrhLc9lZv14SWr4muyMcs6uqqlG2SHQtRUFRXbqj4DOhKsU0Sm+kC8eWYCSu65WPKPYwwAR7YM6ug==", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "base64-arraybuffer": "^0.2.0", + "borc": "^2.1.1", + "buffer": "^6.0.3", + "simple-cbor": "^0.4.1" + }, + "peerDependencies": { + "@dfinity/candid": "^1.2.0", + "@dfinity/principal": "^1.2.0" + } + }, + "node_modules/@dfinity/identity-secp256k1/node_modules/@dfinity/candid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-1.2.0.tgz", + "integrity": "sha512-L6gV3ODIFC9qNenq3zuRvHrTwH36IM5utVH2wMS5f5eIUeG9fNe+avYLRPBUJwdeX7cM7xhvDgE/m/aN2cZvKQ==", + "peer": true, + "peerDependencies": { + "@dfinity/principal": "^1.2.0" + } + }, + "node_modules/@dfinity/identity-secp256k1/node_modules/@dfinity/principal": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-1.2.0.tgz", + "integrity": "sha512-7eurqPDL5ptlTTLMJPeiO75FAumXHsWEWDVQaN6XpA3aZtmofNK4Sb5g5Ne9syeuoCJcW3mFBbbFtFNxggxu+g==", + "peer": true, + "dependencies": { + "@noble/hashes": "^1.3.1" + } + }, + "node_modules/@dfinity/identity-secp256k1/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@dfinity/principal": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-0.19.3.tgz", + "integrity": "sha512-+nixVvdGt7ECxRvLXDXsvU9q9sSPssBtDQ4bXa149SK6gcYcmZ6lfWIi3DJNqj3tGROxILVBsguel9tECappsA==", + "dev": true, + "peer": true, + "dependencies": { + "@noble/hashes": "^1.3.1" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.4.tgz", + "integrity": "sha512-uBIbiYMeSsy2U0XQoOGVVcpIktjLMEKa7ryz2RLr7L/vTnANNEsPVAh4xOv7ondGz6ac1zVb0F8Jx20rQikffQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.4.tgz", + "integrity": "sha512-mRsi2vJsk4Bx/AFsNBqOH2fqedxn5L/moT58xgg51DjX1la64Z3Npicut2VbhvDFO26qjWtPMsVxCd80YTFVeg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.4.tgz", + "integrity": "sha512-4iPufZ1TMOD3oBlGFqHXBpa3KFT46aLl6Vy7gwed0ZSYgHaZ/mihbYb4t7Z9etjkC9Al3ZYIoOaHrU60gcMy7g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.4.tgz", + "integrity": "sha512-Lviw8EzxsVQKpbS+rSt6/6zjn9ashUZ7Tbuvc2YENgRl0yZTktGlachZ9KMJUsVjZEGFVu336kl5lBgDN6PmpA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.4.tgz", + "integrity": "sha512-YHbSFlLgDwglFn0lAO3Zsdrife9jcQXQhgRp77YiTDja23FrC2uwnhXMNkAucthsf+Psr7sTwYEryxz6FPAVqw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.4.tgz", + "integrity": "sha512-vz59ijyrTG22Hshaj620e5yhs2dU1WJy723ofc+KUgxVCM6zxQESmWdMuVmUzxtGqtj5heHyB44PjV/HKsEmuQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.4.tgz", + "integrity": "sha512-3sRbQ6W5kAiVQRBWREGJNd1YE7OgzS0AmOGjDmX/qZZecq8NFlQsQH0IfXjjmD0XtUYqr64e0EKNFjMUlPL3Cw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.4.tgz", + "integrity": "sha512-z/4ArqOo9EImzTi4b6Vq+pthLnepFzJ92BnofU1jgNlcVb+UqynVFdoXMCFreTK7FdhqAzH0vmdwW5373Hm9pg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.4.tgz", + "integrity": "sha512-ZWmWORaPbsPwmyu7eIEATFlaqm0QGt+joRE9sKcnVUG3oBbr/KYdNE2TnkzdQwX6EDRdg/x8Q4EZQTXoClUqqA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.4.tgz", + "integrity": "sha512-EGc4vYM7i1GRUIMqRZNCTzJh25MHePYsnQfKDexD8uPTCm9mK56NIL04LUfX2aaJ+C9vyEp2fJ7jbqFEYgO9lQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.4.tgz", + "integrity": "sha512-WVhIKO26kmm8lPmNrUikxSpXcgd6HDog0cx12BUfA2PkmURHSgx9G6vA19lrlQOMw+UjMZ+l3PpbtzffCxFDRg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.4.tgz", + "integrity": "sha512-keYY+Hlj5w86hNp5JJPuZNbvW4jql7c1eXdBUHIJGTeN/+0QFutU3GrS+c27L+NTmzi73yhtojHk+lr2+502Mw==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.4.tgz", + "integrity": "sha512-tQ92n0WMXyEsCH4m32S21fND8VxNiVazUbU4IUGVXQpWiaAxOBvtOtbEt3cXIV3GEBydYsY8pyeRMJx9kn3rvw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.4.tgz", + "integrity": "sha512-tRRBey6fG9tqGH6V75xH3lFPpj9E8BH+N+zjSUCnFOX93kEzqS0WdyJHkta/mmJHn7MBaa++9P4ARiU4ykjhig==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.4.tgz", + "integrity": "sha512-152aLpQqKZYhThiJ+uAM4PcuLCAOxDsCekIbnGzPKVBRUDlgaaAfaUl5NYkB1hgY6WN4sPkejxKlANgVcGl9Qg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.4.tgz", + "integrity": "sha512-Mi4aNA3rz1BNFtB7aGadMD0MavmzuuXNTaYL6/uiYIs08U7YMPETpgNn5oue3ICr+inKwItOwSsJDYkrE9ekVg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.4.tgz", + "integrity": "sha512-9+Wxx1i5N/CYo505CTT7T+ix4lVzEdz0uCoYGxM5JDVlP2YdDC1Bdz+Khv6IbqmisT0Si928eAxbmGkcbiuM/A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.4.tgz", + "integrity": "sha512-MFsHleM5/rWRW9EivFssop+OulYVUoVcqkyOkjiynKBCGBj9Lihl7kh9IzrreDyXa4sNkquei5/DTP4uCk25xw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.4.tgz", + "integrity": "sha512-6Xq8SpK46yLvrGxjp6HftkDwPP49puU4OF0hEL4dTxqCbfx09LyrbUj/D7tmIRMj5D5FCUPksBbxyQhp8tmHzw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.4.tgz", + "integrity": "sha512-PkIl7Jq4mP6ke7QKwyg4fD4Xvn8PXisagV/+HntWoDEdmerB2LTukRZg728Yd1Fj+LuEX75t/hKXE2Ppk8Hh1w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.4.tgz", + "integrity": "sha512-ga676Hnvw7/ycdKB53qPusvsKdwrWzEyJ+AtItHGoARszIqvjffTwaaW3b2L6l90i7MO9i+dlAW415INuRhSGg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.4.tgz", + "integrity": "sha512-HP0GDNla1T3ZL8Ko/SHAS2GgtjOg+VmWnnYLhuTksr++EnduYB0f3Y2LzHsUwb2iQ13JGoY6G3R8h6Du/WG6uA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@swc/core": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.91.tgz", + "integrity": "sha512-r950d0fdlZ8qbSDyvApn3HyCojiZE8xpgJzQvypeMi32dalYwugdJKWyLB55JIGMRGJ8+lmVvY4MPGkSR3kXgA==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.91", + "@swc/core-darwin-x64": "1.3.91", + "@swc/core-linux-arm-gnueabihf": "1.3.91", + "@swc/core-linux-arm64-gnu": "1.3.91", + "@swc/core-linux-arm64-musl": "1.3.91", + "@swc/core-linux-x64-gnu": "1.3.91", + "@swc/core-linux-x64-musl": "1.3.91", + "@swc/core-win32-arm64-msvc": "1.3.91", + "@swc/core-win32-ia32-msvc": "1.3.91", + "@swc/core-win32-x64-msvc": "1.3.91" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.91.tgz", + "integrity": "sha512-7kHGiQ1he5khcEeJuHDmLZPM3rRL/ith5OTmV6bOPsoHi46kLeixORW+ts1opC3tC9vu6xbk16xgX0QAJchc1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.91.tgz", + "integrity": "sha512-8SpU18FbFpZDVzsHsAwdI1thF/picQGxq9UFxa8W+T9SDnbsqwFJv/6RqKJeJoDV6qFdl2OLjuO0OL7xrp0qnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.91.tgz", + "integrity": "sha512-fOq4Cy8UbwX1yf0WB0d8hWZaIKCnPtPGguRqdXGLfwvhjZ9SIErT6PnmGTGRbQCNCIkOZWHKyTU0r8t2dN3haQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.91.tgz", + "integrity": "sha512-fki4ioRP/Esy4vdp8T34RCV+V9dqkRmOt763pf74pdiyFV2dPLXa5lnw/XvR1RTfPGknrYgjEQLCfZlReTryRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.91.tgz", + "integrity": "sha512-XrG+DUUqNtfVLcJ20imby7fpBwQNG5VsEQBzQndSonPyUOa2YkTbBb60YDondfQGDABopuHH8gHN8o2H2/VCnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.91.tgz", + "integrity": "sha512-d11bYhX+YPBr/Frcjc6eVn3C0LuS/9U1Li9EmQ+6s9EpYtYRl2ygSlC8eueLbaiazBnCVYFnc8bU4o0kc5B9sw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.91.tgz", + "integrity": "sha512-2SRp5Dke2P4jCQePkDx9trkkTstnRpZJVw5r3jvYdk0zeO6iC4+ZPvvoWXJLigqQv/fZnIiSUfJ6ssOoaEqTzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.91.tgz", + "integrity": "sha512-l9qKXikOxj42UIjbeZpz9xtBmr736jOMqInNP8mVF2/U+ws5sI8zJjcOFFtfis4ru7vWCXhB1wtltdlJYO2vGA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.91.tgz", + "integrity": "sha512-+s+52O0QVPmzOgjEe/rcb0AK6q/J7EHKwAyJCu/FaYO9df5ovE0HJjSKP6HAF0dGPO5hkENrXuNGujofUH9vtQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.91", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.91.tgz", + "integrity": "sha512-7u9HDQhjUC3Gv43EFW84dZtduWCSa4MgltK+Sp9zEGti6WXqDPu/ESjvDsQEVYTBEMEvZs/xVAXPgLVHorV5nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "optional": true, + "peer": true + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "optional": true, + "peer": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz", + "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw==" + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz", + "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/azle": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/azle/-/azle-0.21.1.tgz", + "integrity": "sha512-pPAMmgs4X7joasm4YnfZibnEEDgvtYwfpScfSqcOgRLYJnRwil+xgnkIns/F3iAaRoRmy84pPKPIN5J3ysHQRw==", + "dependencies": { + "@dfinity/agent": "^1.1.0", + "@dfinity/identity-secp256k1": "^1.1.0", + "@types/uuid": "^9.0.4", + "buffer": "^6.0.3", + "chokidar": "^3.6.0", + "crypto-browserify": "^3.12.0", + "esbuild": "^0.19.3", + "ethers": "^6.11.1", + "fs-extra": "10.0.1", + "hash-of-directory": "^1.0.1", + "http-message-parser": "^0.0.34", + "js-sha256": "0.9.0", + "net": "^1.0.2", + "pako": "^2.1.0", + "text-encoding": "0.7.0", + "ts-node": "10.3.1", + "typescript": "^5.2.2", + "uuid": "^9.0.1" + }, + "bin": { + "azle": "bin.js" + } + }, + "node_modules/azle/node_modules/@dfinity/agent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/agent/-/agent-1.2.0.tgz", + "integrity": "sha512-i9mH0PO73nrhLc9lZv14SWr4muyMcs6uqqlG2SHQtRUFRXbqj4DOhKsU0Sm+kC8eWYCSu65WPKPYwwAR7YM6ug==", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "base64-arraybuffer": "^0.2.0", + "borc": "^2.1.1", + "buffer": "^6.0.3", + "simple-cbor": "^0.4.1" + }, + "peerDependencies": { + "@dfinity/candid": "^1.2.0", + "@dfinity/principal": "^1.2.0" + } + }, + "node_modules/azle/node_modules/@dfinity/candid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/candid/-/candid-1.2.0.tgz", + "integrity": "sha512-L6gV3ODIFC9qNenq3zuRvHrTwH36IM5utVH2wMS5f5eIUeG9fNe+avYLRPBUJwdeX7cM7xhvDgE/m/aN2cZvKQ==", + "peer": true, + "peerDependencies": { + "@dfinity/principal": "^1.2.0" + } + }, + "node_modules/azle/node_modules/@dfinity/principal": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@dfinity/principal/-/principal-1.2.0.tgz", + "integrity": "sha512-7eurqPDL5ptlTTLMJPeiO75FAumXHsWEWDVQaN6XpA3aZtmofNK4Sb5g5Ne9syeuoCJcW3mFBbbFtFNxggxu+g==", + "peer": true, + "dependencies": { + "@noble/hashes": "^1.3.1" + } + }, + "node_modules/azle/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/azle/node_modules/ts-node": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.1.tgz", + "integrity": "sha512-Yw3W2mYzhHfCHOICGNJqa0i+rbL0rAyg7ZIHxU+K4pgY8gd2Lh1j+XbHCusJMykbj6RZMJVOY0MlHVd+GOivcw==", + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" + }, + "node_modules/base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "dependencies": { + "@noble/hashes": "^1.2.0" + } + }, + "node_modules/bitcoinjs-lib": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz", + "integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/borc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/borc/-/borc-2.1.2.tgz", + "integrity": "sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==", + "dependencies": { + "bignumber.js": "^9.0.0", + "buffer": "^5.5.0", + "commander": "^2.15.0", + "ieee754": "^1.1.13", + "iso-url": "~0.4.7", + "json-text-sequence": "~0.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.4", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/bs58": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "dependencies": { + "base-x": "^4.0.0" + } + }, + "node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delimit-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/delimit-stream/-/delimit-stream-0.1.0.tgz", + "integrity": "sha512-a02fiQ7poS5CnjiJBAsjGLPp5EwVoGHNeu9sziBd9huppRfsAFIpv5zNLv0V1gbop53ilngAf5Kf331AwcoRBQ==" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/ecpair": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", + "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", + "dependencies": { + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.19.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.4.tgz", + "integrity": "sha512-x7jL0tbRRpv4QUyuDMjONtWFciygUxWaUM1kMX2zWxI0X2YWOt7MSA0g4UdeSiHM8fcYVzpQhKYOycZwxTdZkA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.4", + "@esbuild/android-arm64": "0.19.4", + "@esbuild/android-x64": "0.19.4", + "@esbuild/darwin-arm64": "0.19.4", + "@esbuild/darwin-x64": "0.19.4", + "@esbuild/freebsd-arm64": "0.19.4", + "@esbuild/freebsd-x64": "0.19.4", + "@esbuild/linux-arm": "0.19.4", + "@esbuild/linux-arm64": "0.19.4", + "@esbuild/linux-ia32": "0.19.4", + "@esbuild/linux-loong64": "0.19.4", + "@esbuild/linux-mips64el": "0.19.4", + "@esbuild/linux-ppc64": "0.19.4", + "@esbuild/linux-riscv64": "0.19.4", + "@esbuild/linux-s390x": "0.19.4", + "@esbuild/linux-x64": "0.19.4", + "@esbuild/netbsd-x64": "0.19.4", + "@esbuild/openbsd-x64": "0.19.4", + "@esbuild/sunos-x64": "0.19.4", + "@esbuild/win32-arm64": "0.19.4", + "@esbuild/win32-ia32": "0.19.4", + "@esbuild/win32-x64": "0.19.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ethers": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.11.1.tgz", + "integrity": "sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + }, + "node_modules/ethers/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-prop": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/get-prop/-/get-prop-0.0.10.tgz", + "integrity": "sha512-XRSGBgcIisSMLJ/dwe1y/eMm9yzLicEJKmEXA3ArBkDkCW2nyRroLOoKIz+SdxuG5SI7ym2QHaTU5ifCl7MTDg==" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-of-directory": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hash-of-directory/-/hash-of-directory-1.0.1.tgz", + "integrity": "sha512-PX6VaxD6JK8R4113ChdTtEnWIo2XA9mz4yrtGBuUGUKtWCj6iWWYj/qwjdfs3Zgm+FdiNj0Vmt4VwPlwxx8WHw==" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hdkey": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hdkey/-/hdkey-2.1.0.tgz", + "integrity": "sha512-i9Wzi0Dy49bNS4tXXeGeu0vIcn86xXdPQUpEYg+SO1YiO8HtomjmmRMaRyqL0r59QfcD4PfVbSF3qmsWFwAemA==", + "dependencies": { + "bs58check": "^2.1.2", + "ripemd160": "^2.0.2", + "safe-buffer": "^5.1.1", + "secp256k1": "^4.0.0" + } + }, + "node_modules/hdkey/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/hdkey/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/hdkey/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-message-parser": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/http-message-parser/-/http-message-parser-0.0.34.tgz", + "integrity": "sha512-KABKXT347AYvQoaMZg9/K+/GqW6gfB4pKCiTyMUYnosfkdkaBkrXE/cWGSLk5jvD5tiDeLFlYSHLhhPhQKbRrA==", + "dependencies": { + "buffer": "^4.9.1", + "concat-stream": "^1.5.1", + "get-prop": "0.0.10", + "minimist": "^1.2.0", + "stream-buffers": "^3.0.0" + }, + "bin": { + "http-message-parser": "bin/http-message-parser.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-message-parser/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/iso-url": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz", + "integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "node_modules/json-text-sequence": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/json-text-sequence/-/json-text-sequence-0.1.1.tgz", + "integrity": "sha512-L3mEegEWHRekSHjc7+sc8eJhba9Clq1PZ8kMkzf8OxElhXc8O4TS5MwcVlj9aEbm5dr81N90WHC5nAz3UO971w==", + "dependencies": { + "delimit-stream": "0.1.0" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/net": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz", + "integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==" + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-cbor": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/simple-cbor/-/simple-cbor-0.4.1.tgz", + "integrity": "sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-buffers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-encoding": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", + "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", + "deprecated": "no longer maintained" + }, + "node_modules/tiny-secp256k1": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz", + "integrity": "sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==", + "dependencies": { + "uint8array-tools": "0.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uint8array-tools": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", + "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/wif/node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/wif/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/wif/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/examples/bitcoin_psbt/package.json b/examples/bitcoin_psbt/package.json new file mode 100644 index 0000000000..47c3e51d78 --- /dev/null +++ b/examples/bitcoin_psbt/package.json @@ -0,0 +1,34 @@ +{ + "scripts": { + "bitcoin": ".bitcoin/bin/bitcoind -conf=$(pwd)/.bitcoin.conf -datadir=$(pwd)/.bitcoin/data --port=18444", + "clean": "rm -rf .bitcoin/data/regtest", + "generate": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf -generate $npm_config_blocks", + "getblock": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblock $npm_config_blockhash 2", + "getblockcount": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockcount", + "getblockhash": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockhash $npm_config_index", + "getrawmempool": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawmempool true", + "getrawtransaction": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawtransaction $npm_config_txid", + "help": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf help", + "ic": "dfx start --clean --host 127.0.0.1:8000", + "install": "./scripts/install.sh", + "deploy": "dfx deploy basic_bitcoin --specified-id bkyz2-fmaaa-aaaaa-qaaaq-cai", + "pretest": "BITCOIN_NETWORK=regtest ts-node --transpile-only --ignore=false test/pretest.ts", + "test": "ts-node --transpile-only --ignore=false test/test.ts", + "premanualtest": "BITCOIN_NETWORK=regtest ts-node --transpile-only --ignore=false test/pretest.ts", + "manualtest": "ts-node --transpile-only --ignore=false test/test.ts" + }, + "dependencies": { + "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", + "azle": "^0.21.1", + "bitcoinjs-lib": "^6.1.5", + "ecpair": "^2.1.0", + "express": "^4.18.2", + "tiny-secp256k1": "^2.2.3" + }, + "devDependencies": { + "@dfinity/agent": "^0.19.2", + "@types/express": "^4.17.21", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/examples/bitcoin_psbt/scripts/bitcoin/getallblockhashes.sh b/examples/bitcoin_psbt/scripts/bitcoin/getallblockhashes.sh new file mode 100755 index 0000000000..d66d10abf9 --- /dev/null +++ b/examples/bitcoin_psbt/scripts/bitcoin/getallblockhashes.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +block_height=$(.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockcount) + +for ((i=1; i<=$block_height; i++)) +do + block_hash=$(.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockhash $i) + echo "Block $i: $block_hash" +done diff --git a/examples/bitcoin_psbt/scripts/bitcoin/transactionCount.js b/examples/bitcoin_psbt/scripts/bitcoin/transactionCount.js new file mode 100644 index 0000000000..1785372ed5 --- /dev/null +++ b/examples/bitcoin_psbt/scripts/bitcoin/transactionCount.js @@ -0,0 +1,29 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { execSync } = require('child_process'); + +const CLI = '.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf'; + +function getBlockTransactionCount() { + try { + const blockCount = parseInt( + execSync(`${CLI} getblockcount`).toString() + ); + + for (let i = 1; i <= blockCount; i++) { + const blockHash = execSync(`${CLI} getblockhash ${i}`) + .toString() + .trim(); + const blockInfo = JSON.parse( + execSync(`${CLI} getblock ${blockHash}`).toString() + ); + const transactionCount = blockInfo.tx.length; + console.log( + `Block ${i} (${blockHash}) has ${transactionCount} transactions.` + ); + } + } catch (err) { + console.error(err); + } +} + +getBlockTransactionCount(); diff --git a/examples/bitcoin_psbt/scripts/install.sh b/examples/bitcoin_psbt/scripts/install.sh new file mode 100755 index 0000000000..a4d0f03526 --- /dev/null +++ b/examples/bitcoin_psbt/scripts/install.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +mkdir -p .bitcoin/data + +# Check if bitcoind executable exists; if not, download and extract it +if [ ! -f ".bitcoin/bin/bitcoind" ]; then + curl -o bitcoin.tar.gz https://bitcoincore.org/bin/bitcoin-core-23.0/bitcoin-23.0-x86_64-linux-gnu.tar.gz + tar xzf bitcoin.tar.gz --overwrite --strip-components=1 --directory=.bitcoin/ bitcoin-23.0/bin/ + rm -rf bitcoin.tar.gz +fi diff --git a/examples/bitcoin_psbt/src/bitcoin_psbt.ts b/examples/bitcoin_psbt/src/bitcoin_psbt.ts new file mode 100644 index 0000000000..8399e0703b --- /dev/null +++ b/examples/bitcoin_psbt/src/bitcoin_psbt.ts @@ -0,0 +1,278 @@ +//! A demo of a very bare-bones bitcoin "wallet". +//! +//! The wallet here showcases how bitcoin addresses can be be computed +//! and how bitcoin transactions can be signed. It is missing several +//! pieces that any production-grade wallet would have, including: +//! +//! * Support for address types that aren't P2PKH. +//! * Caching spent UTXOs so that they are not reused in future transactions. +//! * Option to set the fee. +import { + BitcoinNetwork, + MillisatoshiPerByte, + Satoshi, + Utxo +} from 'azle/canisters/management'; +import { Psbt, Signer, SignerAsync, Transaction } from 'bitcoinjs-lib'; +import { ValidateSigFunction } from 'bitcoinjs-lib/src/psbt'; +import { Buffer } from 'buffer'; +import { ECPairAPI } from 'ecpair'; + +import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; +import { + determineNetwork, + publicKeyToP2pkhAddress +} from '../../basic_bitcoin/src/bitcoin_wallet'; +import * as ecdsaApi from '../../basic_bitcoin/src/ecdsa_api'; + +type TransactionHashes = { + [txid: string]: string; +}; + +/// Sends a transaction to the network that transfers the given amount to the +/// given destination, where the source of the funds is the canister itself +/// at the given derivation path. +export async function send( + network: BitcoinNetwork, + derivationPath: Uint8Array[], + keyName: string, + dstAddress: string, + amount: Satoshi, + transactionHashes: TransactionHashes, + ECPair: ECPairAPI +): Promise { + const feePerByte = await calculateFeePerByte(network); + + const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); + const ownAddress = publicKeyToP2pkhAddress(network, ownPublicKey); + + // Note that pagination may have to be used to get all UTXOs for the given address. + // For the sake of simplicity, it is assumed here that the `utxo` field in the response + // contains all UTXOs. + const ownUtxos = (await bitcoinApi.getUtxos(network, ownAddress.toString())) + .utxos; + + // Build the transaction that sends `amount` to the destination address. + const transaction = await buildPsbt( + ownPublicKey, + ownAddress, + ownUtxos, + dstAddress, + amount, + feePerByte, + network, + transactionHashes, + ECPair + ); + + // Sign the transaction. + const signer = getSigner(ownPublicKey, keyName, derivationPath); + const validator = getValidator(ECPair); + const signedTransaction = await signPsbt(transaction, signer, validator); + const signedTransactionBytes = signedTransaction.toBuffer(); + console.info( + `Signed transaction: ${signedTransactionBytes.toString('hex')}` + ); + + console.info('Sending transaction...'); + await bitcoinApi.sendTransaction(network, signedTransactionBytes); + console.info('Done'); + + return signedTransaction.getId(); +} + +async function calculateFeePerByte(network: BitcoinNetwork): Promise { + // Get fee percentiles from previous transactions to estimate our own fee. + const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); + + return feePercentiles.length === 0 + ? // There are no fee percentiles. This case can only happen on a regtest + // network where there are no non-coinbase transactions. In this case, + // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) + 2_000n + : // Choose the 50th percentile for sending fees. + feePercentiles[50]; +} + +// Builds a transaction to send the given `amount` of satoshis to the +// destination address. +async function buildPsbt( + ownPublicKey: Uint8Array, + ownAddress: string, + ownUtxos: Utxo[], + dstAddress: string, + amount: Satoshi, + feePerByte: MillisatoshiPerByte, + network: BitcoinNetwork, + transactionHashes: TransactionHashes, + ECPair: ECPairAPI +): Promise { + // We have a chicken-and-egg problem where we need to know the length + // of the transaction in order to compute its proper fee, but we need + // to know the proper fee in order to figure out the inputs needed for + // the transaction. + // + // We solve this problem iteratively. We start with a fee of zero, build + // and sign a transaction, see what its size is, and then update the fee, + // rebuild the transaction, until the fee is set to the correct amount. + console.info('Building transaction...'); + let totalFee = 0n; + // eslint-disable-next-line no-constant-condition + while (true) { + let transaction = buildPsbtWithFee( + ownUtxos, + ownAddress, + dstAddress, + amount, + totalFee, + network, + transactionHashes + ); + + // Sign the transaction. In this case, we only care about the size + // of the signed transaction. + const signer = getMockSigner(ownPublicKey, ECPair); + const validator = getMockValidator(); + const signedTransaction = await signPsbt( + transaction.clone(), + signer, + validator + ); + + const signedTxBytesLen = BigInt(signedTransaction.byteLength()); + + if ((signedTxBytesLen * feePerByte) / 1_000n === totalFee) { + console.info(`Transaction built with fee ${totalFee}.`); + return transaction; + } else { + totalFee = (signedTxBytesLen * feePerByte) / 1_000n; + } + } +} + +function buildPsbtWithFee( + ownUtxos: Utxo[], + ownAddress: string, + destAddress: string, + amount: bigint, + fee: bigint, + network: BitcoinNetwork, + transactionHashes: TransactionHashes +): Psbt { + // Assume that any amount below this threshold is dust. + const dustThreshold = 1_000n; + + // Select which UTXOs to spend. We naively spend the oldest available UTXOs, + // even if they were previously spent in a transaction. This isn't a + // problem as long as at most one transaction is created per block and + // we're using min_confirmations of 1. + let utxosToSpend: Utxo[] = []; + let totalSpent = 0n; + for (const utxo of [...ownUtxos].reverse()) { + totalSpent += utxo.value; + utxosToSpend.push(utxo); + if (totalSpent >= amount + fee) { + // We have enough inputs to cover the amount we want to spend. + break; + } + } + + if (totalSpent < amount + fee) { + throw new Error( + `Insufficient balance: ${totalSpent}, trying to transfer ${amount} satoshi with fee ${fee}` + ); + } + + let transaction = new Psbt({ network: determineNetwork(network) }); + let newTransaction = new Psbt({ network: determineNetwork(network) }); + transaction.setVersion(1); + newTransaction.setVersion(1); + + for (const utxo of utxosToSpend) { + const previousTxidHash = Buffer.from(utxo.outpoint.txid); + const txid = previousTxidHash.reverse().toString('hex'); + const nonWitnessUtxo = Buffer.from(transactionHashes[txid], 'hex'); + transaction.addInput({ + hash: Buffer.from(utxo.outpoint.txid), + index: utxo.outpoint.vout, + nonWitnessUtxo + }); + newTransaction.addInput({ + hash: Buffer.from(utxo.outpoint.txid), + index: utxo.outpoint.vout + }); + // TODO see why these are different + } + + const remainingAmount = totalSpent - amount - fee; + + transaction.addOutput({ address: destAddress, value: Number(amount) }); + + if (remainingAmount >= dustThreshold) { + transaction.addOutput({ + address: ownAddress, + value: Number(remainingAmount) + }); + } + + return transaction; +} + +// Sign a bitcoin transaction. +// +// IMPORTANT: This method is for demonstration purposes only and it only +// supports signing transactions if: +// +// 1. All the inputs are referencing outpoints that are owned by `own_address`. +// 2. `own_address` is a P2PKH address. +async function signPsbt( + transaction: Psbt, + signer: Signer | SignerAsync, + validator: ValidateSigFunction +): Promise { + await transaction.signAllInputsAsync(signer); + transaction.validateSignaturesOfAllInputs(validator); + transaction.finalizeAllInputs(); + return transaction.extractTransaction(); +} + +function getSigner( + publicKey: Uint8Array, + keyName: string, + derivationPath: Uint8Array[] +): SignerAsync { + return { + sign: async (hashBuffer) => { + const sec1 = await ecdsaApi.signWithECDSA( + keyName, + derivationPath, + Uint8Array.from(hashBuffer) + ); + + return Buffer.from(sec1); + }, + publicKey: Buffer.from(publicKey) + }; +} + +function getMockSigner(ownPublicKey: Uint8Array, ECPair: ECPairAPI): Signer { + const keyPair = ECPair.makeRandom(); + return { + sign: (hashBuffer) => { + return keyPair.sign(hashBuffer); + }, + publicKey: Buffer.from(ownPublicKey) + }; +} + +function getValidator(ECPair: ECPairAPI): ValidateSigFunction { + return (pubkey: Buffer, msghash: Buffer, signature: Buffer): boolean => { + return ECPair.fromPublicKey(pubkey).verify(msghash, signature); + }; +} + +function getMockValidator(): ValidateSigFunction { + return (_pubkey: Buffer, _msghash: Buffer, _signature: Buffer): boolean => { + return true; + }; +} diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts new file mode 100644 index 0000000000..67c72a7c03 --- /dev/null +++ b/examples/bitcoin_psbt/src/index.ts @@ -0,0 +1,75 @@ +// import * as ecc from 'tiny-secp256k1/lib/'; // TODO we should switch to this import as soon as we have wasm support +import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; +import { jsonParse, jsonStringify } from 'azle'; +import { BitcoinNetwork } from 'azle/canisters/management'; +import { ECPairFactory } from 'ecpair'; +import express, { Request } from 'express'; + +import { determineKeyName, determineNetwork } from '../../basic_bitcoin/src'; +import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; +import * as bitcoinWallet from '../../basic_bitcoin/src/bitcoin_wallet'; +import * as bitcoinPsbt from './bitcoin_psbt'; + +// The bitcoin network to connect to. +// +// When developing locally this should be `Regtest`. +// When deploying to the IC this should be `Testnet`. +// `Mainnet` is currently unsupported. +const NETWORK: BitcoinNetwork = determineNetwork( + process.env.BITCOIN_NETWORK +) ?? { + testnet: null +}; + +// The derivation path to use for ECDSA secp256k1. +const DERIVATION_PATH: Uint8Array[] = []; + +// The ECDSA key name. +const KEY_NAME: string = determineKeyName(NETWORK); + +const app = express(); + +const ECPair = ECPairFactory(ecc); + +app.use(express.json()); + +/// Returns the balance of the given bitcoin address. +app.get( + '/get-balance', + async (req: Request, res) => { + const balance = await bitcoinApi.getBalance(NETWORK, req.query.address); + + res.send(jsonStringify(balance)); + } +); + +/// Returns the P2PKH address of this canister at a specific derivation path. +app.get('/get-p2pkh-address', async (req, res) => { + const address = await bitcoinWallet.getP2pkhAddress( + NETWORK, + KEY_NAME, + DERIVATION_PATH + ); + + res.send(address); +}); + +/// Sends the given amount of bitcoin from this canister to the given address. +/// Returns the transaction ID. +app.post('/send', async (req, res) => { + const { transactions, destinationAddress, amountInSatoshi } = req.body; + + const txId = await bitcoinPsbt.send( + NETWORK, + DERIVATION_PATH, + KEY_NAME, + destinationAddress, + BigInt(jsonParse(JSON.stringify(amountInSatoshi))), + transactions, + ECPair + ); + + res.send(txId); +}); + +app.listen(); diff --git a/examples/bitcoin_psbt/test/bitcoin.ts b/examples/bitcoin_psbt/test/bitcoin.ts new file mode 100644 index 0000000000..730d8aac5d --- /dev/null +++ b/examples/bitcoin_psbt/test/bitcoin.ts @@ -0,0 +1,90 @@ +import { jsonParse } from 'azle'; +import { execSync } from 'child_process'; + +type Block = { + hash: string; + confirmations: number; + height: number; + version: number; + versionHex: string; + merkleroot: string; + time: number; + mediantime: number; + nonce: number; + bits: string; + difficulty: number; + chainwork: string; + nTx: number; + previousblockhash: string; + nextblockhash: string; + strippedsize: number; + size: number; + weight: number; + tx: Tx[]; +}; + +type Tx = { + txid: string; + hash: string; + version: number; + size: number; + vsize: number; + weight: number; + locktime: number; + vin: [ + { + coinbase: string; + txinwitness: string[]; + sequence: number; + } + ]; + vout: Vout[]; + hex: string; +}; + +type Vout = { + value: number; + n: number; + scriptPubKey: { + asm: string; + desc: string; + hex: string; + address: string; + type: string; + }; +}; + +type TransactionHashes = { + [txid: string]: string; +}; + +export function getUtxoHashes(): TransactionHashes { + return Array.from({ length: 102 }).reduce((acc, _, blockHeight) => { + const blockHash = getBlockHash(blockHeight); + const block = getBlock(blockHash); + const result = getTransactionHashAndIdFromBlock(block); + return { ...acc, ...result }; + }, {} as TransactionHashes); +} + +function getBlock(hash: string): Block { + const getBlockResult = execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblock ${hash} 2` + ) + .toString() + .trim(); + + return jsonParse(getBlockResult); +} + +function getBlockHash(blockIndex: number): string { + return execSync( + `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockhash ${blockIndex}` + ) + .toString() + .trim(); +} + +function getTransactionHashAndIdFromBlock(block: Block): TransactionHashes { + return { [block.tx[0].txid]: block.tx[0].hex }; +} diff --git a/examples/bitcoin_psbt/test/manual_tests.ts b/examples/bitcoin_psbt/test/manual_tests.ts new file mode 100644 index 0000000000..b5b87660ab --- /dev/null +++ b/examples/bitcoin_psbt/test/manual_tests.ts @@ -0,0 +1,12 @@ +import { getCanisterId } from 'azle/dfx'; +import { runTests } from 'azle/test'; + +import { getTests } from './tests'; + +const canisterId = getCanisterId('basic_bitcoin'); + +// Allows running of the tests without starting and stopping a Bitcoin daemon +// automatically. That is to say you will need to start and stop the Bitcoin +// daemon manually. Great for running cli commands after or during the tests to +// check the state of the test network +runTests(getTests(canisterId)); diff --git a/examples/bitcoin_psbt/test/pretest.ts b/examples/bitcoin_psbt/test/pretest.ts new file mode 100644 index 0000000000..27f9b07e60 --- /dev/null +++ b/examples/bitcoin_psbt/test/pretest.ts @@ -0,0 +1,13 @@ +import { execSync } from 'child_process'; + +async function pretest() { + execSync(`dfx canister uninstall-code basic_bitcoin || true`, { + stdio: 'inherit' + }); + + execSync(`npm run deploy`, { + stdio: 'inherit' + }); +} + +pretest(); diff --git a/examples/bitcoin_psbt/test/test.ts b/examples/bitcoin_psbt/test/test.ts new file mode 100644 index 0000000000..7ad3f4a12d --- /dev/null +++ b/examples/bitcoin_psbt/test/test.ts @@ -0,0 +1,45 @@ +import { getCanisterId } from 'azle/dfx'; +import { runTests } from 'azle/test'; +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; +import { existsSync, rmSync } from 'fs-extra'; + +import { getTests } from './tests'; + +const canisterId = getCanisterId('basic_bitcoin'); + +export async function whileRunningBitcoinDaemon( + callback: () => Promise | void +) { + const bitcoinDaemon = await startBitcoinDaemon(); + await callback(); + bitcoinDaemon.kill(); +} + +async function startBitcoinDaemon(): Promise { + if (existsSync(`.bitcoin/regtest`)) { + rmSync('.bitcoin/regtest', { recursive: true, force: true }); + } + const bitcoinDaemon = spawn('.bitcoin/bin/bitcoind', [ + `-conf=${process.cwd()}/.bitcoin.conf`, + `-datadir=${process.cwd()}/.bitcoin`, + '--port=18444' + ]); + + process.on('uncaughtException', () => { + if (!bitcoinDaemon.killed) { + bitcoinDaemon.kill(); + } + }); + + process.on('exit', () => { + if (!bitcoinDaemon.killed) { + bitcoinDaemon.kill(); + } + }); + + console.info(`starting bitcoind...`); + await new Promise((resolve) => setTimeout(resolve, 5000)); + return bitcoinDaemon; +} + +whileRunningBitcoinDaemon(() => runTests(getTests(canisterId))); diff --git a/examples/bitcoin_psbt/test/tests.ts b/examples/bitcoin_psbt/test/tests.ts new file mode 100644 index 0000000000..6cd015e21b --- /dev/null +++ b/examples/bitcoin_psbt/test/tests.ts @@ -0,0 +1,100 @@ +import * as dns from 'node:dns'; +dns.setDefaultResultOrder('ipv4first'); + +import { jsonStringify } from 'azle'; +import { Test } from 'azle/test'; + +import { + createWallet, + generate, + generateToAddress +} from '../../basic_bitcoin/test/bitcoin'; +import { + compareBalances, + getBalance, + getFeeFromTransaction, + getP2pkhAddress, + waitForMempool +} from '../../basic_bitcoin/test/tests'; +import { getUtxoHashes } from './bitcoin'; + +const SINGLE_BLOCK_REWARD = 5_000_000_000n; +const FIRST_MINING_SESSION = 101; +const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; +const TO_ADDRESS = 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD'; // Regtest address from this WIF L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv + +let lastTx = ''; + +// TODO adding HD wallets and showing how to use the Derivation path might be nice + +export function getTests(canisterId: string): Test[] { + const origin = `http://${canisterId}.localhost:8000`; + return [ + { + name: 'Set up minting wallet', + prep: async () => { + createWallet('minty'); + } + }, + { + name: 'first mint BTC', + prep: async () => { + const address = await getP2pkhAddress(origin); + generateToAddress(address, FIRST_MINING_SESSION); + } + }, + { name: 'wait for blocks to settle', wait: 60_000 }, + { + name: '/send from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + prep: async () => { + const body = jsonStringify({ + transactions: getUtxoHashes(), + amountInSatoshi: FIRST_AMOUNT_SENT, + destinationAddress: TO_ADDRESS + }); + const response = await fetch(`${origin}/send`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + }); + lastTx = await response.text(); + console.info(lastTx); + } + }, + { + name: 'wait for transaction to be added to mempool', + prep: waitForMempool + }, + { + name: 'second mint BTC', + prep: async () => { + generate(1); + } + }, + { name: 'wait for blocks to settle', wait: 15_000 }, + { + name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv final', + test: async () => { + const balance = await getBalance(origin, TO_ADDRESS); + + return compareBalances(FIRST_AMOUNT_SENT, balance); + } + }, + { + name: '/get-balance final', + test: async () => { + const address = await getP2pkhAddress(origin); + const balance = await getBalance(origin, address); + + const fee = getFeeFromTransaction(lastTx, SINGLE_BLOCK_REWARD); + + const blockRewards = + SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION); + + const expectedBalance = blockRewards - FIRST_AMOUNT_SENT - fee; + + return compareBalances(expectedBalance, balance); + } + } + ]; +} diff --git a/examples/bitcoin_psbt/tsconfig.json b/examples/bitcoin_psbt/tsconfig.json new file mode 100644 index 0000000000..0817cb3fc1 --- /dev/null +++ b/examples/bitcoin_psbt/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2020", + "moduleResolution": "node", + "allowJs": true, + "outDir": "HACK_BECAUSE_OF_ALLOW_JS", + "allowSyntheticDefaultImports": true + } +} From 81e3f70a88f8c842b00d1c8d02596dbb8a369849 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 16 May 2024 11:19:35 -0600 Subject: [PATCH 26/49] WIP --- examples/bitcoin_psbt/src/index.ts | 18 +- .../{bitcoin_psbt.ts => psbt-non-segwit.ts} | 5 + examples/bitcoin_psbt/src/psbt.ts | 289 ++++++++++++++++++ 3 files changed, 308 insertions(+), 4 deletions(-) rename examples/bitcoin_psbt/src/{bitcoin_psbt.ts => psbt-non-segwit.ts} (97%) create mode 100644 examples/bitcoin_psbt/src/psbt.ts diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts index 67c72a7c03..bd8051f62b 100644 --- a/examples/bitcoin_psbt/src/index.ts +++ b/examples/bitcoin_psbt/src/index.ts @@ -7,8 +7,8 @@ import express, { Request } from 'express'; import { determineKeyName, determineNetwork } from '../../basic_bitcoin/src'; import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; -import * as bitcoinWallet from '../../basic_bitcoin/src/bitcoin_wallet'; -import * as bitcoinPsbt from './bitcoin_psbt'; +import * as bitcoinPsbt from './psbt'; +import * as bitcoinPsbtNonSegWit from './psbt-non-segwit'; // The bitcoin network to connect to. // @@ -45,7 +45,7 @@ app.get( /// Returns the P2PKH address of this canister at a specific derivation path. app.get('/get-p2pkh-address', async (req, res) => { - const address = await bitcoinWallet.getP2pkhAddress( + const address = await bitcoinPsbt.getP2wpkhAddress( NETWORK, KEY_NAME, DERIVATION_PATH @@ -59,7 +59,7 @@ app.get('/get-p2pkh-address', async (req, res) => { app.post('/send', async (req, res) => { const { transactions, destinationAddress, amountInSatoshi } = req.body; - const txId = await bitcoinPsbt.send( + const txId = await bitcoinPsbtNonSegWit.send( NETWORK, DERIVATION_PATH, KEY_NAME, @@ -72,4 +72,14 @@ app.post('/send', async (req, res) => { res.send(txId); }); +/// Returns the UTXOs of the given bitcoin address. +app.get( + '/get-utxos', + async (req: Request, res) => { + const utxos = await bitcoinApi.getUtxos(NETWORK, req.query.address); + + res.send(jsonStringify(utxos)); + } +); + app.listen(); diff --git a/examples/bitcoin_psbt/src/bitcoin_psbt.ts b/examples/bitcoin_psbt/src/psbt-non-segwit.ts similarity index 97% rename from examples/bitcoin_psbt/src/bitcoin_psbt.ts rename to examples/bitcoin_psbt/src/psbt-non-segwit.ts index 8399e0703b..5d6f48aa39 100644 --- a/examples/bitcoin_psbt/src/bitcoin_psbt.ts +++ b/examples/bitcoin_psbt/src/psbt-non-segwit.ts @@ -207,12 +207,17 @@ function buildPsbtWithFee( const remainingAmount = totalSpent - amount - fee; transaction.addOutput({ address: destAddress, value: Number(amount) }); + newTransaction.addOutput({ address: destAddress, value: Number(amount) }); if (remainingAmount >= dustThreshold) { transaction.addOutput({ address: ownAddress, value: Number(remainingAmount) }); + newTransaction.addOutput({ + address: ownAddress, + value: Number(remainingAmount) + }); } return transaction; diff --git a/examples/bitcoin_psbt/src/psbt.ts b/examples/bitcoin_psbt/src/psbt.ts new file mode 100644 index 0000000000..cca71c1d81 --- /dev/null +++ b/examples/bitcoin_psbt/src/psbt.ts @@ -0,0 +1,289 @@ +//! A demo of a very bare-bones bitcoin "wallet". +//! +//! The wallet here showcases how bitcoin addresses can be be computed +//! and how bitcoin transactions can be signed. It is missing several +//! pieces that any production-grade wallet would have, including: +//! +//! * Support for address types that aren't P2PKH. +//! * Caching spent UTXOs so that they are not reused in future transactions. +//! * Option to set the fee. +import { + BitcoinNetwork, + MillisatoshiPerByte, + Satoshi, + Utxo +} from 'azle/canisters/management'; +import { + payments, + Psbt, + Signer, + SignerAsync, + Transaction +} from 'bitcoinjs-lib'; +import { ValidateSigFunction } from 'bitcoinjs-lib/src/psbt'; +import { Buffer } from 'buffer'; +import { ECPairAPI } from 'ecpair'; + +import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; +import { determineNetwork } from '../../basic_bitcoin/src/bitcoin_wallet'; +import * as ecdsaApi from '../../basic_bitcoin/src/ecdsa_api'; + +/// Sends a transaction to the network that transfers the given amount to the +/// given destination, where the source of the funds is the canister itself +/// at the given derivation path. +export async function send( + network: BitcoinNetwork, + derivationPath: Uint8Array[], + keyName: string, + dstAddress: string, + amount: Satoshi, + ECPair: ECPairAPI +): Promise { + const feePerByte = await calculateFeePerByte(network); + + const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); + const ownAddress = publicKeyToP2wpkhAddress(network, ownPublicKey); + + // Note that pagination may have to be used to get all UTXOs for the given address. + // For the sake of simplicity, it is assumed here that the `utxo` field in the response + // contains all UTXOs. + const ownUtxos = (await bitcoinApi.getUtxos(network, ownAddress.toString())) + .utxos; + + // Build the transaction that sends `amount` to the destination address. + const transaction = await buildPsbt( + ownPublicKey, + ownAddress, + ownUtxos, + dstAddress, + amount, + feePerByte, + network, + ECPair + ); + + // Sign the transaction. + const signer = getSigner(ownPublicKey, keyName, derivationPath); + const validator = getValidator(ECPair); + const signedTransaction = await signPsbt(transaction, signer, validator); + const signedTransactionBytes = signedTransaction.toBuffer(); + console.info( + `Signed transaction: ${signedTransactionBytes.toString('hex')}` + ); + + console.info('Sending transaction...'); + await bitcoinApi.sendTransaction(network, signedTransactionBytes); + console.info('Done'); + + return signedTransaction.getId(); +} + +async function calculateFeePerByte(network: BitcoinNetwork): Promise { + // Get fee percentiles from previous transactions to estimate our own fee. + const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); + + return feePercentiles.length === 0 + ? // There are no fee percentiles. This case can only happen on a regtest + // network where there are no non-coinbase transactions. In this case, + // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) + 2_000n + : // Choose the 50th percentile for sending fees. + feePercentiles[50]; +} + +// Builds a transaction to send the given `amount` of satoshis to the +// destination address. +async function buildPsbt( + ownPublicKey: Uint8Array, + ownAddress: string, + ownUtxos: Utxo[], + dstAddress: string, + amount: Satoshi, + feePerByte: MillisatoshiPerByte, + network: BitcoinNetwork, + ECPair: ECPairAPI +): Promise { + // We have a chicken-and-egg problem where we need to know the length + // of the transaction in order to compute its proper fee, but we need + // to know the proper fee in order to figure out the inputs needed for + // the transaction. + // + // We solve this problem iteratively. We start with a fee of zero, build + // and sign a transaction, see what its size is, and then update the fee, + // rebuild the transaction, until the fee is set to the correct amount. + console.info('Building transaction...'); + let totalFee = 0n; + // eslint-disable-next-line no-constant-condition + while (true) { + let transaction = buildPsbtWithFee( + ownUtxos, + ownAddress, + dstAddress, + amount, + totalFee, + network + ); + + // Sign the transaction. In this case, we only care about the size + // of the signed transaction. + const signer = getMockSigner(ownPublicKey, ECPair); + const validator = getMockValidator(); + const signedTransaction = await signPsbt( + transaction.clone(), + signer, + validator + ); + + const signedTxBytesLen = BigInt(signedTransaction.byteLength()); + + if ((signedTxBytesLen * feePerByte) / 1_000n === totalFee) { + console.info(`Transaction built with fee ${totalFee}.`); + return transaction; + } else { + totalFee = (signedTxBytesLen * feePerByte) / 1_000n; + } + } +} + +function buildPsbtWithFee( + ownUtxos: Utxo[], + ownAddress: string, + destAddress: string, + amount: bigint, + fee: bigint, + network: BitcoinNetwork +): Psbt { + // Assume that any amount below this threshold is dust. + const dustThreshold = 1_000n; + + // Select which UTXOs to spend. We naively spend the oldest available UTXOs, + // even if they were previously spent in a transaction. This isn't a + // problem as long as at most one transaction is created per block and + // we're using min_confirmations of 1. + let utxosToSpend: Utxo[] = []; + let totalSpent = 0n; + for (const utxo of [...ownUtxos].reverse()) { + totalSpent += utxo.value; + utxosToSpend.push(utxo); + if (totalSpent >= amount + fee) { + // We have enough inputs to cover the amount we want to spend. + break; + } + } + + if (totalSpent < amount + fee) { + throw new Error( + `Insufficient balance: ${totalSpent}, trying to transfer ${amount} satoshi with fee ${fee}` + ); + } + + let transaction = new Psbt({ network: determineNetwork(network) }); + transaction.setVersion(1); + + for (const utxo of utxosToSpend) { + transaction.addInput({ + hash: Buffer.from(utxo.outpoint.txid), + index: utxo.outpoint.vout + }); + } + + const remainingAmount = totalSpent - amount - fee; + + transaction.addOutput({ address: destAddress, value: Number(amount) }); + + if (remainingAmount >= dustThreshold) { + transaction.addOutput({ + address: ownAddress, + value: Number(remainingAmount) + }); + } + + return transaction; +} + +// Sign a bitcoin transaction. +// +// IMPORTANT: This method is for demonstration purposes only and it only +// supports signing transactions if: +// +// 1. All the inputs are referencing outpoints that are owned by `own_address`. +// 2. `own_address` is a P2PKH address. +async function signPsbt( + transaction: Psbt, + signer: Signer | SignerAsync, + validator: ValidateSigFunction +): Promise { + await transaction.signAllInputsAsync(signer); + transaction.validateSignaturesOfAllInputs(validator); + transaction.finalizeAllInputs(); + return transaction.extractTransaction(); +} + +function getSigner( + publicKey: Uint8Array, + keyName: string, + derivationPath: Uint8Array[] +): SignerAsync { + return { + sign: async (hashBuffer) => { + const sec1 = await ecdsaApi.signWithECDSA( + keyName, + derivationPath, + Uint8Array.from(hashBuffer) + ); + + return Buffer.from(sec1); + }, + publicKey: Buffer.from(publicKey) + }; +} + +function getMockSigner(ownPublicKey: Uint8Array, ECPair: ECPairAPI): Signer { + const keyPair = ECPair.makeRandom(); + return { + sign: (hashBuffer) => { + return keyPair.sign(hashBuffer); + }, + publicKey: Buffer.from(ownPublicKey) + }; +} + +function getValidator(ECPair: ECPairAPI): ValidateSigFunction { + return (pubkey: Buffer, msghash: Buffer, signature: Buffer): boolean => { + return ECPair.fromPublicKey(pubkey).verify(msghash, signature); + }; +} + +function getMockValidator(): ValidateSigFunction { + return (_pubkey: Buffer, _msghash: Buffer, _signature: Buffer): boolean => { + return true; + }; +} + +/// Returns the P2PKH address of this canister at the given derivation path. +export async function getP2wpkhAddress( + network: BitcoinNetwork, + keyName: string, + derivationPath: Uint8Array[] +): Promise { + // Fetch the public key of the given derivation path. + const publicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); + + // Compute the address. + return publicKeyToP2wpkhAddress(network, publicKey); +} + +// Converts a public key to a P2PKH address. +export function publicKeyToP2wpkhAddress( + network: BitcoinNetwork, + publicKey: Uint8Array +): string { + const { address } = payments.p2wpkh({ + pubkey: Buffer.from(publicKey), + network: determineNetwork(network) + }); + if (address === undefined) { + throw new Error('Unable to get address from the canister'); + } + return address; +} From 9bd8a17e2e486ea119f9be81ad8619342dd21303 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 16 May 2024 15:24:47 -0600 Subject: [PATCH 27/49] WIP checkpoint --- examples/bitcoin_psbt/package-lock.json | 2 +- examples/bitcoin_psbt/src/index.ts | 32 ++++++++++++++----------- examples/bitcoin_psbt/src/psbt.ts | 19 ++++++++++++--- examples/bitcoin_psbt/test/tests.ts | 2 +- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/examples/bitcoin_psbt/package-lock.json b/examples/bitcoin_psbt/package-lock.json index a63a63721f..93413a397e 100644 --- a/examples/bitcoin_psbt/package-lock.json +++ b/examples/bitcoin_psbt/package-lock.json @@ -1,5 +1,5 @@ { - "name": "basic_bitcoin", + "name": "bitcoin_psbt", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts index bd8051f62b..9377638a33 100644 --- a/examples/bitcoin_psbt/src/index.ts +++ b/examples/bitcoin_psbt/src/index.ts @@ -8,7 +8,6 @@ import express, { Request } from 'express'; import { determineKeyName, determineNetwork } from '../../basic_bitcoin/src'; import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; import * as bitcoinPsbt from './psbt'; -import * as bitcoinPsbtNonSegWit from './psbt-non-segwit'; // The bitcoin network to connect to. // @@ -57,19 +56,24 @@ app.get('/get-p2pkh-address', async (req, res) => { /// Sends the given amount of bitcoin from this canister to the given address. /// Returns the transaction ID. app.post('/send', async (req, res) => { - const { transactions, destinationAddress, amountInSatoshi } = req.body; - - const txId = await bitcoinPsbtNonSegWit.send( - NETWORK, - DERIVATION_PATH, - KEY_NAME, - destinationAddress, - BigInt(jsonParse(JSON.stringify(amountInSatoshi))), - transactions, - ECPair - ); - - res.send(txId); + const { destinationAddress, amountInSatoshi } = req.body; + + try { + const txId = await bitcoinPsbt.send( + NETWORK, + DERIVATION_PATH, + KEY_NAME, + destinationAddress, + BigInt(jsonParse(JSON.stringify(amountInSatoshi))), + ECPair + ); + res.send(txId); + } catch (err: any) { + console.log('We had an error'); + console.log(err.message); + console.log(err); + res.send('FAILED TO SEND TRANSACTION'); + } }); /// Returns the UTXOs of the given bitcoin address. diff --git a/examples/bitcoin_psbt/src/psbt.ts b/examples/bitcoin_psbt/src/psbt.ts index cca71c1d81..1ea548af4f 100644 --- a/examples/bitcoin_psbt/src/psbt.ts +++ b/examples/bitcoin_psbt/src/psbt.ts @@ -14,6 +14,7 @@ import { Utxo } from 'azle/canisters/management'; import { + address, payments, Psbt, Signer, @@ -51,6 +52,7 @@ export async function send( .utxos; // Build the transaction that sends `amount` to the destination address. + console.log('here: start build'); const transaction = await buildPsbt( ownPublicKey, ownAddress, @@ -65,8 +67,10 @@ export async function send( // Sign the transaction. const signer = getSigner(ownPublicKey, keyName, derivationPath); const validator = getValidator(ECPair); + console.log('here: about to sign'); const signedTransaction = await signPsbt(transaction, signer, validator); const signedTransactionBytes = signedTransaction.toBuffer(); + console.log('here: finish signing'); console.info( `Signed transaction: ${signedTransactionBytes.toString('hex')}` ); @@ -151,7 +155,7 @@ function buildPsbtWithFee( destAddress: string, amount: bigint, fee: bigint, - network: BitcoinNetwork + bitcoinNetwork: BitcoinNetwork ): Psbt { // Assume that any amount below this threshold is dust. const dustThreshold = 1_000n; @@ -177,13 +181,18 @@ function buildPsbtWithFee( ); } - let transaction = new Psbt({ network: determineNetwork(network) }); + const network = determineNetwork(bitcoinNetwork); + let transaction = new Psbt({ network }); transaction.setVersion(1); for (const utxo of utxosToSpend) { transaction.addInput({ hash: Buffer.from(utxo.outpoint.txid), - index: utxo.outpoint.vout + index: utxo.outpoint.vout, + witnessUtxo: { + script: address.toOutputScript(ownAddress, network), + value: Number(utxo.value) + } }); } @@ -213,9 +222,13 @@ async function signPsbt( signer: Signer | SignerAsync, validator: ValidateSigFunction ): Promise { + console.log('signPsbt: signing all inputs'); await transaction.signAllInputsAsync(signer); + console.log('signPsbt: validating'); transaction.validateSignaturesOfAllInputs(validator); + console.log('signPsbt: finalizing'); transaction.finalizeAllInputs(); + console.log('signPsbt: extracting'); return transaction.extractTransaction(); } diff --git a/examples/bitcoin_psbt/test/tests.ts b/examples/bitcoin_psbt/test/tests.ts index 6cd015e21b..cce6e480a0 100644 --- a/examples/bitcoin_psbt/test/tests.ts +++ b/examples/bitcoin_psbt/test/tests.ts @@ -43,7 +43,7 @@ export function getTests(canisterId: string): Test[] { generateToAddress(address, FIRST_MINING_SESSION); } }, - { name: 'wait for blocks to settle', wait: 60_000 }, + { name: 'wait for blocks to settle', wait: 30_000 }, { name: '/send from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', prep: async () => { From 7b478f1f95d4fcc66b317fec9af64637131569c9 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 16 May 2024 16:15:23 -0600 Subject: [PATCH 28/49] checkpoint --- examples/basic_bitcoin/test/tests.ts | 18 +- examples/bitcoin_psbt/src/index.ts | 2 +- examples/bitcoin_psbt/src/psbt-non-segwit.ts | 283 ------------------- examples/bitcoin_psbt/src/psbt.ts | 14 +- examples/bitcoin_psbt/test/tests.ts | 12 +- 5 files changed, 25 insertions(+), 304 deletions(-) delete mode 100644 examples/bitcoin_psbt/src/psbt-non-segwit.ts diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index e8c77cc7ec..6c385392b5 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -37,7 +37,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-p2pkh-address', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); return { Ok: canisterAddressForm.length === address.length }; } @@ -45,7 +45,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); const balance = await getBalance(origin, address); return compareBalances(0n, balance); @@ -54,7 +54,7 @@ export function getTests(canisterId: string): Test[] { { name: 'first mint BTC', prep: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); generateToAddress(address, FIRST_MINING_SESSION); } }, @@ -62,7 +62,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); const balance = await getBalance(origin, address); return compareBalances( @@ -74,7 +74,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-utxos', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); const response = await fetch( `${origin}/get-utxos?address=${address}`, @@ -153,7 +153,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance final', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); const balance = await getBalance(origin, address); canisterPreviousBalance = balance; @@ -233,7 +233,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance big', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); const balance = await getBalance(origin, address); // At the time this transaction was made, the next utxos to use will be from block rewards. @@ -323,8 +323,8 @@ function getTotalOutput(tx: Transaction): number { }, 0); } -export async function getP2pkhAddress(origin: string): Promise { - const response = await fetch(`${origin}/get-p2pkh-address`, { +export async function getP2wpkhAddress(origin: string): Promise { + const response = await fetch(`${origin}/get-p2wpkh-address`, { headers: [['X-Ic-Force-Update', 'true']] }); return await response.text(); diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts index 9377638a33..6f982674d7 100644 --- a/examples/bitcoin_psbt/src/index.ts +++ b/examples/bitcoin_psbt/src/index.ts @@ -43,7 +43,7 @@ app.get( ); /// Returns the P2PKH address of this canister at a specific derivation path. -app.get('/get-p2pkh-address', async (req, res) => { +app.get('/get-p2wpkh-address', async (req, res) => { const address = await bitcoinPsbt.getP2wpkhAddress( NETWORK, KEY_NAME, diff --git a/examples/bitcoin_psbt/src/psbt-non-segwit.ts b/examples/bitcoin_psbt/src/psbt-non-segwit.ts deleted file mode 100644 index 5d6f48aa39..0000000000 --- a/examples/bitcoin_psbt/src/psbt-non-segwit.ts +++ /dev/null @@ -1,283 +0,0 @@ -//! A demo of a very bare-bones bitcoin "wallet". -//! -//! The wallet here showcases how bitcoin addresses can be be computed -//! and how bitcoin transactions can be signed. It is missing several -//! pieces that any production-grade wallet would have, including: -//! -//! * Support for address types that aren't P2PKH. -//! * Caching spent UTXOs so that they are not reused in future transactions. -//! * Option to set the fee. -import { - BitcoinNetwork, - MillisatoshiPerByte, - Satoshi, - Utxo -} from 'azle/canisters/management'; -import { Psbt, Signer, SignerAsync, Transaction } from 'bitcoinjs-lib'; -import { ValidateSigFunction } from 'bitcoinjs-lib/src/psbt'; -import { Buffer } from 'buffer'; -import { ECPairAPI } from 'ecpair'; - -import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; -import { - determineNetwork, - publicKeyToP2pkhAddress -} from '../../basic_bitcoin/src/bitcoin_wallet'; -import * as ecdsaApi from '../../basic_bitcoin/src/ecdsa_api'; - -type TransactionHashes = { - [txid: string]: string; -}; - -/// Sends a transaction to the network that transfers the given amount to the -/// given destination, where the source of the funds is the canister itself -/// at the given derivation path. -export async function send( - network: BitcoinNetwork, - derivationPath: Uint8Array[], - keyName: string, - dstAddress: string, - amount: Satoshi, - transactionHashes: TransactionHashes, - ECPair: ECPairAPI -): Promise { - const feePerByte = await calculateFeePerByte(network); - - const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); - const ownAddress = publicKeyToP2pkhAddress(network, ownPublicKey); - - // Note that pagination may have to be used to get all UTXOs for the given address. - // For the sake of simplicity, it is assumed here that the `utxo` field in the response - // contains all UTXOs. - const ownUtxos = (await bitcoinApi.getUtxos(network, ownAddress.toString())) - .utxos; - - // Build the transaction that sends `amount` to the destination address. - const transaction = await buildPsbt( - ownPublicKey, - ownAddress, - ownUtxos, - dstAddress, - amount, - feePerByte, - network, - transactionHashes, - ECPair - ); - - // Sign the transaction. - const signer = getSigner(ownPublicKey, keyName, derivationPath); - const validator = getValidator(ECPair); - const signedTransaction = await signPsbt(transaction, signer, validator); - const signedTransactionBytes = signedTransaction.toBuffer(); - console.info( - `Signed transaction: ${signedTransactionBytes.toString('hex')}` - ); - - console.info('Sending transaction...'); - await bitcoinApi.sendTransaction(network, signedTransactionBytes); - console.info('Done'); - - return signedTransaction.getId(); -} - -async function calculateFeePerByte(network: BitcoinNetwork): Promise { - // Get fee percentiles from previous transactions to estimate our own fee. - const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); - - return feePercentiles.length === 0 - ? // There are no fee percentiles. This case can only happen on a regtest - // network where there are no non-coinbase transactions. In this case, - // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) - 2_000n - : // Choose the 50th percentile for sending fees. - feePercentiles[50]; -} - -// Builds a transaction to send the given `amount` of satoshis to the -// destination address. -async function buildPsbt( - ownPublicKey: Uint8Array, - ownAddress: string, - ownUtxos: Utxo[], - dstAddress: string, - amount: Satoshi, - feePerByte: MillisatoshiPerByte, - network: BitcoinNetwork, - transactionHashes: TransactionHashes, - ECPair: ECPairAPI -): Promise { - // We have a chicken-and-egg problem where we need to know the length - // of the transaction in order to compute its proper fee, but we need - // to know the proper fee in order to figure out the inputs needed for - // the transaction. - // - // We solve this problem iteratively. We start with a fee of zero, build - // and sign a transaction, see what its size is, and then update the fee, - // rebuild the transaction, until the fee is set to the correct amount. - console.info('Building transaction...'); - let totalFee = 0n; - // eslint-disable-next-line no-constant-condition - while (true) { - let transaction = buildPsbtWithFee( - ownUtxos, - ownAddress, - dstAddress, - amount, - totalFee, - network, - transactionHashes - ); - - // Sign the transaction. In this case, we only care about the size - // of the signed transaction. - const signer = getMockSigner(ownPublicKey, ECPair); - const validator = getMockValidator(); - const signedTransaction = await signPsbt( - transaction.clone(), - signer, - validator - ); - - const signedTxBytesLen = BigInt(signedTransaction.byteLength()); - - if ((signedTxBytesLen * feePerByte) / 1_000n === totalFee) { - console.info(`Transaction built with fee ${totalFee}.`); - return transaction; - } else { - totalFee = (signedTxBytesLen * feePerByte) / 1_000n; - } - } -} - -function buildPsbtWithFee( - ownUtxos: Utxo[], - ownAddress: string, - destAddress: string, - amount: bigint, - fee: bigint, - network: BitcoinNetwork, - transactionHashes: TransactionHashes -): Psbt { - // Assume that any amount below this threshold is dust. - const dustThreshold = 1_000n; - - // Select which UTXOs to spend. We naively spend the oldest available UTXOs, - // even if they were previously spent in a transaction. This isn't a - // problem as long as at most one transaction is created per block and - // we're using min_confirmations of 1. - let utxosToSpend: Utxo[] = []; - let totalSpent = 0n; - for (const utxo of [...ownUtxos].reverse()) { - totalSpent += utxo.value; - utxosToSpend.push(utxo); - if (totalSpent >= amount + fee) { - // We have enough inputs to cover the amount we want to spend. - break; - } - } - - if (totalSpent < amount + fee) { - throw new Error( - `Insufficient balance: ${totalSpent}, trying to transfer ${amount} satoshi with fee ${fee}` - ); - } - - let transaction = new Psbt({ network: determineNetwork(network) }); - let newTransaction = new Psbt({ network: determineNetwork(network) }); - transaction.setVersion(1); - newTransaction.setVersion(1); - - for (const utxo of utxosToSpend) { - const previousTxidHash = Buffer.from(utxo.outpoint.txid); - const txid = previousTxidHash.reverse().toString('hex'); - const nonWitnessUtxo = Buffer.from(transactionHashes[txid], 'hex'); - transaction.addInput({ - hash: Buffer.from(utxo.outpoint.txid), - index: utxo.outpoint.vout, - nonWitnessUtxo - }); - newTransaction.addInput({ - hash: Buffer.from(utxo.outpoint.txid), - index: utxo.outpoint.vout - }); - // TODO see why these are different - } - - const remainingAmount = totalSpent - amount - fee; - - transaction.addOutput({ address: destAddress, value: Number(amount) }); - newTransaction.addOutput({ address: destAddress, value: Number(amount) }); - - if (remainingAmount >= dustThreshold) { - transaction.addOutput({ - address: ownAddress, - value: Number(remainingAmount) - }); - newTransaction.addOutput({ - address: ownAddress, - value: Number(remainingAmount) - }); - } - - return transaction; -} - -// Sign a bitcoin transaction. -// -// IMPORTANT: This method is for demonstration purposes only and it only -// supports signing transactions if: -// -// 1. All the inputs are referencing outpoints that are owned by `own_address`. -// 2. `own_address` is a P2PKH address. -async function signPsbt( - transaction: Psbt, - signer: Signer | SignerAsync, - validator: ValidateSigFunction -): Promise { - await transaction.signAllInputsAsync(signer); - transaction.validateSignaturesOfAllInputs(validator); - transaction.finalizeAllInputs(); - return transaction.extractTransaction(); -} - -function getSigner( - publicKey: Uint8Array, - keyName: string, - derivationPath: Uint8Array[] -): SignerAsync { - return { - sign: async (hashBuffer) => { - const sec1 = await ecdsaApi.signWithECDSA( - keyName, - derivationPath, - Uint8Array.from(hashBuffer) - ); - - return Buffer.from(sec1); - }, - publicKey: Buffer.from(publicKey) - }; -} - -function getMockSigner(ownPublicKey: Uint8Array, ECPair: ECPairAPI): Signer { - const keyPair = ECPair.makeRandom(); - return { - sign: (hashBuffer) => { - return keyPair.sign(hashBuffer); - }, - publicKey: Buffer.from(ownPublicKey) - }; -} - -function getValidator(ECPair: ECPairAPI): ValidateSigFunction { - return (pubkey: Buffer, msghash: Buffer, signature: Buffer): boolean => { - return ECPair.fromPublicKey(pubkey).verify(msghash, signature); - }; -} - -function getMockValidator(): ValidateSigFunction { - return (_pubkey: Buffer, _msghash: Buffer, _signature: Buffer): boolean => { - return true; - }; -} diff --git a/examples/bitcoin_psbt/src/psbt.ts b/examples/bitcoin_psbt/src/psbt.ts index 1ea548af4f..b078d44230 100644 --- a/examples/bitcoin_psbt/src/psbt.ts +++ b/examples/bitcoin_psbt/src/psbt.ts @@ -7,6 +7,8 @@ //! * Support for address types that aren't P2PKH. //! * Caching spent UTXOs so that they are not reused in future transactions. //! * Option to set the fee. +// import * as ecc from 'tiny-secp256k1/lib/'; // TODO we should switch to this import as soon as we have wasm support +import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; import { BitcoinNetwork, MillisatoshiPerByte, @@ -252,18 +254,20 @@ function getSigner( } function getMockSigner(ownPublicKey: Uint8Array, ECPair: ECPairAPI): Signer { - const keyPair = ECPair.makeRandom(); + const _keyPair = ECPair.makeRandom(); return { - sign: (hashBuffer) => { - return keyPair.sign(hashBuffer); + sign: (_hashBuffer) => { + // return keyPair.sign(hashBuffer); + return Buffer.from(new Array(64).fill(1)); }, publicKey: Buffer.from(ownPublicKey) }; } -function getValidator(ECPair: ECPairAPI): ValidateSigFunction { +function getValidator(_ECPair: ECPairAPI): ValidateSigFunction { return (pubkey: Buffer, msghash: Buffer, signature: Buffer): boolean => { - return ECPair.fromPublicKey(pubkey).verify(msghash, signature); + return ecc.verify(msghash, pubkey, signature); + // return ECPair.fromPublicKey(pubkey).verify(msghash, signature); }; } diff --git a/examples/bitcoin_psbt/test/tests.ts b/examples/bitcoin_psbt/test/tests.ts index cce6e480a0..3f957ad68a 100644 --- a/examples/bitcoin_psbt/test/tests.ts +++ b/examples/bitcoin_psbt/test/tests.ts @@ -13,7 +13,7 @@ import { compareBalances, getBalance, getFeeFromTransaction, - getP2pkhAddress, + getP2wpkhAddress, waitForMempool } from '../../basic_bitcoin/test/tests'; import { getUtxoHashes } from './bitcoin'; @@ -21,7 +21,7 @@ import { getUtxoHashes } from './bitcoin'; const SINGLE_BLOCK_REWARD = 5_000_000_000n; const FIRST_MINING_SESSION = 101; const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; -const TO_ADDRESS = 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD'; // Regtest address from this WIF L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv +const TO_ADDRESS = 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD'; // TODO test other kinds of addresses not just p2pkh let lastTx = ''; @@ -39,13 +39,13 @@ export function getTests(canisterId: string): Test[] { { name: 'first mint BTC', prep: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); generateToAddress(address, FIRST_MINING_SESSION); } }, { name: 'wait for blocks to settle', wait: 30_000 }, { - name: '/send from canister to L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv', + name: `/send from canister to ${TO_ADDRESS}`, prep: async () => { const body = jsonStringify({ transactions: getUtxoHashes(), @@ -73,7 +73,7 @@ export function getTests(canisterId: string): Test[] { }, { name: 'wait for blocks to settle', wait: 15_000 }, { - name: '/get-balance of L3BybjkmnMdXE6iNEaeZTjVMTHA4TvpYbQozc264Lto9yVDis2nv final', + name: `/get-balance of ${TO_ADDRESS} final`, test: async () => { const balance = await getBalance(origin, TO_ADDRESS); @@ -83,7 +83,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance final', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getP2wpkhAddress(origin); const balance = await getBalance(origin, address); const fee = getFeeFromTransaction(lastTx, SINGLE_BLOCK_REWARD); From 86c73db946087ae13aa47493eb7e32d14c8930fc Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 16 May 2024 17:05:36 -0600 Subject: [PATCH 29/49] finish psbt example --- examples/basic_bitcoin/src/bitcoin_wallet.ts | 4 +- examples/bitcoin_psbt/package-lock.json | 48 ----- examples/bitcoin_psbt/package.json | 1 - examples/bitcoin_psbt/src/index.ts | 51 ++---- examples/bitcoin_psbt/src/psbt.ts | 178 ++++++++----------- examples/bitcoin_psbt/test/tests.ts | 11 +- 6 files changed, 105 insertions(+), 188 deletions(-) diff --git a/examples/basic_bitcoin/src/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts index 0402cc31c1..a1af2cd4d8 100644 --- a/examples/basic_bitcoin/src/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/bitcoin_wallet.ts @@ -20,7 +20,7 @@ import { Buffer } from 'buffer'; import * as bitcoinApi from './bitcoin_api'; import * as ecdsaApi from './ecdsa_api'; -type SignFun = ( +export type SignFun = ( keyName: string, derivationPath: Uint8Array[], messageHash: Uint8Array @@ -289,7 +289,7 @@ export function publicKeyToP2pkhAddress( } // A mock for rubber-stamping ECDSA signatures. -function mockSigner( +export function mockSigner( _keyName: string, _derivationPath: Uint8Array[], _messageHash: Uint8Array diff --git a/examples/bitcoin_psbt/package-lock.json b/examples/bitcoin_psbt/package-lock.json index 93413a397e..caf2608171 100644 --- a/examples/bitcoin_psbt/package-lock.json +++ b/examples/bitcoin_psbt/package-lock.json @@ -9,7 +9,6 @@ "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "azle": "^0.21.1", "bitcoinjs-lib": "^6.1.5", - "ecpair": "^2.1.0", "express": "^4.18.2", "tiny-secp256k1": "^2.2.3" }, @@ -1647,19 +1646,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "node_modules/ecpair": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", - "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", - "dependencies": { - "randombytes": "^2.1.0", - "typeforce": "^1.18.0", - "wif": "^2.0.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2990,40 +2976,6 @@ "node": ">= 0.8" } }, - "node_modules/wif": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", - "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", - "dependencies": { - "bs58check": "<3.0.0" - } - }, - "node_modules/wif/node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/wif/node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/wif/node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, "node_modules/ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", diff --git a/examples/bitcoin_psbt/package.json b/examples/bitcoin_psbt/package.json index 47c3e51d78..6791244c20 100644 --- a/examples/bitcoin_psbt/package.json +++ b/examples/bitcoin_psbt/package.json @@ -21,7 +21,6 @@ "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "azle": "^0.21.1", "bitcoinjs-lib": "^6.1.5", - "ecpair": "^2.1.0", "express": "^4.18.2", "tiny-secp256k1": "^2.2.3" }, diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts index 6f982674d7..960be5dd3d 100644 --- a/examples/bitcoin_psbt/src/index.ts +++ b/examples/bitcoin_psbt/src/index.ts @@ -1,8 +1,5 @@ -// import * as ecc from 'tiny-secp256k1/lib/'; // TODO we should switch to this import as soon as we have wasm support -import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; import { jsonParse, jsonStringify } from 'azle'; import { BitcoinNetwork } from 'azle/canisters/management'; -import { ECPairFactory } from 'ecpair'; import express, { Request } from 'express'; import { determineKeyName, determineNetwork } from '../../basic_bitcoin/src'; @@ -28,8 +25,6 @@ const KEY_NAME: string = determineKeyName(NETWORK); const app = express(); -const ECPair = ECPairFactory(ecc); - app.use(express.json()); /// Returns the balance of the given bitcoin address. @@ -42,7 +37,17 @@ app.get( } ); -/// Returns the P2PKH address of this canister at a specific derivation path. +/// Returns the UTXOs of the given bitcoin address. +app.get( + '/get-utxos', + async (req: Request, res) => { + const utxos = await bitcoinApi.getUtxos(NETWORK, req.query.address); + + res.send(jsonStringify(utxos)); + } +); + +/// Returns the P2WPKH address of this canister at a specific derivation path. app.get('/get-p2wpkh-address', async (req, res) => { const address = await bitcoinPsbt.getP2wpkhAddress( NETWORK, @@ -58,32 +63,14 @@ app.get('/get-p2wpkh-address', async (req, res) => { app.post('/send', async (req, res) => { const { destinationAddress, amountInSatoshi } = req.body; - try { - const txId = await bitcoinPsbt.send( - NETWORK, - DERIVATION_PATH, - KEY_NAME, - destinationAddress, - BigInt(jsonParse(JSON.stringify(amountInSatoshi))), - ECPair - ); - res.send(txId); - } catch (err: any) { - console.log('We had an error'); - console.log(err.message); - console.log(err); - res.send('FAILED TO SEND TRANSACTION'); - } + const txId = await bitcoinPsbt.send( + NETWORK, + DERIVATION_PATH, + KEY_NAME, + destinationAddress, + BigInt(jsonParse(JSON.stringify(amountInSatoshi))) + ); + res.send(txId); }); -/// Returns the UTXOs of the given bitcoin address. -app.get( - '/get-utxos', - async (req: Request, res) => { - const utxos = await bitcoinApi.getUtxos(NETWORK, req.query.address); - - res.send(jsonStringify(utxos)); - } -); - app.listen(); diff --git a/examples/bitcoin_psbt/src/psbt.ts b/examples/bitcoin_psbt/src/psbt.ts index b078d44230..fd9a777652 100644 --- a/examples/bitcoin_psbt/src/psbt.ts +++ b/examples/bitcoin_psbt/src/psbt.ts @@ -7,7 +7,7 @@ //! * Support for address types that aren't P2PKH. //! * Caching spent UTXOs so that they are not reused in future transactions. //! * Option to set the fee. -// import * as ecc from 'tiny-secp256k1/lib/'; // TODO we should switch to this import as soon as we have wasm support +// import * as ecc from 'tiny-secp256k1'; // TODO we should switch to this import as soon as we have wasm support import * as ecc from '@bitcoin-js/tiny-secp256k1-asmjs'; import { BitcoinNetwork, @@ -15,22 +15,31 @@ import { Satoshi, Utxo } from 'azle/canisters/management'; -import { - address, - payments, - Psbt, - Signer, - SignerAsync, - Transaction -} from 'bitcoinjs-lib'; +import { address, payments, Psbt, Transaction } from 'bitcoinjs-lib'; import { ValidateSigFunction } from 'bitcoinjs-lib/src/psbt'; import { Buffer } from 'buffer'; -import { ECPairAPI } from 'ecpair'; import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; -import { determineNetwork } from '../../basic_bitcoin/src/bitcoin_wallet'; +import { + determineNetwork, + mockSigner, + SignFun +} from '../../basic_bitcoin/src/bitcoin_wallet'; import * as ecdsaApi from '../../basic_bitcoin/src/ecdsa_api'; +/// Returns the P2PKH address of this canister at the given derivation path. +export async function getP2wpkhAddress( + network: BitcoinNetwork, + keyName: string, + derivationPath: Uint8Array[] +): Promise { + // Fetch the public key of the given derivation path. + const publicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); + + // Compute the address. + return publicKeyToP2wpkhAddress(network, publicKey); +} + /// Sends a transaction to the network that transfers the given amount to the /// given destination, where the source of the funds is the canister itself /// at the given derivation path. @@ -39,10 +48,19 @@ export async function send( derivationPath: Uint8Array[], keyName: string, dstAddress: string, - amount: Satoshi, - ECPair: ECPairAPI + amount: Satoshi ): Promise { - const feePerByte = await calculateFeePerByte(network); + // Get fee percentiles from previous transactions to estimate our own fee. + const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); + + const feePerByte = + feePercentiles.length === 0 + ? // There are no fee percentiles. This case can only happen on a regtest + // network where there are no non-coinbase transactions. In this case, + // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) + 2_000n + : // Choose the 50th percentile for sending fees. + feePercentiles[50]; const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); const ownAddress = publicKeyToP2wpkhAddress(network, ownPublicKey); @@ -54,7 +72,6 @@ export async function send( .utxos; // Build the transaction that sends `amount` to the destination address. - console.log('here: start build'); const transaction = await buildPsbt( ownPublicKey, ownAddress, @@ -62,17 +79,19 @@ export async function send( dstAddress, amount, feePerByte, - network, - ECPair + network ); // Sign the transaction. - const signer = getSigner(ownPublicKey, keyName, derivationPath); - const validator = getValidator(ECPair); - console.log('here: about to sign'); - const signedTransaction = await signPsbt(transaction, signer, validator); + const signedTransaction = await signPsbt( + ownPublicKey, + transaction, + keyName, + derivationPath, + ecdsaApi.signWithECDSA, + validator + ); const signedTransactionBytes = signedTransaction.toBuffer(); - console.log('here: finish signing'); console.info( `Signed transaction: ${signedTransactionBytes.toString('hex')}` ); @@ -84,19 +103,6 @@ export async function send( return signedTransaction.getId(); } -async function calculateFeePerByte(network: BitcoinNetwork): Promise { - // Get fee percentiles from previous transactions to estimate our own fee. - const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(network); - - return feePercentiles.length === 0 - ? // There are no fee percentiles. This case can only happen on a regtest - // network where there are no non-coinbase transactions. In this case, - // we use a default of 2000 millisatoshis/byte (i.e. 2 satoshi/byte) - 2_000n - : // Choose the 50th percentile for sending fees. - feePercentiles[50]; -} - // Builds a transaction to send the given `amount` of satoshis to the // destination address. async function buildPsbt( @@ -106,8 +112,7 @@ async function buildPsbt( dstAddress: string, amount: Satoshi, feePerByte: MillisatoshiPerByte, - network: BitcoinNetwork, - ECPair: ECPairAPI + network: BitcoinNetwork ): Promise { // We have a chicken-and-egg problem where we need to know the length // of the transaction in order to compute its proper fee, but we need @@ -132,12 +137,13 @@ async function buildPsbt( // Sign the transaction. In this case, we only care about the size // of the signed transaction. - const signer = getMockSigner(ownPublicKey, ECPair); - const validator = getMockValidator(); const signedTransaction = await signPsbt( + ownPublicKey, transaction.clone(), - signer, - validator + '', // mock key name + [], // mock derivation path + mockSigner, + mockValidator ); const signedTxBytesLen = BigInt(signedTransaction.byteLength()); @@ -198,10 +204,10 @@ function buildPsbtWithFee( }); } - const remainingAmount = totalSpent - amount - fee; - transaction.addOutput({ address: destAddress, value: Number(amount) }); + const remainingAmount = totalSpent - amount - fee; + if (remainingAmount >= dustThreshold) { transaction.addOutput({ address: ownAddress, @@ -220,28 +226,16 @@ function buildPsbtWithFee( // 1. All the inputs are referencing outpoints that are owned by `own_address`. // 2. `own_address` is a P2PKH address. async function signPsbt( + ownPublicKey: Uint8Array, transaction: Psbt, - signer: Signer | SignerAsync, + keyName: string, + derivationPath: Uint8Array[], + signer: SignFun, validator: ValidateSigFunction ): Promise { - console.log('signPsbt: signing all inputs'); - await transaction.signAllInputsAsync(signer); - console.log('signPsbt: validating'); - transaction.validateSignaturesOfAllInputs(validator); - console.log('signPsbt: finalizing'); - transaction.finalizeAllInputs(); - console.log('signPsbt: extracting'); - return transaction.extractTransaction(); -} - -function getSigner( - publicKey: Uint8Array, - keyName: string, - derivationPath: Uint8Array[] -): SignerAsync { - return { + await transaction.signAllInputsAsync({ sign: async (hashBuffer) => { - const sec1 = await ecdsaApi.signWithECDSA( + const sec1 = await signer( keyName, derivationPath, Uint8Array.from(hashBuffer) @@ -249,49 +243,15 @@ function getSigner( return Buffer.from(sec1); }, - publicKey: Buffer.from(publicKey) - }; -} - -function getMockSigner(ownPublicKey: Uint8Array, ECPair: ECPairAPI): Signer { - const _keyPair = ECPair.makeRandom(); - return { - sign: (_hashBuffer) => { - // return keyPair.sign(hashBuffer); - return Buffer.from(new Array(64).fill(1)); - }, publicKey: Buffer.from(ownPublicKey) - }; -} - -function getValidator(_ECPair: ECPairAPI): ValidateSigFunction { - return (pubkey: Buffer, msghash: Buffer, signature: Buffer): boolean => { - return ecc.verify(msghash, pubkey, signature); - // return ECPair.fromPublicKey(pubkey).verify(msghash, signature); - }; -} - -function getMockValidator(): ValidateSigFunction { - return (_pubkey: Buffer, _msghash: Buffer, _signature: Buffer): boolean => { - return true; - }; -} - -/// Returns the P2PKH address of this canister at the given derivation path. -export async function getP2wpkhAddress( - network: BitcoinNetwork, - keyName: string, - derivationPath: Uint8Array[] -): Promise { - // Fetch the public key of the given derivation path. - const publicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); - - // Compute the address. - return publicKeyToP2wpkhAddress(network, publicKey); + }); + transaction.validateSignaturesOfAllInputs(validator); + transaction.finalizeAllInputs(); + return transaction.extractTransaction(); } -// Converts a public key to a P2PKH address. -export function publicKeyToP2wpkhAddress( +// Converts a public key to a P2WPKH address. +function publicKeyToP2wpkhAddress( network: BitcoinNetwork, publicKey: Uint8Array ): string { @@ -304,3 +264,19 @@ export function publicKeyToP2wpkhAddress( } return address; } + +function validator( + pubkey: Buffer, + msghash: Buffer, + signature: Buffer +): boolean { + return ecc.verify(msghash, pubkey, signature); +} + +function mockValidator( + _pubkey: Buffer, + _msghash: Buffer, + _signature: Buffer +): boolean { + return true; +} diff --git a/examples/bitcoin_psbt/test/tests.ts b/examples/bitcoin_psbt/test/tests.ts index 3f957ad68a..15f5923f6e 100644 --- a/examples/bitcoin_psbt/test/tests.ts +++ b/examples/bitcoin_psbt/test/tests.ts @@ -23,7 +23,7 @@ const FIRST_MINING_SESSION = 101; const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; const TO_ADDRESS = 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD'; // TODO test other kinds of addresses not just p2pkh -let lastTx = ''; +let lastTxid = ''; // TODO adding HD wallets and showing how to use the Derivation path might be nice @@ -57,8 +57,8 @@ export function getTests(canisterId: string): Test[] { headers: { 'Content-Type': 'application/json' }, body }); - lastTx = await response.text(); - console.info(lastTx); + lastTxid = await response.text(); + console.info(lastTxid); } }, { @@ -86,7 +86,10 @@ export function getTests(canisterId: string): Test[] { const address = await getP2wpkhAddress(origin); const balance = await getBalance(origin, address); - const fee = getFeeFromTransaction(lastTx, SINGLE_BLOCK_REWARD); + const fee = getFeeFromTransaction( + lastTxid, + SINGLE_BLOCK_REWARD + ); const blockRewards = SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION); From 507b6d3c8eb61090f30ba330f9ba3a45a8bb925c Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Thu, 16 May 2024 17:10:39 -0600 Subject: [PATCH 30/49] rename to match basic bitcoin --- examples/bitcoin_psbt/src/{psbt.ts => bitcoin_wallet.ts} | 0 examples/bitcoin_psbt/src/index.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/bitcoin_psbt/src/{psbt.ts => bitcoin_wallet.ts} (100%) diff --git a/examples/bitcoin_psbt/src/psbt.ts b/examples/bitcoin_psbt/src/bitcoin_wallet.ts similarity index 100% rename from examples/bitcoin_psbt/src/psbt.ts rename to examples/bitcoin_psbt/src/bitcoin_wallet.ts diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts index 960be5dd3d..b456e74845 100644 --- a/examples/bitcoin_psbt/src/index.ts +++ b/examples/bitcoin_psbt/src/index.ts @@ -4,7 +4,7 @@ import express, { Request } from 'express'; import { determineKeyName, determineNetwork } from '../../basic_bitcoin/src'; import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; -import * as bitcoinPsbt from './psbt'; +import * as bitcoinPsbt from './bitcoin_wallet'; // The bitcoin network to connect to. // From 3c54a430137757599df04d8de60cc600f18d98af Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Fri, 17 May 2024 10:50:42 -0600 Subject: [PATCH 31/49] minor pr fixes --- examples/basic_bitcoin/README.md | 2 +- examples/basic_bitcoin/test/tests.ts | 18 ++-- examples/bitcoin_psbt/README.md | 2 +- examples/bitcoin_psbt/dfx.json | 2 +- examples/bitcoin_psbt/package.json | 2 - ...blockhashes.sh => get_all_block_hashes.sh} | 0 ...ansactionCount.js => transaction_count.js} | 0 examples/bitcoin_psbt/test/bitcoin.ts | 90 ------------------- examples/bitcoin_psbt/test/manual_tests.ts | 2 +- examples/bitcoin_psbt/test/pretest.ts | 2 +- examples/bitcoin_psbt/test/test.ts | 40 +-------- examples/bitcoin_psbt/test/tests.ts | 10 ++- 12 files changed, 23 insertions(+), 147 deletions(-) rename examples/bitcoin_psbt/scripts/bitcoin/{getallblockhashes.sh => get_all_block_hashes.sh} (100%) rename examples/bitcoin_psbt/scripts/bitcoin/{transactionCount.js => transaction_count.js} (100%) delete mode 100644 examples/bitcoin_psbt/test/bitcoin.ts diff --git a/examples/basic_bitcoin/README.md b/examples/basic_bitcoin/README.md index 98f6cdd7c6..afa1355000 100644 --- a/examples/basic_bitcoin/README.md +++ b/examples/basic_bitcoin/README.md @@ -31,7 +31,7 @@ fi dfx start --clean --host 127.0.0.1:8000 ``` -## basic_bitcion +## basic_bitcoin ```bash BITCOIN_NETWORK=regtest dfx deploy' diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 6c385392b5..d3be261397 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -37,7 +37,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-p2pkh-address', test: async () => { - const address = await getP2wpkhAddress(origin); + const address = await getP2pkhAddress(origin); return { Ok: canisterAddressForm.length === address.length }; } @@ -45,7 +45,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance', test: async () => { - const address = await getP2wpkhAddress(origin); + const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); return compareBalances(0n, balance); @@ -54,7 +54,7 @@ export function getTests(canisterId: string): Test[] { { name: 'first mint BTC', prep: async () => { - const address = await getP2wpkhAddress(origin); + const address = await getP2pkhAddress(origin); generateToAddress(address, FIRST_MINING_SESSION); } }, @@ -62,7 +62,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance', test: async () => { - const address = await getP2wpkhAddress(origin); + const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); return compareBalances( @@ -74,7 +74,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-utxos', test: async () => { - const address = await getP2wpkhAddress(origin); + const address = await getP2pkhAddress(origin); const response = await fetch( `${origin}/get-utxos?address=${address}`, @@ -153,7 +153,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance final', test: async () => { - const address = await getP2wpkhAddress(origin); + const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); canisterPreviousBalance = balance; @@ -233,7 +233,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance big', test: async () => { - const address = await getP2wpkhAddress(origin); + const address = await getP2pkhAddress(origin); const balance = await getBalance(origin, address); // At the time this transaction was made, the next utxos to use will be from block rewards. @@ -323,8 +323,8 @@ function getTotalOutput(tx: Transaction): number { }, 0); } -export async function getP2wpkhAddress(origin: string): Promise { - const response = await fetch(`${origin}/get-p2wpkh-address`, { +async function getP2pkhAddress(origin: string): Promise { + const response = await fetch(`${origin}/get-p2pkh-address`, { headers: [['X-Ic-Force-Update', 'true']] }); return await response.text(); diff --git a/examples/bitcoin_psbt/README.md b/examples/bitcoin_psbt/README.md index 98f6cdd7c6..afa1355000 100644 --- a/examples/bitcoin_psbt/README.md +++ b/examples/bitcoin_psbt/README.md @@ -31,7 +31,7 @@ fi dfx start --clean --host 127.0.0.1:8000 ``` -## basic_bitcion +## basic_bitcoin ```bash BITCOIN_NETWORK=regtest dfx deploy' diff --git a/examples/bitcoin_psbt/dfx.json b/examples/bitcoin_psbt/dfx.json index 3fcd94fb20..2cf88aa8c9 100644 --- a/examples/bitcoin_psbt/dfx.json +++ b/examples/bitcoin_psbt/dfx.json @@ -1,6 +1,6 @@ { "canisters": { - "basic_bitcoin": { + "bitcoin_psbt": { "type": "azle", "main": "src/index.ts", "env": ["BITCOIN_NETWORK"] diff --git a/examples/bitcoin_psbt/package.json b/examples/bitcoin_psbt/package.json index 6791244c20..3b93eb4d2d 100644 --- a/examples/bitcoin_psbt/package.json +++ b/examples/bitcoin_psbt/package.json @@ -9,9 +9,7 @@ "getrawmempool": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawmempool true", "getrawtransaction": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawtransaction $npm_config_txid", "help": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf help", - "ic": "dfx start --clean --host 127.0.0.1:8000", "install": "./scripts/install.sh", - "deploy": "dfx deploy basic_bitcoin --specified-id bkyz2-fmaaa-aaaaa-qaaaq-cai", "pretest": "BITCOIN_NETWORK=regtest ts-node --transpile-only --ignore=false test/pretest.ts", "test": "ts-node --transpile-only --ignore=false test/test.ts", "premanualtest": "BITCOIN_NETWORK=regtest ts-node --transpile-only --ignore=false test/pretest.ts", diff --git a/examples/bitcoin_psbt/scripts/bitcoin/getallblockhashes.sh b/examples/bitcoin_psbt/scripts/bitcoin/get_all_block_hashes.sh similarity index 100% rename from examples/bitcoin_psbt/scripts/bitcoin/getallblockhashes.sh rename to examples/bitcoin_psbt/scripts/bitcoin/get_all_block_hashes.sh diff --git a/examples/bitcoin_psbt/scripts/bitcoin/transactionCount.js b/examples/bitcoin_psbt/scripts/bitcoin/transaction_count.js similarity index 100% rename from examples/bitcoin_psbt/scripts/bitcoin/transactionCount.js rename to examples/bitcoin_psbt/scripts/bitcoin/transaction_count.js diff --git a/examples/bitcoin_psbt/test/bitcoin.ts b/examples/bitcoin_psbt/test/bitcoin.ts deleted file mode 100644 index 730d8aac5d..0000000000 --- a/examples/bitcoin_psbt/test/bitcoin.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { jsonParse } from 'azle'; -import { execSync } from 'child_process'; - -type Block = { - hash: string; - confirmations: number; - height: number; - version: number; - versionHex: string; - merkleroot: string; - time: number; - mediantime: number; - nonce: number; - bits: string; - difficulty: number; - chainwork: string; - nTx: number; - previousblockhash: string; - nextblockhash: string; - strippedsize: number; - size: number; - weight: number; - tx: Tx[]; -}; - -type Tx = { - txid: string; - hash: string; - version: number; - size: number; - vsize: number; - weight: number; - locktime: number; - vin: [ - { - coinbase: string; - txinwitness: string[]; - sequence: number; - } - ]; - vout: Vout[]; - hex: string; -}; - -type Vout = { - value: number; - n: number; - scriptPubKey: { - asm: string; - desc: string; - hex: string; - address: string; - type: string; - }; -}; - -type TransactionHashes = { - [txid: string]: string; -}; - -export function getUtxoHashes(): TransactionHashes { - return Array.from({ length: 102 }).reduce((acc, _, blockHeight) => { - const blockHash = getBlockHash(blockHeight); - const block = getBlock(blockHash); - const result = getTransactionHashAndIdFromBlock(block); - return { ...acc, ...result }; - }, {} as TransactionHashes); -} - -function getBlock(hash: string): Block { - const getBlockResult = execSync( - `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblock ${hash} 2` - ) - .toString() - .trim(); - - return jsonParse(getBlockResult); -} - -function getBlockHash(blockIndex: number): string { - return execSync( - `.bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getblockhash ${blockIndex}` - ) - .toString() - .trim(); -} - -function getTransactionHashAndIdFromBlock(block: Block): TransactionHashes { - return { [block.tx[0].txid]: block.tx[0].hex }; -} diff --git a/examples/bitcoin_psbt/test/manual_tests.ts b/examples/bitcoin_psbt/test/manual_tests.ts index b5b87660ab..e86acc05ce 100644 --- a/examples/bitcoin_psbt/test/manual_tests.ts +++ b/examples/bitcoin_psbt/test/manual_tests.ts @@ -3,7 +3,7 @@ import { runTests } from 'azle/test'; import { getTests } from './tests'; -const canisterId = getCanisterId('basic_bitcoin'); +const canisterId = getCanisterId('bitcoin_psbt'); // Allows running of the tests without starting and stopping a Bitcoin daemon // automatically. That is to say you will need to start and stop the Bitcoin diff --git a/examples/bitcoin_psbt/test/pretest.ts b/examples/bitcoin_psbt/test/pretest.ts index 27f9b07e60..f6901018c7 100644 --- a/examples/bitcoin_psbt/test/pretest.ts +++ b/examples/bitcoin_psbt/test/pretest.ts @@ -1,7 +1,7 @@ import { execSync } from 'child_process'; async function pretest() { - execSync(`dfx canister uninstall-code basic_bitcoin || true`, { + execSync(`dfx canister uninstall-code bitcoin_psbt || true`, { stdio: 'inherit' }); diff --git a/examples/bitcoin_psbt/test/test.ts b/examples/bitcoin_psbt/test/test.ts index 7ad3f4a12d..f345a9ec3f 100644 --- a/examples/bitcoin_psbt/test/test.ts +++ b/examples/bitcoin_psbt/test/test.ts @@ -1,45 +1,9 @@ import { getCanisterId } from 'azle/dfx'; +import { whileRunningBitcoinDaemon } from 'azle/examples/basic_bitcoin/test/test'; import { runTests } from 'azle/test'; -import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; -import { existsSync, rmSync } from 'fs-extra'; import { getTests } from './tests'; -const canisterId = getCanisterId('basic_bitcoin'); - -export async function whileRunningBitcoinDaemon( - callback: () => Promise | void -) { - const bitcoinDaemon = await startBitcoinDaemon(); - await callback(); - bitcoinDaemon.kill(); -} - -async function startBitcoinDaemon(): Promise { - if (existsSync(`.bitcoin/regtest`)) { - rmSync('.bitcoin/regtest', { recursive: true, force: true }); - } - const bitcoinDaemon = spawn('.bitcoin/bin/bitcoind', [ - `-conf=${process.cwd()}/.bitcoin.conf`, - `-datadir=${process.cwd()}/.bitcoin`, - '--port=18444' - ]); - - process.on('uncaughtException', () => { - if (!bitcoinDaemon.killed) { - bitcoinDaemon.kill(); - } - }); - - process.on('exit', () => { - if (!bitcoinDaemon.killed) { - bitcoinDaemon.kill(); - } - }); - - console.info(`starting bitcoind...`); - await new Promise((resolve) => setTimeout(resolve, 5000)); - return bitcoinDaemon; -} +const canisterId = getCanisterId('bitcoin_psbt'); whileRunningBitcoinDaemon(() => runTests(getTests(canisterId))); diff --git a/examples/bitcoin_psbt/test/tests.ts b/examples/bitcoin_psbt/test/tests.ts index 15f5923f6e..53d2338468 100644 --- a/examples/bitcoin_psbt/test/tests.ts +++ b/examples/bitcoin_psbt/test/tests.ts @@ -13,10 +13,8 @@ import { compareBalances, getBalance, getFeeFromTransaction, - getP2wpkhAddress, waitForMempool } from '../../basic_bitcoin/test/tests'; -import { getUtxoHashes } from './bitcoin'; const SINGLE_BLOCK_REWARD = 5_000_000_000n; const FIRST_MINING_SESSION = 101; @@ -48,7 +46,6 @@ export function getTests(canisterId: string): Test[] { name: `/send from canister to ${TO_ADDRESS}`, prep: async () => { const body = jsonStringify({ - transactions: getUtxoHashes(), amountInSatoshi: FIRST_AMOUNT_SENT, destinationAddress: TO_ADDRESS }); @@ -101,3 +98,10 @@ export function getTests(canisterId: string): Test[] { } ]; } + +export async function getP2wpkhAddress(origin: string): Promise { + const response = await fetch(`${origin}/get-p2wpkh-address`, { + headers: [['X-Ic-Force-Update', 'true']] + }); + return await response.text(); +} From 6aaabbde8632ff91ac9d7778cfff05817d376ae6 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Fri, 17 May 2024 12:49:26 -0600 Subject: [PATCH 32/49] reuse tests from basic bitcoin --- examples/basic_bitcoin/test/bitcoin-daemon.ts | 38 +++++++ examples/basic_bitcoin/test/test.ts | 45 +------- examples/basic_bitcoin/test/tests.ts | 28 +++-- examples/bitcoin_psbt/src/index.ts | 8 ++ .../test/{manual_tests.ts => manual_test.ts} | 5 +- examples/bitcoin_psbt/test/pretest.ts | 2 +- examples/bitcoin_psbt/test/test.ts | 20 +++- examples/bitcoin_psbt/test/tests.ts | 107 ------------------ 8 files changed, 89 insertions(+), 164 deletions(-) create mode 100644 examples/basic_bitcoin/test/bitcoin-daemon.ts rename examples/bitcoin_psbt/test/{manual_tests.ts => manual_test.ts} (66%) delete mode 100644 examples/bitcoin_psbt/test/tests.ts diff --git a/examples/basic_bitcoin/test/bitcoin-daemon.ts b/examples/basic_bitcoin/test/bitcoin-daemon.ts new file mode 100644 index 0000000000..104133e205 --- /dev/null +++ b/examples/basic_bitcoin/test/bitcoin-daemon.ts @@ -0,0 +1,38 @@ +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; +import { existsSync, rmSync } from 'fs-extra'; + +export async function whileRunningBitcoinDaemon( + callback: () => Promise | void +) { + const bitcoinDaemon = await startBitcoinDaemon(); + await callback(); + bitcoinDaemon.kill(); +} + +async function startBitcoinDaemon(): Promise { + if (existsSync(`.bitcoin/data/regtest`)) { + rmSync('.bitcoin/data/regtest', { recursive: true, force: true }); + } + const bitcoinDaemon = spawn('.bitcoin/bin/bitcoind', [ + `-conf=${process.cwd()}/.bitcoin.conf`, + `-datadir=${process.cwd()}/.bitcoin/data`, + '--port=18444' + ]); + + process.on('uncaughtException', () => { + if (!bitcoinDaemon.killed) { + bitcoinDaemon.kill(); + } + }); + + process.on('exit', () => { + if (!bitcoinDaemon.killed) { + bitcoinDaemon.kill(); + } + }); + + console.info(`starting bitcoind...`); + // This await is necessary to ensure the daemon is running + await new Promise((resolve) => setTimeout(resolve, 5000)); + return bitcoinDaemon; +} diff --git a/examples/basic_bitcoin/test/test.ts b/examples/basic_bitcoin/test/test.ts index 34b64e1d11..d33d59881c 100644 --- a/examples/basic_bitcoin/test/test.ts +++ b/examples/basic_bitcoin/test/test.ts @@ -1,46 +1,11 @@ import { getCanisterId } from 'azle/dfx'; import { runTests } from 'azle/test'; -import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; -import { existsSync, rmSync } from 'fs-extra'; -import { getTests } from './tests'; +import { whileRunningBitcoinDaemon } from './bitcoin-daemon'; +import { getP2pkhAddress, getTests, P2PKH_ADDRESS_FORM } from './tests'; const canisterId = getCanisterId('basic_bitcoin'); -export async function whileRunningBitcoinDaemon( - callback: () => Promise | void -) { - const bitcoinDaemon = await startBitcoinDaemon(); - await callback(); - bitcoinDaemon.kill(); -} - -async function startBitcoinDaemon(): Promise { - if (existsSync(`.bitcoin/data/regtest`)) { - rmSync('.bitcoin/data/regtest', { recursive: true, force: true }); - } - const bitcoinDaemon = spawn('.bitcoin/bin/bitcoind', [ - `-conf=${process.cwd()}/.bitcoin.conf`, - `-datadir=${process.cwd()}/.bitcoin/data`, - '--port=18444' - ]); - - process.on('uncaughtException', () => { - if (!bitcoinDaemon.killed) { - bitcoinDaemon.kill(); - } - }); - - process.on('exit', () => { - if (!bitcoinDaemon.killed) { - bitcoinDaemon.kill(); - } - }); - - console.info(`starting bitcoind...`); - // This await is necessary to ensure the daemon is running - await new Promise((resolve) => setTimeout(resolve, 5000)); - return bitcoinDaemon; -} - -whileRunningBitcoinDaemon(() => runTests(getTests(canisterId))); +whileRunningBitcoinDaemon(() => + runTests(getTests(canisterId, getP2pkhAddress, P2PKH_ADDRESS_FORM)) +); diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index d3be261397..2417e9b399 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -14,6 +14,7 @@ import { getTransaction } from './bitcoin'; +export const P2PKH_ADDRESS_FORM = 'mhVmPSYFraAYnA4ZP6KUx41P3dKgAg27Cm'; // p2pkh-address on the regtest will generally be of this form, starting with m or n and this many characters. const SINGLE_BLOCK_REWARD = 5_000_000_000n; const FIRST_MINING_SESSION = 101; const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; @@ -24,9 +25,14 @@ let lastTxid = ''; let toAddressPreviousBalance = 0n; let canisterPreviousBalance = 0n; -export function getTests(canisterId: string): Test[] { +export type AddressFunc = (origin: string) => Promise; + +export function getTests( + canisterId: string, + getAddress: AddressFunc, + addressForm: string +): Test[] { const origin = `http://${canisterId}.localhost:8000`; - const canisterAddressForm = 'mhVmPSYFraAYnA4ZP6KUx41P3dKgAg27Cm'; // p2pkh-address on the regtest will generally be of this form, starting with m or n and this many characters. return [ { name: 'Set up minting wallet', @@ -37,15 +43,15 @@ export function getTests(canisterId: string): Test[] { { name: '/get-p2pkh-address', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getAddress(origin); - return { Ok: canisterAddressForm.length === address.length }; + return { Ok: addressForm.length === address.length }; } }, { name: '/get-balance', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getAddress(origin); const balance = await getBalance(origin, address); return compareBalances(0n, balance); @@ -54,7 +60,7 @@ export function getTests(canisterId: string): Test[] { { name: 'first mint BTC', prep: async () => { - const address = await getP2pkhAddress(origin); + const address = await getAddress(origin); generateToAddress(address, FIRST_MINING_SESSION); } }, @@ -62,7 +68,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getAddress(origin); const balance = await getBalance(origin, address); return compareBalances( @@ -74,7 +80,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-utxos', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getAddress(origin); const response = await fetch( `${origin}/get-utxos?address=${address}`, @@ -153,7 +159,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance final', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getAddress(origin); const balance = await getBalance(origin, address); canisterPreviousBalance = balance; @@ -233,7 +239,7 @@ export function getTests(canisterId: string): Test[] { { name: '/get-balance big', test: async () => { - const address = await getP2pkhAddress(origin); + const address = await getAddress(origin); const balance = await getBalance(origin, address); // At the time this transaction was made, the next utxos to use will be from block rewards. @@ -323,7 +329,7 @@ function getTotalOutput(tx: Transaction): number { }, 0); } -async function getP2pkhAddress(origin: string): Promise { +export async function getP2pkhAddress(origin: string): Promise { const response = await fetch(`${origin}/get-p2pkh-address`, { headers: [['X-Ic-Force-Update', 'true']] }); diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts index b456e74845..d82d5f28d4 100644 --- a/examples/bitcoin_psbt/src/index.ts +++ b/examples/bitcoin_psbt/src/index.ts @@ -47,6 +47,14 @@ app.get( } ); +/// Returns the 100 fee percentiles measured in millisatoshi/byte. +/// Percentiles are computed from the last 10,000 transactions (if available). +app.get('/get-current-fee-percentiles', async (req, res) => { + const feePercentiles = await bitcoinApi.getCurrentFeePercentiles(NETWORK); + + res.send(jsonStringify(feePercentiles)); +}); + /// Returns the P2WPKH address of this canister at a specific derivation path. app.get('/get-p2wpkh-address', async (req, res) => { const address = await bitcoinPsbt.getP2wpkhAddress( diff --git a/examples/bitcoin_psbt/test/manual_tests.ts b/examples/bitcoin_psbt/test/manual_test.ts similarity index 66% rename from examples/bitcoin_psbt/test/manual_tests.ts rename to examples/bitcoin_psbt/test/manual_test.ts index e86acc05ce..9521bef1be 100644 --- a/examples/bitcoin_psbt/test/manual_tests.ts +++ b/examples/bitcoin_psbt/test/manual_test.ts @@ -1,7 +1,8 @@ import { getCanisterId } from 'azle/dfx'; +import { getTests } from 'azle/examples/basic_bitcoin/test/tests'; import { runTests } from 'azle/test'; -import { getTests } from './tests'; +import { getP2wpkhAddress, P2WPKH_ADDRESS_FORM } from './test'; const canisterId = getCanisterId('bitcoin_psbt'); @@ -9,4 +10,4 @@ const canisterId = getCanisterId('bitcoin_psbt'); // automatically. That is to say you will need to start and stop the Bitcoin // daemon manually. Great for running cli commands after or during the tests to // check the state of the test network -runTests(getTests(canisterId)); +runTests(getTests(canisterId, getP2wpkhAddress, P2WPKH_ADDRESS_FORM)); diff --git a/examples/bitcoin_psbt/test/pretest.ts b/examples/bitcoin_psbt/test/pretest.ts index f6901018c7..b12e17f7d4 100644 --- a/examples/bitcoin_psbt/test/pretest.ts +++ b/examples/bitcoin_psbt/test/pretest.ts @@ -5,7 +5,7 @@ async function pretest() { stdio: 'inherit' }); - execSync(`npm run deploy`, { + execSync(`BITCOIN_NETWORK=regtest dfx deploy`, { stdio: 'inherit' }); } diff --git a/examples/bitcoin_psbt/test/test.ts b/examples/bitcoin_psbt/test/test.ts index f345a9ec3f..1d8134ca56 100644 --- a/examples/bitcoin_psbt/test/test.ts +++ b/examples/bitcoin_psbt/test/test.ts @@ -1,9 +1,23 @@ +import * as dns from 'node:dns'; +dns.setDefaultResultOrder('ipv4first'); + import { getCanisterId } from 'azle/dfx'; -import { whileRunningBitcoinDaemon } from 'azle/examples/basic_bitcoin/test/test'; +import { whileRunningBitcoinDaemon } from 'azle/examples/basic_bitcoin/test/bitcoin-daemon'; +import { getTests } from 'azle/examples/basic_bitcoin/test/tests'; import { runTests } from 'azle/test'; -import { getTests } from './tests'; +export const P2WPKH_ADDRESS_FORM = + 'bcrt1qcxzt0xpf8qe3q75rsd30thrhgwzddy85f0cu4w'; + +export async function getP2wpkhAddress(origin: string): Promise { + const response = await fetch(`${origin}/get-p2wpkh-address`, { + headers: [['X-Ic-Force-Update', 'true']] + }); + return await response.text(); +} const canisterId = getCanisterId('bitcoin_psbt'); -whileRunningBitcoinDaemon(() => runTests(getTests(canisterId))); +whileRunningBitcoinDaemon(() => + runTests(getTests(canisterId, getP2wpkhAddress, P2WPKH_ADDRESS_FORM)) +); diff --git a/examples/bitcoin_psbt/test/tests.ts b/examples/bitcoin_psbt/test/tests.ts deleted file mode 100644 index 53d2338468..0000000000 --- a/examples/bitcoin_psbt/test/tests.ts +++ /dev/null @@ -1,107 +0,0 @@ -import * as dns from 'node:dns'; -dns.setDefaultResultOrder('ipv4first'); - -import { jsonStringify } from 'azle'; -import { Test } from 'azle/test'; - -import { - createWallet, - generate, - generateToAddress -} from '../../basic_bitcoin/test/bitcoin'; -import { - compareBalances, - getBalance, - getFeeFromTransaction, - waitForMempool -} from '../../basic_bitcoin/test/tests'; - -const SINGLE_BLOCK_REWARD = 5_000_000_000n; -const FIRST_MINING_SESSION = 101; -const FIRST_AMOUNT_SENT = SINGLE_BLOCK_REWARD / 2n; -const TO_ADDRESS = 'n4HY51WrdxATGEPqYvoNkEsTteRfuRMxpD'; // TODO test other kinds of addresses not just p2pkh - -let lastTxid = ''; - -// TODO adding HD wallets and showing how to use the Derivation path might be nice - -export function getTests(canisterId: string): Test[] { - const origin = `http://${canisterId}.localhost:8000`; - return [ - { - name: 'Set up minting wallet', - prep: async () => { - createWallet('minty'); - } - }, - { - name: 'first mint BTC', - prep: async () => { - const address = await getP2wpkhAddress(origin); - generateToAddress(address, FIRST_MINING_SESSION); - } - }, - { name: 'wait for blocks to settle', wait: 30_000 }, - { - name: `/send from canister to ${TO_ADDRESS}`, - prep: async () => { - const body = jsonStringify({ - amountInSatoshi: FIRST_AMOUNT_SENT, - destinationAddress: TO_ADDRESS - }); - const response = await fetch(`${origin}/send`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body - }); - lastTxid = await response.text(); - console.info(lastTxid); - } - }, - { - name: 'wait for transaction to be added to mempool', - prep: waitForMempool - }, - { - name: 'second mint BTC', - prep: async () => { - generate(1); - } - }, - { name: 'wait for blocks to settle', wait: 15_000 }, - { - name: `/get-balance of ${TO_ADDRESS} final`, - test: async () => { - const balance = await getBalance(origin, TO_ADDRESS); - - return compareBalances(FIRST_AMOUNT_SENT, balance); - } - }, - { - name: '/get-balance final', - test: async () => { - const address = await getP2wpkhAddress(origin); - const balance = await getBalance(origin, address); - - const fee = getFeeFromTransaction( - lastTxid, - SINGLE_BLOCK_REWARD - ); - - const blockRewards = - SINGLE_BLOCK_REWARD * BigInt(FIRST_MINING_SESSION); - - const expectedBalance = blockRewards - FIRST_AMOUNT_SENT - fee; - - return compareBalances(expectedBalance, balance); - } - } - ]; -} - -export async function getP2wpkhAddress(origin: string): Promise { - const response = await fetch(`${origin}/get-p2wpkh-address`, { - headers: [['X-Ic-Force-Update', 'true']] - }); - return await response.text(); -} From 5da5b7edbbc3fb4a81d16206c20c09c07f5c4e55 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Fri, 17 May 2024 13:26:44 -0600 Subject: [PATCH 33/49] make more consistent with basic bitcoin --- examples/basic_bitcoin/src/bitcoin_wallet.ts | 2 +- examples/basic_bitcoin/tsconfig.json | 4 ++-- examples/bitcoin_psbt/package.json | 8 ++++---- examples/bitcoin_psbt/src/bitcoin_wallet.ts | 21 +++++++++++--------- examples/bitcoin_psbt/src/index.ts | 7 ++++--- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/examples/basic_bitcoin/src/bitcoin_wallet.ts b/examples/basic_bitcoin/src/bitcoin_wallet.ts index a1af2cd4d8..467ea48224 100644 --- a/examples/basic_bitcoin/src/bitcoin_wallet.ts +++ b/examples/basic_bitcoin/src/bitcoin_wallet.ts @@ -274,7 +274,7 @@ async function signTransaction( } // Converts a public key to a P2PKH address. -export function publicKeyToP2pkhAddress( +function publicKeyToP2pkhAddress( network: BitcoinNetwork, publicKey: Uint8Array ): string { diff --git a/examples/basic_bitcoin/tsconfig.json b/examples/basic_bitcoin/tsconfig.json index 552e5f0cbd..0817cb3fc1 100644 --- a/examples/basic_bitcoin/tsconfig.json +++ b/examples/basic_bitcoin/tsconfig.json @@ -4,7 +4,7 @@ "target": "ES2020", "moduleResolution": "node", "allowJs": true, - "allowSyntheticDefaultImports": true, - "outDir": "HACK_BECAUSE_OF_ALLOW_JS" + "outDir": "HACK_BECAUSE_OF_ALLOW_JS", + "allowSyntheticDefaultImports": true } } diff --git a/examples/bitcoin_psbt/package.json b/examples/bitcoin_psbt/package.json index 3b93eb4d2d..eb52787c22 100644 --- a/examples/bitcoin_psbt/package.json +++ b/examples/bitcoin_psbt/package.json @@ -9,11 +9,11 @@ "getrawmempool": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawmempool true", "getrawtransaction": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf getrawtransaction $npm_config_txid", "help": ".bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf help", + "premanualtest": "ts-node --transpile-only --ignore=false test/pretest.ts", + "manualtest": "ts-node --transpile-only --ignore=false test/test.ts", "install": "./scripts/install.sh", - "pretest": "BITCOIN_NETWORK=regtest ts-node --transpile-only --ignore=false test/pretest.ts", - "test": "ts-node --transpile-only --ignore=false test/test.ts", - "premanualtest": "BITCOIN_NETWORK=regtest ts-node --transpile-only --ignore=false test/pretest.ts", - "manualtest": "ts-node --transpile-only --ignore=false test/test.ts" + "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", + "test": "ts-node --transpile-only --ignore=false test/test.ts" }, "dependencies": { "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", diff --git a/examples/bitcoin_psbt/src/bitcoin_wallet.ts b/examples/bitcoin_psbt/src/bitcoin_wallet.ts index fd9a777652..9868026657 100644 --- a/examples/bitcoin_psbt/src/bitcoin_wallet.ts +++ b/examples/bitcoin_psbt/src/bitcoin_wallet.ts @@ -62,9 +62,11 @@ export async function send( : // Choose the 50th percentile for sending fees. feePercentiles[50]; + // Fetch our public key, P2PKH address, and UTXOs. const ownPublicKey = await ecdsaApi.ecdsaPublicKey(keyName, derivationPath); const ownAddress = publicKeyToP2wpkhAddress(network, ownPublicKey); + console.info('Fetching UTXOs...'); // Note that pagination may have to be used to get all UTXOs for the given address. // For the sake of simplicity, it is assumed here that the `utxo` field in the response // contains all UTXOs. @@ -72,7 +74,7 @@ export async function send( .utxos; // Build the transaction that sends `amount` to the destination address. - const transaction = await buildPsbt( + const transaction = await buildTransaction( ownPublicKey, ownAddress, ownUtxos, @@ -82,8 +84,9 @@ export async function send( network ); - // Sign the transaction. - const signedTransaction = await signPsbt( + console.info(`Transaction to sign: ${transaction.toHex()}`); + + const signedTransaction = await signTransaction( ownPublicKey, transaction, keyName, @@ -91,6 +94,7 @@ export async function send( ecdsaApi.signWithECDSA, validator ); + const signedTransactionBytes = signedTransaction.toBuffer(); console.info( `Signed transaction: ${signedTransactionBytes.toString('hex')}` @@ -105,7 +109,7 @@ export async function send( // Builds a transaction to send the given `amount` of satoshis to the // destination address. -async function buildPsbt( +async function buildTransaction( ownPublicKey: Uint8Array, ownAddress: string, ownUtxos: Utxo[], @@ -126,7 +130,7 @@ async function buildPsbt( let totalFee = 0n; // eslint-disable-next-line no-constant-condition while (true) { - let transaction = buildPsbtWithFee( + const transaction = buildTransactionWithFee( ownUtxos, ownAddress, dstAddress, @@ -137,7 +141,7 @@ async function buildPsbt( // Sign the transaction. In this case, we only care about the size // of the signed transaction. - const signedTransaction = await signPsbt( + const signedTransaction = await signTransaction( ownPublicKey, transaction.clone(), '', // mock key name @@ -157,7 +161,7 @@ async function buildPsbt( } } -function buildPsbtWithFee( +function buildTransactionWithFee( ownUtxos: Utxo[], ownAddress: string, destAddress: string, @@ -191,7 +195,6 @@ function buildPsbtWithFee( const network = determineNetwork(bitcoinNetwork); let transaction = new Psbt({ network }); - transaction.setVersion(1); for (const utxo of utxosToSpend) { transaction.addInput({ @@ -225,7 +228,7 @@ function buildPsbtWithFee( // // 1. All the inputs are referencing outpoints that are owned by `own_address`. // 2. `own_address` is a P2PKH address. -async function signPsbt( +async function signTransaction( ownPublicKey: Uint8Array, transaction: Psbt, keyName: string, diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts index d82d5f28d4..e57a457135 100644 --- a/examples/bitcoin_psbt/src/index.ts +++ b/examples/bitcoin_psbt/src/index.ts @@ -4,7 +4,7 @@ import express, { Request } from 'express'; import { determineKeyName, determineNetwork } from '../../basic_bitcoin/src'; import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; -import * as bitcoinPsbt from './bitcoin_wallet'; +import * as bitcoinWallet from './bitcoin_wallet'; // The bitcoin network to connect to. // @@ -57,7 +57,7 @@ app.get('/get-current-fee-percentiles', async (req, res) => { /// Returns the P2WPKH address of this canister at a specific derivation path. app.get('/get-p2wpkh-address', async (req, res) => { - const address = await bitcoinPsbt.getP2wpkhAddress( + const address = await bitcoinWallet.getP2wpkhAddress( NETWORK, KEY_NAME, DERIVATION_PATH @@ -71,13 +71,14 @@ app.get('/get-p2wpkh-address', async (req, res) => { app.post('/send', async (req, res) => { const { destinationAddress, amountInSatoshi } = req.body; - const txId = await bitcoinPsbt.send( + const txId = await bitcoinWallet.send( NETWORK, DERIVATION_PATH, KEY_NAME, destinationAddress, BigInt(jsonParse(JSON.stringify(amountInSatoshi))) ); + res.send(txId); }); From 51e264ac9b243b1562c75fc9b07ef1c46453197c Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Fri, 17 May 2024 13:42:00 -0600 Subject: [PATCH 34/49] update readmes --- examples/basic_bitcoin/README.md | 2 +- examples/bitcoin_psbt/README.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/basic_bitcoin/README.md b/examples/basic_bitcoin/README.md index afa1355000..d2ddd21241 100644 --- a/examples/basic_bitcoin/README.md +++ b/examples/basic_bitcoin/README.md @@ -57,7 +57,7 @@ You can now use the `send` function to try sending some BTC to another address, .bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf generatetoaddress 1 ``` -You should see some output such as `2023-05-30T20:33:25Z CreateNewBlock(): block weight: 1804 txs: 1 fees: 454 sigops 408` in your Bitcoin node's terminal indicating that your transaction was included in the block. +You should see some output such as `2023-05-30T20:33:25Z CreateNewBlock(): block weight: 1804 txs: 1 fees: 454 sigops 408` in your Bitcoin node's terminal (not the dfx terminal) indicating that your transaction was included in the block. Now if you call the functions with the `n2dcQfuwFw7M2UYzLfM6P7DwewsQaygb8S` address you should see a new balance, utxos, and fee percentiles. diff --git a/examples/bitcoin_psbt/README.md b/examples/bitcoin_psbt/README.md index afa1355000..2a4fd4d3d8 100644 --- a/examples/bitcoin_psbt/README.md +++ b/examples/bitcoin_psbt/README.md @@ -1,6 +1,6 @@ # Installation -This is an implementation of the [basic_bitcoin example](https://github.com/dfinity/examples/tree/master/rust/basic_bitcoin) written in TypeScript with Azle. It's focused on showing you basic ICP/Bitcoin concepts in a local development environment. +This is a variation of the [basic_bitcoin example](https://github.com/dfinity/examples/tree/master/rust/basic_bitcoin) written in TypeScript with Azle. It's focused on showing you basic ICP/Bitcoin concepts in a local development environment. The principal difference between this and the [Azle basic_bitcoin example](https://github.com/demergent-labs/azle/tree/main/examples/basic_bitcoin) is that this example is demonstrating the use of partially signed bitcoin transactions (PSBT) and SegWit. This example also provides additional helper scripts for working with a local bitcoin node. ## bitcoind @@ -41,7 +41,7 @@ BITCOIN_NETWORK=regtest dfx deploy' After setting up your local Bitcoin node and deploying the canister locally, you can interact with your canister from the command-line or from the browser. To interact with your canister from the browser, use the URL displayed in the terminal after deploy, which will look something like this: `http://127.0.0.1:8000/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai` -This example provides 5 functions to call: `getBalance`, `getUtxos`, `getCurrentFeePercentiles`, `getP2PKHAddress`, and `send`. +This example provides 5 functions to call: `getBalance`, `getUtxos`, `getCurrentFeePercentiles`, `getP2WPKHAddress`, and `send`. To create Bitcoin that can be used to test the `send` functionality, first mine 101 blocks to the address you obtain from calling `getP2PKHAddress`. You must mine 101 blocks because there is a rule in Bitcoin that block rewards cannot be spent until 100 blocks have been mined on top: @@ -51,15 +51,15 @@ To create Bitcoin that can be used to test the `send` functionality, first mine Give this process a minute or two before expecting the balance of your address to update properly. -You can now use the `send` function to try sending some BTC to another address, for example to the address `n2dcQfuwFw7M2UYzLfM6P7DwewsQaygb8S`. After calling the `send` function you should receive a transaction hash such as `44fdde20b88027e6a8786689739b0fec74f9e6c86cad3f4d5c05e43a51d97445`. To make sure the transaction is included into the Bitcoin blockchain, you must mine a new block: +You can now use the `send` function to try sending some BTC to another address, for example to the address `bcrt1qcxzt0xpf8qe3q75rsd30thrhgwzddy85f0cu4w`. After calling the `send` function you should receive a transaction hash such as `44fdde20b88027e6a8786689739b0fec74f9e6c86cad3f4d5c05e43a51d97445`. To make sure the transaction is included into the Bitcoin blockchain, you must mine a new block: ```bash .bitcoin/bin/bitcoin-cli -conf=$(pwd)/.bitcoin.conf generatetoaddress 1 ``` -You should see some output such as `2023-05-30T20:33:25Z CreateNewBlock(): block weight: 1804 txs: 1 fees: 454 sigops 408` in your Bitcoin node's terminal indicating that your transaction was included in the block. +You should see some output such as `2023-05-30T20:33:25Z CreateNewBlock(): block weight: 1804 txs: 1 fees: 454 sigops 408` in your Bitcoin node's terminal (not the dfx terminal) indicating that your transaction was included in the block. -Now if you call the functions with the `n2dcQfuwFw7M2UYzLfM6P7DwewsQaygb8S` address you should see a new balance, utxos, and fee percentiles. +Now if you call the functions with the `bcrt1qcxzt0xpf8qe3q75rsd30thrhgwzddy85f0cu4w` address you should see a new balance, utxos, and fee percentiles. If you need more information you can view the following: From c9e9160eb992d9c0f759e6ff7592aa822de051b9 Mon Sep 17 00:00:00 2001 From: sudoshreyansh Date: Sat, 18 May 2024 14:42:12 +0530 Subject: [PATCH 35/49] add regtest to bitcoin networks --- canisters/management/ic.did | 1 + 1 file changed, 1 insertion(+) diff --git a/canisters/management/ic.did b/canisters/management/ic.did index 3aae0f5f20..28b974ffa1 100644 --- a/canisters/management/ic.did +++ b/canisters/management/ic.did @@ -88,6 +88,7 @@ type satoshi = nat64; type bitcoin_network = variant { mainnet; testnet; + regtest; }; type bitcoin_address = text; From 0b65baac481c5575b30ffc1f718c706817f02a66 Mon Sep 17 00:00:00 2001 From: sudoshreyansh Date: Sat, 18 May 2024 15:17:15 +0530 Subject: [PATCH 36/49] add requested changes --- examples/management_canister/src/index.ts | 6 +++--- examples/management_canister/test/tests.ts | 4 ++-- .../reference/management_canister/clear_chunk_store.md | 2 +- .../management_canister/install_chunked_code.md | 2 +- .../src/reference/management_canister/stored_chunks.md | 7 +++---- .../src/reference/management_canister/upload_chunk.md | 9 ++++----- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/examples/management_canister/src/index.ts b/examples/management_canister/src/index.ts index ff3700bcd3..ed73d76663 100644 --- a/examples/management_canister/src/index.ts +++ b/examples/management_canister/src/index.ts @@ -85,14 +85,14 @@ export default Canister({ executeUploadChunk: update( [Principal, blob], ChunkHash, - async (canisterId, chunk): Promise => { + async (canisterId, chunk) => { if (process.env.AZLE_TEST_FETCH === 'true' || false) { const response = await fetch(`icp://aaaaa-aa/upload_chunk`, { body: serialize({ args: [ { canister_id: canisterId, - chunk: chunk + chunk } ] }) @@ -104,7 +104,7 @@ export default Canister({ args: [ { canister_id: canisterId, - chunk: chunk + chunk } ] }); diff --git a/examples/management_canister/test/tests.ts b/examples/management_canister/test/tests.ts index c575f05b29..e2d728a87f 100644 --- a/examples/management_canister/test/tests.ts +++ b/examples/management_canister/test/tests.ts @@ -53,7 +53,7 @@ export function getTests(managementCanister: ActorSubclass<_SERVICE>): Test[] { const result = await managementCanister.executeInstallCode( canisterId, - wasmModule as any + wasmModule ); return { @@ -92,7 +92,7 @@ export function getTests(managementCanister: ActorSubclass<_SERVICE>): Test[] { const chunkUploadResult = await managementCanister.executeUploadChunk( canisterId, - wasmModule as any + wasmModule ); return { diff --git a/the_azle_book/src/reference/management_canister/clear_chunk_store.md b/the_azle_book/src/reference/management_canister/clear_chunk_store.md index 4fa9fb12be..af3fa916c6 100644 --- a/the_azle_book/src/reference/management_canister/clear_chunk_store.md +++ b/the_azle_book/src/reference/management_canister/clear_chunk_store.md @@ -7,7 +7,7 @@ Examples: - [management_canister](https://github.com/demergent-labs/azle/tree/main/examples/management_canister) ```typescript -import { Canister, ic, update, Principal } from 'azle'; +import { bool, Canister, ic, Principal, update } from 'azle'; import { managementCanister } from 'azle/canisters/management'; export default Canister({ diff --git a/the_azle_book/src/reference/management_canister/install_chunked_code.md b/the_azle_book/src/reference/management_canister/install_chunked_code.md index 6af52da329..fbf1f11aad 100644 --- a/the_azle_book/src/reference/management_canister/install_chunked_code.md +++ b/the_azle_book/src/reference/management_canister/install_chunked_code.md @@ -7,7 +7,7 @@ Examples: - [management_canister](https://github.com/demergent-labs/azle/tree/main/examples/management_canister) ```typescript -import { Vec, blob, bool, Canister, ic, Principal, update, None } from 'azle'; +import { blob, bool, Canister, ic, None, Principal, update } from 'azle'; import { managementCanister } from 'azle/canisters/management'; export default Canister({ diff --git a/the_azle_book/src/reference/management_canister/stored_chunks.md b/the_azle_book/src/reference/management_canister/stored_chunks.md index d1c4557f6a..25f064b099 100644 --- a/the_azle_book/src/reference/management_canister/stored_chunks.md +++ b/the_azle_book/src/reference/management_canister/stored_chunks.md @@ -7,11 +7,10 @@ Examples: - [management_canister](https://github.com/demergent-labs/azle/tree/main/examples/management_canister) ```typescript -import { Canister, ic, update } from 'azle'; +import { Canister, ic, Principal, query } from 'azle'; import { - StoredChunksArgs, - StoredChunksResult, - managementCanister + managementCanister, + StoredChunksResult } from 'azle/canisters/management'; export default Canister({ diff --git a/the_azle_book/src/reference/management_canister/upload_chunk.md b/the_azle_book/src/reference/management_canister/upload_chunk.md index 3aa1e0613c..17e5bd032f 100644 --- a/the_azle_book/src/reference/management_canister/upload_chunk.md +++ b/the_azle_book/src/reference/management_canister/upload_chunk.md @@ -7,15 +7,14 @@ Examples: - [management_canister](https://github.com/demergent-labs/azle/tree/main/examples/management_canister) ```typescript -import { Canister, ic, update } from 'azle'; +import { blob, Canister, ic, Principal, update } from 'azle'; import { - UploadChunkArgs, - UploadChunkResult, - managementCanister + managementCanister, + UploadChunkResult } from 'azle/canisters/management'; export default Canister({ - executeUploadChunk: query( + executeUploadChunk: update( [Principal, blob], UploadChunkResult, async (canisterId, wasmChunk) => { From 05ca86d1082aba33ffe42d324f14e4fb44e2ec59 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 20 May 2024 09:42:19 -0600 Subject: [PATCH 37/49] pr fixes --- .../basic_bitcoin/test/{bitcoin-daemon.ts => bitcoin_daemon.ts} | 2 +- examples/bitcoin_psbt/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename examples/basic_bitcoin/test/{bitcoin-daemon.ts => bitcoin_daemon.ts} (94%) diff --git a/examples/basic_bitcoin/test/bitcoin-daemon.ts b/examples/basic_bitcoin/test/bitcoin_daemon.ts similarity index 94% rename from examples/basic_bitcoin/test/bitcoin-daemon.ts rename to examples/basic_bitcoin/test/bitcoin_daemon.ts index 104133e205..1d07ae2811 100644 --- a/examples/basic_bitcoin/test/bitcoin-daemon.ts +++ b/examples/basic_bitcoin/test/bitcoin_daemon.ts @@ -33,6 +33,6 @@ async function startBitcoinDaemon(): Promise { console.info(`starting bitcoind...`); // This await is necessary to ensure the daemon is running - await new Promise((resolve) => setTimeout(resolve, 5000)); + await new Promise((resolve) => setTimeout(resolve, 5_000)); return bitcoinDaemon; } diff --git a/examples/bitcoin_psbt/README.md b/examples/bitcoin_psbt/README.md index 2a4fd4d3d8..b110bdc332 100644 --- a/examples/bitcoin_psbt/README.md +++ b/examples/bitcoin_psbt/README.md @@ -31,7 +31,7 @@ fi dfx start --clean --host 127.0.0.1:8000 ``` -## basic_bitcoin +## bitcoin_psbt ```bash BITCOIN_NETWORK=regtest dfx deploy' From ac0bd184a0f54eb19be891b7ad355dbf87ed386a Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 20 May 2024 11:22:17 -0600 Subject: [PATCH 38/49] pr fixes --- examples/basic_bitcoin/test/test.ts | 2 +- examples/bitcoin_psbt/test/manual_test.ts | 2 +- examples/bitcoin_psbt/test/pretest.ts | 2 ++ examples/bitcoin_psbt/test/test.ts | 12 ++---------- examples/bitcoin_psbt/test/tests.ts | 9 +++++++++ 5 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 examples/bitcoin_psbt/test/tests.ts diff --git a/examples/basic_bitcoin/test/test.ts b/examples/basic_bitcoin/test/test.ts index d33d59881c..67d00a84f1 100644 --- a/examples/basic_bitcoin/test/test.ts +++ b/examples/basic_bitcoin/test/test.ts @@ -1,7 +1,7 @@ import { getCanisterId } from 'azle/dfx'; import { runTests } from 'azle/test'; -import { whileRunningBitcoinDaemon } from './bitcoin-daemon'; +import { whileRunningBitcoinDaemon } from './bitcoin_daemon'; import { getP2pkhAddress, getTests, P2PKH_ADDRESS_FORM } from './tests'; const canisterId = getCanisterId('basic_bitcoin'); diff --git a/examples/bitcoin_psbt/test/manual_test.ts b/examples/bitcoin_psbt/test/manual_test.ts index 9521bef1be..cff2f67434 100644 --- a/examples/bitcoin_psbt/test/manual_test.ts +++ b/examples/bitcoin_psbt/test/manual_test.ts @@ -2,7 +2,7 @@ import { getCanisterId } from 'azle/dfx'; import { getTests } from 'azle/examples/basic_bitcoin/test/tests'; import { runTests } from 'azle/test'; -import { getP2wpkhAddress, P2WPKH_ADDRESS_FORM } from './test'; +import { getP2wpkhAddress, P2WPKH_ADDRESS_FORM } from './tests'; const canisterId = getCanisterId('bitcoin_psbt'); diff --git a/examples/bitcoin_psbt/test/pretest.ts b/examples/bitcoin_psbt/test/pretest.ts index b12e17f7d4..772d9b9bc5 100644 --- a/examples/bitcoin_psbt/test/pretest.ts +++ b/examples/bitcoin_psbt/test/pretest.ts @@ -8,6 +8,8 @@ async function pretest() { execSync(`BITCOIN_NETWORK=regtest dfx deploy`, { stdio: 'inherit' }); + + execSync(`cd ../basic_bitcoin && npm install`, { stdio: 'inherit' }); } pretest(); diff --git a/examples/bitcoin_psbt/test/test.ts b/examples/bitcoin_psbt/test/test.ts index 1d8134ca56..53b15f8d0a 100644 --- a/examples/bitcoin_psbt/test/test.ts +++ b/examples/bitcoin_psbt/test/test.ts @@ -2,19 +2,11 @@ import * as dns from 'node:dns'; dns.setDefaultResultOrder('ipv4first'); import { getCanisterId } from 'azle/dfx'; -import { whileRunningBitcoinDaemon } from 'azle/examples/basic_bitcoin/test/bitcoin-daemon'; +import { whileRunningBitcoinDaemon } from 'azle/examples/basic_bitcoin/test/bitcoin_daemon'; import { getTests } from 'azle/examples/basic_bitcoin/test/tests'; import { runTests } from 'azle/test'; -export const P2WPKH_ADDRESS_FORM = - 'bcrt1qcxzt0xpf8qe3q75rsd30thrhgwzddy85f0cu4w'; - -export async function getP2wpkhAddress(origin: string): Promise { - const response = await fetch(`${origin}/get-p2wpkh-address`, { - headers: [['X-Ic-Force-Update', 'true']] - }); - return await response.text(); -} +import { getP2wpkhAddress, P2WPKH_ADDRESS_FORM } from './tests'; const canisterId = getCanisterId('bitcoin_psbt'); diff --git a/examples/bitcoin_psbt/test/tests.ts b/examples/bitcoin_psbt/test/tests.ts new file mode 100644 index 0000000000..5e3d66d591 --- /dev/null +++ b/examples/bitcoin_psbt/test/tests.ts @@ -0,0 +1,9 @@ +export const P2WPKH_ADDRESS_FORM = + 'bcrt1qcxzt0xpf8qe3q75rsd30thrhgwzddy85f0cu4w'; + +export async function getP2wpkhAddress(origin: string): Promise { + const response = await fetch(`${origin}/get-p2wpkh-address`, { + headers: [['X-Ic-Force-Update', 'true']] + }); + return await response.text(); +} From cfdd21e177f6b964d96421305f8b2d831d80d53b Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 20 May 2024 11:30:25 -0600 Subject: [PATCH 39/49] install basic_bitcoin packages before deploy --- examples/bitcoin_psbt/test/pretest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/bitcoin_psbt/test/pretest.ts b/examples/bitcoin_psbt/test/pretest.ts index 772d9b9bc5..359c91c93d 100644 --- a/examples/bitcoin_psbt/test/pretest.ts +++ b/examples/bitcoin_psbt/test/pretest.ts @@ -5,11 +5,11 @@ async function pretest() { stdio: 'inherit' }); + execSync(`cd ../basic_bitcoin && npm install`, { stdio: 'inherit' }); + execSync(`BITCOIN_NETWORK=regtest dfx deploy`, { stdio: 'inherit' }); - - execSync(`cd ../basic_bitcoin && npm install`, { stdio: 'inherit' }); } pretest(); From 5a7d2301692f87d6e8f80f2515bcb79b709ecff8 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 20 May 2024 14:23:24 -0600 Subject: [PATCH 40/49] depend on basic_bitcoin --- examples/basic_bitcoin/package-lock.json | 1 + examples/basic_bitcoin/package.json | 1 + examples/basic_bitcoin/test/tests.ts | 2 +- examples/bitcoin_psbt/package-lock.json | 99 ++++++++++++++++++++- examples/bitcoin_psbt/package.json | 3 +- examples/bitcoin_psbt/src/bitcoin_wallet.ts | 13 ++- examples/bitcoin_psbt/src/index.ts | 4 +- examples/bitcoin_psbt/test/manual_test.ts | 2 +- examples/bitcoin_psbt/test/pretest.ts | 2 - examples/bitcoin_psbt/test/test.ts | 4 +- src/compiler/compile_typescript_code.ts | 9 +- 11 files changed, 118 insertions(+), 22 deletions(-) diff --git a/examples/basic_bitcoin/package-lock.json b/examples/basic_bitcoin/package-lock.json index 1aa664f77a..2e6b92a3c6 100644 --- a/examples/basic_bitcoin/package-lock.json +++ b/examples/basic_bitcoin/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "basic_bitcoin", "hasInstallScript": true, "dependencies": { "azle": "^0.21.1", diff --git a/examples/basic_bitcoin/package.json b/examples/basic_bitcoin/package.json index 21d48d64a1..0c57304c80 100644 --- a/examples/basic_bitcoin/package.json +++ b/examples/basic_bitcoin/package.json @@ -1,4 +1,5 @@ { + "name": "basic_bitcoin", "scripts": { "install": "./scripts/install.sh", "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 2417e9b399..980982b0ee 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -41,7 +41,7 @@ export function getTests( } }, { - name: '/get-p2pkh-address', + name: '/get-address', test: async () => { const address = await getAddress(origin); diff --git a/examples/bitcoin_psbt/package-lock.json b/examples/bitcoin_psbt/package-lock.json index caf2608171..2b30009ba0 100644 --- a/examples/bitcoin_psbt/package-lock.json +++ b/examples/bitcoin_psbt/package-lock.json @@ -8,6 +8,7 @@ "dependencies": { "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "azle": "^0.21.1", + "basic_bitcoin": "file:../basic_bitcoin", "bitcoinjs-lib": "^6.1.5", "express": "^4.18.2", "tiny-secp256k1": "^2.2.3" @@ -19,6 +20,20 @@ "typescript": "^5.2.2" } }, + "../basic_bitcoin": { + "hasInstallScript": true, + "dependencies": { + "azle": "^0.21.1", + "bitcoinjs-lib": "^6.1.5", + "express": "^4.18.2" + }, + "devDependencies": { + "@dfinity/agent": "^0.19.2", + "@types/express": "^4.17.21", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, "node_modules/@adraffy/ens-normalize": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", @@ -891,6 +906,11 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz", "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==" }, + "node_modules/@types/validator": { + "version": "13.11.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.10.tgz", + "integrity": "sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -980,23 +1000,29 @@ }, "node_modules/azle": { "version": "0.21.1", - "resolved": "https://registry.npmjs.org/azle/-/azle-0.21.1.tgz", - "integrity": "sha512-pPAMmgs4X7joasm4YnfZibnEEDgvtYwfpScfSqcOgRLYJnRwil+xgnkIns/F3iAaRoRmy84pPKPIN5J3ysHQRw==", + "resolved": "git+ssh://git@github.com/demergent-labs/azle.git#0162634ef12581f2918ef201dbe2d6e246b5267f", + "license": "MIT", "dependencies": { "@dfinity/agent": "^1.1.0", "@dfinity/identity-secp256k1": "^1.1.0", "@types/uuid": "^9.0.4", "buffer": "^6.0.3", "chokidar": "^3.6.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", "crypto-browserify": "^3.12.0", "esbuild": "^0.19.3", + "esbuild-plugin-tsc": "^0.4.0", "ethers": "^6.11.1", "fs-extra": "10.0.1", "hash-of-directory": "^1.0.1", "http-message-parser": "^0.0.34", + "intl": "^1.2.5", "js-sha256": "0.9.0", "net": "^1.0.2", "pako": "^2.1.0", + "reflect-metadata": "^0.2.2", + "repl": "^0.1.3", "text-encoding": "0.7.0", "ts-node": "10.3.1", "typescript": "^5.2.2", @@ -1136,6 +1162,10 @@ } ] }, + "node_modules/basic_bitcoin": { + "resolved": "../basic_bitcoin", + "link": true + }, "node_modules/bech32": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", @@ -1420,6 +1450,21 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -1733,6 +1778,17 @@ "@esbuild/win32-x64": "0.19.4" } }, + "node_modules/esbuild-plugin-tsc": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/esbuild-plugin-tsc/-/esbuild-plugin-tsc-0.4.0.tgz", + "integrity": "sha512-q9gWIovt1nkwchMLc2zhyksaiHOv3kDK4b0AUol8lkMCRhJ1zavgfb2fad6BKp7FT9rh/OHmEBXVjczLoi/0yw==", + "dependencies": { + "strip-comments": "^2.0.1" + }, + "peerDependencies": { + "typescript": "^4.0.0 || ^5.0.0" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2179,6 +2235,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/intl": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz", + "integrity": "sha512-rK0KcPHeBFBcqsErKSpvZnrOmWOj+EmDkyJ57e90YWaQNqbcivcqmKDlHEeNprDWOsKzPsh1BfSpPQdDvclHVw==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2262,6 +2323,11 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/libphonenumber-js": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.1.tgz", + "integrity": "sha512-Wze1LPwcnzvcKGcRHFGFECTaLzxOtujwpf924difr5zniyYv1C2PiW0419qDR7m8lKDxsImu5mwxFuXhXpjmvw==" + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2607,6 +2673,19 @@ "node": ">=8.10.0" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + }, + "node_modules/repl": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/repl/-/repl-0.1.3.tgz", + "integrity": "sha512-C3ZEHaX28+EvM9lPiXl9ruN2g5M5sUvyCIDvZ0M4VCusfA1Cn0+z3tJcQl/lvxPsBm82q4hKHKebPlE3SEhFKg==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -2775,6 +2854,14 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "engines": { + "node": ">=10" + } + }, "node_modules/text-encoding": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", @@ -2960,6 +3047,14 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/varuint-bitcoin": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", diff --git a/examples/bitcoin_psbt/package.json b/examples/bitcoin_psbt/package.json index eb52787c22..abefc62e2b 100644 --- a/examples/bitcoin_psbt/package.json +++ b/examples/bitcoin_psbt/package.json @@ -13,11 +13,12 @@ "manualtest": "ts-node --transpile-only --ignore=false test/test.ts", "install": "./scripts/install.sh", "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", - "test": "ts-node --transpile-only --ignore=false test/test.ts" + "test": "NODE_PRESERVE_SYMLINKS=1 ts-node --transpile-only --ignore=false test/test.ts" }, "dependencies": { "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "azle": "^0.21.1", + "basic_bitcoin": "file:../basic_bitcoin", "bitcoinjs-lib": "^6.1.5", "express": "^4.18.2", "tiny-secp256k1": "^2.2.3" diff --git a/examples/bitcoin_psbt/src/bitcoin_wallet.ts b/examples/bitcoin_psbt/src/bitcoin_wallet.ts index 9868026657..29dcf5ff52 100644 --- a/examples/bitcoin_psbt/src/bitcoin_wallet.ts +++ b/examples/bitcoin_psbt/src/bitcoin_wallet.ts @@ -15,17 +15,16 @@ import { Satoshi, Utxo } from 'azle/canisters/management'; -import { address, payments, Psbt, Transaction } from 'bitcoinjs-lib'; -import { ValidateSigFunction } from 'bitcoinjs-lib/src/psbt'; -import { Buffer } from 'buffer'; - -import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; +import * as bitcoinApi from 'basic_bitcoin/src/bitcoin_api'; import { determineNetwork, mockSigner, SignFun -} from '../../basic_bitcoin/src/bitcoin_wallet'; -import * as ecdsaApi from '../../basic_bitcoin/src/ecdsa_api'; +} from 'basic_bitcoin/src/bitcoin_wallet'; +import * as ecdsaApi from 'basic_bitcoin/src/ecdsa_api'; +import { address, payments, Psbt, Transaction } from 'bitcoinjs-lib'; +import { ValidateSigFunction } from 'bitcoinjs-lib/src/psbt'; +import { Buffer } from 'buffer'; /// Returns the P2PKH address of this canister at the given derivation path. export async function getP2wpkhAddress( diff --git a/examples/bitcoin_psbt/src/index.ts b/examples/bitcoin_psbt/src/index.ts index e57a457135..aeaf9e2703 100644 --- a/examples/bitcoin_psbt/src/index.ts +++ b/examples/bitcoin_psbt/src/index.ts @@ -1,9 +1,9 @@ import { jsonParse, jsonStringify } from 'azle'; import { BitcoinNetwork } from 'azle/canisters/management'; +import { determineKeyName, determineNetwork } from 'basic_bitcoin/src'; +import * as bitcoinApi from 'basic_bitcoin/src/bitcoin_api'; import express, { Request } from 'express'; -import { determineKeyName, determineNetwork } from '../../basic_bitcoin/src'; -import * as bitcoinApi from '../../basic_bitcoin/src/bitcoin_api'; import * as bitcoinWallet from './bitcoin_wallet'; // The bitcoin network to connect to. diff --git a/examples/bitcoin_psbt/test/manual_test.ts b/examples/bitcoin_psbt/test/manual_test.ts index cff2f67434..1f3c190a2e 100644 --- a/examples/bitcoin_psbt/test/manual_test.ts +++ b/examples/bitcoin_psbt/test/manual_test.ts @@ -1,6 +1,6 @@ import { getCanisterId } from 'azle/dfx'; -import { getTests } from 'azle/examples/basic_bitcoin/test/tests'; import { runTests } from 'azle/test'; +import { getTests } from 'basic_bitcoin/test/tests'; import { getP2wpkhAddress, P2WPKH_ADDRESS_FORM } from './tests'; diff --git a/examples/bitcoin_psbt/test/pretest.ts b/examples/bitcoin_psbt/test/pretest.ts index 359c91c93d..b12e17f7d4 100644 --- a/examples/bitcoin_psbt/test/pretest.ts +++ b/examples/bitcoin_psbt/test/pretest.ts @@ -5,8 +5,6 @@ async function pretest() { stdio: 'inherit' }); - execSync(`cd ../basic_bitcoin && npm install`, { stdio: 'inherit' }); - execSync(`BITCOIN_NETWORK=regtest dfx deploy`, { stdio: 'inherit' }); diff --git a/examples/bitcoin_psbt/test/test.ts b/examples/bitcoin_psbt/test/test.ts index 53b15f8d0a..946d1379b7 100644 --- a/examples/bitcoin_psbt/test/test.ts +++ b/examples/bitcoin_psbt/test/test.ts @@ -2,9 +2,9 @@ import * as dns from 'node:dns'; dns.setDefaultResultOrder('ipv4first'); import { getCanisterId } from 'azle/dfx'; -import { whileRunningBitcoinDaemon } from 'azle/examples/basic_bitcoin/test/bitcoin_daemon'; -import { getTests } from 'azle/examples/basic_bitcoin/test/tests'; import { runTests } from 'azle/test'; +import { whileRunningBitcoinDaemon } from 'basic_bitcoin/test/bitcoin_daemon'; +import { getTests } from 'basic_bitcoin/test/tests'; import { getP2wpkhAddress, P2WPKH_ADDRESS_FORM } from './tests'; diff --git a/src/compiler/compile_typescript_code.ts b/src/compiler/compile_typescript_code.ts index c680581475..743464d934 100644 --- a/src/compiler/compile_typescript_code.ts +++ b/src/compiler/compile_typescript_code.ts @@ -37,14 +37,14 @@ export async function compileTypeScriptToJavaScript( // behave in all async situations setTimeout(() => { const canisterMethods = CanisterMethods.default !== undefined ? CanisterMethods.default() : Server(() => globalThis._azleNodeServer)(); - + globalThis.candidInfoFunction = () => { const candidInfo = canisterMethods.getIdl([]).accept(new DidVisitor(), { ...getDefaultVisitorData(), isFirstService: true, systemFuncs: canisterMethods.getSystemFunctionIdls() }); - + return JSON.stringify({ candid: toDidString(candidInfo), canisterMethods: { @@ -53,8 +53,8 @@ export async function compileTypeScriptToJavaScript( ...canisterMethods } }); - }; - + }; + // TODO I do not know how to get the module exports yet with wasmedge_quickjs globalThis.exports.canisterMethods = canisterMethods; }); @@ -121,6 +121,7 @@ export async function bundleFromString( write: false, logLevel: 'silent', target: 'es2020', + preserveSymlinks: true, alias: { internal: `${finalWasmedgeQuickJsPath}/modules/internal`, util: `${finalWasmedgeQuickJsPath}/modules/util`, From 47177a268aa0fcd250f405fc27ed989096a35f44 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Mon, 20 May 2024 16:42:54 -0600 Subject: [PATCH 41/49] add dependency to the bitcore-lib example --- examples/bitcoinjs-lib/package-lock.json | 31 ++++++++++++++++++++++++ examples/bitcoinjs-lib/package.json | 3 ++- examples/bitcoinjs-lib/test/pretest.ts | 2 -- examples/bitcoinjs-lib/test/tests.ts | 2 +- examples/bitcore-lib/package-lock.json | 3 ++- examples/bitcore-lib/package.json | 1 + 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/examples/bitcoinjs-lib/package-lock.json b/examples/bitcoinjs-lib/package-lock.json index e09192c464..305117bbff 100644 --- a/examples/bitcoinjs-lib/package-lock.json +++ b/examples/bitcoinjs-lib/package-lock.json @@ -9,6 +9,7 @@ "azle": "0.21.1", "bitcoinjs-lib": "^6.1.5", "bitcoinjs-message": "^2.2.0", + "bitcore-lib-example": "file:../bitcore-lib", "ecpair": "^2.1.0", "express": "^4.18.2", "tiny-secp256k1": "^2.2.3" @@ -19,6 +20,20 @@ "typescript": "^5.2.2" } }, + "../bitcore-lib": { + "name": "bitcore-lib-example", + "dependencies": { + "azle": "0.21.1", + "bitcore-lib": "^10.0.23", + "express": "^4.18.2" + }, + "devDependencies": { + "@types/bitcore-lib": "^0.15.6", + "@types/express": "^4.17.21", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, "node_modules/@adraffy/ens-normalize": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", @@ -1184,6 +1199,10 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/bitcore-lib-example": { + "resolved": "../bitcore-lib", + "link": true + }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -3782,6 +3801,18 @@ } } }, + "bitcore-lib-example": { + "version": "file:../bitcore-lib", + "requires": { + "@types/bitcore-lib": "^0.15.6", + "@types/express": "^4.17.21", + "azle": "0.21.1", + "bitcore-lib": "^10.0.23", + "express": "^4.18.2", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", diff --git a/examples/bitcoinjs-lib/package.json b/examples/bitcoinjs-lib/package.json index 384552dfda..eaf5a56835 100644 --- a/examples/bitcoinjs-lib/package.json +++ b/examples/bitcoinjs-lib/package.json @@ -1,13 +1,14 @@ { "scripts": { "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", - "test": "ts-node --transpile-only --ignore=false test/test.ts" + "test": "NODE_PRESERVE_SYMLINKS=1 ts-node --transpile-only --ignore=false test/test.ts" }, "dependencies": { "@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3", "azle": "0.21.1", "bitcoinjs-lib": "^6.1.5", "bitcoinjs-message": "^2.2.0", + "bitcore-lib-example": "file:../bitcore-lib", "ecpair": "^2.1.0", "express": "^4.18.2", "tiny-secp256k1": "^2.2.3" diff --git a/examples/bitcoinjs-lib/test/pretest.ts b/examples/bitcoinjs-lib/test/pretest.ts index 59c3b610bb..9a94953f69 100644 --- a/examples/bitcoinjs-lib/test/pretest.ts +++ b/examples/bitcoinjs-lib/test/pretest.ts @@ -8,8 +8,6 @@ async function pretest() { execSync(`dfx deploy`, { stdio: 'inherit' }); - - execSync(`cd ../bitcore-lib && npm install`, { stdio: 'inherit' }); } pretest(); diff --git a/examples/bitcoinjs-lib/test/tests.ts b/examples/bitcoinjs-lib/test/tests.ts index 07a397580c..c13fe1e14c 100644 --- a/examples/bitcoinjs-lib/test/tests.ts +++ b/examples/bitcoinjs-lib/test/tests.ts @@ -1,8 +1,8 @@ import * as dns from 'node:dns'; dns.setDefaultResultOrder('ipv4first'); -import { getTests as getBitcoinTests } from 'azle/examples/bitcore-lib/test/tests'; import { Test } from 'azle/test'; +import { getTests as getBitcoinTests } from 'bitcore-lib-example/test/tests'; export function getTests(canisterId: string): Test[] { const origin = `http://${canisterId}.localhost:8000`; diff --git a/examples/bitcore-lib/package-lock.json b/examples/bitcore-lib/package-lock.json index e79e343f15..268428cc10 100644 --- a/examples/bitcore-lib/package-lock.json +++ b/examples/bitcore-lib/package-lock.json @@ -1,9 +1,10 @@ { - "name": "bitcore-lib", + "name": "bitcore-lib-example", "lockfileVersion": 2, "requires": true, "packages": { "": { + "name": "bitcore-lib-example", "dependencies": { "azle": "0.21.1", "bitcore-lib": "^10.0.23", diff --git a/examples/bitcore-lib/package.json b/examples/bitcore-lib/package.json index 7129030a59..e26ab957bf 100644 --- a/examples/bitcore-lib/package.json +++ b/examples/bitcore-lib/package.json @@ -1,4 +1,5 @@ { + "name": "bitcore-lib-example", "scripts": { "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", "test": "ts-node --transpile-only --ignore=false test/test.ts" From 5a7524fb91481449a20625cb473c335d977bd340 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 21 May 2024 10:59:04 -0600 Subject: [PATCH 42/49] preserve Symlinks in the tsconfig as well --- examples/bitcoin_psbt/tsconfig.json | 3 ++- examples/bitcoinjs-lib/tsconfig.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/bitcoin_psbt/tsconfig.json b/examples/bitcoin_psbt/tsconfig.json index 0817cb3fc1..07ddb1a007 100644 --- a/examples/bitcoin_psbt/tsconfig.json +++ b/examples/bitcoin_psbt/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "node", "allowJs": true, "outDir": "HACK_BECAUSE_OF_ALLOW_JS", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "preserveSymlinks": true } } diff --git a/examples/bitcoinjs-lib/tsconfig.json b/examples/bitcoinjs-lib/tsconfig.json index 0817cb3fc1..07ddb1a007 100644 --- a/examples/bitcoinjs-lib/tsconfig.json +++ b/examples/bitcoinjs-lib/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "node", "allowJs": true, "outDir": "HACK_BECAUSE_OF_ALLOW_JS", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "preserveSymlinks": true } } From b2475e2af215242cb84a1abe2d5f7d2727c42bb2 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 21 May 2024 12:38:35 -0600 Subject: [PATCH 43/49] add fix for fee percentile --- examples/basic_bitcoin/test/tests.ts | 2 +- examples/bitcoin_psbt/dfx.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/basic_bitcoin/test/tests.ts b/examples/basic_bitcoin/test/tests.ts index 980982b0ee..986cc416ba 100644 --- a/examples/basic_bitcoin/test/tests.ts +++ b/examples/basic_bitcoin/test/tests.ts @@ -288,7 +288,7 @@ export function getTests( const feePercentiles = jsonParse(await response.text()); return { - Ok: feePercentiles.length === 0 + Ok: feePercentiles.length === 101 }; } } diff --git a/examples/bitcoin_psbt/dfx.json b/examples/bitcoin_psbt/dfx.json index 2cf88aa8c9..7d2e587ddc 100644 --- a/examples/bitcoin_psbt/dfx.json +++ b/examples/bitcoin_psbt/dfx.json @@ -17,6 +17,7 @@ }, "defaults": { "bitcoin": { + "canister_init_arg": "(record { stability_threshold = 1000 : nat; network = variant { regtest }; blocks_source = principal \"aaaaa-aa\"; fees = record { get_utxos_base = 0 : nat; get_utxos_cycles_per_ten_instructions = 0 : nat; get_utxos_maximum = 0 : nat; get_balance = 0 : nat; get_balance_maximum = 0 : nat; get_current_fee_percentiles = 0 : nat; get_current_fee_percentiles_maximum = 0 : nat; send_transaction_base = 0 : nat; send_transaction_per_byte = 0 : nat; }; syncing = variant { enabled }; api_access = variant { enabled }; disable_api_if_not_fully_synced = variant { enabled }})", "enabled": true, "nodes": ["127.0.0.1:18444"], "log_level": "info" From 288ad8ad12d642d150948d483d0720bc4352ba3c Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 21 May 2024 14:02:45 -0600 Subject: [PATCH 44/49] apply same fix to basic bitcoin --- examples/basic_bitcoin/dfx.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/basic_bitcoin/dfx.json b/examples/basic_bitcoin/dfx.json index 3fcd94fb20..9e365d0fe6 100644 --- a/examples/basic_bitcoin/dfx.json +++ b/examples/basic_bitcoin/dfx.json @@ -17,6 +17,7 @@ }, "defaults": { "bitcoin": { + "canister_init_arg": "(record { stability_threshold = 1000 : nat; network = variant { regtest }; blocks_source = principal \"aaaaa-aa\"; fees = record { get_utxos_base = 0 : nat; get_utxos_cycles_per_ten_instructions = 0 : nat; get_utxos_maximum = 0 : nat; get_balance = 0 : nat; get_balance_maximum = 0 : nat; get_current_fee_percentiles = 0 : nat; get_current_fee_percentiles_maximum = 0 : nat; send_transaction_base = 0 : nat; send_transaction_per_byte = 0 : nat; }; syncing = variant { enabled }; api_access = variant { enabled }; disable_api_if_not_fully_synced = variant { enabled }})", "enabled": true, "nodes": ["127.0.0.1:18444"], "log_level": "info" From 539268d815a391aa9967baec19f0bec7da64dbcf Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 21 May 2024 14:34:00 -0600 Subject: [PATCH 45/49] add wait time to autoreload --- examples/autoreload/test/tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/autoreload/test/tests.ts b/examples/autoreload/test/tests.ts index 6f31e3081d..4aac5ac20e 100644 --- a/examples/autoreload/test/tests.ts +++ b/examples/autoreload/test/tests.ts @@ -157,7 +157,7 @@ export function getTests(canisterId: string): Test[] { }, { name: 'waiting for Azle to reload', - wait: 30_000 + wait: 60_000 }, { name: '/test', From dcdd6d05bd91aed1ce2924eaa1c2add19d832c36 Mon Sep 17 00:00:00 2001 From: Benjamin DeMann Date: Tue, 4 Jun 2024 10:36:32 -0600 Subject: [PATCH 46/49] rename bitcore-lib and bitcoinjs-lib examples --- .github/workflows/test.yml | 4 +-- .../.gitignore | 0 .../{bitcoinjs-lib => bitcoinjs_lib}/LICENCE | 0 .../{bitcoinjs-lib => bitcoinjs_lib}/dfx.json | 0 .../package-lock.json | 26 ++++++++++++++----- .../package.json | 2 +- .../src/index.ts | 0 .../test/pretest.ts | 0 .../test/test.ts | 0 .../test/tests.ts | 2 +- .../tsconfig.json | 0 .../{bitcore-lib => bitcore_lib}/.gitignore | 0 examples/{bitcore-lib => bitcore_lib}/LICENCE | 0 .../{bitcore-lib => bitcore_lib}/dfx.json | 0 .../package-lock.json | 4 +-- .../{bitcore-lib => bitcore_lib}/package.json | 2 +- .../{bitcore-lib => bitcore_lib}/src/index.ts | 0 .../test/pretest.ts | 0 .../{bitcore-lib => bitcore_lib}/test/test.ts | 0 .../test/tests.ts | 3 +++ .../tsconfig.json | 0 21 files changed, 30 insertions(+), 13 deletions(-) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/.gitignore (100%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/LICENCE (100%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/dfx.json (100%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/package-lock.json (99%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/package.json (91%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/src/index.ts (100%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/test/pretest.ts (100%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/test/test.ts (100%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/test/tests.ts (98%) rename examples/{bitcoinjs-lib => bitcoinjs_lib}/tsconfig.json (100%) rename examples/{bitcore-lib => bitcore_lib}/.gitignore (100%) rename examples/{bitcore-lib => bitcore_lib}/LICENCE (100%) rename examples/{bitcore-lib => bitcore_lib}/dfx.json (100%) rename examples/{bitcore-lib => bitcore_lib}/package-lock.json (99%) rename examples/{bitcore-lib => bitcore_lib}/package.json (93%) rename examples/{bitcore-lib => bitcore_lib}/src/index.ts (100%) rename examples/{bitcore-lib => bitcore_lib}/test/pretest.ts (100%) rename examples/{bitcore-lib => bitcore_lib}/test/test.ts (100%) rename examples/{bitcore-lib => bitcore_lib}/test/tests.ts (98%) rename examples/{bitcore-lib => bitcore_lib}/tsconfig.json (100%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 709966cf37..c544a9286f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,8 +79,8 @@ jobs: "examples/basic_bitcoin", "examples/bitcoin", "examples/bitcoin_psbt", - "examples/bitcoinjs-lib", - "examples/bitcore-lib", + "examples/bitcoinjs_lib", + "examples/bitcore_lib", "examples/blob_array", "examples/bytes", "examples/call_raw", diff --git a/examples/bitcoinjs-lib/.gitignore b/examples/bitcoinjs_lib/.gitignore similarity index 100% rename from examples/bitcoinjs-lib/.gitignore rename to examples/bitcoinjs_lib/.gitignore diff --git a/examples/bitcoinjs-lib/LICENCE b/examples/bitcoinjs_lib/LICENCE similarity index 100% rename from examples/bitcoinjs-lib/LICENCE rename to examples/bitcoinjs_lib/LICENCE diff --git a/examples/bitcoinjs-lib/dfx.json b/examples/bitcoinjs_lib/dfx.json similarity index 100% rename from examples/bitcoinjs-lib/dfx.json rename to examples/bitcoinjs_lib/dfx.json diff --git a/examples/bitcoinjs-lib/package-lock.json b/examples/bitcoinjs_lib/package-lock.json similarity index 99% rename from examples/bitcoinjs-lib/package-lock.json rename to examples/bitcoinjs_lib/package-lock.json index 305117bbff..c838c60934 100644 --- a/examples/bitcoinjs-lib/package-lock.json +++ b/examples/bitcoinjs_lib/package-lock.json @@ -1,5 +1,5 @@ { - "name": "bitcoinjs-lib", + "name": "bitcoinjs_lib", "lockfileVersion": 2, "requires": true, "packages": { @@ -9,7 +9,7 @@ "azle": "0.21.1", "bitcoinjs-lib": "^6.1.5", "bitcoinjs-message": "^2.2.0", - "bitcore-lib-example": "file:../bitcore-lib", + "bitcore_lib_example": "file:../bitcore_lib", "ecpair": "^2.1.0", "express": "^4.18.2", "tiny-secp256k1": "^2.2.3" @@ -20,8 +20,22 @@ "typescript": "^5.2.2" } }, + "../bitcore_lib": { + "dependencies": { + "azle": "0.21.1", + "bitcore-lib": "^10.0.23", + "express": "^4.18.2" + }, + "devDependencies": { + "@types/bitcore-lib": "^0.15.6", + "@types/express": "^4.17.21", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, "../bitcore-lib": { "name": "bitcore-lib-example", + "extraneous": true, "dependencies": { "azle": "0.21.1", "bitcore-lib": "^10.0.23", @@ -1199,8 +1213,8 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/bitcore-lib-example": { - "resolved": "../bitcore-lib", + "node_modules/bitcore_lib_example": { + "resolved": "../bitcore_lib", "link": true }, "node_modules/bn.js": { @@ -3801,8 +3815,8 @@ } } }, - "bitcore-lib-example": { - "version": "file:../bitcore-lib", + "bitcore_lib_example": { + "version": "file:../bitcore_lib", "requires": { "@types/bitcore-lib": "^0.15.6", "@types/express": "^4.17.21", diff --git a/examples/bitcoinjs-lib/package.json b/examples/bitcoinjs_lib/package.json similarity index 91% rename from examples/bitcoinjs-lib/package.json rename to examples/bitcoinjs_lib/package.json index eaf5a56835..86b1c67054 100644 --- a/examples/bitcoinjs-lib/package.json +++ b/examples/bitcoinjs_lib/package.json @@ -8,7 +8,7 @@ "azle": "0.21.1", "bitcoinjs-lib": "^6.1.5", "bitcoinjs-message": "^2.2.0", - "bitcore-lib-example": "file:../bitcore-lib", + "bitcore_lib_example": "file:../bitcore_lib", "ecpair": "^2.1.0", "express": "^4.18.2", "tiny-secp256k1": "^2.2.3" diff --git a/examples/bitcoinjs-lib/src/index.ts b/examples/bitcoinjs_lib/src/index.ts similarity index 100% rename from examples/bitcoinjs-lib/src/index.ts rename to examples/bitcoinjs_lib/src/index.ts diff --git a/examples/bitcoinjs-lib/test/pretest.ts b/examples/bitcoinjs_lib/test/pretest.ts similarity index 100% rename from examples/bitcoinjs-lib/test/pretest.ts rename to examples/bitcoinjs_lib/test/pretest.ts diff --git a/examples/bitcoinjs-lib/test/test.ts b/examples/bitcoinjs_lib/test/test.ts similarity index 100% rename from examples/bitcoinjs-lib/test/test.ts rename to examples/bitcoinjs_lib/test/test.ts diff --git a/examples/bitcoinjs-lib/test/tests.ts b/examples/bitcoinjs_lib/test/tests.ts similarity index 98% rename from examples/bitcoinjs-lib/test/tests.ts rename to examples/bitcoinjs_lib/test/tests.ts index c13fe1e14c..cbb79acdac 100644 --- a/examples/bitcoinjs-lib/test/tests.ts +++ b/examples/bitcoinjs_lib/test/tests.ts @@ -2,7 +2,7 @@ import * as dns from 'node:dns'; dns.setDefaultResultOrder('ipv4first'); import { Test } from 'azle/test'; -import { getTests as getBitcoinTests } from 'bitcore-lib-example/test/tests'; +import { getTests as getBitcoinTests } from 'bitcore_lib_example/test/tests'; export function getTests(canisterId: string): Test[] { const origin = `http://${canisterId}.localhost:8000`; diff --git a/examples/bitcoinjs-lib/tsconfig.json b/examples/bitcoinjs_lib/tsconfig.json similarity index 100% rename from examples/bitcoinjs-lib/tsconfig.json rename to examples/bitcoinjs_lib/tsconfig.json diff --git a/examples/bitcore-lib/.gitignore b/examples/bitcore_lib/.gitignore similarity index 100% rename from examples/bitcore-lib/.gitignore rename to examples/bitcore_lib/.gitignore diff --git a/examples/bitcore-lib/LICENCE b/examples/bitcore_lib/LICENCE similarity index 100% rename from examples/bitcore-lib/LICENCE rename to examples/bitcore_lib/LICENCE diff --git a/examples/bitcore-lib/dfx.json b/examples/bitcore_lib/dfx.json similarity index 100% rename from examples/bitcore-lib/dfx.json rename to examples/bitcore_lib/dfx.json diff --git a/examples/bitcore-lib/package-lock.json b/examples/bitcore_lib/package-lock.json similarity index 99% rename from examples/bitcore-lib/package-lock.json rename to examples/bitcore_lib/package-lock.json index 268428cc10..c21463baa8 100644 --- a/examples/bitcore-lib/package-lock.json +++ b/examples/bitcore_lib/package-lock.json @@ -1,10 +1,10 @@ { - "name": "bitcore-lib-example", + "name": "bitcore_lib_example", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "bitcore-lib-example", + "name": "bitcore_lib_example", "dependencies": { "azle": "0.21.1", "bitcore-lib": "^10.0.23", diff --git a/examples/bitcore-lib/package.json b/examples/bitcore_lib/package.json similarity index 93% rename from examples/bitcore-lib/package.json rename to examples/bitcore_lib/package.json index e26ab957bf..f21b930652 100644 --- a/examples/bitcore-lib/package.json +++ b/examples/bitcore_lib/package.json @@ -1,5 +1,5 @@ { - "name": "bitcore-lib-example", + "name": "bitcore_lib_example", "scripts": { "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", "test": "ts-node --transpile-only --ignore=false test/test.ts" diff --git a/examples/bitcore-lib/src/index.ts b/examples/bitcore_lib/src/index.ts similarity index 100% rename from examples/bitcore-lib/src/index.ts rename to examples/bitcore_lib/src/index.ts diff --git a/examples/bitcore-lib/test/pretest.ts b/examples/bitcore_lib/test/pretest.ts similarity index 100% rename from examples/bitcore-lib/test/pretest.ts rename to examples/bitcore_lib/test/pretest.ts diff --git a/examples/bitcore-lib/test/test.ts b/examples/bitcore_lib/test/test.ts similarity index 100% rename from examples/bitcore-lib/test/test.ts rename to examples/bitcore_lib/test/test.ts diff --git a/examples/bitcore-lib/test/tests.ts b/examples/bitcore_lib/test/tests.ts similarity index 98% rename from examples/bitcore-lib/test/tests.ts rename to examples/bitcore_lib/test/tests.ts index abc6c3d1f9..d4a9499511 100644 --- a/examples/bitcore-lib/test/tests.ts +++ b/examples/bitcore_lib/test/tests.ts @@ -1,3 +1,6 @@ +import * as dns from 'node:dns'; +dns.setDefaultResultOrder('ipv4first'); + import { Test } from 'azle/test'; export function getTests(canisterId: string): Test[] { diff --git a/examples/bitcore-lib/tsconfig.json b/examples/bitcore_lib/tsconfig.json similarity index 100% rename from examples/bitcore-lib/tsconfig.json rename to examples/bitcore_lib/tsconfig.json From ee1245500338744490e865516a1921a5f849a7d1 Mon Sep 17 00:00:00 2001 From: Jordan Last Date: Fri, 7 Jun 2024 15:59:19 -0600 Subject: [PATCH 47/49] fix sqlite_drizzle npm type issues --- examples/sqlite/package.json | 1 + examples/sqlite_drizzle/package-lock.json | 30 +++++++++++++++++++++++ examples/sqlite_drizzle/package.json | 3 ++- examples/sqlite_drizzle/test/test.ts | 2 +- examples/sqlite_drizzle/tsconfig.json | 3 ++- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/examples/sqlite/package.json b/examples/sqlite/package.json index 59a9acc714..2a73f06edc 100644 --- a/examples/sqlite/package.json +++ b/examples/sqlite/package.json @@ -1,4 +1,5 @@ { + "name": "sqlite_example", "scripts": { "pretest": "ts-node --transpile-only --ignore=false test/pretest.ts", "test": "ts-node --transpile-only --ignore=false test/test.ts" diff --git a/examples/sqlite_drizzle/package-lock.json b/examples/sqlite_drizzle/package-lock.json index a0bcec50e4..34f8e8ecec 100644 --- a/examples/sqlite_drizzle/package-lock.json +++ b/examples/sqlite_drizzle/package-lock.json @@ -8,6 +8,20 @@ "azle": "0.21.1", "drizzle-orm": "^0.30.9", "express": "^4.18.2", + "sql.js": "1.8.0", + "sqlite_example": "file:../sqlite" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/sql.js": "^1.4.9", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, + "../sqlite": { + "dependencies": { + "azle": "0.21.1", + "express": "^4.18.2", "sql.js": "1.8.0" }, "devDependencies": { @@ -2716,6 +2730,10 @@ "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.8.0.tgz", "integrity": "sha512-3HD8pSkZL+5YvYUI8nlvNILs61ALqq34xgmF+BHpqxe68yZIJ1H+sIVIODvni25+CcxHUxDyrTJUL0lE/m7afw==" }, + "node_modules/sqlite_example": { + "resolved": "../sqlite", + "link": true + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4820,6 +4838,18 @@ "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.8.0.tgz", "integrity": "sha512-3HD8pSkZL+5YvYUI8nlvNILs61ALqq34xgmF+BHpqxe68yZIJ1H+sIVIODvni25+CcxHUxDyrTJUL0lE/m7afw==" }, + "sqlite_example": { + "version": "file:../sqlite", + "requires": { + "@types/express": "^4.17.21", + "@types/sql.js": "^1.4.9", + "azle": "0.21.1", + "express": "^4.18.2", + "sql.js": "1.8.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/examples/sqlite_drizzle/package.json b/examples/sqlite_drizzle/package.json index ac106226ee..28dfcac1aa 100644 --- a/examples/sqlite_drizzle/package.json +++ b/examples/sqlite_drizzle/package.json @@ -7,7 +7,8 @@ "azle": "0.21.1", "drizzle-orm": "^0.30.9", "express": "^4.18.2", - "sql.js": "1.8.0" + "sql.js": "1.8.0", + "sqlite_example": "file:../sqlite" }, "devDependencies": { "@types/express": "^4.17.21", diff --git a/examples/sqlite_drizzle/test/test.ts b/examples/sqlite_drizzle/test/test.ts index cef87949cb..6e764d1823 100644 --- a/examples/sqlite_drizzle/test/test.ts +++ b/examples/sqlite_drizzle/test/test.ts @@ -1,6 +1,6 @@ import { getCanisterId } from 'azle/dfx'; -import { getTests } from 'azle/examples/sqlite/test/tests'; import { runTests } from 'azle/test'; +import { getTests } from 'sqlite_example/test/tests'; const canisterId = getCanisterId('sqlite_drizzle'); diff --git a/examples/sqlite_drizzle/tsconfig.json b/examples/sqlite_drizzle/tsconfig.json index 0817cb3fc1..07ddb1a007 100644 --- a/examples/sqlite_drizzle/tsconfig.json +++ b/examples/sqlite_drizzle/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "node", "allowJs": true, "outDir": "HACK_BECAUSE_OF_ALLOW_JS", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "preserveSymlinks": true } } From 290d42cc48dc73c9e54fcffcb7e7f4e8cc3fe0c3 Mon Sep 17 00:00:00 2001 From: Jordan Last Date: Fri, 7 Jun 2024 16:22:13 -0600 Subject: [PATCH 48/49] fix counts --- examples/sqlite/package-lock.json | 3 ++- examples/sqlite/src/entities/posts/db.ts | 9 ++++----- examples/sqlite/src/entities/users/db.ts | 9 ++++----- examples/sqlite_drizzle/src/entities/posts/db.ts | 10 ++++------ examples/sqlite_drizzle/src/entities/users/db.ts | 10 ++++------ 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/examples/sqlite/package-lock.json b/examples/sqlite/package-lock.json index 6355752f9f..1ca314621a 100644 --- a/examples/sqlite/package-lock.json +++ b/examples/sqlite/package-lock.json @@ -1,9 +1,10 @@ { - "name": "sqlite", + "name": "sqlite_example", "lockfileVersion": 2, "requires": true, "packages": { "": { + "name": "sqlite_example", "dependencies": { "azle": "0.21.1", "express": "^4.18.2", diff --git a/examples/sqlite/src/entities/posts/db.ts b/examples/sqlite/src/entities/posts/db.ts index 053eb9f9e1..f3207d30f2 100644 --- a/examples/sqlite/src/entities/posts/db.ts +++ b/examples/sqlite/src/entities/posts/db.ts @@ -30,11 +30,10 @@ export function getPost(db: Database, id: number): Post | null { } export function countPosts(db: Database): number { - const results = - sqlite`SELECT id FROM posts ORDER BY id DESC LIMIT 1`( - db, - (sqlValues) => sqlValues[0] as number - ); + const results = sqlite`SELECT COUNT(*) FROM posts`( + db, + (sqlValues) => sqlValues[0] as number + ); return results[0] ?? 0; } diff --git a/examples/sqlite/src/entities/users/db.ts b/examples/sqlite/src/entities/users/db.ts index 77d8a7f224..2cda5d6aa9 100644 --- a/examples/sqlite/src/entities/users/db.ts +++ b/examples/sqlite/src/entities/users/db.ts @@ -27,11 +27,10 @@ export function getUser(db: Database, id: number): User | null { } export function countUsers(db: Database): number { - const results = - sqlite`SELECT id FROM users ORDER BY id DESC LIMIT 1`( - db, - (sqlValues) => sqlValues[0] as number - ); + const results = sqlite`SELECT COUNT(*) FROM users`( + db, + (sqlValues) => sqlValues[0] as number + ); return results[0] ?? 0; } diff --git a/examples/sqlite_drizzle/src/entities/posts/db.ts b/examples/sqlite_drizzle/src/entities/posts/db.ts index 5f9e8b4052..3b8d90af90 100644 --- a/examples/sqlite_drizzle/src/entities/posts/db.ts +++ b/examples/sqlite_drizzle/src/entities/posts/db.ts @@ -1,4 +1,4 @@ -import { desc, eq } from 'drizzle-orm'; +import { count, eq } from 'drizzle-orm'; import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { DrizzleDb } from '../../db'; @@ -61,13 +61,11 @@ export async function getPost( export async function countPosts(drizzleDb: DrizzleDb): Promise { const results = await drizzleDb .select({ - id: Posts.id + count: count() }) - .from(Posts) - .orderBy(desc(Posts.id)) - .limit(1); + .from(Posts); - return results[0]?.id ?? 0; + return results[0]?.count ?? 0; } export async function createPost( diff --git a/examples/sqlite_drizzle/src/entities/users/db.ts b/examples/sqlite_drizzle/src/entities/users/db.ts index 141473ecb1..79baf3876b 100644 --- a/examples/sqlite_drizzle/src/entities/users/db.ts +++ b/examples/sqlite_drizzle/src/entities/users/db.ts @@ -1,4 +1,4 @@ -import { desc, eq } from 'drizzle-orm'; +import { count, eq } from 'drizzle-orm'; import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; import { DrizzleDb } from '../../db'; @@ -32,13 +32,11 @@ export async function getUser( export async function countUsers(drizzleDb: DrizzleDb): Promise { const results = await drizzleDb .select({ - id: Users.id + count: count() }) - .from(Users) - .orderBy(desc(Users.id)) - .limit(1); + .from(Users); - return results[0]?.id ?? 0; + return results[0]?.count ?? 0; } export async function createUser( From 26747131e1aecb664c6fac99f78207c165481dd8 Mon Sep 17 00:00:00 2001 From: Jordan Last Date: Fri, 7 Jun 2024 16:44:13 -0600 Subject: [PATCH 49/49] update tests with constants and variables --- examples/sqlite/test/tests.ts | 69 +++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/examples/sqlite/test/tests.ts b/examples/sqlite/test/tests.ts index 6d59a4c934..973df13504 100644 --- a/examples/sqlite/test/tests.ts +++ b/examples/sqlite/test/tests.ts @@ -4,6 +4,9 @@ dns.setDefaultResultOrder('ipv4first'); import { Test } from 'azle/test'; import { execSync } from 'child_process'; +const USERS_BATCH_AMOUNT = 499; +const POSTS_BATCH_AMOUNT = 299; + export function getTests(canisterId: string): Test[] { const origin = `http://${canisterId}.localhost:8000`; @@ -33,28 +36,38 @@ export function getTests(canisterId: string): Test[] { ...usersTestsBeforeBatch(origin), ...postsTestsBeforeBatch(origin), { - name: '/users/batch/499', + name: `/users/batch/${USERS_BATCH_AMOUNT}`, test: async () => { - const response = await fetch(`${origin}/users/batch/499`, { - method: 'POST' - }); + const response = await fetch( + `${origin}/users/batch/${USERS_BATCH_AMOUNT}`, + { + method: 'POST' + } + ); const responseJson = await response.json(); return { - Ok: responseJson.Success === '499 users created' + Ok: + responseJson.Success === + `${USERS_BATCH_AMOUNT} users created` }; } }, { - name: '/posts/batch/299', + name: `/posts/batch/${POSTS_BATCH_AMOUNT}`, test: async () => { - const response = await fetch(`${origin}/posts/batch/299`, { - method: 'POST' - }); + const response = await fetch( + `${origin}/posts/batch/${POSTS_BATCH_AMOUNT}`, + { + method: 'POST' + } + ); const responseJson = await response.json(); return { - Ok: responseJson.Success === '299 posts created' + Ok: + responseJson.Success === + `${POSTS_BATCH_AMOUNT} posts created` }; } }, @@ -442,6 +455,8 @@ function postsTestsBeforeBatch(origin: string): Test[] { } function usersTestsAfterBatch(origin: string): Test[] { + const totalNumUsers = USERS_BATCH_AMOUNT + 1 + POSTS_BATCH_AMOUNT + 1; + return [ { name: '/users not empty', @@ -450,30 +465,32 @@ function usersTestsAfterBatch(origin: string): Test[] { const responseJson = await response.json(); return { - Ok: responseJson.length === 800 + Ok: responseJson.length === totalNumUsers }; } }, { - name: '/users/count 800', + name: `/users/count ${totalNumUsers}`, test: async () => { const response = await fetch(`${origin}/users/count`); const responseJson = await response.json(); return { - Ok: responseJson === 800 + Ok: responseJson === totalNumUsers }; } }, { - name: '/users/800 not null', + name: `/users/${totalNumUsers} not null`, test: async () => { - const response = await fetch(`${origin}/users/800`); + const response = await fetch( + `${origin}/users/${totalNumUsers}` + ); const responseJson = await response.json(); return { Ok: - responseJson.id === 800 && + responseJson.id === totalNumUsers && typeof responseJson.username === 'string' && typeof responseJson.age === 'number' }; @@ -483,38 +500,44 @@ function usersTestsAfterBatch(origin: string): Test[] { } function postsTestsAfterBatch(origin: string): Test[] { + const totalNumPosts = POSTS_BATCH_AMOUNT + 1; + return [ { name: '/posts not empty', test: async () => { - const response = await fetch(`${origin}/posts?limit=300`); + const response = await fetch( + `${origin}/posts?limit=${totalNumPosts}` + ); const responseJson = await response.json(); return { - Ok: responseJson.length === 300 + Ok: responseJson.length === totalNumPosts }; } }, { - name: '/posts/count 300', + name: `/posts/count ${totalNumPosts}`, test: async () => { const response = await fetch(`${origin}/posts/count`); const responseJson = await response.json(); return { - Ok: responseJson === 300 + Ok: responseJson === totalNumPosts }; } }, { - name: '/posts/300 not null', + name: `/posts/${totalNumPosts} not null`, test: async () => { - const response = await fetch(`${origin}/posts/300`); + const response = await fetch( + `${origin}/posts/${totalNumPosts}` + ); const responseJson = await response.json(); return { Ok: - responseJson.id === 300 && + responseJson.id === totalNumPosts && typeof responseJson.title === 'string' && typeof responseJson.body === 'string' && typeof responseJson.user.id === 'number' &&