From 257cea9606fc23e979f1b9e0ecd2d83f8fd76785 Mon Sep 17 00:00:00 2001 From: Mathieu Geukens Date: Mon, 8 Jun 2026 15:09:32 +0200 Subject: [PATCH 1/5] Improve WalletConnect, deployment, and optimization docs --- .cspell.json | 1 + website/docs/guides/deployment.md | 164 +++++++++---------- website/docs/guides/optimization.md | 4 +- website/docs/guides/walletconnect.md | 236 ++++++++++++++++++++++----- 4 files changed, 274 insertions(+), 131 deletions(-) diff --git a/.cspell.json b/.cspell.json index 45a20d26..3c4ac474 100644 --- a/.cspell.json +++ b/.cspell.json @@ -50,6 +50,7 @@ "cryptocurrency", "collateralized", "datasig", + "defi", "deserialisation", "docblock", "electroncash", diff --git a/website/docs/guides/deployment.md b/website/docs/guides/deployment.md index f5cccb1a..1e8bfe2e 100644 --- a/website/docs/guides/deployment.md +++ b/website/docs/guides/deployment.md @@ -3,21 +3,21 @@ title: Contract Deployment sidebar_label: Contract Deployment --- -## When Do You Need a Deployment? +Not every CashScript contract needs a deployment transaction. In the UTXO model, many UTXOs can live on the same contract address and use the same spending rules. For contracts like multisig wallets, vaults, and escrows, you can compile the contract, share the address, and send funds to it. The contract is ready to use as soon as you know its address. -In the UTXO model, multiple UTXOs can live on the same address and are spendable under the same conditions. Many contracts, like multisig wallets, vaults, or escrows, work exactly this way. You compile the contract, share the address, and anyone can send funds to it. There is no "deployment" step: the contract is ready to use the moment you know its address. - -Deployment becomes necessary when you're building **stateful contract systems**. In these systems, a unique CashToken category (token ID) identifies the contract and its state. The token ID is created through a special genesis transaction, and the contract UTXOs are initialized with the right tokens, capabilities, and state. This is what we mean by "deploying" a contract. +Deployment is only needed for **stateful contract systems** where CashTokens authenticate contract state. In these systems, a genesis transaction creates one or more token categories and initializes the contract UTXOs with the right token amounts, NFT capabilities, and NFT commitments. :::tip -If your contract simply enforces spending conditions, you don't need a deployment. Just use the contract address. Deployment is for systems where CashTokens authenticate and track contract state. +If your contract only enforces spending conditions, use the contract address directly. Deployment is for systems where CashTokens identify and track contract state. ::: -## Before the Genesis Transaction +## Preparing a Deployment + +Before constructing the genesis transaction, you need to know which constructor arguments, token IDs, setup UTXOs, initial state, and permanent addresses the deployment will use. ### Contract Addresses -CashScript contract addresses are **deterministic**: they are derived from the contract's compiled bytecode combined with the constructor arguments. Given the same artifact and the same arguments, you will always get the same address. +CashScript contract addresses are deterministic. They are derived from the compiled artifact and the constructor arguments. Given the same artifact and arguments, the SDK produces the same address every time. ```ts import { Contract } from 'cashscript'; @@ -25,93 +25,93 @@ import artifact from './my_contract.artifact.js'; const constructorArgs = [oraclePublicKey, startBlockHeight] as const; const contract = new Contract(artifact, [...constructorArgs], { provider }); -console.log(contract.address); // same inputs → same address every time -console.log(contract.tokenAddress); // CashToken-aware address + +console.log(contract.address); // same inputs produce the same address +console.log(contract.tokenAddress); // CashToken-aware address ``` -This means you can reconstruct a contract's address at any time, on any machine, without querying the blockchain, as long as you have the artifact and the constructor arguments. This property is essential for [verifying deployments](#verifying-a-deployment). +This means you can reconstruct a contract address on any machine without querying the blockchain, as long as you have the artifact and constructor arguments. This property is essential for [verifying deployments](#verifying-a-deployment). -### Preparing Constructor Arguments +### Constructor Arguments -Some constructor arguments are straightforward constants, others require preparation before deployment: +Some constructor arguments are simple constants. Others need to be prepared before deployment: -- **Token IDs from other categories** — in complex systems with multiple token categories, contracts often reference each other's token IDs as constructor arguments. -- **Locking bytecodes from other contracts** — in multi-contract systems, one contract may reference another by locking bytecode. This creates a dependency chain: you must instantiate the referenced contract first, extract its locking bytecode, and pass it as a constructor argument to the dependent contract. -- **Public keys** — for contracts that validate signed messages (e.g. from an oracle provider), you need the signer's public key. For owner authentication, you need to derive the public key hash from a keypair ahead of time. +- **Token IDs from other categories**: multi-contract systems often pass other token category IDs as constructor arguments. +- **Locking bytecodes from other contracts**: one contract may authenticate another by locking bytecode. Instantiate the referenced contract first, then pass its `lockingBytecode` to the dependent contract. +- **Public keys**: oracle or owner checks often require a public key or public key hash that must be derived before deployment. ```ts -import { binToHex, cashAddressToLockingBytecode } from '@bitauth/libauth'; +import { Contract } from 'cashscript'; -// Reverse byte order of a hex string (big-endian ↔ little-endian) +// Reverse byte order of a hex string. function reverseHex(hex: string): string { return hex.match(/../g)!.reverse().join(''); } -// Token IDs must be byte-reversed for use as constructor args +// Token IDs are often byte-reversed before being used in contract arguments. const argsA = [reverseHex(tokenIdX), reverseHex(tokenIdY)] as const; const contractA = new Contract(artifactA, [...argsA], { provider }); -// Extract locking bytecode to pass to a dependent contract +// Pass contractA's locking bytecode to a dependent contract. const argsB = [contractA.lockingBytecode, oraclePublicKey, startBlockHeight] as const; const contractB = new Contract(artifactB, [...argsB], { provider }); ``` :::caution -Double-check all arguments before deploying. Some parameters, like fee destination addresses, are permanent once deployed and cannot be changed. The wallets behind these addresses require proper creation, backup, and security before deployment. +Double-check all constructor arguments before deploying. Some values, such as fee destination addresses or authority keys, may be permanent once the contract system is live. ::: -### Token IDs +### Token IDs and vout0 UTXOs -In Bitcoin Cash, a new token category can be created by spending a UTXO with `vout: 0`. The resulting token ID equals the txid of the UTXO with `vout: 0`. So if you already know which UTXO you will use for the genesis transaction, you already know what the token ID will be and can use it directly. +On Bitcoin Cash, a new token category can be created by spending a UTXO at output index `0`. The token ID equals the txid of that UTXO. If you know which `vout: 0` UTXO will be used for the genesis transaction, you already know the token ID that transaction can create. + +For deployments with multiple token categories, prepare one `vout: 0` UTXO per token category. This lets you know every token ID before instantiating contracts that reference those IDs. Once the setup UTXOs are prepared, the genesis transactions can be broadcast in parallel if they do not spend from each other. ### Setup Wallet -To create vout0 UTXOs and broadcast the genesis transaction, you need a funded wallet. This is typically a standard P2PKH wallet derived with enough BCH to fund all contract outputs (each needs at least dust amount, typically 1000 sats). +Use a funded setup wallet to create the `vout: 0` UTXOs and broadcast the genesis transaction. Each contract output also needs enough BCH for dust, commonly 1000 sats. -The setup wallet may already have UTXOs at vout 0, but usually you need to prepare them. You can do this by sending BCH from the setup wallet back to itself as the sole output of a transaction. Since it's the only output, it will be at index 0. +The setup wallet may already have suitable `vout: 0` UTXOs. If not, send BCH from the setup wallet back to itself as the only output of a transaction. Since it is the only output, it will be at index `0`. ```ts import { TransactionBuilder, SignatureTemplate, ElectrumNetworkProvider } from 'cashscript'; import type { Utxo } from 'cashscript'; -// Helper to create a vout0 UTXO by sending BCH to yourself +// Create a vout0 UTXO by sending BCH to yourself. async function createVout0( provider: ElectrumNetworkProvider, address: string, utxos: Utxo[], template: SignatureTemplate, - amount: bigint, ): Promise { - // Pick a non-vout0 UTXO to avoid consuming an existing vout0 - const selectedUtxo = utxos.find(utxo => utxo.satoshis > amount && utxo.vout !== 0 && !utxo.token); + const selectedUtxo = utxos.find(utxo => utxo.vout !== 0 && !utxo.token); if (!selectedUtxo) throw new Error('No eligible UTXO available'); + const amount = selectedUtxo.satoshis - 500n; const txBuilder = new TransactionBuilder({ provider }); txBuilder.addInput(selectedUtxo, template.unlockP2PKH()); - txBuilder.addOutput({ to: address, amount: selectedUtxo.satoshis - 500n }); - const txDetails = await txBuilder.send(); + txBuilder.addOutput({ to: address, amount }); - return { satoshis: selectedUtxo.satoshis - 500n, txid: txDetails.txid, vout: 0 }; + const txDetails = await txBuilder.send(); + return { satoshis: amount, txid: txDetails.txid, vout: 0 }; } ``` -For deployments with multiple token categories, you need multiple vout0 UTXOs, one per token category you want to create. Since contracts may reference each other's token IDs as constructor arguments, it is recommended to prepare all vout0 UTXOs first so that every token ID is known before instantiating any contracts. The genesis transactions themselves can then be broadcast in parallel since they have no inter-transaction dependencies. - :::tip -Always test your full deployment flow on chipnet before mainnet. Validating your transaction structure, constructor arguments, and initial state encoding on chipnet first avoids costly mistakes. +Test the full deployment flow on chipnet before mainnet. This validates your transaction structure, constructor arguments, and state encoding before anything has permanent value. ::: ## Genesis Transaction -The genesis transaction creates the CashToken category and distributes the initial tokens to contract addresses. This typically includes: +The genesis transaction creates the CashToken category and sends the initial token outputs to the relevant contract addresses. It can include: -- **Fungible tokens** — the initial token supply distributed to contract addresses -- **NFTs with minting capability** — for contracts that need to create new NFTs during operation -- **NFTs with mutable capability** — for contracts that store updatable state in the NFT commitment -- **NFTs with no capability (immutable)** — for contracts that carry fixed identifying data +- **Fungible tokens**: the initial token supply. +- **Minting NFTs**: authority for contracts that need to create new NFTs later. +- **Mutable NFTs**: updatable contract state stored in NFT commitments. +- **Immutable NFTs**: fixed identifying data. :::info -When fungible token supply will be locked inside covenants, it is common to mint the maximum possible amount (`9223372036854775807`). This is only safe when the covenants strictly enforce the actual circulating supply. Minting the max avoids needing to predict future supply needs. +When fungible token supply will be locked inside covenants, it is common to mint the maximum possible amount (`9223372036854775807`). This is only safe when the covenants strictly enforce the actual circulating supply. ::: ```ts @@ -136,23 +136,23 @@ txBuilder.addOutput({ nft: { capability: 'minting', commitment: initialStateHex }, }, }); -// ... add more outputs as needed +// Add more outputs as needed. txBuilder.addBchChangeOutputIfNeeded({ to: changeAddress, feeRate: 1.0 }); const txDetails = await txBuilder.send(); ``` -### Initial State via NFT Commitments +### Initial State -NFT commitments are used to encode the initial state of a contract at deployment. For example, a contract might store a starting counter, a block height, or a configuration value in its NFT commitment. The commitment is a hex-encoded byte string that the contract's covenant logic knows how to read and update. +NFT commitments are commonly used to encode the initial state of a contract at deployment. For example, a contract might store a starting counter, block height, or configuration value in the NFT commitment. -Use `@bitauth/libauth` to encode values into commitment bytes. This ensures values follow the same VM number encoding that the Bitcoin Cash VM uses: +Use the same encoding that your contract expects. For VM number values, use `@cashscript/utils` and `@bitauth/libauth` helpers to avoid mismatches. ```ts import { binToHex } from '@bitauth/libauth'; import { encodeIntAsFixedBytes } from '@cashscript/utils'; -// Encode initial state as a commitment (e.g. 4-byte counter + 4-byte blockHeight) +// Encode initial state as 4-byte counter + 4-byte block height. function encodeInitialState(counter: bigint, blockHeight: bigint): string { const encodedCounter = encodeIntAsFixedBytes(counter, 4); const encodedBlockHeight = encodeIntAsFixedBytes(blockHeight, 4); @@ -160,9 +160,11 @@ function encodeInitialState(counter: bigint, blockHeight: bigint): string { } ``` -### UTXO Duplication +### Duplicate Contract UTXOs + +For systems expecting [concurrent usage](/docs/guides/concurrency), create multiple identical contract UTXOs in the genesis transaction. Each duplicate sits at the same contract address with the same token type, allowing independent transactions to spend different UTXOs without conflicting. -For systems expecting [concurrent usage](/docs/guides/concurrency), you can create multiple identical contract UTXOs in the same genesis transaction. Each duplicate UTXO sits on the same contract address with the same token type, allowing independent transactions to spend different UTXOs without conflicting. When duplicating contract UTXOs that hold fungible tokens, the total supply should be evenly distributed across them. The last UTXO can absorb the remainder to ensure the total is exact: +When duplicating UTXOs that hold fungible tokens, distribute the total supply exactly across them: ```ts const supplyPerUtxo = MAX_TOKEN_SUPPLY / BigInt(numberOfDuplicates); @@ -170,42 +172,35 @@ const remainder = MAX_TOKEN_SUPPLY % BigInt(numberOfDuplicates); const supplyLastUtxo = supplyPerUtxo + remainder; ``` -### BCMR Authchain +### BCMR Metadata -The [Bitcoin Cash Metadata Registry (BCMR)](https://cashtokens.org/docs/bcmr/chip/) is the standard for associating metadata (name, ticker, decimals, icon) with CashToken categories. The metadata is resolved through an authchain, a chain of transactions starting from a specific output in the genesis transaction. +The [Bitcoin Cash Metadata Registry (BCMR)][bcmr] is the standard for associating metadata with CashToken categories, such as name, ticker, decimals, and icon. Wallets and indexers resolve this metadata through an authchain. -To set up the authchain during deployment, include a dust output (e.g. 1000 sats) at output index 0 to a designated authchain address as part of the genesis transaction. This output becomes the starting point for metadata resolution. Wallets and indexers follow the authchain from this output to find the latest BCMR metadata. +To start the authchain during deployment, include a dust output at index `0` to a designated authchain address. This output becomes the starting point for metadata resolution. ```ts -// Include a dust output for the BCMR authchain +// Include a dust output for the BCMR authchain. txBuilder.addOutput({ to: bcmrAuthchainAddress, amount: 1000n }); ``` -Metadata can be published in the genesis transaction itself by including an OP_RETURN output with the BCMR protocol identifier, the SHA-256 hash of the registry JSON, and the registry URL. Alternatively, the metadata can be published in a later authchain transaction. This approach is simpler since you don't need to know the registry hash at genesis time. See the [CashTokens guide](/docs/guides/cashtokens#cashtokens-bcmr-metadata) for more on BCMR metadata and tooling. +Metadata can be published in the genesis transaction with an `OP_RETURN` output containing the BCMR protocol identifier, registry hash, and registry URL. It can also be published later in an authchain transaction. See the [CashTokens guide](/docs/guides/cashtokens#cashtokens-bcmr-metadata) for more on BCMR metadata and tooling. :::note -In a two-step deployment, you prepare the vout0 UTXOs first (learning the token IDs), publish the BCMR registry, then broadcast the genesis transaction with the registry hash in the OP_RETURN. This avoids needing a follow-up authchain update. +A two-step deployment can avoid a follow-up authchain update: prepare the `vout: 0` UTXO first, publish the BCMR registry, then broadcast the genesis transaction with the registry hash in the `OP_RETURN`. ::: -## Post-Deployment +## After Deployment Once the genesis transaction is broadcast: -- **Save the deployment configuration** — persist the final [deployment config](#deployment-configuration) (token IDs, constructor args, network) so it can be referenced by application code, verification scripts, and future deployments. -- **Verify BCMR indexing** — if you published BCMR metadata, check that a BCMR indexer has resolved your token metadata correctly. Wallets rely on this for displaying token names and icons. -- **Verify the deployment** — if you built a standalone [verification script](#verifying-a-deployment), run it against the live deployment to confirm everything matches. -- **Set up infrastructure** — see the [infrastructure guide](/docs/guides/infrastructure) for guidance on storing contract details and setting up transaction servers. +- **Save the deployment configuration**: persist token IDs, constructor arguments, network, artifact version, and other parameters. +- **Verify BCMR indexing**: check that a BCMR indexer resolves your token metadata correctly. +- **Verify the deployment**: run a standalone verification script against the live deployment. +- **Set up infrastructure**: see the [infrastructure guide](/docs/guides/infrastructure) for storing contract details and setting up transaction servers. ## Deployment Configuration -After the genesis transaction, it's worth capturing all deployment parameters into a typed configuration object. This makes deployments reproducible and easy to reference in application code, verification scripts, and tests. - -A deployment configuration typically includes: - -- **Name and network** — a human-readable identifier and whether it targets `mainnet` or `chipnet` -- **Contract version** — which version of the contract artifacts are being deployed -- **Token IDs** — the CashToken category IDs created during genesis -- **Contract parameters** — the constructor arguments used to instantiate the contracts +Capture deployment parameters in a typed configuration object. This makes deployments reproducible and easy to reference from application code, verification scripts, and tests. ```ts interface MyDeployment { @@ -218,35 +213,33 @@ interface MyDeployment { contractParams: { oraclePublicKey: string; startBlockHeight: bigint; - // ... other params + // Add other params here. }; } ``` -Maintaining named deployment objects lets you keep multiple deployments side by side: production, staging, and testing variants with different parameters (e.g. testing oracles), so you can iterate quickly during development before committing to a mainnet deployment. +Use your deployment configuration as the single source of truth for verification scripts, application code, and documentation. :::tip -Use your deployment configuration as the single source of truth for verification scripts, application code, and documentation. If the deployment config is correct, everything downstream can be derived from it. +Maintaining named deployment objects lets you keep production, staging, and testing deployments side by side. This makes it easy to use different parameters, such as testing oracles, while iterating before a mainnet deployment. ::: ## Verifying a Deployment -A contract system is only trustless if its deployment can be independently verified. Deployment scripts may not be open source, and even if they are, there is no guarantee the published script is what was actually used. Contract developers should provide standalone verification scripts so that security researchers and technical users can independently confirm a deployment's correctness. +A contract system is only trustless if its deployment can be independently verified. Deployment scripts may not be open source, and even if they are, users still need a way to verify what exists on-chain. -Since contract addresses are deterministic, verification works by reconstructing every expected address from the contract artifacts and deployment parameters, then comparing the result against what actually exists on-chain. +Since contract addresses are deterministic, verification works by reconstructing every expected address from the artifacts and deployment parameters, then comparing the result against the genesis transaction outputs. ```ts import { Contract } from 'cashscript'; import artifact from './my_contract.artifact.js'; -// Reconstruct all contract addresses from the deployment config const constructorArgs = [ deployment.contractParams.oraclePublicKey, deployment.contractParams.startBlockHeight, ] as const; -const contract = new Contract(artifact, [...constructorArgs], { provider }); -// This should match the address where tokens were sent +const contract = new Contract(artifact, [...constructorArgs], { provider }); const expectedAddress = contract.tokenAddress; ``` @@ -256,32 +249,31 @@ Provide verification scripts as part of your project, not bundled into the deplo ### What to Verify -Verification should inspect every output of the genesis transaction, not just the ones you expect. If tokens are sent to an unexpected address, like a regular P2PKH, this could give full authority over the token category to the deployer. For example, a minting NFT on a personal address means the holder can mint unlimited tokens outside the contract's rules. +Verification should inspect every output of the genesis transaction, not just the outputs you expect. If tokens are sent to an unexpected address, such as a regular P2PKH address, that address may hold authority over the token category. -Build a list of all expected contract addresses, then iterate over all outputs and check: +Check at least the following: -1. **No unexpected token outputs** — every output carrying token data must go to a known contract address -2. **Token category** — do the outputs carry the correct token ID? -3. **NFT capability** — is it `minting`, `mutable`, or `none` as expected for each contract address? -4. **NFT commitment** — does it contain the correct initial state? -5. **Fungible token amount** — is the total supply distributed correctly? Fungible tokens should only appear on the expected contract addresses. -6. **Genesis transaction inputs** — does the first input's previous txid match the expected token ID? This confirms the token was genuinely created in this transaction. +1. **Token outputs**: every token output goes to a known contract address or expected authchain address. +2. **Token category**: each token output uses the expected token ID. +3. **NFT capability**: each NFT has the expected capability, such as `minting`, `mutable`, or `none`. +4. **NFT commitment**: commitments contain the expected initial state. +5. **Fungible token amount**: total supply is distributed correctly. +6. **Genesis input**: the first input's previous txid matches the expected token ID. ```ts -// Build the list of all expected contract addresses const expectedAddresses = new Set([ contractA.tokenAddress, contractB.tokenAddress, - // ... all expected contract addresses + // Add all expected contract addresses. ]); -// Iterate over ALL outputs of the genesis transaction for (const output of genesisTxOutputs) { if (output.tokenData && !expectedAddresses.has(output.address)) { throw new Error(`Unexpected token output to address: ${output.address}`); } - // Validate each known address has the right capability, commitment, and amount + // Validate capability, commitment, and amount for each known address. } ``` +[bcmr]: https://cashtokens.org/docs/bcmr/chip/ diff --git a/website/docs/guides/optimization.md b/website/docs/guides/optimization.md index 1bddc811..e19e0b09 100644 --- a/website/docs/guides/optimization.md +++ b/website/docs/guides/optimization.md @@ -176,7 +176,7 @@ If you use hand-optimized `bytecode` in your Contract's artifact, the `debug` in ::: :::tip -You can create an `Artifact` for a fully hand-written contract so it becomes possible to use the contract with the nice features of the CashScript SDK! An example of this is [Cauldron_Swap_Test][Cauldron_Swap_Test] which uses `Artifact bytecode` not produced by `cashc` at all but still uses the CashScript SDK. +You can create an `Artifact` for a fully hand-written contract so it becomes possible to use the contract with the nice features of the CashScript SDK! An example of this is the [unofficial Cauldron Swap SDK][Cauldron-Swap-SDK], which uses `Artifact bytecode` not produced by `cashc` at all but still uses the CashScript SDK. ::: ### Method 2) Custom Unlockers @@ -204,5 +204,5 @@ interface GenerateUnlockingBytecodeOptions { [BitauthIDE]: https://ide.bitauth.com -[Cauldron_Swap_Test]: https://github.com/mr-zwets/Cauldron_Swap_Test +[Cauldron-Swap-SDK]: https://github.com/mr-zwets/Cauldron-Swap-SDK [addInput()]: /docs/sdk/transaction-builder#addinput diff --git a/website/docs/guides/walletconnect.md b/website/docs/guides/walletconnect.md index 37974809..3a5bc8b2 100644 --- a/website/docs/guides/walletconnect.md +++ b/website/docs/guides/walletconnect.md @@ -2,19 +2,21 @@ title: WalletConnect --- -The BCH WalletConnect spec lays out a BCH-specific API for how Bitcoin Cash dapps can communicate to BCH Wallets. BCH WalletConnect uses the generic 'WalletConnect' transport layer but the contents being communicated is what's BCH-specific. The standard was designed with CashScript smart contract usage in mind. +CashScript can prepare transactions for both BCH WalletConnect and WizardConnect. For smart contract dapps, the main differences are that BCH WalletConnect is a single-address wallet protocol with transaction and message signing, while WizardConnect is HD-wallet-aware and can support custom extensions but does not provide a generic message-signing method. -The BCH WalletConnect standard is supported in multiple wallets and a wide range of dapps. You can find a full list of Bitcoin Cash dapps supporting WalletConnect on [Tokenaut.cash](https://tokenaut.cash/dapps?filter=walletconnect). +## BCH WalletConnect + +The BCH WalletConnect spec lays out a BCH-specific API for how Bitcoin Cash dapps can communicate with BCH wallets. BCH WalletConnect uses the generic WalletConnect transport layer, but the messages being exchanged are Bitcoin Cash-specific. + +The standard is supported in multiple wallets and dapps. You can find a list of Bitcoin Cash dapps supporting WalletConnect on [Tokenaut.cash]. :::tip -The specification is called ['wc2-bch-bcr'](https://github.com/mainnet-pat/wc2-bch-bcr) and has extra discussion and info on the [BCH research forum](https://bitcoincashresearch.org/t/wallet-connect-v2-support-for-bitcoincash/). +The specification is called [`wc2-bch-bcr`][wc2-bch-bcr] and has extra discussion on the [BCH research forum]. ::: -## signTransaction Interface - -Most relevant for smart contract usage is the BCH-WalletConnect `signTransaction` interface. +### signTransaction Interface -> This is a most generic interface to propose a bitcoincash transaction to a wallet which reconstructs it and signs it on behalf of the wallet user. +Most relevant for smart contract usage is the BCH WalletConnect `signTransaction` interface. ```ts signTransaction: (wcTransactionObj: WcTransactionObject) => Promise; @@ -45,83 +47,81 @@ interface SignedTxObject { } ``` -To use the BCH WalletConnect `signTransaction` API, we need to pass a `wcTransactionObj`. -CashScript `TransactionBuilder` has a `generateWcTransactionObject` method for creating this object. +CashScript `TransactionBuilder` has a `generateWcTransactionObject()` method for creating the `WcTransactionObject`. -Below we show 2 examples, the first example using spending a user-input and in the second example spending from a user-contract with placeholders for `userPubKey` and `userSig` +### Spending User Inputs -### Spending a user-input - -Below is example code from the `CreateContract` code of the 'Hodl Vault' dapp repository, [link to source code](https://github.com/mr-zwets/bch-hodl-dapp/blob/main/src/views/CreateContract.vue#L14). +Use `placeholderP2PKHUnlocker(userAddress)` for P2PKH inputs that should be signed by the connected wallet. ```ts import { TransactionBuilder, placeholderP2PKHUnlocker } from "cashscript"; -async function proposeWcTransaction(userAddress: string){ - // use a placeholderUnlocker which will be replaced by the user's wallet - const placeholderUnlocker = placeholderP2PKHUnlocker(userAddress) +async function proposeWcTransaction(userAddress: string) { + // Use a placeholder unlocker which will be replaced by the user's wallet + const placeholderUnlocker = placeholderP2PKHUnlocker(userAddress); - // use the CashScript SDK to construct a transaction - const transactionBuilder = new TransactionBuilder({provider: store.provider}) - transactionBuilder.addInputs(userInputUtxos, placeholderUnlocker) - transactionBuilder.addOpReturnOutput(opReturnData) - transactionBuilder.addOutput(contractOutput) - if(changeAmount > 550n) transactionBuilder.addOutput(changeOutput) + // Use the CashScript SDK to construct a transaction + const transactionBuilder = new TransactionBuilder({ provider }); + transactionBuilder.addInputs(userInputUtxos, placeholderUnlocker); + transactionBuilder.addOpReturnOutput(opReturnData); + transactionBuilder.addOutput(contractOutput); + if (changeAmount > 550n) transactionBuilder.addOutput(changeOutput); - // Generate WalletConnect transaction object with custom 'broadcast' and 'userPrompt' options + // Generate a WalletConnect transaction object with custom broadcast and prompt options const wcTransactionObj = transactionBuilder.generateWcTransactionObject({ broadcast: true, userPrompt: "Create HODL Contract", }); - // pass wcTransactionObj to WalletConnect client - // (see signWcTransaction implementation below) + // Pass wcTransactionObj to the WalletConnect client + // See the signWcTransaction implementation below const signResult = await signWcTransaction(wcTransactionObj); // Handle signResult success / failure } ``` -### Spending from a user-contract +### Spending From A User Contract -Below is example code from the `unlockHodlVault` code of the 'Hodl Vault' dapp repository, [link to source code](https://github.com/mr-zwets/bch-hodl-dapp/blob/main/src/views/UserContracts.vue#L66). +Use `placeholderSignature()` and `placeholderPublicKey()` for contract arguments that should be filled in by the wallet. ```ts import { TransactionBuilder, placeholderSignature, placeholderPublicKey } from "cashscript"; -async function unlockHodlVault(){ - // We use a placeholder signature and public key so this can be filled in by the user's wallet - const placeholderSig = placeholderSignature() - const placeholderPubKey = placeholderPublicKey() +async function unlockHodlVault() { + // Use placeholder arguments which will be filled in by the user's wallet + const placeholderSig = placeholderSignature(); + const placeholderPubKey = placeholderPublicKey(); - const transactionBuilder = new TransactionBuilder({provider: store.provider}) + // Use the CashScript SDK to construct a transaction + const transactionBuilder = new TransactionBuilder({ provider }); - transactionBuilder.setLocktime(store.currentBlockHeight) - transactionBuilder.addInputs(contractUtxos, hodlContract.unlock.spend(placeholderPubKey, placeholderSig)) - transactionBuilder.addOutput(reclaimOutput) + transactionBuilder.setLocktime(currentBlockHeight); + transactionBuilder.addInputs(contractUtxos, hodlContract.unlock.spend(placeholderPubKey, placeholderSig)); + transactionBuilder.addOutput(reclaimOutput); - // Generate WalletConnect transaction object with custom 'broadcast' and 'userPrompt' options + // Generate a WalletConnect transaction object with custom broadcast and prompt options const wcTransactionObj = transactionBuilder.generateWcTransactionObject({ broadcast: true, userPrompt: "Reclaim HODL Value", }); - // pass wcTransactionObj to WalletConnect client - // (see signWcTransaction implementation below) + // Pass wcTransactionObj to the WalletConnect client + // See the signWcTransaction implementation below const signResult = await signWcTransaction(wcTransactionObj); // Handle signResult success / failure } ``` -## signTransaction Wallet interaction +### Wallet Interaction -To communicate the `wcTransactionObj` with the user's Wallet we use the `@walletconnect/sign-client` library to request the connected user's wallet to sign the transaction. +To send the `WcTransactionObject` to the user's wallet, use `@walletconnect/sign-client`. -See [the source code](https://github.com/mr-zwets/bch-hodl-dapp/blob/main/src/store/store.ts#L60) for how to initialize the `signClient` and for details about the `connectedChain` and `session`. +See [the Hodl Vault source code][hodl-vault-sign-client] for how to initialize the `signClient` and for details about the `connectedChain` and `session`. ```ts -import SignClient from '@walletconnect/sign-client'; +import SignClient from "@walletconnect/sign-client"; import { stringify } from "@bitauth/libauth"; import { type WcTransactionObject } from "cashscript"; @@ -130,7 +130,7 @@ interface SignedTxObject { signedTransactionHash: string; } -async function signWcTransaction(wcTransactionObj: WcTransactionObject): SignedTxObject | undefined { +async function signWcTransaction(wcTransactionObj: WcTransactionObject): Promise { try { const result = await signClient.request({ chainId: connectedChain, @@ -146,3 +146,153 @@ async function signWcTransaction(wcTransactionObj: WcTransactionObject): SignedT } } ``` + +## WizardConnect + +WizardConnect is an HD-wallet-aware signing protocol. For the transaction itself it reuses the BCH WalletConnect transaction object, but it additionally requires HD path metadata called `inputPaths`. This tells the wallet which HD key should sign each input. + +:::tip +See the [WizardConnect documentation] and [WizardConnect GitLab repository] for the protocol details. +::: + +The only difference from BCH WalletConnect is this extra `inputPaths` list, so CashScript does not add a separate WizardConnect abstraction. You build the transaction object with the existing `generateWcTransactionObject()` method and attach `inputPaths` yourself before sending the request to the wallet. + +The WizardConnect sign request has the following shape: + +```ts +interface SignTransactionRequest { + // The same object returned by generateWcTransactionObject() + transaction: WcTransactionObject; + // One entry per input the wallet must sign + inputPaths: InputPath[]; +} + +// [inputIndex, pathName, addressIndex] +type InputPath = [number, string, number]; +``` + +Each `inputPaths` entry maps a transaction input to an HD key: + +- `inputIndex`: the input's position in the transaction's input list. +- `pathName`: the WizardConnect path name, such as `"receive"`, `"change"` or `"defi"`. +- `addressIndex`: the child index on that path. + +The wallet derives the key for each listed input index, and uses it both to sign P2PKH user inputs and to fill placeholder signatures or public keys inside contract inputs. Inputs the wallet does not need to sign, such as contract inputs with complete unlocking bytecode, are left out of `inputPaths`. + +### Spending User Inputs + +Build the transaction with `placeholderP2PKHUnlocker()`, exactly as with BCH WalletConnect. Then construct `inputPaths` for the user inputs, matching the order in which you added them. + +```ts +import { TransactionBuilder, placeholderP2PKHUnlocker } from "cashscript"; + +async function proposeWizardTransaction() { + // Use placeholder unlockers which will be replaced by the user's wallet + const transactionBuilder = new TransactionBuilder({ provider }); + + // Input 0: a UTXO on the receive path at address index 5 + transactionBuilder.addInput(userReceiveUtxo, placeholderP2PKHUnlocker(userReceiveAddress)); + // Input 1: a UTXO on the change path at address index 2 + transactionBuilder.addInput(userChangeUtxo, placeholderP2PKHUnlocker(userChangeAddress)); + + transactionBuilder.addOutput(contractOutput); + if (changeAmount > 550n) transactionBuilder.addOutput(changeOutput); + + // Build the standard WalletConnect transaction object + const transaction = transactionBuilder.generateWcTransactionObject({ + broadcast: false, + userPrompt: "Create Contract", + }); + + // Attach the HD path metadata for each user input, matching the input order above + const inputPaths: [number, string, number][] = [ + [0, "receive", 5], + [1, "change", 2], + ]; + + // Pass the request to the WizardConnect client + // See the signWizardTransaction implementation below + const signResult = await signWizardTransaction({ transaction, inputPaths }); + + // Handle signResult success / failure +} +``` + +Because the `inputPaths` indices reference the final transaction input order, construct them after deciding the input order. + +### Spending From A User Contract + +Contract inputs that use `placeholderSignature()` and `placeholderPublicKey()` work too, for example reclaiming from a Hodl Vault or withdrawing from a Cauldron pool. Add an `inputPaths` entry for the contract input's index and the wallet fills the placeholder signature and public key using the HD key for that path. + +```ts +import { TransactionBuilder, placeholderSignature, placeholderPublicKey } from "cashscript"; + +async function unlockHodlVault() { + // Use placeholder arguments which will be filled in by the user's wallet + const placeholderSig = placeholderSignature(); + const placeholderPubKey = placeholderPublicKey(); + + const transactionBuilder = new TransactionBuilder({ provider }); + transactionBuilder.setLocktime(currentBlockHeight); + // Input 0: the contract UTXO, whose sig and pubkey placeholders the wallet fills in + transactionBuilder.addInput(contractUtxo, hodlContract.unlock.spend(placeholderPubKey, placeholderSig)); + transactionBuilder.addOutput(reclaimOutput); + + // Build the standard WalletConnect transaction object + const transaction = transactionBuilder.generateWcTransactionObject({ + broadcast: false, + userPrompt: "Reclaim HODL Value", + }); + + // Point input 0 at the user's receive key at address index 5 + const inputPaths: [number, string, number][] = [ + [0, "receive", 5], + ]; + + // Pass the request to the WizardConnect client + const signResult = await signWizardTransaction({ transaction, inputPaths }); + + // Handle signResult success / failure +} +``` + +:::note +Filling `sig` and `pubkey` placeholders inside a contract input comes from the underlying BCH WalletConnect transaction format, not from the WizardConnect spec itself. The spec only describes `inputPaths` as selecting the HD key for inputs that need wallet signing, so this behaviour is wallet-dependent. It is implemented in wallets such as Paytaca, but confirm support with your target wallet. +::: + +:::tip +WizardConnect covers transaction signing only. For arbitrary message signing, use BCH WalletConnect or a WizardConnect custom extension. +::: + +### Wallet Interaction + +Send the request to your WizardConnect dapp client. Its `signTransaction` method takes the `transaction` and `inputPaths` and returns the signed transaction. + +```ts +import { type WcTransactionObject } from "cashscript"; + +interface SignTransactionRequest { + transaction: WcTransactionObject; + inputPaths: [number, string, number][]; +} + +async function signWizardTransaction( + request: SignTransactionRequest, +): Promise { + try { + const result = await dappConnectionManager.signTransaction(request); + return result.signedTransaction; + } catch (error) { + return undefined; + } +} +``` + +See the [WizardConnect documentation] for how to set up the `dappConnectionManager` and establish a connection with the user's wallet. + +[Tokenaut.cash]: https://tokenaut.cash/dapps?filter=walletconnect +[wc2-bch-bcr]: https://github.com/mainnet-pat/wc2-bch-bcr +[BCH research forum]: https://bitcoincashresearch.org/t/wallet-connect-v2-support-for-bitcoincash/ +[hodl-vault-sign-client]: https://github.com/mr-zwets/bch-hodl-dapp/blob/main/src/store/store.ts#L60 +[WizardConnect documentation]: https://docs.riftenlabs.com/wizardconnect/ +[WizardConnect GitLab repository]: https://gitlab.com/riftenlabs/lib/wizardconnect From 1bc02dd66c8ac46b9f797f5101c468015b70d4be Mon Sep 17 00:00:00 2001 From: Mathieu Geukens Date: Mon, 8 Jun 2026 15:14:27 +0200 Subject: [PATCH 2/5] Fix typos and grammar across docs guides --- website/docs/guides/adversarial.md | 10 +++++----- website/docs/guides/cashtokens.md | 11 ++++++++--- website/docs/guides/covenants.md | 10 +++++----- website/docs/guides/debugging.md | 2 +- website/docs/guides/infrastructure.md | 2 +- website/docs/guides/lifecycle.md | 2 +- website/docs/language/contracts.md | 4 ++++ 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/website/docs/guides/adversarial.md b/website/docs/guides/adversarial.md index c2db9355..8b5b1d77 100644 --- a/website/docs/guides/adversarial.md +++ b/website/docs/guides/adversarial.md @@ -3,7 +3,7 @@ title: Adversarial Analysis sidebar_label: Adversarial Analysis --- -In this guide we'll dive into "adversarial analysis" for smart contract systems. Adversarial analysis means to analyze your system from the point of a potential malicious 3rd party which might want to hamper or attack your system. This guide will build further on knowledge from the [the transaction lifecycle guide](/docs/guides/lifecycle). +In this guide we'll dive into "adversarial analysis" for smart contract systems. Adversarial analysis means to analyze your system from the point of a potential malicious 3rd party which might want to hamper or attack your system. This guide will build further on knowledge from the [transaction lifecycle guide](/docs/guides/lifecycle). ## The Happy Case @@ -21,7 +21,7 @@ The adversarial case is where 3rd parties intentionally double spend unconfirmed In an adversarial environment where double spends occur, user-created transactions interacting with public are not certain to be confirmed. This means waiting for block confirmations is required to be sure the transaction isn't cancelled. ::: -There is 2 categories to consider for adversarial double spends: +There are 2 categories to consider for adversarial double spends: 1) Race-condition double spends (no miner help required) @@ -37,7 +37,7 @@ For an adversarial attack to pull off this time-sensitive attack, he would requi ### Late Double Spends -In the case of a late double spend (which does not try to exploit a race condition) the adversarial actor need help from a miner. +In the case of a late double spend (which does not try to exploit a race condition) the adversarial actor needs help from a miner. Either the adversarial actor needs to convince the miners to abandon their first seen rule or he needs to be mining himself to be able to construct his own block. :::caution @@ -100,7 +100,7 @@ Adversarial analysis should take into account that "first-seen rule" is just a c ### Specialized Block-Builders -As described in the section on "stale-state arbitrage" economic actors may be incentivized to strategically create a competing transaction chain which takes advantage of an older price state/ratio which has not yet been confirmed in the blockchain. Although miners are not specialized in the optimal construction of DeFi transactions in a block, miner would over time be likely to team up with teams/companies creating this type of software for them. +As described in the section on "stale-state arbitrage" economic actors may be incentivized to strategically create a competing transaction chain which takes advantage of an older price state/ratio which has not yet been confirmed in the blockchain. Although miners are not specialized in the optimal construction of DeFi transactions in a block, miners would over time be likely to team up with teams/companies creating this type of software for them. :::note Ethereum with its large amount of MEV has already seen the emergence of specialized 'block builder' as a new class of relevant economic actors separate from the block proposer (who signs the block). @@ -120,7 +120,7 @@ This strategy of batching same-block trades (or "joint-execution") is the key co ### Centralized Co-signing -For contract systems relying on a continuously update on-chain oracle price feed, the problem of 'stale-state arbitrage' reappears. +For contract systems relying on a continuously updated on-chain oracle price feed, the problem of 'stale-state arbitrage' reappears. However in this context the only known solution to adversarial actors exploiting stale state with a late double spend is to require centralized co-signing in the contract system. The drawback of this approach is that it introduces a central party to enforce honest, sequential actions to prevent late double spends. The approach introduces the need for interactivity and assumes that the central signing service does not collude or cannot be bribed, additionally it also introduces new possible security concerns. diff --git a/website/docs/guides/cashtokens.md b/website/docs/guides/cashtokens.md index bda86712..42a9c5eb 100644 --- a/website/docs/guides/cashtokens.md +++ b/website/docs/guides/cashtokens.md @@ -38,7 +38,7 @@ The maximum size for a fungible token `amount` is the max signed 64-bit integer ### Non-Fungible Tokens -The `nft` info on a UTXO will only be present if the UTXO contains an NFT. The `nft` object has 2 properties: the `capability` and the `commitment`. The `commitment` is the data field for the NFT which can is allowed to be up to 128 bytes. +The `nft` info on a UTXO will only be present if the UTXO contains an NFT. The `nft` object has 2 properties: the `capability` and the `commitment`. The `commitment` is the data field for the NFT which is allowed to be up to 128 bytes. Capability `none` then refers to an immutable NFT where the commitment cannot be changed. The `mutable` capability means the `commitment` field can change over time, usually to contain smart contract state. Lastly the `minting` capability means that the NFT can create new NFTs from the same `category`. @@ -91,7 +91,7 @@ bytes tx.inputs[i].tokenCategory When accessing the `tokenCategory` through introspection the result returns `0x` (empty byte string) when that specific item does not contain tokens. If the item does have tokens it returns the `bytes32 tokenCategory`. When the item contains an NFT with a capability, the 32-byte `tokenCategory` is concatenated together with `0x01` for a mutable NFT and `0x02` for a minting NFT. -If you want to check for an NFT using introspection, you have either split the `tokenCategory` from the `capability` or check the concatenation of the `tokenCategory` and `capability`. +If you want to check for an NFT using introspection, you have to either split the `tokenCategory` from the `capability` or check the concatenation of the `tokenCategory` and `capability`. ```solidity // Constructor parameters: providedCategory @@ -162,6 +162,11 @@ The easiest way to prevent issues with "junk" empty NFTs is to check that only N The `tokenCategory` introspection variable returns the tokenCategory in the original unreversed order, this is unlike wallets and explorers which use the reversed byte-order. So be careful about the byte-order of `tokenCategory` when working with BCH smart contracts. ```ts +// Reverse byte order of a hex string. +function reverseHex(hex: string): string { + return hex.match(/../g)!.reverse().join(''); +} + // when using a standard encoded tokenId, reverse the hex before using it in your contract const contract = new Contract(artifact, [reverseHex(tokenId)], { provider }) ``` @@ -186,7 +191,7 @@ Signing for CashTokens inputs is designed in such a way that pre-CashTokens wall ## CashTokens Genesis transactions -A CashTokens genesis transaction is a transaction which creates a new `category` of CashTokens. To create a CashTokens genesis transaction you need a `vout0` UTXO because the txid of the UTXO will be you newly created `category`. +A CashTokens genesis transaction is a transaction which creates a new `category` of CashTokens. To create a CashTokens genesis transaction you need a `vout0` UTXO because the txid of the UTXO will be your newly created `category`. The requirement for a `vout0` UTXO can mean that you might need to create a setup transaction "pre-genesis" which will create this output. The "pre-genesis" txid then is your token's `category`. diff --git a/website/docs/guides/covenants.md b/website/docs/guides/covenants.md index 5bc53158..f371146f 100644 --- a/website/docs/guides/covenants.md +++ b/website/docs/guides/covenants.md @@ -35,7 +35,7 @@ When using CashScript, you can access a lot of *introspection data* that can be While we know the individual data fields, it's not immediately clear how this can be used to create useful smart contracts on Bitcoin Cash. However, there are several constraints that can be created using these fields — most important of which are constraints on the recipients of funds — so that is what we discuss. ### Restricting P2PKH recipients -One interesting technique in Bitcoin Cash is called blind escrow, meaning that funds are placed in an escrow contract. This contract can only release the funds to one of the escrow participants, and has no other control over the funds. We can implement this blind escrow as a covenants by restricting the possible recipients (although there are other possible designs for escrows). +One interesting technique in Bitcoin Cash is called blind escrow, meaning that funds are placed in an escrow contract. This contract can only release the funds to one of the escrow participants, and has no other control over the funds. We can implement this blind escrow as a covenant by restricting the possible recipients (although there are other possible designs for escrows). ```solidity contract Escrow(bytes20 arbiter, bytes20 buyer, bytes20 seller) { @@ -219,7 +219,7 @@ We use `tx.locktime` to introspect the value of the timelock, and to write the v #### Integer padding for local state -Padding an integer to a fixed-size byte-length is a very important when storing local state in an nftCommitment. We can use the `toPaddedBytes(int, length)` function to pad the integer to the desired length. When casting a script number to bytes, developers need to consider what the preferable fixed-size length is for each individual case depending on the integer range. Below we add a table with info on the maximum integer size for common cases: +Padding an integer to a fixed-size byte-length is very important when storing local state in an nftCommitment. We can use the `toPaddedBytes(int, length)` function to pad the integer to the desired length. When casting a script number to bytes, developers need to consider what the preferable fixed-size length is for each individual case depending on the integer range. Below we add a table with info on the maximum integer size for common cases: | Integer Type | Max integer value | Max Byte Size in Script Number Format | | -------------- | -----------------------------------| ---------------------------------------| @@ -234,7 +234,7 @@ VM numbers follow Script Number format (A.K.A. CSCriptNum), to convert VM number ### Issuing NFTs as receipts -A covenant that manages funds (BCH + fungible tokens of a certain category) which are pooled together from different people often wants to enable its participants to also exit the covenants with their funds. It would be incredibly hard continuously updating a data structure to keep track of which address contributed how much in the local state of the contract. A much better solution is to issue receipts each time funds are added to the pool! This way the contract does not have a 'global view' of who owns what at any time, but it can validate the receipts when invoking a withdrawal. +A covenant that manages funds (BCH + fungible tokens of a certain category) which are pooled together from different people often wants to enable its participants to also exit the covenants with their funds. It would be incredibly hard to continuously update a data structure to keep track of which address contributed how much in the local state of the contract. A much better solution is to issue receipts each time funds are added to the pool! This way the contract does not have a 'global view' of who owns what at any time, but it can validate the receipts when invoking a withdrawal. Technically this happens by minting a new NFT, with in the commitment field the amount of satoshis or fungible tokens that were contributed to the pool, and sending this to the address of the contributor. @@ -311,8 +311,8 @@ contract PooledFunds( require(tx.inputs[1].tokenCategory == tokenCategoryReceipt); // Read the amount that was contributed to the pool from the NFT commitment - bytes ntfCommitmentData = tx.inputs[1].nftCommitment; - bytes actionIdentifier, bytes amountToWithdrawBytes = ntfCommitmentData.split(1); + bytes nftCommitmentData = tx.inputs[1].nftCommitment; + bytes actionIdentifier, bytes amountToWithdrawBytes = nftCommitmentData.split(1); int amountToWithdraw = int(amountToWithdrawBytes); if (actionIdentifier == 0x01) { diff --git a/website/docs/guides/debugging.md b/website/docs/guides/debugging.md index 5189623f..daa4131a 100644 --- a/website/docs/guides/debugging.md +++ b/website/docs/guides/debugging.md @@ -45,7 +45,7 @@ Whenever a transaction fails, there will be a link in the console to open your s It's also possible to export the transaction for step-by-step debugging in the BitAuth IDE without failure. To do so, you can call the `getBitauthUri()` function on the transaction. This will return a URI that can be opened in the BitAuth IDE. ```ts -const uri = await transactionBuilder.getBitauthUri(); +const uri = transactionBuilder.getBitauthUri(); ``` :::caution diff --git a/website/docs/guides/infrastructure.md b/website/docs/guides/infrastructure.md index 46718c48..df95fdb2 100644 --- a/website/docs/guides/infrastructure.md +++ b/website/docs/guides/infrastructure.md @@ -31,7 +31,7 @@ To construct the full contract script you need both the `constructor` arguments To store the contract details off-chain, you will need to run a database server which stores the specific constructor arguments for each contract, this will translate into their respective smart contract addresses. This is crucial data for users to spend from BCH locked in such a smart contract. So this approach does introduce a single point of failure. :::caution -When using off-chain storage, it is the crucial responsibility of smart contract service to keep track of the contract details making up the `P2SH` contract, as user-wallets currently aren't capable to keep track of contract details and are fully reliant on the app server to store this critical info. +When using off-chain storage, it is the crucial responsibility of the smart contract service to keep track of the contract details making up the `P2SH` contract, as user-wallets currently aren't capable of keeping track of contract details and are fully reliant on the app server to store this critical info. ::: ### On-chain storage diff --git a/website/docs/guides/lifecycle.md b/website/docs/guides/lifecycle.md index 7b7f8a01..671a47df 100644 --- a/website/docs/guides/lifecycle.md +++ b/website/docs/guides/lifecycle.md @@ -23,7 +23,7 @@ Even if a miner sets a higher minimum fee for inclusion in his own blocks, 1 sat Before transactions are included in a block they are waiting for block inclusion in the mempool of the full nodes. Because transactions in the mempool are "seen" but not included in the blockchain yet, the latest state of the blockchain of who owns what is somewhat fuzzy. -In a normal scenario it's only a matter of time before a BCH transaction in the mempool gets included in a block. Where things get more complex however is if there are **competing unconfirmed transactions**. In this scenario it is **not** necessarily the clear that a transaction is destined to be included in the blockchain. In other words, the latest state of the blockchain is still undecided. +In a normal scenario it's only a matter of time before a BCH transaction in the mempool gets included in a block. Where things get more complex however is if there are **competing unconfirmed transactions**. In this scenario it is **not** necessarily clear that a transaction is destined to be included in the blockchain. In other words, the latest state of the blockchain is still undecided. :::tip This is why many BCH indexers will allow you to query UTXOs with the option to include or exclude unconfirmed transactions. By default indexers will include unconfirmed UTXOs/unconfirmed transactions in the query result. diff --git a/website/docs/language/contracts.md b/website/docs/language/contracts.md index 8b2631ef..b6e45949 100644 --- a/website/docs/language/contracts.md +++ b/website/docs/language/contracts.md @@ -59,6 +59,10 @@ contract TransferWithTimeout(pubkey sender, pubkey recipient, int timeout) { } ``` +:::note +The functions described here are top-level contract functions, which act as the entry points for spending from the contract. CashScript does not yet support user-defined reusable functions that can be called from within other functions. Callable functions are likely coming in CashScript v0.14, which would also allow function calls inside loops and loops inside reusable functions. +::: + ### Function Arguments Function arguments are provided by the user in the unlocking script of the transaction inputs when spending from the contract. Note that function arguments are variables and can be reassigned inside the function body. From 2d3fe6f66f53a66ee242d15e5f4272c15bc056cd Mon Sep 17 00:00:00 2001 From: Mathieu Geukens Date: Mon, 8 Jun 2026 16:21:52 +0200 Subject: [PATCH 3/5] Fix code examples, API references, and grammar across docs --- website/docs/basics/about-bch.md | 6 +++--- website/docs/basics/about.md | 4 ++-- website/docs/basics/getting-started.md | 6 +++--- website/docs/community-sdks/python-sdk.md | 2 +- website/docs/compiler/artifacts.md | 5 ++++- website/docs/compiler/grammar.md | 6 +++++- website/docs/compiler/script-limits.md | 4 ++-- website/docs/language/examples.md | 4 ++-- website/docs/language/functions.md | 6 +++--- website/docs/language/globals.md | 16 ++++++++-------- website/docs/language/syntax-highlighting.md | 13 +------------ website/docs/language/types.md | 2 +- website/docs/releases/migration-notes.md | 4 ++-- website/docs/releases/release-notes.md | 6 +++--- website/docs/sdk/electrum-network-provider.md | 6 +++--- website/docs/sdk/examples.md | 12 +++++------- website/docs/sdk/other-network-providers.md | 2 +- website/docs/sdk/testing-setup.md | 10 +++++----- website/docs/sdk/transaction-builder.md | 19 ++++++++++--------- website/docs/sdk/typescript-sdk.md | 12 ++++++------ website/docs/showcase.md | 2 +- 21 files changed, 71 insertions(+), 76 deletions(-) diff --git a/website/docs/basics/about-bch.md b/website/docs/basics/about-bch.md index 3ce1a8b8..46eb7a7f 100644 --- a/website/docs/basics/about-bch.md +++ b/website/docs/basics/about-bch.md @@ -13,9 +13,9 @@ To learn more about the Bitcoin Basics refer to the book ['Mastering Bitcoin'][M ## How BCH differs from BTC -Although BCH and BTC share the same Bitcoin fundamentals, both projects have significantly diverged in some areas since 2017. For example, Bitcoin Cash does not have Segwit or Taproot, instead Bitcoin Cash has had multiple upgrades specifically focused on improving the smart contract capabilities. Bitcoin Cash has re-enabled many useful opcodes, has introduce native introspection, has added CashTokens, has reworked the script limits and introduced BigInts. +Although BCH and BTC share the same Bitcoin fundamentals, both projects have significantly diverged in some areas since 2017. For example, Bitcoin Cash does not have Segwit or Taproot, instead Bitcoin Cash has had multiple upgrades specifically focused on improving the smart contract capabilities. Bitcoin Cash has re-enabled many useful opcodes, has introduced native introspection, has added CashTokens, has reworked the script limits and introduced BigInts. -So part that **has** significantly diverged between BTC and BCH is the *virtual machine* (VM), the environment in which smart contracts are evaluated. The Bitcoin Cash VM is also referred to as the **CashVM**. The greatly improved VM specifically is what makes it possible to write powerful smart contracts on BCH in the first place! On the overview of [all the BCH network upgrades][BCH upgrades], you'll notice the recent years have been fully focused on VM improvements. +So the part that **has** significantly diverged between BTC and BCH is the *virtual machine* (VM), the environment in which smart contracts are evaluated. The Bitcoin Cash VM is also referred to as the **CashVM**. The greatly improved VM specifically is what makes it possible to write powerful smart contracts on BCH in the first place! On the overview of [all the BCH network upgrades][BCH upgrades], you'll notice the recent years have been fully focused on VM improvements. ## UTXO model Bitcoin Cash transactions work with in- and outputs. All current balances are so called *Unspent Transaction Outputs (UTXOs)*, which simply means they can be used as inputs for future spending transactions. @@ -44,7 +44,7 @@ The UTXO model where transactions only have access to the local transaction cont ### Bitcoin Cash Script -The locking and unlocking scripts of regular transactions and smart contracts on Bitcoin Cash are written using Bitcoin Cash' transaction scripting language, referred to as BCH Script (or Bitcoin Cash Script in full). BCH Script is a stack based assembly-like language, because it is a low-level language and requires stack management it is hard to write complex smart contract in Script directly. +The locking and unlocking scripts of regular transactions and smart contracts on Bitcoin Cash are written using Bitcoin Cash's transaction scripting language, referred to as BCH Script (or Bitcoin Cash Script in full). BCH Script is a stack based assembly-like language, because it is a low-level language and requires stack management it is hard to write complex smart contracts in Script directly. ### CashScript diff --git a/website/docs/basics/about.md b/website/docs/basics/about.md index f91de61b..92082996 100644 --- a/website/docs/basics/about.md +++ b/website/docs/basics/about.md @@ -3,11 +3,11 @@ title: What is CashScript? sidebar_label: About CashScript --- -CashScript is a high-level programming language for smart contracts on Bitcoin Cash. It offers a strong abstraction layer over Bitcoin Cash' native virtual machine, BCH Script. CashScript was created in 2019 and has seen major upgrades over the years, thereby supporting new script functionalities enabled with the different Bitcoin Cash network upgrades — including native introspection and CashTokens. +CashScript is a high-level programming language for smart contracts on Bitcoin Cash. It offers a strong abstraction layer over Bitcoin Cash's native virtual machine, BCH Script. CashScript was created in 2019 and has seen major upgrades over the years, thereby supporting new script functionalities enabled with the different Bitcoin Cash network upgrades — including native introspection and CashTokens. The CashScript syntax is based on Ethereum's smart contract language Solidity, but its functionality is very different since smart contracts on Bitcoin Cash differ greatly from smart contracts on Ethereum. You can read more on these differences on the ['About Bitcoin Cash' page](./about-bch.md). -CashScript comes with a powerful TypeScript SDK for creating, debugging and testing smart contract transactions on BCH. The SDK has an integrated networkProvider API to fetch data from the BCH blockchain. With all this functionality, the Typescript SDK makes it easy to use CashScript contracts in applications in the browser or on the server in a type-safe way. +CashScript comes with a powerful TypeScript SDK for creating, debugging and testing smart contract transactions on BCH. The SDK has an integrated networkProvider API to fetch data from the BCH blockchain. With all this functionality, the TypeScript SDK makes it easy to use CashScript contracts in applications in the browser or on the server in a type-safe way. CashScript aims to make building smart contract apps on Bitcoin Cash accessible to a wide range of developers and to power the next-generation of UTXO smart contract applications. diff --git a/website/docs/basics/getting-started.md b/website/docs/basics/getting-started.md index 83d34a71..2e35f3a4 100644 --- a/website/docs/basics/getting-started.md +++ b/website/docs/basics/getting-started.md @@ -70,7 +70,7 @@ contract TransferWithTimeout(pubkey sender, pubkey recipient, int timeout) { Let's take some time to understand the contract structure. At the top, the smart contract declares the CashScript language version it's using with `pragma`. -Then a `TransferWithTimeout` is declare which takes in 3 contract arguments and has 2 contract functions: `transfer` and `timeout`. +Then a `TransferWithTimeout` is declared which takes in 3 contract arguments and has 2 contract functions: `transfer` and `timeout`. These contract functions both have `require` statements necessary to be met to be able to spend BCH from the contract. :::tip @@ -97,7 +97,7 @@ After creating a contract artifact, we can now use the TypeScript SDK to initial :::info The CashScript SDK is written in TypeScript meaning that you can either use TypeScript or vanilla JavaScript to use the SDK. -It's recommended to use TypeScript for full type-safety of all you contract logic. +It's recommended to use TypeScript for full type-safety of all your contract logic. ::: ### Installing the TypeScript SDK @@ -180,4 +180,4 @@ console.log(transferDetails); Congrats 🎉! You've successfully created a transaction spending from a Bitcoin Cash smart contract! -To use the `timeout` function you need to use Alice as signer for the spending condition. Secondly you also need get to use `.setLocktime()` with a valid argument during the transaction building to pass the `tx.time` check of the `timeout` function. +To use the `timeout` function you need to use Alice as signer for the spending condition. Secondly you also need to use `.setLocktime()` with a valid argument during the transaction building to pass the `tx.time` check of the `timeout` function. diff --git a/website/docs/community-sdks/python-sdk.md b/website/docs/community-sdks/python-sdk.md index 32aa4edc..39011dac 100644 --- a/website/docs/community-sdks/python-sdk.md +++ b/website/docs/community-sdks/python-sdk.md @@ -39,7 +39,7 @@ The usage of the 4 classes in your code is as follows: before using the SDK you For more complete examples of the SDK flow, refer to the [runnable examples][examples] in the Python SDK repository. -#### example +#### Example ```python from cashscript_py import Contract, ElectrumNetworkProvider, Output, SignatureTemplate, TransactionBuilder diff --git a/website/docs/compiler/artifacts.md b/website/docs/compiler/artifacts.md index b2f21d22..fc5cfcb1 100644 --- a/website/docs/compiler/artifacts.md +++ b/website/docs/compiler/artifacts.md @@ -26,8 +26,10 @@ interface Artifact { sourceMap: string // see documentation for `generateSourceMap` logs: LogEntry[] // log entries generated from `console.log` statements requires: RequireStatement[] // messages for failing `require` statements + sourceTags?: string // semantic tags for opcodes (e.g. loop update/condition ranges) } updatedAt: string // Last datetime this artifact was updated (in ISO format) + fingerprint?: string // SHA256 of the normalized bytecode pattern (BCH bytecode fingerprinting standard) } interface AbiInput { @@ -56,10 +58,11 @@ interface StackItem { interface RequireStatement { ip: number; // instruction pointer line: number; // line in the source code - message: string; // custom message for failing `require` statement + message?: string; // custom message for failing `require` statement } interface CompilerOptions { enforceFunctionParameterTypes?: boolean; // Enforce function parameter types (default: true) + enforceLocktimeGuard?: boolean; // Enforce the tx.locktime guard (default: true) } ``` diff --git a/website/docs/compiler/grammar.md b/website/docs/compiler/grammar.md index caf61a4c..93f5d8d7 100644 --- a/website/docs/compiler/grammar.md +++ b/website/docs/compiler/grammar.md @@ -35,7 +35,11 @@ contractDefinition ; functionDefinition - : 'function' Identifier parameterList '{' statement* '}' + : 'function' Identifier parameterList functionBody + ; + +functionBody + : '{' statement* '}' ; parameterList diff --git a/website/docs/compiler/script-limits.md b/website/docs/compiler/script-limits.md index 7deffd55..f13d43cf 100644 --- a/website/docs/compiler/script-limits.md +++ b/website/docs/compiler/script-limits.md @@ -6,7 +6,7 @@ sidebar_label: Script & Transaction Limits Bitcoin Cash imposes various constraints on scripts and transactions to ensure efficient contract execution and network stability. We'll categorize these limits into 2 types: 'Contract-related limits' and 'General transaction limits'. :::note -Some of the limits below are hard BCH consensus rules, others are standardness relay rules which are still present significant practical barriers. These relay rules however are only enforced on network propagation. You can read more about the [BCH standardness rules here][standardness-docs]. +Some of the limits below are hard BCH consensus rules, others are standardness relay rules which still present significant practical barriers. These relay rules however are only enforced on network propagation. You can read more about the [BCH standardness rules here][standardness-docs]. ::: ## Contract-related limits @@ -76,7 +76,7 @@ For ease of development, it is standard practice to use `1,000` satoshis as dust :::note The standard practice of 1,000 satoshis as dust amount for outputs is for the `P2PKH`, `P2SH20` and `P2SH32` output types. -For custom locking bytecode outputs a higher dust limits may be required, you can [find more info here][info-dust-limit]. +For custom locking bytecode outputs a higher dust limit may be required, you can [find more info here][info-dust-limit]. ::: ### Minimum Relay Fee diff --git a/website/docs/language/examples.md b/website/docs/language/examples.md index 1db15fcb..1f77b1b0 100644 --- a/website/docs/language/examples.md +++ b/website/docs/language/examples.md @@ -114,7 +114,7 @@ contract DexContract(bytes20 poolOwnerPkh) { // transaction format changes in the future. require(tx.version == 2); - // Verify that this contract lives on on the output with the same input as this contract. + // Verify that this contract lives on the output with the same input as this contract. bytes inputBytecode = tx.inputs[this.activeInputIndex].lockingBytecode; bytes outputBytecode = tx.outputs[this.activeInputIndex].lockingBytecode; require(inputBytecode == outputBytecode); @@ -133,7 +133,7 @@ contract DexContract(bytes20 poolOwnerPkh) { require(effectiveOutputK >= targetK); } function withdrawal(pubkey poolOwnerPk, sig poolOwnerSig) { - require(hash160(poolOwnerPk) == poolOwner); + require(hash160(poolOwnerPk) == poolOwnerPkh); require(checkSig(poolOwnerSig, poolOwnerPk)); } } diff --git a/website/docs/language/functions.md b/website/docs/language/functions.md index f407017a..ea7abe2b 100644 --- a/website/docs/language/functions.md +++ b/website/docs/language/functions.md @@ -76,21 +76,21 @@ All signature checking functions must comply with the [NULLFAIL][bip146] rule wh #### Nullfail example -The `NULLFAIL` rule means passing an invalid signature to `checkSig()` does not return `false` — it fails the script. To safely return `false` on a signature check, use an empty `0x` signature instead, as shown below: +The `NULLFAIL` rule means passing an invalid signature to `checkSig()` does not return `false` — it fails the script. To safely return `false` on a signature check, use an empty `0x` signature instead, as shown below: ```solidity // this script will immediately fail // because userSig will be invalid for either the 'seller' or the 'referee' require(checkSig(userSig, seller) || checkSig(userSig, referee)); -// instead, use 2 different signatures +// instead, use 2 different signatures // set the unused signature to 0x so 'checkSig' returns false require(checkSig(sellerSig, seller) || checkSig(userSig, referee)); ``` ### checkSig() ```solidity -bool checksig(sig s, pubkey pk) +bool checkSig(sig s, pubkey pk) ``` Checks that transaction signature `s` is valid for the current transaction and matches with public key `pk`. diff --git a/website/docs/language/globals.md b/website/docs/language/globals.md index ae822449..3acf7cf5 100644 --- a/website/docs/language/globals.md +++ b/website/docs/language/globals.md @@ -9,7 +9,7 @@ It can be difficult to fully grasp the intricacies of time locks, so if you're s ### tx.time -`tx.time` is used to create *absolute* time lock on the transaction. The value of `tx.time` can either represent the block number of the spending transaction or its timestamp. When comparing it with values below `500,000,000`, it is treated as a blocknumber, while higher values are treated as a timestamp. +`tx.time` is used to create an *absolute* time lock on the transaction. The value of `tx.time` can either represent the block number of the spending transaction or its timestamp. When comparing it with values below `500,000,000`, it is treated as a blocknumber, while higher values are treated as a timestamp. `tx.time` corresponds to the `nLocktime` field of the current transaction using the `OP_CHECKLOCKTIMEVERIFY` opcode. Because of this `tx.time` can only be used in the following way: @@ -24,7 +24,7 @@ To access the value of the `nLocktime` field for assignment, use the `tx.locktim Because of the way time locks work, **a corresponding time lock needs to be added to the transaction**. This can be done with the CashScript SDK by calling [`setLocktime()`][setLocktime()] on the `TransactionBuilder` instance. ### this.age -`this.age` is used to create *relative* time lock on a UTXO. The value of `this.age` can either represent a number of blocks, or a number of *chunks*, which are 512 seconds. The corresponding *transaction level* time lock determines which of the two options is used. +`this.age` is used to create a *relative* time lock on a UTXO. The value of `this.age` can either represent a number of blocks, or a number of *chunks*, which are 512 seconds. The corresponding *transaction level* time lock determines which of the two options is used. `this.age` corresponds to the `nSequence` field of the current evaluated *UTXO* using the `OP_CHECKSEQUENCEVERIFY` opcode. Because of this `this.age` can only be used in the following way: @@ -181,9 +181,9 @@ bytes tx.outputs[i].lockingBytecode Represents the locking bytecode (`scriptPubKey`) of a specific output. -#### tx.output[i].tokenCategory +#### tx.outputs[i].tokenCategory ```solidity -bytes tx.output[i].tokenCategory +bytes tx.outputs[i].tokenCategory ``` Represents the `tokenCategory` of a specific output. Returns `0x` when that specific output contains no tokens. When the output contains an NFT with a capability, the 32-byte `tokenCategory` is concatenated together with `0x01` for a mutable NFT and `0x02` for a minting NFT. @@ -192,16 +192,16 @@ Represents the `tokenCategory` of a specific output. Returns `0x` when that spec The `tokenCategory` introspection variable returns the tokenCategory in the original unreversed order, this is unlike wallets and explorers which use the reversed byte-order. So be careful about the byte-order of `tokenCategory` when working with BCH smart contracts. ::: -#### tx.output[i].nftCommitment +#### tx.outputs[i].nftCommitment ```solidity -bytes tx.output[i].nftCommitment +bytes tx.outputs[i].nftCommitment ``` Represents the NFT commitment data of a specific output. -#### tx.output[i].tokenAmount +#### tx.outputs[i].tokenAmount ```solidity -int tx.output[i].tokenAmount +int tx.outputs[i].tokenAmount ``` Represents the amount of fungible tokens of a specific output. Maximum size for a `tokenAmount` is a 64-bit integer. diff --git a/website/docs/language/syntax-highlighting.md b/website/docs/language/syntax-highlighting.md index 810880b7..1aeec973 100644 --- a/website/docs/language/syntax-highlighting.md +++ b/website/docs/language/syntax-highlighting.md @@ -22,23 +22,12 @@ To have the extension automatically suggested for any developer looking at your Cursor and other VS Code forks can use the VS Code extension mentioned above. This extension should be findable in the extensions menu within the editor. If it is not, you can manually install it from the [Open VSX Registry](https://open-vsx.org/extension/CashScript/cashscript-vscode). ## Sublime Text -The most popular Solidity plugin for Sublime Text 2/3 is [Ethereum](https://packagecontrol.io/packages/Ethereum). Install this plugin with [Package Control](https://packagecontrol.io/), open a `.cash` file and set Solidity as the syntax language in the Sublime menu bar: +The most popular Solidity plugin for Sublime Text is [Ethereum](https://packagecontrol.io/packages/Ethereum). Install this plugin with [Package Control](https://packagecontrol.io/), open a `.cash` file and set Solidity as the syntax language in the Sublime menu bar: > View -> Syntax -> Open all with current extension as ... -> Solidity This associates `.cash` files with Solidity, and enables syntax highlighting for your CashScript files. -## Atom -The most popular Solidity plugin for Atom is [language-solidity](https://atom.io/packages/language-solidity). Install this plugin and add the following snippet to your Config file: - -```yaml title="~/.atom/config.cson" -core: - customFileTypes: - "source.solidity": ["cash"] -``` - -This associates `.cash` files with Solidity, and enables syntax highlighting for your CashScript files. - ## Vim The most popular Solidity plugin for Vim is [vim-solidity](https://github.com/TovarishFin/vim-solidity). Install this plugin and add the following snippet to your `.vimrc`: diff --git a/website/docs/language/types.md b/website/docs/language/types.md index 835c7900..cdcdf61f 100644 --- a/website/docs/language/types.md +++ b/website/docs/language/types.md @@ -180,7 +180,7 @@ bool b = bool(5); // true ### Casting Table -See the following table for information on which types can be cast to other which other types. +See the following table for information on which types can be cast to which other types. | Type | Implicitly castable to | Explicitly castable to | | ------- | ---------------------- | ---------------------------------- | diff --git a/website/docs/releases/migration-notes.md b/website/docs/releases/migration-notes.md index 3e1c3578..8c2864e9 100644 --- a/website/docs/releases/migration-notes.md +++ b/website/docs/releases/migration-notes.md @@ -293,7 +293,7 @@ const contract = new Contract(artifact, constructorArgs, options); ``` #### SIGHASH_UTXO -All signature templates use `SIGHASH_ALL | SIGHASH_UTXOS` now, to keep using the only the previous `SIGHASH_ALL` overwrite it in the following way: +All signature templates use `SIGHASH_ALL | SIGHASH_UTXOS` now, to keep using only the previous `SIGHASH_ALL`, overwrite it in the following way: ```ts const sig = new SignatureTemplate(wif, HashType.SIGHASH_ALL); ``` @@ -346,7 +346,7 @@ contract Mecenas(bytes20 recipient, bytes20 funder, int pledge, int period) { if (intValue <= pledge + minerFee) { // The contract has less value than the pledge, or equal. - // The recipient must claim all of of it. + // The recipient must claim all of it. bytes8 amount1 = bytes8(intValue - minerFee); bytes34 out1 = new OutputP2PKH(amount1, recipient); diff --git a/website/docs/releases/release-notes.md b/website/docs/releases/release-notes.md index c48ebaf5..8e1e517d 100644 --- a/website/docs/releases/release-notes.md +++ b/website/docs/releases/release-notes.md @@ -102,7 +102,7 @@ https://x.com/CashScriptBCH/status/1973692336782876974 - :bug: Disallow incorrect bounded bytes typing when using `.split()`. #### CashScript SDK -- :bug: Fix bug with where `ElectrumNetworkProvider` would disconnect in browser on visibility change of the page. +- :bug: Fix bug where `ElectrumNetworkProvider` would disconnect in browser on visibility change of the page. ## v0.11.2 @@ -279,7 +279,7 @@ https://x.com/CashScriptBCH/status/1713928572677583023 This release also contains several breaking changes, please refer to the [migration notes](/docs/releases/migration-notes) for more information. #### cashc compiler -- :sparkles: Add support for the new CashTokens introspection functionality (`tokenCategory`,`nftCommitment`and `tokenAmount`for both in- and outputs). +- :sparkles: Add support for the new CashTokens introspection functionality (`tokenCategory`, `nftCommitment` and `tokenAmount` for both in- and outputs). - :sparkles: Add `LockingBytecodeP2SH32` to generate the new P2SH32 standard locking script. - :bug: Fix optimisation bug that caused `OP_0NOTEQUAL` to be applied to non-integer values. - :boom: **BREAKING**: Move to Pure ESM. @@ -495,7 +495,7 @@ https://x.com/RoscoKalis/status/1267440143624884227 - Remove the `TxOptions` argument and other arguments to the Transaction `send()` function. - Instead these parameters are passed in through fluent functions `from()`, `to()`, `withOpReturn()`, `withAge()`, `withTime()`, `withHardcodedFee()`, `withFeePerByte()` and `withMinChange()`. - After specifying at least one output with either `to()` or `withOpReturn()`the transaction is ready. From here the transaction can be sent to the network with the `send()` function, the transaction hex can be returned with the `build()` function, or the meep debugging command can be returned with the `meep()` function. -- :boom: Remove `Contract.fromCashFile()` and `Contract.fromArtifact()` which were deprecated in favour or `Contract.compile()` and `Contract.import()` in v0.2.2. +- :boom: Remove `Contract.fromCashFile()` and `Contract.fromArtifact()` which were deprecated in favour of `Contract.compile()` and `Contract.import()` in v0.2.2. #### Migration This update contains several breaking changes. See the [migration notes](/docs/releases/migration-notes#v03-to-v04) for a full migration guide. diff --git a/website/docs/sdk/electrum-network-provider.md b/website/docs/sdk/electrum-network-provider.md index a408ad96..51e37f93 100644 --- a/website/docs/sdk/electrum-network-provider.md +++ b/website/docs/sdk/electrum-network-provider.md @@ -6,7 +6,7 @@ The CashScript SDK needs to connect to the BCH network to perform certain operat ## Creating an ElectrumNetworkProvider -The ElectrumNetworkProvider uses [@electrum-cash/network][electrum-cash] library to connect to the configured electrum server. The connection uses a single, trusted electrum server so it does no have any fallback logic and does not validate SPV proofs for chain inclusion. +The ElectrumNetworkProvider uses [@electrum-cash/network][electrum-cash] library to connect to the configured electrum server. The connection uses a single, trusted electrum server so it does not have any fallback logic and does not validate SPV proofs for chain inclusion. By default the `ElectrumNetworkProvider` creates a short-lived connection only when requests are pending. To configure this see the section on '[Manual Connection Management](#manual-connection-management)'. @@ -171,14 +171,14 @@ When initializing an `ElectrumNetworkProvider` you have the option in the constr If intending to use electrum-cash subscriptions, make sure to set `manualConnectionManagement` to true, so the `ElectrumNetworkProvider` does not disconnect after each request. -#### example +#### Example ```ts import { ElectrumClient } from '@electrum-cash/network'; import { ElectrumNetworkProvider } from 'cashscript'; const electrum = new ElectrumClient('CashScript Application', '1.4.1', 'chipnet.bch.ninja'); -const provider = new ElectrumNetworkProvider(Network.CHIPNET, { +const provider = new ElectrumNetworkProvider('chipnet', { electrum, manualConnectionManagement: true }); await electrum.connect(); diff --git a/website/docs/sdk/examples.md b/website/docs/sdk/examples.md index 54c26031..aeac11bc 100644 --- a/website/docs/sdk/examples.md +++ b/website/docs/sdk/examples.md @@ -61,7 +61,6 @@ export const oracleAddress = encodeCashAddress('bchtest', 'p2pkhWithTokens', ora For development purposes, we'll use the `MockNetworkProvider` so you can simulate transactions in a 'mock' network environment. To do proper testing, you also need to add "mock" UTXOs to the `MockNetworkProvider`. ```ts title="hodl_vault.ts" -import { stringify } from '@bitauth/libauth'; import { Contract, SignatureTemplate, MockNetworkProvider, randomUtxo } from 'cashscript'; import { compileFile } from 'cashc'; import { URL } from 'url'; @@ -70,7 +69,7 @@ import { URL } from 'url'; import { alicePub, oraclePub, -} from './common.ts'; +} from './common.js'; // Compile the HodlVault contract to an artifact object const artifact = compileFile(new URL('hodl_vault.cash', import.meta.url)); @@ -93,7 +92,7 @@ console.log('contract balance:', await contract.getBalance()); ### Building the Oracle -We need the create the functionality for generating and signing the oracle message to use in the HodlVault contract: +We need to create the functionality for generating and signing the oracle message to use in the HodlVault contract: ```ts title="PriceOracle.ts" import { padMinimallyEncodedVmNumber, flattenBinArray } from '@bitauth/libauth'; @@ -121,11 +120,10 @@ export class PriceOracle { ### Sending a Transaction -Finally, we can put all of this together to create a working smart contract application. We use the generated keys as a contract arguments directly or in a `SignatureTemplate` to create a transaction signature. +Finally, we can put all of this together to create a working smart contract application. We use the generated keys as contract arguments directly or in a `SignatureTemplate` to create a transaction signature. ```ts title="hodl_vault.ts" -import { stringify } from '@bitauth/libauth'; -import { Contract, SignatureTemplate, MockNetworkProvider } from 'cashscript'; +import { Contract, SignatureTemplate, MockNetworkProvider, TransactionBuilder, randomUtxo } from 'cashscript'; import { compileFile } from 'cashc'; import { URL } from 'url'; @@ -135,7 +133,7 @@ import { alicePub, oracle, oraclePub, -} from './common.ts'; +} from './common.js'; // Compile the HodlVault contract to an artifact object const artifact = compileFile(new URL('hodl_vault.cash', import.meta.url)); diff --git a/website/docs/sdk/other-network-providers.md b/website/docs/sdk/other-network-providers.md index 418673a0..fa1ea2b4 100644 --- a/website/docs/sdk/other-network-providers.md +++ b/website/docs/sdk/other-network-providers.md @@ -39,7 +39,7 @@ interface MockNetworkProvider extends NetworkProvider { The `updateUtxoSet` option is used to determine whether the UTXO set should be updated after a transaction is sent. If `updateUtxoSet` is `true` (default), the UTXO set will be updated to reflect the new state of the mock network. If `updateUtxoSet` is `false`, the UTXO set will not be updated. -The `vmTarget` option defaults to the current VM of `BCH_2025_05`, but this can be changed to test your contract against different BCH virtual machine targets. +The `vmTarget` option defaults to the current VM of `BCH_2026_05`, but this can be changed to test your contract against different BCH virtual machine targets. #### Example ```ts diff --git a/website/docs/sdk/testing-setup.md b/website/docs/sdk/testing-setup.md index 0b3e3c3c..f0e92466 100644 --- a/website/docs/sdk/testing-setup.md +++ b/website/docs/sdk/testing-setup.md @@ -12,7 +12,7 @@ For a quick start with a CashScript testing setup, check out the [example testin The `MockNetworkProvider` is a special network provider that allows you to evaluate transactions locally without interacting with the Bitcoin Cash network. This is useful when writing automated tests for your contracts, or when debugging your contract locally. -To create a new virtual UTXO use `provider.addUtxo(address, utxo)`. You can use the helper functions `randomUtxo()`, `randomToken()` and `randomNFT()` to generate random partial Utxo info which you can be overwritten with custom values. +To create a new virtual UTXO use `provider.addUtxo(address, utxo)`. You can use the helper functions `randomUtxo()`, `randomToken()` and `randomNFT()` to generate random partial Utxo info which can be overwritten with custom values. #### Example @@ -20,9 +20,9 @@ To create a new virtual UTXO use `provider.addUtxo(address, utxo)`. You can use import { MockNetworkProvider, randomUtxo, randomToken, randomNFT } from 'cashscript'; const provider = new MockNetworkProvider(); -const contractUtxo = provider.addUtxo(contract.address, { vout: 0, txid: "ab...", satoshis: 10000n }); +provider.addUtxo(contract.address, { vout: 0, txid: "ab...", satoshis: 10000n }); -const aliceUtxo = provider.addUtxo(aliceAddress, randomUtxo({ +provider.addUtxo(aliceAddress, randomUtxo({ satoshis: 1000n, token: { ...randomNFT(), ...randomToken() }, })); @@ -34,7 +34,7 @@ By default, the `MockNetworkProvider` evaluates transactions locally but does no ## Automated testing -To make writing automated tests for CashScript contracts easier, we provide Vitest and Jest extensions that enables easy testing of `console.log` values and `require` error messages. To use the extension, you can import it from `cashscript/vitest` or `cashscript/jest`. +To make writing automated tests for CashScript contracts easier, we provide Vitest and Jest extensions that enable easy testing of `console.log` values and `require` error messages. To use the extension, you can import it from `cashscript/vitest` or `cashscript/jest`. ```ts import 'cashscript/vitest'; @@ -94,7 +94,7 @@ describe('Example contract', () => { it('should pass require statement when correct parameter is passed', async () => { const transaction = new TransactionBuilder({ provider }) - .addInput(contractUtxo, contract.unlock.exampleFunction(999n)) + .addInput(contractUtxo, contract.unlock.exampleFunction(1000n)) .addOutput({ to: contract.address, amount: 10000n }) expect(transaction).not.toFailRequire(); }); diff --git a/website/docs/sdk/transaction-builder.md b/website/docs/sdk/transaction-builder.md index 1f26ed5b..63d6e2c1 100644 --- a/website/docs/sdk/transaction-builder.md +++ b/website/docs/sdk/transaction-builder.md @@ -207,7 +207,7 @@ Sets the locktime for the transaction to set a transaction-level absolute timelo #### Example ```ts // Set locktime one day from now -transactionBuilder.setLocktime(((Date.now() / 1000) + 24 * 60 * 60) * 1000); +transactionBuilder.setLocktime((Date.now() / 1000) + 24 * 60 * 60); ``` ### getTransactionSize() @@ -283,7 +283,7 @@ const txHex = new TransactionBuilder({ provider, maximumFeeSatoshis }) ### debug() ```ts -transactionBuilder.debug(): DebugResult +transactionBuilder.debug(): DebugResults ``` If you want to debug a transaction locally instead of sending it to the network, you can call the `debug()` function on the transaction. This will return intermediate values and the final result of the transaction. It will also show any logged values and `require` error messages. @@ -302,7 +302,7 @@ It is unsafe to debug transactions on mainnet using the BitAuth IDE as private k ### getVmResourceUsage() ```ts -transaction.getVmResourceUsage(verbose: boolean = false): Array +transactionBuilder.getVmResourceUsage(verbose: boolean = false): Array ``` The `getVmResourceUsage()` function allows you to get the VM resource usage for the transaction. This can be useful for debugging and optimization. The VM resource usage is calculated for each input individually so the result is an array of `VmResourceUsage` results corresponding to each of the transaction inputs. @@ -351,7 +351,7 @@ interface WcTransactionOptions { } interface WcTransactionObject { - transaction: TransactionCommon | string; + transaction: TransactionCommon; sourceOutputs: WcSourceOutput[]; broadcast?: boolean; userPrompt?: string; @@ -408,11 +408,12 @@ When sending a transaction, the CashScript SDK will throw an error if the transa ```ts interface FailedRequireError { message: string; - contractName: string; - requireStatement: { ip: number, line: number, message: string }; - inputIndex: number, - libauthErrorMessage?: string, - bitauthUri?: string; + artifact: Artifact; + failingInstructionPointer: number; + requireStatement: { ip: number, line: number, message?: string }; + inputIndex: number; + bitauthUri: string; + libauthErrorMessage?: string; } ``` diff --git a/website/docs/sdk/typescript-sdk.md b/website/docs/sdk/typescript-sdk.md index 873db08d..7d419ef7 100644 --- a/website/docs/sdk/typescript-sdk.md +++ b/website/docs/sdk/typescript-sdk.md @@ -6,7 +6,7 @@ CashScript offers a TypeScript SDK, which makes it easy to build smart contract The CashScript SDK enables advanced debugging tooling for CashScript contracts, standardized network providers to get BCH blockchain information and a simple API for transaction building when using smart contracts. The TypeScript SDK has [full TypeScript integration](#full-typescript-integration) with the CashScript smart contract language. -The full type-safety enables clear APIs which communicates info about the expected argument to each function and method. +The full type-safety enables clear APIs which communicate info about the expected arguments to each function and method. This in turn speeds up development time and allows for higher code quality with better safety guarantees. :::info @@ -19,7 +19,7 @@ The CashScript TypeScript SDK is designed to make it as easy as possible to crea If you are not using the CashScript contract language, you can still use the CashScript SDK for transaction building and BCH networking functionality! This can be especially useful if you are familiar with the CashScript classes and want manual control over the input and outputs in a transaction. The SDK makes it easy to spend from P2PKH inputs and send to different types of outputs, including OP_RETURN data outputs. -It's also possible the use the CashScript SDK for hand-optimized contract **not** written with the CashScript contract language, but this is considered [advanced usage](#advanced-non-cashscript-contracts). +It's also possible to use the CashScript SDK for hand-optimized contracts **not** written with the CashScript contract language, but this is considered [advanced usage](#advanced-non-cashscript-contracts). ## The 4 SDK Classes @@ -35,9 +35,9 @@ The documentation also follows the structure of these 4 classes: The usage of the 4 classes in your code is as follows: before using the SDK you create one or multiple contract artifacts compiled by `cashc`. Then to start using the SDK, you instantiate a `NetworkProvider`, which you then provide to instantiate a `Contract` from an `Artifact`. Once you have a `Contract` instance, you can use it in the `TransactionBuilder`. During transaction building you might need to generate a signature, in which case you would instantiate a `SignatureTemplate`. -For an more complete example of the SDK flow, refer to the [SDK Example](./examples.md). +For a more complete example of the SDK flow, refer to the [SDK Example](./examples.md). -#### example +#### Example ```ts import { Contract, ElectrumNetworkProvider, TransactionBuilder, SignatureTemplate } from 'cashscript'; @@ -58,10 +58,10 @@ const transactionBuilder = new TransactionBuilder({ provider }); ## Full TypeScript Integration -The constructor of the `Contract` class takes in an `Artifact`, this is the output of the `cashc` compiler and can be configured to either output a JSON or TS file. To have the best TypeScript integration, we recommend generating the artifact in the `.ts` format and importing it into your TypeScript project from that `.ts` file. The type benefits are explained in more details in the documentation for the [Contract](./instantiation#constructor) class. +The constructor of the `Contract` class takes in an `Artifact`, this is the output of the `cashc` compiler and can be configured to either output a JSON or TS file. To have the best TypeScript integration, we recommend generating the artifact in the `.ts` format and importing it into your TypeScript project from that `.ts` file. The type benefits are explained in more detail in the documentation for the [Contract](./instantiation#constructor) class. ## Advanced: non-CashScript Contracts -You can also use the CashScript SDK without relying on the CashScript contract language and compiler. This way you can still leverage the a lot of the tooling while having full control over the raw BCH script so this can be hand-written or hand-optimized. +You can also use the CashScript SDK without relying on the CashScript contract language and compiler. This way you can still leverage a lot of the tooling while having full control over the raw BCH script so this can be hand-written or hand-optimized. There's two ways to go about this, either you create a custom `Artifact` so you can still use the `Contract` class or you create a custom `Unlocker` to use in the transaction building directly. These two methods for using hand optimized contract bytecode are discussed in the [optimization guide](/docs/guides/optimization#advanced-hand-optimizing-bytecode). diff --git a/website/docs/showcase.md b/website/docs/showcase.md index e3b37426..e1eead82 100644 --- a/website/docs/showcase.md +++ b/website/docs/showcase.md @@ -66,7 +66,7 @@ BCH Guru is a non-custodial price-prediction platform and a collectible NFT proj -The CashTokens Studio is an application for creating CashTokens and for their managing metadata updates and reserved supply. The CashTokens Studio uses CashScript to lock the AuthUTXO in an AuthGuard contract to prevent accidentally spending the authority to be able to update the token's metadata or release reserved supply. +The CashTokens Studio is an application for creating CashTokens and for managing their metadata updates and reserved supply. The CashTokens Studio uses CashScript to lock the AuthUTXO in an AuthGuard contract to prevent accidentally spending the authority to be able to update the token's metadata or release reserved supply. ## FundMe.cash From 859fcc83530c604cfa757b39f307fd6f5240106e Mon Sep 17 00:00:00 2001 From: Mathieu Geukens Date: Mon, 8 Jun 2026 16:24:34 +0200 Subject: [PATCH 4/5] improve warning on legacy docs --- website/docs/sdk/legacy-transaction-builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/sdk/legacy-transaction-builder.md b/website/docs/sdk/legacy-transaction-builder.md index 16a2ff11..6a4cbdeb 100644 --- a/website/docs/sdk/legacy-transaction-builder.md +++ b/website/docs/sdk/legacy-transaction-builder.md @@ -3,7 +3,7 @@ title: Old Transaction Builder --- :::caution -This is the documentation for the old and now deprecated 'Simple Transaction Builder' which operated on a single contract. +This is the documentation for the old 'Simple Transaction Builder' which operated on a single contract. This API was **removed in v0.12** and is no longer available in the SDK. It is strongly recommended to migrate over to the new default transaction builder [using the migration notes](/docs/releases/migration-notes). ::: From 897a2feba702273997a9dd701fc41da270136762 Mon Sep 17 00:00:00 2001 From: Rosco Kalis Date: Tue, 9 Jun 2026 10:12:26 +0200 Subject: [PATCH 5/5] Use swapEndianness instead of reverseHex --- website/docs/guides/cashtokens.md | 10 ++++------ website/docs/guides/deployment.md | 10 +++------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/website/docs/guides/cashtokens.md b/website/docs/guides/cashtokens.md index 42a9c5eb..0c0393af 100644 --- a/website/docs/guides/cashtokens.md +++ b/website/docs/guides/cashtokens.md @@ -162,13 +162,11 @@ The easiest way to prevent issues with "junk" empty NFTs is to check that only N The `tokenCategory` introspection variable returns the tokenCategory in the original unreversed order, this is unlike wallets and explorers which use the reversed byte-order. So be careful about the byte-order of `tokenCategory` when working with BCH smart contracts. ```ts -// Reverse byte order of a hex string. -function reverseHex(hex: string): string { - return hex.match(/../g)!.reverse().join(''); -} +import { swapEndianness } from '@bitauth/libauth'; +import { Contract } from 'cashscript'; -// when using a standard encoded tokenId, reverse the hex before using it in your contract -const contract = new Contract(artifact, [reverseHex(tokenId)], { provider }) +// when using a standard encoded tokenId, swap the endianness of the hex before using it in your contract +const contract = new Contract(artifact, [swapEndianness(tokenId)], { provider }) ``` #### Combined BCH + CashTokens UTXOs diff --git a/website/docs/guides/deployment.md b/website/docs/guides/deployment.md index 1e8bfe2e..221335e5 100644 --- a/website/docs/guides/deployment.md +++ b/website/docs/guides/deployment.md @@ -41,15 +41,11 @@ Some constructor arguments are simple constants. Others need to be prepared befo - **Public keys**: oracle or owner checks often require a public key or public key hash that must be derived before deployment. ```ts +import { swapEndianness } from '@bitauth/libauth'; import { Contract } from 'cashscript'; -// Reverse byte order of a hex string. -function reverseHex(hex: string): string { - return hex.match(/../g)!.reverse().join(''); -} - -// Token IDs are often byte-reversed before being used in contract arguments. -const argsA = [reverseHex(tokenIdX), reverseHex(tokenIdY)] as const; +// when using a standard encoded tokenId, swap the endianness of the hex before using it in your contract +const argsA = [swapEndianness(tokenIdX), swapEndianness(tokenIdY)] as const; const contractA = new Contract(artifactA, [...argsA], { provider }); // Pass contractA's locking bytecode to a dependent contract.