Building a Transaction Manually in the Browser
Manually creating a transaction in a browser environment gives you precise control over UTXO selection, calldata crafting, and transaction parameters. This guide walks you through the process of building a transaction manually, specifically for interactions with OP_NET smart contracts.
Steps to Build a Transaction
1. Request Public Key and UTXOs
First, you must fetch the connected wallet's public key and UTXOs to fund the transaction.
import { networks } from "@btc-vision/bitcoin";
import { Address } from "@btc-vision/transaction";
const pubKey = await opnet.getPublicKey();
const account = Address.fromString(pubKey); // Sender's address
const utxos = await provider.utxoManager.getUTXOs({
address: account.p2tr(networks.regtest), // Sender's address (P2TR)
});
Ensure you have enough UTXOs to cover the transaction inputs, gas fees, and any additional outputs.
Learn more about UTXO Manager.
2. Define the Contract Instance
Use the getContract
method to define the contract you want to interact with. This step ensures you have a properly typed contract instance that can encode calldata for the desired function.
const contractAddress = "bcrt1qexampleaddress"; // Replace with a valid contract address
const contractInstance = getContract<IOP_20Contract>(
contractAddress, // Contract address
OP_20_ABI, // Contract ABI
provider, // Provider instance
networks.regtest, // Target network
account // Sender's address
);
Ensure the contract instance matches the contract you want to interact with, including the correct address and ABI.
Learn more about Interacting with Smart Contracts.
3. Encode Calldata
Prepare the calldata for the contract interaction using the contract instance's methods, ensuring it matches the function you want to call.
const mintCall = await contractInstance.mint(account, 100n);
// Alternatively, you can manually encode the calldata
// const mintCall = contractInstance.encodeCalldata(
// "mint", // Function name
// [account, 100n] // Function arguments
// );
if (!mintCall || !mintCall.calldata) {
throw new Error("Mint call failed.");
}
4. Configure Interaction Parameters
Set up the InteractionParametersWithoutSigner
object, which includes details like the sender, UTXOs, calldata, and gas settings.
const interactionParameters: InteractionParametersWithoutSigner = {
from: account.p2tr(networks.regtest), // Sender's address (P2TR)
to: contractAddress, // Contract address
utxos, // UTXOs for funding
network: networks.regtest, // Target network
feeRate: Number((await provider.gasParameters()).baseGas * 2n), // Gas fee rate (satoshis per byte)
priorityFee: 5000n, // Priority fee for faster processing
calldata: mintCall.calldata, // Call data from the mint function call
};
5. Sign the Transaction
Finally, sign the transaction using the signInteraction
method provided by the OP_WALLET Web3Provider.
const signedTransaction = await opnet.web3.signInteraction(
interactionParameters
);
console.log("Signed Transaction:", signedTransaction);
You can also use the opnet.web3.signAndBroadcastInteraction
method to sign and broadcast the transaction in a single step.
const signedTransaction = await opnet.web3.signAndBroadcastInteraction(
interactionParameters
);
console.log("Signed Transaction:", signedTransaction);
Full Example
import { networks } from "@btc-vision/bitcoin";
import {
Address,
InteractionParametersWithoutSigner,
} from "@btc-vision/transaction";
import { getContract, IOP_20Contract, JSONRpcProvider, OP_20_ABI } from "opnet";
// Initialize the provider and OP_NET instance
const provider = new JSONRpcProvider(
"https://regtest.opnet.org",
networks.regtest
);
const opnet = window.opnet;
if (!opnet || !opnet.web3) {
throw new Error("OP_WALLET is not available in this browser.");
}
// Fetch the public key for the connected wallet
const pubKey = await opnet.getPublicKey();
const account = Address.fromString(pubKey);
// Fetch UTXOs for funding
const utxos = await provider.utxoManager.getUTXOs({
address: account.p2tr(networks.regtest), // Sender's address (P2TR)
});
if (!utxos || utxos.length === 0) {
throw new Error("No UTXOs available.");
}
// Define the contract address (replace with a valid contract address)
const contractAddress = "bcrt1qexampleaddress";
// Define the contract instance
const contractInstance = getContract<IOP_20Contract>(
contractAddress, // Contract address
OP_20_ABI, // Contract ABI
provider, // Provider instance
networks.regtest, // Target network
account // Sender's address
);
// Example: Call a function on the contract to mint tokens
const mintCall = await contractInstance.mint(account, 100n);
// Alternatively, you can manually encode the calldata
// const mintCall = contractInstance.encodeCalldata(
// "mint", // Function name
// [account, 100n] // Function arguments
// );
if (!mintCall || !mintCall.calldata) {
throw new Error("Mint call failed.");
}
// Define interaction parameters
const interactionParameters: InteractionParametersWithoutSigner = {
from: account.p2tr(networks.regtest), // Sender's address (P2TR)
to: contractAddress, // Contract address
utxos, // UTXOs for funding
network: networks.regtest, // Target network
feeRate: Number((await provider.gasParameters()).baseGas * 2n), // Gas fee rate (satoshis per byte)
priorityFee: 5000n, // Priority fee for faster processing
calldata: mintCall.calldata, // Call data from the mint function call
};
// Sign the transaction using the Web3Provider
const signedTransaction = await opnet.web3.signInteraction(
interactionParameters
);
console.log("Signed Transaction:", signedTransaction);
Transaction Outputs
The signInteraction
method returns an array with the following structure:
[0]
: The first transaction to send.[1]
: The second transaction to send.[2]
: An array of newly created UTXOs.
You must broadcast the transactions in the order they appear in the output array. (i.e., [0]
first, then [1]
).
Learn how to send a manually built transaction on OP_NET.
Best Practices
- Always fetch UTXOs that meet the requirements of your transaction and validate their sufficiency.
- Ensure your gas estimate includes a buffer to prevent transaction failures due to fluctuating network conditions.
- Verify the calldata matches the intended contract interaction to avoid unexpected behavior.