Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions ARCs/arc-1410.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
arc: 1410
title: Partitioned Token Balances
description: Partitioned sub-ledger model for security tokens on Algorand
author: Ludovit Scholtz (@scholtz)
discussions-to: https://github.com/algorandfoundation/ARCs/discussions
status: Draft
type: Standards Track
category: Interface
sub-category: Application
created: 2025-09-03
requires: 88, 200
replaces:
---

# ARC-1410: Partitioned Token Balances

## Abstract

ARC-1410 defines partitioned balances for Algorand security tokens. Partitions allow segregating holdings (e.g., unrestricted, locked, escrow, tranche A/B) while preserving a single ARC-200 token supply. Each partition acts as a logical sub-ledger with rules influencing transfer eligibility.

## Motivation

Regulated assets often require lock-ups and categorization of units. Instead of deploying multiple ARC-200 tokens, partitions provide granular control, improved UX, and simplified corporate actions (single total supply). Ethereum's ERC-1410 informs this ARC; we adapt to Algorand state constraints and atomic group mechanics.

## Specification

### Partition Identifier

A partition is identified by an ARC-4 `address` (not a 32-byte arbitrary hash). Divergence from ERC-1410: Ethereum permits `bytes32` (or shorter labels hashed); ARC-1410 fixes the identifier to an address to enable:

- Native authorization (partition controller / escrow account as identifier)
- Reuse of address indexing infra
- Clear zero-address semantics for the default/unrestricted partition

Implementations MAY map human-readable labels to partition addresses via a registry box `partition-labels` storing tuples `(label -> address)`.

### State Representation

Two recommended models:

1. Local State Mapping: Each account's local state stores per-partition balances under truncated keys (e.g., first 8 bytes of partition address) when partition count is small (<8 due to schema limits).
2. Boxes: Each holder has a box named `p-<addr>-<partitionAddr>` storing a `uint64` balance (msgpack encoded). Preferred for scalable partition counts.

A global box `partitions` MAY store an indexed list of active partition addresses and optional metadata (label, flags).

### Invariants

For every account: `Σ partitionBalances == ARC-200 balance`. Contract logic MUST enforce this on issuance, redemption, and transfers. Discrepancies MUST trigger failure.

### Balance Semantics

Clarifications relative to ERC-1410 behavior:

- Moving units between partitions (e.g., release from locked to unrestricted) MUST NOT change the holder's total ARC-200 balance; it's a reclassification only.
- A partition move operation MUST debit the source partition balance and credit the destination partition balance in the same atomic group as (or instead of) any ARC-200 unit movement so the invariant holds every step.
- Plain ARC-200 (ARC-200 standard) transfers that occur without partition context MUST be defined by implementation policy. Recommended rule: they operate on the default/unrestricted (zero-address) partition ONLY. If the sender does not have enough units in that default partition, the transfer MUST fail (rather than implicitly draining other partitions) to avoid silent regulatory bypass.
- Implementations MAY forbid bare ARC-200 transfers entirely (forcing `arc1410_transfer_by_partition`) by having the ARC-200 logic route through the application; this MUST be clearly documented if chosen.
- Receiving units via a plain ARC-200 transfer credits only the default/unrestricted partition under the recommended rule above. It MUST NOT auto-spread across other partitions.
- Partition-aware issuance/redemption (`arc1410_issue_by_partition`, `arc1410_redeem_by_partition`, `arc1410_operator_redeem_by_partition`) directly adjust both the ARC-200 total and the specified partition balance.

### Methods (ABI Recommendations)

(All snake*case with ARC-4 types; `partition: address`. ARC-1410 methods are namespaced with `arc1410*`.)

- `arc1410_balance_of_partition(holder: address, partition: address) -> (amount: uint256)`
- `arc1410_partitions_of(holder: address, page: uint64) -> (partitions: address[])` (MAY paginate if large. if not, page = 0)
- `arc1410_transfer_by_partition(partition: address, to: address, amount: uint256, data: bytes) -> (receiverPartition: address)`
- `arc1410_can_transfer_by_partition(from: address, partition: address, to: address, amount: uint256, data: bytes) -> (code: uint8, status: bytes, receiverPartition: address)`
- `arc1410_operator_transfer_by_partition(from: address, partition: address, to: address, amount: uint256, data: bytes) -> (receiverPartition: address)`
- `arc1410_is_operator(holder: address, operator: address, partition: address) -> (flag: uint64)` (1 = authorized; zero partition = any)
- `arc1410_authorize_operator(holder: address, operator: address, partition: address)` (partition = zero address to authorize all)
- `arc1410_revoke_operator(holder: address, operator: address, partition: address)`
- `arc1410_authorize_operator_by_portion(holder: address, operator: address, partition: address, amount: uint256)` (Sets a remaining transferable allowance for operator limited to the specified partition.)
- `arc1410_is_operator_by_portion(holder: address, operator: address, partition: address) -> (flag: uint64)` (1 if remaining allowance > 0 for that partition)
- `arc1410_issue_by_partition(to: address, partition: address, amount: uint256, data: bytes)`
- `arc1410_redeem_by_partition(partition: address, amount: uint256, data: bytes)`
- `arc1410_operator_redeem_by_partition(from: address, partition: address, amount: uint256, data: bytes)`
- `arc1410_set_partition_flag(partition: address, flag_key: bytes, value: uint64)`

Portion operators provide granular delegation without granting full operator rights. If both a full operator authorization and a portion allowance exist, full authorization takes precedence and does not consume the allowance. Portion allowance is decremented on each successful `arc1410_operator_transfer_by_partition` / `arc1410_operator_redeem_by_partition` that relies on it.

#### arc1410_can_transfer_by_partition

Purpose: Off-chain (readonly) preflight check returning a compact code and human-readable status string WITHOUT mutating state.

Return tuple fields:

- `code: uint8` machine-readable status
- `status: bytes` UTF-8 short description (MAY be truncated client-side if large)
- `receiverPartition: address` partition that would receive the units (may differ if recipient lacks the specified partition and fallback/default is used)

Error / status codes (draft allocation separate from ARC-1594 codes space):

- `0x51` success (transfer allowed)
- `0x50` partition_not_exists (sender partition missing / zero balance structure absent)
- `0x52` insufficient_partition_balance
- `0x57` invalid_receiver (zero address or disallowed)
- `0x58` operator_not_authorized (caller not permitted for given holder / partition OR portion allowance insufficient)
- `0x5A` receiver_partition_restricted (future use; e.g., flagged destination partition) OPTIONAL
- `0x5B` partition_flag_blocked (flag indicates non-transferable) OPTIONAL
- `0x5F` internal_error (unexpected condition; SHOULD NOT occur in normal path)

Implementations MAY extend codes >= `0x60`; clients MUST treat unknown non-zero codes as failure.

### Transfer Semantics

`arc1410_transfer_by_partition` MUST internally call or replicate ARC-1594 validation logic including partition-specific flags:

- Transferable flag absent or zero -> return code `12` PartitionRestricted.
- Check sender partition balance.
- Adjust sender + recipient partition balances atomically with ARC-200 movement (inner transaction or paired ARC-200 transfer).

If `partition` is the zero address it denotes the default/unrestricted partition when such a concept is used.

`arc1410_operator_transfer_by_partition` and `arc1410_operator_redeem_by_partition` MUST succeed if either:

1. Caller is a fully authorized operator for the partition (or global) OR
2. Caller has a sufficient portion allowance (`arc1410_authorize_operator_by_portion`) for that partition covering the requested amount.

If path (2) is used, the allowance MUST be decremented atomically by the transferred / redeemed amount. If both apply, do not decrement the allowance.

Redeeming (burning) by partition for end users uses `arc1410_redeem_by_partition` without a `from` parameter; the caller (`Txn.sender`) is implicitly the holder. The earlier draft signature including `from` is DEPRECATED and MUST NOT be implemented by conforming contracts. Operator-driven redemption for another holder MUST use `arc1410_operator_redeem_by_partition` which retains the explicit `from` parameter.

### Discovery

Wallets & indexers discover partitions via:

1. Reading global `partitions` box.
2. Querying `arc1410_partitions_of`.

### Events (Logs)

- Tag 0x11 arc1410_partition_transfer | from | to | partition(address) | amount
- Tag 0x12 arc1410_partition_issue | to | partition(address) | amount
- Tag 0x13 arc1410_partition_redeem | from | partition(address) | amount
- Tag 0x14 arc1410_operator_authorized | holder | operator | partition
- Tag 0x15 arc1410_operator_revoked | holder | operator | partition
- Tag 0x16 arc1410_operator_portion_authorized | holder | operator | partition | amount (OPTIONAL)

### Merging / Splitting

Optional methods MAY allow migrating balances between partitions with issuer authorization (e.g., releasing locked units).

### Cost Considerations

Use boxes to avoid local state schema inflation for large partition sets. Batch operations (e.g., multi-partition redemption) can be structured as multiple app calls in a group.

## Security Considerations

- Prevent creation of unauthorized partitions (maintain an allowlist of partition addresses).
- Avoid address reuse for unrelated semantic partitions without proper migration flows.
- Ensure merging logic cannot bypass lock-ups (validate destination partition flags).
- Distinguish clearly in UI between full operator and portion operator grants to prevent overestimation of delegated power.

## Reference Implementation

[arc1410.algo.ts](https://github.com/scholtz/arc-1400/blob/main/projects/arc-1400/smart_contracts/security_token/arc1410.algo.ts)

## Backwards Compatibility

Applications not aware of partitions can treat the zero address partition as canonical, reading ARC-200 balance minus sum of restricted partitions if exposed.

## Rationale

Using address identifiers unifies partition identity with potential escrow / controller addresses and simplifies tooling reuse.

## Copyright

CC0 1.0 Universal.
Loading