# 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](https://docs.bitbadges.io/token-standard/learn/approval-criteria/max-number-of-transfers). 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.
