IBalanceDecreasingTransaction
An interface to detect transactions that decrease the balance of a specific address or are signed by that address on external blockchains.
Sourced from IBalanceDecreasingTransaction.sol
on GitHub.
Overview
The IBalanceDecreasingTransaction interface allows smart contracts to identify and verify transactions on external blockchains that either:
- Decrease the balance of a specific address, or
- Are signed by the address in question (even if the balance increases)
This attestation type is particularly useful for detecting when a party has violated an agreement by moving funds that were promised to be locked, providing grounds for liquidating collateral locked in a smart contract on Flare.
Supported Chains
Network Type | Supported Chains |
---|---|
Mainnet | BTC (Bitcoin), DOGE (Dogecoin), XRP (XRP Ledger) |
Testnet | testBTC (Bitcoin Testnet v3), testDOGE , testXRP |
Chain-Specific Implementation Details
UTXO Chains (Bitcoin and Dogecoin)
- sourceAddressIndicator: Index of the transaction input in hex padded to a 0x-prefixed 32-byte string
- sourceAddress: Address of the indicated transaction input
- spentAmount: Sum of values of all inputs with sourceAddress minus sum of all outputs with sourceAddress (can be negative)
- blockTimestamp: Mediantime of the block
Account-based Chains (XRPL)
- sourceAddressIndicator: Standard address hash of the address to monitor
- sourceAddress: Address whose standard hash matches the sourceAddressIndicator
- spentAmount: Difference between the balance after and before the transaction (can be negative)
- blockTimestamp: close_time of the ledger converted to Unix time
Interface Definition
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;
/**
* @custom:name IBalanceDecreasingTransaction
* @custom:id 0x02
* @custom:supported BTC, DOGE, XRP
* @author Flare
* @notice A detection of a transaction that either decreases the balance for some address or is
* signed by the source address.
* Such an attestation could prove a violation of an agreement and therefore provides grounds to liquidate
* some funds locked by a smart contract on Flare.
*
* A transaction is considered "balance decreasing" for the address, if the balance after the
* transaction is lower than before or the address is among the signers of the transaction
* (even if its balance is greater than before the transaction).
* @custom:verification The transaction with `transactionId` is fetched from the API of the
* source blockchain node or relevant indexer.
* If the transaction cannot be fetched or the transaction is in a block that does not have a
* sufficient number of confirmations, the attestation request is rejected.
*
* Once the transaction is received, the response fields are extracted if the transaction is balance
* decreasing for the indicated address.
* Some of the request and response fields are chain specific as described below.
* The fields can be computed with the help of a balance decreasing summary.
*
* ### UTXO (Bitcoin and Dogecoin)
*
* - `sourceAddressIndicator` is the the index of the transaction input in hex padded to a 0x prefixed 32-byte string.
* If the indicated input does not exist or the indicated input does not have the address,
* the attestation request is rejected.
* The `sourceAddress` is the address of the indicated transaction input.
* - `spentAmount` is the sum of values of all inputs with sourceAddress minus the sum of
* all outputs with `sourceAddress`.
* Can be negative.
* - `blockTimestamp` is the mediantime of a block.
*
* ### XRPL
*
* - `sourceAddressIndicator` is the standard address hash of the address whose balance has been decreased.
* If the address indicated by `sourceAddressIndicator` is not among the signers of the transaction and the balance
* of the address was not lowered in the transaction, the attestation request is rejected.
*
* - `spentAmount` is the difference between the balance of the indicated address after and before the transaction.
* Can be negative.
* - `blockTimestamp` is the close_time of a ledger converted to unix time.
*
* @custom:lut `blockTimestamp`
* @custom:lutlimit `0x127500`, `0x127500`, `0x127500`
*/
interface IBalanceDecreasingTransaction {
/**
* @notice Toplevel request
* @param attestationType ID of the attestation type.
* @param sourceId ID of the data source.
* @param messageIntegrityCode `MessageIntegrityCode` that is derived from the expected response.
* @param requestBody Data defining the request. Type and interpretation is determined by the `attestationType`.
*/
struct Request {
bytes32 attestationType;
bytes32 sourceId;
bytes32 messageIntegrityCode;
RequestBody requestBody;
}
/**
* @notice Toplevel response
* @param attestationType Extracted from the request.
* @param sourceId Extracted from the request.
* @param votingRound The ID of the State Connector round in which the request was considered.
* This is a security measure to prevent a collision of attestation hashes.
* @param lowestUsedTimestamp The lowest timestamp used to generate the response.
* @param requestBody Extracted from the request.
* @param responseBody Data defining the response. The verification rules for the construction of the
* response body and the type are defined per specific `attestationType`.
*/
struct Response {
bytes32 attestationType;
bytes32 sourceId;
uint64 votingRound;
uint64 lowestUsedTimestamp;
RequestBody requestBody;
ResponseBody responseBody;
}
/**
* @notice Toplevel proof
* @param merkleProof Merkle proof corresponding to the attestation response.
* @param data Attestation response.
*/
struct Proof {
bytes32[] merkleProof;
Response data;
}
/**
* @notice Request body for IBalanceDecreasingTransaction attestation type
* @param transactionId ID of the payment transaction.
* @param sourceAddressIndicator The indicator of the address whose balance has been decreased.
*/
struct RequestBody {
bytes32 transactionId;
bytes32 sourceAddressIndicator;
}
/**
* @notice Response body for IBalanceDecreasingTransaction attestation type.
* @param blockNumber The number of the block in which the transaction is included.
* @param blockTimestamp The timestamp of the block in which the transaction is included.
* @param sourceAddressHash Standard address hash of the address indicated by the `sourceAddressIndicator`.
* @param spentAmount Amount spent by the source address in minimal units.
* @param standardPaymentReference Standard payment reference of the transaction.
*/
struct ResponseBody {
uint64 blockNumber;
uint64 blockTimestamp;
bytes32 sourceAddressHash;
int256 spentAmount;
bytes32 standardPaymentReference;
}
}
Structs
Request
Toplevel request structure.
Parameters
attestationType
: ID of the attestation type (0x02 for BalanceDecreasingTransaction)sourceId
: ID of the data source (e.g., BTC, DOGE, XRP)messageIntegrityCode
: MessageIntegrityCode derived from the expected responserequestBody
: Data defining the request
Response
Toplevel response structure.
Parameters
attestationType
: Extracted from the requestsourceId
: Extracted from the requestvotingRound
: The ID of the State Connector round in which the request was consideredlowestUsedTimestamp
: The lowest timestamp used to generate the responserequestBody
: Extracted from the requestresponseBody
: Data defining the response
Proof
Toplevel proof structure for verification.
Parameters
merkleProof
: Merkle proof corresponding to the attestation responsedata
: Attestation response
RequestBody
Request body specific to balance decreasing transactions.
Parameters
transactionId
: Unique identifier of the transaction to checksourceAddressIndicator
: Indicator of the address to monitor (interpretation varies by chain)
ResponseBody
Response body containing details about the balance decreasing transaction.
Parameters
blockNumber
: Block number containing the transactionblockTimestamp
: Timestamp of the block containing the transactionsourceAddressHash
: Standard hash of the address indicated by sourceAddressIndicatorspentAmount
: Amount spent by the source address (can be negative)standardPaymentReference
: Standard payment reference of the transaction
Implementation Notes
- Attestation ID:
0x02
- The
lowestUsedTimestamp
parameter uses the value ofblockTimestamp
- The
lutlimit
(Lowest Used Timestamp limit) is0x127500
(1,209,600 seconds = 14 days) for all supported chains - A negative
spentAmount
value indicates the address received more funds than it sent - The transaction is still considered "balance decreasing" if the address signed the transaction, even if the balance increased
Practical Applications
- Cross-chain Collateral Monitoring: Detect when funds that should be locked on one chain are moved, triggering liquidation of collateral on Flare
- Agreement Enforcement: Verify if parties adhere to contractual agreements involving locking funds
- Fraud Detection: Monitor specific addresses for suspicious outgoing transactions
- Conditional Smart Contracts: Trigger contract actions based on external chain activity
Usage Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@flare-foundation/flare-smart-contracts-v2/contracts/userInterfaces/IFdcHub.sol";
import "@flare-foundation/flare-smart-contracts-v2/contracts/userInterfaces/IFdcVerification.sol";
import "@flare-foundation/flare-smart-contracts-v2/contracts/userInterfaces/fdc/IBalanceDecreasingTransaction.sol";
contract LockMonitor {
IFdcHub private fdcHub;
IFdcVerification private fdcVerification;
bytes32 private constant ATTESTATION_TYPE_BALANCE_DECREASING = 0x0200000000000000000000000000000000000000000000000000000000000000;
bytes32 private constant SOURCE_ID_BTC = 0x4254430000000000000000000000000000000000000000000000000000000000;
mapping(bytes32 => bool) public lockedAddresses; // sourceAddressHash => isLocked
constructor(address _fdcHubAddress, address _fdcVerificationAddress) {
fdcHub = IFdcHub(_fdcHubAddress);
fdcVerification = IFdcVerification(_fdcVerificationAddress);
}
// Register an address as locked (should not spend funds)
function registerLockedAddress(bytes32 sourceAddressHash) external {
lockedAddresses[sourceAddressHash] = true;
}
// Request verification of a transaction that might violate lock agreement
function checkViolation(bytes32 transactionId, bytes32 sourceAddressIndicator) external payable {
// Create request body
IBalanceDecreasingTransaction.RequestBody memory requestBody = IBalanceDecreasingTransaction.RequestBody({
transactionId: transactionId,
sourceAddressIndicator: sourceAddressIndicator
});
// Encode the full request
bytes memory encodedRequest = abi.encode(
ATTESTATION_TYPE_BALANCE_DECREASING,
SOURCE_ID_BTC,
bytes32(0), // messageIntegrityCode (would need to be calculated properly)
requestBody
);
// Submit the request with payment
fdcHub.requestAttestation{value: msg.value}(encodedRequest);
}
// Verify a provided proof and take action if a locked address has spent funds
function verifyViolation(IBalanceDecreasingTransaction.Proof calldata _proof)
external
returns (bool isViolation)
{
// Verify the proof using FdcVerification
bool proofVerified = fdcVerification.verifyBalanceDecreasingTransaction(_proof);
if (proofVerified) {
// Extract the sourceAddressHash
bytes32 sourceAddressHash = _proof.data.responseBody.sourceAddressHash;
int256 spentAmount = _proof.data.responseBody.spentAmount;
// Check if this is a locked address and the amount spent is positive
if (lockedAddresses[sourceAddressHash] && spentAmount > 0) {
// Take action - e.g., liquidate collateral, notify stakeholders, etc.
lockedAddresses[sourceAddressHash] = false; // Remove from locked addresses
return true;
}
}
return false;
}
}