diff --git a/.gitignore b/.gitignore
index 1945b137..c041cbfe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ npm-debug.log
 **/dist/
 **/dist-test/
 **/node_modules/
+packages/cashscript/src/network/BchdGrpcNetworkProvider/generated
 
 **/.DS_Store
 /**/.project
diff --git a/packages/cashscript/package.json b/packages/cashscript/package.json
index b725ea8b..917c078a 100644
--- a/packages/cashscript/package.json
+++ b/packages/cashscript/package.json
@@ -30,6 +30,7 @@
     "test": "test"
   },
   "scripts": {
+    "generate:bchd-grpc": "cd ./src/network/BchdGrpcNetworkProvider && mkdir -p generated && protoc --ts_out ./generated --proto_path . bchrpc.proto && sed -i 's/bchrpc\";/bchrpc.js\";/' ./generated/bchrpc.client.ts",
     "build": "yarn clean && yarn compile",
     "build:test": "yarn clean:test && yarn compile:test",
     "clean": "rm -rf ./dist",
@@ -46,6 +47,7 @@
     "@bitauth/libauth": "^3.1.0-next.2",
     "@cashscript/utils": "^0.11.0-next.0",
     "@mr-zwets/bchn-api-wrapper": "^1.0.1",
+    "@protobuf-ts/grpcweb-transport": "^2.9.4",
     "delay": "^6.0.0",
     "electrum-cash": "^2.0.10",
     "fast-deep-equal": "^3.1.3",
@@ -54,6 +56,7 @@
   },
   "devDependencies": {
     "@jest/globals": "^29.7.0",
+    "@protobuf-ts/plugin": "^2.9.4",
     "@psf/bch-js": "^6.8.0",
     "@types/pako": "^2.0.3",
     "@types/semver": "^7.5.8",
diff --git a/packages/cashscript/src/index.ts b/packages/cashscript/src/index.ts
index 53c364bb..9d1652b1 100644
--- a/packages/cashscript/src/index.ts
+++ b/packages/cashscript/src/index.ts
@@ -19,5 +19,6 @@ export {
   ElectrumNetworkProvider,
   FullStackNetworkProvider,
   MockNetworkProvider,
+  BchdGrpcNetworkProvider,
 } from './network/index.js';
 export { randomUtxo, randomToken, randomNFT } from './utils.js';
diff --git a/packages/cashscript/src/network/BchdGrpcNetworkProvider/bchrpc.proto b/packages/cashscript/src/network/BchdGrpcNetworkProvider/bchrpc.proto
new file mode 100644
index 00000000..7984b074
--- /dev/null
+++ b/packages/cashscript/src/network/BchdGrpcNetworkProvider/bchrpc.proto
@@ -0,0 +1,1018 @@
+syntax = "proto3";
+option go_package="github.com/gcash/bchd/bchrpc/pb";
+
+package pb;
+option java_package = "cash.bchd.rpc";
+
+// bchrpc contains a set of RPCs that can be exposed publicly via
+// the command line options. This service could be authenticated or
+// unauthenticated.
+service bchrpc {
+
+    // GetMempoolInfo returns the state of the current mempool.
+    rpc GetMempoolInfo(GetMempoolInfoRequest) returns (GetMempoolInfoResponse) {}
+
+    // GetMempool returns information about all transactions currently in the memory pool.
+    // Offers an option to return full transactions or just transactions hashes.
+    rpc GetMempool(GetMempoolRequest) returns (GetMempoolResponse) {}
+
+    // GetBlockchainInfo returns data about the blockchain including the most recent
+    // block hash and height.
+    rpc GetBlockchainInfo(GetBlockchainInfoRequest) returns (GetBlockchainInfoResponse) {}
+
+    // GetBlockInfo returns metadata and info for a specified block.
+    rpc GetBlockInfo(GetBlockInfoRequest)returns (GetBlockInfoResponse) {}
+
+    // GetBlock returns detailed data for a block.
+    rpc GetBlock(GetBlockRequest) returns (GetBlockResponse) {}
+
+    // GetRawBlock returns a block in a serialized format.
+    rpc GetRawBlock(GetRawBlockRequest) returns (GetRawBlockResponse) {}
+
+    // GetBlockFilter returns the compact filter (cf) of a block as a Golomb-Rice encoded set.
+    //
+    // **Requires CfIndex**
+    rpc GetBlockFilter(GetBlockFilterRequest) returns (GetBlockFilterResponse) {}
+
+    // GetHeaders takes a block locator object and returns a batch of no more than 2000
+    // headers. Upon parsing the block locator, if the server concludes there has been a
+    // fork, it will send headers starting at the fork point, or genesis if no blocks in
+    // the locator are in the best chain. If the locator is already at the tip no headers
+    // will be returned.
+    // see: bchd/bchrpc/documentation/wallet_operation.md
+    rpc GetHeaders(GetHeadersRequest) returns (GetHeadersResponse) {}
+
+    // GetTransaction returns a transaction given a transaction hash.
+    //
+    // **Requires TxIndex**
+    // **Requires SlpIndex for slp related information **
+    rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse) {}
+
+    // GetRawTransaction returns a serialized transaction given a transaction hash.
+    //
+    // **Requires TxIndex**
+    rpc GetRawTransaction(GetRawTransactionRequest) returns (GetRawTransactionResponse) {}
+
+    // GetAddressTransactions returns the transactions for the given address. Offers offset,
+    // limit, and from block options.
+    //
+    // **Requires AddressIndex**
+    // **Requires SlpIndex for slp related information **
+    rpc GetAddressTransactions(GetAddressTransactionsRequest) returns (GetAddressTransactionsResponse) {}
+
+    // GetRawAddressTransactions returns the serialized raw transactions for
+    // the given address. Offers offset, limit, and from block options.
+    //
+    // **Requires AddressIndex**
+    rpc GetRawAddressTransactions(GetRawAddressTransactionsRequest) returns (GetRawAddressTransactionsResponse) {}
+
+    // GetAddressUnspentOutputs returns all the unspent transaction outputs
+    // for the given address.
+    //
+    // **Requires AddressIndex**
+    // **Requires SlpIndex for slp related information **
+    rpc GetAddressUnspentOutputs(GetAddressUnspentOutputsRequest) returns (GetAddressUnspentOutputsResponse) {}
+
+    // GetUnspentOutput takes an unspent output in the utxo set and returns
+    // the utxo metadata or not found.
+    //
+    // **Requires SlpIndex for slp related information **
+    rpc GetUnspentOutput(GetUnspentOutputRequest) returns (GetUnspentOutputResponse) {}
+
+    // GetMerkleProof returns a Merkle (SPV) proof for a specific transaction
+    // in the provided block.
+    //
+    // **Requires TxIndex**
+    rpc GetMerkleProof(GetMerkleProofRequest) returns (GetMerkleProofResponse) {}
+
+    // GetSlpTokenMetadata return slp token metadata for one or more tokens.
+    //
+    // **Requires SlpIndex**
+    rpc GetSlpTokenMetadata(GetSlpTokenMetadataRequest) returns (GetSlpTokenMetadataResponse) {}
+
+    // GetSlpParsedScript returns marshalled object from parsing an slp pubKeyScript 
+    // using goslp package.  This endpoint does not require SlpIndex.
+    rpc GetSlpParsedScript(GetSlpParsedScriptRequest) returns (GetSlpParsedScriptResponse) {}
+
+    // GetSlpTrustedValidation returns slp validity related information for one or more transactions.
+    //
+    // **Requires SlpIndex**
+    rpc GetSlpTrustedValidation(GetSlpTrustedValidationRequest) returns (GetSlpTrustedValidationResponse) {}
+
+    // GraphSearch returns all the transactions needed for a client to validate an SLP graph
+    //
+    // **Requires SlpIndex and SlpGraphSearch**
+    rpc GetSlpGraphSearch (GetSlpGraphSearchRequest) returns (GetSlpGraphSearchResponse) {}
+
+    // CheckSlpTransaction checks the validity of a supposed slp transaction before it is broadcasted.
+    rpc CheckSlpTransaction(CheckSlpTransactionRequest) returns (CheckSlpTransactionResponse) {}
+
+    // Submit a transaction to all connected peers.
+    rpc SubmitTransaction(SubmitTransactionRequest) returns (SubmitTransactionResponse) {}
+
+    // SubscribeTransactions creates subscription to all relevant transactions based on
+    // the subscription filter.
+    //
+    // This RPC does not use bidirectional streams and therefore can be used
+    // with grpc-web. You will need to close and reopen the stream whenever
+    // you want to update the subscription filter. If you are not using grpc-web
+    // then SubscribeTransactionStream is more appropriate.
+    //
+    // **Requires TxIndex to receive input metadata**
+    // **Requires SlpIndex to receive slp input/output metadata, or SlpTokenMetadata**
+    rpc SubscribeTransactions(SubscribeTransactionsRequest) returns (stream TransactionNotification) {}
+
+    // SubscribeTransactionStream subscribes to relevant transactions based on
+    // the subscription requests. The parameters to filter transactions on can
+    // be updated by sending new SubscribeTransactionsRequest objects on the stream.
+    //
+    // NOTE: Because this RPC is using bi-directional streaming it cannot be used with
+    // grpc-web.
+    //
+    // **Requires TxIndex to receive input metadata**
+    rpc SubscribeTransactionStream(stream SubscribeTransactionsRequest) returns (stream TransactionNotification) {}
+
+    // SubscribeBlocks creates a subscription for notifications of new blocks being
+    // connected to the blockchain or blocks being disconnected.
+    rpc SubscribeBlocks(SubscribeBlocksRequest) returns (stream BlockNotification) {}
+}
+
+
+// RPC MESSAGES
+
+message GetMempoolInfoRequest {}
+message GetMempoolInfoResponse {
+    // The count of transactions in the mempool
+    uint32 size = 1;
+    // The size in bytes of all transactions in the mempool
+    uint32 bytes = 2;
+}
+
+message GetMempoolRequest {
+    // When `full_transactions` is true, full transaction data is provided
+    // instead of just transaction hashes. Default is false.
+    bool full_transactions = 1;
+}
+
+message GetMempoolResponse {
+    message TransactionData {
+        // Either one of the two following is provided, depending on the request.
+        oneof txids_or_txs {
+            // The transaction hash, little-endian.
+            bytes transaction_hash = 1;
+            // The transaction data.
+            Transaction transaction = 2;
+        }
+    }
+
+    // List of unconfirmed transactions.
+    repeated TransactionData transaction_data = 1;
+}
+
+message GetBlockchainInfoRequest {}
+message GetBlockchainInfoResponse {
+
+    // Bitcoin network types
+    enum BitcoinNet {
+
+        // Live public network with monetary value.
+        MAINNET  = 0;
+        // An isolated environment for automated testing.
+        REGTEST  = 1;
+        // A public environment where monetary value is agreed to be zero,
+        // and some checks for transaction conformity are disabled.
+        TESTNET3 = 2;
+        // Private testnets for large scale simulations (or stress testing),
+        // where a specified list of nodes is used, rather than node discovery.
+        SIMNET   = 3;
+        // Latest Testnet.
+        TESTNET4 = 4;
+    }
+
+    // Which network the node is operating on.
+    BitcoinNet bitcoin_net = 1;
+
+    // The current number of blocks on the longest chain.
+    int32 best_height = 2;
+    // The hash of the best (tip) block in the most-work fully-validated chain, little-endian.
+    bytes best_block_hash = 3;
+    // Threshold for adding new blocks.
+    double difficulty = 4;
+    // Median time of the last 11 blocks.
+    int64 median_time = 5;
+    // When `tx_index` is true, the node has full transaction index enabled.
+    bool tx_index = 6;
+    // When `addr_index` is true, the node has address index enabled and may
+    // be used with call related by address.
+    bool addr_index =7;
+    // When `slp_index` is true, the node has the slp index enabled and may
+    // be used with slp related rpc methods and also causes slp metadata to be added
+    // in some of the existing rpc methods.
+    bool slp_index = 8;
+    // When `slp_graphsearch` is true, the node is able to handle calls to slp graph search
+    bool slp_graphsearch = 9;
+}
+
+message GetBlockInfoRequest {
+    oneof hash_or_height {
+        // The block hash as a byte array or base64 encoded string, little-endian.
+        bytes hash = 1;
+        // The block number.
+        int32 height = 2;
+    }
+}
+message GetBlockInfoResponse {
+    // Marshaled block header data, as well as metadata.
+    BlockInfo info = 1;
+}
+
+message GetBlockRequest {
+    oneof hash_or_height {
+        // The block hash as a byte array or base64 encoded string, little-endian.
+        bytes hash = 1;
+        // The block number.
+        int32 height = 2;
+    }
+    // When `full_transactions` is true, full transactions are returned
+    // instead of just hashes. Default is false.
+    bool full_transactions = 3;
+}
+message GetBlockResponse {
+    // A marshaled block.
+    Block block = 1;
+}
+
+message GetRawBlockRequest {
+    oneof hash_or_height {
+        // The block hash as a byte array or base64 encoded string, little-endian.
+        bytes hash = 1;
+        // The block number.
+        int32 height = 2;
+    }
+}
+message GetRawBlockResponse {
+    // Raw block data (with header) serialized according the the bitcoin block protocol.
+    bytes block = 1;
+}
+
+message GetBlockFilterRequest {
+    oneof hash_or_height {
+        // The block hash as a byte array or base64 encoded string, little-endian.
+        bytes hash = 1;
+        // The block number.
+        int32 height = 2;
+    }
+}
+
+message GetBlockFilterResponse {
+    // A compact filter matching input outpoints and public key scripts contained
+    // in a block (encoded according to BIP158).
+    bytes filter = 1;
+}
+
+// Request headers using a list of known block hashes.
+message GetHeadersRequest {
+    // A list of block hashes known to the client (most recent first) which
+    // is exponentially sparser toward the genesis block (0), little-endian.
+    // Common practice is to include all of the last 10 blocks, and then
+    // 9 blocks for each order of ten thereafter.
+    repeated bytes block_locator_hashes = 1;
+    // hash of the latest desired block header, little-endian; only blocks
+    // occurring before the stop will be returned.
+    bytes stop_hash = 2;
+}
+message GetHeadersResponse {
+    // List of block headers.
+    repeated BlockInfo headers = 1;
+}
+
+// Get a transaction from a transaction hash.
+message GetTransactionRequest {
+    // A transaction hash, little-endian.
+    bytes hash = 1;
+
+    bool include_token_metadata = 2;
+}
+message GetTransactionResponse {
+    // A marshaled transaction.
+    Transaction transaction = 1;
+
+    SlpTokenMetadata token_metadata = 2;
+}
+
+// Get an encoded transaction from a transaction hash.
+message GetRawTransactionRequest {
+    // A transaction hash, little-endian.
+    bytes hash = 1;
+}
+message GetRawTransactionResponse {
+    // Raw transaction in bytes.
+    bytes transaction = 1;
+}
+
+// Get marshaled transactions related to a specific address.
+//
+// RECOMMENDED:
+// Parameters have been provided to query without creating
+//   performance issues on the node or client.
+//
+// - The number of transactions to skip and fetch allow for iterating
+//       over a large set of transactions, if necessary.
+//
+// - A starting block parameter (either `hash` or `height`)
+//       may then be used to filter results to those occurring
+//       after a certain time.
+//
+// This approach will reduce network traffic and response processing
+//   for the client, as well as reduce workload on the node.
+message GetAddressTransactionsRequest {
+    // The address to query transactions, in lowercase cashaddr format.
+    // The network prefix is optional (i.e. "cashaddress:").
+    string address = 1;
+
+    // The number of confirmed transactions to skip, starting with the oldest first.
+    // Does not affect results of unconfirmed transactions.
+    uint32 nb_skip = 2;
+    // Specify the number of transactions to fetch.
+    uint32 nb_fetch = 3;
+
+
+    oneof start_block {
+        // Recommended. Only get transactions after (or within) a
+        // starting block identified by hash, little-endian.
+        bytes hash = 4;
+        // Recommended. Only get transactions after (or within) a
+        // starting block identified by block number.
+        int32 height = 5;
+    }
+}
+message GetAddressTransactionsResponse {
+    // Transactions that have been included in a block.
+    repeated Transaction confirmed_transactions = 1;
+    // Transactions in mempool which have not been included in a block.
+    repeated MempoolTransaction unconfirmed_transactions = 2;
+}
+
+// Get encoded transactions related to a specific address.
+//
+// RECOMMENDED:
+// Parameters have been provided to query without creating
+//   performance issues on the node or client.
+//
+// - The number of transactions to skip and fetch allow for iterating
+//       over a large set of transactions, if necessary.
+//
+// - A starting block parameter (either `hash` or `height`)
+//       may then be used to filter results to those occurring
+//       after a certain time.
+//
+// This approach will reduce network traffic and response processing
+//   for the client, as well as reduce workload on the node.
+message GetRawAddressTransactionsRequest {
+    // The address to query transactions, in lowercase cashaddr format.
+    // The network prefix is optional (i.e. "cashaddress:").
+    string address = 1;
+
+    // The number of confirmed transactions to skip, starting with the oldest first.
+    // Does not affect results of unconfirmed transactions.
+    uint32 nb_skip = 2;
+    // Specify the number of transactions to fetch.
+    uint32 nb_fetch = 3;
+
+    oneof start_block {
+        // Recommended. Only return transactions after some starting block
+        // identified by hash, little-endian.
+        bytes hash = 4;
+        // Recommended. Only return transactions after some starting block
+        // identified by block number.
+        int32 height = 5;
+    }
+}
+message GetRawAddressTransactionsResponse {
+    // Transactions that have been included in a block.
+    repeated bytes confirmed_transactions = 1;
+    // Transactions in mempool which have not been included in a block.
+    repeated bytes unconfirmed_transactions = 2;
+}
+
+message GetAddressUnspentOutputsRequest {
+    // The address to query transactions, in lowercase cashaddr format.
+    // The network identifier is optional (i.e. "cashaddress:").
+    string address = 1;
+    // When `include_mempool` is true, unconfirmed transactions from mempool
+    // are returned. Default is false.
+    bool include_mempool = 2;
+    bool include_token_metadata = 3;
+}
+message GetAddressUnspentOutputsResponse {
+    // List of unspent outputs.
+    repeated UnspentOutput outputs = 1;
+    repeated SlpTokenMetadata token_metadata = 2;
+}
+
+message GetUnspentOutputRequest {
+    // The hash of the transaction, little-endian.
+    bytes hash = 1;
+    // The number of the output, starting from zero.
+    uint32 index = 2;
+    // When include_mempool is true, unconfirmed transactions from mempool
+    // are returned. Default is false.
+    bool include_mempool = 3;
+    bool include_token_metadata = 4;
+}
+message GetUnspentOutputResponse {
+    // A reference to the related input.
+    Transaction.Input.Outpoint outpoint = 1;
+    // Locking script dictating how funds can be spent in the future
+    bytes pubkey_script = 2;
+    // Amount in satoshi.
+    int64 value = 3;
+    // When is_coinbase is true, the transaction was the first in a block,
+    // created by a miner, and used to pay the block reward
+    bool is_coinbase = 4;
+    // The index number of the block containing the transaction creating the output.
+    int32 block_height = 5;
+
+    SlpToken slp_token = 6;
+    SlpTokenMetadata token_metadata = 7;
+
+    CashToken cash_token = 8;
+}
+
+message GetMerkleProofRequest {
+    // A transaction hash, little-endian.
+    bytes transaction_hash = 1;
+}
+message GetMerkleProofResponse {
+    // Block header information for the corresponding transaction
+    BlockInfo block = 1;
+    // A list containing the transaction hash, the adjacent leaf transaction hash
+    // and the hashes of the highest nodes in the merkle tree not built with the transaction.
+    // Proof hashes are ordered following transaction order, or left to right on the merkle tree
+    repeated bytes hashes = 2;
+    // Binary representing the location of the matching transaction in the full merkle tree,
+    // starting with the root (`1`) at position/level 0, where `1` corresponds
+    // to a left branch and `01` is a right branch.
+    bytes flags = 3;
+}
+
+message SubmitTransactionRequest {
+    // The encoded transaction.
+    bytes transaction = 1;
+    bool skip_slp_validity_check = 2;
+    repeated SlpRequiredBurn required_slp_burns = 3;
+}
+message SubmitTransactionResponse {
+    // Transaction hash, little-endian.
+    bytes hash = 1;
+}
+
+message CheckSlpTransactionRequest {
+    bytes transaction = 1;
+    repeated SlpRequiredBurn required_slp_burns = 2;
+
+    // Using the slp specification as a basis for validity judgement can lead to confusion for new users and
+    // result in accidental token burns.  use_spec_validity_judgement will cause the response's is_valid property
+    // to be returned according to the slp specification.  Therefore, use_spec_validity_judgement is false by
+    // default in order to avoid accidental token burns.  When use_spec_validity_judgement is false we return
+    // invalid in any case which would result in a burned token, unless the burn is explicitly included as an
+    // item in required_slp_burns property.
+    //
+    // When use_spec_validity_judgement is true, there are three cases where the is_valid response property
+    // will be returned as valid, instead of invalid, as per the slp specification.  
+    //   1) inputs > outputs
+    //   2) missing transaction outputs
+    //   3) burned inputs from other tokens
+    // 
+    // required_slp_burns is not used when use_spec_validity_judgement is set to true.
+    //
+    bool use_spec_validity_judgement = 3;
+}
+
+message CheckSlpTransactionResponse {
+    bool is_valid = 1;
+    string invalid_reason = 2;
+    int32 best_height = 3;
+}
+
+// Request to subscribe or unsubscribe from a stream of transactions.
+message SubscribeTransactionsRequest {
+    // Subscribe to a filter. add items to a filter
+    TransactionFilter subscribe = 1;
+    // Unsubscribe to a filter, remove items from a filter
+    TransactionFilter unsubscribe = 2;
+
+    // When include_mempool is true, new unconfirmed transactions from mempool are
+    // included apart from the ones confirmed in a block.
+    bool include_mempool = 3;
+
+    // When include_in_block is true, transactions are included when they are confirmed.
+    // This notification is sent in addition to any requested mempool notifications.
+    bool include_in_block = 4;
+
+    // When serialize_tx is true, transactions are serialized using
+    // bitcoin protocol encoding. Default is false, transaction will be Marshaled
+    // (see `Transaction`, `MempoolTransaction` and `TransactionNotification`)
+    bool serialize_tx = 5;
+}
+
+// Options to define data structure to be sent by SubscribeBlock stream:
+//
+//  - BlockInfo (block metadata): `BlockInfo`
+//      - SubscribeBlocksRequest {}
+//
+//  - Marshaled Block (with transaction hashes): `Block`
+//      - SubscribeBlocksRequest {
+//            full_block = true
+//        }
+//  - Marshaled Block (with full transaction data): `Block`
+//      - SubscribeBlocksRequest {
+//            full_block = true
+//            full_transactions = true
+//        }
+//  - Serialized Block acccording to bitcoin protocol encoding: `bytes`
+//      - SubscribeBlocksRequest {
+//            serialize_block = true
+//        }
+message SubscribeBlocksRequest {
+    // When full_block is true, a complete marshaled block is sent. See `Block`.
+    // Default is false, block metadata is sent. See `BlockInfo`.
+    bool full_block = 1;
+
+    // When full_transactions is true, provide full transaction info
+    // for a marshaled block.
+    // Default is false, only the transaction hashes are included for
+    // a marshaled block. See `TransactionData`.
+    bool full_transactions = 2;
+
+    // When serialize_block is true, blocks are serialized using bitcoin protocol encoding.
+    // Default is false, block will be Marshaled (see `BlockInfo` and `BlockNotification`)
+    bool serialize_block = 3;
+}
+
+message GetSlpTokenMetadataRequest {
+    repeated bytes token_ids = 1;
+}
+
+message GetSlpTokenMetadataResponse {
+    repeated SlpTokenMetadata token_metadata = 1;
+}
+
+message GetSlpParsedScriptRequest {
+    bytes slp_opreturn_script = 1;
+}
+
+message GetSlpParsedScriptResponse {
+    string parsing_error = 1;
+    bytes token_id = 2;
+    SlpAction slp_action = 3;
+    SlpTokenType token_type = 4;
+    oneof slp_metadata {
+        SlpV1GenesisMetadata v1_genesis = 5;    // NFT1 Group also uses this
+        SlpV1MintMetadata v1_mint = 6;          // NFT1 Group also uses this
+        SlpV1SendMetadata v1_send = 7;          // NFT1 Group also uses this
+        SlpV1Nft1ChildGenesisMetadata v1_nft1_child_genesis = 8;
+        SlpV1Nft1ChildSendMetadata v1_nft1_child_send = 9;
+    }
+}
+
+message GetSlpTrustedValidationRequest {
+    message Query {
+        bytes prev_out_hash = 1;
+        uint32 prev_out_vout = 2;
+        repeated bytes graphsearch_valid_hashes = 3;
+    }
+    repeated Query queries = 1;
+    bool include_graphsearch_count = 2;
+}
+
+message GetSlpTrustedValidationResponse {
+    message ValidityResult {
+        bytes prev_out_hash = 1;
+        uint32 prev_out_vout = 2;
+        bytes token_id = 3;
+        SlpAction slp_action = 4;
+        SlpTokenType token_type = 5;
+        oneof validity_result_type {
+            uint64 v1_token_amount = 6 [jstype = JS_STRING];
+            bool v1_mint_baton = 7;
+        }
+        bytes slp_txn_opreturn = 8;
+        uint32 graphsearch_txn_count = 9;
+    }
+
+    repeated ValidityResult results = 1;
+}
+
+message GetSlpGraphSearchRequest {
+    bytes hash = 1;
+    repeated bytes valid_hashes = 2;
+}
+
+message GetSlpGraphSearchResponse {
+    repeated bytes txdata = 1;
+}
+
+// NOTIFICATIONS
+
+message BlockNotification {
+    // State of the block in relation to the chain.
+    enum Type {
+        CONNECTED = 0;
+        DISCONNECTED = 1;
+    }
+
+    // Whether the block is connected to the chain.
+    Type type = 1;
+    oneof block {
+        // Marshaled block header data, as well as metadata stored by the node.
+        BlockInfo block_info = 2;
+        // A Block.
+        Block marshaled_block = 3;
+        // Binary block, serialized using bitcoin protocol encoding.
+        bytes serialized_block = 4;
+    }
+}
+
+message TransactionNotification {
+    // State of the transaction acceptance.
+    enum Type {
+        // A transaction in mempool.
+        UNCONFIRMED = 0;
+        // A transaction in a block.
+        CONFIRMED   = 1;
+    }
+
+    // Whether or not the transaction has been included in a block.
+    Type type = 1;
+    oneof transaction {
+        // A transaction included in a block.
+        Transaction confirmed_transaction = 2;
+        // A transaction in mempool.
+        MempoolTransaction unconfirmed_transaction = 3;
+        // Binary transaction, serialized using bitcoin protocol encoding.
+        bytes serialized_transaction = 4;
+    }
+}
+
+
+// DATA MESSAGES
+
+// Metadata for identifying and validating a block
+message BlockInfo {
+    // Identification.
+
+    // The double sha256 hash of the six header fields in the first 80 bytes
+    // of the block, when encoded according the bitcoin protocol, little-endian.
+    // sha256(sha256(encoded_header))
+    bytes hash = 1;
+    // The block number, an incremental index for each block mined.
+    int32 height = 2;
+
+    // Block header data.
+
+    // A version number to track software/protocol upgrades.
+    int32 version = 3;
+    // Hash of the previous block, little-endian.
+    bytes previous_block = 4;
+    // The root of the Merkle Tree built from all transactions in the block, little-endian.
+    bytes merkle_root = 5;
+    // When mining of the block started, expressed in seconds since 1970-01-01.
+    int64 timestamp = 6;
+    // Difficulty in Compressed Target Format.
+    uint32 bits = 7;
+    // A random value that was generated during block mining which happened to
+    // result in a computed block hash below the difficulty target at the time.
+    uint32 nonce = 8;
+
+    // Metadata.
+
+    // Number of blocks in a chain, including the block itself upon creation.
+    int32 confirmations = 9;
+    // Difficulty target at time of creation.
+    double difficulty = 10;
+    // Hash of the next block in this chain, little-endian.
+    bytes next_block_hash = 11;
+    // Size of the block in bytes.
+    int32 size = 12;
+    // The median block time of the latest 11 block timestamps.
+    int64 median_time = 13;
+}
+
+message Block {
+    message TransactionData {
+        oneof txids_or_txs {
+            // Just the transaction hash, little-endian.
+            bytes transaction_hash = 1;
+            // A marshaled transaction.
+            Transaction transaction = 2;
+        }
+    }
+    // Block header data, as well as metadata stored by the node.
+    BlockInfo info = 1;
+    // List of transactions or transaction hashes.
+    repeated TransactionData transaction_data = 2;
+}
+
+message Transaction {
+    message Input {
+        message Outpoint {
+            // The hash of the transaction containing the output to be spent, little-endian
+            bytes hash = 1;
+            // The index of specific output on the transaction.
+            uint32 index = 2;
+        }
+        // The number of the input, starting from zero.
+        uint32 index = 1;
+        // The related outpoint.
+        Outpoint outpoint = 2;
+        // An unlocking script asserting a transaction is permitted to spend
+        // the Outpoint (UTXO)
+        bytes signature_script = 3;
+        // As of BIP-68, the sequence number is interpreted as a relative
+        // lock-time for the input.
+        uint32 sequence = 4;
+        // Amount in satoshi.
+        int64 value = 5;
+        // The pubkey_script of the previous output that is being spent.
+        bytes previous_script = 6;
+        // The bitcoin addresses associated with this input.
+        string address = 7;
+        SlpToken slp_token = 8;
+        CashToken cash_token = 9;
+    }
+
+    message Output {
+        // The number of the output, starting from zero.
+        uint32 index = 1;
+        // The number of satoshis to be transferred.
+        int64 value = 2;
+        // The public key script used to pay coins.
+        bytes pubkey_script = 3;
+        // The bitcoin addresses associated with this output.
+        string address = 4;
+        // The type of script.
+        string script_class = 5;
+        // The script expressed in Bitcoin Cash Script.
+        string disassembled_script = 6;
+        SlpToken slp_token = 7;
+        CashToken cash_token = 8;
+    }
+
+    // The double sha256 hash of the encoded transaction, little-endian.
+    // sha256(sha256(encoded_transaction))
+    bytes hash = 1;
+    // The version of the transaction format.
+    int32 version = 2;
+    // List of inputs.
+    repeated Input inputs = 3;
+    // List of outputs.
+    repeated Output outputs = 4;
+    // The block height or timestamp after which this transaction is allowed.
+    // If value is greater than 500 million, it is assumed to be an epoch timestamp,
+    // otherwise it is treated as a block-height. Default is zero, or lock.
+    uint32 lock_time = 5;
+
+    // Metadata
+
+    // The size of the transaction in bytes.
+    int32 size = 8;
+    // When the transaction was included in a block, in epoch time.
+    int64 timestamp = 9;
+    // Number of blocks including proof of the transaction, including
+    // the block it appeared.
+    int32 confirmations = 10;
+    // Number of the block containing the transaction.
+    int32 block_height = 11;
+    // Hash of the block the transaction was recorded in, little-endian.
+    bytes block_hash = 12;
+
+    SlpTransactionInfo slp_transaction_info = 13;
+}
+
+message MempoolTransaction {
+    Transaction transaction = 1;
+    // The time when the transaction was added too the pool.
+    int64 added_time = 2;
+    // The block height when the transaction was added to the pool.
+    int32 added_height = 3;
+    // The total fee in satoshi the transaction pays.
+    int64 fee = 4;
+    // The fee in satoshi per kilobyte the transaction pays.
+    int64 fee_per_kb = 5;
+    // The priority of the transaction when it was added to the pool.
+    double starting_priority = 6;
+}
+
+message UnspentOutput {
+    // A reference to the output given by transaction hash and index.
+    Transaction.Input.Outpoint outpoint = 1;
+    // The public key script used to pay coins.
+    bytes pubkey_script = 2;
+    // The amount in satoshis
+    int64 value = 3;
+    // When is_coinbase is true, the output is the first in the block,
+    // a generation transaction, the result of mining.
+    bool is_coinbase = 4;
+    // The block number containing the UXTO.
+    int32 block_height = 5;
+
+    SlpToken slp_token = 6;
+    CashToken cash_token = 7;
+}
+
+message TransactionFilter {
+    // Filter by address(es)
+    repeated string addresses = 1;
+
+    // Filter by output hash and index.
+    repeated Transaction.Input.Outpoint outpoints = 2;
+
+    // Filter by data elements contained in pubkey scripts.
+    repeated bytes data_elements = 3;
+
+    // Subscribed/Unsubscribe to everything. Other filters
+    // will be ignored.
+    bool all_transactions = 4;
+
+    // Subscribed/Unsubscribe to everything slp. Other filters
+    // will be ignored, except this filter will be overriden by all_transactions=true
+    bool all_slp_transactions = 5;
+
+    // only transactions associated with the included tokenIds
+    repeated bytes slp_token_ids = 6;
+}
+
+// CashToken info used in transaction inputs / outputs
+//
+// WARNING: Some languages (e.g., JavaScript) may not properly handle the 'uint64'
+// for large amounts. For this reason, an annotation has been added for JS to
+// return a string for the amount field instead of casting uint64 to the JS 'number'
+// type. Other languages may require similar treatment.
+//
+message CashToken {
+    bytes category_id = 1;
+    uint64 amount = 2 [jstype = JS_STRING];
+    bytes commitment = 3;
+    bytes bitfield = 4;
+}
+
+// SlpToken info used in transaction inputs / outputs
+//
+// WARNING: Some languages (e.g., JavaScript) may not properly handle the 'uint64'
+// for large amounts. For this reason, an annotation has been added for JS to
+// return a string for the amount field instead of casting uint64 to the JS 'number'
+// type. Other languages may require similar treatment.
+//
+message SlpToken {
+    bytes token_id = 1;
+    uint64 amount = 2 [jstype = JS_STRING];
+    bool is_mint_baton = 3;
+    string address = 4;
+    uint32 decimals = 5;
+    SlpAction slp_action = 6;
+    SlpTokenType token_type = 7;
+}
+
+enum SlpTokenType {
+    VERSION_NOT_SET = 0;
+    V1_FUNGIBLE = 1;
+    V1_NFT1_CHILD = 65;
+    V1_NFT1_GROUP = 129;
+}
+
+// SlpTransactionInfo is used inside the Transaction message type.
+message SlpTransactionInfo {
+    SlpAction slp_action = 1;
+    enum ValidityJudgement {
+        UNKNOWN_OR_INVALID = 0;
+        VALID = 1;
+    }
+    ValidityJudgement validity_judgement = 2;
+    string parse_error = 3;
+    bytes token_id = 4;
+    enum BurnFlags {
+        BURNED_INPUTS_OUTPUTS_TOO_HIGH = 0;
+        BURNED_INPUTS_BAD_OPRETURN = 1;
+        BURNED_INPUTS_OTHER_TOKEN = 2;
+        BURNED_OUTPUTS_MISSING_BCH_VOUT = 3;
+        BURNED_INPUTS_GREATER_THAN_OUTPUTS = 4;
+    }
+    repeated BurnFlags burn_flags = 5;
+    oneof tx_metadata {
+        SlpV1GenesisMetadata v1_genesis = 6;    // NFT1 Group also uses this
+        SlpV1MintMetadata v1_mint = 7;          // NFT1 Group also uses this
+        SlpV1SendMetadata v1_send = 8;          // NFT1 Group also uses this
+        SlpV1Nft1ChildGenesisMetadata v1_nft1_child_genesis = 9;
+        SlpV1Nft1ChildSendMetadata v1_nft1_child_send = 10;
+    }
+}
+
+// SlpV1GenesisMetadata is used to marshal type 1 and NFT1 Group GENESIS OP_RETURN scriptPubKey
+message SlpV1GenesisMetadata {
+    bytes name = 1;
+    bytes ticker = 2;
+    bytes document_url = 3;
+    bytes document_hash = 4;
+    uint32 decimals = 5;
+    uint32 mint_baton_vout = 6;
+    uint64 mint_amount = 7 [jstype = JS_STRING];
+}
+
+// SlpV1MintMetadata is used to marshal type 1 MINT OP_RETURN scriptPubKey
+message SlpV1MintMetadata {
+    uint32 mint_baton_vout = 1;
+    uint64 mint_amount = 2 [jstype = JS_STRING];
+}
+
+// SlpV1SendMetadata is used to marshal type 1 and NFT1 Group SEND OP_RETURN scriptPubKey
+message SlpV1SendMetadata {
+    repeated uint64 amounts = 1 [jstype = JS_STRING];
+}
+
+// SlpV1Nft1ChildGenesisMetadata is used to marshal NFT1 Child GENESIS OP_RETURN scriptPubKey
+message SlpV1Nft1ChildGenesisMetadata {
+    bytes name = 1;
+    bytes ticker = 2;
+    bytes document_url = 3;
+    bytes document_hash = 4;
+    uint32 decimals = 5;
+    bytes group_token_id = 6;
+}
+
+// SlpV1Nft1ChildSendMetadata is used to marshal NFT1 Child SEND OP_RETURN scriptPubKey
+message SlpV1Nft1ChildSendMetadata {
+    bytes group_token_id = 1;
+}
+
+// SlpAction is used to allow clients to identify the type of slp transaction from this single field.
+//
+// NOTE: All enum types except for "NON_SLP" may be annotated with one or more BurnFlags.
+//
+enum SlpAction {
+    NON_SLP = 0;
+    NON_SLP_BURN = 1;
+    SLP_PARSE_ERROR = 2;
+    SLP_UNSUPPORTED_VERSION = 3;
+    SLP_V1_GENESIS = 4;
+    SLP_V1_MINT = 5;
+    SLP_V1_SEND = 6;
+    SLP_V1_NFT1_GROUP_GENESIS = 7;
+    SLP_V1_NFT1_GROUP_MINT = 8;
+    SLP_V1_NFT1_GROUP_SEND = 9;
+    SLP_V1_NFT1_UNIQUE_CHILD_GENESIS = 10;
+    SLP_V1_NFT1_UNIQUE_CHILD_SEND = 11;
+}
+
+// SlpTokenMetadata is used to marshal metadata about a specific TokenID
+message SlpTokenMetadata {
+	bytes token_id = 1;
+ 	SlpTokenType token_type = 2;
+	oneof type_metadata {
+        V1Fungible v1_fungible = 3;
+        V1NFT1Group v1_nft1_group = 4;
+        V1NFT1Child v1_nft1_child = 5;
+    }
+
+    // V1Fungible is used to marshal metadata specific to Type 1 token IDs
+    message V1Fungible {
+        string token_ticker = 1;
+        string token_name = 2;
+        string token_document_url = 3;
+        bytes token_document_hash = 4;
+        uint32 decimals = 5;
+        bytes mint_baton_hash = 6;
+        uint32 mint_baton_vout = 7;
+    }
+
+    // V1NFT1Group is used to marshal metadata specific to NFT1 Group token IDs
+    message V1NFT1Group {
+        string token_ticker = 1;
+        string token_name = 2;
+        string token_document_url = 3;
+        bytes token_document_hash = 4;
+        uint32 decimals = 5;
+        bytes mint_baton_hash = 6;
+        uint32 mint_baton_vout = 7;
+    }
+
+    // V1NFT1Child is used to marshal metadata specific to NFT1 Child token IDs
+    message V1NFT1Child {
+        string token_ticker = 1;
+        string token_name = 2;
+        string token_document_url = 3;
+        bytes token_document_hash = 4;
+        bytes group_id = 5;
+    }
+}
+
+// SlpRequiredBurn is used by clients to allow token burning
+message SlpRequiredBurn {
+    Transaction.Input.Outpoint outpoint = 1;
+    bytes token_id = 2;
+    SlpTokenType token_type = 3;
+    oneof burn_intention {
+        uint64 amount = 4 [jstype = JS_STRING];
+        uint32 mint_baton_vout = 5;
+    }
+}
diff --git a/packages/cashscript/src/network/BchdGrpcNetworkProvider/index.ts b/packages/cashscript/src/network/BchdGrpcNetworkProvider/index.ts
new file mode 100644
index 00000000..190b58be
--- /dev/null
+++ b/packages/cashscript/src/network/BchdGrpcNetworkProvider/index.ts
@@ -0,0 +1,84 @@
+import { binToHex, hexToBin } from '@bitauth/libauth';
+import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport';
+import { Utxo, Network, TokenDetails } from '../../interfaces.js';
+import NetworkProvider from '../NetworkProvider.js';
+import { bchrpcClient } from './generated/bchrpc.client.js';
+
+type NFTCapabilities = NonNullable<TokenDetails["nft"]>["capability"];
+
+export default class BchdGrpcNetworkProvider implements NetworkProvider {
+  client: bchrpcClient;
+
+  constructor(public network: Network, baseUrl: string) {
+    const transport = new GrpcWebFetchTransport({ baseUrl });
+    this.client = new bchrpcClient(transport);
+  }
+
+  private parseNFTCapabilities(bitfield: number): NFTCapabilities {
+    const nftCapabilities = bitfield & 0b1111;
+    switch (nftCapabilities) {
+      case 0:
+        return 'none';
+      case 1:
+        return 'mutable';
+      case 2:
+        return 'minting';
+      default:
+        throw new Error(`Invalid NFT capabilities: ${nftCapabilities}`);
+    }
+  }
+
+  async getUtxos(address: string): Promise<Utxo[]> {
+    const { response } = await this.client.getAddressUnspentOutputs({
+      address,
+      includeMempool: true,
+      includeTokenMetadata: false,
+    });
+
+    const utxos = response.outputs.map(
+      (utxo) =>
+        ({
+          txid: binToHex(utxo.outpoint!.hash.reverse()),
+          vout: utxo.outpoint!.index,
+          satoshis: utxo.value,
+          token: utxo.cashToken
+            ? {
+                category: binToHex(utxo.cashToken.categoryId.reverse()),
+                amount: BigInt(utxo.cashToken.amount),
+                nft: utxo.cashToken.commitment
+                  ? {
+                      capability: this.parseNFTCapabilities(utxo.cashToken.bitfield[0]),
+                      commitment: binToHex(utxo.cashToken.commitment),
+                    }
+                  : undefined,
+              }
+            : undefined,
+        } satisfies Utxo)
+    );
+
+    return utxos;
+  }
+
+  async getBlockHeight(): Promise<number> {
+    const { response } = await this.client.getBlockchainInfo({});
+    return response.bestHeight;
+  }
+
+  async getRawTransaction(txid: string): Promise<string> {
+    const { response } = await this.client.getRawTransaction({
+      hash: hexToBin(txid).reverse(),
+    });
+
+    return binToHex(response.transaction);
+  }
+
+  async sendRawTransaction(txHex: string): Promise<string> {
+    const { response } = await this.client.submitTransaction({
+      transaction: hexToBin(txHex),
+      requiredSlpBurns: [],
+      skipSlpValidityCheck: true,
+    });
+
+    return binToHex(response.hash);
+  }
+}
diff --git a/packages/cashscript/src/network/index.ts b/packages/cashscript/src/network/index.ts
index 8d31ddde..38061724 100644
--- a/packages/cashscript/src/network/index.ts
+++ b/packages/cashscript/src/network/index.ts
@@ -3,3 +3,4 @@ export { default as BitcoinRpcNetworkProvider } from './BitcoinRpcNetworkProvide
 export { default as ElectrumNetworkProvider } from './ElectrumNetworkProvider.js';
 export { default as FullStackNetworkProvider } from './FullStackNetworkProvider.js';
 export { default as MockNetworkProvider } from './MockNetworkProvider.js';
+export { default as BchdGrpcNetworkProvider } from './BchdGrpcNetworkProvider/index.js';
diff --git a/packages/utils/src/artifact.ts b/packages/utils/src/artifact.ts
index ab3b99ac..17c45a00 100644
--- a/packages/utils/src/artifact.ts
+++ b/packages/utils/src/artifact.ts
@@ -1,4 +1,4 @@
-import fs, { PathLike } from 'fs';
+import fs, { PathLike } from 'node:fs';
 
 export interface AbiInput {
   name: string;
diff --git a/yarn.lock b/yarn.lock
index 15fa8ffb..1db06aaf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2533,6 +2533,50 @@
   dependencies:
     "@types/node" ">= 8"
 
+"@protobuf-ts/grpcweb-transport@^2.9.4":
+  version "2.9.4"
+  resolved "https://registry.yarnpkg.com/@protobuf-ts/grpcweb-transport/-/grpcweb-transport-2.9.4.tgz#e98d543d2838e95d5cec1a22f23a02de88393401"
+  integrity sha512-6aQgwPTgX6FkqWqmNts3uk8T/C5coJoH7U87zgaZY/Wo2EVa9SId5bXTM8uo4WR+CN8j9W4c9ij1yG13Hc3xUw==
+  dependencies:
+    "@protobuf-ts/runtime" "^2.9.4"
+    "@protobuf-ts/runtime-rpc" "^2.9.4"
+
+"@protobuf-ts/plugin-framework@^2.9.4":
+  version "2.9.4"
+  resolved "https://registry.yarnpkg.com/@protobuf-ts/plugin-framework/-/plugin-framework-2.9.4.tgz#d7a617dedda4a12c568fdc1db5aa67d5e4da2406"
+  integrity sha512-9nuX1kjdMliv+Pes8dQCKyVhjKgNNfwxVHg+tx3fLXSfZZRcUHMc1PMwB9/vTvc6gBKt9QGz5ERqSqZc0++E9A==
+  dependencies:
+    "@protobuf-ts/runtime" "^2.9.4"
+    typescript "^3.9"
+
+"@protobuf-ts/plugin@^2.9.4":
+  version "2.9.4"
+  resolved "https://registry.yarnpkg.com/@protobuf-ts/plugin/-/plugin-2.9.4.tgz#4e593e59013aaad313e7abbabe6e61964ef0ca28"
+  integrity sha512-Db5Laq5T3mc6ERZvhIhkj1rn57/p8gbWiCKxQWbZBBl20wMuqKoHbRw4tuD7FyXi+IkwTToaNVXymv5CY3E8Rw==
+  dependencies:
+    "@protobuf-ts/plugin-framework" "^2.9.4"
+    "@protobuf-ts/protoc" "^2.9.4"
+    "@protobuf-ts/runtime" "^2.9.4"
+    "@protobuf-ts/runtime-rpc" "^2.9.4"
+    typescript "^3.9"
+
+"@protobuf-ts/protoc@^2.9.4":
+  version "2.9.4"
+  resolved "https://registry.yarnpkg.com/@protobuf-ts/protoc/-/protoc-2.9.4.tgz#a92262ee64d252998540238701d2140f4ffec081"
+  integrity sha512-hQX+nOhFtrA+YdAXsXEDrLoGJqXHpgv4+BueYF0S9hy/Jq0VRTVlJS1Etmf4qlMt/WdigEes5LOd/LDzui4GIQ==
+
+"@protobuf-ts/runtime-rpc@^2.9.4":
+  version "2.9.4"
+  resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.9.4.tgz#d6ab2316c0ba67ce5a08863bb23203a837ff2a3b"
+  integrity sha512-y9L9JgnZxXFqH5vD4d7j9duWvIJ7AShyBRoNKJGhu9Q27qIbchfzli66H9RvrQNIFk5ER7z1Twe059WZGqERcA==
+  dependencies:
+    "@protobuf-ts/runtime" "^2.9.4"
+
+"@protobuf-ts/runtime@^2.9.4":
+  version "2.9.4"
+  resolved "https://registry.yarnpkg.com/@protobuf-ts/runtime/-/runtime-2.9.4.tgz#db8a78b1c409e26d258ca39464f4757d804add8f"
+  integrity sha512-vHRFWtJJB/SiogWDF0ypoKfRIZ41Kq+G9cEFj6Qm1eQaAhJ1LDFvgZ7Ja4tb3iLOQhz0PaoPnnOijF1qmEqTxg==
+
 "@psf/bch-js@^6.8.0":
   version "6.8.0"
   resolved "https://registry.yarnpkg.com/@psf/bch-js/-/bch-js-6.8.0.tgz#c7b4c77a52c82795df4f249c78ad11d55ffc6af4"
@@ -10733,6 +10777,11 @@ typeforce@^1.18.0:
   resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
   integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
 
+typescript@^3.9:
+  version "3.9.10"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
+  integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
+
 typescript@^5.7.3:
   version "5.7.3"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"