Approving Tokens for Spending on OP_NET

In this section, you will learn how to approve tokens for spending, which is a crucial step before interacting with decentralized exchanges or smart contracts. When interacting with a decentralized exchange (like Motoswap), you must first approve the contract to spend your tokens on your behalf.


Step 1: Install Required Packages

Ensure you have the necessary dependencies installed in your project:

npm install opnet bitcoinjs-lib
# or
yarn add opnet bitcoinjs-lib

Step 2: Set Up the Provider

First, you need to set up the provider to communicate with the OP_NET metaprotocol.

import * as bitcoinjs from "bitcoinjs-lib";
import { JSONRpcProvider } from "opnet";

// Define the OP_NET RPC provider
const rpcUrl = "https://regtest.opnet.org";
const provider = new JSONRpcProvider(rpcUrl);

// Define the Bitcoin network (e.g., regtest, mainnet)
const network = bitcoinjs.networks.regtest;


Step 4: Approve Token Spending

To approve token spending, you need to interact with the token's smart contract (following the OP_20 standard) and give permission to the decentralized exchange (or any other contract) to spend tokens on your behalf.

import {
  IInteractionParameters,
  TransactionFactory,
  Wallet,
} from "@btc-vision/transaction";
import { getContract, IOP_20Contract, OP_20_ABI, UTXO } from "opnet";

/**
 * Approves tokens for spending on the OP_NET metaprotocol.
 *
 * @param {Wallet} walletGet - The wallet object containing keypairs and addresses.
 * @param {string} tokenAddress - The address of the token (OP_20 standard).
 * @param {bigint} approveAmount - The amount of tokens to approve for spending.
 * @param {UTXO[]} utxos - The UTXOs for funding the transaction.
 * @returns Approval transaction result
 */
async function approveToken(
  walletGet: Wallet,
  tokenAddress: string,
  approveAmount: bigint,
  utxos: UTXO[]
) {
  try {
    // Get the token contract using the OP_20 ABI
    const contract = getContract<IOP_20Contract>(
      tokenAddress,
      OP_20_ABI,
      provider,
      walletGet.p2tr
    );

    // The address of the decentralized exchange or contract that will spend the tokens
    const routerAddress = "bcrt1qplnz54sca73t8a03nh494jatr9ffjg6ecarrj8"; // Actual router address on regtest

    // Check the current allowance (how much the router is allowed to spend)
    const currentAllowance = await contract.allowance(
      walletGet.p2tr,
      routerAddress
    );

    if ("error" in currentAllowance) {
      throw new Error(currentAllowance.error);
    }

    const remainingAllowance = BigInt(currentAllowance.decoded[0].toString());

    // Only approve if the current allowance is less than the amount to approve
    if (remainingAllowance >= approveAmount) {
      return { success: true, message: "Token is already approved." };
    }

    // Approve the maximum possible amount (or the amount you want)
    const maxApprovalAmount = BigInt(
      "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
    );
    const approveTx = await contract.approve(routerAddress, maxApprovalAmount);

    if ("error" in approveTx) {
      throw new Error(approveTx.error);
    }

    // Create transaction parameters for the approval interaction
    const interactionParameters: IInteractionParameters = {
      from: walletGet.p2tr,
      to: contract.address.toString(),
      utxos, // UTXOs to fund the transaction
      signer: walletGet.keypair, // Wallet's keypair for signing the transaction
      network, // The BitcoinJS network
      feeRate: 100, // Fee rate in satoshis per byte
      priorityFee: 330n, // Priority fee for faster transaction
      calldata: approveTx.calldata as Buffer, // The calldata for the approve interaction
    };

    // Create and sign the transaction
    const transactionFactory = new TransactionFactory();
    const signedTx = await transactionFactory.signInteraction(
      interactionParameters
    );

    // Broadcast the transaction
    const firstTxBroadcast = await provider.sendRawTransaction(
      signedTx[0],
      false
    );
    if (!firstTxBroadcast.success) {
      throw new Error("First transaction broadcast failed.");
    }

    const secondTxBroadcast = await provider.sendRawTransaction(
      signedTx[1],
      false
    );
    if (!secondTxBroadcast.success) {
      throw new Error("Second transaction broadcast failed.");
    }

    return { success: true };
  } catch (error) {
    console.error("Token approval failed:", error);
    return { success: false, message: error };
  }
}

// Example usage:
async function main() {
  const walletWif = "your-wallet-private-key-in-wif-format"; // Replace with your WIF private key
  const walletGet = Wallet.fromWif(walletWif, network); // Import wallet using WIF format
  const tokenAddress = "your-token-address"; // Replace with your token's address
  const approveAmount = 100_000n; // Amount of tokens to approve for spending

  const utxos = await getUTXOs(walletGet.addresses, rpcUrl, approveAmount); // Fetch UTXOs for the approval

  const result = await approveToken(
    walletGet,
    tokenAddress,
    approveAmount,
    utxos
  );
  if (result.success) {
    console.log("Token successfully approved for spending.");
  } else {
    console.error("Approval failed:", result.message);
  }
}

main();

Last updated