opnet
Version:
The perfect library for building Bitcoin-based applications.
671 lines (540 loc) • 17.9 kB
Markdown
# Contract Deployment Examples
This guide covers deploying smart contracts to the OPNet network.
## Table of Contents
- [Overview](#overview)
- [Prerequisites](#prerequisites)
- [Basic Deployment](#basic-deployment)
- [Deployment with Constructor](#deployment-with-constructor)
- [IDeploymentParameters Reference](#ideploymentparameters-reference)
- [Calldata Construction](#calldata-construction)
- [Deployment Result](#deployment-result)
- [Batch Deployment](#batch-deployment)
- [Verify Deployment](#verify-deployment)
- [Complete Deployment Service](#complete-deployment-service)
- [Best Practices](#best-practices)
## Overview
Contract deployment on OPNet involves creating a funding transaction and a reveal transaction that contains the contract bytecode.
```mermaid
sequenceDiagram
participant W as Wallet
participant F as TransactionFactory
participant N as OPNet Network
W->>F: Prepare deployment (bytecode + calldata)
F->>F: Create funding TX
F->>F: Create reveal TX
W->>N: Broadcast funding TX
W->>N: Broadcast reveal TX
N->>N: Validate & deploy contract
N-->>W: Contract address
```
## Prerequisites
```typescript
import {
AddressTypes,
BinaryWriter,
IDeploymentParameters,
TransactionFactory,
Wallet,
UTXO,
Mnemonic,
MLDSASecurityLevel,
} from '@btc-vision/transaction';
import { JSONRpcProvider } from 'opnet';
import { networks } from '@btc-vision/bitcoin';
import * as fs from 'fs';
const network = networks.regtest;
const provider = new JSONRpcProvider({ url: 'https://regtest.opnet.org', network });
const mnemonic = new Mnemonic('your seed phrase here ...', '', network, MLDSASecurityLevel.LEVEL2);
const wallet = mnemonic.deriveUnisat(AddressTypes.P2TR, 0); // OPWallet-compatible
const factory = new TransactionFactory();
```
## Basic Deployment
### Deploy Contract Without Constructor
```typescript
async function deployContract(
bytecodeFile: string,
wallet: Wallet
): Promise<string> {
// Read bytecode from WASM file
const bytecode = fs.readFileSync(bytecodeFile);
// Get UTXOs for funding
const utxos = await provider.utxoManager.getUTXOs({
address: wallet.p2tr,
});
if (utxos.length === 0) {
throw new Error('No UTXOs available for deployment');
}
// Get challenge for proof of work
const challenge = await provider.getChallenge();
// Prepare deployment parameters
const deploymentParams: IDeploymentParameters = {
from: wallet.p2tr,
utxos: utxos,
signer: wallet.keypair,
mldsaSigner: wallet.mldsaKeypair,
network: network,
feeRate: 5,
priorityFee: 0n,
gasSatFee: 10_000n,
bytecode: bytecode,
challenge: challenge,
linkMLDSAPublicKeyToAddress: true,
revealMLDSAPublicKey: true,
};
// Sign deployment
const deployment = await factory.signDeployment(deploymentParams);
console.log('Contract address:', deployment.contractAddress);
console.log('Funding TX:', deployment.transaction[0]);
console.log('Reveal TX:', deployment.transaction[1]);
// Broadcast funding transaction
const fundingResult = await provider.sendRawTransaction(
deployment.transaction[0]
);
console.log('Funding TX ID:', fundingResult.txid);
// Broadcast reveal transaction
const revealResult = await provider.sendRawTransaction(
deployment.transaction[1]
);
console.log('Reveal TX ID:', revealResult.txid);
return deployment.contractAddress;
}
// Usage
const contractAddress = await deployContract('./bytecode/MyContract.wasm', wallet);
console.log('Deployed at:', contractAddress);
```
## Deployment with Constructor
### Token Contract with Initial Parameters
```typescript
async function deployToken(
bytecodeFile: string,
name: string,
symbol: string,
decimals: number,
maxSupply: bigint,
wallet: Wallet
): Promise<string> {
const bytecode = fs.readFileSync(bytecodeFile);
// Construct calldata for constructor
const calldata = new BinaryWriter();
calldata.writeStringWithLength(name);
calldata.writeStringWithLength(symbol);
calldata.writeU8(decimals);
calldata.writeU256(maxSupply);
// Get UTXOs and challenge
const utxos = await provider.utxoManager.getUTXOs({
address: wallet.p2tr,
});
const challenge = await provider.getChallenge();
const deploymentParams: IDeploymentParameters = {
from: wallet.p2tr,
utxos: utxos,
signer: wallet.keypair,
mldsaSigner: wallet.mldsaKeypair,
network: network,
feeRate: 5,
priorityFee: 0n,
gasSatFee: 10_000n,
bytecode: bytecode,
calldata: calldata.getBuffer(),
challenge: challenge,
linkMLDSAPublicKeyToAddress: true,
revealMLDSAPublicKey: true,
};
const deployment = await factory.signDeployment(deploymentParams);
// Broadcast transactions
await provider.sendRawTransaction(deployment.transaction[0]);
await provider.sendRawTransaction(deployment.transaction[1]);
return deployment.contractAddress;
}
// Usage
const tokenAddress = await deployToken(
'./bytecode/MyToken.wasm',
'My Token',
'MTK',
8,
21_000_000_00000000n, // 21 million with 8 decimals
wallet
);
```
### NFT Contract with Configuration
```typescript
async function deployNFT(
bytecodeFile: string,
name: string,
symbol: string,
maxSupply: bigint,
baseUri: string,
wallet: Wallet
): Promise<string> {
const bytecode = fs.readFileSync(bytecodeFile);
// Construct NFT constructor calldata
const calldata = new BinaryWriter();
calldata.writeStringWithLength(name);
calldata.writeStringWithLength(symbol);
calldata.writeU256(maxSupply);
calldata.writeStringWithLength(baseUri);
const utxos = await provider.utxoManager.getUTXOs({
address: wallet.p2tr,
});
const challenge = await provider.getChallenge();
const deploymentParams: IDeploymentParameters = {
from: wallet.p2tr,
utxos: utxos,
signer: wallet.keypair,
mldsaSigner: wallet.mldsaKeypair,
network: network,
feeRate: 5,
priorityFee: 0n,
gasSatFee: 15_000n, // Higher gas for NFT contract
bytecode: bytecode,
calldata: calldata.getBuffer(),
challenge: challenge,
linkMLDSAPublicKeyToAddress: true,
revealMLDSAPublicKey: true,
};
const deployment = await factory.signDeployment(deploymentParams);
await provider.sendRawTransaction(deployment.transaction[0]);
await provider.sendRawTransaction(deployment.transaction[1]);
return deployment.contractAddress;
}
// Usage
const nftAddress = await deployNFT(
'./bytecode/MyNFT.wasm',
'My NFT Collection',
'MNFT',
10000n,
'ipfs://QmBaseHash/',
wallet
);
```
## IDeploymentParameters Reference
```typescript
interface IDeploymentParameters {
// Required: Wallet information
from: string; // Deployer's p2tr address
signer: UniversalSigner; // ECDSA keypair
mldsaSigner?: QuantumBIP32Interface; // ML-DSA keypair (optional)
network: Network; // Bitcoin network
// Required: UTXOs and funding
utxos: UTXO[]; // UTXOs for funding
feeRate: number; // Fee rate in sat/vB
priorityFee?: bigint; // Additional priority fee
gasSatFee: bigint; // Gas allocation in sats
// Required: Contract data
bytecode: Uint8Array; // WASM bytecode
challenge: ProofOfWorkChallenge; // PoW challenge
// Optional: Constructor calldata
calldata?: Uint8Array; // Constructor arguments
// Optional: ML-DSA options
linkMLDSAPublicKeyToAddress?: boolean; // Link quantum key
revealMLDSAPublicKey?: boolean; // Reveal in transaction
}
```
## Calldata Construction
### Using BinaryWriter
```typescript
import { BinaryWriter } from '@btc-vision/transaction';
// Create calldata for various types
function createConstructorCalldata(): Uint8Array {
const writer = new BinaryWriter();
// String with length prefix
writer.writeStringWithLength('My Contract');
// Numbers
writer.writeU8(8); // uint8
writer.writeU16(1000); // uint16
writer.writeU32(1000000); // uint32
writer.writeU64(1000000n); // uint64 (bigint)
writer.writeU256(10n ** 18n); // uint256 (bigint)
// Boolean
writer.writeBoolean(true);
// Address
writer.writeAddress(Address.fromString('0x...'));
// Bytes
writer.writeBytes(new TextEncoder().encode('data'));
// Arrays
writer.writeU16(3); // Array length
writer.writeU256(100n);
writer.writeU256(200n);
writer.writeU256(300n);
return writer.getBuffer();
}
```
### Common Constructor Patterns
```typescript
// Token constructor
function tokenCalldata(
name: string,
symbol: string,
decimals: number,
maxSupply: bigint
): Uint8Array {
const writer = new BinaryWriter();
writer.writeStringWithLength(name);
writer.writeStringWithLength(symbol);
writer.writeU8(decimals);
writer.writeU256(maxSupply);
return writer.getBuffer();
}
// NFT constructor
function nftCalldata(
name: string,
symbol: string,
maxSupply: bigint,
pricePerToken: bigint,
baseUri: string
): Uint8Array {
const writer = new BinaryWriter();
writer.writeStringWithLength(name);
writer.writeStringWithLength(symbol);
writer.writeU256(maxSupply);
writer.writeU64(pricePerToken);
writer.writeStringWithLength(baseUri);
return writer.getBuffer();
}
// Staking constructor
function stakingCalldata(
stakingToken: Address,
rewardToken: Address,
rewardRate: bigint
): Uint8Array {
const writer = new BinaryWriter();
writer.writeAddress(stakingToken);
writer.writeAddress(rewardToken);
writer.writeU256(rewardRate);
return writer.getBuffer();
}
```
## Deployment Result
The `signDeployment` method returns:
```typescript
interface DeploymentResult {
// The deployed contract's address (p2op format)
contractAddress: string;
// Array of raw transactions [funding, reveal]
transaction: [string, string];
// UTXOs available after deployment
utxos: UTXO[];
// Estimated fees paid
estimatedFees: bigint;
}
```
## Batch Deployment
Deploy multiple contracts in sequence:
```typescript
interface ContractDeployment {
file: string;
calldata?: Uint8Array;
}
async function batchDeploy(
contracts: ContractDeployment[],
wallet: Wallet
): Promise<Map<string, string>> {
const deployed = new Map<string, string>();
let utxos = await provider.utxoManager.getUTXOs({
address: wallet.p2tr,
});
for (const contract of contracts) {
const bytecode = fs.readFileSync(contract.file);
const challenge = await provider.getChallenge();
const deploymentParams: IDeploymentParameters = {
from: wallet.p2tr,
utxos: utxos,
signer: wallet.keypair,
mldsaSigner: wallet.mldsaKeypair,
network: network,
feeRate: 5,
priorityFee: 0n,
gasSatFee: 10_000n,
bytecode: bytecode,
calldata: contract.calldata,
challenge: challenge,
linkMLDSAPublicKeyToAddress: true,
revealMLDSAPublicKey: true,
};
try {
const deployment = await factory.signDeployment(deploymentParams);
await provider.sendRawTransaction(deployment.transaction[0]);
await provider.sendRawTransaction(deployment.transaction[1]);
deployed.set(contract.file, deployment.contractAddress);
// Update UTXOs for next deployment
utxos = deployment.utxos;
console.log(`Deployed ${contract.file} at ${deployment.contractAddress}`);
} catch (error) {
console.error(`Failed to deploy ${contract.file}:`, error);
}
}
return deployed;
}
// Usage
const contracts: ContractDeployment[] = [
{ file: './bytecode/Token.wasm', calldata: tokenCalldata('Token', 'TKN', 8, 1000000n) },
{ file: './bytecode/NFT.wasm', calldata: nftCalldata('NFT', 'NFT', 10000n, 100000n, 'ipfs://') },
];
const addresses = await batchDeploy(contracts, wallet);
console.log('Deployed contracts:', addresses);
```
## Verify Deployment
```typescript
async function verifyDeployment(
contractAddress: string
): Promise<boolean> {
try {
const code = await provider.getCode(
Address.fromString(contractAddress),
true
);
if (code.bytecode.length > 0) {
console.log('Contract verified');
console.log('Bytecode size:', code.bytecode.length, 'bytes');
return true;
}
} catch {
console.log('Contract not found at address');
}
return false;
}
// Usage
const isDeployed = await verifyDeployment(contractAddress);
```
## Complete Deployment Service
```typescript
class DeploymentService {
private provider: JSONRpcProvider;
private factory: TransactionFactory;
private wallet: Wallet;
private network: Network;
constructor(
provider: JSONRpcProvider,
wallet: Wallet,
network: Network
) {
this.provider = provider;
this.factory = new TransactionFactory();
this.wallet = wallet;
this.network = network;
}
async deploy(
bytecode: Uint8Array,
calldata?: Uint8Array,
options?: {
feeRate?: number;
gasSatFee?: bigint;
}
): Promise<{
contractAddress: string;
fundingTxId: string;
revealTxId: string;
}> {
const utxos = await this.provider.utxoManager.getUTXOs({
address: this.wallet.p2tr,
});
if (utxos.length === 0) {
throw new Error('No UTXOs available');
}
const challenge = await this.provider.getChallenge();
const deploymentParams: IDeploymentParameters = {
from: this.wallet.p2tr,
utxos: utxos,
signer: this.wallet.keypair,
mldsaSigner: this.wallet.mldsaKeypair,
network: this.network,
feeRate: options?.feeRate ?? 5,
priorityFee: 0n,
gasSatFee: options?.gasSatFee ?? 10_000n,
bytecode: bytecode,
calldata: calldata,
challenge: challenge,
linkMLDSAPublicKeyToAddress: true,
revealMLDSAPublicKey: true,
};
const deployment = await this.factory.signDeployment(deploymentParams);
// Broadcast
const fundingResult = await this.provider.sendRawTransaction(
deployment.transaction[0]
);
const revealResult = await this.provider.sendRawTransaction(
deployment.transaction[1]
);
return {
contractAddress: deployment.contractAddress,
fundingTxId: fundingResult.txid,
revealTxId: revealResult.txid,
};
}
async deployToken(
bytecodeFile: string,
name: string,
symbol: string,
decimals: number,
maxSupply: bigint
): Promise<string> {
const bytecode = fs.readFileSync(bytecodeFile);
const calldata = tokenCalldata(name, symbol, decimals, maxSupply);
const result = await this.deploy(bytecode, calldata);
return result.contractAddress;
}
async deployNFT(
bytecodeFile: string,
name: string,
symbol: string,
maxSupply: bigint,
baseUri: string
): Promise<string> {
const bytecode = fs.readFileSync(bytecodeFile);
const writer = new BinaryWriter();
writer.writeStringWithLength(name);
writer.writeStringWithLength(symbol);
writer.writeU256(maxSupply);
writer.writeStringWithLength(baseUri);
const result = await this.deploy(bytecode, writer.getBuffer());
return result.contractAddress;
}
async verify(contractAddress: string): Promise<boolean> {
try {
const code = await this.provider.getCode(
Address.fromString(contractAddress),
true
);
return code.bytecode.length > 0;
} catch {
return false;
}
}
}
// Usage
const deployer = new DeploymentService(provider, wallet, network);
const tokenAddr = await deployer.deployToken(
'./bytecode/Token.wasm',
'My Token',
'MTK',
8,
1000000_00000000n
);
const verified = await deployer.verify(tokenAddr);
console.log('Token deployed:', tokenAddr, 'Verified:', verified);
```
## Best Practices
1. **Test on Regtest**: Always deploy to regtest first before mainnet
2. **Sufficient UTXOs**: Ensure wallet has enough UTXOs for funding
3. **Challenge Timing**: Get challenge right before deployment (they expire)
4. **Verify Deployment**: Always verify the contract was deployed correctly
5. **Save Addresses**: Store deployed contract addresses for reference
6. **Gas Estimation**: Use higher gasSatFee for complex contracts
## Next Steps
- [OP20 Examples](./op20-examples.md) - Interact with deployed tokens
- [OP721 Examples](./op721-examples.md) - Interact with deployed NFTs
- [Contract Overview](../contracts/overview.md) - Contract interaction patterns
[← Previous: Advanced Swaps](./advanced-swaps.md) | [Next: Bitcoin Balances →](../bitcoin/balances.md)