Skip to content

Commit b496879

Browse files
committed
Update Contract Deployment guide + sidebar order
1 parent cfa6b3a commit b496879

2 files changed

Lines changed: 24 additions & 45 deletions

File tree

website/docs/guides/deployment.md

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ sidebar_label: Contract Deployment
77

88
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.
99

10-
Deployment becomes necessary when you're building **stateful contract systems with authentication**. 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.
10+
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.
1111

1212
:::tip
13-
If your contract simply enforces spending conditions (like requiring multiple signatures or a timelock), you don't need a deployment. Just use the contract address. Deployment is for systems where CashTokens authenticate and track contract state.
13+
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.
1414
:::
1515

1616
## Before the Genesis Transaction
1717

1818
### Contract Addresses
1919

20-
CashScript contract addresses are **deterministic**: they are derived from the contract's compiled bytecode (the artifact) combined with the constructor arguments. Given the same artifact and the same arguments, you will always get the same address.
20+
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.
2121

2222
```ts
2323
import { Contract } from 'cashscript';
@@ -33,9 +33,9 @@ This means you can reconstruct a contract's address at any time, on any machine,
3333

3434
### Preparing Constructor Arguments
3535

36-
Some constructor arguments are straightforward constants (block heights, timelocks). Others require preparation before deployment:
36+
Some constructor arguments are straightforward constants, others require preparation before deployment:
3737

38-
- **Token IDs from other categories** — in complex systems with multiple token categories, contracts often reference each other's token IDs as constructor arguments. Note that token IDs must be byte-reversed when passed as constructor arguments, because the Bitcoin Cash VM uses little-endian byte order internally while token IDs are displayed in big-endian (the same applies to transaction IDs).
38+
- **Token IDs from other categories** — in complex systems with multiple token categories, contracts often reference each other's token IDs as constructor arguments.
3939
- **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.
4040
- **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.
4141

@@ -48,36 +48,25 @@ function reverseHex(hex: string): string {
4848
}
4949

5050
// Token IDs must be byte-reversed for use as constructor args
51-
const argsA = [reverseHex(tokenIdB), reverseHex(tokenIdC)] as const;
51+
const argsA = [reverseHex(tokenIdX), reverseHex(tokenIdY)] as const;
5252
const contractA = new Contract(artifactA, [...argsA], { provider });
5353

5454
// Extract locking bytecode to pass to a dependent contract
55-
const lockingBytecodeResult = cashAddressToLockingBytecode(contractA.address);
56-
if (typeof lockingBytecodeResult === 'string') throw new Error('Invalid address');
57-
const lockingBytecodeA = binToHex(lockingBytecodeResult.bytecode);
58-
59-
const argsB = [lockingBytecodeA, oraclePublicKey, startBlockHeight] as const;
55+
const argsB = [contractA.lockingBytecode, oraclePublicKey, startBlockHeight] as const;
6056
const contractB = new Contract(artifactB, [...argsB], { provider });
6157
```
6258

6359
:::caution
64-
Constructor arguments are baked into the contract address. If any argument changes, even a single byte of a public key, the contract address changes entirely. 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.
60+
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.
6561
:::
6662

6763
### Token IDs
6864

69-
In Bitcoin Cash, a new token category is created when a UTXO with `vout: 0` is spent as input index 0 of a transaction. Both conditions must be met. The resulting token ID equals the txid of that spent UTXO.
70-
71-
```ts
72-
// The token ID equals the txid of the vout0 UTXO spent as input index 0
73-
const tokenId = genesisUtxo.txid;
74-
```
75-
76-
Once you have the vout0 UTXO, its txid (the future token ID) is already known before constructing the genesis transaction.
65+
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.
7766

7867
### Setup Wallet
7968

80-
To create vout0 UTXOs and broadcast the genesis transaction, you need a funded wallet. This is typically a standard P2PKH wallet derived from a WIF private key or an HD seed phrase, with enough BCH to fund all contract outputs (each needs at least dust amount, typically 1000 sats).
69+
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).
8170

8271
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.
8372

@@ -108,10 +97,6 @@ async function createVout0(
10897

10998
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.
11099

111-
:::note
112-
If you plan to include a BCMR OP_RETURN in your genesis transaction, keep the setup wallet "quiet" with minimal transaction history. Some wallets resolve token metadata by querying all transactions on the vout0 address, and a busy address can cause this resolution to be slow or fail.
113-
:::
114-
115100
:::tip
116101
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.
117102
:::
@@ -138,13 +123,16 @@ const tokenId = genesisUtxo.txid;
138123
const txBuilder = new TransactionBuilder({ provider });
139124
txBuilder.addInput(genesisUtxo, template.unlockP2PKH());
140125
txBuilder.addOutput({
141-
to: contractA.tokenAddress, amount: 1000n,
126+
to: contractA.tokenAddress,
127+
amount: 1000n,
142128
token: { category: tokenId, amount: 9223372036854775807n },
143129
});
144130
txBuilder.addOutput({
145-
to: contractB.tokenAddress, amount: 1000n,
131+
to: contractB.tokenAddress,
132+
amount: 1000n,
146133
token: {
147-
category: tokenId, amount: 0n,
134+
category: tokenId,
135+
amount: 0n,
148136
nft: { capability: 'minting', commitment: initialStateHex },
149137
},
150138
});
@@ -161,29 +149,20 @@ NFT commitments are used to encode the initial state of a contract at deployment
161149
Use `@bitauth/libauth` to encode values into commitment bytes. This ensures values follow the same VM number encoding that the Bitcoin Cash VM uses:
162150

163151
```ts
164-
import { binToHex, bigIntToVmNumber, padMinimallyEncodedVmNumber } from '@bitauth/libauth';
165-
166-
// Encode a bigint as a fixed-length VM number (matching OP_NUM2BIN behaviour)
167-
function bigIntToFixedBytes(value: bigint, byteLength: number): string {
168-
const minimal = bigIntToVmNumber(value);
169-
if (minimal.length > byteLength) throw new Error('value exceeds the requested byteLength');
170-
// Return early if already the right length — padMinimallyEncodedVmNumber
171-
// would overshoot by one byte in this case
172-
if (minimal.length === byteLength) return binToHex(minimal);
173-
return binToHex(padMinimallyEncodedVmNumber(minimal, byteLength));
174-
}
152+
import { binToHex } from '@bitauth/libauth';
153+
import { encodeIntAsFixedBytes } from '@cashscript/utils';
175154

176155
// Encode initial state as a commitment (e.g. 4-byte counter + 4-byte blockHeight)
177156
function encodeInitialState(counter: bigint, blockHeight: bigint): string {
178-
return bigIntToFixedBytes(counter, 4) + bigIntToFixedBytes(blockHeight, 4);
157+
const encodedCounter = encodeIntAsFixedBytes(counter, 4);
158+
const encodedBlockHeight = encodeIntAsFixedBytes(blockHeight, 4);
159+
return binToHex(encodedCounter) + binToHex(encodedBlockHeight);
179160
}
180161
```
181162

182163
### UTXO Duplication
183164

184-
For systems expecting concurrent usage, 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. The number of duplicates you create determines the system's concurrency capacity. See [accidental race-conditions](/docs/guides/lifecycle#accidental-race-conditions) in the lifecycle guide for more on why this matters.
185-
186-
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:
165+
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:
187166

188167
```ts
189168
const supplyPerUtxo = MAX_TOKEN_SUPPLY / BigInt(numberOfDuplicates);
@@ -195,7 +174,7 @@ const supplyLastUtxo = supplyPerUtxo + remainder;
195174

196175
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.
197176

198-
To set up the authchain during deployment, include a dust output (e.g. 1000 sats) 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.
177+
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.
199178

200179
```ts
201180
// Include a dust output for the BCMR authchain

website/sidebars.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ const sidebars: SidebarsConfig = {
6161
items: [
6262
'guides/covenants',
6363
'guides/lifecycle',
64-
'guides/concurrency',
6564
'guides/cashtokens',
6665
'guides/deployment',
6766
'guides/infrastructure',
67+
'guides/concurrency',
6868
'guides/walletconnect',
6969
'guides/debugging',
7070
'guides/optimization',

0 commit comments

Comments
 (0)