Skip to main content

IPayment

An interface to relay and verify payment transactions in native currencies across multiple external blockchains.

Sourced from IPayment.sol on GitHub.

Overview

The IPayment interface enables smart contracts on Flare networks to access and verify native currency payment transactions from external blockchains. It provides a standardized way to prove that payments have occurred between entities, similar to traditional banking transfers, with support for payment references. This attestation type handles the different transaction models across various blockchains, offering a unified interface for payment verification.

Supported Chains

Network TypeSupported Chains
MainnetBTC (Bitcoin), DOGE (Dogecoin), XRP (XRP Ledger)
TestnettestBTC (Bitcoin Testnet v3), testDOGE, testXRP

Chain-Specific Implementation Details

UTXO Chains (Bitcoin and Dogecoin)

  • Transaction Model: Uses inputs and outputs, where multiple addresses can contribute inputs and receive outputs
  • inUtxo: Index of the transaction input with the source address
  • utxo: Index of the transaction output with the receiving address
  • blockTimestamp: Derived from the mediantime of the block

Account-based Chains (XRPL)

  • Transaction Model: Direct transfers between accounts
  • inUtxo and utxo: Always set to 0 (not used)
  • blockTimestamp: Derived from the close_time of the ledger converted to UNIX time

Interface Definition

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.6 <0.9;

/**
* @custom:name IPayment
* @custom:id 0x01
* @custom:supported BTC, DOGE, XRP
* @author Flare
* @notice A relay of a transaction on an external chain that is considered a payment in a native currency.
* Various blockchains support different types of native payments. For each blockchain, it is specified how a payment
* transaction should be formed to be provable by this attestation type.
* The provable payments emulate traditional banking payments from entity A to entity B in native currency with an
* optional payment reference.
* @custom:verification The transaction with `transactionId` is fetched from the API of the 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](/specs/attestations/configs.md#finalityconfirmation), the attestation request is rejected.
*
* Once the transaction is received, the payment summary is computed according to the rules for the source chain.
* If the summary is successfully calculated, the response is assembled from the summary.
* `blockNumber` and `blockTimestamp` are retrieved from the block if they are not included in the transaction data.
* For Bitcoin and Dogecoin, `blockTimestamp` is mediantime of the block.
* For XRPL, `blockTimestamp` is close time of the ledger converted to UNIX time.
*
* If the summary is not successfully calculated, the attestation request is rejected.
* @custom:lut `blockTimestamp`
* @custom:lutlimit `0x127500`, `0x127500`, `0x127500`
*/
interface IPayment {
/**
* @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 (struct) 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.
* @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 Payment attestation type
* @param transactionId ID of the payment transaction.
* @param inUtxo For UTXO chains, this is the index of the transaction input with source address.
* Always 0 for the non-utxo chains.
* @param utxo For UTXO chains, this is the index of the transaction output with receiving address.
* Always 0 for the non-utxo chains.
*/
struct RequestBody {
bytes32 transactionId;
uint256 inUtxo;
uint256 utxo;
}

/**
* @notice Response body for Payment attestation type
* @param blockNumber 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 source address.
* @param sourceAddressesRoot The root of the Merkle tree of the source addresses.
* @param receivingAddressHash Standard address hash of the receiving address.
* The zero 32-byte string if there is no receivingAddress (if `status` is not success).
* @param intendedReceivingAddressHash Standard address hash of the intended receiving address.
* Relevant if the transaction is unsuccessful.
* @param spentAmount Amount in minimal units spent by the source address.
* @param intendedSpentAmount Amount in minimal units to be spent by the source address.
* Relevant if the transaction status is unsuccessful.
* @param receivedAmount Amount in minimal units received by the receiving address.
* @param intendedReceivedAmount Amount in minimal units intended to be received by the receiving address.
* Relevant if the transaction is unsuccessful.
* @param standardPaymentReference Standard payment reference of the transaction.
* @param oneToOne Indicator whether only one source and one receiver are involved in the transaction.
* @param status Success status of the transaction: 0 - success, 1 - failed by sender's fault,
* 2 - failed by receiver's fault.
*/
struct ResponseBody {
uint64 blockNumber;
uint64 blockTimestamp;
bytes32 sourceAddressHash;
bytes32 sourceAddressesRoot;
bytes32 receivingAddressHash;
bytes32 intendedReceivingAddressHash;
int256 spentAmount;
int256 intendedSpentAmount;
int256 receivedAmount;
int256 intendedReceivedAmount;
bytes32 standardPaymentReference;
bool oneToOne;
uint8 status;
}
}

Structs

Request

Toplevel request structure.

Parameters

  • attestationType: ID of the attestation type (0x01 for Payment)
  • sourceId: ID of the data source (e.g., BTC, DOGE, XRP)
  • messageIntegrityCode: MessageIntegrityCode derived from the expected response
  • requestBody: Data defining the request

Response

Toplevel response structure.

Parameters

  • attestationType: Extracted from the request
  • sourceId: Extracted from the request
  • votingRound: The ID of the State Connector round in which the request was considered
  • lowestUsedTimestamp: The lowest timestamp used to generate the response
  • requestBody: Extracted from the request
  • responseBody: Data defining the response

Proof

Toplevel proof structure for verification.

Parameters

  • merkleProof: Merkle proof corresponding to the attestation response
  • data: Attestation response

RequestBody

Request body specific to payment verification.

Parameters

  • transactionId: The unique identifier of the transaction to verify
  • inUtxo: Index of the transaction input with source address (UTXO chains only)
  • utxo: Index of the transaction output with receiving address (UTXO chains only)

ResponseBody

Response body containing payment transaction details.

Parameters

  • blockNumber: Block number containing the transaction
  • blockTimestamp: Timestamp of the block containing the transaction
  • sourceAddressHash: Standard hash of the source address
  • sourceAddressesRoot: Root of the Merkle tree of all source addresses
  • receivingAddressHash: Standard hash of the receiving address (zero if transaction failed)
  • intendedReceivingAddressHash: Standard hash of the intended receiving address (for failed transactions)
  • spentAmount: Amount spent by the source address in minimal units
  • intendedSpentAmount: Amount intended to be spent (for failed transactions)
  • receivedAmount: Amount received by the receiving address
  • intendedReceivedAmount: Amount intended to be received (for failed transactions)
  • standardPaymentReference: Standard payment reference included in the transaction
  • oneToOne: True if the transaction involves only one source and one receiver
  • status: Transaction status (0=success, 1=sender failure, 2=receiver failure)

Transaction Status Codes

Status CodeDescriptionExplanation
0SUCCESSTransaction completed successfully
1SENDER_FAILURETransaction failed due to issues on the sender's side
2RECEIVER_FAILURETransaction failed due to issues on the receiver's side

Implementation Notes

  • Attestation ID: 0x01
  • The lowestUsedTimestamp parameter uses the value of blockTimestamp
  • The lutlimit (Lowest Used Timestamp limit) is 0x127500 (1,209,600 seconds = 14 days) for all supported chains
  • Standard payment references can be included for cross-chain payment identification
  • For account-based chains like XRPL, inUtxo and utxo parameters should be set to 0
  • The sourceAddressesRoot is a Merkle tree root of all transaction input addresses, useful for multi-input transactions
  • The oneToOne flag helps identify simple peer-to-peer transfers versus more complex multi-party transactions

Standard Payment Reference

A standardized payment reference follows a specific format to ensure consistency across different blockchains:

  • For UTXO chains (BTC, DOGE): Derived from OP_RETURN outputs or specific patterns in transaction data
  • For XRPL: Derived from the MemoData field in the transaction

Payment references allow for correlation of payments across chains and facilitate payment reconciliation in cross-chain applications.

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/IPayment.sol";

contract PaymentVerifier {
IFdcHub private fdcHub;
IFdcVerification private fdcVerification;

bytes32 private constant ATTESTATION_TYPE_PAYMENT = 0x0100000000000000000000000000000000000000000000000000000000000000;
bytes32 private constant SOURCE_ID_BTC = 0x4254430000000000000000000000000000000000000000000000000000000000;

// Store verified payments
mapping(bytes32 => bool) public verifiedPayments; // transactionId => verified

constructor(address _fdcHubAddress, address _fdcVerificationAddress) {
fdcHub = IFdcHub(_fdcHubAddress);
fdcVerification = IFdcVerification(_fdcVerificationAddress);
}

// Request verification of a payment transaction
function requestPaymentVerification(
bytes32 transactionId,
uint256 inUtxo,
uint256 utxo
) external payable {
// Create request body
IPayment.RequestBody memory requestBody = IPayment.RequestBody({
transactionId: transactionId,
inUtxo: inUtxo,
utxo: utxo
});

// Encode the full request
bytes memory encodedRequest = abi.encode(
ATTESTATION_TYPE_PAYMENT,
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 payment proof
function verifyPayment(IPayment.Proof calldata _proof)
external
returns (
bool success,
bytes32 sourceAddressHash,
bytes32 receivingAddressHash,
int256 amount,
bytes32 paymentReference
)
{
// Verify the proof using FdcVerification
bool proofVerified = fdcVerification.verifyPayment(_proof);

if (proofVerified) {
// Extract the payment details from the proof
bytes32 transactionId = _proof.data.requestBody.transactionId;
IPayment.ResponseBody memory response = _proof.data.responseBody;

// Check if this is a successful payment
if (response.status == 0) {
// Store that this transaction has been verified
verifiedPayments[transactionId] = true;

return (
true,
response.sourceAddressHash,
response.receivingAddressHash,
response.receivedAmount,
response.standardPaymentReference
);
}
}

return (false, bytes32(0), bytes32(0), 0, bytes32(0));
}
}