# Predetermined Balances

## Overview

Predetermined balances provide fine-grained control over the exact amounts and order of transfers in an approval. Unlike traditional tally-based systems where you approve a total amount (e.g., 100 tokens) without controlling the specific combinations, predetermined balances let you explicitly define:

* **Exact amounts** that must be transferred
* **Specific order** of transfers
* **Precise token IDs and ownership times** for each transfer

**Key Principle**: The transfer will fail if the balances are not EXACTLY as defined in the predetermined balances.

## Interface Definition

```typescript
export interface PredeterminedBalances<T extends NumberType> {
    manualBalances: ManualBalances<T>[];
    incrementedBalances: IncrementedBalances<T>;
    orderCalculationMethod: PredeterminedOrderCalculationMethod;
}
```

## Balance Definition Methods

There are two mutually exclusive ways to define balances:

### 1. Manual Balances

Define an array of specific balance sets manually. Each element corresponds to a different transfer.

```json
{
    "manualBalances": [
        {
            "amount": "1",
            "tokenIds": [
                {
                    "start": "1",
                    "end": "1"
                }
            ],
            "ownershipTimes": [
                {
                    "start": "1691978400000",
                    "end": "1723514400000"
                }
            ]
        },
        {
            "amount": "5",
            "tokenIds": [
                {
                    "start": "2",
                    "end": "6"
                }
            ],
            "ownershipTimes": [
                {
                    "start": "1691978400000",
                    "end": "1723514400000"
                }
            ]
        }
    ]
}
```

**Use Case**: When you need complete control over each specific transfer amount and timing.

### 2. Incremented Balances

Define starting balances and rules for subsequent transfers. Perfect for sequential minting or time-based releases or other common patterns. Note that most options are incompatible with each other.

```json
{
    "incrementedBalances": {
        "startBalances": [
            {
                "amount": "1",
                "tokenIds": [
                    {
                        "start": "1",
                        "end": "1"
                    }
                ],
                "ownershipTimes": [
                    {
                        "start": "1691978400000",
                        "end": "1723514400000"
                    }
                ]
            }
        ],
        "incrementTokenIdsBy": "1",
        "incrementOwnershipTimesBy": "0",
        "durationFromTimestamp": "0",
        "allowOverrideTimestamp": false,
        "allowOverrideWithAnyValidToken": false,
        "allowAmountScaling": false,
        "recurringOwnershipTimes": {
            "startTime": "0",
            "intervalLength": "0",
            "chargePeriodLength": "0"
        }
    }
}
```

#### Increment Options

| Field                            | Description                                           | Example                                              |
| -------------------------------- | ----------------------------------------------------- | ---------------------------------------------------- |
| `incrementTokenIdsBy`            | Amount to increment token IDs by after each transfer  | `"1"` = next transfer gets token ID 2, then 3, etc.  |
| `incrementOwnershipTimesBy`      | Amount to increment ownership times by                | `"86400000"` = add 1 day to ownership times          |
| `durationFromTimestamp`          | Calculate ownership times from timestamp + duration   | `"2592000000"` = 30 days from transfer time          |
| `allowOverrideTimestamp`         | Allow custom timestamp override in transfer           | `true` = users can specify custom start time         |
| `allowOverrideWithAnyValidToken` | Allow any valid token ID (one) override               | `true` = users can specify any single valid token ID |
| `allowAmountScaling`             | Allow proportional integer multiples of startBalances | `true` = transfer any quantity, coinTransfers scale  |
| `maxScalingMultiplier`           | Maximum scaling multiplier (required when scaling on) | `"1000000000000"` = set high for micro-unit bases    |
| `recurringOwnershipTimes`        | Define recurring time intervals                       | Monthly subscriptions, weekly rewards                |

#### Duration From Timestamp

Dynamically calculate ownership times from a timestamp plus a set duration. This overwrites all ownership times in the starting balances.

```json
{
    "durationFromTimestamp": "2592000000", // 30 days in milliseconds
    "allowOverrideTimestamp": true
}
```

**Behavior**:

* **Default**: Uses transfer time as the base timestamp
* **Override**: If `allowOverrideTimestamp` is true, users can specify a custom timestamp in `MsgTransferTokens` `precalculationOptions`
* **Calculation**: `ownershipTime end = baseTimestamp + durationFromTimestamp - 1`
* **Overwrite**: All ownership times in starting balances are replaced with \[{ "start": baseTimestamp, "end": baseTimestamp + durationFromTimestamp - 1 }]

**Common Duration Values (milliseconds):**

| Duration  | Milliseconds |
| --------- | ------------ |
| 5 minutes | 300000       |
| 1 hour    | 3600000      |
| 1 day     | 86400000     |
| 1 week    | 604800000    |
| 30 days   | 2592000000   |
| 1 year    | 31536000000  |

#### Recurring Ownership Times

Define repeating time intervals for subscriptions or periodic rewards:

```json
{
    "recurringOwnershipTimes": {
        "startTime": "1691978400000", // When intervals begin
        "intervalLength": "2592000000", // 30 days in milliseconds
        "chargePeriodLength": "604800000" // 7 days advance charging
    }
}
```

**Example**: Monthly subscription starting August 13, 2023, with 7-day advance charging period.

#### Amount Scaling

Enable proportional transfers where users can transfer any integer multiple of the base amount. When `allowAmountScaling` is true, `startBalances` defines the 1x base unit, and `approvalCriteria.coinTransfers` scale by the same multiplier automatically.

```json
{
    "incrementedBalances": {
        "startBalances": [
            {
                "amount": "1",
                "tokenIds": [{"start": "1", "end": "1"}],
                "ownershipTimes": [{"start": "1", "end": "18446744073709551615"}]
            }
        ],
        "incrementTokenIdsBy": "0",
        "incrementOwnershipTimesBy": "0",
        "durationFromTimestamp": "0",
        "allowOverrideTimestamp": false,
        "allowOverrideWithAnyValidToken": false,
        "allowAmountScaling": true,
        "maxScalingMultiplier": "1000000000000",
        "recurringOwnershipTimes": {
            "startTime": "0",
            "intervalLength": "0",
            "chargePeriodLength": "0"
        }
    }
}
```

**Constraints**: When `allowAmountScaling` is true, all other incrementedBalances fields must be zero/false/nil. The base must be a static balance set — no dynamic behavior. `maxScalingMultiplier` must be > 0.

**How it works**:

* The chain computes `multiplier = transferAmount / baseAmount`
* The multiplier must be an integer >= 1 (no fractional scaling)
* The multiplier must be <= `maxScalingMultiplier`
* Each `approvalCriteria.coinTransfers` amount is multiplied by the same factor
* Precalculation supports scaling via `scalingMultiplier` in `precalculationOptions` — set it to the desired multiplier (e.g., `"5"` for 5x) and the chain returns scaled balances directly. Alternatively, compute balances client-side and set them on the transfer.

**Best practice**: Set `startBalances` to the smallest possible base unit (e.g., `amount: "1"` for 1 micro-unit) and use a large `maxScalingMultiplier`. This ensures users can transfer any granular amount — since scaling only works with integer multiples, a micro-unit base avoids fractional limitations.

**Use cases**:

* **Pay-per-token**: Base = 1 micro-unit, coinTransfers = 1 micro-unit of payment denom. Users buy any exact amount.
* **Prediction market deposits**: Base = 1 micro-YES + 1 micro-NO. Deposit 1 USDC (1,000,000 micro) → 1,000,000x multiplier.
* **Credit token purchases**: Base = 1 micro-credit for 1 micro-payment. Buy any amount in one transaction.

**Security considerations**:

* `maxScalingMultiplier` MUST be > 0 when `allowAmountScaling` is true — the chain rejects 0 (no unlimited scaling)
* **`maxScalingMultiplier` is enforced per transfer, not cumulatively.** A user can execute multiple transfers each up to the max. To cap total exposure, pair scaling with `maxNumTransfers` (limit number of uses) or `approvalAmounts` (limit total token quantity). Without these, the only limit is the escrow/approver's available balance.
* When `coinTransfers` use `overrideFromWithApproverAddress: true`, the escrow/approver pays `multiplier * baseAmount` per transfer — set `maxScalingMultiplier` conservatively and always set `maxNumTransfers` or `approvalAmounts` to bound total payout
* Amount scaling is **incompatible** with Quest, Subscription, Invoice, Product, Bid/Listing, and Scheduled Payment standards (these require fixed amounts per transfer)
* The `review_collection` tool flags `allowAmountScaling + overrideFromWithApproverAddress` as a warning for review

## Precalculating Balances

### The Race Condition Problem

Predetermined balances can change rapidly between transaction broadcast and confirmation. For example:

* Other users' mints get processed
* Token IDs shift due to concurrent activity
* Manual balance specification becomes unreliable

### The Solution: Precalculation

Use `precalculateBalancesFromApproval` in [MsgTransferTokens](https://github.com/trevormil/bitbadges-docs/blob/master/token-standard/x-tokenization/messages/msg-transfer-tokens.md) to dynamically calculate balances at execution time.

```typescript
{
  precalculateBalancesFromApproval: {
    approvalId: string;           // The approval to precalculate from
    approvalLevel: string;        // "collection" | "incoming" | "outgoing"
    approverAddress: string;      // "" if collection-level
    version: string;              // Must specify exact version
    precalculationOptions: {
      overrideTimestamp: string;  // Optional: override timestamp (milliseconds)
      tokenIdsOverride: UintRange[]; // Optional: override token IDs
      scalingMultiplier: string;  // Optional: scale balances by this multiplier (requires allowAmountScaling)
    }
  }
}
```

## Precalculation Options

When using `precalculateBalancesFromApproval`, you can override calculation parameters. These options only apply when the corresponding flags are enabled in `IncrementedBalances`.

| Field               | Type          | When It Applies                                                    | Validation                                                               |
| ------------------- | ------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `overrideTimestamp` | string (Uint) | `durationFromTimestamp` set and `allowOverrideTimestamp` is `true` | If zero, uses current block time                                         |
| `tokenIdsOverride`  | UintRange\[]  | `allowOverrideWithAnyValidToken` is `true`                         | Must be exactly one range with `start == end`                            |
| `scalingMultiplier` | string (Uint) | `allowAmountScaling` is `true`                                     | Must be <= `maxScalingMultiplier`. 0 means no scaling (returns 1x base). |

**overrideTimestamp**: Overrides the base timestamp for ownership time calculation. Ownership times become `[overrideTimestamp, overrideTimestamp + durationFromTimestamp - 1]`.

**tokenIdsOverride**: Replaces incrementally calculated token IDs. The token ID must be in the collection's `validTokenIds`.

**scalingMultiplier**: When `allowAmountScaling` is true, multiplies all precalculated balance amounts by this value. The chain computes the 1x base as usual, then scales. This lets API/SDK callers use standard precalculation with scaling instead of computing balances client-side.

```json
{
    "precalculateBalancesFromApproval": {
        "approvalId": "approval-1",
        "approvalLevel": "collection",
        "approverAddress": "",
        "version": "1",
        "precalculationOptions": {
            "overrideTimestamp": "1704067200000",
            "tokenIdsOverride": [{ "start": "5", "end": "5" }]
        }
    }
}
```

If the corresponding flags are `false`, the options are ignored (no error).

## Order Calculation Methods

The system needs to determine which balance set to use for each transfer. This is controlled by the `orderCalculationMethod`.

### How Order Numbers Work

The order number determines which balances to transfer, but it works differently depending on the balance type:

#### Manual Balances

* **Order number = 0**: Transfer `manualBalances[0]` (first element)
* **Order number = 1**: Transfer `manualBalances[1]` (second element)
* **Order number = 5**: Transfer `manualBalances[5]` (sixth element)

**Example**: If you have 3 manual balance sets, order numbers 0, 1, and 2 will use each set once. Order number 3 would be out of bounds.

#### Incremented Balances

* **Order number = 0**: Use starting balances as-is (no increments)
* **Order number = 1**: Apply increments once to starting balances
* **Order number = 5**: Apply increments five times to starting balances

**Example**: Starting with token ID 1, increment by 1:

* Order 0: Token ID 1
* Order 1: Token ID 2
* Order 2: Token ID 3
* Order 5: Token ID 6

### Transfer-Based Order Numbers

Track the number of transfers to determine order:

| Method                                 | Description           | Use Case                    |
| -------------------------------------- | --------------------- | --------------------------- |
| `useOverallNumTransfers`               | Global transfer count | Simple sequential transfers |
| `usePerToAddressNumTransfers`          | Per-recipient count   | User-specific limits        |
| `usePerFromAddressNumTransfers`        | Per-sender count      | Sender-specific limits      |
| `usePerInitiatedByAddressNumTransfers` | Per-initiator count   | Initiator-specific limits   |

**Important**: Uses the same tracker as [Max Number of Transfers](/token-standard/learn/approval-criteria/max-number-of-transfers.md). Trackers are:

* Increment-only and immutable
* Shared between predetermined balances and max transfer limits
* Must be carefully managed to avoid conflicts

### Merkle-Based Order Numbers

Use Merkle challenge leaf indices (leftmost = 0, rightmost = numLeaves - 1) for reserved transfers:

```typescript
{
  "useMerkleChallengeLeafIndex": true,
  "challengeTrackerId": "uniqueId"
}
```

**Use Case**: Reserve specific token IDs for specific users or claim codes.

## Order Calculation Interface

```typescript
export interface PredeterminedOrderCalculationMethod {
    useOverallNumTransfers: boolean;
    usePerToAddressNumTransfers: boolean;
    usePerFromAddressNumTransfers: boolean;
    usePerInitiatedByAddressNumTransfers: boolean;
    useMerkleChallengeLeafIndex: boolean;
    challengeTrackerId: string;
}
```

## Boundary Handling

### Understanding Bounds

Every approval defines bounds through its core fields (tokenIds, ownershipTimes, etc.). For example:

* **Token IDs**: 1-100
* **Ownership Times**: Mon-Fri only
* **Transfer Times**: Specific date range

Predetermined balances must work within these bounds, but note that order numbers can eventually exceed them.

### Boundary Scenarios

#### Complete Out-of-Bounds

**Scenario**: Order number corresponds to balances completely outside approval bounds.

**Example**:

* Approval allows token IDs 1-100
* Increment by 1 for each transfer
* Order number 101 would require token ID 101 (out of bounds)

**Result**: Transfer is ignored because token ID 101 never matches the approval's token ID range.

#### Partial Overlap

**Scenario**: Order number corresponds to balances that partially overlap with approval bounds.

**Example**:

* Approval allows token IDs 1-100
* Transfer requires token IDs 95-105
* Token IDs 95-100 are in bounds, 101-105 are out of bounds

**Result**:

* Only in-bounds balances (95-100) are approved by current approval
* Out-of-bounds balances (101-105) must be approved by a separate approval
* The complete transfer (95-105) must still be exactly as defined

**Important**: The transfer will fail unless all out-of-bounds balances are approved by other approvals.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.bitbadges.io/token-standard/learn/approval-criteria/predetermined-balances.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
