# Welcome

**Welcome to MNEE for developers.**

Here you’ll find everything you need to integrate, explore, and build with MNEE — a USD-backed stablecoin designed for instant, low-cost transactions. Whether you’re a developer, product designer, or platform architect, this portal provides clear guidance, powerful tools, and real-world examples to help you leverage MNEE in your apps and services. **Let’s make money move smarter!**

### Jump right in

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Introduction</strong></td><td>Get to know what MNEE is and how it works.</td><td><a href="/files/JCLTVDEAHSXtvDMJHsRh">/files/JCLTVDEAHSXtvDMJHsRh</a></td><td></td><td><a href="/pages/CyH2xJQs9yWJ1S8BYNav">/pages/CyH2xJQs9yWJ1S8BYNav</a></td></tr><tr><td><strong>QuickStart</strong></td><td>Send your first payment in 5 minutes or less!</td><td><a href="/files/xpGKwDkvBIzDpFHkuYf3">/files/xpGKwDkvBIzDpFHkuYf3</a></td><td></td><td><a href="/pages/JjjojIyKxaiBPzzLwvtg">/pages/JjjojIyKxaiBPzzLwvtg</a></td></tr><tr><td><strong>MNEE CLI</strong></td><td>Simplify your experience by using the MNEE CLI tool.</td><td><a href="/files/hUHrWj9VaBFTicav0JIw">/files/hUHrWj9VaBFTicav0JIw</a></td><td></td><td><a href="/pages/bkjtnEilOghs99Cx6vYc">/pages/bkjtnEilOghs99Cx6vYc</a></td></tr></tbody></table>


# Introduction

MNEE: A Fast, Stable USD-Backed Stablecoin

MNEE (pronounced “money”) is a fast, USD-backed stablecoin designed for digital payments, gaming, remittances, and more. Fully collateralized 1:1 with U.S. Treasury bills and cash equivalents, MNEE ensures stability and transparency through regular audits. It operates on the high-speed [1Sat Ordinals protocol](https://docs.1satordinals.com/), offering near-zero fees and instant finality. MNEE is regulated in Antigua with full AML/KYC compliance.

{% hint style="success" %}
Building with MNEE requires no previous blockchain experience :raised\_hands:
{% endhint %}

### Why Build with MNEE?

**Key benefits:**

* **⚡ Instant Transactions**

  Send and receive in under a second via [1Sat](https://docs.1satordinals.com/).
* **💸 Gasless UX**

  No need for users to manage or hold gas tokens on 1Sat.
* **🧩 Composable & Open**

  Integrate MNEE into any app, bot, or backend in minutes.
* **🌍 Multi-Chain by Design**

  Tap into both Ordinals and Ethereum ecosystems.
* **📉 Low Fees**

  MNEE enables micropayments and high-frequency interactions with near-zero cost.

### Common Use Cases

| Use Case               | Description                                                                 |
| ---------------------- | --------------------------------------------------------------------------- |
| In-App Payments        | Frictionless tipping, rewards, or purchases                                 |
| Microtransactions      | Pay-per-click, stream metering, API monetization                            |
| Peer-to-Peer Transfers | Send stablecoins without needing complex wallets                            |
| Stable Game Currencies | Avoid volatile tokens in gameplay or economies                              |
| Remittances            | Instant cross-border payments and remittances without the fees and friction |
| A2A (Agent-to-Agent)   | Empower artificial intelligence with agent-to-agent payments                |

### Who Is This For?

This documentation is designed for:

* Web2 and Web3 builders
* Product teams integrating payments
* Wallet and fintech startups
* Open-source tinkerers and experimenters

If you want to give your users the power of programmable dollars—with blazing-fast finality and no blockchain headaches—you’re in the right place.

***


# Quick Start

This guide was created to help you get started as quickly as possible using the MNEE Typescript SDK.

{% hint style="warning" %}
If you’re not planning to use a Node.js environment, please [use the MNEE API directly](/api-reference/mnee-api). We’re actively developing SDKs for other popular frameworks.
{% endhint %}

{% hint style="success" %}
Give your AI or LLM of choice [all the context they need](/dev-tools/llm-context).
{% endhint %}

### 1. Get your API keys

Create your MNEE Developer account and generate your API keys.

<a href="https://developer.mnee.net/" class="button primary" data-icon="code">Developer Portal</a>

### 2. Install the [Typescript SDK](https://github.com/mnee-xyz/mnee)

```bash
npm i @mnee/ts-sdk
```

### 3. Configure the SDK

Configure the SDK with your API settings and call `config()`. This will return the [current configuration](/mnee-sdk/config) of the MNEE API based on your environment.

```typescript
import Mnee from '@mnee/ts-sdk';

const config = {
  environment: 'sandbox', // or 'production'
  apiKey: 'your-api-key'
};

const mnee = new Mnee(config);

mnee.config().then(mneeConfig => {
  console.log('MNEE Configuration:', mneeConfig);
});

```

{% hint style="success" %}
If you see the configuration log, you've successfully made your first request!
{% endhint %}

### 4. Send MNEE

After ensuring that you've funded your wallet, you can easily [send MNEE](/mnee-sdk/transfer) using the `transfer()` method.

{% hint style="warning" %}
If you need MNEE for testing, your sandbox keys can request 10 MNEE every 24 hours using the [sandbox faucet found here](/dev-tools/coin-faucet).

\
Note: If you're using the [MNEE CLI](/dev-tools/mnee-cli), you can get your WIF by [running mnee export](/dev-tools/mnee-cli#export-private-key).
{% endhint %}

```typescript
const recipient = 'recipient-mnee-address';
const amount = 0.01; // Amount in MNEE as float (up to 5 decimals)
const wif = 'your-wif-private-key';

// Prepare the transfer request
const request = [
  {
    address: recipient,
    amount,
  },
];

// Send the payment
mnee.transfer(request, wif).then(response => {
  console.log('Transfer result:', response);
});
```

If you'd like to review the MNEE SDK code, it's open-source and available here:\
<https://github.com/mnee-xyz/mnee>


# Authentication

To use the MNEE SDK and interact with the MNEE API, you must authenticate your requests with an API key.

### Why Do I Need an API Key?

The API key is required to:

* Securely identify your application or integration.
* Enable access to protected endpoints and features.
* Track usage and ensure fair access for all users.

### How to Get an API Key?

Create a MNEE Developer account and generate your API keys for free.

<a href="https://developer.mnee.net/" class="button primary" data-icon="code">Developer Portal</a>

### Using Your API Key

Once you have your API key, you can use it in your application as follows:

```typescript
import Mnee from '@mnee/ts-sdk';

const mnee = new Mnee({
  environment: 'sandbox', // or 'production'
  apiKey: 'YOUR_API_KEY_HERE',
});
```

{% hint style="danger" %}
Make sure to keep your API key private and never share it publicly.
{% endhint %}

### Troubleshooting

* If you have any questions, please contact **<developer@mnee.io>**

<br>


# Get Config

The `config` method retrieves the current configuration for the MNEE service. This configuration includes essential parameters such as the token ID, current fees, and other settings required for interacting with the MNEE network.

### Usage

```typescript
import Mnee from '@mnee/ts-sdk';

const config = {
  environment: 'sandbox', // or 'production'
  apiKey: 'your-api-key', // optional
};
const mnee = new Mnee(config);

mnee.config().then(mneeConfig => {
  console.log('MNEE Configuration:', mneeConfig);
});
```

### Response

The method returns a Promise that resolves to an `MNEEConfig` object, which contains the configuration details.

#### Sample Response

```json
{
  "approver": "020a177d6a5e6f3a8689acd2e313bd1cf0dcf5a243d1cc67b7218602aee9e04b2f",
  "feeAddress": "19Vq2TV8aVhFNLQkhDMdnEQ7zT96x6F3PK",
  "burnAddress": "1FGEBTUu7EqWWK5DKrG6pxjEGLahpATnA8",
  "mintAddress": "1inHbiwj2jrEcZPiSYnfgJ8FmS1Bmk4Dh",
  "fees": [
    { "min": 0, "max": 1000000, "fee": 100 },
    { "min": 1000001, "max": 9007199254740991, "fee": 1000 }
  ],
  "decimals": 5,
  "tokenId": "ae59f3b898ec61acbdb6cc7a245fabeded0c094bf046f35206a3aec60ef88127_0"
}
```

### Configuration Properties

* **approver**: The public key of the MNEE approver/cosigner service
* **feeAddress**: The address where transaction fees are sent
* **burnAddress**: The address used for burning MNEE tokens
* **mintAddress**: The address used for minting new MNEE tokens
* **fees**: Array of fee tiers based on transaction amount
  * **min**: Minimum amount for this fee tier (in atomic units)
  * **max**: Maximum amount for this fee tier (in atomic units)
  * **fee**: Fee amount for this tier (in atomic units)
* **decimals**: Number of decimal places for MNEE (5 decimals = 100,000 atomic units per MNEE)
* **tokenId**: The unique identifier for the MNEE token on the blockchain

### Common Use Cases

#### Calculate Transaction Fees

```typescript
const config = await mnee.config();
const transferAmount = mnee.toAtomicAmount(10); // 10 MNEE

// Find applicable fee tier
const feeTier = config.fees.find(tier => 
  transferAmount >= tier.min && transferAmount <= tier.max
);

if (feeTier) {
  const feeInMNEE = mnee.fromAtomicAmount(feeTier.fee);
  console.log(`Fee for 10 MNEE transfer: ${feeInMNEE} MNEE`);
}
```

#### Verify Token Configuration

```typescript
const config = await mnee.config();
console.log(`Token ID: ${config.tokenId}`);
console.log(`Decimals: ${config.decimals}`);
console.log(`1 MNEE = ${Math.pow(10, config.decimals)} atomic units`);
```

#### Check Special Addresses

```typescript
const config = await mnee.config();
console.log('Fee collection address:', config.feeAddress);
console.log('Burn address:', config.burnAddress);
console.log('Mint address:', config.mintAddress);
```

#### Validate Approver Key

```typescript
const config = await mnee.config();
console.log('Approver public key:', config.approver);
// This key is used to validate MNEE transactions
```

### Notes

* The configuration is cached after the first call for performance
* Fee tiers are applied based on the transaction amount in atomic units
* The approver public key is essential for validating MNEE transactions
* All amounts in the fees array are in atomic units (1 MNEE = 100,000 atomic units)

### See Also

* [Validate Transaction](/utils/validate) - Validate transactions using approver configuration
* [Transfer](/mnee-sdk/transfer) - Create transfers with automatic fee calculation
* [Unit Conversion](/utils/unit-conversions) - Convert between MNEE and atomic units


# Check Balance

## Check Balance

The `balance` method retrieves the balance for a specific MNEE address. This method is useful for checking how many MNEE tokens are associated with a given address.

### Usage

```typescript
const address = '1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3';

mnee.balance(address).then(balance => {
  console.log('Your balance:', balance);
});
```

### Response

The method returns a Promise that resolves to a `MNEEBalance` object, which includes the address and the amount of MNEE tokens.

#### Sample Response

```json
{
  "address": "1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3",
  "amount": 461163,
  "decimalAmount": 4.61163
}
```

## Check Balances

The `balances` method retrieves the balances for multiple MNEE addresses in a single call. This is useful for checking the balances of several addresses at once.

### Usage

```typescript
const addresses = ['1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3', '1BFaJwJz5KPYGe28afDkGswbuKK6uK8hzQ'];

mnee.balances(addresses).then(balances => {
  console.log('Balances:', balances);
});
```

### Response

The method returns a Promise that resolves to an array of `MNEEBalance` objects, each containing the address and the amount of MNEE tokens.

#### Sample Response

```json
[
  {
    "address": "1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3",
    "amount": 461163,
    "decimalAmount": 4.61163
  },
  {
    "address": "1BFaJwJz5KPYGe28afDkGswbuKK6uK8hzQ",
    "amount": 1500,
    "decimalAmount": 0.015
  }
]
```

### Balance Properties

* **address**: The Bitcoin address that was queried
* **amount**: The balance in atomic units (100,000 atomic units = 1 MNEE)
* **decimalAmount**: The balance in MNEE (human-readable format with decimals)

### Common Use Cases

#### Single Address - Display User Balance

```typescript
const balance = await mnee.balance(userAddress);
console.log(`You have ${balance.decimalAmount} MNEE`);
```

#### Single Address - Check Sufficient Funds

```typescript
const requiredAmount = 10; // 10 MNEE
const balance = await mnee.balance(address);

if (balance.decimalAmount >= requiredAmount) {
  console.log('Sufficient funds available');
} else {
  console.log(`Insufficient funds. Need ${requiredAmount - balance.decimalAmount} more MNEE`);
}
```

#### Multiple Addresses - Calculate Total Balance

```typescript
const addresses = ['address1', 'address2', 'address3'];
const balances = await mnee.balances(addresses);

const totalBalance = balances.reduce((sum, balance) => sum + balance.decimalAmount, 0);
console.log(`Total balance across all addresses: ${totalBalance} MNEE`);
```

#### Multiple Addresses - Find Funded Addresses

```typescript
const balances = await mnee.balances(addresses);
const fundedAddresses = balances.filter(balance => balance.decimalAmount > 0);

console.log('Addresses with funds:');
fundedAddresses.forEach(balance => {
  console.log(`${balance.address}: ${balance.decimalAmount} MNEE`);
});
```

#### HD Wallet Balance Check

```typescript
// Generate HD wallet addresses
const hdAddresses = [];
for (let i = 0; i < 20; i++) {
  hdAddresses.push(hdWallet.deriveAddress(i, false).address);
}

// Check all addresses at once
const balances = await mnee.balances(hdAddresses);
const totalHDBalance = balances.reduce((sum, b) => sum + b.decimalAmount, 0);
console.log(`HD Wallet total: ${totalHDBalance} MNEE`);
```

#### Monitor Balance Changes

```typescript
async function monitorBalance(address, intervalMs = 10000) {
  let previousBalance = 0;
  
  setInterval(async () => {
    const balance = await mnee.balance(address);
    if (balance.decimalAmount !== previousBalance) {
      console.log(`Balance changed: ${previousBalance} → ${balance.decimalAmount} MNEE`);
      previousBalance = balance.decimalAmount;
    }
  }, intervalMs);
}
```

### Performance Considerations

* Use `balance()` for single address queries
* Use `balances()` when checking 2 or more addresses (more efficient than multiple `balance()` calls)
* For very large sets of addresses (100+), consider using [batch operations](/utils/batch-operations)

### Notes

* The balance is calculated from all UTXOs owned by the address
* Both `amount` and `decimalAmount` represent the same value in different units
* Empty or invalid addresses will return a balance of 0
* The order of returned balances matches the order of input addresses

### See Also

* [Get UTXOs](/mnee-sdk/get-utxos) - Get detailed UTXO information
* [Unit Conversion](/utils/unit-conversions) - Convert between atomic units and MNEE
* [Batch Operations](/utils/batch-operations) - Process hundreds of addresses efficiently


# Get UTXOs

The `getUtxos` method retrieves the Unspent Transaction Outputs (UTXOs) for one or more MNEE addresses. UTXOs represent the spendable MNEE tokens associated with an address and are essential for constructing new transactions.

### Usage

#### Basic Usage

```typescript
const address = '1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3';

// Returns up to 10 UTXOs by default
const utxos = await mnee.getUtxos(address);
console.log('UTXOs:', utxos);
```

{% hint style="success" %}
For convenience, you can also call [getEnoughUtxos()](/mnee-sdk/get-enough-utxos) or [getAllUtxos()](/mnee-sdk/get-all-utxos)
{% endhint %}

#### With Pagination

```typescript
const address = '1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3';

// Get first page with 20 UTXOs
const page1 = await mnee.getUtxos(address, 0, 20);
console.log('First 20 UTXOs:', page1);

// Get second page
const page2 = await mnee.getUtxos(address, 1, 20);
console.log('Next 20 UTXOs:', page2);

// Get UTXOs in ascending order (oldest first)
const ascUtxos = await mnee.getUtxos(address, 0, 50, 'asc');
console.log('Oldest UTXOs first:', ascUtxos);
```

#### Multiple Addresses

```typescript
const addresses = ['1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3', '1BFaJwJz5KPYGe28afDkGswbuKK6uK8hzQ'];

// Returns up to 10 UTXOs by default
const utxos = await mnee.getUtxos(addresses);
console.log('UTXOs from all addresses:', utxos);

// Get more UTXOs by specifying size
const moreUtxos = await mnee.getUtxos(addresses, 0, 100, 'desc');
console.log('First 100 UTXOs (newest first):', moreUtxos);
```

### Parameters

* **address**: Single Bitcoin address or array of addresses
* **page** (optional): Page number starting from 0
* **size** (optional): Number of UTXOs per page (default: 10)
* **order** (optional): Sort order - 'asc' for oldest first, 'desc' for newest first (default: 'desc')

### Response

The method returns a Promise that resolves to an array of `MNEEUtxo` objects. Each UTXO contains detailed information about the MNEE tokens, including BSV21 protocol data and cosigner information.

#### Sample Response

```json5
[
  {
    "data": {
      "bsv21": {
        "amt": 95799,
        "dec": 5,
        "icon": "1FGEBTUu7EqWWK5DKrG6pxjEGLahpATnA8",
        "id": "ae59f3b898ec61acbdb6cc7a245fabeded0c094bf046f35206a3aec60ef88127_0",
        "op": "transfer",
        "sym": "MNEE"
      },
      "cosign": {
        "address": "17cgGUmStWwcYgHg3kxmzXSp6JUbj8XA3u",
        "cosigner": "03d47c2e48c59b3f58b96c9e616d0a84c6e02725e47beefcb5b5a8fbe21a3c5e3a"
      }
    },
    "height": 857421,
    "idx": 0,
    "outpoint": "d7fe19af19332d8ab1d83ed82003ecc41c8c5def8e786b58e90512e82087302a:0",
    "owners": ["1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3"],
    "satoshis": 1000,
    "score": 85742100001
  },
  {
    "data": {
      "bsv21": {
        "amt": 50000,
        "dec": 5,
        "icon": "1FGEBTUu7EqWWK5DKrG6pxjEGLahpATnA8",
        "id": "ae59f3b898ec61acbdb6cc7a245fabeded0c094bf046f35206a3aec60ef88127_0",
        "op": "transfer",
        "sym": "MNEE"
      },
      "cosign": {
        "address": "1PqgNQwyPbc1Ue8QwEDFJUP2monKv9hSo4",
        "cosigner": "03d47c2e48c59b3f58b96c9e616d0a84c6e02725e47beefcb5b5a8fbe21a3c5e3a"
      }
    },
    "height": 857420,
    "idx": 1,
    "outpoint": "a9b2c3d4e5f678901234567890abcdef1234567890abcdef1234567890abcdef:1",
    "owners": ["1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3"],
    "satoshis": 1000,
    "score": 85742000002
  }
]
```

### UTXO Properties

#### Main Properties

* **outpoint**: The full UTXO identifier in format `txid:vout`
* **height**: The block height when this UTXO was created
* **idx**: The output index within the transaction
* **owners**: Array of addresses that can spend this UTXO
* **satoshis**: The BSV satoshis in this output (not MNEE amount)
* **score**: A sortable score based on height and index

#### BSV21 Data (`data.bsv21`)

* **amt**: The amount of MNEE tokens in atomic units (100,000 = 1 MNEE)
* **dec**: Number of decimal places (5 for MNEE)
* **icon**: The icon address for the token
* **id**: The token ID
* **op**: The operation type (typically "transfer")
* **sym**: The token symbol ("MNEE")

#### Cosigner Data (`data.cosign`)

* **address**: The cosigner address for this UTXO
* **cosigner**: The cosigner public key

### Common Use Cases

#### Calculate Total Spendable Balance

```typescript
// WARNING: Default only returns 10 UTXOs - may not be complete balance!
// Specify a larger size or use pagination for accurate balance
const utxos = await mnee.getUtxos(address, 0, 1000); // Get up to 1000 UTXOs
const totalAtomicUnits = utxos.reduce((sum, utxo) => sum + utxo.data.bsv21.amt, 0);
const totalMNEE = mnee.fromAtomicAmount(totalAtomicUnits);
console.log(`Total spendable: ${totalMNEE} MNEE`);

// For accurate balance, consider using the balance() method instead:
const balance = await mnee.balance(address);
console.log(`Total balance: ${balance.decimalAmount} MNEE`);
```

#### Get All UTXOs with Pagination

```typescript
async function getAllUtxosWithPagination(address) {
  const allUtxos = [];
  const pageSize = 100;
  let page = 0;
  
  while (true) {
    const utxos = await mnee.getUtxos(address, page, pageSize);
    allUtxos.push(...utxos);
    
    console.log(`Retrieved page ${page + 1}: ${utxos.length} UTXOs`);
    
    // If we got less than pageSize, we've reached the end
    if (utxos.length < pageSize) break;
    
    page++;
  }
  
  console.log(`Total UTXOs retrieved: ${allUtxos.length}`);
  return allUtxos;
}
```

#### Find UTXOs Above a Certain Amount

```typescript
const utxos = await mnee.getUtxos(address);
const largeUtxos = utxos.filter(utxo => utxo.data.bsv21.amt >= 10000); // 0.1 MNEE or more
console.log(`Found ${largeUtxos.length} UTXOs with 0.1 MNEE or more`);
```

#### Prepare UTXOs for Multi-Source Transfer

```typescript
const addresses = ['address1', 'address2', 'address3'];
const allUtxos = await mnee.getUtxos(addresses);

// Group UTXOs by owner address for transferMulti
const utxosByAddress = allUtxos.reduce((acc, utxo) => {
  const owner = utxo.owners[0];
  if (!acc[owner]) acc[owner] = [];
  acc[owner].push(utxo);
  return acc;
}, {});

// Convert to transferMulti format
const inputs = allUtxos.map(utxo => ({
  txid: utxo.outpoint.split(':')[0],
  vout: parseInt(utxo.outpoint.split(':')[1]),
  wif: 'private-key-for-owner' // You need to provide the WIF for each UTXO owner
}));
```

#### Filter UTXOs by Operation Type

```typescript
const utxos = await mnee.getUtxos(address);
const transferUtxos = utxos.filter(utxo => utxo.data.bsv21.op === 'transfer');
console.log(`Found ${transferUtxos.length} transfer UTXOs`);
```

### Performance Considerations

* The API returns only 10 UTXOs by default - specify a larger `size` parameter if you need more
* For addresses with many UTXOs, use pagination to retrieve all of them:

```typescript
// Get all UTXOs for an address
async function getAllUtxos(address) {
  const allUtxos = [];
  const pageSize = 100; // Balance between efficiency and memory
  let page = 0;
  let hasMore = true;
  
  while (hasMore) {
    const utxos = await mnee.getUtxos(address, page, pageSize);
    allUtxos.push(...utxos);
    hasMore = utxos.length === pageSize;
    page++;
  }
  
  return allUtxos;
}
```

### Important Notes

* **Default limit is 10 UTXOs** - Always specify the `size` parameter if you need more
* If an address has more UTXOs than your page size, use pagination to retrieve all of them
* For just checking balance, use the [`balance()`](/mnee-sdk/check-balance) method which is more efficient
* UTXOs are sorted by score (based on height and index) in descending order by default

### See Also

* [Balance](/mnee-sdk/check-balance) - Get balance without UTXO details (more efficient for balance checks)
* [Transfer Multi](/mnee-sdk/transfer-multi) - Use UTXOs for multi-source transfers
* [Get Enough UTXOs](/mnee-sdk/get-enough-utxos) - Get just enough UTXOs for and address and amount
* [Get All UTXOs](/mnee-sdk/get-all-utxos) - Get all UTXOs for a given address


# Get Enough UTXOS

The `getEnoughUtxos` method retrieves just enough Unspent Transaction Outputs (UTXOs) for a MNEE address to cover a specified token amount. This method is optimized for transfer operations, as it stops fetching UTXOs once the required amount is reached, making it more efficient than fetching all UTXOs.

### Usage

#### Basic Usage

```typescript
const address = '1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3';
const requiredAmount = 500000; // 5.00000 MNEE in atomic units

try {
  const utxos = await mnee.getEnoughUtxos(address, requiredAmount);
  console.log('UTXOs for transfer:', utxos);
  console.log('Number of UTXOs needed:', utxos.length);
} catch (error) {
  console.error('Insufficient balance:', error.message);
}
```

### Parameters

| Parameter                | Type     | Required | Description                                                         |
| ------------------------ | -------- | -------- | ------------------------------------------------------------------- |
| `address`                | `string` | Yes      | The MNEE address to fetch UTXOs for                                 |
| `totalAtomicTokenAmount` | `number` | Yes      | The required amount in atomic units (1 MNEE = 100,000 atomic units) |

### Response

The method returns a Promise that resolves to an array of `MNEEUtxo` objects containing just enough UTXOs to meet or exceed the required amount.

#### MNEEUtxo Structure

```typescript
type MNEEUtxo = {
  data: {
    bsv21: {
      amt: number; // Amount in atomic units
      dec: number; // Decimal places
      icon: string; // Token icon
      id: string; // Token ID
      op: string; // Operation type
      sym: string; // Token symbol
    };
    cosign: {
      address: string; // Cosigner address
      cosigner: string; // Cosigner identifier
    };
  };
  height: number; // Block height
  idx: number; // Transaction index
  outpoint: string; // Transaction outpoint (txid_vout)
  satoshis: number; // Satoshi amount
  script: string; // Script hex
  txid: string; // Transaction ID
  vout: number; // Output index
};
```

### Error Handling

The method throws an error if there are insufficient MNEE tokens in the address to meet the required amount:

```typescript
try {
  const utxos = await mnee.getEnoughUtxos(address, 1000000);
} catch (error) {
  // Error message format: "Insufficient MNEE balance. Max transfer amount: X.XXXXX"
  console.error(error.message);
}
```

### Performance Considerations

* **Efficient**: Only fetches UTXOs until the required amount is reached
* **Pagination**: Uses 25 UTXOs per page to balance API efficiency and memory usage
* **Early Exit**: Stops immediately when sufficient UTXOs are found
* **No Sorting**: UTXOs are returned in the order they're fetched (newest first by default)

### Use Cases

1. **Pre-transfer Validation**: Check if an address has enough tokens before attempting a transfer
2. **UTXO Selection**: Get the exact UTXOs needed for a specific transaction amount
3. **Balance Verification**: Verify sufficient funds while minimizing API calls
4. **Wallet Operations**: Prepare UTXOs for transaction construction

### See Also

* [Balance](/mnee-sdk/check-balance) - Get balance without UTXO details (more efficient for balance checks)
* [Transfer Multi](/mnee-sdk/transfer-multi) - Use UTXOs for multi-source transfers
* [Get Enough UTXOs](/mnee-sdk/get-enough-utxos) - Get just enough UTXOs for and address and amount
* [Get All UTXOs](/mnee-sdk/get-all-utxos) - Get all UTXOs for a given address


# Get All UTXOs

The `getAllUtxos` method retrieves all Unspent Transaction Outputs (UTXOs) for a MNEE address. This method fetches every UTXO associated with the address by automatically paginating through all available results, making it ideal for comprehensive balance calculations and wallet management operations.

### Usage

#### Basic Usage

```typescript
const address = '1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3';

try {
  const utxos = await mnee.getAllUtxos(address);
  console.log('All UTXOs:', utxos);
  console.log('Total UTXOs found:', utxos.length);

  // Calculate total balance
  const totalBalance = utxos.reduce((sum, utxo) => sum + utxo.data.bsv21.amt, 0);
  console.log('Total balance (atomic):', totalBalance);
  console.log('Total balance (MNEE):', mnee.fromAtomicAmount(totalBalance));
} catch (error) {
  console.error('Error fetching UTXOs:', error.message);
}
```

### Parameters

| Parameter | Type     | Required | Description                         |
| --------- | -------- | -------- | ----------------------------------- |
| `address` | `string` | Yes      | The MNEE address to fetch UTXOs for |

### Response

The method returns a Promise that resolves to an array of `MNEEUtxo` objects containing all UTXOs for the specified address.

#### MNEEUtxo Structure

```typescript
type MNEEUtxo = {
  data: {
    bsv21: {
      amt: number; // Amount in atomic units
      dec: number; // Decimal places
      icon: string; // Token icon
      id: string; // Token ID
      op: string; // Operation type
      sym: string; // Token symbol
    };
    cosign: {
      address: string; // Cosigner address
      cosigner: string; // Cosigner identifier
    };
  };
  height: number; // Block height
  idx: number; // Transaction index
  outpoint: string; // Transaction outpoint (txid_vout)
  satoshis: number; // Satoshi amount
  script: string; // Script hex
  txid: string; // Transaction ID
  vout: number; // Output index
};
```

### Error Handling

The method handles various error scenarios gracefully:

```typescript
try {
  const utxos = await mnee.getAllUtxos(address);
  if (utxos.length === 0) {
    console.log('No UTXOs found for this address');
  }
} catch (error) {
  console.error('Error:', error.message);
  // Common errors: Invalid address, network issues, API key problems
}
```

### Performance Considerations

* **Complete Fetch**: Retrieves ALL UTXOs for the address, which may take longer for addresses with many UTXOs
* **Automatic Pagination**: Uses 25 UTXOs per page and automatically continues until all are fetched
* **Memory Usage**: Stores all UTXOs in memory - consider using `getUtxos` with pagination for very large UTXO sets
* **Network Intensive**: Makes multiple API calls for addresses with many UTXOs

### Use Cases

1. **Complete Balance Calculation**: Get exact total balance including all small UTXOs
2. **Wallet Display**: Show all available UTXOs in a wallet interface
3. **UTXO Management**: Analyze UTXO distribution and consolidation needs
4. **Audit Operations**: Verify all tokens associated with an address
5. **Advanced Transfer Planning**: Select optimal UTXOs for complex transactions

### Comparison with Related Methods

| Method           | Purpose               | Performance           | Use Case                |
| ---------------- | --------------------- | --------------------- | ----------------------- |
| `getAllUtxos`    | Fetch ALL UTXOs       | Slower for large sets | Complete wallet view    |
| `getEnoughUtxos` | Fetch just enough     | Faster for transfers  | Pre-transfer validation |
| `getUtxos`       | Fetch with pagination | Most flexible         | Custom pagination needs |

### Examples

#### Calculate Complete Balance

```typescript
async function getCompleteBalance(address: string) {
  try {
    const utxos = await mnee.getAllUtxos(address);

    const totalAtomic = utxos.reduce((sum, utxo) => sum + utxo.data.bsv21.amt, 0);
    const totalMnee = mnee.fromAtomicAmount(totalAtomic);

    return {
      address,
      totalUtxos: utxos.length,
      totalBalance: totalMnee,
      utxos,
    };
  } catch (error) {
    return {
      address,
      error: error.message,
      totalUtxos: 0,
      totalBalance: 0,
      utxos: [],
    };
  }
}
```

#### UTXO Analysis

```typescript
async function analyzeUtxos(address: string) {
  const utxos = await mnee.getAllUtxos(address);

  if (utxos.length === 0) {
    return { message: 'No UTXOs found' };
  }

  const amounts = utxos.map((utxo) => utxo.data.bsv21.amt);
  const totalBalance = amounts.reduce((sum, amt) => sum + amt, 0);
  const averageUtxo = totalBalance / utxos.length;
  const smallestUtxo = Math.min(...amounts);
  const largestUtxo = Math.max(...amounts);

  return {
    totalUtxos: utxos.length,
    totalBalance: mnee.fromAtomicAmount(totalBalance),
    averageUtxo: mnee.fromAtomicAmount(averageUtxo),
    smallestUtxo: mnee.fromAtomicAmount(smallestUtxo),
    largestUtxo: mnee.fromAtomicAmount(largestUtxo),
    consolidationNeeded: utxos.length > 100, // Suggest consolidation for many small UTXOs
  };
}
```

#### Find Specific UTXOs

```typescript
async function findLargeUtxos(address: string, minimumAmount: number) {
  const utxos = await mnee.getAllUtxos(address);
  const atomicMinimum = mnee.toAtomicAmount(minimumAmount);

  const largeUtxos = utxos.filter((utxo) => utxo.data.bsv21.amt >= atomicMinimum);

  return {
    found: largeUtxos.length,
    utxos: largeUtxos,
    totalValue: mnee.fromAtomicAmount(largeUtxos.reduce((sum, utxo) => sum + utxo.data.bsv21.amt, 0)),
  };
}
```

### When NOT to Use

* **Simple balance checks** - Use [`balance`](/mnee-sdk/check-balance) method instead
* **Transfer preparation** - Use [`getEnoughUtxos`](/mnee-sdk/get-enough-utxos) for better performance
* **Large UTXO sets** - Consider [`getUtxos`](/mnee-sdk/get-utxos) with pagination for better memory management
* **Real-time operations** - May be too slow for time-sensitive operations

### See Also

* [Balance](/mnee-sdk/check-balance) - Get balance without UTXO details (more efficient for balance checks)
* [Transfer Multi](/mnee-sdk/transfer-multi) - Use UTXOs for multi-source transfers
* [Get Enough UTXOs](/mnee-sdk/get-enough-utxos) - Get just enough UTXOs for and address and amount
* [Get All UTXOs](/mnee-sdk/get-all-utxos) - Get all UTXOs for a given address


# Transfer

The `transfer` method creates and optionally broadcasts MNEE token transfers. It handles all the complexity of creating valid MNEE transactions, including UTXO selection, fee calculation, and cosigner authorization.

### Usage

#### Basic Transfer

```typescript
const recipients = [{ address: 'recipient-address', amount: 2.55 }];
const wif = 'sender-wif-key';

const response = await mnee.transfer(recipients, wif);
console.log('Ticket ID:', response.ticketId);
```

#### Multiple Recipients

```typescript
const recipients = [
  { address: 'recipient-1-address', amount: 2.55 },
  { address: 'recipient-2-address', amount: 5 },
  { address: 'recipient-3-address', amount: 0.75 },
];
const wif = 'sender-wif-key';

const response = await mnee.transfer(recipients, wif);
console.log('Ticket ID:', response.ticketId);

// Check status of the transfer
const status = await mnee.getTxStatus(response.ticketId);
console.log('Status:', status);
```

#### Create Without Broadcasting

```typescript
const recipients = [{ address: 'recipient-address', amount: 10 }];

// Set broadcast to false to create but not submit
const response = await mnee.transfer(recipients, wif, { broadcast: false });
console.log('Raw transaction:', response.rawtx);
// Ticket ID will not be available when broadcast is false
```

#### Transfer with Webhook Callback

```typescript
const recipients = [{ address: 'recipient-address', amount: 10 }];

// Provide webhook URL for async status updates
const response = await mnee.transfer(recipients, wif, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook/mnee',
  extraData: { type: 'utf8', data: 'your-custom-data'}
});

console.log('Ticket ID:', response.ticketId);
// Your webhook will receive status updates as the transaction progresses
```

### Parameters

* **request**: Array of `SendMNEE` objects, each containing:
  * **address**: Recipient Bitcoin address
  * **amount**: Amount to send in MNEE (not atomic units)
* **wif**: Wallet Import Format private key of the sender
* **transferOptions** (optional): Object containing:
  * **broadcast**: Whether to broadcast the transaction (default: `true`)
  * **callbackUrl**: Webhook URL for status updates (only when broadcast is true)
  * **extraData**: Attach custom metadata in an `OP_RETURN` output. Can be a single object or an array of objects, each with a `type` (`'utf8'` or `'hex'`) and a `data` (string) property.

### Response

Returns a `TransferResponse` object:

```typescript
{
  ticketId?: string;  // Ticket ID for tracking (only if broadcast is true)
  rawtx?: string;     // The raw transaction hex (only if broadcast is false)
}
```

### Common Use Cases

#### Simple Payment

```typescript
async function payInvoice(recipientAddress, amountMNEE, senderWif) {
  try {
    const response = await mnee.transfer([{ address: recipientAddress, amount: amountMNEE }], senderWif);
    console.log(`Payment sent! Ticket: ${response.ticketId}`);

    // Get transaction ID from status
    const status = await mnee.getTxStatus(response.ticketId);
    return status.tx_id;
  } catch (error) {
    console.error('Payment failed:', error.message);
    throw error;
  }
}
```

#### Batch Payments

```typescript
async function distributePayments(payments, senderWif) {
  // payments is array of {address, amount}
  try {
    const response = await mnee.transfer(payments, senderWif);
    console.log(`Distributed to ${payments.length} recipients`);
    console.log(`Ticket ID: ${response.ticketId}`);

    // Log each payment
    payments.forEach((p) => {
      console.log(`  - ${p.address}: ${p.amount} MNEE`);
    });

    return response.ticketId;
  } catch (error) {
    console.error('Distribution failed:', error.message);
    throw error;
  }
}
```

#### Two-Step Transfer with Validation

```typescript
async function secureTransfer(recipients, wif) {
  // Step 1: Create transaction without broadcasting
  const txResponse = await mnee.transfer(recipients, wif, { broadcast: false });

  // Step 2: Validate the transaction
  const isValid = await mnee.validateMneeTx(txResponse.rawtx, recipients);
  if (!isValid) {
    throw new Error('Transaction validation failed');
  }

  // Step 3: Parse to review
  const parsed = await mnee.parseTxFromRawTx(txResponse.rawtx);
  console.log('Transaction details:', parsed);

  // Step 4: Broadcast if everything looks good
  const submitResponse = await mnee.submitRawTx(txResponse.rawtx);
  return submitResponse.ticketId;
}
```

#### Transfer with Balance Check

```typescript
async function safeTransfer(recipients, wif, senderAddress) {
  // Calculate total needed
  const totalNeeded = recipients.reduce((sum, r) => sum + r.amount, 0);

  // Check balance
  const balance = await mnee.balance(senderAddress);
  if (balance.decimalAmount < totalNeeded) {
    throw new Error(`Insufficient balance. Have ${balance.decimalAmount}, need ${totalNeeded} MNEE`);
  }

  // Proceed with transfer
  const response = await mnee.transfer(recipients, wif);
  console.log(`Transfer complete: ${response.ticketId}`);

  // Get transaction ID
  const status = await mnee.getTxStatus(response.ticketId);
  return status.tx_id;
}
```

#### Micro-Payment Channel

```typescript
async function sendMicroPayment(address, amount, wif) {
  const MIN_AMOUNT = 0.001; // 0.001 MNEE minimum

  if (amount < MIN_AMOUNT) {
    throw new Error(`Amount too small. Minimum is ${MIN_AMOUNT} MNEE`);
  }

  const response = await mnee.transfer([{ address, amount }], wif);

  // Get transaction ID from status
  const status = await mnee.getTxStatus(response.ticketId);

  return {
    txid: status.tx_id,
    ticketId: response.ticketId,
    amount: amount,
    timestamp: new Date().toISOString(),
  };
}
```

### Error Handling

The transfer method can throw several specific errors:

```typescript
try {
  const response = await mnee.transfer(recipients, wif);
} catch (error) {
  switch (true) {
    case error.message.includes('Config not fetched'):
      console.error('Failed to fetch cosigner configuration');
      break;
    case error.message.includes('Invalid transfer options'):
      console.error('Invalid recipients or amounts');
      break;
    case error.message.includes('Private key not found'):
      console.error('Invalid WIF private key');
      break;
    case error.message.includes('Invalid amount'):
      console.error('Amount must be greater than 0');
      break;
    case error.message.includes('Insufficient MNEE balance'):
      console.error('Not enough MNEE tokens');
      break;
    case error.message.includes('Failed to broadcast transaction'):
      console.error('Cosigner rejected the transaction');
      break;
    case error.message.includes('Invalid API key'):
      console.error('API key authentication failed (401/403)');
      break;
    case error.message.includes('HTTP error! status:'):
      console.error('API request failed:', error.message);
      break;
    default:
      console.error('Transfer failed:', error.message);
  }
}
```

### Important Notes

* Amounts are specified in MNEE, not atomic units (1 MNEE = 100,000 atomic units)
* The method automatically:
  * Selects appropriate UTXOs
  * Calculates fees based on transaction size
  * Adds change output if needed
  * Obtains cosigner authorization
* Minimum transfer amount is determined by dust limit (check via `config()`)
* All recipients must have valid Bitcoin addresses
* The sender must have sufficient balance to cover amounts + fees
* When broadcast is true, the transaction is processed asynchronously and you receive a ticketId to track status

### See Also

* [Transfer Multi](/mnee-sdk/transfer-multi) - Advanced transfers with UTXO control
* [Submit Raw Transaction](/mnee-sdk/submit-raw-tx) - Broadcast pre-created transactions
* [Get Transaction Status](/mnee-sdk/get-transaction-status) - Track transaction status
* [Transfer Webhooks](/mnee-sdk/transfer-webhooks) - Webhook callbacks for async updates
* [Validate Transaction](/utils/validate) - Validate before broadcasting
* [Check Balance](/mnee-sdk/check-balance) - Verify sufficient funds


# Transfer Multi

The `transferMulti` method enables advanced MNEE transfers using multiple source UTXOs with different private keys. This method provides full control over which UTXOs to spend and is essential for complex wallet operations like consolidation, HD wallet transfers, and multi-signature scenarios.

### Usage

#### Basic Multi-Source Transfer

```typescript
const options = {
  inputs: [
    { txid: 'abc123...', vout: 0, wif: 'L1PrivateKey...' },
    { txid: 'def456...', vout: 1, wif: 'L2PrivateKey...' }
  ],
  recipients: [
    { address: '1DestinationAddress...', amount: 100 }
  ],
  changeAddress: '1ChangeAddress...'
};

const response = await mnee.transferMulti(options);
console.log('Ticket ID:', response.ticketId);

// Check transaction status
const status = await mnee.getTxStatus(response.ticketId);
console.log('Transaction ID:', status.tx_id);
```

#### Multiple Change Addresses

```typescript
const options = {
  inputs: [
    { txid: 'abc123...', vout: 0, wif: 'L1...' },
    { txid: 'def456...', vout: 1, wif: 'L2...' },
    { txid: 'ghi789...', vout: 0, wif: 'L3...' }
  ],
  recipients: [
    { address: '1Recipient...', amount: 50 }
  ],
  changeAddress: [
    { address: '1Change1...', amount: 30 },
    { address: '1Change2...', amount: 20 }
  ]
};

const response = await mnee.transferMulti(options);
console.log('Ticket ID:', response.ticketId);
```

#### Transfer with Webhook Callback

```typescript
const options = {
  inputs: [
    { txid: 'abc123...', vout: 0, wif: 'L1...' },
    { txid: 'def456...', vout: 1, wif: 'L2...' }
  ],
  recipients: [
    { address: '1Recipient...', amount: 75 }
  ]
};

// Add webhook for async status updates
const response = await mnee.transferMulti(options, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook',
  extraData: [{ type: 'utf8', data: 'your-custom-data'}, { type: 'utf8', data: 'your-custom-data'}],
});

console.log('Ticket ID:', response.ticketId);
// Your webhook will receive status updates
```

### Parameters

#### TransferMultiOptions

* **inputs**: Array of input UTXOs to spend
  * **txid**: Transaction ID of the UTXO
  * **vout**: Output index within the transaction
  * **wif**: Private key (WIF format) that controls this UTXO
* **recipients**: Array of `SendMNEE` objects for destinations
  * **address**: Recipient address
  * **amount**: Amount in MNEE
* **changeAddress** (optional): Where to send change
  * Can be a single address (string)
  * Or array of addresses with specific amounts

#### TransferOptions (second parameter, optional)

* **broadcast**: Whether to broadcast the transaction (default: `true`)
* **callbackUrl**: Webhook URL for status updates (only when broadcast is true)
* **extraData**: Attach custom metadata in an `OP_RETURN` output. Can be a single object or an array of objects, each with a `type` (`'utf8'` or `'hex'`) and a `data` (string) property.

### Response

Returns a `TransferResponse` object:

```typescript
{
  ticketId?: string;  // Ticket ID for tracking (only if broadcast is true)
  rawtx?: string;     // The raw transaction hex (only if broadcast is false)
}
```

### Common Use Cases

#### UTXO Consolidation

```typescript
async function consolidateUTXOs(address, wif) {
  // Get all UTXOs for the address
  const utxos = await mnee.getAllUtxos(address);
  
  // Calculate total amount
  const totalAmount = utxos.reduce((sum, utxo) => 
    sum + utxo.data.bsv21.amt, 0
  );
  const totalMNEE = mnee.fromAtomicAmount(totalAmount);
  
  // Prepare inputs
  const inputs = utxos.map(utxo => ({
    txid: utxo.outpoint.split(':')[0],
    vout: parseInt(utxo.outpoint.split(':')[1]),
    wif: wif
  }));
  
  // Send all to same address (minus estimated fee)
  const response = await mnee.transferMulti({
    inputs,
    recipients: [{ address, amount: totalMNEE - 0.001 }], // Leave some for fee
    changeAddress: address
  });
  
  console.log(`Consolidated ${utxos.length} UTXOs into 1`);
  
  // Get transaction ID from status
  const status = await mnee.getTxStatus(response.ticketId);
  return status.tx_id;
}
```

#### HD Wallet Transfer

```typescript
async function hdWalletTransfer(hdWallet, recipients, totalAmount) {
  // Find addresses with balance
  const addresses = [];
  const wifs = {};
  let collectedAmount = 0;
  
  for (let i = 0; collectedAmount < totalAmount && i < 100; i++) {
    const derived = hdWallet.deriveAddress(i, false);
    const balance = await mnee.balance(derived.address);
    
    if (balance.decimalAmount > 0) {
      addresses.push(derived.address);
      wifs[derived.address] = derived.wif;
      collectedAmount += balance.decimalAmount;
    }
  }
  
  // Get UTXOs for all addresses (specify size to get all)
  const allUtxos = await mnee.getUtxos(addresses, 0, 1000);
  
  // Prepare inputs
  const inputs = allUtxos.map(utxo => ({
    txid: utxo.outpoint.split(':')[0],
    vout: parseInt(utxo.outpoint.split(':')[1]),
    wif: wifs[utxo.owners[0]]
  }));
  
  // Create transfer
  const response = await mnee.transferMulti({
    inputs,
    recipients,
    changeAddress: hdWallet.deriveAddress(0, true).address // change address
  });
  
  // Wait for confirmation
  const status = await mnee.getTxStatus(response.ticketId);
  return status.tx_id;
}
```

#### Multi-Wallet Aggregation

```typescript
async function aggregateFromMultipleWallets(wallets, destinationAddress) {
  const allInputs = [];
  let totalAmount = 0;
  
  // Collect UTXOs from each wallet
  for (const wallet of wallets) {
    const utxos = await mnee.getUtxos(wallet.address);
    
    for (const utxo of utxos) {
      allInputs.push({
        txid: utxo.outpoint.split(':')[0],
        vout: parseInt(utxo.outpoint.split(':')[1]),
        wif: wallet.wif
      });
      totalAmount += utxo.data.bsv21.amt;
    }
  }
  
  const totalMNEE = mnee.fromAtomicAmount(totalAmount);
  
  // Transfer all to destination
  const response = await mnee.transferMulti({
    inputs: allInputs,
    recipients: [{ 
      address: destinationAddress, 
      amount: totalMNEE - 0.002 // Leave room for fees
    }]
  });
  
  console.log(`Aggregated from ${wallets.length} wallets`);
  
  // Wait for transaction to be broadcast
  const status = await mnee.getTxStatus(response.ticketId);
  return status.tx_id;
}
```

#### Distributed Change

```typescript
async function transferWithDistributedChange(inputs, recipient, changeAddresses) {
  // Calculate total input amount
  let totalInput = 0;
  for (const input of inputs) {
    // You'd need to look up UTXO amounts
    const utxo = await getUTXODetails(input.txid, input.vout);
    totalInput += utxo.amount;
  }
  
  const totalInputMNEE = mnee.fromAtomicAmount(totalInput);
  const changeAmount = totalInputMNEE - recipient.amount - 0.002; // fees
  
  // Distribute change evenly
  const changePerAddress = changeAmount / changeAddresses.length;
  const changeOutputs = changeAddresses.map(addr => ({
    address: addr,
    amount: changePerAddress
  }));
  
  const response = await mnee.transferMulti({
    inputs,
    recipients: [recipient],
    changeAddress: changeOutputs
  });
  
  return response;
}
```

#### Specific UTXO Selection

```typescript
async function spendSpecificUTXOs(utxoList, recipient) {
  // utxoList contains specific UTXOs to spend
  const inputs = utxoList.map(utxo => ({
    txid: utxo.txid,
    vout: utxo.vout,
    wif: utxo.wif
  }));
  
  const response = await mnee.transferMulti({
    inputs,
    recipients: [recipient]
  }, { broadcast: false }); // Create but don't broadcast
  
  // Validate before broadcasting
  const isValid = await mnee.validateMneeTx(response.rawtx);
  if (isValid) {
    const result = await mnee.submitRawTx(response.rawtx);
    
    // Wait for confirmation
    const status = await mnee.getTxStatus(result.ticketId);
    return status.tx_id;
  }
  
  throw new Error('Transaction validation failed');
}
```

### Important Notes

* Each input must have its own WIF (private key)
* The method does NOT automatically select UTXOs - you must specify exact inputs
* Total input amount must cover recipients + fees
* Change calculation is manual unless using single change address
* When using multiple change addresses, ensure amounts are specified correctly
* Fees are automatically calculated and deducted from outputs

### Error Handling

The transferMulti method can throw several specific errors:

```typescript
try {
  const response = await mnee.transferMulti(options);
} catch (error) {
  switch (true) {
    case error.message.includes('Config not fetched'):
      console.error('Failed to fetch cosigner configuration');
      break;
    case error.message.includes('Invalid transfer options'):
      console.error('Invalid options structure');
      break;
    case error.message.includes('Invalid amount'):
      console.error('Total recipient amount must be greater than 0');
      break;
    case error.message.includes('Insufficient MNEE balance'):
      console.error('Input UTXOs don\'t cover output amounts + fees');
      break;
    case error.message.includes('Failed to broadcast transaction'):
      console.error('Cosigner rejected the transaction');
      break;
    case error.message.includes('Invalid API key'):
      console.error('API key authentication failed (401/403)');
      break;
    case error.message.includes('Duplicate UTXO'):
      console.error('Same UTXO used multiple times in inputs');
      break;
    case error.message.includes('Invalid WIF'):
      console.error('One or more private keys are invalid');
      break;
    case error.message.includes('Failed to fetch UTXO'):
      console.error('One or more input UTXOs not found or already spent');
      break:
    case error.message.includes('HTTP error! status:'):
      console.error('API request failed:', error.message);
      break;
    default:
      console.error('Transfer failed:', error.message);
  }
}
```

### See Also

* [Transfer](/mnee-sdk/transfer) - Simple transfers with automatic UTXO selection
* [Get UTXOs](/mnee-sdk/get-utxos) - Find available UTXOs to spend
* [Get Transaction Status](/mnee-sdk/get-transaction-status) - Track transaction status
* [Transfer Webhooks](/mnee-sdk/transfer-webhooks) - Webhook callbacks for async updates
* [Submit Raw Transaction](/mnee-sdk/submit-raw-tx) - Broadcast created transactions


# Submit Raw Tx

The `submitRawTx` method submits a pre-signed raw transaction to the MNEE network for asynchronous processing. This is useful when you have a transaction that was created offline, received from another service, or created with `broadcast: false` and need to broadcast it later.

### Usage

```typescript
const rawTxHex = '0100000001...'; // Your signed raw transaction hex
const result = await mnee.submitRawTx(rawTxHex);
console.log('Ticket ID:', result.ticketId);

// Check transaction status
const status = await mnee.getTxStatus(result.ticketId);
console.log('Transaction ID:', status.tx_id);
```

#### With Webhook Callback

```typescript
const rawTxHex = '0100000001...'; // Your signed raw transaction hex

// Submit with webhook for async status updates
const result = await mnee.submitRawTx(rawTxHex, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook',
  extraData: { type: 'utf8', data: 'your-custom-data'}
});

console.log('Ticket ID:', result.ticketId);
// Your webhook will receive status updates as the transaction progresses
```

### Parameters

* **rawTxHex**: The complete, signed raw transaction in hexadecimal format
* **transferOptions** (optional): Object containing:
  * **broadcast**: Whether to broadcast the transaction (default: `true`)
  * **callbackUrl**: Webhook URL for status updates (only when broadcast is true)
  * **extraData**: Attach custom metadata in an `OP_RETURN` output. Can be a single object or an array of objects, each with a `type` (`'utf8'` or `'hex'`) and a `data` (string) property.

### Response

Returns a `TransferResponse` object:

```typescript
{
  ticketId?: string;  // Ticket ID for tracking (only if broadcast is true)
  rawtx?: string;     // The raw transaction hex (only if broadcast is false)
}
```

### Common Use Cases

#### Delayed Broadcasting

```typescript
async function createAndHoldTransaction(recipients, wif) {
  // Create transaction without broadcasting
  const created = await mnee.transfer(recipients, wif, { broadcast: false });
  
  // Store for later (database, file, etc.)
  await saveTransaction(created.rawtx);
  
  // Later, when ready to broadcast
  const savedTx = await loadTransaction();
  const result = await mnee.submitRawTx(savedTx);
  
  console.log('Transaction ticket:', result.ticketId);
  
  // Wait for confirmation
  let status;
  do {
    status = await mnee.getTxStatus(result.ticketId);
    await new Promise(resolve => setTimeout(resolve, 2000));
  } while (status.status === 'BROADCASTING');
  
  if (status.status === 'SUCCESS' || status.status === 'MINED') {
    return status.tx_id;
  } else {
    throw new Error('Transaction failed');
  }
}
```

#### Transaction Queue System

```typescript
class TransactionQueue {
  constructor(mnee) {
    this.mnee = mnee;
    this.queue = [];
  }
  
  async addTransaction(rawTx) {
    this.queue.push({
      rawTx,
      added: new Date(),
      status: 'pending'
    });
  }
  
  async processQueue() {
    for (const tx of this.queue) {
      if (tx.status === 'pending') {
        try {
          const result = await this.mnee.submitRawTx(tx.rawTx);
          tx.status = 'submitted';
          tx.ticketId = result.ticketId;
          tx.submitTime = new Date();
          
          console.log(`Submitted: ${result.ticketId}`);
          
          // Check status asynchronously
          this.checkStatus(tx.ticketId).then(status => {
            if (status.status === 'SUCCESS' || status.status === 'MINED') {
              tx.status = 'confirmed';
              tx.txid = status.tx_id;
            } else if (status.status === 'FAILED') {
              tx.status = 'failed';
              tx.error = status.errors;
            }
          });
        } catch (error) {
          tx.status = 'failed';
          tx.error = error.message;
          console.error(`Failed: ${error.message}`);
        }
      }
    }
  }
  
  async checkStatus(ticketId) {
    return await this.mnee.getTxStatus(ticketId);
  }
}
```

#### Multi-Stage Approval Process

```typescript
async function multiStageTransfer(recipients, wif) {
  // Stage 1: Create
  const created = await mnee.transfer(recipients, wif, { broadcast: false });
  
  // Stage 2: Validate
  const isValid = await mnee.validateMneeTx(created.rawtx, recipients);
  if (!isValid) {
    throw new Error('Transaction validation failed');
  }
  
  // Stage 3: Review
  const parsed = await mnee.parseTxFromRawTx(created.rawtx);
  const approved = await getApproval(parsed);
  
  if (!approved) {
    throw new Error('Transaction not approved');
  }
  
  // Stage 4: Submit
  const result = await mnee.submitRawTx(created.rawtx);
  
  // Stage 5: Confirm
  let status;
  do {
    status = await mnee.getTxStatus(result.ticketId);
    if (status.status === 'FAILED') {
      throw new Error('Transaction failed: ' + status.errors);
    }
    await new Promise(resolve => setTimeout(resolve, 2000));
  } while (status.status === 'BROADCASTING');
  
  return status.tx_id;
}
```

#### Retry Failed Broadcasts

```typescript
async function submitWithRetry(rawTxHex, maxRetries = 3) {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const result = await mnee.submitRawTx(rawTxHex);
      console.log(`Success on attempt ${i + 1}: ${result.ticketId}`);
      
      // Wait for confirmation
      let status;
      let attempts = 0;
      while (attempts < 30) {
        status = await mnee.getTxStatus(result.ticketId);
        
        if (status.status === 'SUCCESS' || status.status === 'MINED') {
          return status.tx_id;
        }
        
        if (status.status === 'FAILED') {
          throw new Error('Transaction failed: ' + status.errors);
        }
        
        await new Promise(resolve => setTimeout(resolve, 2000));
        attempts++;
      }
      
      throw new Error('Transaction timeout after 60 seconds');
    } catch (error) {
      lastError = error;
      console.log(`Attempt ${i + 1} failed: ${error.message}`);
      
      // Wait before retry (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
    }
  }
  
  throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}
```

#### External Wallet Integration

```typescript
async function integrateExternalWallet(externalWalletAPI) {
  // Get signed transaction from external wallet
  const signedTx = await externalWalletAPI.createMNEETransfer({
    to: 'recipient-address',
    amount: 10
  });
  
  // Validate before submitting
  const isValid = await mnee.validateMneeTx(signedTx);
  if (!isValid) {
    throw new Error('External wallet created invalid transaction');
  }
  
  // Submit to network with webhook
  const result = await mnee.submitRawTx(signedTx, {
    callbackUrl: 'https://your-api.com/webhook'
  });
  
  // Wait for confirmation
  let status;
  do {
    status = await mnee.getTxStatus(result.ticketId);
    await new Promise(resolve => setTimeout(resolve, 2000));
  } while (status.status === 'BROADCASTING');
  
  if (status.status === 'FAILED') {
    throw new Error('Transaction failed: ' + status.errors);
  }
  
  // Notify external wallet of success
  await externalWalletAPI.confirmBroadcast(status.tx_id);
  
  return status.tx_id;
}
```

### Error Handling

The submitRawTx method can throw several specific errors:

```typescript
try {
  const result = await mnee.submitRawTx(rawTxHex);
  console.log('Success:', result.ticketId);
} catch (error) {
  switch (error.message) {
    case 'Raw transaction is required':
      console.error('No transaction hex provided');
      break;
    case 'Callback URL cannot be provided when broadcast is false':
      console.error('Cannot use webhook without broadcasting');
      break;
    case 'Failed to submit transaction':
      console.error('Submission to network failed');
      break;
    case 'Invalid API key':
      console.error('API key authentication failed (401/403)');
      break;
    default:
      if (error.message.includes('HTTP error! status:')) {
        console.error('API request failed:', error.message);
      } else {
        console.error('Submit failed:', error.message);
      }
  }
}
```

### Important Notes

* The transaction must be completely signed before submission
* The transaction must be valid according to MNEE protocol rules
* Once broadcast, transactions cannot be reversed
* If a transaction has already been broadcast, submitting again will fail
* Transactions are processed asynchronously - a ticketId is returned immediately for tracking
* Use `getTxStatus` to check if the transaction was successfully broadcast to the network
* Webhook callbacks provide real-time status updates without polling
* The transaction ID is only available after the status reaches SUCCESS

### See Also

* [Transfer](/mnee-sdk/transfer) - Create and broadcast transactions
* [Transfer Multi](/mnee-sdk/transfer-multi) - Create complex transactions
* [Get Transaction Status](/mnee-sdk/get-transaction-status) - Track transaction status
* [Transfer Webhooks](/mnee-sdk/transfer-webhooks) - Webhook callbacks for async updates
* [Validate Transaction](/utils/validate) - Validate before submitting
* [Parse Transaction](/utils/transaction-parsing) - Examine transaction details


# Get Transaction Status

The `getTxStatus` method retrieves the current status of a transaction that was submitted asynchronously. When you submit a transaction using `transfer`, `transferMulti`, or `submitRawTx` with `broadcast: true`, you receive a `ticketId` that can be used to track the transaction's progress.

### Usage

#### Basic Status Check

```typescript
const ticketId = '5d4b9bfb-4dee-4f9b-bb0c-6b068572fae3'; // From transfer response
const status = await mnee.getTxStatus(ticketId); 
console.log('Transaction status:', status.status); console.log('Transaction ID:', status.tx_id);
```

#### Poll Until Complete

```typescript
async function waitForTransaction(ticketId) {
  let status;
  let attempts = 0;
  const maxAttempts = 30; // 60 seconds with 2-second intervals
  
  while (attempts < maxAttempts) {
    status = await mnee.getTxStatus(ticketId);
    
    if (status.status === 'SUCCESS' || status.status === 'MINED') {
      console.log('Transaction confirmed:', status.tx_id);
      return status;
    }
    
    if (status.status === 'FAILED') {
      throw new Error(`Transaction failed: ${status.errors}`);
    }
    
    // Still broadcasting, wait and retry
    await new Promise(resolve => setTimeout(resolve, 2000));
    attempts++;
  }
  
  throw new Error('Transaction timeout after 60 seconds');
}
```

### Parameters

* **ticketId**: The ticket ID returned from a transfer or submitRawTx operation

### Response

Returns a `TransferStatus` object:

```json5
{
  id: string;              // The ticket ID
  tx_id: string;           // The blockchain transaction ID
  tx_hex: string;          // The raw transaction hex
  action_requested: 'transfer';  // The requested action
  status: 'BROADCASTING' | 'SUCCESS' | 'MINED' | 'FAILED';
  createdAt: string;       // ISO timestamp when ticket was created
  updatedAt: string;       // ISO timestamp of last update
  errors: string | null;   // Error details if status is FAILED
}
```

### Status Values

* **BROADCASTING**: Transaction is being broadcast to the network
* **SUCCESS**: Transaction successfully broadcast and accepted by the network
* **MINED**: Transaction has been confirmed in a block
* **FAILED**: Transaction failed (check `errors` field for details)

### Common Use Cases

#### After Transfer

```typescript
async function transferAndWait(recipients, wif) {
  // Initiate transfer
  const response = await mnee.transfer(recipients, wif);
  console.log('Transfer initiated:', response.ticketId);
  
  // Wait for confirmation
  let status;
  do {
    status = await mnee.getTxStatus(response.ticketId);
    console.log('Current status:', status.status);
    
    if (status.status === 'FAILED') {
      throw new Error(`Transfer failed: ${status.errors}`);
    }
    
    await new Promise(resolve => setTimeout(resolve, 2000));
  } while (status.status === 'BROADCASTING');
  
  console.log('Transaction ID:', status.tx_id);
  return status.tx_id;
}
```

#### With Timeout and Retry

```typescript
async function getTxWithRetry(ticketId, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const status = await mnee.getTxStatus(ticketId);
      return status;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      // Wait before retry (exponential backoff)
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
    }
  }
}
```

#### Batch Status Checking

```typescript
async function checkMultipleTransactions(ticketIds) {
  const results = await Promise.allSettled(
    ticketIds.map(id => mnee.getTxStatus(id))
  );
  
  const statuses = results.map((result, index) => {
    if (result.status === 'fulfilled') {
      return {
        ticketId: ticketIds[index],
        status: result.value.status,
        txId: result.value.tx_id,
        errors: result.value.errors
      };
    } else {
      return {
        ticketId: ticketIds[index],
        status: 'ERROR',
        error: result.reason.message
      };
    }
  });
  
  // Group by status
  const grouped = {
    broadcasting: statuses.filter(s => s.status === 'BROADCASTING'),
    success: statuses.filter(s => s.status === 'SUCCESS'),
    mined: statuses.filter(s => s.status === 'MINED'),
    failed: statuses.filter(s => s.status === 'FAILED'),
    error: statuses.filter(s => s.status === 'ERROR')
  };
  
  console.log('Status summary:', {
    broadcasting: grouped.broadcasting.length,
    success: grouped.success.length,
    mined: grouped.mined.length,
    failed: grouped.failed.length,
    error: grouped.error.length
  });
  
  return grouped;
}
```

#### Transaction Monitor

```typescript
class TransactionMonitor {
  constructor(mnee) {
    this.mnee = mnee;
    this.monitoring = new Map();
  }
  
  async monitor(ticketId, callback, intervalMs = 2000) {
    if (this.monitoring.has(ticketId)) {
      console.log('Already monitoring:', ticketId);
      return;
    }
    
    const interval = setInterval(async () => {
      try {
        const status = await this.mnee.getTxStatus(ticketId);
        
        // Notify callback of status change
        callback(ticketId, status);
        
        // Stop monitoring if transaction is complete
        if (['SUCCESS', 'MINED', 'FAILED'].includes(status.status)) {
          this.stop(ticketId);
        }
      } catch (error) {
        console.error(`Error checking ${ticketId}:`, error);
        callback(ticketId, { status: 'ERROR', error: error.message });
        this.stop(ticketId);
      }
    }, intervalMs);
    
    this.monitoring.set(ticketId, interval);
  }
  
  stop(ticketId) {
    const interval = this.monitoring.get(ticketId);
    if (interval) {
      clearInterval(interval);
      this.monitoring.delete(ticketId);
      console.log('Stopped monitoring:', ticketId);
    }
  }
  
  stopAll() {
    for (const [ticketId, interval] of this.monitoring) {
      clearInterval(interval);
    }
    this.monitoring.clear();
    console.log('Stopped all monitoring');
  }
}

// Usage
const monitor = new TransactionMonitor(mnee);

monitor.monitor(ticketId, (id, status) => {
  console.log(`${id}: ${status.status}`);
  if (status.status === 'SUCCESS') {
    console.log('Transaction successful!', status.tx_id);
  }
});

```

### Error Handling

```typescript
try {
  const status = await mnee.getTxStatus(ticketId);
  
  if (status.status === 'FAILED') {
    // Handle based on error type
    if (status.errors?.includes('Insufficient')) {
      console.error('Not enough funds');
    } else if (status.errors?.includes('Invalid')) {
      console.error('Invalid transaction');
    } else {
      console.error('Transaction failed:', status.errors);
    }
  }
} catch (error) {
  if (error.message === 'Invalid API key') {
    console.error('API authentication failed');
  } else if (error.message.includes('Ticket not found')) {
    console.error('Invalid ticket ID');
  } else {
    console.error('Failed to get status:', error.message);
  }
}
```

### Important Notes

* The `tx_id` field will be empty until the transaction reaches SUCCESS status
* Tickets expire after a certain period - check status promptly after submission
* Status changes are one-way: BROADCASTING → SUCCESS → MINED (or → FAILED)
* Once a status reaches SUCCESS, MINED, or FAILED, it will not change
* For real-time updates without polling, use webhook callbacks when submitting transactions

### See Also

* [Transfer](/mnee-sdk/transfer) - Create and broadcast transactions
* [Transfer Multi](/mnee-sdk/transfer-multi) - Advanced transfers with multiple inputs
* [Submit Raw Transaction](/mnee-sdk/submit-raw-tx) - Submit pre-signed transactions
* Transfer Webhooks - Real-time status updates via webhooks


# Transfer Webhooks

The MNEE SDK supports webhook callbacks for asynchronous transaction status updates. When you provide a \`callbackUrl\` in your transfer options, the MNEE API will send real-time status updates to your webhook endpoint as the transaction progresses through various states.&#x20;

### How It Works

When you initiate a transfer with a webhook URL, the API will:

1. Accept your transaction and return a `ticketId` immediately
2. Process the transaction asynchronously
3. Send POST requests to your webhook URL as the transaction status changes
4. Continue sending updates until the transaction reaches a final state (SUCCESS, MINED, or FAILED)

### Webhook Response Format

Your webhook endpoint will receive a POST request with the following `TransferWebhookResponse` payload:

```json5
{
  id: string;              // The ticket ID for this transaction
  tx_id: string;           // The blockchain transaction ID
  tx_hex: string;          // The raw transaction hex
  action_requested: 'transfer';  // Always 'transfer' for MNEE transactions
  callback_url: string;    // Your webhook URL (for verification)
  status: 'BROADCASTING' | 'SUCCESS' | 'MINED' | 'FAILED';
  createdAt: string;       // ISO timestamp when ticket was created
  updatedAt: string;       // ISO timestamp of this update
  errors: string | null;   // Error details if status is FAILED
}
```

### Status Flow

Transactions typically progress through these states:

* **BROADCASTING** → Transaction is being broadcast to the network
* **SUCCESS** → Transaction successfully broadcast and accepted by the network
* **MINED** → Transaction has been mined into a block
* **FAILED** → Transaction failed (check `errors` field for details)

### Usage Examples

#### Basic Transfer With Webhook

```typescript
const options = {
  inputs: [
    { txid: 'abc...', vout: 0, wif: 'wif1' },
    { txid: 'def...', vout: 1, wif: 'wif2' }
  ],
  recipients: [
    { address: 'address1', amount: 5.0 },
    { address: 'address2', amount: 3.5 }
  ]
};

const response = await mnee.transferMulti(options, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook'
});

console.log('Multi-transfer submitted:', response.ticketId);
```

#### Submit Raw Transaction With Webhook

```typescript
const rawTxHex = '0100000001...'; // Your signed transaction

const response = await mnee.submitRawTx(rawTxHex, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook'
});

console.log('Raw transaction submitted:', response.ticketId);
```

### Implementing a Webhook Endpoint

#### Express.js Example

```typescript
import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhook', async (req, res) => {
  const webhookData = req.body;
  
  console.log(`Transaction ${webhookData.id} status: ${webhookData.status}`);
  
  switch (webhookData.status) {
    case 'BROADCASTING':
      // Transaction is being broadcast
      await updateDatabase(webhookData.id, 'broadcasting');
      break;
      
    case 'SUCCESS':
      // Transaction successfully broadcast
      await updateDatabase(webhookData.id, 'success', webhookData.tx_id);
      await notifyUser(webhookData.id, 'Your transaction has been broadcast!');
      break;
      
    case 'MINED':
      // Transaction mined into a block
      await updateDatabase(webhookData.id, 'confirmed', webhookData.tx_id);
      await notifyUser(webhookData.id, 'Your transaction has been confirmed!');
      break;
      
    case 'FAILED':
      // Transaction failed
      await updateDatabase(webhookData.id, 'failed', null, webhookData.errors);
      await notifyUser(webhookData.id, `Transaction failed: ${webhookData.errors}`);
      break;
  }
  
  // Always respond with 200 to acknowledge receipt
  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});
```

### Best Practices

#### 1. Always Return 200 OK

Always return a 200 status code to acknowledge receipt, even if processing fails. This prevents the webhook from being retried unnecessarily.

```typescript
app.post('/webhook', async (req, res) => {
  try {
    await processWebhook(req.body);
  } catch (error) {
    // Log error but still return 200
    console.error('Webhook processing failed:', error);
  }
  
  res.status(200).json({ received: true });
});
```

#### 2. Implement Idempotency

Webhooks may be sent multiple times for the same status. Design your handler to be idempotent:

```typescript
async function processWebhook(data) {
  // Check if we've already processed this update
  const processed = await checkIfProcessed(data.id, data.status, data.updatedAt);
  if (processed) {
    console.log(`Already processed ${data.id} at status ${data.status}`);
    return;
  }
  
  // Process the update
  await updateTransactionStatus(data);
  
  // Mark as processed
  await markAsProcessed(data.id, data.status, data.updatedAt);
}
```

#### 3. Handle Timeouts Gracefully

Set up fallback polling for critical transactions in case webhooks fail:

```typescript
async function transferWithFallback(recipients, wif, webhookUrl) {
  const response = await mnee.transfer(recipients, wif, {
    broadcast: true,
    callbackUrl: webhookUrl
  });
  
  // Set up fallback polling after 30 seconds
  setTimeout(async () => {
    const status = await mnee.getTxStatus(response.ticketId);
    if (status.status === 'BROADCASTING') {
      // Webhook might have failed, start polling
      pollTransactionStatus(response.ticketId);
    }
  }, 30000);
  
  return response;
}
```

#### 4. Secure Your Endpoint

Implement security measures to protect your webhook endpoint:

```typescript
// Use a secret path
app.post('/webhook/' + process.env.WEBHOOK_SECRET, handler);

// Implement rate limiting
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 100 // limit to 100 requests per minute
});
app.use('/webhook', limiter);

// Validate payload structure
function validateWebhookPayload(data) {
  return (
    typeof data.id === 'string' &&
    typeof data.tx_id === 'string' &&
    typeof data.status === 'string' &&
    ['BROADCASTING', 'SUCCESS', 'MINED', 'FAILED'].includes(data.status)
  );
}
```

#### 5. Queue For Processing

For high-volume applications, queue webhook payloads for async processing:

```typescript
import Queue from 'bull';
const webhookQueue = new Queue('webhook-processing');

app.post('/webhook', async (req, res) => {
  // Immediately queue for processing
  await webhookQueue.add('process-webhook', req.body);
  
  // Return immediately
  res.status(200).json({ received: true });
});

// Process queue items
webhookQueue.process('process-webhook', async (job) => {
  const webhookData = job.data;
  await processWebhook(webhookData);
});
```

### Testing Webhooks

#### Local Development with ngrok

For local testing, use ngrok to expose your local server:

```bash
# Install ngrok
npm install -g ngrok

# Start your local server on port 3000
npm run dev

# In another terminal, expose port 3000
ngrok http 3000

# Use the ngrok URL as your webhook
# https://abc123.ngrok.io/webhook
```

#### Test Webhook Server

Create a simple test server to log webhook calls:

```typescript
// test-webhook-server.js
const express = require('express');
const app = express();

app.use(express.json());
app.use(express.text());

// Log all webhooks
app.all('*', (req, res) => {
  console.log('=== Webhook Received ===');
  console.log('Method:', req.method);
  console.log('Path:', req.path);
  console.log('Headers:', req.headers);
  console.log('Body:', req.body);
  console.log('========================');
  
  res.status(200).json({ received: true });
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Test webhook server listening on port ${port}`);
});
```

### Error Handling

#### Webhook Delivery Failures

If webhook delivery fails, you can still check transaction status using the ticket ID:

```typescript
async function checkTransactionWithFallback(ticketId) {
  try {
    // Check if we received webhook updates
    const webhookStatus = await getWebhookStatus(ticketId);
    if (webhookStatus) {
      return webhookStatus;
    }
    
    // Fall back to polling
    console.log('No webhook received, polling status...');
    const status = await mnee.getTxStatus(ticketId);
    return status;
  } catch (error) {
    console.error('Failed to check transaction status:', error);
    throw error;
  }
}
```

#### Handling Failed Transactions

When a webhook indicates a failed transaction:

```typescript
async function handleFailedTransaction(webhookData) {
  // Log the failure
  console.error(`Transaction ${webhookData.id} failed:`, webhookData.errors);
  
  // Parse error for specific handling
  if (webhookData.errors?.includes('Insufficient')) {
    // Handle insufficient funds
    await notifyUserInsufficientFunds(webhookData.id);
  } else if (webhookData.errors?.includes('Invalid')) {
    // Handle invalid transaction
    await notifyUserInvalidTransaction(webhookData.id);
  } else {
    // Generic error handling
    await notifyUserTransactionFailed(webhookData.id, webhookData.errors);
  }
  
  // Maybe retry with different parameters
  if (shouldRetry(webhookData.errors)) {
    await retryTransaction(webhookData.id);
  }
}
```

### Important Notes

* Webhooks are only sent when `broadcast: true` and a `callbackUrl` is provided
* The webhook URL must be publicly accessible (not localhost unless using ngrok or similar)
* Webhooks may arrive out of order - always check the `updatedAt` timestamp
* Multiple webhooks may be sent for the same status - implement idempotency
* Webhook delivery is not guaranteed - implement fallback polling for critical transactions
* The `tx_id` field will be empty until the transaction reaches SUCCESS status
* Always respond quickly to webhooks (< 5 seconds) to avoid timeouts

### See Also

* [Transfer](/mnee-sdk/transfer) - Create and broadcast transactions
* [Transfer Multi](/mnee-sdk/transfer-multi) - Advanced transfers with multiple inputs
* [Submit Raw Transaction](/mnee-sdk/submit-raw-tx) - Submit pre-signed transactions
* [Get Transaction Status](/mnee-sdk/get-transaction-status) - Poll for transaction status


# Tx History

The MNEE SDK provides methods to retrieve transaction history for addresses, with support for pagination and batch queries.

### Recent Transaction History

The `recentTxHistory` method retrieves the transaction history for a single address.

#### Usage

```typescript
const history = await mnee.recentTxHistory('your-address-here');
console.log('History:', history);
```

#### With Pagination

```typescript
// Get first page (most recent transactions)
const firstPage = await mnee.recentTxHistory(address, undefined, 10);

// Get next page using nextScore
const secondPage = await mnee.recentTxHistory(
  address, 
  firstPage.nextScore, 
  10
);
```

#### Parameters

* **address**: The Bitcoin address to query
* **fromScore** (optional): Starting score for pagination
* **limit** (optional): Maximum number of transactions to return

#### Response

Returns a `TxHistoryResponse` object:

```typescript
{
  address: string;
  history: TxHistory[];
  nextScore: number;
}
```

#### Sample Response

```json
{
  "address": "1G6CB3Ch4zFkPmuhZzEyChQmrQPfi86qk3",
  "history": [
    {
      "txid": "d7fe19af19332d8ab1d83ed82003ecc41c8c5def8e786b58e90512e82087302a",
      "height": 857421,
      "status": "confirmed",
      "type": "receive",
      "amount": 5000,
      "counterparties": [
        {
          "address": "1Sender...",
          "amount": 5000
        }
      ],
      "fee": 100,
      "score": 857421.00001
    },
    {
      "txid": "abc123...",
      "height": 857420,
      "status": "confirmed",
      "type": "send",
      "amount": 2500,
      "counterparties": [
        {
          "address": "1Recipient...",
          "amount": 2500
        }
      ],
      "fee": 100,
      "score": 857420.00002
    }
  ],
  "nextScore": 857419.00003
}
```

### Recent Transaction Histories (Multiple)

The `recentTxHistories` method retrieves transaction histories for multiple addresses in a single call.

#### Usage

```typescript
const params = [
  { address: 'address1' },
  { address: 'address2', fromScore: 0, limit: 10 }
];

const histories = await mnee.recentTxHistories(params);
console.log('Histories:', histories);
```

#### Parameters

Array of `AddressHistoryParams`, each containing:

* **address**: The Bitcoin address
* **fromScore** (optional): Starting score for pagination
* **limit** (optional): Maximum transactions per address

#### Response

Returns an array of `TxHistoryResponse` objects, one for each address.

### Transaction History Properties

#### TxHistory Object

* **txid**: Transaction identifier
* **height**: Block height (0 for unconfirmed)
* **status**: `"confirmed"` or `"unconfirmed"`
* **type**: `"send"` or `"receive"`
* **amount**: Amount in atomic units
* **counterparties**: Array of addresses and amounts involved
* **fee**: Transaction fee in atomic units
* **score**: Sortable score for pagination

#### Counterparty Object

* **address**: The counterparty's address
* **amount**: Amount sent to/from this address

### Common Use Cases

#### Display Transaction List

```typescript
async function displayTransactions(address) {
  const history = await mnee.recentTxHistory(address, undefined, 20);
  
  console.log(`Transaction History for ${address}:`);
  history.history.forEach(tx => {
    const amount = mnee.fromAtomicAmount(tx.amount);
    const symbol = tx.type === 'receive' ? '+' : '-';
    const status = tx.status === 'confirmed' ? '✓' : '⏳';
    
    console.log(`${status} ${symbol}${amount} MNEE - ${tx.txid.substring(0, 8)}...`);
    
    tx.counterparties.forEach(cp => {
      console.log(`    ${tx.type === 'receive' ? 'from' : 'to'}: ${cp.address}`);
    });
  });
}
```

#### Calculate Total Received

```typescript
async function calculateTotalReceived(address) {
  let totalReceived = 0;
  let nextScore = undefined;
  
  // Paginate through all history
  while (true) {
    const history = await mnee.recentTxHistory(address, nextScore, 100);
    
    // Sum received amounts
    const pageReceived = history.history
      .filter(tx => tx.type === 'receive' && tx.status === 'confirmed')
      .reduce((sum, tx) => sum + tx.amount, 0);
    
    totalReceived += pageReceived;
    
    // Check if more pages exist
    if (history.history.length < 100 || !history.nextScore) {
      break;
    }
    
    nextScore = history.nextScore;
  }
  
  return mnee.fromAtomicAmount(totalReceived);
}
```

#### Monitor for New Transactions

```typescript
async function monitorAddress(address, callback) {
  let lastTxid = null;
  
  setInterval(async () => {
    const history = await mnee.recentTxHistory(address, undefined, 1);
    
    if (history.history.length > 0) {
      const latestTx = history.history[0];
      
      if (latestTx.txid !== lastTxid) {
        lastTxid = latestTx.txid;
        callback(latestTx);
      }
    }
  }, 30000); // Check every 30 seconds
}

// Usage
monitorAddress('your-address', (tx) => {
  const amount = mnee.fromAtomicAmount(tx.amount);
  console.log(`New ${tx.type}: ${amount} MNEE`);
});
```

#### Multi-Address Portfolio History

```typescript
async function getPortfolioHistory(addresses) {
  const params = addresses.map(addr => ({
    address: addr,
    limit: 10 // Recent 10 transactions per address
  }));
  
  const histories = await mnee.recentTxHistories(params);
  
  // Combine and sort all transactions
  const allTransactions = histories.flatMap(h => 
    h.history.map(tx => ({ ...tx, address: h.address }))
  );
  
  // Sort by score (most recent first)
  allTransactions.sort((a, b) => b.score - a.score);
  
  return allTransactions;
}
```

#### Export Transaction History

```typescript
async function exportToCSV(address) {
  const rows = ['Date,Type,Amount,Counterparty,TxID,Status'];
  let nextScore = undefined;
  
  while (true) {
    const history = await mnee.recentTxHistory(address, nextScore, 100);
    
    history.history.forEach(tx => {
      const date = new Date(tx.height * 600000).toISOString(); // Estimate
      const amount = mnee.fromAtomicAmount(tx.amount);
      const counterparty = tx.counterparties[0]?.address || 'Unknown';
      
      rows.push(
        `${date},${tx.type},${amount},${counterparty},${tx.txid},${tx.status}`
      );
    });
    
    if (history.history.length < 100) break;
    nextScore = history.nextScore;
  }
  
  return rows.join('\n');
}
```

#### Find Transactions with Specific Address

```typescript
async function findTransactionsWith(myAddress, targetAddress) {
  const matching = [];
  let nextScore = undefined;
  
  while (true) {
    const history = await mnee.recentTxHistory(myAddress, nextScore, 100);
    
    const matches = history.history.filter(tx =>
      tx.counterparties.some(cp => cp.address === targetAddress)
    );
    
    matching.push(...matches);
    
    if (history.history.length < 100) break;
    nextScore = history.nextScore;
  }
  
  return matching;
}
```

### Pagination Best Practices

* Start with `fromScore: undefined` for the most recent transactions
* Use the returned `nextScore` to fetch the next page
* When `history.length < limit`, you've reached the end
* Store `nextScore` to resume pagination later
* Higher scores represent more recent transactions

### Performance Tips

* Use `recentTxHistories` for multiple addresses instead of multiple `recentTxHistory` calls
* Limit page size based on your UI needs (10-50 for display, 100+ for analysis)
* Cache results when appropriate
* For large-scale analysis, consider using [batch operations](/utils/batch-operations)

### See Also

* [Parse Transaction](/utils/transaction-parsing) - Get detailed transaction information
* [Balance](/mnee-sdk/check-balance) - Get current balance
* [Batch Operations](/utils/batch-operations) - Process history for many addresses


# Types

```typescript
export type Environment = 'production' | 'sandbox';

export type SdkConfig = {
  environment: Environment;
  apiKey?: string;
};

export type MNEEFee = {
  min: number;
  max: number;
  fee: number;
};

export type MNEEConfig = {
  approver: string;
  feeAddress: string;
  burnAddress: string;
  mintAddress: string;
  fees: MNEEFee[];
  decimals: number;
  tokenId: string;
};

export type MNEEOperation = 'transfer' | 'burn' | 'deploy+mint';
export type TxOperation = 'transfer' | 'burn' | 'deploy' | 'mint';

export type MNEEUtxo = {
  data: {
    bsv21: {
      amt: number;
      dec: number;
      icon: string;
      id: string;
      op: string;
      sym: string;
    };
    cosign: {
      address: string;
      cosigner: string;
    };
  };
  height: number;
  idx: number;
  outpoint: string;
  owners: string[];
  satoshis: number;
  score: number;
  script: string;
  txid: string;
  vout: number;
};

export type SignatureRequest = {
  prevTxid: string;
  outputIndex: number;
  inputIndex: number;
  satoshis: number;
  address: string | string[];
  script?: string;
  sigHashType?: number;
  csIdx?: number;
  data?: unknown;
};

export type TransactionFormat = 'tx' | 'beef' | 'ef';

export type MNEEBalance = {
  address: string;
  amount: number;
  decimalAmount: number;
};

export type SendMNEE = {
  address: string;
  amount: number;
};

export type GetSignatures = {
  rawtx: string;
  sigRequests: SignatureRequest[];
  format?: TransactionFormat;
};

export type SignatureResponse = {
  inputIndex: number;
  sig: string;
  pubKey: string;
  sigHashType: number;
  csIdx?: number;
};

export type MneeInscription = {
  p: string;
  op: string;
  id: string;
  amt: string;
};

export type ParsedCosigner = {
  cosigner: string;
  address: string;
};

export interface File {
  hash: string;
  size: number;
  type: string;
  content: number[];
}

export interface Inscription {
  file?: File;
  fields?: { [key: string]: any };
  parent?: string;
}

export type BalanceResponse = Array<{
  address: string;
  amt: number;
  precised: number;
}>;

export type TransferResponse = { ticketId?: string; rawtx?: string };

export type TransferStatus = {
  id: string;
  tx_id: string;
  tx_hex: string;
  action_requested: 'transfer';
  status: 'BROADCASTING' | 'SUCCESS' | 'MINED' | 'FAILED';
  createdAt: string;
  updatedAt: string;
  errors: string | null;
};

export type TransferOptions = {
  broadcast?: boolean;
  callbackUrl?: string;
  // callbackSecret?: string; // TODO: Add this back in if/when we have a way to generate a secret
};

export type TransferWebhookResponse = {
  id: string;
  tx_id: string;
  tx_hex: string;
  action_requested: 'transfer';
  callback_url: string;
  status: 'SUCCESS' | 'BROADCASTING' | 'MINED' | 'FAILED';
  createdAt: string;
  updatedAt: string;
  errors: string | null;
};

export interface TransferMultiOptions {
  inputs: Array<{
    txid: string;
    vout: number;
    wif: string; // WIF for this specific UTXO
  }>;
  recipients: SendMNEE[];
  changeAddress?:
    | string
    | Array<{
        address: string;
        amount: number;
      }>; // Optional, can be single address or multiple with amounts
}

export type MneeSync = {
  txid: string;
  outs: null;
  height: number;
  idx: number;
  score: number;
  rawtx: string;
  senders: string[];
  receivers: string[];
};

export type Counterparty = {
  address: string;
  amount: number;
};

export type TxStatus = 'confirmed' | 'unconfirmed';
export type TxType = 'send' | 'receive';

export type TxHistory = {
  txid: string;
  height: number;
  status: TxStatus;
  type: TxType;
  amount: number;
  counterparties: Counterparty[];
  fee: number;
  score: number;
};

export type TxHistoryResponse = {
  address: string;
  history: TxHistory[];
  nextScore: number;
};

export type TxAddressAmount = {
  address: string;
  amount: number;
};

export type ParseTxResponse = {
  txid: string;
  environment: Environment;
  type: TxOperation;
  inputs: TxAddressAmount[];
  outputs: TxAddressAmount[];
  isValid: boolean;
  inputTotal: string;
  outputTotal: string;
};

export interface ParseOptions {
  includeRaw?: boolean;
}

export interface ParseTxExtendedResponse extends ParseTxResponse {
  raw?: {
    txHex: string;
    inputs: Array<{
      txid: string;
      vout: number;
      scriptSig: string;
      sequence: number;
      satoshis: number;
      address?: string;
      tokenData?: any;
    }>;
    outputs: Array<{
      value: number;
      scriptPubKey: string;
      address?: string;
      tokenData?: any;
    }>;
  };
}

export interface AddressHistoryParams {
  address: string;
  fromScore?: number;
  limit?: number;
  order?: 'asc' | 'desc';
}

export interface ProcessedInput {
  address?: string;
  amount: number;
  satoshis: number;
  inscription?: MneeInscription | null;
  cosigner?: ParsedCosigner;
}

export interface ProcessedOutput {
  address?: string;
  amount: number;
  satoshis: number;
  inscription?: MneeInscription | null;
  cosigner?: ParsedCosigner;
}

export interface TxInputResponse {
  inputs: ProcessedInput[];
  total: bigint;
  environment?: Environment;
  type?: TxOperation;
}

export interface TxOutputResponse {
  outputs: ProcessedOutput[];
  total: bigint;
  environment?: Environment;
  type?: TxOperation;
}
```


# Validate

The `validateMneeTx` method validates MNEE transactions to ensure they are properly formatted and authorized by the cosigner. It supports both basic validation (checking if the transaction is well-formed) and deep validation (verifying against expected outputs).

### Usage

#### Basic Validation

```typescript
const rawtx = '0100000002b170f2d41764c...'; // raw tx hex
const isValid = await mnee.validateMneeTx(rawtx);
console.log('Transaction is valid:', isValid);
```

#### Deep Validation (with expected outputs)

```typescript
const rawtx = '0100000002b170f2d41764c...'; // raw tx hex
const expectedOutputs = [
  { address: 'recipient-1-address', amount: 1 },
  { address: 'recipient-2-address', amount: 10.25 },
];

const isValid = await mnee.validateMneeTx(rawtx, expectedOutputs);
console.log('Transaction matches expected outputs:', isValid);
```

### Parameters

* **rawTxHex**: The raw transaction hex string to validate
* **request** (optional): An array of `SendMNEE` objects representing the expected transfer details
  * If provided: Validates that the transaction matches the specified outputs
  * If not provided: Only validates that the transaction is well-formed with proper cosigner authorization

### Response

Returns a Promise that resolves to a boolean:

* `true`: The transaction is valid
* `false`: The transaction is invalid

### Common Use Cases

#### Validate Before Broadcasting

```typescript
// Create a transaction
const transferRequest = [
  { address: '1Recipient...', amount: 5.5 }
];
const response = await mnee.transfer(transferRequest, wif, { broadcast: false });

// Validate before submitting
const isValid = await mnee.validateMneeTx(response.rawtx);
if (isValid) {
  const submitResult = await mnee.submitRawTx(response.rawtx);
  console.log('Transaction submitted, ticket ID:', submitResult.ticketId);
  
  // Get transaction ID from status
  const status = await mnee.getTxStatus(submitResult.ticketId);
  console.log('Transaction ID:', status.tx_id);
} else {
  console.log('Transaction validation failed');
}
```

#### Verify External Transactions

```typescript
// Receive a transaction from external source
const externalRawTx = '...'; // raw tx from another wallet/service

// Basic validation
const isWellFormed = await mnee.validateMneeTx(externalRawTx);
console.log('Transaction structure valid:', isWellFormed);

// Parse to see details
if (isWellFormed) {
  const parsed = await mnee.parseTxFromRawTx(externalRawTx);
  console.log('Transaction details:', parsed);
}
```

#### Validate Multi-Recipient Transactions

```typescript
const expectedTransfers = [
  { address: '1Address1...', amount: 2.5 },
  { address: '1Address2...', amount: 7.3 },
  { address: '1Address3...', amount: 0.2 }
];

// Create transaction with multiple recipients
const response = await mnee.transfer(expectedTransfers, wif, false);

// Validate it matches our expectations
const isValid = await mnee.validateMneeTx(response.rawtx, expectedTransfers);
console.log('Multi-recipient transaction valid:', isValid);
```

#### Integration Testing

```typescript
// Test transaction creation and validation
async function testTransactionCreation() {
  const testTransfer = [{ address: testAddress, amount: 0.001 }];
  
  try {
    // Create transaction
    const tx = await mnee.transfer(testTransfer, testWif, false);
    
    // Validate structure
    const basicValid = await mnee.validateMneeTx(tx.rawtx);
    assert(basicValid, 'Basic validation should pass');
    
    // Validate outputs
    const deepValid = await mnee.validateMneeTx(tx.rawtx, testTransfer);
    assert(deepValid, 'Deep validation should pass');
    
    console.log('Transaction validation tests passed');
  } catch (error) {
    console.error('Validation test failed:', error);
  }
}
```

### Validation Checks

The method performs the following validations:

#### Basic Validation (always performed)

* Transaction hex is valid and can be decoded
* Transaction has proper MNEE inscription format
* Cosigner signature is present and valid
* Transaction structure follows MNEE protocol rules

#### Deep Validation (when request provided)

* All specified recipients are present in outputs
* Transfer amounts match exactly (in atomic units)
* No unexpected outputs (except change and fees)
* Total output amounts are correct

### Notes

* Validation is performed locally without network calls
* The cosigner public key is obtained from the MNEE configuration
* Amount comparisons are done in atomic units to avoid floating-point issues
* Change outputs and fee outputs are automatically accounted for in deep validation

### See Also

* [Transfer](/mnee-sdk/transfer) - Create MNEE transfers
* [Submit Raw Transaction](/mnee-sdk/submit-raw-tx) - Submit validated transactions
* [Parse Transaction](/utils/transaction-parsing) - Examine transaction details


# Unit Conversions

MNEE uses atomic units for precise calculations and to avoid floating-point arithmetic errors. The SDK provides two methods for converting between human-readable MNEE amounts and atomic units.

### Conversion Methods

#### toAtomicAmount

Converts a human-readable MNEE amount to atomic units.

```typescript
const atomic = mnee.toAtomicAmount(1.5);
console.log(atomic); // 150000
```

#### fromAtomicAmount

Converts atomic units to human-readable MNEE amount.

```typescript
const human = mnee.fromAtomicAmount(150000);
console.log(human); // 1.5
```

### Understanding Units

* **1 MNEE = 100,000 atomic units**
* MNEE has 5 decimal places
* All blockchain operations use atomic units
* User interfaces should display MNEE amounts

### Common Use Cases

#### Preparing Transfer Amounts

```typescript
// User wants to send 10.5 MNEE
const userAmount = 10.5;
const atomicAmount = mnee.toAtomicAmount(userAmount);

// Use atomic amount for internal calculations
console.log(`Sending ${atomicAmount} atomic units`);

// But show user-friendly amount
console.log(`Sending ${userAmount} MNEE`);
```

#### Displaying Balances

```typescript
const balance = await mnee.balance(address);

// The balance object already includes both formats
console.log(`Atomic: ${balance.amount}`);        // 1234567
console.log(`MNEE: ${balance.decimalAmount}`);   // 12.34567

// Or convert manually
const mneeAmount = mnee.fromAtomicAmount(balance.amount);
console.log(`You have ${mneeAmount} MNEE`);
```

#### Fee Calculations

```typescript
const config = await mnee.config();

// Find fee for a 50 MNEE transfer
const transferAtomic = mnee.toAtomicAmount(50);

const feeTier = config.fees.find(tier => 
  transferAtomic >= tier.min && transferAtomic <= tier.max
);

// Convert fee to MNEE for display
const feeMNEE = mnee.fromAtomicAmount(feeTier.fee);
console.log(`Transfer fee: ${feeMNEE} MNEE`);
```

#### UTXO Amount Calculations

```typescript
const utxos = await mnee.getUtxos(address);

// Sum UTXO amounts (in atomic units)
const totalAtomic = utxos.reduce((sum, utxo) => 
  sum + utxo.data.bsv21.amt, 0
);

// Convert to MNEE for display
const totalMNEE = mnee.fromAtomicAmount(totalAtomic);
console.log(`Total in UTXOs: ${totalMNEE} MNEE`);
```

#### Input Validation

```typescript
function validateAmount(userInput) {
  const amount = parseFloat(userInput);
  
  if (isNaN(amount) || amount <= 0) {
    throw new Error('Invalid amount');
  }
  
  // Check decimal places
  const atomic = mnee.toAtomicAmount(amount);
  const backToMnee = mnee.fromAtomicAmount(atomic);
  
  if (amount !== backToMnee) {
    throw new Error('Too many decimal places (max 5)');
  }
  
  // Check minimum (dust limit)
  const config = await mnee.config();
  if (atomic < config.fees[0].fee) {
    throw new Error(`Minimum amount is ${mnee.fromAtomicAmount(config.fees[0].fee)} MNEE`);
  }
  
  return amount;
}
```

#### Precision Handling

```typescript
// Avoid floating point issues
const amount1 = 0.1;
const amount2 = 0.2;

// Wrong way (floating point error)
const wrongSum = amount1 + amount2; // 0.30000000000000004

// Right way (using atomic units)
const atomic1 = mnee.toAtomicAmount(amount1);
const atomic2 = mnee.toAtomicAmount(amount2);
const atomicSum = atomic1 + atomic2;
const correctSum = mnee.fromAtomicAmount(atomicSum); // 0.3
```

#### Format for Display

```typescript
function formatMNEE(atomicAmount) {
  const mneeAmount = mnee.fromAtomicAmount(atomicAmount);
  
  // Format with appropriate decimal places
  if (mneeAmount >= 1) {
    return mneeAmount.toFixed(2); // "1.50"
  } else if (mneeAmount >= 0.01) {
    return mneeAmount.toFixed(3); // "0.015"
  } else {
    return mneeAmount.toFixed(5); // "0.00015"
  }
}
```

#### Batch Amount Processing

```typescript
// Convert multiple amounts efficiently
const userAmounts = [1.5, 2.3, 0.45, 10];
const atomicAmounts = userAmounts.map(amt => mnee.toAtomicAmount(amt));

// Process in atomic units
const total = atomicAmounts.reduce((sum, amt) => sum + amt, 0);
const average = total / atomicAmounts.length;

// Convert back for display
console.log(`Total: ${mnee.fromAtomicAmount(total)} MNEE`);
console.log(`Average: ${mnee.fromAtomicAmount(average)} MNEE`);
```

### Important Notes

* Always use atomic units for calculations to avoid rounding errors
* MNEE amounts in the SDK methods ([transfer](/mnee-sdk/transfer), etc.) expect decimal MNEE values, not atomic for better UX.
* Maximum precision is 5 decimal places
* When displaying to users, consider formatting appropriately
* Database storage should use atomic units (integers) for accuracy

### Conversion Table

| MNEE    | Atomic Units |
| ------- | ------------ |
| 0.00001 | 1            |
| 0.0001  | 10           |
| 0.001   | 100          |
| 0.01    | 1,000        |
| 0.1     | 10,000       |
| 1       | 100,000      |
| 10      | 1,000,000    |
| 100     | 10,000,000   |

### See Also

* [Configuration](/mnee-sdk/config) - Fee tiers use atomic units
* [Balance](/mnee-sdk/check-balance) - Returns both atomic and decimal amounts
* [Get UTXOs](/mnee-sdk/get-utxos) - UTXO amounts are in atomic units
* [Transfer](/mnee-sdk/transfer) - Accepts amounts in MNEE (decimal)


# Transaction Parsing

The MNEE SDK provides methods to parse and analyze MNEE transactions, extracting detailed information about inputs, outputs, and validation status.

### Parse Transaction by ID

The `parseTx` method parses a transaction using its transaction ID.

#### Usage

```typescript
const parsed = await mnee.parseTx('txid-here');
console.log('Parsed TX:', parsed);
```

#### With Extended Data

```typescript
// Include raw transaction details
const parsed = await mnee.parseTx('txid-here', { includeRaw: true });
console.log('Raw data:', parsed.raw);
```

### Parse Transaction from Raw Hex

The `parseTxFromRawTx` method parses a transaction from its raw hexadecimal representation.

#### Usage

```typescript
const parsed = await mnee.parseTxFromRawTx('raw-tx-hex-here');
console.log('Parsed TX:', parsed);
```

#### With Extended Data

```typescript
const parsed = await mnee.parseTxFromRawTx('raw-tx-hex', { includeRaw: true });
```

### Parameters

#### parseTx

* **txid**: Transaction ID to parse
* **options** (optional): `ParseOptions` object
  * **includeRaw**: Include detailed raw transaction data

#### parseTxFromRawTx

* **rawTxHex**: Raw transaction in hexadecimal format
* **options** (optional): Same as above

### Response

Returns a `ParseTxResponse` or `ParseTxExtendedResponse` object.

#### Basic Response

```json
{
  "txid": "d7fe19af19332d8ab1d83ed82003ecc41c8c5def8e786b58e90512e82087302a",
  "environment": "production",
  "type": "transfer",
  "inputs": [
    {
      "address": "1Sender...",
      "amount": 10000
    }
  ],
  "outputs": [
    {
      "address": "1Recipient...",
      "amount": 5000
    },
    {
      "address": "1Change...",
      "amount": 5000
    }
  ],
  "isValid": true,
  "inputTotal": "10000",
  "outputTotal": "10000"
}
```

#### Extended Response (with includeRaw)

Includes all basic fields plus:

```json
{
  "raw": {
    "txHex": "0100000001...",
    "inputs": [
      {
        "txid": "previous-tx-id",
        "vout": 0,
        "scriptSig": "...",
        "sequence": 4294967295,
        "satoshis": 1000,
        "address": "1Sender...",
        "tokenData": { /* MNEE token data */ }
      }
    ],
    "outputs": [
      {
        "value": 1000,
        "scriptPubKey": "...",
        "address": "1Recipient...",
        "tokenData": { /* MNEE token data */ }
      }
    ],
    "version": 1,
    "lockTime": 0,
    "size": 250,
    "hash": "..."
  }
}
```

### Response Properties

#### Basic Properties

* **txid**: Transaction identifier
* **environment**: `"production"` or `"sandbox"`
* **type**: Operation type (`"transfer"`, `"burn"`, etc.)
* **inputs**: Array of input addresses and amounts
* **outputs**: Array of output addresses and amounts
* **isValid**: Whether the transaction is valid
* **inputTotal**: Total input amount (string)
* **outputTotal**: Total output amount (string)

#### Extended Properties (raw)

* **txHex**: Complete raw transaction hex
* **inputs**: Detailed input information
* **outputs**: Detailed output information
* **version**: Transaction version
* **lockTime**: Transaction lock time
* **size**: Transaction size in bytes
* **hash**: Transaction hash

### Common Use Cases

#### Transaction Analysis

```typescript
async function analyzeTransaction(txid) {
  const parsed = await mnee.parseTx(txid);
  
  console.log(`Transaction ${txid}:`);
  console.log(`- Type: ${parsed.type}`);
  console.log(`- Valid: ${parsed.isValid}`);
  console.log(`- Environment: ${parsed.environment}`);
  
  // Calculate fee
  const fee = parseInt(parsed.inputTotal) - parseInt(parsed.outputTotal);
  console.log(`- Fee: ${mnee.fromAtomicAmount(fee)} MNEE`);
  
  // Analyze flows
  console.log('\nInputs:');
  parsed.inputs.forEach(input => {
    console.log(`  ${input.address}: ${mnee.fromAtomicAmount(input.amount)} MNEE`);
  });
  
  console.log('\nOutputs:');
  parsed.outputs.forEach(output => {
    console.log(`  ${output.address}: ${mnee.fromAtomicAmount(output.amount)} MNEE`);
  });
}
```

#### Verify Transaction Before Acceptance

```typescript
async function verifyIncomingTransaction(txid, expectedAmount, senderAddress) {
  const parsed = await mnee.parseTx(txid);
  
  // Check if valid
  if (!parsed.isValid) {
    throw new Error('Invalid transaction');
  }
  
  // Verify sender
  const fromSender = parsed.inputs.some(input => 
    input.address === senderAddress
  );
  if (!fromSender) {
    throw new Error('Transaction not from expected sender');
  }
  
  // Verify amount
  const myAddress = 'my-address';
  const received = parsed.outputs
    .filter(output => output.address === myAddress)
    .reduce((sum, output) => sum + output.amount, 0);
  
  if (received < mnee.toAtomicAmount(expectedAmount)) {
    throw new Error('Insufficient amount received');
  }
  
  return true;
}
```

#### Debug Failed Transactions

```typescript
async function debugTransaction(rawTxHex) {
  const parsed = await mnee.parseTxFromRawTx(rawTxHex, { includeRaw: true });
  
  console.log('Transaction Debug Info:');
  console.log(`- Valid: ${parsed.isValid}`);
  console.log(`- Size: ${parsed.raw.size} bytes`);
  console.log(`- Input Total: ${mnee.fromAtomicAmount(parseInt(parsed.inputTotal))} MNEE`);
  console.log(`- Output Total: ${mnee.fromAtomicAmount(parseInt(parsed.outputTotal))} MNEE`);
  
  // Check for common issues
  if (!parsed.isValid) {
    console.log('\n❌ Transaction is invalid');
  }
  
  if (parsed.inputTotal === parsed.outputTotal) {
    console.log('\n⚠️ Warning: No fee included');
  }
  
  // Analyze inputs
  console.log('\nInput Details:');
  parsed.raw.inputs.forEach((input, i) => {
    console.log(`Input ${i}:`);
    console.log(`  Previous TX: ${input.txid}:${input.vout}`);
    console.log(`  Address: ${input.address || 'Unknown'}`);
    console.log(`  Token Data: ${JSON.stringify(input.tokenData)}`);
  });
}
```

#### Track Transaction Flow

```typescript
async function trackTokenFlow(startTxid, depth = 3) {
  const flow = [];
  const queue = [{ txid: startTxid, level: 0 }];
  const visited = new Set();
  
  while (queue.length > 0 && queue[0].level < depth) {
    const { txid, level } = queue.shift();
    
    if (visited.has(txid)) continue;
    visited.add(txid);
    
    const parsed = await mnee.parseTx(txid);
    flow.push({ txid, level, parsed });
    
    // Find subsequent transactions
    for (const output of parsed.outputs) {
      // Would need to query for transactions spending these outputs
      // This is a simplified example
    }
  }
  
  return flow;
}
```

#### Export Transaction Details

```typescript
async function exportTransactionDetails(txid) {
  const parsed = await mnee.parseTx(txid, { includeRaw: true });
  
  const details = {
    summary: {
      txid: parsed.txid,
      type: parsed.type,
      valid: parsed.isValid,
      fee: parseInt(parsed.inputTotal) - parseInt(parsed.outputTotal),
      timestamp: new Date().toISOString() // Would need block time
    },
    inputs: parsed.inputs.map(input => ({
      address: input.address,
      amount: mnee.fromAtomicAmount(input.amount)
    })),
    outputs: parsed.outputs.map(output => ({
      address: output.address,
      amount: mnee.fromAtomicAmount(output.amount)
    })),
    raw: parsed.raw
  };
  
  return JSON.stringify(details, null, 2);
}
```

#### Validate Complex Transactions

```typescript
async function validateComplexTransaction(txid, rules) {
  const parsed = await mnee.parseTx(txid);
  
  const validations = {
    isValid: parsed.isValid,
    hasMinimumFee: false,
    hasExpectedRecipients: false,
    hasNoUnknownOutputs: false
  };
  
  // Check minimum fee
  const fee = parseInt(parsed.inputTotal) - parseInt(parsed.outputTotal);
  validations.hasMinimumFee = fee >= rules.minimumFee;
  
  // Check expected recipients
  validations.hasExpectedRecipients = rules.expectedRecipients.every(
    expected => parsed.outputs.some(
      output => output.address === expected.address && 
                output.amount >= expected.amount
    )
  );
  
  // Check for unknown outputs
  const knownAddresses = new Set([
    ...rules.expectedRecipients.map(r => r.address),
    ...rules.changeAddresses || []
  ]);
  
  validations.hasNoUnknownOutputs = parsed.outputs.every(
    output => knownAddresses.has(output.address)
  );
  
  return validations;
}
```

### Important Notes

* Transaction parsing includes automatic validation
* Amounts in the response are in atomic units
* The `isValid` flag indicates if the transaction follows MNEE protocol rules
* Extended data (`includeRaw: true`) provides blockchain-level details
* Input/output totals are provided as strings to preserve precision

### See Also

* [Validate Transaction](/utils/validate) - Validate transaction structure
* [Transaction History](/mnee-sdk/tx-history) - Get transaction history
* [Submit Raw Transaction](/mnee-sdk/submit-raw-tx) - Broadcast transactions


# Script Parsing

The MNEE SDK provides methods to parse inscription data and cosigner information from Bitcoin scripts.

### Parse Inscription

The `parseInscription` method extracts inscription data from a Bitcoin script. This is useful for analyzing on-chain data and understanding transaction metadata.

#### Usage

```typescript
import { Script } from '@bsv/sdk';

const script = Script.fromHex('...');
const inscription = mnee.parseInscription(script);
console.log('Inscription:', inscription);
```

#### Response

Returns an `Inscription` object or `undefined` if no inscription is found:

```typescript
{
  file?: {
    hash: string;
    size: number;
    type: string;
    content: number[];
  };
  fields?: {
    [key: string]: any;
  };
  parent?: string;
}
```

### Parse Cosigner Scripts

The `parseCosignerScripts` method extracts cosigner public keys and addresses from an array of scripts.

#### Usage

```typescript
import { Script } from '@bsv/sdk';

const scripts = [
  Script.fromHex('...'),
  Script.fromHex('...')
];

const cosigners = mnee.parseCosignerScripts(scripts);
console.log('Cosigner addresses:', cosigners);
```

#### Response

Returns an array of `ParsedCosigner` objects:

```typescript
[
  {
    cosigner: "03d47c2e48c59b3f58b96c9e616d0a84c6e02725e47beefcb5b5a8fbe21a3c5e3a",
    address: "17cgGUmStWwcYgHg3kxmzXSp6JUbj8XA3u"
  }
]
```

### Common Use Cases

#### Extract Inscription Data

```typescript
async function analyzeInscription(txid) {
  // Get transaction details
  const parsed = await mnee.parseTx(txid, { includeRaw: true });
  
  // Check each output for inscriptions
  for (const output of parsed.raw.outputs) {
    const script = Script.fromHex(output.scriptPubKey);
    const inscription = mnee.parseInscription(script);
    
    if (inscription) {
      console.log('Found inscription:', inscription);
      
      if (inscription.file) {
        console.log(`File type: ${inscription.file.type}`);
        console.log(`File size: ${inscription.file.size} bytes`);
        console.log(`File hash: ${inscription.file.hash}`);
      }
      
      if (inscription.fields) {
        console.log('Custom fields:', inscription.fields);
      }
    }
  }
}
```

#### Verify Cosigner Authorization

```typescript
async function verifyCosigner(rawTxHex) {
  const tx = Transaction.fromHex(rawTxHex);
  const scripts = tx.outputs.map(output => output.script);
  
  const cosigners = mnee.parseCosignerScripts(scripts);
  
  // Get expected cosigner from config
  const config = await mnee.config();
  const expectedCosigner = config.approver;
  
  // Verify cosigner
  const authorized = cosigners.some(
    c => c.cosigner === expectedCosigner
  );
  
  if (!authorized) {
    throw new Error('Transaction not authorized by cosigner');
  }
  
  return cosigners;
}
```

#### Extract Metadata from Transactions

```typescript
async function extractMetadata(txid) {
  const parsed = await mnee.parseTx(txid, { includeRaw: true });
  const metadata = {
    inscriptions: [],
    cosigners: [],
    customData: {}
  };
  
  // Process outputs
  for (let i = 0; i < parsed.raw.outputs.length; i++) {
    const output = parsed.raw.outputs[i];
    const script = Script.fromHex(output.scriptPubKey);
    
    // Check for inscription
    const inscription = mnee.parseInscription(script);
    if (inscription) {
      metadata.inscriptions.push({
        outputIndex: i,
        inscription
      });
    }
    
    // Check for cosigner
    const cosigners = mnee.parseCosignerScripts([script]);
    if (cosigners.length > 0) {
      metadata.cosigners.push({
        outputIndex: i,
        cosigner: cosigners[0]
      });
    }
  }
  
  return metadata;
}
```

#### Analyze File Inscriptions

```typescript
function analyzeFileInscription(inscription) {
  if (!inscription?.file) {
    return null;
  }
  
  const analysis = {
    type: inscription.file.type,
    size: inscription.file.size,
    hash: inscription.file.hash,
    humanSize: formatBytes(inscription.file.size),
    isImage: inscription.file.type.startsWith('image/'),
    isText: inscription.file.type.startsWith('text/'),
    isJson: inscription.file.type === 'application/json'
  };
  
  // Extract content if it's text
  if (analysis.isText || analysis.isJson) {
    try {
      const text = Buffer.from(inscription.file.content).toString('utf8');
      analysis.content = analysis.isJson ? JSON.parse(text) : text;
    } catch (e) {
      analysis.contentError = e.message;
    }
  }
  
  return analysis;
}

function formatBytes(bytes) {
  if (bytes === 0) return '0 Bytes';
  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
```

#### Find Transactions with Specific Inscriptions

```typescript
async function findInscriptionsByType(addresses, fileType) {
  const matches = [];
  
  for (const address of addresses) {
    const history = await mnee.recentTxHistory(address, undefined, 100);
    
    for (const tx of history.history) {
      const parsed = await mnee.parseTx(tx.txid, { includeRaw: true });
      
      for (const output of parsed.raw.outputs) {
        const script = Script.fromHex(output.scriptPubKey);
        const inscription = mnee.parseInscription(script);
        
        if (inscription?.file?.type === fileType) {
          matches.push({
            txid: tx.txid,
            address: output.address,
            inscription
          });
        }
      }
    }
  }
  
  return matches;
}
```

#### Build Custom Scripts

```typescript
// Example: Create a script with custom data
function createCustomScript(data) {
  const script = new Script();
  
  // Add OP_RETURN for data storage
  script.writeOpCode(OpCode.OP_RETURN);
  
  // Add custom data
  const dataBuffer = Buffer.from(JSON.stringify(data));
  script.writeBin(dataBuffer);
  
  return script;
}

// Verify custom data
function parseCustomScript(script) {
  const chunks = script.chunks;
  
  if (chunks[0]?.opCode === OpCode.OP_RETURN && chunks[1]?.buf) {
    try {
      const data = JSON.parse(chunks[1].buf.toString('utf8'));
      return data;
    } catch (e) {
      return null;
    }
  }
  
  return null;
}
```

### Important Notes

* Not all scripts contain inscriptions or cosigner data
* The `parseInscription` method returns `undefined` if no inscription is found
* Cosigner scripts are typically found in MNEE transaction outputs
* File content in inscriptions is stored as a byte array
* Always handle the case where parsing returns no data

### Script Types

MNEE transactions may contain various script types:

1. **Standard P2PKH**: Regular Bitcoin addresses
2. **Inscription Scripts**: Contain embedded data/files
3. **Cosigner Scripts**: Include cosigner authorization
4. **Multi-signature Scripts**: Require multiple signatures

### See Also

* [Parse Transaction](/utils/transaction-parsing) - Get full transaction details
* [Validate Transaction](/utils/validate) - Verify transaction validity
* [Configuration](/mnee-sdk/config) - Get expected cosigner information


# HD Wallet

The MNEE SDK includes a complete BIP32/BIP44 hierarchical deterministic (HD) wallet implementation. This allows you to manage multiple addresses from a single mnemonic seed phrase, perfect for wallet applications and advanced key management.

### Setup

```typescript
import Mnee, { HDWallet } from '@mnee/ts-sdk';

// Initialize MNEE SDK
const mnee = new Mnee({
    environment: 'sandbox', 
    apiKey: 'your-api-key'
});

// For examples below, assume these are already set up
```

### Static Methods

#### Generate Mnemonic

Generate a new BIP39 mnemonic phrase (12 words).

```typescript
const mnemonic = HDWallet.generateMnemonic();
console.log('New mnemonic:', mnemonic);
// Output: "abandon abandon abandon ... about"
```

#### Validate Mnemonic

Check if a mnemonic phrase is valid.

```typescript
const isValid = HDWallet.isValidMnemonic(mnemonic);
console.log('Mnemonic valid:', isValid);
```

### Creating an HD Wallet

```typescript
const mnemonic = 'your twelve word mnemonic phrase here ...';
const hdWallet = mnee.HDWallet(mnemonic, {
  derivationPath: "m/44'/236'/0'",
  cacheSize: 1000  // Optional: number of addresses to cache
});
```

#### Parameters

* **mnemonic**: BIP39 mnemonic phrase (12-24 words)
* **options**: `HDWalletOptions` object
  * **derivationPath**: BIP44 derivation path (e.g., `"m/44'/236'/0'"`)
  * **cacheSize** (optional): Number of derived addresses to cache (default: 1000)

### Deriving Addresses

#### Single Address

```typescript
// Derive external (receive) address at index 0
const addressInfo = hdWallet.deriveAddress(0, false);
console.log('Address:', addressInfo.address);
console.log('Private Key (WIF):', addressInfo.privateKey);
console.log('Derivation Path:', addressInfo.path);

// Derive change address at index 0
const changeInfo = hdWallet.deriveAddress(0, true);
```

#### Multiple Addresses

```typescript
// Derive 10 receive addresses starting at index 0
const addresses = await hdWallet.deriveAddresses(0, 10, false);

addresses.forEach((info, i) => {
  console.log(`Address ${i}: ${info.address}`);
});

// Derive 5 change addresses starting at index 10
const changeAddresses = await hdWallet.deriveAddresses(10, 5, true);
```

#### Response Structure

Each derived address returns an `AddressInfo` object:

```typescript
{
  address: string;      // Bitcoin address
  privateKey: string;   // Private key in WIF format
  path: string;         // Full derivation path
}
```

### Getting Private Keys for Addresses

Retrieve private keys for specific addresses by scanning the HD wallet.

```typescript
const addresses = [
  '1Address1...',
  '1Address2...',
  '1Address3...'
];

const result = hdWallet.getPrivateKeysForAddresses(addresses);
console.log('Private keys:', result.privateKeys);
console.log('Paths:', result.paths);
// result.privateKeys: {
//   '1Address1...': 'L1PrivateKey...',
//   '1Address2...': 'L2PrivateKey...',
//   '1Address3...': 'L3PrivateKey...'
// }
// result.paths: {
//   '1Address1...': "m/44'/236'/0'/0/0",
//   '1Address2...': "m/44'/236'/0'/0/1",
//   '1Address3...': "m/44'/236'/0'/1/0"
// }
```

#### With Scan Options

```typescript
const result = hdWallet.getPrivateKeysForAddresses(addresses, {
  maxScanReceive: 10000,  // Max receive addresses to scan
  maxScanChange: 10000,   // Max change addresses to scan
  scanStrategy: 'parallel' // 'sequential' or 'parallel'
});
```

### Common Use Cases

#### Create New Wallet

```typescript
function createNewWallet(mnee) {
  // Generate new mnemonic
  const mnemonic = HDWallet.generateMnemonic();
  
  // Create HD wallet
  const hdWallet = mnee.HDWallet(mnemonic, {
    derivationPath: "m/44'/236'/0'",
    cacheSize: 100
  });
  
  // Generate first few addresses
  const addresses = [];
  for (let i = 0; i < 5; i++) {
    const info = hdWallet.deriveAddress(i, false);
    addresses.push(info.address);
  }
  
  return {
    mnemonic,  // Save this securely!
    addresses
  };
}
```

#### Restore Wallet from Mnemonic

```typescript
async function restoreWallet(mnemonic, mneeInstance) {
  // Validate mnemonic
  if (!HDWallet.isValidMnemonic(mnemonic)) {
    throw new Error('Invalid mnemonic phrase');
  }
  
  // Create HD wallet
  const hdWallet = mneeInstance.HDWallet(mnemonic, {
    derivationPath: "m/44'/236'/0'"
  });
  
  // Scan for used addresses
  const usedAddresses = [];
  let consecutiveUnused = 0;
  const gapLimit = 20;
  
  for (let i = 0; consecutiveUnused < gapLimit; i++) {
    const info = hdWallet.deriveAddress(i, false);
    const balance = await mneeInstance.balance(info.address);
    
    if (balance.decimalAmount > 0) {
      usedAddresses.push({
        ...info,
        balance: balance.decimalAmount
      });
      consecutiveUnused = 0;
    } else {
      consecutiveUnused++;
    }
  }
  
  return usedAddresses;
}
```

#### HD Wallet Send

```typescript
async function hdWalletSend(hdWallet, recipients, totalAmount) {
  // Collect UTXOs from HD addresses
  const utxos = [];
  const wifs = {};
  let collected = 0;
  
  for (let i = 0; collected < totalAmount && i < 100; i++) {
    const info = hdWallet.deriveAddress(i, false);
    const addressUtxos = await mnee.getUtxos(info.address);
    
    if (addressUtxos.length > 0) {
      utxos.push(...addressUtxos);
      wifs[info.address] = info.privateKey;
      
      const addressTotal = addressUtxos.reduce(
        (sum, utxo) => sum + utxo.data.bsv21.amt, 0
      );
      collected += mnee.fromAtomicAmount(addressTotal);
    }
  }
  
  // Prepare inputs for transferMulti
  const inputs = utxos.map(utxo => ({
    txid: utxo.outpoint.split(':')[0],
    vout: parseInt(utxo.outpoint.split(':')[1]),
    wif: wifs[utxo.owners[0]]
  }));
  
  // Use next change address
  const changeInfo = hdWallet.deriveAddress(0, true);
  
  // Send transaction
  const response = await mnee.transferMulti({
    inputs,
    recipients,
    changeAddress: changeInfo.address
  });
  
  return response.txid;
}
```

#### Address Labeling System

```typescript
class HDWalletManager {
  constructor(mnee, mnemonic, options) {
    this.hdWallet = mnee.HDWallet(mnemonic, options);
    this.labels = new Map(); // address -> label
    this.addressIndex = { receive: 0, change: 0 };
  }
  
  generateNewAddress(label, isChange = false) {
    const index = isChange ? 
      this.addressIndex.change++ : 
      this.addressIndex.receive++;
    
    const info = this.hdWallet.deriveAddress(index, isChange);
    this.labels.set(info.address, label);
    
    return {
      ...info,
      label,
      index,
      type: isChange ? 'change' : 'receive'
    };
  }
  
  getAddressByLabel(label) {
    for (const [address, addrLabel] of this.labels) {
      if (addrLabel === label) {
        return address;
      }
    }
    return null;
  }
  
  async getBalanceReport() {
    const report = [];
    
    for (const [address, label] of this.labels) {
      const balance = await mnee.balance(address);
      report.push({
        address,
        label,
        balance: balance.decimalAmount
      });
    }
    
    return report;
  }
}
```

#### Backup and Recovery

```typescript
function exportWalletData(hdWallet, maxAddresses = 100) {
  const backup = {
    version: 1,
    created: new Date().toISOString(),
    addresses: {
      receive: [],
      change: []
    }
  };
  
  // Export receive addresses
  for (let i = 0; i < maxAddresses; i++) {
    const info = hdWallet.deriveAddress(i, false);
    backup.addresses.receive.push({
      index: i,
      address: info.address,
      path: info.path
    });
  }
  
  // Export change addresses
  for (let i = 0; i < maxAddresses / 2; i++) {
    const info = hdWallet.deriveAddress(i, true);
    backup.addresses.change.push({
      index: i,
      address: info.address,
      path: info.path
    });
  }
  
  return backup;
}
```

#### Simplified Private Key Retrieval

The `getPrivateKeys` method provides a simpler interface for just getting private keys:

```typescript
const addresses = ['1Address1...', '1Address2...'];
const privateKeys = hdWallet.getPrivateKeys(addresses, {
  maxScanReceive: 5000,
  maxScanChange: 5000,
  scanStrategy: 'parallel'
});
// Returns: { '1Address1...': 'L1PrivateKey...', '1Address2...': 'L2PrivateKey...' }
```

#### Scan Addresses with Gap Limit

Use the BIP44 standard gap limit scanning to find all used addresses:

```typescript
const checkAddressUsed = async (address) => {
  const balance = await mnee.balance(address);
  return balance.amount > 0;
};

const discovered = await hdWallet.scanAddressesWithGapLimit(
  checkAddressUsed,
  {
    gapLimit: 20,        // Standard BIP44 gap limit
    scanChange: true,    // Also scan change addresses
    maxScan: 10000      // Maximum addresses to scan
  }
);

console.log('Receive addresses:', discovered.receive);
console.log('Change addresses:', discovered.change);
```

#### Cache Management

```typescript
// Clear the cache to free memory
hdWallet.clearCache();

// Check current cache size
const cacheSize = hdWallet.getCacheSize();
console.log(`Cache contains ${cacheSize} addresses`);
```

### Best Practices

#### Security

* **Never store mnemonics in plain text**
* **Use secure key storage solutions**
* **Implement proper access controls**
* **Clear sensitive data from memory when done**

#### Performance

* **Use appropriate cache sizes** based on your needs
* **Batch operations** when deriving multiple addresses
* **Scan efficiently** using gap limits

#### Address Management

* **Follow BIP44 standards** for derivation paths
* **Use separate addresses** for each transaction (privacy)
* **Track address indexes** to avoid reuse
* **Implement gap limit** scanning (typically 20)

### Derivation Paths

Standard BIP44 path format: `m/purpose'/coin'/account'/change/index`

* **Purpose**: 44' (BIP44)
* **Coin**: 236' (BSV)
* **Account**: 0' (first account)
* **Change**: 0 (external) or 1 (internal)
* **Index**: Address index (0, 1, 2, ...)

Example paths:

* First receive address: `m/44'/236'/0'/0/0`
* First change address: `m/44'/236'/0'/1/0`
* 10th receive address: `m/44'/236'/0'/0/9`

### See Also

* Transfer Multi - Use HD wallet addresses for transfers
* Get UTXOs - Scan HD addresses for UTXOs
* Batch Operations - Process many HD addresses efficiently

### See Also

* [Transfer Multi](/mnee-sdk/transfer-multi) - Use HD wallet addresses for transfers
* [Get UTXOs](/mnee-sdk/get-utxos) - Scan HD addresses for UTXOs
* [Batch Operations](/utils/batch-operations) - Process many HD addresses efficiently


# Batch Operations

The MNEE SDK provides a powerful batch processing system for handling multiple operations efficiently. It includes automatic chunking, rate limiting, error recovery, and progress tracking.

### Setup

```typescript
import Mnee from '@mnee/ts-sdk';

// Initialize MNEE SDK
const mnee = new Mnee({
    environment: 'sandbox', 
    apiKey: 'your-api-key'
});

// For examples below, assume mnee is already set up
```

### Getting Started

Access batch operations through the `batch()` method:

```typescript
const batch = mnee.batch();
```

### Available Methods

#### Get UTXOs for Multiple Addresses

Retrieve UTXOs for multiple addresses with automatic chunking and error handling.

```typescript
const addresses = [
  '1Address1...',
  '1Address2...',
  '1Address3...'
];

const result = await batch.getUtxos(addresses, {
  onProgress: (completed, total, errors) => {
    console.log(`Progress: ${completed}/${total} chunks, Errors: ${errors}`);
  }
});

console.log('Results:', result.results);
console.log('Errors:', result.errors);
```

#### Get Balances for Multiple Addresses

Efficiently retrieve balances for multiple addresses.

```typescript
const addresses = ['1Address1...', '1Address2...', '1Address3...'];

const result = await batch.getBalances(addresses);

// Calculate total balance
const totalBalance = result.results.reduce(
  (sum, balance) => sum + balance.decimalAmount, 
  0
);
console.log(`Total: ${totalBalance} MNEE`);
```

#### Get Transaction Histories

Retrieve transaction histories for multiple addresses with custom parameters.

```typescript
const params = [
  { address: 'address1', limit: 100 },
  { address: 'address2', fromScore: 850000, limit: 50 },
  { address: 'address3', limit: 200 }
];

const result = await batch.getTxHistories(params);
```

#### Parse Multiple Transactions

Parse multiple transactions with optional extended data.

```typescript
const txids = [
  'txid1...',
  'txid2...',
  'txid3...'
];

const result = await batch.parseTx(txids, {
  parseOptions: { includeRaw: true }
});

// Access parsed transactions
result.results.forEach(({ txid, parsed }) => {
  console.log(`${txid}: ${parsed.isValid ? 'Valid' : 'Invalid'}`);
});
```

### Configuration Options

All batch methods support the following options:

```typescript
interface BatchOptions {
  /** Maximum items per API call (default: 20) */
  chunkSize?: number;
  
  /** API requests per second limit (default: 3) */
  requestsPerSecond?: number;
  
  /** Continue processing if an error occurs (default: false) */
  continueOnError?: boolean;
  
  /** Maximum retries per chunk (default: 3) */
  maxRetries?: number;
  
  /** Retry delay in milliseconds (default: 1000) */
  retryDelay?: number;
  
  /** Progress callback (reports chunk progress, not individual items) */
  onProgress?: (completed: number, total: number, errors: number) => void;
}
```

### Response Structure

All batch operations return a `BatchResult`:

```typescript
interface BatchResult<T> {
  results: T[];           // Successful results
  errors: BatchError[];   // Errors encountered
  totalProcessed: number; // Total chunks processed
  totalErrors: number;    // Total errors
}

interface BatchError {
  items: string[];       // Items that failed
  error: {
    message: string;     // Error message
    code?: string;       // Optional error code
  };
  retryCount: number;    // Number of retries attempted
}
```

### Common Use Cases

#### Portfolio Balance Check

```typescript
async function getPortfolioBalance(mnee, addresses) {
  const batch = mnee.batch();
  const result = await batch.getBalances(addresses, {
    chunkSize: 50,
    continueOnError: true,
    onProgress: (completed, total) => {
      console.log(`Processed ${completed}/${total} batches`);
    }
  });

  // Calculate statistics
  const stats = {
    totalBalance: 0,
    addressesWithBalance: 0,
    errors: result.errors.length
  };

  result.results.forEach(balance => {
    stats.totalBalance += balance.decimalAmount;
    if (balance.amount > 0) {
      stats.addressesWithBalance++;
    }
  });

  return stats;
}
```

#### UTXO Collection

```typescript
async function collectAllUtxos(mnee, addresses) {
  const batch = mnee.batch();
  const result = await batch.getUtxos(addresses, {
    continueOnError: true,
    chunkSize: 100
  });

  // Flatten UTXOs
  const allUtxos = result.results.flatMap(r => r.utxos);
  
  // Calculate total value
  const totalValue = allUtxos.reduce(
    (sum, utxo) => sum + utxo.data.bsv21.amt, 
    0
  );

  console.log(`Found ${allUtxos.length} UTXOs`);
  console.log(`Total value: ${mnee.fromAtomicAmount(totalValue)} MNEE`);
  
  return allUtxos;
}
```

#### Transaction Analysis

```typescript
async function analyzeTransactions(mnee, txids) {
  const batch = mnee.batch();
  const result = await batch.parseTx(txids, {
    parseOptions: { includeRaw: true },
    continueOnError: true,
    onProgress: (completed, total, errors) => {
      const percentage = Math.round((completed / total) * 100);
      console.log(`${percentage}% complete (${errors} errors)`);
    }
  });

  const analysis = {
    valid: 0,
    invalid: 0,
    types: {},
    totalFees: 0
  };

  result.results.forEach(({ parsed }) => {
    if (parsed.isValid) {
      analysis.valid++;
      analysis.types[parsed.type] = (analysis.types[parsed.type] || 0) + 1;
      
      const fee = parseInt(parsed.inputTotal) - parseInt(parsed.outputTotal);
      analysis.totalFees += fee;
    } else {
      analysis.invalid++;
    }
  });

  return analysis;
}
```

#### Error Recovery

```typescript
async function robustBatchProcess(mnee, addresses) {
  const batch = mnee.batch();
  const result = await batch.getBalances(addresses, {
    continueOnError: true,
    maxRetries: 5,
    retryDelay: 2000
  });

  // Process successful results
  console.log(`Successfully processed: ${result.results.length}`);

  // Handle errors
  if (result.errors.length > 0) {
    console.log(`Failed items: ${result.totalErrors}`);
    
    // Retry failed addresses individually
    for (const error of result.errors) {
      console.log(`Error for ${error.items.join(', ')}: ${error.error.message}`);
      
      // Could implement custom retry logic here
    }
  }

  return result;
}
```

#### High-Performance Processing

```typescript
async function highPerformanceScan(mnee, addresses) {
  const batch = mnee.batch();
  // Adjust for higher API limits if available
  const result = await batch.getUtxos(addresses, {
    chunkSize: 100,        // Larger chunks
    requestsPerSecond: 10, // Higher rate limit
    continueOnError: true
  });

  return result;
}
```

#### Progress Monitoring

```typescript
async function monitoredBatchOperation(mnee, addresses) {
  const batch = mnee.batch();
  const startTime = Date.now();
  
  const result = await batch.getBalances(addresses, {
    onProgress: (completed, total, errors) => {
      const elapsed = Date.now() - startTime;
      const rate = completed / (elapsed / 1000);
      const remaining = total - completed;
      const eta = remaining / rate;
      
      console.log(`Progress: ${completed}/${total}`);
      console.log(`Rate: ${rate.toFixed(2)} chunks/sec`);
      console.log(`ETA: ${eta.toFixed(0)} seconds`);
      console.log(`Errors: ${errors}`);
    }
  });

  const totalTime = (Date.now() - startTime) / 1000;
  console.log(`Completed in ${totalTime.toFixed(2)} seconds`);
  
  return result;
}
```

#### Batch History Export

```typescript
async function exportAllHistories(mnee, addresses, outputFile) {
  const batch = mnee.batch();
  const params = addresses.map(addr => ({ 
    address: addr, 
    limit: 1000 
  }));

  const result = await batch.getTxHistories(params, {
    chunkSize: 10,
    continueOnError: true
  });

  // Convert to CSV
  const csv = ['Address,TxID,Type,Amount,Status'];
  
  result.results.forEach(history => {
    history.history.forEach(tx => {
      csv.push([
        history.address,
        tx.txid,
        tx.type,
        mnee.fromAtomicAmount(tx.amount),
        tx.status
      ].join(','));
    });
  });

  return csv.join('\n');
}
```

### Best Practices

#### Chunk Size Optimization

* **Small operations**: Use default chunk size (20)
* **Large datasets**: Increase to 50-100 for better performance
* **Rate-limited APIs**: Reduce chunk size to avoid hitting limits

#### Error Handling

* Use `continueOnError: true` for resilient processing
* Check the `errors` array in the response
* Implement custom retry logic for critical operations

#### Performance Tips

* Adjust `requestsPerSecond` based on your API limits
* Use progress callbacks for long-running operations
* Process results as they complete rather than waiting for all

#### Memory Management

* For very large datasets, process results incrementally
* Clear processed data from memory when no longer needed
* Consider streaming results to disk for massive operations

### Rate Limiting

The batch system includes intelligent rate limiting:

* Respects the configured `requestsPerSecond` limit
* Automatically handles concurrent request management
* Ensures minimum delay between API calls
* Works efficiently even with fractional rates (e.g., 0.5 requests/second)

### Error Types

Common errors you might encounter:

1. **Invalid Input**: Empty or malformed addresses/txids
2. **API Errors**: Network issues or service unavailability
3. **Rate Limit**: Exceeded API rate limits
4. **Validation Errors**: Invalid Bitcoin addresses or transaction IDs

### See Also

* [Get UTXOs](/mnee-sdk/get-utxos) - Single address UTXO retrieval
* [Balance](/mnee-sdk/check-balance) - Single address balance check
* [Transaction History](/mnee-sdk/tx-history) - Single address history
* [Parse Transaction](/utils/transaction-parsing) - Single transaction parsing


# MNEE API

Welcome to the MNEE API Reference! This section provides detailed information about the available methods, parameters, and responses for interacting **directly** with the MNEE API. The API is designed to be intuitive and easy to use, allowing you to manage and take full control of your integration.

{% hint style="success" %}
Notice that you can click **"Test It"** on the right side of the api docs. Once the testing interface opens, you can switch between the sandbox endpoint and the production endpoint by clicking the URL up top.\
\
You will need to enter your [API KEY](/getting-started/authentication) for the `auth_token`
{% endhint %}

## Get config

> Get MNEE configuration and fee structure

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.ConfigResponse":{"type":"object","properties":{"approver":{"type":"string"},"decimals":{"type":"integer"},"feeAddress":{"type":"string"},"burnAddress":{"type":"string"},"mintAddress":{"type":"string"},"fees":{"type":"array","items":{"$ref":"#/components/schemas/mnee.ConfigFee"}},"tokenId":{"type":"string"}}},"mnee.ConfigFee":{"type":"object","properties":{"fee":{"type":"integer"},"max":{"type":"integer"},"min":{"type":"integer"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v1/config":{"get":{"summary":"Get config","description":"Get MNEE configuration and fee structure","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.ConfigResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## Get all MNEE transactions

> Gather a list of all MNEE transactions

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.TxResult":{"type":"object","properties":{"blkHash":{"type":"string"},"blkTime":{"type":"integer"},"height":{"type":"integer"},"idx":{"type":"integer"},"outs":{"type":"array","items":{"type":"integer"}},"rawtx":{"type":"string"},"receivers":{"type":"array","items":{"type":"string"}},"score":{"type":"number"},"senders":{"type":"array","items":{"type":"string"}},"txid":{"type":"string"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v1/sync":{"get":{"summary":"Get all MNEE transactions","description":"Gather a list of all MNEE transactions","parameters":[{"name":"from","in":"query","description":"Min score","schema":{"type":"integer"}},{"name":"limit","in":"query","description":"Maximum number of transactions to return","schema":{"type":"integer","default":1000}}],"responses":{"200":{"description":"Signed transactions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/mnee.TxResult"}}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## Get transactions for specific addresses

> Gather a list of all MNEE transactions for a batch of addresses

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.TxResult":{"type":"object","properties":{"blkHash":{"type":"string"},"blkTime":{"type":"integer"},"height":{"type":"integer"},"idx":{"type":"integer"},"outs":{"type":"array","items":{"type":"integer"}},"rawtx":{"type":"string"},"receivers":{"type":"array","items":{"type":"string"}},"score":{"type":"number"},"senders":{"type":"array","items":{"type":"string"}},"txid":{"type":"string"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v1/sync":{"post":{"summary":"Get transactions for specific addresses","description":"Gather a list of all MNEE transactions for a batch of addresses","parameters":[{"name":"from","in":"query","schema":{"type":"integer"}},{"name":"limit","in":"query","description":"Maximum number of transactions to return per address (defaults to 0, which returns all transactions)","schema":{"type":"integer","default":0}},{"name":"order","in":"query","description":"Sync Order","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"responses":{"200":{"description":"Signed transactions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/mnee.TxResult"}}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## POST /v1/transfer

> Submit a partially-signed transfer transaction

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.TransactionData":{"type":"object","properties":{"rawtx":{"type":"string"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v1/transfer":{"post":{"summary":"Submit a partially-signed transfer transaction","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.TransactionData"}}}},"responses":{"200":{"description":"Signed transaction","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.TransactionData"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## Transfer Mnee tokens

> Validates and transfers mnee transactions. Returns a ticket ID for tracking the transaction.

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.TransactionData":{"type":"object","properties":{"rawtx":{"type":"string"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v2/transfer":{"post":{"summary":"Transfer Mnee tokens","description":"Validates and transfers mnee transactions. Returns a ticket ID for tracking the transaction.","tags":["Transactions"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.TransactionData"}}}},"responses":{"200":{"description":"Transfer submitted successfully. Returns ticket ID for tracking.","content":{"text/plain":{"schema":{"type":"string","description":"Ticket ID for tracking the transfer transaction"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## Get Ticket

> Returns Ticket By Ticket ID

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.Ticket":{"type":"object","properties":{"action_requested":{"type":"string"},"callback_url":{"type":"string"},"createdAt":{"type":"string"},"errors":{"type":"string"},"id":{"type":"string"},"status":{"type":"string"},"tx_hex":{"type":"string"},"tx_id":{"type":"string"},"updatedAt":{"type":"string"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v2/ticket":{"get":{"summary":"Get Ticket","description":"Returns Ticket By Ticket ID","tags":["Ticket"],"parameters":[{"name":"ticketID","in":"query","required":true,"schema":{"type":"string"},"description":"Ticket ID"}],"responses":{"200":{"description":"Ticket response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.Ticket"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## POST /v1/utxos

> Retrieve UTXOs for a batch of addresses

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.Txo":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/mnee.Data"},"height":{"type":"integer"},"idx":{"type":"integer"},"outpoint":{"type":"string"},"owners":{"type":"array","items":{"type":"string"}},"satoshis":{"type":"integer"},"score":{"type":"number"},"script":{"type":"string"},"txid":{"type":"string"},"vout":{"type":"integer"}}},"mnee.Data":{"type":"object","properties":{"bsv21":{"$ref":"#/components/schemas/mnee.Bsv21"},"cosign":{"$ref":"#/components/schemas/mnee.Cosign"}}},"mnee.Bsv21":{"type":"object","properties":{"amt":{"type":"integer"},"dec":{"type":"integer"},"icon":{"type":"string"},"id":{"type":"string"},"op":{"type":"string"},"sym":{"type":"string"}}},"mnee.Cosign":{"type":"object","properties":{"address":{"type":"string"},"cosigner":{"type":"string"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v1/utxos":{"post":{"summary":"Retrieve UTXOs for a batch of addresses","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"responses":{"200":{"description":"UTXOs","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/mnee.Txo"}}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## Get Utxos by addresses

> Returns the array of unspent utxos

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.Txo":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/mnee.Data"},"height":{"type":"integer"},"idx":{"type":"integer"},"outpoint":{"type":"string"},"owners":{"type":"array","items":{"type":"string"}},"satoshis":{"type":"integer"},"score":{"type":"number"},"script":{"type":"string"},"txid":{"type":"string"},"vout":{"type":"integer"}}},"mnee.Data":{"type":"object","properties":{"bsv21":{"$ref":"#/components/schemas/mnee.Bsv21"},"cosign":{"$ref":"#/components/schemas/mnee.Cosign"}}},"mnee.Bsv21":{"type":"object","properties":{"amt":{"type":"integer"},"dec":{"type":"integer"},"icon":{"type":"string"},"id":{"type":"string"},"op":{"type":"string"},"sym":{"type":"string"}}},"mnee.Cosign":{"type":"object","properties":{"address":{"type":"string"},"cosigner":{"type":"string"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v2/utxos":{"post":{"summary":"Get Utxos by addresses","description":"Returns the array of unspent utxos","tags":["Transactions"],"parameters":[{"name":"page","in":"query","schema":{"type":"string","default":"1"},"description":"Default: 1"},{"name":"size","in":"query","schema":{"type":"string","default":"10"},"description":"Default: 10"},{"name":"order","in":"query","schema":{"type":"string"},"description":"Sync Order"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"Addresses to lookup for utxos"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/mnee.Txo"}}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## Fetch balances for given addresses

> Accepts a list of addresses and returns their balances.

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.BalanceData":{"type":"object","properties":{"address":{"type":"string"},"amt":{"type":"number"},"precised":{"type":"number"}}}}},"paths":{"/v2/balance":{"post":{"summary":"Fetch balances for given addresses","description":"Accepts a list of addresses and returns their balances.","tags":["balances"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"List of addresses"}}}},"responses":{"200":{"description":"Balances result","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/mnee.BalanceData"}}}}},"400":{"description":"Invalid JSON payload","content":{"text/plain":{"schema":{"type":"string"}}}},"405":{"description":"Method Not Allowed","content":{"text/plain":{"schema":{"type":"string"}}}}}}}}}
```

## GET /v1/tx/{txid}

> Retrieve transaction

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"servers":[{"url":"https://sandbox-proxy-api.mnee.net","description":"Sandbox Environment"},{"url":"https://proxy-api.mnee.net","description":"Production Environment"}],"security":[{"AuthToken":[]}],"components":{"securitySchemes":{"AuthToken":{"type":"apiKey","in":"query","name":"auth_token"}},"schemas":{"mnee.TransactionData":{"type":"object","properties":{"rawtx":{"type":"string"}}},"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}},"paths":{"/v1/tx/{txid}":{"get":{"summary":"Retrieve transaction","parameters":[{"name":"txid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Transaction data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.TransactionData"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/mnee.HttpError"}}}}}}}}}
```

## The mnee.BalanceData object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.BalanceData":{"type":"object","properties":{"address":{"type":"string"},"amt":{"type":"number"},"precised":{"type":"number"}}}}}}
```

## The mnee.Bsv21 object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.Bsv21":{"type":"object","properties":{"amt":{"type":"integer"},"dec":{"type":"integer"},"icon":{"type":"string"},"id":{"type":"string"},"op":{"type":"string"},"sym":{"type":"string"}}}}}}
```

## The mnee.ConfigFee object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.ConfigFee":{"type":"object","properties":{"fee":{"type":"integer"},"max":{"type":"integer"},"min":{"type":"integer"}}}}}}
```

## The mnee.ConfigResponse object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.ConfigResponse":{"type":"object","properties":{"approver":{"type":"string"},"decimals":{"type":"integer"},"feeAddress":{"type":"string"},"burnAddress":{"type":"string"},"mintAddress":{"type":"string"},"fees":{"type":"array","items":{"$ref":"#/components/schemas/mnee.ConfigFee"}},"tokenId":{"type":"string"}}},"mnee.ConfigFee":{"type":"object","properties":{"fee":{"type":"integer"},"max":{"type":"integer"},"min":{"type":"integer"}}}}}}
```

## The mnee.Cosign object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.Cosign":{"type":"object","properties":{"address":{"type":"string"},"cosigner":{"type":"string"}}}}}}
```

## The mnee.Data object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.Data":{"type":"object","properties":{"bsv21":{"$ref":"#/components/schemas/mnee.Bsv21"},"cosign":{"$ref":"#/components/schemas/mnee.Cosign"}}},"mnee.Bsv21":{"type":"object","properties":{"amt":{"type":"integer"},"dec":{"type":"integer"},"icon":{"type":"string"},"id":{"type":"string"},"op":{"type":"string"},"sym":{"type":"string"}}},"mnee.Cosign":{"type":"object","properties":{"address":{"type":"string"},"cosigner":{"type":"string"}}}}}}
```

## The mnee.HttpError object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.HttpError":{"type":"object","properties":{"code":{"type":"integer"},"message":{"type":"string"}}}}}}
```

## The mnee.Ticket object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.Ticket":{"type":"object","properties":{"action_requested":{"type":"string"},"callback_url":{"type":"string"},"createdAt":{"type":"string"},"errors":{"type":"string"},"id":{"type":"string"},"status":{"type":"string"},"tx_hex":{"type":"string"},"tx_id":{"type":"string"},"updatedAt":{"type":"string"}}}}}}
```

## The mnee.TransactionData object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.TransactionData":{"type":"object","properties":{"rawtx":{"type":"string"}}}}}}
```

## The mnee.TxResult object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.TxResult":{"type":"object","properties":{"blkHash":{"type":"string"},"blkTime":{"type":"integer"},"height":{"type":"integer"},"idx":{"type":"integer"},"outs":{"type":"array","items":{"type":"integer"}},"rawtx":{"type":"string"},"receivers":{"type":"array","items":{"type":"string"}},"score":{"type":"number"},"senders":{"type":"array","items":{"type":"string"}},"txid":{"type":"string"}}}}}}
```

## The mnee.Txo object

```json
{"openapi":"3.0.3","info":{"title":"Mnee API","version":"1.0"},"components":{"schemas":{"mnee.Txo":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/mnee.Data"},"height":{"type":"integer"},"idx":{"type":"integer"},"outpoint":{"type":"string"},"owners":{"type":"array","items":{"type":"string"}},"satoshis":{"type":"integer"},"score":{"type":"number"},"script":{"type":"string"},"txid":{"type":"string"},"vout":{"type":"integer"}}},"mnee.Data":{"type":"object","properties":{"bsv21":{"$ref":"#/components/schemas/mnee.Bsv21"},"cosign":{"$ref":"#/components/schemas/mnee.Cosign"}}},"mnee.Bsv21":{"type":"object","properties":{"amt":{"type":"integer"},"dec":{"type":"integer"},"icon":{"type":"string"},"id":{"type":"string"},"op":{"type":"string"},"sym":{"type":"string"}}},"mnee.Cosign":{"type":"object","properties":{"address":{"type":"string"},"cosigner":{"type":"string"}}}}}}
```


# MNEE CLI

The MNEE CLI tool is a command-line interface for managing MNEE tokens and wallets. It provides a secure way to create, manage, and interact with MNEE wallets across different environments.

The open source repo can be [found here](https://github.com/mnee-xyz/mnee-cli).

### Installation

```bash
npm install -g @mnee/cli
```

### Available Commands

#### Create a New Wallet

```bash
mnee create
```

Creates a new wallet with the following features:

* Generates a new private key
* Securely stores the encrypted key
* Allows naming the wallet
* Supports both production and sandbox environments
* Sets up password protection

#### View Wallet Address

```bash
mnee address
```

Displays the address of your currently active wallet.

#### Check Balance

```bash
mnee balance
```

Shows the current MNEE balance of your active wallet.

#### View Transaction History

```bash
mnee history
```

Displays the transaction history for your active wallet.Options:

* -u, --unconfirmed: Show only unconfirmed transactions
* -f, --fresh: Clear cache and fetch fresh history from the beginning

#### Transfer MNEE

```bash
mnee transfer
```

Initiates a transfer of MNEE tokens to another address. The command will:

1. Prompt for the recipient's address
2. Ask for the amount to transfer
3. Request your wallet password for security
4. Execute the transfer and provide a transaction ID

#### Export Private Key

```bash
mnee export
```

Exports your wallet's private key in WIF format. This command:

* Requires your wallet password
* Includes a security confirmation step
* Displays the key in a secure format

#### Delete a Wallet

```bash
mnee delete <walletName>
```

Deletes a specified wallet. Features:

* Requires password confirmation
* Automatically switches to another wallet if available
* Cannot be undone

#### List Wallets

```bash
mnee list
```

Shows all your wallets and allows you to:

* View all wallet names and addresses
* See which wallet is currently active
* Switch between wallets

#### Rename a Wallet

```bash
mnee rename <oldName> <newName>
```

Renames an existing wallet. The new name must:

* Be between 1-50 characters
* Contain only letters, numbers, hyphens, and underscores
* Not contain spaces
* Be unique among your wallets

#### Import Existing Wallet

```bash
mnee import
```

Imports an existing wallet using a WIF private key. The process:

1. Prompts for the WIF key
2. Allows setting a new name
3. Requires password creation
4. Encrypts and stores the key securely

### Authentication & Developer Portal

#### Login

```bash
mnee login
```

Authenticate with the MNEE Developer Portal

#### Logout

```bash
mnee logout
```

Sign out of the MNEE Developer Portal

#### Who Am I?

```bash
mnee whoami
```

Show current authenticated user information

#### Faucet

```bash
mnee faucet
```

Request sandbox tokens (requires authentication)

{% hint style="warning" %}
The faucet command is only available in the sandbox environment
{% endhint %}

{% hint style="info" %}
You can also use -a \<address> or --address \<address> to specify a specific deposit address
{% endhint %}

***

### Environment Support

The CLI supports two environments:

* Production: For real MNEE tokens
* Sandbox: For testing purposes

### Security Features

* All private keys are encrypted before storage
* Password protection for sensitive operations
* Secure key storage using system keychain
* Confirmation prompts for dangerous operations

### Best Practices

1. Always keep your password secure
2. Regularly backup your wallet information
3. Use the sandbox environment for testing
4. Never share your private key or password
5. Use strong, unique passwords for each wallet

### Error Handling

The CLI includes comprehensive error handling for:

* Invalid commands
* Network issues
* Authentication failures
* Invalid inputs
* Duplicate wallet names
* Incorrect passwords


# Coin Faucet

## Faucet

The Faucet feature allows you to request a small amount of MNEE tokens (sandbox) for testing and development purposes. This section provides a simple way to receive tokens using the [MNEE CLI](/dev-tools/mnee-cli).

### 1. Make sure you have created a Developer Account.

<a href="https://developer.mnee.net/" class="button primary" data-icon="code">Developer Portal</a>

### 2. Install the [MNEE CLI](/dev-tools/mnee-cli)

```bash
npm i -g @mnee/cli
```

### 3. Create a MNEE *<mark style="color:orange;">Sandbox</mark>* Wallet

```bash
mnee create
```

{% hint style="warning" %}
Be sure to select "Sandbox"
{% endhint %}

### 4. Connect to the Dev Portal

```bash
mnee login
```

### 4. Request tokens

```bash
mnee faucet
```

### 6. Check your balance

```bash
mnee balance
```

{% hint style="success" %}
At this point you should have tokens to work with in the Sandbox [CLI](/dev-tools/mnee-cli) and [SDK](/mnee-sdk/config) environments. Go forth and change the world!
{% endhint %}

### Usage Guidelines

* Rate Limiting: You can request tokens once every 24 hours based on the API key used.
* Amount: Each request will send 10 MNEE tokens


# WOC Plugin

MNEE is fully supported by the [whatsonchain.com](https://whatsonchain.com/) block explorer and has a convenient plugin that makes inspecting MNEE transactions a breeze.

### Example Transactions

Production: <https://whatsonchain.com/tx/8246a52e12bf3066742f5bca6fc8b06531a07d551e9e7ba12dc4cbf279d50d79?tab=m8eqcrbs>

Sandbox: <https://whatsonchain.com/tx/7a2f10a7210d8d32ce379290fd6feee12fa862ec17c5760f8d21341b142d48a6?tab=m8eqcrbs>

{% hint style="warning" %}
Take note of the `tab` query param appended to the end of the example transaction URLs. We suggest always adding `?tab=m8eqcrbs` to any reference links within your application. This will automatically launch the MNEE plugin and give a nice clean view of the transaction for your users.
{% endhint %}

### Normal Usage

When searching for transactions directly, you can still access the plugin manually.

<figure><img src="/files/o9Yt0hEl9JONK2RwZ2DG" alt=""><figcaption></figcaption></figure>

***

<figure><img src="/files/tWHM7NHsKf6YF7MEnFyd" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
The plugin supports both [production](https://whatsonchain.com/tx/8246a52e12bf3066742f5bca6fc8b06531a07d551e9e7ba12dc4cbf279d50d79?tab=m8eqcrbs) and [sandbox](https://whatsonchain.com/tx/7a2f10a7210d8d32ce379290fd6feee12fa862ec17c5760f8d21341b142d48a6?tab=m8eqcrbs) related transactions. Sandbox transactions are denoted as being test coins to avoid any confusion as shown below.
{% endhint %}

<figure><img src="/files/GenAqmqZi9ETckDR7kXp" alt=""><figcaption></figcaption></figure>


# LLM Context

This document provides comprehensive documentation for the MNEE SDK, designed to give LLMs full context for working with the SDK.

````markdown
# MNEE SDK Complete Reference

This document provides comprehensive documentation for the MNEE SDK, designed to give LLMs full context for working with the SDK.

## Table of Contents

1. [Setup and Configuration](#setup-and-configuration)
2. [Core Methods](#core-methods)
3. [Batch Operations](#batch-operations)
4. [HD Wallet](#hd-wallet)
5. [Type Definitions](#type-definitions)
6. [Webhook Support](#webhook-support)

## Setup and Configuration

### Installation and Initialization

```typescript
import Mnee from '@mnee/ts-sdk';

// Initialize MNEE SDK
const mnee = new Mnee({ 
  environment: 'production', // or 'sandbox' (required)
  apiKey: 'your-api-key'     // optional but recommended
});

// All types are also exported from the main module
import { 
  MNEEBalance, 
  MNEEUtxo, 
  TransferResponse,
  HDWallet 
  // ... and more
} from '@mnee/ts-sdk';
```

#### SdkConfig Type

```typescript
type SdkConfig = {
  environment: 'production' | 'sandbox';
  apiKey?: string;
};
```

### Configuration

The `config()` method retrieves the current MNEE service configuration including fee structure and system addresses.

```typescript
const config = await mnee.config();
```

#### Response Structure

```typescript
interface MNEEConfig {
  approver: string;      // Cosigner public key
  feeAddress: string;    // Fee collection address
  burnAddress: string;   // Burn operations address
  mintAddress: string;   // Mint operations address
  fees: FeeTier[];       // Fee structure tiers
}

interface FeeTier {
  min: number;    // Minimum amount (atomic units)
  max: number;    // Maximum amount (atomic units)
  fee: number;    // Fee amount (atomic units)
}
```

## Core Methods

### Balance Operations

#### Single Address Balance

```typescript
const balance = await mnee.balance('address');
// Returns: { address: string, amount: number, decimalAmount: number }
```

#### Multiple Address Balances

```typescript
const balances = await mnee.balances(['address1', 'address2']);
// Returns: MNEEBalance[]
```

### UTXO Operations

```typescript
// Single address (returns up to 10 UTXOs by default)
const utxos = await mnee.getUtxos('address');

// With pagination
const utxos = await mnee.getUtxos('address', 0, 100, 'desc');
// Parameters: address, page, size (max 1000), order ('asc' | 'desc')

// Multiple addresses
const utxos = await mnee.getUtxos(['address1', 'address2'], 0, 50);
// Returns: MNEEUtxo[]

// Get just enough UTXOS for a specific amount (optimized for transfers)
const requiredAmount = mnee.toAtomicAmount(5.0); // Convert 5 MNEE to atomic units
const enoughUtxos = await mnee.getEnoughUtxos('address', requiredAmount); // Returns: MNEEUtxo[] - stops fetching once sufficient amount is reached

// Get all Utxos for an address (comprehensive wallet view)
const allUtxos = await mnee.getAllUtxos('address'); // Returns: MNEEUtxo[] - fetchtes every UTXO for the address

```

#### UTXO Structure (BSV21)

```typescript
interface MNEEUtxo {
  txid: string;
  vout: number;
  outpoint: string;  // "txid_vout"
  satoshis: number;
  accSats: number;
  script: string;
  owners: string[];
  data: {
    types: string[];
    insc: {
      json: any;
      text: string;
      words: string[];
      file: {
        hash: string;
        size: number;
        type: string;
      };
    };
    map: { [key: string]: any };
    b: {
      hash: string;
      size: number;
      type: string;
    };
    sigmas: Array<{ algorithm: string; address: string; signature: string; index?: number }>;
    list: {
      payout: Array<{ address: string; value: number }>;
      lock: { until: number };
    };
    bsv20: { [key: string]: any };
    bsv21: {
      id: string;    // Token ID
      p: string;     // Protocol
      op: string;    // Operation
      amt: number;   // Amount in atomic units
      sym: string;   // Symbol
      icon: string;  // Icon URL
      dec: number;   // Decimals
    };
  };
}
```

### Transfer Operations

#### Simple Transfer

```typescript
const recipients: SendMNEE[] = [
  { address: 'recipient1', amount: 10.5 },
  { address: 'recipient2', amount: 5.25 }
];

const response = await mnee.transfer(
  recipients, 
  'sender-private-key-wif',
  { broadcast: true, callbackUrl: 'https://your-api.com/webhook' }  // optional
);
// Returns: TransferResponse

// Get transaction ID from status
const status = await mnee.getTxStatus(response.ticketId);
console.log('Transaction ID:', status.tx_id);
```

#### Multi-Source Transfer

```typescript
const options: TransferMultiOptions = {
  inputs: [
    { txid: 'txid1', vout: 0, wif: 'wif1' },
    { txid: 'txid2', vout: 1, wif: 'wif2' }
  ],
  recipients: [
    { address: 'recipient1', amount: 15.75 },
    { address: 'recipient2', amount: 8.50 }
  ],
  changeAddress: 'change-address' // optional
};

const response = await mnee.transferMulti(options, { broadcast: true });
// Returns: TransferResponse

// Get transaction ID from status
const status = await mnee.getTxStatus(response.ticketId);
console.log('Transaction ID:', status.tx_id);
```

#### Transfer Response

```typescript
interface TransferResponse {
  ticketId?: string;  // Ticket ID for tracking (only if broadcast is true)
  rawtx?: string;     // The raw transaction hex (only if broadcast is false)
}
```

#### Transaction Status

```typescript
const status = await mnee.getTxStatus(ticketId);
// Returns: TransferStatus

interface TransferStatus {
  id: string;
  tx_id: string;
  tx_hex: string;
  action_requested: 'transfer';
  status: 'BROADCASTING' | 'SUCCESS' | 'MINED' | 'FAILED';
  createdAt: string;
  updatedAt: string;
  errors: string | null;
}
```

### Transaction Validation

```typescript
const isValid = await mnee.validateMneeTx(rawTxHex);
// Or with expected recipients
const isValid = await mnee.validateMneeTx(rawTxHex, recipients);
// Returns: boolean
```

### Submit Raw Transaction

```typescript
const response = await mnee.submitRawTx(rawTxHex, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook'  // optional
});
// Returns: TransferResponse with ticketId

// Get transaction ID from status
const status = await mnee.getTxStatus(response.ticketId);
console.log('Transaction ID:', status.tx_id);
```

### Unit Conversion

```typescript
// Convert MNEE to atomic units (1 MNEE = 100,000 atomic)
const atomic = mnee.toAtomicAmount(1.5);  // Returns: 150000

// Convert atomic units to MNEE
const mneeAmount = mnee.fromAtomicAmount(150000);  // Returns: 1.5
```

### Transaction History

#### Single Address History

```typescript
const history = await mnee.recentTxHistory(address, fromScore, limit);
// Returns: TxHistoryResponse
```

#### Multiple Address Histories

```typescript
const params: AddressHistoryParams[] = [
  { address: 'address1', limit: 100 },
  { address: 'address2', fromScore: 850000, limit: 50 }
];
const histories = await mnee.recentTxHistories(params);
// Returns: TxHistoryResponse[]
```

#### History Response Structure

```typescript
interface TxHistoryResponse {
  address: string;
  history: TxHistory[];
  nextScore: number;
}

interface TxHistory {
  txid: string;
  height: number;          // 0 for unconfirmed
  status: 'confirmed' | 'unconfirmed';
  type: 'send' | 'receive';
  amount: number;          // Atomic units
  counterparties: Array<{ address: string; amount: number }>;
  fee: number;
  score: number;           // For pagination
}
```

### Transaction Parsing

#### Parse by Transaction ID

```typescript
const parsed = await mnee.parseTx(txid);
// With extended data
const parsed = await mnee.parseTx(txid, { includeRaw: true });
// Returns: ParseTxResponse | ParseTxExtendedResponse
```

#### Parse from Raw Transaction

```typescript
const parsed = await mnee.parseTxFromRawTx(rawTxHex);
// With extended data
const parsed = await mnee.parseTxFromRawTx(rawTxHex, { includeRaw: true });
```

#### Parse Response Structure

```typescript
interface ParseTxResponse {
  txid: string;
  environment: 'production' | 'sandbox';
  type: string;  // 'transfer', 'burn', etc.
  inputs: Array<{ address: string; amount: number }>;
  outputs: Array<{ address: string; amount: number }>;
  isValid: boolean;
  inputTotal: string;   // String to preserve precision
  outputTotal: string;  // String to preserve precision
}

interface ParseTxExtendedResponse extends ParseTxResponse {
  raw: {
    txHex: string;
    inputs: Array<{
      txid: string;
      vout: number;
      scriptSig: string;
      sequence: number;
      satoshis: number;
      address: string;
      tokenData: any;
    }>;
    outputs: Array<{
      value: number;
      scriptPubKey: string;
      address: string;
      tokenData: any;
    }>;
    version: number;
    lockTime: number;
    size: number;
    hash: string;
  };
}
```

### Script Parsing

#### Parse Inscription

```typescript
import { Script } from '@bsv/sdk';

const script = Script.fromHex('...');
const inscription = mnee.parseInscription(script);
// Returns: Inscription | undefined
```

#### Parse Cosigner Scripts

```typescript
const scripts = [Script.fromHex('...'), Script.fromHex('...')];
const cosigners = mnee.parseCosignerScripts(scripts);
// Returns: ParsedCosigner[]
```

#### Inscription Structure

```typescript
interface Inscription {
  file?: {
    hash: string;
    size: number;
    type: string;
    content: number[];
  };
  fields?: { [key: string]: any };
  parent?: string;
}

interface ParsedCosigner {
  cosigner: string;  // Public key
  address: string;   // Bitcoin address
}
```

## Batch Operations

### Setup

```typescript
const batch = mnee.batch();
```

### Batch Configuration

```typescript
interface BatchOptions {
  chunkSize?: number;         // Max items per API call (default: 20)
  requestsPerSecond?: number; // Rate limit (default: 3)
  continueOnError?: boolean;  // Continue on error (default: false)
  maxRetries?: number;        // Max retries per chunk (default: 3)
  retryDelay?: number;        // Retry delay in ms (default: 1000)
  onProgress?: (completed: number, total: number, errors: number) => void;
}
```

### Batch Methods

#### Get UTXOs

```typescript
const result = await batch.getUtxos(addresses, options);
// Returns: BatchResult<BatchUtxoResult>
```

#### Get Balances

```typescript
const result = await batch.getBalances(addresses, options);
// Returns: BatchResult<MNEEBalance>
```

#### Get Transaction Histories

```typescript
const params = addresses.map(addr => ({ address: addr, limit: 100 }));
const result = await batch.getTxHistories(params, options);
// Returns: BatchResult<TxHistoryResponse>
```

#### Parse Transactions

```typescript
const result = await batch.parseTx(txids, {
  parseOptions: { includeRaw: true },
  ...batchOptions
});
// Returns: BatchResult<BatchParseTxResult>
```

### Batch Response Structure

```typescript
interface BatchResult<T> {
  results: T[];
  errors: BatchError[];
  totalProcessed: number;
  totalErrors: number;
}

interface BatchError {
  items: string[];
  error: {
    message: string;
    code?: string;
  };
  retryCount: number;
}

interface BatchUtxoResult {
  address: string;
  utxos: MNEEUtxo[];
}

interface BatchParseTxResult {
  txid: string;
  parsed: ParseTxResponse | ParseTxExtendedResponse;
}
```

## HD Wallet

### Setup

```typescript
import Mnee, { HDWallet } from '@mnee/ts-sdk';

const mnee = new Mnee({ 
  environment: 'production',  // required
  apiKey: 'your-api-key'      // optional but recommended
});
```

### Static Methods

```typescript
// Static methods can be accessed via Mnee.HDWallet or imported HDWallet
import Mnee, { HDWallet } from '@mnee/ts-sdk';

// Generate new mnemonic (12 words)
const mnemonic = HDWallet.generateMnemonic();
// or
const mnemonic = Mnee.HDWallet.generateMnemonic();

// Validate mnemonic
const isValid = HDWallet.isValidMnemonic(mnemonic);
// or
const isValid = Mnee.HDWallet.isValidMnemonic(mnemonic);
```

### Create HD Wallet

```typescript
const hdWallet = mnee.HDWallet(mnemonic, {
  derivationPath: "m/44'/236'/0'",  // BIP44 path
  cacheSize: 1000                    // Optional cache size
});
```

### Derive Addresses

#### Single Address

```typescript
// Receive address (change = false)
const addressInfo = hdWallet.deriveAddress(0, false);
// Change address (change = true)
const changeInfo = hdWallet.deriveAddress(0, true);

// AddressInfo structure
{
  address: string;      // Bitcoin address
  privateKey: string;   // WIF format
  path: string;         // Full derivation path
}
```

#### Multiple Addresses

```typescript
const addresses = await hdWallet.deriveAddresses(0, 10, false);
// Returns: AddressInfo[]
```

### Get Private Keys

```typescript
// Get private keys for specific addresses
const result = hdWallet.getPrivateKeysForAddresses(addresses, {
  maxScanReceive: 10000,
  maxScanChange: 10000,
  scanStrategy: 'parallel'  // or 'sequential'
});
// Returns: { privateKeys: {}, paths: {} }

// Simplified version
const privateKeys = hdWallet.getPrivateKeys(addresses, options);
// Returns: { [address: string]: string }
```

### Scan with Gap Limit

```typescript
const checkAddressUsed = async (address) => {
  const balance = await mnee.balance(address);
  return balance.amount > 0;
};

const discovered = await hdWallet.scanAddressesWithGapLimit(
  checkAddressUsed,
  {
    gapLimit: 20,
    scanChange: true,
    maxScan: 10000
  }
);
// Returns: { receive: AddressInfo[], change: AddressInfo[] }
```

### Cache Management

```typescript
hdWallet.clearCache();
const cacheSize = hdWallet.getCacheSize();
```

## Important Notes

### Unit System
- 1 MNEE = 100,000 atomic units
- All blockchain operations use atomic units
- User-facing amounts should be in MNEE (decimal)
- SDK methods expecting amounts use MNEE values (not atomic)

### Address Validation
- Bitcoin addresses starting with 1, 3, or bc1
- Invalid addresses in batch operations are handled based on `continueOnError` setting

### Error Handling

The SDK throws standard JavaScript Error objects with descriptive messages. Common error scenarios:

#### Initialization Errors
- `"Invalid environment. Must be either 'production' or 'sandbox'"` - Invalid environment parameter
- `"MNEE API key cannot be an empty string"` - Empty API key provided
- `"Invalid API key"` - API key authentication failed

#### Validation Errors
- `"Invalid Bitcoin address: <address>"` - Address format validation failed
- `"No valid Bitcoin addresses provided"` - No valid addresses in batch
- `"Invalid transaction ID: empty or not a string"` - Invalid transaction ID format
- `"Invalid transaction ID format: <txid>"` - Transaction ID not 64 hex characters

#### Batch Operation Errors
- `"Input must be an array of addresses"` - Non-array input to batch methods
- `"Input must be an array of transaction IDs"` - Non-array input to parseTx
- `"Max retries exceeded"` - Batch operation failed after all retries

#### HD Wallet Errors
- `"Invalid mnemonic phrase"` - Invalid BIP39 mnemonic
- `"Failed to derive private key for path: <path>"` - Derivation failure
- `"Could not find private keys for <n> address(es)"` - Address not found in HD wallet scan

#### Transfer/Submit Errors (POST methods)
- `"Config not fetched"` - Failed to get cosigner configuration
- `"Insufficient MNEE balance"` - Not enough tokens for transfer
- `"Failed to broadcast transaction"` - Cosigner rejected transaction
- `"Failed to submit raw transaction"` - Submit raw tx failed

#### Error Handling Patterns

```typescript
// Basic error handling
try {
  const result = await mnee.transfer(recipients, wif);
} catch (error) {
  console.error('Transfer failed:', error.message);
}

// Batch operations with continueOnError
const result = await batch.getBalances(addresses, {
  continueOnError: true  // Continue processing on errors
});

// Check for partial failures
if (result.errors.length > 0) {
  result.errors.forEach(error => {
    console.log(`Failed addresses: ${error.items.join(', ')}`);
    console.log(`Error: ${error.error.message}`);
  });
}

// Handle API authentication errors
try {
  const result = await mnee.transfer(recipients, wif);
} catch (error) {
  if (error.message === 'Invalid API key') {
    // Handle authentication failure (401/403)
  } else if (error.message.includes('HTTP error! status:')) {
    // Handle other HTTP errors
  }
}
```

Note: When methods make POST requests to the cosigner API (transfer, transferMulti, submitRawTx), they handle HTTP 401/403 as "Invalid API key" and other HTTP errors as "HTTP error! status: {code}".

## Webhook Support

Transactions can be tracked via webhook callbacks for real-time status updates.

### Webhook Response Format

```typescript
interface TransferWebhookResponse {
  id: string;              // The ticket ID
  tx_id: string;           // The blockchain transaction ID
  tx_hex: string;          // The raw transaction hex
  action_requested: 'transfer';  // Always 'transfer' for MNEE transactions
  callback_url: string;    // Your webhook URL (for verification)
  status: 'BROADCASTING' | 'SUCCESS' | 'MINED' | 'FAILED';
  createdAt: string;       // ISO timestamp when ticket was created
  updatedAt: string;       // ISO timestamp of this update
  errors: string | null;   // Error details if status is FAILED
}
```

### Using Webhooks

```typescript
// Transfer with webhook
const response = await mnee.transfer(recipients, wif, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook'
});

// TransferMulti with webhook
const response = await mnee.transferMulti(options, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook'
});

// Submit raw transaction with webhook
const response = await mnee.submitRawTx(rawTxHex, {
  broadcast: true,
  callbackUrl: 'https://your-api.com/webhook'
});
```

### Webhook Status Flow

- **BROADCASTING** → Transaction is being broadcast to the network
- **SUCCESS** → Transaction successfully broadcast and accepted by the network
- **MINED** → Transaction has been mined into a block
- **FAILED** → Transaction failed (check `errors` field for details)

### Performance
- Batch operations automatically chunk requests
- Rate limiting prevents API throttling
- Progress callbacks report chunk completion, not individual items
- HD wallet caches derived addresses for performance

### Security
- Never store private keys or mnemonics in plain text
- Use WIF format for private keys
- HD wallet follows BIP32/BIP44 standards
- Cosigner validation available via config and script parsing
````


