Skill: contact-nbwfoundation/btcd-skill-beta (version 1.0.0)

--- name: btcd-skill-beta description: Run the BTCD collateralization flow on PGP chain. Use when the user asks to run a BTCD loan, collateralization flow, create/take orders, lock BTC, submit proofs, claim BTCD tokens, or repay loans on the PGP network. --- # BTCD PGP Collateralization Flow This skill contains a **complete, self-contained** Node.js project to run the full BTCD collateralization lifecycle on the PGP (Elastos) chain. ## Bundled Code Location All runnable code is inside the `scripts/` directory relative to this SKILL.md: ``` scripts/ ├── package.json ├── .env.template.txt ├── setup.js ├── 00-create-order.js ├── 01-take-order.js ├── 02-lock-btc-collateral.js ├── 03-submit-btc-proof.js ├── 05-claim-btcd-tokens.js ├── 06-repay-loan.js ├── abi/ # Contract ABIs (LoanContract, Order, Issuer, ERC20, ArbitratorManager) ├── utils/ # Shared utilities (config, wallet, EVM/BTC clients, logger, state, proof, arbiter) └── state/ # Flow state persists here ``` ## Setup Instructions 1. **Copy the entire `scripts/` directory** to a working directory: ```bash cp -r <path-to-this-skill>/scripts/ /tmp/btcd-flow/ cd /tmp/btcd-flow/ ``` 2. **Create `.env`** from the template. Only two values need changing: ```bash cp .env.template.txt .env ``` Edit `.env` and set: - `EVM_PRIVATE_KEY` — your EVM private key (with `0x` prefix) - `BTC_PRIVATE_KEY` — your BTC private key (hex, **no** `0x` prefix) Optionally adjust `LENDING_AMOUNT` (minimum 10) and `LENDING_DAYS` (only 90 or 180). All other values (contract addresses, RPC URLs, subgraph URLs) are **fixed for PGP chain** and must not be changed. 3. **Install dependencies:** ```bash npm install ``` 4. **Ensure wallets are funded:** - **BTC wallet**: Must have enough mainnet BTC for collateral + miner fees. - **EVM wallet**: Must have PGA tokens for gas fees on PGP chain. To get PGA tokens, go to **https://swap.pgpgas.org** — you need USDT on BSC chain, bridge it to PGP, then swap for PGA tokens and/or BTCD. ## CRITICAL: Check State Before Running **Before running any step**, always read `state/flow-state.json` to understand what has already been completed. This prevents catastrophic errors like double-locking BTC collateral. - If the `steps` object already has a completed entry for the step you're about to run, **do not re-run it**. - If step `02-lock-btc-collateral` has a `btcTxId` but no `confirmations` (or `confirmations < 3`), the BTC was already broadcast — re-running will **resume confirmation waiting**, not send a new transaction. - If `02-lock-btc-collateral` shows `confirmations >= 3`, it is fully done — proceed to step 03. Example of a partially completed state (safe to resume step 02, do NOT re-run steps 00 or 01): ```json { "steps": { "00-create-order": { "orderId": "0x...", "completedAt": "..." }, "01-take-order": { "orderId": "0x...", "preImage": "...", "completedAt": "..." }, "02-lock-btc-collateral": { "btcTxId": "abc...", "confirmations": 0 } } } ``` ## Execution Steps Run each step **sequentially** from the scripts directory. Each step has interactive confirmation prompts (press `y` + Enter). ``` Flow Progress: - [ ] Step 0: Setup - [ ] Step 1: Create Order - [ ] Step 2: Take Order - [ ] Step 3: Lock BTC Collateral - [ ] Step 4: Submit BTC Proof - [ ] Step 5: Claim BTCD Tokens ← flow complete here - [ ] Step 6: Repay Loan ← ONLY when user explicitly requests ``` ### Step 0: Setup ```bash npm run setup ``` Validates config, initializes wallets, checks EVM balance, creates `state/flow-state.json`. ### Step 1: Create Order ```bash npm run 00-create-order ``` Creates a lending order via the Issuer contract. Uses `LENDING_AMOUNT` and `LENDING_DAYS` from `.env`. Saves the **Order ID** to state. ### Step 2: Take Order ```bash npm run 01-take-order ``` Takes the order from Step 1. Generates a preImage, selects the best arbiter from the subgraph, and calls `takeOrder()`. Pays the arbiter's fee in native PGA tokens. ### Step 3: Lock BTC Collateral ```bash npm run 02-lock-btc ``` Sends BTC to the lock script address. **No interactive prompts** — the script is fully automated: - Checks `state/flow-state.json` first. If a `btcTxId` already exists, it resumes confirmation waiting instead of sending new BTC. - If already completed (`confirmations >= 3`), it skips entirely. - Defaults to standard collateral amount (no staking discount). - Broadcasts BTC tx and waits for **3 confirmations** (~30 min). Safe to re-run if interrupted — it will never double-send BTC. ### Step 4: Submit BTC Proof ```bash npm run 03-submit-proof ``` Generates ZKP proof from the BTC transaction and submits to the EVM order contract. **Skip `04-arbiter-fee`** — not needed. ### Step 5: Claim BTCD Tokens ```bash npm run 05-claim-btcd ``` Calls `borrow()` with the preImage from Step 2. BTCD tokens are minted to your EVM wallet. ### Step 6: Repay Loan (ONLY when explicitly requested by user) ```bash npm run 06-repay ``` Calculates repayment (principal + interest), approves BTCD, signs the BTC repayment transaction, and calls `repay()`. **DO NOT run this step automatically.** The flow is considered complete after Step 5 (Claim BTCD). Repaying the loan unlocks the BTC collateral and closes the position — only do this when the user explicitly asks to repay. Running it prematurely defeats the purpose of the collateralization. **Skip `07-unlock`** — not needed. ## State Management State persists in `state/flow-state.json`. Each step reads from prior steps and writes its results. When Step 6 (Repay) completes, the state file is **automatically archived** to `state/flow-state-<timestamp>.json` and removed, so the next run starts fresh. Archived files serve as a historical record of completed flows. | Step | Writes | Read By | |------|--------|---------| | 00-create-order | `orderId` | 01-take-order | | 01-take-order | `orderId`, `preImage`, `btcAddress`, `btcPublicKey`, `arbiterAddress` | 02-lock-btc, 05-claim-btcd, 06-repay | | 02-lock-btc | `btcTxId`, `scriptAddress`, `confirmations` | 03-submit-proof, 06-repay | | 05-claim-btcd | `btcdReceived` | 06-repay | ## Troubleshooting - **"Order status is X, expected Y"**: Steps must run in order. Check `state/flow-state.json`. - **BTC confirmation timeout**: Re-run Step 3 — it detects the existing `btcTxId` and resumes waiting. - **Insufficient PGA for gas**: Get PGA tokens from https://swap.pgpgas.org (USDT on BSC → bridge to PGP → swap for PGA). - **Insufficient BTC**: Ensure BTC wallet has enough for collateral amount + miner fees. - **Contract error on takeOrder**: Order may already be taken. Check order status on-chain. - **Explorer**: PGP chain transactions can be viewed at `https://pgp.elastos.io/tx/<hash>`. ## PGP Chain Contract Addresses | Contract | Address | |----------|---------| | Loan Contract | [REDACTED_SECRET] | | Issuer | [REDACTED_SECRET] | | BTCD Token | [REDACTED_SECRET] | ## Alternative RPC Endpoints | URL | Notes | |-----|-------| | `https://api.elastos.io/pg` | Primary (default) | | `https://api2.elastos.io/pg` | Backup | | `https://pgp-node.elastos.io` | Alternative |

path: scripts/.env.template.txt
content: # PGP Production ENVIRONMENT
# Agent must set their own EVM_PRIVATE_KEY and BTC_PRIVATE_KEY

EVM_PRIVATE_KEY=<AGENT_EVM_PRIVATE_KEY>
EVM_RPC_URL=https://api.elastos.io/pg

BTC_PRIVATE_KEY=<AGENT_BTC_PRIVATE_KEY_NO_0x_PREFIX>
BTC_NETWORK=mainnet
LOAN_CONTRACT_ADDRESS=0x5cD194C9d34e5B9b7A0E5cBC64C93c1c9277891e
ISSUER_CONTRACT_ADDRESS=0x91cf47c5d2b44Da124d4B54E9207aE6FB63D5Fa7
BTCD_TOKEN_ADDRESS=0xF9BF836FEd97a9c9Bfe4D4c28316b9400C59Cc6B
STAKING_FACTORY_ADDRESS=0xaB5f3Fc4F4Db4B912818C2bF2abd9c2Cbee04A36
FIST_TOKEN_ADDRESS=0x800E5c441b84a3E809E2ec922BeEE9f32f954B11
ARBITRATOR_MANAGER_ADDRESS=0x1f739Ce4238FF2B06D79D802Cb196aaE2025eae0
ARBITRATION_SUBGRAPH_URL=https://graph.eadd.co/subgraphs/name/btcd-arbitrators-pgp
NOWNODES_BTC_URL=https://nownodes-btc.bel2.org
ORDER_ID=
# Minimum 10
LENDING_AMOUNT=10
# Only 90 or 180
LENDING_DAYS=90


path: scripts/00-create-order.js
content: #!/usr/bin/env node

import { logger } from './utils/logger.js';
import { config, validateConfig } from './utils/config.js';
import { initializeWallets, getEVMWallet } from './utils/wallet-manager.js';
import {
  getIssuerContractInstance,
  getLoanContractInstance,
  waitForTransaction,
  parseUnits
} from './utils/evm-client.js';
import { loadState, updateStep } from './utils/state-manager.js';
import prompts from 'prompts';

async function main() {
  logger.section('Step 00: Create Order');

  try {
    validateConfig();
    initializeWallets();

    const state = loadState();
    const wallet = getEVMWallet();

    // Get order parameters
    const lendingAmount = config.test.lendingAmount;
    let lendingDays = config.test.lendingDays;

    const ALLOWED_DAYS = [90, 180];
    const MIN_AMOUNT = 10;

    if (parseFloat(lendingAmount) < MIN_AMOUNT) {
      throw new Error(`LENDING_AMOUNT must be at least ${MIN_AMOUNT} BTCD (got ${lendingAmount})`);
    }

    if (!ALLOWED_DAYS.includes(lendingDays)) {
      const closest = ALLOWED_DAYS.reduce((a, b) => Math.abs(b - lendingDays) < Math.abs(a - lendingDays) ? b : a);
      logger.warning(`LENDING_DAYS=${lendingDays} is not valid. Allowed values: ${ALLOWED_DAYS.join(', ')}. Forcing to ${closest}.`);
      lendingDays = closest;
    }

    logger.info('Order parameters:');
    logger.data('Lending Amount', `${lendingAmount} BTCD`);
    logger.data('Duration', `${lendingDays} days`);
    logger.data('Issuer Contract', config.contracts.issuer);

    // Confirm before proceeding
    const { confirm } = await prompts({
      type: 'confirm',
      name: 'confirm',
      message: 'Create order with these parameters?',
      initial: true
    });

    if (!confirm) {
      logger.warning('Operation cancelled by user');
      return;
    }

    // Get issuer contract instance
    const issuerContract = getIssuerContractInstance(config.contracts.issuer);

    // Get loan contract instance (factory) to parse events
    const loanContract = getLoanContractInstance(config.contracts.loanContract);

    // Convert lending amount to contract value (18 decimals for BTCD)
    const contractLendingAmount = parseUnits(lendingAmount, 18);

    logger.info('Creating order...');
    logger.data('Contract Amount', contractLendingAmount.toString());

    // Create order transaction
    const tx = await issuerContract.createOrder(contractLendingAmount, lendingDays);

    logger.info('Transaction sent, waiting for confirmation...');
    const receipt = await waitForTransaction(tx);

    // Parse OrderCreated event to get order ID
    // Note: OrderCreated event is emitted by LoanContract (factory), not Issuer
    let orderId = null;
    for (const log of receipt.logs) {
      try {
        const parsedLog = loanContract.interface.parseLog(log);
        if (parsedLog.name === 'OrderCreated') {
          orderId = parsedLog.args.orderId;
          logger.info('Found OrderCreated event');
          logger.data('  Order Type', parsedLog.args.orderType.toString());
          logger.data('  Collateral', parsedLog.args.collateral.toString());
          logger.data('  Token', parsedLog.args.token);
          logger.data('  Token Amount', parsedLog.args.tokenAmount.toString());
          break;
        }
      } catch (e) {
        // Not the event we're looking for, continue
      }
    }

    if (!orderId) {
      throw new Error('Failed to extract order ID from transaction receipt');
    }

    logger.success('Order created successfully!');
    logger.data('Order ID', orderId);
    logger.data('Transaction Hash', receipt.transactionHash);

    // Save to state
    updateStep('00-create-order', {
      orderId: orderId,
      lendingAmount: lendingAmount,
      lendingDays: lendingDays,
      txHash: receipt.transactionHash,
      blockNumber: receipt.blockNumber
    });

    logger.info('\nNext step: npm run 01-take-order');
  } catch (error) {
    logger.error('Failed to create order:', error.message);
    if (error.error) {
      logger.error('Contract error:', error.error.message);
    }
    process.exit(1);
  }
}

main();


path: scripts/01-take-order.js
content: #!/usr/bin/env node

import { logger } from './utils/logger.js';
import { config, validateConfig } from './utils/config.js';
import { initializeWallets, getEVMWallet, getBTCAddress, getBTCPublicKey } from './utils/wallet-manager.js';
import { getOrderContractInstance, waitForTransaction, getOrderData } from './utils/evm-client.js';
import { loadState, updateStep, getStep } from './utils/state-manager.js';
import { generatePreImage } from './utils/proof-generator.js';
import { selectBestArbiter, getArbiterFeesFromOrderContract } from './utils/arbiter-client.js';
import prompts from 'prompts';

async function main() {
  logger.section('Step 01: Take Order');

  try {
    validateConfig();
    initializeWallets();

    const state = loadState();
    const wallet = getEVMWallet();

    // Get order ID from previous step or config
    let orderId = config.test.orderId;
    const createOrderStep = getStep('00-create-order');
    if (createOrderStep?.orderId) {
      orderId = createOrderStep.orderId;
      logger.info('Using order ID from create-order step');
    }

    if (!orderId) {
      throw new Error('No order ID found. Either run 00-create-order or set ORDER_ID in .env');
    }

    logger.info('Taking order:');
    logger.data('Order ID', orderId);
    logger.data('Loan Contract', config.contracts.loanContract);

    // Get order details from the order contract
    const orderData = await getOrderData(config.contracts.loanContract, orderId);

    logger.info('Order details:');
    logger.data('Status', orderData.status);
    logger.data('Lending Amount', orderData.lendingAmount.toString());
    logger.data('Collateral Amount', orderData.collateralAmount.toString());
    logger.data('Interest Rate', orderData.interestRate.toString());
    logger.data('Duration', `${orderData.durationDays.toString()} days`);

    if (orderData.status !== 0) {
      throw new Error(`Order status is ${orderData.status}, expected 0 (CREATED)`);
    }

    // Generate preImage
    const { preImage, preImageHash } = generatePreImage();

    // Get BTC info
    const btcAddress = getBTCAddress();
    const btcPublicKey = getBTCPublicKey();

    logger.info('Taker info:');
    logger.data('BTC Address', btcAddress);
    logger.data('BTC Public Key', btcPublicKey);
    logger.data('PreImage Hash', preImageHash);

    // Get order contract instance early (needed for arbiter fees)
    const orderContract = getOrderContractInstance(orderId);

    // Select arbiter dynamically based on availability and deadline
    logger.info('Selecting arbiter for this order...');
    const arbiterAddress = await selectBestArbiter(orderData.durationDays);

    // Get arbiter fees from the order contract
    const arbiterFees = await getArbiterFeesFromOrderContract(orderContract, arbiterAddress);
    const arbiterEthFee = arbiterFees.ethFee;

    logger.info('Arbiter selected:');
    logger.data('Address', arbiterAddress);
    logger.data('ETH Fee', arbiterFees.ethFee.toString());
    logger.data('BTC Fee', arbiterFees.btcFee.toString());

    // Confirm before proceeding
    const { confirm } = await prompts({
      type: 'confirm',
      name: 'confirm',
      message: 'Take this order?',
      initial: true
    });

    if (!confirm) {
      logger.warning('Operation cancelled by user');
      return;
    }

    logger.info('Taking order...');

    // Take order transaction
    const tx = await orderContract.takeOrder(
      btcAddress,
      '0x' + btcPublicKey,
      '0x' + preImageHash,
      arbiterAddress,
      config.btc.network, // 'mainnet' or 'testnet'
      { value: arbiterEthFee } // Arbiter fee in native coin
    );

    logger.info('Transaction sent, waiting for confirmation...');
    const receipt = await waitForTransaction(tx);

    logger.success('Order taken successfully!');
    logger.data('Transaction Hash', receipt.transactionHash);

    // Save to state
    updateStep('01-take-order', {
      orderId: orderId,
      preImage: preImage,
      preImageHash: preImageHash,
      btcAddress: btcAddress,
      btcPublicKey: btcPublicKey,
      arbiterAddress: arbiterAddress,
      txHash: receipt.transactionHash,
      blockNumber: receipt.blockNumber
    });

    logger.info('\nNext step: npm run 02-lock-btc');
  } catch (error) {
    logger.error('Failed to take order:', error.message);
    if (error.error) {
      logger.error('Contract error:', error.error.message);
    }
    process.exit(1);
  }
}

main();


path: scripts/02-lock-btc-collateral.js
content: #!/usr/bin/env node

import { logger } from './utils/logger.js';
import { config, validateConfig } from './utils/config.js';
import { initializeWallets } from './utils/wallet-manager.js';
import { getOrderData, formatUnits } from './utils/evm-client.js';
import { requireStep, updateStep, getStep } from './utils/state-manager.js';
import { createAndSignTransaction, broadcastTransaction, waitForConfirmations } from './utils/btc-client.js';

async function main() {
  logger.section('Step 02: Lock BTC Collateral');

  try {
    validateConfig();
    initializeWallets();

    // Get data from previous step
    const takeOrderStep = requireStep('01-take-order');
    const orderId = takeOrderStep.orderId;

    // Check if this step was already partially or fully completed
    const existingStep = getStep('02-lock-btc-collateral');
    if (existingStep?.confirmations >= 3) {
      logger.success('This step was already completed!');
      logger.data('Transaction ID', existingStep.btcTxId);
      logger.data('Confirmations', existingStep.confirmations);
      logger.info('\nNext step: npm run 03-submit-proof');
      return;
    }

    if (existingStep?.btcTxId) {
      logger.warning('Found existing BTC transaction in state — resuming (NOT sending new BTC)');
      logger.data('Transaction ID', existingStep.btcTxId);

      const confirmations = await waitForConfirmations(existingStep.btcTxId, 3);

      updateStep('02-lock-btc-collateral', {
        ...existingStep,
        confirmations: confirmations
      });

      logger.success('BTC locked successfully!');
      logger.info('\nNext step: npm run 03-submit-proof');
      return;
    }

    logger.info('Locking BTC for order:');
    logger.data('Order ID', orderId);

    // Get order details to find script address and collateral amount
    const orderData = await getOrderData(config.contracts.loanContract, orderId);

    if (orderData.status !== 1) {
      throw new Error(`Order status is ${orderData.status}, expected 1 (TAKEN)`);
    }

    const scriptAddress = orderData.lenderBtcAddress;

    // Default to standard (no staking) collateral amount
    const COLLATERAL_NO_DISCOUNT_FACTOR = 1.625;
    const discountedAmountSats = orderData.collateralAmount.toNumber();
    const standardAmountSats = Math.ceil(discountedAmountSats * COLLATERAL_NO_DISCOUNT_FACTOR);

    const collateralAmountSats = standardAmountSats;
    const collateralAmountBTC = (collateralAmountSats / 100000000).toFixed(8);

    logger.info('Collateral amount (standard, no staking discount):');
    logger.data('Amount', `${collateralAmountBTC} BTC (${collateralAmountSats} sats)`);
    logger.data('Script Address', scriptAddress);

    // Create and sign BTC transaction
    logger.info('Creating BTC transaction...');
    const { hex: txHex, txId } = await createAndSignTransaction(scriptAddress, collateralAmountSats);

    logger.data('Transaction ID', txId);
    logger.data('Transaction Size', `${txHex.length / 2} bytes`);

    // Broadcast transaction
    logger.info('Broadcasting transaction...');
    const broadcastedTxId = await broadcastTransaction(txHex);

    if (broadcastedTxId !== txId) {
      logger.warning('Broadcasted TX ID differs from computed TX ID');
      logger.data('Computed', txId);
      logger.data('Broadcasted', broadcastedTxId);
    }

    // Save transaction ID immediately (before waiting for confirmations)
    updateStep('02-lock-btc-collateral', {
      orderId: orderId,
      scriptAddress: scriptAddress,
      btcTxId: broadcastedTxId,
      amountBTC: collateralAmountBTC,
      amountSats: collateralAmountSats,
      confirmations: 0,
      broadcasted: true
    });

    logger.success('Transaction broadcast saved to state!');
    logger.info('Safe to resume if interrupted during confirmation waiting');

    // Wait for confirmations
    logger.info('Waiting for 3 confirmations (this may take ~30 minutes)...');
    const confirmations = await waitForConfirmations(broadcastedTxId, 3);

    logger.success('BTC locked successfully!');

    // Update state with confirmations
    updateStep('02-lock-btc-collateral', {
      orderId: orderId,
      scriptAddress: scriptAddress,
      btcTxId: broadcastedTxId,
      amountBTC: collateralAmountBTC,
      amountSats: collateralAmountSats,
      confirmations: confirmations
    });

    logger.info('\nNext step: npm run 03-submit-proof');
  } catch (error) {
    logger.error('Failed to lock BTC:', error.message);
    process.exit(1);
  }
}

main();


path: scripts/03-submit-btc-proof.js
content: #!/usr/bin/env node

import { logger } from './utils/logger.js';
import { config, validateConfig } from './utils/config.js';
import { initializeWallets } from './utils/wallet-manager.js';
import { getOrderContractInstance, waitForTransaction } from './utils/evm-client.js';
import { requireStep, updateStep } from './utils/state-manager.js';
import { generateProofParams } from './utils/proof-generator.js';
import prompts from 'prompts';

async function main() {
  logger.section('Step 03: Submit BTC Lock Proof');

  try {
    validateConfig();
    initializeWallets();

    // Get data from previous step
    const lockBtcStep = requireStep('02-lock-btc-collateral');
    const orderId = lockBtcStep.orderId;
    const btcTxId = lockBtcStep.btcTxId;
    const scriptAddress = lockBtcStep.scriptAddress;

    logger.info('Submitting proof for:');
    logger.data('Order ID', orderId);
    logger.data('BTC Transaction', btcTxId);

    // Generate proof parameters
    logger.info('Generating ZKP proof parameters...');
    const proofParams = await generateProofParams(btcTxId, scriptAddress);

    logger.info('Proof parameters:');
    logger.data('Block Height', proofParams.blockHeight);
    logger.data('Merkle Root', proofParams.merkleRoot);
    logger.data('TX Index', proofParams.txIndex);
    logger.data('Script Output Index', proofParams.scriptAddressOutputIndex);

    // Confirm before proceeding
    const { confirm } = await prompts({
      type: 'confirm',
      name: 'confirm',
      message: 'Submit proof to contract?',
      initial: true
    });

    if (!confirm) {
      logger.warning('Operation cancelled by user');
      return;
    }

    // Get the order contract instance
    const orderContract = getOrderContractInstance(orderId);

    // Submit proof
    logger.info('Submitting proof to blockchain...');
    const tx = await orderContract.submitToLenderTransferProof(
      proofParams.txRawData,
      proofParams.blockHeight,
      {
        proof: proofParams.proof,
        root: proofParams.merkleRoot,
        leaf: proofParams.leaf,
        flags: proofParams.positions
      },
      proofParams.txIndex
    );

    logger.info('Transaction sent, waiting for confirmation...');
    const receipt = await waitForTransaction(tx);

    logger.success('Proof submitted successfully!');
    logger.data('Transaction Hash', receipt.transactionHash);

    // Save to state
    updateStep('03-submit-btc-proof', {
      orderId: orderId,
      btcTxId: btcTxId,
      blockHeight: proofParams.blockHeight,
      merkleRoot: proofParams.merkleRoot,
      txIndex: proofParams.txIndex,
      txHash: receipt.transactionHash,
      blockNumber: receipt.blockNumber
    });

    logger.info('\nNext step: npm run 04-arbiter-fee (if required) or npm run 05-claim-btcd');
  } catch (error) {
    logger.error('Failed to submit proof:', error.message);
    if (error.error) {
      logger.error('Contract error:', error.error.message);
    }
    process.exit(1);
  }
}

main();


path: scripts/05-claim-btcd-tokens.js
content: #!/usr/bin/env node

import { logger } from './utils/logger.js';
import { config, validateConfig } from './utils/config.js';
import { initializeWallets, getEVMWallet } from './utils/wallet-manager.js';
import { getOrderContractInstance, getERC20Instance, waitForTransaction, formatUnits } from './utils/evm-client.js';
import { requireStep, updateStep } from './utils/state-manager.js';
import prompts from 'prompts';

async function main() {
  logger.section('Step 05: Claim BTCD Tokens');

  try {
    validateConfig();
    initializeWallets();

    // Get data from previous steps
    const takeOrderStep = requireStep('01-take-order');
    const orderId = takeOrderStep.orderId;
    const preImage = takeOrderStep.preImage;

    if (!preImage) {
      throw new Error('PreImage not found in state. Cannot claim tokens.');
    }

    logger.info('Claiming BTCD tokens for order:');
    logger.data('Order ID', orderId);

    // Get wallet
    const wallet = getEVMWallet();

    // Check current BTCD balance
    const btcdToken = getERC20Instance(config.contracts.btcdToken);
    const balanceBefore = await btcdToken.balanceOf(wallet.address);

    logger.info('Current BTCD balance:');
    logger.data('Balance', formatUnits(balanceBefore, 18));

    // Confirm before proceeding
    const { confirm } = await prompts({
      type: 'confirm',
      name: 'confirm',
      message: 'Claim BTCD tokens (call borrow)?',
      initial: true
    });

    if (!confirm) {
      logger.warning('Operation cancelled by user');
      return;
    }

    // Get order contract instance for the specific order
    const orderContract = getOrderContractInstance(orderId);

    // Call borrow with preImage
    logger.info('Calling borrow()...');
    logger.data('PreImage', preImage);

    const tx = await orderContract.borrow('0x' + preImage);

    logger.info('Transaction sent, waiting for confirmation...');
    const receipt = await waitForTransaction(tx);

    // Check new balance
    const balanceAfter = await btcdToken.balanceOf(wallet.address);
    const received = balanceAfter.sub(balanceBefore);

    logger.success('BTCD tokens claimed successfully!');
    logger.data('Transaction Hash', receipt.transactionHash);
    logger.data('Tokens Received', formatUnits(received, 18));
    logger.data('New Balance', formatUnits(balanceAfter, 18));

    // Save to state
    updateStep('05-claim-btcd-tokens', {
      orderId: orderId,
      txHash: receipt.transactionHash,
      blockNumber: receipt.blockNumber,
      btcdReceived: formatUnits(received, 18),
      btcdBalance: formatUnits(balanceAfter, 18)
    });

    logger.info('\nBTC collateral is now locked, BTCD tokens are in your wallet');
    logger.info('Next step: npm run 06-repay (when ready to repay and unlock BTC)');
  } catch (error) {
    logger.error('Failed to claim BTCD:', error.message);
    if (error.error) {
      logger.error('Contract error:', error.error.message);
    }
    process.exit(1);
  }
}

main();


path: scripts/06-repay-loan.js
content: #!/usr/bin/env node

import { logger } from './utils/logger.js';
import { config, validateConfig } from './utils/config.js';
import { initializeWallets, getEVMWallet, getBTCKeyPair } from './utils/wallet-manager.js';
import {
  getOrderContractInstance,
  getERC20Instance,
  waitForTransaction,
  formatUnits,
  parseUnits,
  getOrderData
} from './utils/evm-client.js';
import { requireStep, updateStep, loadState, archiveState } from './utils/state-manager.js';
import { getTransactionDetails } from './utils/btc-client.js';
import * as bitcoin from 'bitcoinjs-lib';
import bip66 from 'bip66';
import prompts from 'prompts';
import BigNumber from 'bignumber.js';

// Convert R|S signature to DER format
function rsSignatureToDer(rs) {
  const rsBuffer = Buffer.from(rs, 'hex');
  if (rsBuffer.length !== 64) {
    throw new Error(`Invalid rs string signature length. Buffer length is ${rsBuffer.length}, expected 64`);
  }

  const toDER = x => {
    let i = x[0] & 0x80 ? 1 : 0;
    if (i) x = Buffer.concat([Buffer.alloc(1), x], x.length + 1);
    else if (x.length > 1 && x[0] === 0x00 && !(x[1] & 0x80)) x = x.slice(1);
    return x;
  };

  const r = toDER(rsBuffer.subarray(0, 32));
  const s = toDER(rsBuffer.subarray(32));

  const derSignature = bip66.encode(r, s);
  return derSignature.toString('hex');
}

async function main() {
  logger.section('Step 06: Repay Loan');

  try {
    validateConfig();
    initializeWallets();

    // Get data from previous steps
    const takeOrderStep = requireStep('01-take-order');
    const lockBtcStep = requireStep('02-lock-btc-collateral');
    const claimStep = requireStep('05-claim-btcd-tokens');

    const orderId = takeOrderStep.orderId;
    const btcTxId = lockBtcStep.btcTxId;

    logger.info('Repaying loan for order:');
    logger.data('Order ID', orderId);

    const wallet = getEVMWallet();
    const btcKeyPair = getBTCKeyPair();

    // Get order data to calculate repayment amount
    const orderData = await getOrderData(config.contracts.loanContract, orderId);
    const lendingAmount = new BigNumber(orderData.lendingAmount.toString());
    const interestRate = new BigNumber(orderData.interestRate.toString());

    // Calculate repayment amount (principal + interest)
    // interestRate is in 1e18 format (e.g., 0.015 = 15000000000000000)
    // Formula: interest = principal * interestRate / 1e18
    const interestAmount = lendingAmount.multipliedBy(interestRate).dividedBy('1000000000000000000');
    const repayAmount = lendingAmount.plus(interestAmount);

    // Calculate percentage for display
    const interestRatePercent = interestRate.dividedBy('1000000000000000000').multipliedBy(100);

    logger.info('Repayment calculation:');
    logger.data('Principal', formatUnits(orderData.lendingAmount, 18) + ' BTCD');
    logger.data('Interest Rate', `${interestRatePercent.toFixed(4)}%`);
    logger.data('Interest', formatUnits(interestAmount.toFixed(0), 18) + ' BTCD');
    logger.data('Total Repayment', formatUnits(repayAmount.toFixed(0), 18) + ' BTCD');

    // Check BTCD balance
    const btcdToken = getERC20Instance(config.contracts.btcdToken);
    const balance = await btcdToken.balanceOf(wallet.address);

    logger.data('Your BTCD Balance', formatUnits(balance, 18));

    if (balance.lt(repayAmount.toFixed(0))) {
      throw new Error('Insufficient BTCD balance for repayment');
    }

    // Check and approve BTCD spending
    const orderContract = getOrderContractInstance(orderId);
    const allowance = await btcdToken.allowance(wallet.address, orderId);

    if (allowance.lt(repayAmount.toFixed(0))) {
      logger.info('Approving BTCD spending...');

      const approveTx = await btcdToken.approve(orderId, repayAmount.toFixed(0));
      await waitForTransaction(approveTx);

      logger.success('BTCD spending approved');
    } else {
      logger.info('BTCD spending already approved');
    }

    // Get the unsigned BTC repayment transaction from contract
    logger.info('Getting repay BTC transaction from contract...');
    const repayBtcNoWitnessTx = await orderContract.getRepayBtcNoWitnessTx();
    const rawBtcTx = repayBtcNoWitnessTx.slice(2); // Remove 0x prefix

    logger.data('Raw BTC TX (hex)', rawBtcTx.substring(0, 100) + '...');

    // Parse and sign the transaction
    const unsignedBtcTx = bitcoin.Transaction.fromHex(rawBtcTx);

    // Get the locked BTC transaction details for value
    const lockedTxDetails = await getTransactionDetails(btcTxId);

    // Find script output index
    let scriptOutputIndex = 0;
    if (lockBtcStep.scriptAddress) {
      scriptOutputIndex = lockedTxDetails.vout.findIndex(v => {
        // Handle different API formats
        if (v.scriptPubKey?.addresses?.includes(lockBtcStep.scriptAddress)) return true;
        if (v.scriptPubKey?.address === lockBtcStep.scriptAddress) return true;
        if (v.scriptpubkey_address === lockBtcStep.scriptAddress) return true;
        return false;
      });
      if (scriptOutputIndex === -1) scriptOutputIndex = 0; // Fallback to first output
    }

    // Get value - handle different API formats
    const vout = lockedTxDetails.vout[scriptOutputIndex];
    let orderSatsValue;

    if (vout.value !== undefined) {
      // Check if value is in BTC (decimal) or satoshis (integer)
      if (vout.value < 1) {
        // Likely BTC (decimal) - convert to sats
        orderSatsValue = Math.floor(vout.value * 100000000);
      } else {
        // Already in satoshis
        orderSatsValue = vout.value;
      }
    } else {
      logger.error('Could not find value in output:', JSON.stringify(vout));
      throw new Error('Could not extract BTC value from locked transaction');
    }

    logger.data('Order BTC Value (sats)', orderSatsValue);

    // Get loan script from order data
    const loanScript = await orderContract.loanScript();
    const scriptBuffer = Buffer.from(loanScript.slice(2), 'hex');

    // Sign with borrower's key
    logger.info('Signing repayment transaction with borrower key...');
    const hashForWitness = unsignedBtcTx.hashForWitnessV0(
      0,
      scriptBuffer,
      orderSatsValue,
      bitcoin.Transaction.SIGHASH_ALL
    );

    const signature = btcKeyPair.sign(hashForWitness);
    const borrowerSignature = rsSignatureToDer(signature.toString('hex'));

    logger.data('Borrower Signature (DER)', borrowerSignature.substring(0, 50) + '...');

    // Confirm before proceeding
    const { confirm } = await prompts({
      type: 'confirm',
      name: 'confirm',
      message: 'Repay loan (this will lock your BTCD until lender unlocks BTC)?',
      initial: true
    });

    if (!confirm) {
      logger.warning('Operation cancelled by user');
      return;
    }

    // Call repay on contract
    logger.info('Calling repay() on contract...');
    const repayTx = await orderContract.repay('0x' + rawBtcTx, '0x' + borrowerSignature);

    logger.info('Transaction sent, waiting for confirmation...');
    const receipt = await waitForTransaction(repayTx);

    logger.success('Loan repaid successfully!');
    logger.data('Transaction Hash', receipt.transactionHash);

    // Save to state
    updateStep('06-repay-loan', {
      orderId: orderId,
      repayAmount: formatUnits(repayAmount.toFixed(0), 18),
      borrowerSignature: borrowerSignature,
      txHash: receipt.transactionHash,
      blockNumber: receipt.blockNumber
    });

    // Archive completed flow state and reset for next run
    const archivePath = archiveState();
    logger.success(`Flow state archived to: ${archivePath}`);
    logger.info('State has been reset — next run will start a fresh flow.');

    logger.info('\nLoan repaid! Waiting for lender to unlock BTC...');
  } catch (error) {
    logger.error('Failed to repay loan:', error.message);
    if (error.error) {
      logger.error('Contract error:', error.error.message);
    }
    process.exit(1);
  }
}

main();


path: scripts/abi/ArbitratorManager.json
content: {
  "contractName": "ArbitratorManager",
  "abi": [
    {
      "inputs": [],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "inputs": [],
      "name": "InvalidInitialization",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "NotInitializing",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        }
      ],
      "name": "OwnableInvalidOwner",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "OwnableUnauthorizedAccount",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "ReentrancyGuardReentrantCall",
      "type": "error"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        }
      ],
      "name": "ArbitratorDeadlineUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "ethFeeRate",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "btcFeeRate",
          "type": "uint256"
        }
      ],
      "name": "ArbitratorFeeRateUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "ArbitratorFrozen",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "ArbitratorPaused",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "operator",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "revenueAddress",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "string",
          "name": "btcAddress",
          "type": "string"
        },
        {
          "indexed": false,
          "internalType": "bytes",
          "name": "btcPubKey",
          "type": "bytes"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "feeRate",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "btcFeeRate",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        }
      ],
      "name": "ArbitratorRegistered",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "transactionId",
          "type": "bytes32"
        }
      ],
      "name": "ArbitratorReleased",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "ArbitratorTerminatedWithSlash",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "ArbitratorUnpaused",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "newArbitratorWhitelist",
          "type": "address"
        }
      ],
      "name": "ArbitratorWhitelistUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "transactionId",
          "type": "bytes32"
        }
      ],
      "name": "ArbitratorWorking",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "assetManager",
          "type": "address"
        }
      ],
      "name": "AssetManagerUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "oldManager",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newManager",
          "type": "address"
        }
      ],
      "name": "CompensationManagerUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "newConfigManager",
          "type": "address"
        }
      ],
      "name": "ConfigManagerUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "version",
          "type": "uint64"
        }
      ],
      "name": "Initialized",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "operator",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "bytes",
          "name": "btcPubKey",
          "type": "bytes"
        },
        {
          "indexed": false,
          "internalType": "string",
          "name": "btcAddress",
          "type": "string"
        }
      ],
      "name": "OperatorSet",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "previousOwner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newOwner",
          "type": "address"
        }
      ],
      "name": "OwnershipTransferred",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "ethAddress",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "bytes",
          "name": "btcPubKey",
          "type": "bytes"
        },
        {
          "indexed": false,
          "internalType": "string",
          "name": "btcAddress",
          "type": "string"
        }
      ],
      "name": "RevenueAddressesSet",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "assetAddress",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256[]",
          "name": "nftTokenIds",
          "type": "uint256[]"
        }
      ],
      "name": "StakeAdded",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "ethAmount",
          "type": "uint256"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "erc20Address",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "erc20Amount",
          "type": "uint256"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "nftAddress",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256[]",
          "name": "nftTokenIds",
          "type": "uint256[]"
        }
      ],
      "name": "StakeWithdrawn",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "oldManager",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newManager",
          "type": "address"
        }
      ],
      "name": "TransactionManagerUpdated",
      "type": "event"
    },
    {
      "inputs": [],
      "name": "arbitratorWhitelist",
      "outputs": [
        {
          "internalType": "contract IArbitratorWhitelist",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "assetManager",
      "outputs": [
        {
          "internalType": "contract AssetManager",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "compensationManager",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "configManager",
      "outputs": [
        {
          "internalType": "contract ConfigManager",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "frozenArbitrator",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "getArbitratorAssets",
      "outputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "ethAmount",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "erc20Token",
              "type": "address"
            },
            {
              "internalType": "uint256",
              "name": "erc20Amount",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "nftContract",
              "type": "address"
            },
            {
              "internalType": "uint256[]",
              "name": "nftTokenIds",
              "type": "uint256[]"
            }
          ],
          "internalType": "struct DataTypes.ArbitratorAssets",
          "name": "",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "getArbitratorBasicInfo",
      "outputs": [
        {
          "components": [
            {
              "internalType": "address",
              "name": "arbitrator",
              "type": "address"
            },
            {
              "internalType": "bool",
              "name": "paused",
              "type": "bool"
            },
            {
              "internalType": "uint256",
              "name": "registerTime",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "deadline",
              "type": "uint256"
            }
          ],
          "internalType": "struct DataTypes.ArbitratorBasicInfo",
          "name": "",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "getArbitratorOperationInfo",
      "outputs": [
        {
          "components": [
            {
              "internalType": "address",
              "name": "operator",
              "type": "address"
            },
            {
              "internalType": "bytes",
              "name": "operatorBtcPubKey",
              "type": "bytes"
            },
            {
              "internalType": "string",
              "name": "operatorBtcAddress",
              "type": "string"
            },
            {
              "internalType": "bytes32",
              "name": "activeTransactionId",
              "type": "bytes32"
            },
            {
              "internalType": "uint256",
              "name": "lastSubmittedWorkTime",
              "type": "uint256"
            }
          ],
          "internalType": "struct DataTypes.ArbitratorOperationInfo",
          "name": "",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "getArbitratorRevenueInfo",
      "outputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "currentFeeRate",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "currentBTCFeeRate",
              "type": "uint256"
            },
            {
              "internalType": "bytes",
              "name": "revenueBtcPubKey",
              "type": "bytes"
            },
            {
              "internalType": "string",
              "name": "revenueBtcAddress",
              "type": "string"
            },
            {
              "internalType": "address",
              "name": "revenueETHAddress",
              "type": "address"
            }
          ],
          "internalType": "struct DataTypes.ArbitratorRevenueInfo",
          "name": "",
          "type": "tuple"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "getAvailableStake",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "duration",
          "type": "uint256"
        },
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "getBtcFee",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "fee",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "duration",
          "type": "uint256"
        },
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "getFee",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "fee",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_configManager",
          "type": "address"
        }
      ],
      "name": "initialize",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitratorAddress",
          "type": "address"
        }
      ],
      "name": "isActiveArbitrator",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "isConfigModifiable",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "isFrozenStatus",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "operator",
          "type": "address"
        }
      ],
      "name": "isOperatorOf",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "isPaused",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "owner",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "pause",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "token",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        },
        {
          "internalType": "string",
          "name": "defaultBtcAddress",
          "type": "string"
        },
        {
          "internalType": "bytes",
          "name": "defaultBtcPubKey",
          "type": "bytes"
        },
        {
          "internalType": "uint256",
          "name": "feeRate",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "btcFeeRate",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        }
      ],
      "name": "registerArbitratorByStakeERC20",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "defaultBtcAddress",
          "type": "string"
        },
        {
          "internalType": "bytes",
          "name": "defaultBtcPubKey",
          "type": "bytes"
        },
        {
          "internalType": "uint256",
          "name": "feeRate",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "btcFeeRate",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        }
      ],
      "name": "registerArbitratorByStakeETH",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256[]",
          "name": "tokenIds",
          "type": "uint256[]"
        },
        {
          "internalType": "string",
          "name": "defaultBtcAddress",
          "type": "string"
        },
        {
          "internalType": "bytes",
          "name": "defaultBtcPubKey",
          "type": "bytes"
        },
        {
          "internalType": "uint256",
          "name": "feeRate",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "btcFeeRate",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        }
      ],
      "name": "registerArbitratorByStakeNFT",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "internalType": "bytes32",
          "name": "transactionId",
          "type": "bytes32"
        }
      ],
      "name": "releaseArbitrator",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "renounceOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        }
      ],
      "name": "setArbitratorDeadline",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_arbitratorWhitelist",
          "type": "address"
        }
      ],
      "name": "setArbitratorWhitelist",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        },
        {
          "internalType": "bytes32",
          "name": "transactionId",
          "type": "bytes32"
        }
      ],
      "name": "setArbitratorWorking",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_assetManager",
          "type": "address"
        }
      ],
      "name": "setAssetManager",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_compensationManager",
          "type": "address"
        }
      ],
      "name": "setCompensationManager",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_configManager",
          "type": "address"
        }
      ],
      "name": "setConfigManager",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "ethFeeRate",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "btcFeeRate",
          "type": "uint256"
        }
      ],
      "name": "setFeeRates",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "operator",
          "type": "address"
        },
        {
          "internalType": "bytes",
          "name": "btcPubKey",
          "type": "bytes"
        },
        {
          "internalType": "string",
          "name": "btcAddress",
          "type": "string"
        }
      ],
      "name": "setOperator",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "ethAddress",
          "type": "address"
        },
        {
          "internalType": "bytes",
          "name": "btcPubKey",
          "type": "bytes"
        },
        {
          "internalType": "string",
          "name": "btcAddress",
          "type": "string"
        }
      ],
      "name": "setRevenueAddresses",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_transactionManager",
          "type": "address"
        }
      ],
      "name": "setTransactionManager",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "token",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "stakeERC20",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "stakeETH",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256[]",
          "name": "tokenIds",
          "type": "uint256[]"
        }
      ],
      "name": "stakeNFT",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "arbitrator",
          "type": "address"
        }
      ],
      "name": "terminateArbitratorWithSlash",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "transactionManager",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "newOwner",
          "type": "address"
        }
      ],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "unpause",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "unstake",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]
}

path: scripts/abi/ERC20.json
content: {
  "contractName": "IERC20",
  "abi": [
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Approval",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "Transfer",
      "type": "event"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        }
      ],
      "name": "allowance",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "spender",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "approve",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "balanceOf",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "totalSupply",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "transfer",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "from",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "transferFrom",
      "outputs": [
        {
          "internalType": "bool",
          "name": "",
          "type": "bool"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]
}

path: scripts/abi/Issuer.json
content: {
  "contractName": "Issuer",
  "abi": [
    {
      "inputs": [],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "target",
          "type": "address"
        }
      ],
      "name": "AddressEmptyCode",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "AddressInsufficientBalance",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "FailedInnerCall",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidInitialization",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "NotInitializing",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        }
      ],
      "name": "OwnableInvalidOwner",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "OwnableUnauthorizedAccount",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "ReentrancyGuardReentrantCall",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "token",
          "type": "address"
        }
      ],
      "name": "SafeERC20FailedOperation",
      "type": "error"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "bytes",
          "name": "newPublicKey",
          "type": "bytes"
        },
        {
          "indexed": false,
          "internalType": "string",
          "name": "newBtcAddress",
          "type": "string"
        }
      ],
      "name": "BtcInfoUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "ETHWithdrawn",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "version",
          "type": "uint64"
        }
      ],
      "name": "Initialized",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "InterestWithdrawn",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "oldLoanContract",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newLoanContract",
          "type": "address"
        }
      ],
      "name": "LoanContractUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "oldOperator",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newOperator",
          "type": "address"
        }
      ],
      "name": "OperatorUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "previousOwner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newOwner",
          "type": "address"
        }
      ],
      "name": "OwnershipTransferred",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "oldStablecoin",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newStablecoin",
          "type": "address"
        }
      ],
      "name": "StableCoinUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "token",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "account",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "name": "TokenWithdrawn",
      "type": "event"
    },
    {
      "inputs": [],
      "name": "btcAddress",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "btcPublicKey",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "contract IOrder",
          "name": "order",
          "type": "address"
        }
      ],
      "name": "closeOrder",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "durationDays",
          "type": "uint256"
        }
      ],
      "name": "createOrder",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getTotalInterest",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "contract IStableCoin",
          "name": "_token",
          "type": "address"
        },
        {
          "internalType": "bytes",
          "name": "_btcPublicKey",
          "type": "bytes"
        },
        {
          "internalType": "string",
          "name": "_btcAddress",
          "type": "string"
        },
        {
          "internalType": "address",
          "name": "_operator",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_loanContract",
          "type": "address"
        }
      ],
      "name": "initialize",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "loanContract",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "operator",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "owner",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "renounceOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "_btcPublicKey",
          "type": "bytes"
        },
        {
          "internalType": "string",
          "name": "_btcAddress",
          "type": "string"
        }
      ],
      "name": "setBtcPublicKeyAndAddress",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_loanContract",
          "type": "address"
        }
      ],
      "name": "setLoanContract",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_operator",
          "type": "address"
        }
      ],
      "name": "setOperator",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_token",
          "type": "address"
        }
      ],
      "name": "setStableCoin",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "contract IOrder",
          "name": "order",
          "type": "address"
        },
        {
          "internalType": "bytes",
          "name": "txData",
          "type": "bytes"
        },
        {
          "internalType": "uint32",
          "name": "blockHeight",
          "type": "uint32"
        },
        {
          "components": [
            {
              "internalType": "bytes32[]",
              "name": "proof",
              "type": "bytes32[]"
            },
            {
              "internalType": "bytes32",
              "name": "root",
              "type": "bytes32"
            },
            {
              "internalType": "bytes32",
              "name": "leaf",
              "type": "bytes32"
            },
            {
              "internalType": "bool[]",
              "name": "flags",
              "type": "bool[]"
            }
          ],
          "internalType": "struct MerkleProofData",
          "name": "merkleProof",
          "type": "tuple"
        }
      ],
      "name": "submitRegularUnlockTransferProof",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "token",
      "outputs": [
        {
          "internalType": "contract IStableCoin",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "newOwner",
          "type": "address"
        }
      ],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address payable",
          "name": "recipient",
          "type": "address"
        }
      ],
      "name": "withdrawETH",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "withdrawInterest",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_token",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "recipient",
          "type": "address"
        }
      ],
      "name": "withdrawToken",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "stateMutability": "payable",
      "type": "receive"
    }
  ]
}

path: scripts/abi/LoanContract.json
content: {
  "contractName": "LoanContract",
  "abi": [
    {
      "inputs": [],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "target",
          "type": "address"
        }
      ],
      "name": "AddressEmptyCode",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "AddressInsufficientBalance",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "FailedInnerCall",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidInitialization",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "NotInitializing",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        }
      ],
      "name": "OwnableInvalidOwner",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "OwnableUnauthorizedAccount",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "token",
          "type": "address"
        }
      ],
      "name": "SafeERC20FailedOperation",
      "type": "error"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "value",
          "type": "uint256"
        }
      ],
      "name": "DelayBlockChanged",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "newGetLoanTools",
          "type": "address"
        }
      ],
      "name": "GetLoanToolsUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "version",
          "type": "uint64"
        }
      ],
      "name": "Initialized",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "newInteretAddress",
          "type": "address"
        }
      ],
      "name": "InterestAddressUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "tools",
          "type": "address"
        }
      ],
      "name": "LoanToolsChanged",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "lockTime1",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "lockTime2",
          "type": "uint256"
        }
      ],
      "name": "LockTimeChanged",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "newOptionWallet",
          "type": "address"
        }
      ],
      "name": "OptionWalletUpdated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "orderId",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "enum OrderType",
          "name": "orderType",
          "type": "uint8"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "collateral",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "token",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "tokenAmount",
          "type": "uint256"
        }
      ],
      "name": "OrderCreated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "factory",
          "type": "address"
        }
      ],
      "name": "OrderFactoryChanged",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "previousOwner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newOwner",
          "type": "address"
        }
      ],
      "name": "OwnershipTransferred",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "time",
          "type": "uint256"
        }
      ],
      "name": "RepaidExpireTimeChanged",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "time",
          "type": "uint256"
        }
      ],
      "name": "SubmitProofExpireTimeChanged",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "time",
          "type": "uint256"
        }
      ],
      "name": "TakenExpireTimeChanged",
      "type": "event"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "tools",
          "type": "address"
        }
      ],
      "name": "SetLoanToolsAddress",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "num",
          "type": "uint256"
        }
      ],
      "name": "changeProofedDelayBlock",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "time",
          "type": "uint256"
        }
      ],
      "name": "changeRepaidExpireTime",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "time",
          "type": "uint256"
        }
      ],
      "name": "changeSubmitProofExpireTime",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "time",
          "type": "uint256"
        }
      ],
      "name": "changeTakenExpireTime",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_token",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_amount",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "_durationDays",
          "type": "uint256"
        },
        {
          "internalType": "string",
          "name": "_btcAddress",
          "type": "string"
        },
        {
          "internalType": "bytes",
          "name": "_publicKey",
          "type": "bytes"
        }
      ],
      "name": "createLendingOrder",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "tokenAddress",
          "type": "address"
        }
      ],
      "name": "getAssetPrice",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getLockTime1",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getLockTime2",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_loanTools",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_orderFactory",
          "type": "address"
        }
      ],
      "name": "initialize",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_loanTools",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_orderFactory",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_getLoanTools",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_interestReceiver",
          "type": "address"
        },
        {
          "internalType": "address",
          "name": "_optionInterestReceiver",
          "type": "address"
        }
      ],
      "name": "initializeData",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "owner",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "renounceOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_getLoanTools",
          "type": "address"
        }
      ],
      "name": "setGetLoanTools",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_interestReceivers",
          "type": "address"
        }
      ],
      "name": "setInterestReceivers",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "lockTime1Seconds",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "lockTime2Seconds",
          "type": "uint256"
        }
      ],
      "name": "setLockTime",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_optionInterestReceiver",
          "type": "address"
        }
      ],
      "name": "setOptionInterestReceiver",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "factory",
          "type": "address"
        }
      ],
      "name": "setOrderFactory",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "newOwner",
          "type": "address"
        }
      ],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]
}

path: scripts/abi/Order.json
content: {
  "contractName": "Order",
  "abi": [
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "target",
          "type": "address"
        }
      ],
      "name": "AddressEmptyCode",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "AddressInsufficientBalance",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "FailedInnerCall",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "InvalidInitialization",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "NotInitializing",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "owner",
          "type": "address"
        }
      ],
      "name": "OwnableInvalidOwner",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "account",
          "type": "address"
        }
      ],
      "name": "OwnableUnauthorizedAccount",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "ReentrancyGuardReentrantCall",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "token",
          "type": "address"
        }
      ],
      "name": "SafeERC20FailedOperation",
      "type": "error"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "txId",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "compensationReceiver",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "timeOutCompensationReceiver",
          "type": "address"
        }
      ],
      "name": "ArbitrationRegistered",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "bytes32",
          "name": "txId",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "bytes",
          "name": "signature",
          "type": "bytes"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "compensationReceiver",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "timeOutCompensationReceiver",
          "type": "address"
        }
      ],
      "name": "ArbitrationRequested",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [],
      "name": "ArbitratorBTCTransferManuallyConfirmed",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [],
      "name": "BorrowSuccessful",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [],
      "name": "BorrowerBTCTransferArbitratorFeeSubmitted",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "acutalAmount",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "enum OrderStatus",
          "name": "status",
          "type": "uint8"
        }
      ],
      "name": "BorrowerBTCTransferProofSubmitted",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint64",
          "name": "version",
          "type": "uint64"
        }
      ],
      "name": "Initialized",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [],
      "name": "LenderBTCTransferManuallyConfirmed",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "bytes32",
          "name": "zkTxHash",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "enum OrderStatus",
          "name": "status",
          "type": "uint8"
        }
      ],
      "name": "LenderBTCTransferProofSubmitted",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [],
      "name": "OrderClosed",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "enum OrderType",
          "name": "orderType",
          "type": "uint8"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "collateral",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "address",
          "name": "token",
          "type": "address"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "tokenAmount",
          "type": "uint256"
        }
      ],
      "name": "OrderCreated",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "string",
          "name": "takerBtcAddress",
          "type": "string"
        },
        {
          "indexed": false,
          "internalType": "string",
          "name": "arbitratorFeeBtcAddress",
          "type": "string"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "ethFee",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "btcFee",
          "type": "uint256"
        }
      ],
      "name": "OrderTaken",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": true,
          "internalType": "address",
          "name": "previousOwner",
          "type": "address"
        },
        {
          "indexed": true,
          "internalType": "address",
          "name": "newOwner",
          "type": "address"
        }
      ],
      "name": "OwnershipTransferred",
      "type": "event"
    },
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "interest",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "principal",
          "type": "uint256"
        },
        {
          "indexed": false,
          "internalType": "bytes",
          "name": "btcTxData",
          "type": "bytes"
        },
        {
          "indexed": false,
          "internalType": "bytes",
          "name": "signature",
          "type": "bytes"
        }
      ],
      "name": "RepaymentMade",
      "type": "event"
    },
    {
      "inputs": [],
      "name": "arbitratorBtcFeeAddress",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "preimage",
          "type": "bytes"
        }
      ],
      "name": "borrow",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "borrowExpirationTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "borrowedTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "borrower",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "borrowerBtcAddress",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "borrowerPublicKey",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "borrowerRepaidTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "rawTx",
          "type": "bytes"
        },
        {
          "internalType": "bytes",
          "name": "signature",
          "type": "bytes"
        }
      ],
      "name": "borrowerSignLockTimeAUnlockTx",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "closeOrder",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "collateralAmount",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "_token",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "_amount",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "_duration",
          "type": "uint256"
        },
        {
          "internalType": "string",
          "name": "_btcAddress",
          "type": "string"
        },
        {
          "internalType": "bytes",
          "name": "_publicKey",
          "type": "bytes"
        }
      ],
      "name": "createLendOrder",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "createTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "currentSelectedArbitrator",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "deadLinesData",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "orderDeadLine",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "submitPledgeProofDeadLine",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "borrowDeadLine",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "repayDeadLine",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "submitRegularProofDeadLine",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getArbiterUnlockTransferZkpStatus",
      "outputs": [
        {
          "internalType": "enum DataTypes.TransactionStatus",
          "name": "",
          "type": "uint8"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getArbitrationBtcNoWitnessTx",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getArbitrationBtcTx",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getLockTimeAUnlockTx",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getLockTimeBUnlockTx",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "getRepayBtcNoWitnessTx",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "lenderSignature",
          "type": "bytes"
        }
      ],
      "name": "getRepayBtcWitnessTx",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "selectedArbitrator",
          "type": "address"
        }
      ],
      "name": "getSelectedArbitratorFees",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "ethFee",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "btcFee",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "address",
              "name": "loanTools",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "getLoanTools",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "interestReceiver",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "optionInterestReceiver",
              "type": "address"
            }
          ],
          "internalType": "struct SetAddress",
          "name": "_setAddress",
          "type": "tuple"
        },
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "takenExpireTime",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "submitProofExpireTime",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "repaidExpireTime",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "borrowExpirationTime",
              "type": "uint256"
            }
          ],
          "internalType": "struct ExpiredTime",
          "name": "_expiredTime",
          "type": "tuple"
        },
        {
          "internalType": "uint256",
          "name": "_proofedDelayBlock",
          "type": "uint256"
        },
        {
          "internalType": "address",
          "name": "_owner",
          "type": "address"
        },
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "lockTime1",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "lockTime2",
              "type": "uint256"
            }
          ],
          "internalType": "struct LockTime",
          "name": "_lockTime",
          "type": "tuple"
        },
        {
          "internalType": "address",
          "name": "_lender",
          "type": "address"
        }
      ],
      "name": "initialize",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "interestRate",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "interestReceiver",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "interestValue",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "lender",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "lenderBtcAddress",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "lenderPublicKey",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "lenderReceiveAddress",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "rawTx",
          "type": "bytes"
        },
        {
          "internalType": "bytes",
          "name": "signature",
          "type": "bytes"
        }
      ],
      "name": "lenderSignLockTimeBUnlockTx",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "limitedDays",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "loanScript",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "lockTime1",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "lockTime2",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "network",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "optionInterestReceiver",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "orderType",
      "outputs": [
        {
          "internalType": "enum OrderType",
          "name": "",
          "type": "uint8"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "owner",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "preImage",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "preImageHash",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "proofedDelayBlock",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "renounceOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "repaidExpireTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "btcRawData",
          "type": "bytes"
        },
        {
          "internalType": "bytes",
          "name": "signature",
          "type": "bytes"
        }
      ],
      "name": "repay",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "repayBtcRawData",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "repaySignature",
      "outputs": [
        {
          "internalType": "bytes",
          "name": "",
          "type": "bytes"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "unLockSignature",
          "type": "bytes"
        }
      ],
      "name": "requestArbitration",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "requestArbitrationTxId",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "status",
      "outputs": [
        {
          "internalType": "enum OrderStatus",
          "name": "",
          "type": "uint8"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "submitProofExpirationTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "txData",
          "type": "bytes"
        },
        {
          "internalType": "uint32",
          "name": "blockHeight",
          "type": "uint32"
        },
        {
          "components": [
            {
              "internalType": "bytes32[]",
              "name": "proof",
              "type": "bytes32[]"
            },
            {
              "internalType": "bytes32",
              "name": "root",
              "type": "bytes32"
            },
            {
              "internalType": "bytes32",
              "name": "leaf",
              "type": "bytes32"
            },
            {
              "internalType": "bool[]",
              "name": "flags",
              "type": "bool[]"
            }
          ],
          "internalType": "struct MerkleProofData",
          "name": "merkleProof",
          "type": "tuple"
        }
      ],
      "name": "submitRegularUnlockTransferProof",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "rawData",
          "type": "bytes"
        },
        {
          "internalType": "uint32",
          "name": "blockHeight",
          "type": "uint32"
        },
        {
          "components": [
            {
              "internalType": "bytes32[]",
              "name": "proof",
              "type": "bytes32[]"
            },
            {
              "internalType": "bytes32",
              "name": "root",
              "type": "bytes32"
            },
            {
              "internalType": "bytes32",
              "name": "leaf",
              "type": "bytes32"
            },
            {
              "internalType": "bool[]",
              "name": "flags",
              "type": "bool[]"
            }
          ],
          "internalType": "struct MerkleProofData",
          "name": "merkleProof",
          "type": "tuple"
        },
        {
          "internalType": "uint32",
          "name": "txIndex",
          "type": "uint32"
        }
      ],
      "name": "submitToArbitratorTransferProof",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "bytes",
          "name": "rawData",
          "type": "bytes"
        },
        {
          "internalType": "uint32",
          "name": "blockHeight",
          "type": "uint32"
        },
        {
          "components": [
            {
              "internalType": "bytes32[]",
              "name": "proof",
              "type": "bytes32[]"
            },
            {
              "internalType": "bytes32",
              "name": "root",
              "type": "bytes32"
            },
            {
              "internalType": "bytes32",
              "name": "leaf",
              "type": "bytes32"
            },
            {
              "internalType": "bool[]",
              "name": "flags",
              "type": "bool[]"
            }
          ],
          "internalType": "struct MerkleProofData",
          "name": "merkleProof",
          "type": "tuple"
        },
        {
          "internalType": "uint32",
          "name": "toLenderTxIndex",
          "type": "uint32"
        }
      ],
      "name": "submitToLenderTransferProof",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "btcAddress",
          "type": "string"
        },
        {
          "internalType": "bytes",
          "name": "publicKey",
          "type": "bytes"
        },
        {
          "internalType": "bytes32",
          "name": "preimageHash",
          "type": "bytes32"
        },
        {
          "internalType": "address",
          "name": "selectedArbitrator",
          "type": "address"
        },
        {
          "internalType": "string",
          "name": "net",
          "type": "string"
        }
      ],
      "name": "takeOrder",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "takenExpireTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "takenTime",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "toArbitratorBtcTx",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "wTxId",
          "type": "bytes32"
        },
        {
          "internalType": "bytes32",
          "name": "txId",
          "type": "bytes32"
        },
        {
          "internalType": "uint32",
          "name": "txIndex",
          "type": "uint32"
        },
        {
          "internalType": "uint256",
          "name": "proofTimestamp",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "blockHeight",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "toBorrowerBtcTx",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "wTxId",
          "type": "bytes32"
        },
        {
          "internalType": "bytes32",
          "name": "txId",
          "type": "bytes32"
        },
        {
          "internalType": "uint32",
          "name": "txIndex",
          "type": "uint32"
        },
        {
          "internalType": "uint256",
          "name": "proofTimestamp",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "blockHeight",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "toLenderBtcTx",
      "outputs": [
        {
          "internalType": "bytes32",
          "name": "wTxId",
          "type": "bytes32"
        },
        {
          "internalType": "bytes32",
          "name": "txId",
          "type": "bytes32"
        },
        {
          "internalType": "uint32",
          "name": "txIndex",
          "type": "uint32"
        },
        {
          "internalType": "uint256",
          "name": "proofTimestamp",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "blockHeight",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "amount",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "token",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "tokenAmount",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "newOwner",
          "type": "address"
        }
      ],
      "name": "transferOwnership",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ]
}

path: scripts/package.json
content: {
  "name": "btcd-skill-beta",
  "version": "1.0.0",
  "description": "BTCD collateralization flow for PGP chain",
  "type": "module",
  "scripts": {
    "setup": "node setup.js",
    "00-create-order": "node 00-create-order.js",
    "01-take-order": "node 01-take-order.js",
    "02-lock-btc": "node 02-lock-btc-collateral.js",
    "03-submit-proof": "node 03-submit-btc-proof.js",
    "05-claim-btcd": "node 05-claim-btcd-tokens.js",
    "06-repay": "node 06-repay-loan.js"
  },
  "dependencies": {
    "ethers": "^5.7.2",
    "bitcoinjs-lib": "^6.1.5",
    "@bitcoinerlab/secp256k1": "^1.1.1",
    "ecpair": "^2.1.0",
    "merkletreejs": "^0.3.11",
    "axios": "^1.3.4",
    "bignumber.js": "^9.1.2",
    "dotenv": "^16.0.3",
    "bip66": "^1.1.5",
    "chalk": "^4.1.2",
    "prompts": "^2.4.2"
  }
}


path: scripts/setup.js
content: #!/usr/bin/env node

import { logger } from './utils/logger.js';
import { config, validateConfig } from './utils/config.js';
import { initializeWallets, getEVMWallet } from './utils/wallet-manager.js';
import { saveState } from './utils/state-manager.js';

async function main() {
  logger.section('BTCD Collateralization Test - Setup');

  try {
    // Validate configuration
    logger.info('Validating configuration...');
    validateConfig();
    logger.success('Configuration validated');

    // Initialize wallets
    const { evmWallet, btcAddress } = initializeWallets();

    // Check balances
    logger.info('Checking balances...');
    const evmBalance = await evmWallet.getBalance();
    logger.data('EVM Balance', `${evmBalance.toString()} wei`);

    // Initialize state file
    const initialState = {
      createdAt: new Date().toISOString(),
      config: {
        evmAddress: evmWallet.address,
        btcAddress: btcAddress,
        network: config.btc.network,
        loanContractAddress: config.contracts.loanContract,
        issuerContractAddress: config.contracts.issuer,
        btcdTokenAddress: config.contracts.btcdToken
      },
      steps: {}
    };

    saveState(initialState);

    logger.success('Setup completed successfully!');
    logger.info('\nNext steps:');
    logger.info('1. Fund your wallets if needed');
    logger.info('2. Run: npm run 00-create-order (to create a new order)');
    logger.info('   OR set ORDER_ID in .env and run: npm run 01-take-order (to take existing order)');
  } catch (error) {
    logger.error('Setup failed:', error.message);
    process.exit(1);
  }
}

main();


path: scripts/utils/arbiter-client.js
content: import axios from 'axios';
import { ethers } from 'ethers';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { logger } from './logger.js';
import { getEVMWallet } from './wallet-manager.js';
import { config } from './config.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const arbitratorManagerABI = JSON.parse(
  readFileSync(join(__dirname, '../abi/ArbitratorManager.json'), 'utf8')
);

const SUBGRAPH_URL =
  process.env.ARBITRATION_SUBGRAPH_URL || 'https://graph.bel2.org/subgraphs/name/btcd-arbitrators-staging';

export async function fetchArbiters() {
  const query = `query FetchArbiters {
    arbiterInfos (where: { isActive: true }) {
      id
      address
      deadLine
      isActive
      paused
    }
  }`;

  try {
    const response = await axios.post(SUBGRAPH_URL, { query }, { headers: { 'Content-Type': 'application/json' } });

    return response.data?.data?.arbiterInfos || [];
  } catch (error) {
    logger.error('Failed to fetch arbiters from subgraph:', error.message);
    return [];
  }
}

export async function selectBestArbiter(orderDurationDays) {
  logger.info('Selecting best arbiter for order...');

  // Fetch arbiters from subgraph
  const arbiters = await fetchArbiters();

  if (arbiters.length === 0) {
    throw new Error('No active arbiters found');
  }

  logger.info(`Found ${arbiters.length} active arbiters`);

  // Calculate minimum deadline (order duration + buffer)
  const now = Math.floor(Date.now() / 1000);
  const orderDurationSeconds = orderDurationDays * 24 * 60 * 60;
  const bufferSeconds = 30 * 24 * 60 * 60; // 30 days buffer
  const minDeadline = now + orderDurationSeconds + bufferSeconds;

  logger.data('Min Arbiter Deadline', new Date(minDeadline * 1000).toISOString());

  // Filter arbiters with deadline after minDeadline
  const validArbiters = arbiters.filter(a => {
    const arbiterDeadline = Number(a.deadLine);
    const isValid = arbiterDeadline > minDeadline && !a.paused && a.isActive;

    if (isValid) {
      logger.data(`  ${a.address.substring(0, 10)}...`, `Deadline: ${new Date(arbiterDeadline * 1000).toISOString()}`);
    }

    return isValid;
  });

  if (validArbiters.length === 0) {
    throw new Error(
      `No arbiters found with deadline after ${new Date(minDeadline * 1000).toISOString()}. ` +
        `Try a shorter loan duration or wait for new arbiters.`
    );
  }

  // Select the first valid arbiter
  const selected = validArbiters[0];

  logger.success(`Selected arbiter: ${selected.address}`);
  logger.data('  Deadline', new Date(Number(selected.deadLine) * 1000).toISOString());

  return selected.address;
}

export async function getArbiterFeesFromOrderContract(orderContractInstance, arbiterAddress) {
  try {
    const fees = await orderContractInstance.getSelectedArbitratorFees(arbiterAddress);
    return {
      ethFee: fees.ethFee,
      btcFee: fees.btcFee
    };
  } catch (error) {
    logger.warning('Failed to get arbiter fees from order contract, using defaults:', error.message);
    return { ethFee: ethers.BigNumber.from(0), btcFee: ethers.BigNumber.from(0) };
  }
}

export async function getArbiterFeesFromManager(duration, arbiterAddress) {
  const wallet = getEVMWallet();
  const contract = new ethers.Contract(
    config.contracts.arbitratorManager || [REDACTED_SECRET],
    arbitratorManagerABI.abi,
    wallet
  );

  try {
    const [ethFee, btcFee] = await Promise.all([
      contract.getFee(duration, arbiterAddress),
      contract.getBtcFee(duration, arbiterAddress)
    ]);

    return {
      ethFee,
      btcFee
    };
  } catch (error) {
    logger.warning('Failed to get arbiter fees from manager, using defaults:', error.message);
    return { ethFee: ethers.BigNumber.from(0), btcFee: ethers.BigNumber.from(0) };
  }
}


path: scripts/utils/btc-client.js
content: import * as bitcoin from 'bitcoinjs-lib';
import axios from 'axios';
import { config } from './config.js';
import { logger } from './logger.js';
import { getBTCKeyPair, getBTCNetwork } from './wallet-manager.js';

const nowNodesUrl = config.api.nowNodesBtc;
const MEMPOOL_API = 'https://mempool.space/api';

// Create axios instance with timeout
const axiosInstance = axios.create({
  timeout: 30000 // 30 second timeout
});

export async function getUTXOs(address) {
  // Try mempool.space API first (more reliable)
  try {
    logger.info(`Fetching UTXOs from mempool.space for ${address}...`);
    const response = await axiosInstance.get(`${MEMPOOL_API}/address/${address}/utxo`);
    logger.success(`Found ${response.data.length} UTXOs`);
    return response.data;
  } catch (mempoolError) {
    logger.warning('Mempool API failed, trying NowNodes:', mempoolError.message);

    // Fallback to NowNodes
    try {
      const response = await axiosInstance.get(`${nowNodesUrl}/address/${address}/utxo`);
      return response.data;
    } catch (nowNodesError) {
      logger.error('Both APIs failed to fetch UTXOs');
      logger.error('Mempool error:', mempoolError.message);
      logger.error('NowNodes error:', nowNodesError.message);
      throw new Error('Failed to fetch UTXOs from all available APIs');
    }
  }
}

export async function getTransactionDetails(txId) {
  // Try mempool.space API first
  try {
    const response = await axiosInstance.get(`${MEMPOOL_API}/tx/${txId}`);
    return response.data;
  } catch (mempoolError) {
    logger.warning('Mempool API failed, trying NowNodes:', mempoolError.message);

    // Fallback to NowNodes
    try {
      const response = await axiosInstance.get(`${nowNodesUrl}/tx/${txId}`);
      return response.data;
    } catch (nowNodesError) {
      logger.error('Both APIs failed to fetch transaction');
      throw new Error(`Failed to fetch transaction ${txId} from all available APIs`);
    }
  }
}

export async function getBlock(blockHash) {
  // Try mempool.space API first
  try {
    const response = await axiosInstance.get(`${MEMPOOL_API}/block/${blockHash}`);
    const txIdsResponse = await axiosInstance.get(`${MEMPOOL_API}/block/${blockHash}/txids`);
    return {
      blockInfo: response.data,
      txIds: txIdsResponse.data
    };
  } catch (mempoolError) {
    logger.warning('Mempool API failed, trying NowNodes:', mempoolError.message);

    // Fallback to NowNodes
    try {
      const response = await axiosInstance.get(`${nowNodesUrl}/block/${blockHash}`);
      return {
        blockInfo: response.data,
        txIds: response.data.tx.map(tx => (typeof tx === 'string' ? tx : tx.txid))
      };
    } catch (nowNodesError) {
      logger.error('Both APIs failed to fetch block');
      throw new Error(`Failed to fetch block ${blockHash} from all available APIs`);
    }
  }
}

export async function broadcastTransaction(rawTxHex) {
  logger.info('Broadcasting BTC transaction...');

  // Try mempool.space API first
  try {
    const response = await axiosInstance.post(`${MEMPOOL_API}/tx`, rawTxHex);
    const txId = response.data;
    logger.success('Transaction broadcast successfully via mempool.space!');
    logger.btcTxHash(txId);
    return txId;
  } catch (mempoolError) {
    logger.warning('Mempool API failed, trying NowNodes:', mempoolError.response?.data || mempoolError.message);

    // Fallback to NowNodes
    try {
      const response = await axiosInstance.post(`${nowNodesUrl}/tx`, rawTxHex, {
        headers: { 'Content-Type': 'text/plain' }
      });
      const txId = response.data;
      logger.success('Transaction broadcast successfully via NowNodes!');
      logger.btcTxHash(txId);
      return txId;
    } catch (nowNodesError) {
      logger.error('Failed to broadcast via both APIs');
      logger.error('Mempool error:', mempoolError.response?.data || mempoolError.message);
      logger.error('NowNodes error:', nowNodesError.response?.data || nowNodesError.message);
      throw new Error('Failed to broadcast transaction to all available APIs');
    }
  }
}

export async function estimateFeeRate() {
  try {
    const response = await axiosInstance.get(`${MEMPOOL_API}/v1/fees/recommended`);
    // Use hourFee for ~1 hour confirmation
    const feeRate = response.data.hourFee || response.data.halfHourFee || 20;
    logger.info(`Estimated fee rate: ${feeRate} sats/vB`);
    return Math.ceil(feeRate);
  } catch (error) {
    logger.warning('Failed to estimate fee from mempool.space, using default 20 sats/vB');
    return 20;
  }
}

export async function createAndSignTransaction(toAddress, amountSats, satsPerVB = null) {
  const keyPair = getBTCKeyPair();
  const network = getBTCNetwork();
  const fromAddress = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network }).address;

  // Get UTXOs
  const utxos = await getUTXOs(fromAddress);
  if (!utxos || utxos.length === 0) {
    throw new Error(`No UTXOs found for address ${fromAddress}`);
  }

  // Get fee rate
  const feeRate = satsPerVB || (await estimateFeeRate());
  logger.info(`Using fee rate: ${feeRate} sats/vB`);

  // Create transaction
  const psbt = new bitcoin.Psbt({ network });

  let totalInput = 0;
  for (const utxo of utxos) {
    // Fetch the full transaction to get the output script
    const txDetails = await getTransactionDetails(utxo.txid);

    // Get scriptPubKey - handle different API response formats
    let scriptPubKey;
    const vout = txDetails.vout[utxo.vout];

    if (vout.scriptpubkey) {
      // mempool.space format
      scriptPubKey = Buffer.from(vout.scriptpubkey, 'hex');
    } else if (vout.scriptPubKey?.hex) {
      // NowNodes format
      scriptPubKey = Buffer.from(vout.scriptPubKey.hex, 'hex');
    } else {
      logger.error('Unknown scriptPubKey format:', JSON.stringify(vout));
      throw new Error('Cannot extract scriptPubKey from transaction output');
    }

    // Get value - handle different formats
    const utxoValue = utxo.value || (vout.value ? Math.floor(vout.value * 100000000) : 0);

    psbt.addInput({
      hash: utxo.txid,
      index: utxo.vout,
      witnessUtxo: {
        script: scriptPubKey,
        value: utxoValue
      }
    });

    totalInput += utxoValue;

    // Use first UTXO for simplicity, or accumulate until we have enough
    if (totalInput >= amountSats + 1000) break; // 1000 sats for fees estimate
  }

  // Add output
  psbt.addOutput({
    address: toAddress,
    value: amountSats
  });

  // Estimate fee
  const estimatedSize = 140 * psbt.txInputs.length + 34 * 2 + 10; // Rough estimate
  const estimatedFee = Math.ceil(estimatedSize * feeRate);

  // Add change output if needed
  const change = totalInput - amountSats - estimatedFee;
  if (change > 546) {
    // Dust threshold
    psbt.addOutput({
      address: fromAddress,
      value: change
    });
  }

  // Sign all inputs
  psbt.signAllInputs(keyPair);
  psbt.finalizeAllInputs();

  const tx = psbt.extractTransaction();
  logger.info('Transaction created:');
  logger.data('  From', fromAddress);
  logger.data('  To', toAddress);
  logger.data('  Amount', `${amountSats} sats`);
  logger.data('  Fee', `${estimatedFee} sats`);
  logger.data('  Total Input', `${totalInput} sats`);
  logger.data('  Change', `${change} sats`);

  return {
    hex: tx.toHex(),
    txId: tx.getId()
  };
}

export async function waitForConfirmations(txId, requiredConfirmations = 3) {
  logger.info(`Waiting for ${requiredConfirmations} confirmations...`);

  let confirmations = 0;
  let currentBlockHeight = null;

  while (confirmations < requiredConfirmations) {
    await new Promise(resolve => setTimeout(resolve, 30000)); // Wait 30 seconds

    try {
      const txDetails = await getTransactionDetails(txId);

      // Handle different API response formats for confirmations
      if (txDetails.confirmations !== undefined) {
        // NowNodes format - direct confirmations field
        confirmations = txDetails.confirmations;
      } else if (txDetails.status) {
        // mempool.space format - calculate from block heights
        if (txDetails.status.confirmed && txDetails.status.block_height) {
          // Get current block height
          if (!currentBlockHeight) {
            try {
              const tipResponse = await axiosInstance.get(`${MEMPOOL_API}/blocks/tip/height`);
              currentBlockHeight = tipResponse.data;
            } catch (e) {
              logger.warning('Could not fetch current block height');
            }
          }

          if (currentBlockHeight) {
            confirmations = currentBlockHeight - txDetails.status.block_height + 1;
          } else {
            // If we can't get current height, assume confirmed = 1 confirmation
            confirmations = txDetails.status.confirmed ? 1 : 0;
          }
        } else {
          confirmations = 0; // Not confirmed yet
        }
      } else {
        confirmations = 0;
      }

      logger.info(`Current confirmations: ${confirmations}/${requiredConfirmations}`);

      // Update current block height periodically
      if (confirmations > 0 && confirmations < requiredConfirmations) {
        try {
          const tipResponse = await axiosInstance.get(`${MEMPOOL_API}/blocks/tip/height`);
          currentBlockHeight = tipResponse.data;
        } catch (e) {
          // Ignore, will retry next loop
        }
      }
    } catch (error) {
      logger.warning('Failed to check confirmations, retrying...', error.message);
    }
  }

  logger.success(`Transaction has ${confirmations} confirmations!`);
  return confirmations;
}


path: scripts/utils/config.js
content: import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Load .env file from the test-flow directory
dotenv.config({ path: join(__dirname, '..', '.env') });

export const config = {
  evm: {
    privateKey: process.env.EVM_PRIVATE_KEY,
    rpcUrl: process.env.EVM_RPC_URL || 'http://localhost:20657'
  },
  btc: {
    privateKey: process.env.BTC_PRIVATE_KEY,
    network: process.env.BTC_NETWORK || 'mainnet'
  },
  contracts: {
    loanContract: process.env.LOAN_CONTRACT_ADDRESS || [REDACTED_SECRET],
    issuer: process.env.ISSUER_CONTRACT_ADDRESS || [REDACTED_SECRET],
    btcdToken: process.env.BTCD_TOKEN_ADDRESS || [REDACTED_SECRET],
    arbitratorManager: process.env.ARBITRATOR_MANAGER_ADDRESS || [REDACTED_SECRET],
    stakingFactory: process.env.STAKING_FACTORY_ADDRESS || [REDACTED_SECRET],
    fistToken: process.env.FIST_TOKEN_ADDRESS || [REDACTED_SECRET]
  },
  api: {
    nowNodesBtc: process.env.NOWNODES_BTC_URL || 'https://nownodes-btc.bel2.org'
  },
  test: {
    orderId: process.env.ORDER_ID,
    lendingAmount: process.env.LENDING_AMOUNT || '10',
    lendingDays: parseInt(process.env.LENDING_DAYS || '90')
  }
};

const VALID_LENDING_DAYS = [90, 180];
const MIN_LENDING_AMOUNT = 10;

export function validateConfig() {
  const errors = [];
  const warnings = [];

  if (!config.evm.privateKey) {
    errors.push('EVM_PRIVATE_KEY is required');
  }

  if (!config.btc.privateKey) {
    errors.push('BTC_PRIVATE_KEY is required');
  }

  if (parseFloat(config.test.lendingAmount) < MIN_LENDING_AMOUNT) {
    warnings.push(`LENDING_AMOUNT=${config.test.lendingAmount} is below minimum (${MIN_LENDING_AMOUNT}). Forcing to ${MIN_LENDING_AMOUNT}.`);
    config.test.lendingAmount = String(MIN_LENDING_AMOUNT);
  }

  if (!VALID_LENDING_DAYS.includes(config.test.lendingDays)) {
    const nearest = VALID_LENDING_DAYS.reduce((a, b) =>
      Math.abs(b - config.test.lendingDays) < Math.abs(a - config.test.lendingDays) ? b : a
    );
    warnings.push(`LENDING_DAYS=${config.test.lendingDays} is invalid (allowed: ${VALID_LENDING_DAYS.join(', ')}). Forcing to ${nearest}.`);
    config.test.lendingDays = nearest;
  }

  if (warnings.length > 0) {
    console.warn('\n⚠️  Configuration warnings:');
    warnings.forEach(w => console.warn(`   ${w}`));
    console.warn('');
  }

  if (errors.length > 0) {
    throw new Error(`Configuration validation failed:\n${errors.join('\n')}`);
  }
}


path: scripts/utils/evm-client.js
content: import { ethers } from 'ethers';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { logger } from './logger.js';
import { getEVMWallet } from './wallet-manager.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Load contract ABIs
const loadABI = filename => {
  const abiPath = join(__dirname, '../abi', filename);
  return JSON.parse(readFileSync(abiPath, 'utf8'));
};

const loanContractABI = loadABI('LoanContract.json');
const orderABI = loadABI('Order.json');
const issuerABI = loadABI('Issuer.json');
const erc20ABI = loadABI('ERC20.json');

export function getLoanContractInstance(address) {
  const wallet = getEVMWallet();
  return new ethers.Contract(address, loanContractABI.abi, wallet);
}

export function getOrderContractInstance(address) {
  const wallet = getEVMWallet();
  return new ethers.Contract(address, orderABI.abi, wallet);
}

export function getIssuerContractInstance(address) {
  const wallet = getEVMWallet();
  return new ethers.Contract(address, issuerABI.abi, wallet);
}

export function getERC20Instance(address) {
  const wallet = getEVMWallet();
  return new ethers.Contract(address, erc20ABI.abi, wallet);
}

export async function waitForTransaction(tx, confirmations = 1) {
  logger.info(`Waiting for transaction confirmation...`);
  logger.txHash(tx.hash);

  const receipt = await tx.wait(confirmations);

  if (receipt.status === 1) {
    logger.success('Transaction confirmed!');
  } else {
    throw new Error('Transaction failed');
  }

  return receipt;
}

export async function getOrderData(loanContractAddress, orderId) {
  // orderId is the address of the deployed order contract
  const orderContract = getOrderContractInstance(orderId);

  try {
    // Read order data from the specific order contract
    // Field names from Order.json ABI
    const orderType = await orderContract.orderType();
    const status = await orderContract.status();
    const token = await orderContract.token();
    const tokenAmount = await orderContract.tokenAmount();
    const collateralAmount = await orderContract.collateralAmount();
    const interestRate = await orderContract.interestRate();
    const limitedDays = await orderContract.limitedDays();
    const createTime = await orderContract.createTime();
    const takenTime = await orderContract.takenTime();
    const lender = await orderContract.lender();
    const borrower = await orderContract.borrower();
    const borrowerBtcAddress = await orderContract.borrowerBtcAddress();
    const borrowerPublicKey = await orderContract.borrowerPublicKey();
    const lenderBtcAddress = await orderContract.lenderBtcAddress();
    const preImageHash = await orderContract.preImageHash();
    const currentSelectedArbitrator = await orderContract.currentSelectedArbitrator();

    return {
      orderType,
      status,
      token,
      lendingAmount: tokenAmount, // Alias for compatibility
      tokenAmount,
      collateralAmount,
      interestRate,
      durationDays: limitedDays, // Alias for compatibility
      limitedDays,
      createdAt: createTime, // Alias for compatibility
      createTime,
      takenAt: takenTime, // Alias for compatibility
      takenTime,
      lender,
      borrower,
      borrowerBtcAddress,
      borrowerBtcPubKey: borrowerPublicKey, // Alias for compatibility
      borrowerPublicKey,
      lenderBtcAddress,
      preImageHash,
      arbiterAddress: currentSelectedArbitrator, // Alias for compatibility
      currentSelectedArbitrator
    };
  } catch (error) {
    logger.error('Failed to get order data:', error.message);
    throw error;
  }
}

export function parseUnits(value, decimals) {
  return ethers.utils.parseUnits(value.toString(), decimals);
}

export function formatUnits(value, decimals) {
  return ethers.utils.formatUnits(value, decimals);
}


path: scripts/utils/logger.js
content: import chalk from 'chalk';

const timestamp = () => new Date().toISOString();

export const logger = {
  info: (msg, ...args) => {
    console.log(chalk.blue(`[${timestamp()}] INFO:`), msg, ...args);
  },

  success: (msg, ...args) => {
    console.log(chalk.green(`[${timestamp()}] SUCCESS:`), msg, ...args);
  },

  warning: (msg, ...args) => {
    console.log(chalk.yellow(`[${timestamp()}] WARNING:`), msg, ...args);
  },

  error: (msg, ...args) => {
    console.error(chalk.red(`[${timestamp()}] ERROR:`), msg, ...args);
  },

  section: title => {
    console.log('\n' + chalk.cyan('='.repeat(60)));
    console.log(chalk.cyan.bold(title));
    console.log(chalk.cyan('='.repeat(60)) + '\n');
  },

  data: (label, value) => {
    console.log(chalk.gray(`  ${label}:`), chalk.white(value));
  },

  txHash: (hash, explorerUrl = 'https://pgp.elastos.io') => {
    console.log(chalk.gray(`  Transaction:`), chalk.white(hash));
    console.log(chalk.gray(`  Explorer:`), chalk.blue(`${explorerUrl}/tx/${hash}`));
  },

  btcTxHash: hash => {
    console.log(chalk.gray(`  BTC Transaction:`), chalk.white(hash));
    console.log(chalk.gray(`  Explorer:`), chalk.blue(`https://mempool.space/tx/${hash}`));
  }
};


path: scripts/utils/proof-generator.js
content: import { MerkleTree } from 'merkletreejs';
import crypto from 'crypto';
import axios from 'axios';
import { getTransactionDetails, getBlock } from './btc-client.js';
import { logger } from './logger.js';

function sha256(data) {
  return crypto.createHash('sha256').update(data).digest();
}

function doubleSha256(data) {
  return sha256(sha256(data));
}

function generateMerkleProof(btcTxIds, paymentBtcTxId) {
  const leaf = '0x' + paymentBtcTxId;
  const leaves = btcTxIds.map(tx => '0x' + tx);
  const tree = new MerkleTree(leaves, sha256, {
    isBitcoinTree: true,
    duplicateOdd: false,
    sort: false
  });

  const merkleRoot = tree.getHexRoot();
  const proof = tree.getHexProof(leaf);
  const positions = tree.getProof(leaf).map(p => p.position === 'right');
  const txIndex = btcTxIds.indexOf(paymentBtcTxId);

  logger.info('Merkle proof generated:');
  logger.data('  Root', merkleRoot);
  logger.data('  Tx Index', txIndex);
  logger.data('  Verified', tree.verify(tree.getProof(leaf), leaf, tree.getRoot()));

  return {
    merkleRoot,
    leaf,
    proof,
    positions,
    txIndex
  };
}

export async function generateProofParams(btcTxId, scriptAddress = null) {
  logger.info('Generating ZKP proof parameters...');
  logger.data('Transaction ID', btcTxId);

  // Get transaction details
  const txDetails = await getTransactionDetails(btcTxId);
  if (!txDetails) {
    throw new Error('Failed to get transaction details');
  }

  // Handle different block hash field names
  const blockHash = txDetails.blockhash || txDetails.status?.block_hash;

  if (!blockHash) {
    logger.error('Transaction details:', JSON.stringify(txDetails, null, 2));
    throw new Error('Transaction has not been confirmed in a block yet. Block hash is missing.');
  }

  logger.data('Block Hash', blockHash);

  // Get block information
  const { blockInfo, txIds } = await getBlock(blockHash);
  if (!blockInfo) {
    throw new Error('Failed to get block info');
  }

  logger.data('Block Height', blockInfo.height);
  logger.data('Transactions in block', txIds.length);

  const blockHeight = blockInfo.height;

  // Get raw transaction hex - handle different formats
  let txRawData;
  const txHex = txDetails.hex || '';
  if (!txHex) {
    // mempool.space might require separate endpoint for hex
    logger.info('Fetching raw transaction hex...');
    try {
      const hexResponse = await axios.get(`https://mempool.space/api/tx/${btcTxId}/hex`);
      txRawData = '0x' + hexResponse.data;
    } catch (e) {
      throw new Error(`Failed to get transaction hex data for ${btcTxId}`);
    }
  } else {
    txRawData = '0x' + txHex;
  }

  // Generate Merkle proof
  const { merkleRoot, proof, leaf, positions, txIndex } = generateMerkleProof(txIds, btcTxId);

  // Get UTXOs (parent transactions)
  const utxos = [];
  const txInputs = txDetails.vin || [];

  for (const vin of txInputs) {
    const parentTxId = vin.txid;
    const txData = await getTransactionDetails(parentTxId);
    if (!txData) {
      throw new Error(`Failed to get parent transaction ${parentTxId}`);
    }
    // Handle different hex field names
    const txHex = txData.hex || '';
    if (!txHex) {
      logger.warning(`No hex data for parent transaction ${parentTxId}, fetching raw tx...`);
      // For mempool.space, might need to fetch hex separately
      try {
        const hexResponse = await axios.get(`https://mempool.space/api/tx/${parentTxId}/hex`);
        utxos.push('0x' + hexResponse.data);
      } catch (e) {
        logger.error(`Could not fetch hex for ${parentTxId}`);
        throw new Error(`Failed to get hex data for parent transaction ${parentTxId}`);
      }
    } else {
      utxos.push('0x' + txHex);
    }
  }

  // Try to find script address index in outputs
  let scriptAddressOutputIndex = -1;
  if (scriptAddress) {
    scriptAddressOutputIndex = txDetails.vout.findIndex(vout => {
      // Handle different API formats
      if (vout.scriptPubKey?.addresses?.includes(scriptAddress)) return true;
      if (vout.scriptPubKey?.address === scriptAddress) return true;
      if (vout.scriptpubkey_address === scriptAddress) return true; // mempool.space format
      return false;
    });

    if (scriptAddressOutputIndex === -1) {
      logger.warning(`Script address ${scriptAddress} not found in transaction outputs`);
    } else {
      logger.data('Script address output index', scriptAddressOutputIndex);
    }
  }

  logger.success('Proof parameters generated successfully!');

  return {
    blockHeight,
    txRawData,
    utxos,
    proof,
    merkleRoot,
    leaf,
    positions,
    scriptAddressOutputIndex,
    txIndex
  };
}

export function generatePreImage() {
  // Generate a random 32-byte preimage
  const preImage = crypto.randomBytes(32).toString('hex');
  const preImageHash = sha256(Buffer.from(preImage, 'hex')).toString('hex');

  logger.info('PreImage generated:');
  logger.data('  PreImage', preImage);
  logger.data('  Hash', preImageHash);

  return {
    preImage,
    preImageHash
  };
}


path: scripts/utils/state-manager.js
content: import { readFileSync, writeFileSync, existsSync, renameSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { logger } from './logger.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Support FLOW_STATE_FILE env var to allow attack scripts to use a separate state file
// Set process.env.FLOW_STATE_FILE = 'flow-state-test.json' before calling any state functions
function getStateFilePath() {
  return join(__dirname, '../state/', process.env.FLOW_STATE_FILE || 'flow-state.json');
}

export function loadState() {
  const STATE_FILE = getStateFilePath();

  if (!existsSync(STATE_FILE)) {
    logger.info(`No existing state file found (${process.env.FLOW_STATE_FILE || 'flow-state.json'}), creating new state`);
    return {
      createdAt: new Date().toISOString(),
      steps: {}
    };
  }

  try {
    const data = readFileSync(STATE_FILE, 'utf8');
    const state = JSON.parse(data);
    logger.info(`State loaded from ${process.env.FLOW_STATE_FILE || 'flow-state.json'}`);
    return state;
  } catch (error) {
    logger.error('Failed to load state:', error.message);
    throw error;
  }
}

/**
 * Load state from a specific file (by filename, relative to state/ directory).
 * Used by attack scripts to read the original state while writing to a different file.
 */
export function loadStateFrom(filename) {
  const filePath = join(__dirname, '../state/', filename);
  if (!existsSync(filePath)) {
    throw new Error(`State file not found: ${filename}`);
  }
  const data = readFileSync(filePath, 'utf8');
  return JSON.parse(data);
}

export function saveState(state) {
  const STATE_FILE = getStateFilePath();

  try {
    state.updatedAt = new Date().toISOString();
    writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
    logger.success(`State saved to ${process.env.FLOW_STATE_FILE || 'flow-state.json'}`);
  } catch (error) {
    logger.error('Failed to save state:', error.message);
    throw error;
  }
}

export function updateStep(stepName, data) {
  const state = loadState();
  state.steps[stepName] = {
    ...data,
    completedAt: new Date().toISOString()
  };
  saveState(state);
  return state;
}

export function getStep(stepName) {
  const state = loadState();
  return state.steps[stepName];
}

export function archiveState() {
  const STATE_FILE = getStateFilePath();
  if (!existsSync(STATE_FILE)) {
    logger.warning('No state file to archive');
    return null;
  }

  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
  const archiveName = `flow-state-${timestamp}.json`;
  const archivePath = join(dirname(STATE_FILE), archiveName);

  renameSync(STATE_FILE, archivePath);
  logger.success(`Archived: ${archiveName}`);

  return archivePath;
}

export function requireStep(stepName, fieldName = null) {
  const step = getStep(stepName);
  if (!step) {
    throw new Error(`Step "${stepName}" has not been completed yet. Please run it first.`);
  }

  if (fieldName && !step[fieldName]) {
    throw new Error(`Field "${fieldName}" not found in step "${stepName}"`);
  }

  return fieldName ? step[fieldName] : step;
}


path: scripts/utils/wallet-manager.js
content: import { ethers } from 'ethers';
import * as bitcoin from 'bitcoinjs-lib';
import * as ecc from '@bitcoinerlab/secp256k1';
import { ECPairFactory } from 'ecpair';
import { config } from './config.js';
import { logger } from './logger.js';

// Initialize ECC library for Bitcoin
bitcoin.initEccLib(ecc);
const ECPair = ECPairFactory(ecc);

let evmWallet = null;
let btcKeyPair = null;
let btcAddress = null;

export function initializeWallets() {
  logger.info('Initializing wallets...');

  // Initialize EVM wallet
  const provider = new ethers.providers.JsonRpcProvider(config.evm.rpcUrl);
  evmWallet = new ethers.Wallet(config.evm.privateKey, provider);

  // Initialize BTC wallet
  const network = config.btc.network === 'mainnet' ? bitcoin.networks.bitcoin : bitcoin.networks.testnet;
  const btcPrivateKeyBuffer = Buffer.from(config.btc.privateKey, 'hex');
  btcKeyPair = ECPair.fromPrivateKey(btcPrivateKeyBuffer, { network });

  // Generate P2WPKH address (native segwit)
  const { address } = bitcoin.payments.p2wpkh({
    pubkey: btcKeyPair.publicKey,
    network
  });
  btcAddress = address;

  logger.success('Wallets initialized');
  logger.data('EVM Address', evmWallet.address);
  logger.data('BTC Address', btcAddress);
  logger.data('BTC Public Key', btcKeyPair.publicKey.toString('hex'));

  return {
    evmWallet,
    btcKeyPair,
    btcAddress
  };
}

export function getEVMWallet() {
  if (!evmWallet) {
    throw new Error('EVM wallet not initialized. Call initializeWallets() first.');
  }
  return evmWallet;
}

export function getBTCKeyPair() {
  if (!btcKeyPair) {
    throw new Error('BTC wallet not initialized. Call initializeWallets() first.');
  }
  return btcKeyPair;
}

export function getBTCAddress() {
  if (!btcAddress) {
    throw new Error('BTC wallet not initialized. Call initializeWallets() first.');
  }
  return btcAddress;
}

export function getBTCPublicKey() {
  if (!btcKeyPair) {
    throw new Error('BTC wallet not initialized. Call initializeWallets() first.');
  }
  return btcKeyPair.publicKey.toString('hex');
}

export function getBTCNetwork() {
  return config.btc.network === 'mainnet' ? bitcoin.networks.bitcoin : bitcoin.networks.testnet;
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)