--- sidebar_position: 2 --- # Transfers A `transfer` is an immutable record of a financial transaction between two accounts. In TigerBeetle, financial transactions are called "transfers" instead of "transactions" because the latter term is heavily overloaded in the context of databases. ### Updates Transfers *cannot be modified* after creation. ## Modes Transfers can either be Single-Phase, where they are executed immediately, or Two-Phase, where they are first put in a Pending state and then either Posted or Voided. For more details on the latter, see the [Two-Phase Transfer guide](../design/two-phase-transfers.md). Fields used by each mode of transfer: | Field | Single-Phase | Pending | Post-Pending | Void-Pending | | ----------------------------- | ------------ | -------- | ------------ | ------------ | | `id` | required | required | required | required | | `debit_account_id` | required | required | optional | optional | | `credit_account_id` | required | required | optional | optional | | `amount` | required | required | optional | optional | | `pending_id` | none | none | required | required | | `user_data_128` | optional | optional | optional | optional | | `user_data_64` | optional | optional | optional | optional | | `user_data_32` | optional | optional | optional | optional | | `timeout` | none | optional | none | none | | `ledger` | required | required | optional | optional | | `code` | required | required | optional | optional | | `flags.linked` | optional | optional | optional | optional | | `flags.pending` | false | true | false | false | | `flags.post_pending_transfer` | false | false | true | false | | `flags.void_pending_transfer` | false | false | false | true | | `flags.balancing_debit` | optional | optional | false | false | | `flags.balancing_credit` | optional | optional | false | false | | `timestamp` | none | none | none | none | ## Fields ### `id` This is a unique identifier for the transaction. Constraints: * Type is 128-bit unsigned integer (16 bytes) * Must not be zero or `2^128 - 1` * Must not conflict with another transfer in the cluster See the [`id` section in the data modeling doc](../design/data-modeling.md#id) for more recommendations on choosing an ID scheme. Note that transfer IDs are unique for the cluster -- not the ledger. If you want to store a relationship between multiple transfers, such as indicating that multiple transfers on different ledgers were part of a single transaction, you should store a transaction ID in one of the [`user_data`](#user_data_128) fields. ### `debit_account_id` This refers to the account to debit the transfer's [`amount`](#amount). Constraints: * Type is 128-bit unsigned integer (16 bytes) * When `flags.post_pending_transfer` and `flags.void_pending_transfer` are unset: * Must match an existing account * Must not be the same as `credit_account_id` * When `flags.post_pending_transfer` or `flags.void_pending_transfer` are set: - If `debit_account_id` is zero, it will be automatically set to the pending transfer's `debit_account_id`. - If `debit_account_id` is nonzero, it must match the corresponding pending transfer's `debit_account_id`. ### `credit_account_id` This refers to the account to credit the transfer's [`amount`](#amount). Constraints: * Type is 128-bit unsigned integer (16 bytes) * When `flags.post_pending_transfer` and `flags.void_pending_transfer` are unset: * Must match an existing account * Must not be the same as `debit_account_id` * When `flags.post_pending_transfer` or `flags.void_pending_transfer` are set: - If `credit_account_id` is zero, it will be automatically set to the pending transfer's `credit_account_id`. - If `credit_account_id` is nonzero, it must match the corresponding pending transfer's `credit_account_id`. ### `amount` This is how much should be debited from the `debit_account_id` account and credited to the `credit_account_id` account. - When `flags.balancing_debit` is set, this is the maximum amount that will be debited/credited, where the actual transfer amount is determined by the debit account's constraints. - When `flags.balancing_credit` is set, this is the maximum amount that will be debited/credited, where the actual transfer amount is determined by the credit account's constraints. Constraints: * Type is 128-bit unsigned integer (16 bytes) * When `flags.post_pending_transfer` is set: * If `amount` is zero, it will be automatically be set to the pending transfer's `amount`. * If `amount` is nonzero, it must be less than or equal to the pending transfer's `amount`. * When `flags.void_pending_transfer` is set: * If `amount` is zero, it will be automatically be set to the pending transfer's `amount`. * If `amount` is nonzero, it must be equal to the pending transfer's `amount`. * When `flags.balancing_debit` and/or `flags.balancing_credit` is set, if `amount` is zero, it will automatically be set to the maximum amount that does not violate the corresponding account limits. (Equivalent to setting `amount = 2^128 - 1`). * When all of the following flags are not set, `amount` must be nonzero: * `flags.post_pending_transfer` * `flags.void_pending_transfer` * `flags.balancing_debit` * `flags.balancing_credit` #### Examples - For representing fractional amounts (e.g. `$12.34`), see [Fractional Amounts](../design/data-modeling.md#fractional-amounts-and-asset-scale). - For balancing transfers, see [Close Account](../recipes/close-account.md). ### `pending_id` If this transfer will post or void a pending transfer, `pending_id` references that pending transfer. If this is not a post or void transfer, it must be zero. See the section on [Two-Phase Transfers](../design/two-phase-transfers.md) for more information on how the `pending_id` is used. Constraints: * Type is 128-bit unsigned integer (16 bytes) * Must be zero if neither void nor pending transfer flag is set * Must match an existing transfer's [`id`](#id) if non-zero ### `user_data_128` This is an optional 128-bit secondary identifier to link this transfer to an external entity or event. As an example, you might generate a [TigerBeetle Time-Based Identifier](../design/data-modeling.md#tigerbeetle-time-based-identifiers-recommended) that ties together a group of transfers. For more information, see [Data Modeling](../design/data-modeling.md#user_data). Constraints: * Type is 128-bit unsigned integer (16 bytes) ### `user_data_64` This is an optional 64-bit secondary identifier to link this transfer to an external entity or event. As an example, you might use this field store an external timestamp. For more information, see [Data Modeling](../design/data-modeling.md#user_data). Constraints: * Type is 64-bit unsigned integer (8 bytes) ### `user_data_32` This is an optional 32-bit secondary identifier to link this transfer to an external entity or event. As an example, you might use this field to store a timezone or locale. For more information, see [Data Modeling](../design/data-modeling.md#user_data). Constraints: * Type is 32-bit unsigned integer (4 bytes) ### `timeout` This is the interval in seconds after a [`pending`](#flagspending) transfer's [arrival at the cluster](#timestamp) that it may be [posted](#flagspost_pending_transfer) or [voided](#flagsvoid_pending_transfer). Zero denotes absence of timeout. Non-pending transfers cannot have a timeout. TigerBeetle makes a best-effort approach to remove pending balances of expired transfers automatically: - Transfers expire _exactly_ at their expiry time ([`timestamp`](#timestamp) _plus_ `timeout` converted in nanoseconds). - Pending balances will never be removed before its expiry. - Expired transfers cannot be manually posted or voided. - It is not guaranteed that the pending balance will be removed exactly at its expiry. In particular, client requests may observe still-pending balances for expired transfers. - Pending balances are removed in chronological order by expiry. If multiple transfers expire at the same time, then ordered by the transfer's creation [`timestamp`](#timestamp). If a transfer `A` has expiry `E₁` and transfer `B` has expiry `E₂`, and `E₁