FPSF-SS-001 — Stablecoin Stack
Document Metadata
| Field | Value |
|---|---|
| Spec ID | FPSF-SS-001 |
| Title | Stablecoin Stack |
| Version | 1.0.0 |
| Status | Draft |
| Date | 2026-03-25 |
| Author | Adalton Reis — reis@fabricpaymentstandards.org |
| Organization | Fabric Payment Standards Foundation |
| Contact | specs@fabricpaymentstandards.org> |
| License | Apache-2.0 |
| Adopts | FPSF-CPD-001 v1.0.0 |
Table of Contents
- Abstract
- Introduction
- Vision and Design Philosophy
- Relationship to FPSF-CPD-001
- System Architecture
- Component Specifications
- Cryptographic Conventions
- Data Structures
- Submission Payloads
- The Acquiring Model
- Settlement Contract — State Variables
- Settlement Contract — Events
- Settlement Contract — Fee Model
- Settlement Contract — Functions
- Validation Rules
- Submission Flow
- End-to-End Payment Flow
- Error Handling
- Security Model
- Conformance
- Future Work
- Normative References
1. Abstract
This document is the foundational specification of the Stablecoin Stack — an open architecture for processing stablecoin payments on Ethereum-compatible networks. It establishes the design philosophy, system architecture, component responsibilities, cryptographic conventions, data structures, submission protocols, on-chain contract interface, end-to-end payment flow, security model, and conformance requirements for the full stack.
The Stablecoin Stack is built on the proposition that cryptographic signatures are a superior foundation for payment authorization compared to identity-based delegation models, and that the tooling to make this practical for everyday users is both achievable and urgently needed. All architectural decisions follow directly from this proposition, and from the foundational model defined in FPSF-CPD-001.
2. Introduction
2.1 Purpose
This specification provides the normative definitions required to implement, deploy, audit, or assess conformance of any component of the Stablecoin Stack. It is intended for:
- developers integrating a wallet or checkout front-end with the payment processor API
- smart-contract engineers implementing or auditing a conformant Settlement Contract
- payment processor operators who need to understand the on-chain guarantees upon which their off-chain service depends
- auditors, regulators, and reviewers assessing correctness, security, and compliance of an existing implementation
2.2 Scope
This specification covers: design principles; system architecture; cryptographic conventions; all data structures; the two primary submission flows (payment transfer and acquirer registration); the full on-chain Settlement Contract interface; validation rules; the end-to-end payment flow; and the security model.
This specification does not cover: the Checkout Engine API exposed to merchant servers (FPSF-SS-003, planned); the Broadcast Layer WebSocket protocol (FPSF-SS-002); individual microservice interface specifications; key management and custody procedures; token issuance and redemption mechanics; and regulatory compliance guidance.
2.3 Normative References
See Section 22.
2.4 Conventions and Terminology
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL are to be interpreted as described in RFC 2119.
| Term | Definition |
|---|---|
| Stablecoin Stack | The full set of components, protocols, and interfaces specified by this document. |
| Payment Processor | An operator that deploys and runs a conformant instance of the Stablecoin Stack. |
| Relayer | The account that submits signed transactions on-chain, absorbing gas costs. Operated by the Payment Processor. |
| Merchant | A business or individual that accepts stablecoin payments. |
| Payer | The individual who holds the stablecoin and signs the payment commitment. |
| Acquirer | A registered third-party participant entitled to a share of the processing fee on payments they referred. |
| Service Provider | An Acquirer who has opted in to public discovery via the Basic Data Service. |
| Settlement Contract | The on-chain Solidity smart contract that verifies signatures, executes token transfers, and distributes fees. |
| Permit Signature | An ERC-2612 off-chain signature authorizing the Settlement Contract to call permit() on the token contract. |
| Binding Signature | An EIP-712 signature over the full operation parameters, authorizing the processor to submit the operation. |
| Charge | A processor-issued record representing an expected payment. |
| Order Reference | A 16-byte reference created by the merchant for tracking. Embedded in PermittedTransfer events. |
| Payload ID | A client-generated identifier for idempotency. |
| Acquirer ID | A bytes16 UUID identifying a registered Acquirer within the Settlement Contract. |
| Zero-UUID | The Acquirer ID consisting entirely of zero bytes. Indicates absence of an Acquirer. |
| Ephemeral Token | A short-lived, single-use token issued by the checkout widget for wallet session retrieval. |
| Principal Amount | The token amount intended to reach the beneficiary before fee deduction. |
| Total With Fees | Principal Amount plus all applicable fees. The value the payer must hold and authorize. |
| Base Fee | An absolute minimum fee in token units, charged on every transfer. Per-token. |
| Basis Points Fee | A percentage-based fee component of the operator fee, expressed in basis points. Per-token. |
| Operator Fee | The combined per-transfer fee earned by the Payment Processor: Base Fee plus Basis Points Fee. Both components are per-token. |
| Acquiring Fee | An additional percentage-based fee charged on behalf of a registered Acquirer. Per-token. |
| Administrator | The privileged account that controls Settlement Contract operational parameters. MUST NOT have ability to move user funds. |
| Hex string | A UTF-8 string with 0x prefix followed by lowercase hexadecimal digits. |
| EVM Address | A 20-byte account identifier: 0x + 40 lowercase hex digits. |
3. Vision and Design Philosophy
3.1 User-Centric by Design
Technical complexity MUST NOT be a barrier to participation. The payer encounters a flow as simple as any mobile payment. All complexity — signature construction, nonce management, fee calculation, gas payment — MUST be absorbed by the infrastructure.
3.2 Payer Sovereignty
The authority to authorize a payment belongs exclusively to the payer, expressed through cryptographic signature. No component can move funds without a valid signed authorization. The system is payer-agnostic: the identity of the account that submits a transaction is irrelevant to its validity.
3.3 Gas Abstraction as a Baseline
Gas is always paid by the Relayer, which recovers costs through the Operator Fee. The user holds only the stablecoin they wish to spend.
3.4 Complementary Rails, Not Replacement
The Stablecoin Stack is a set of additional payment rails, particularly suited to cross-border transfers, privacy-preserving payments, contexts without a shared banking relationship, and scenarios where programmable settlement logic adds value. It is designed to coexist with existing payment infrastructure.
3.5 Open by Default
All specifications, reference implementations, and supporting materials are published under open licenses. There are no proprietary extensions, no closed conformance test suites, and no tollgates on the information required to build a compliant implementation.
4. Relationship to FPSF-CPD-001
This specification adopts the Canonical Payment Definition (FPSF-CPD-001 v1.0.0) as its foundational payment model. Each stablecoin payment processed by the Stablecoin Stack is a Digital Payment in the sense of CPD-001. The mapping is as follows:
| CPD-001 Concept | Stablecoin Stack Equivalent |
|---|---|
Payer | The token holder whose Permit Signature and Binding Signature authorize the transfer |
Payee | The beneficiary address receiving the principal amount |
Value | principal amount in the specified ERC-2612 stablecoin |
AuthorizationProof | The dual-signature structure: permitSig + payWithPermitSig |
ExecutionContext | The Settlement Contract address, chain ID, and network |
CREATED | TransferRequest constructed and signed by the payer |
AUTHORIZED | TransferRequest validated by the Payment Processor |
IN_FLIGHT | Transaction submitted to the network by the Relayer |
SETTLED | PermittedTransfer event confirmed by transfer-history |
FAILED | Transaction reverted or validation failure |
All CPD-001 invariants (uniqueness, integrity, authorization binding, deterministic state, finality) MUST be preserved by conformant implementations.
5. System Architecture
5.1 Principal Actors
| Actor | Role |
|---|---|
| Merchant | Creates charges and receives settlement confirmation. Integrates with the Checkout Engine over mTLS. |
| Payer | Holds the stablecoin, signs the payment commitment, submits the payload via a compliant wallet. Never submits transactions directly to the network. |
| Payment Processor | Operates the off-chain infrastructure (Checkout Engine, Broadcast Layer, Relayer). |
| Acquirer | A registered third party who distributes the payment service and earns a share of the processing fee. |
5.2 Component Overview
| Component | Subsystem | Key Requirement |
|---|---|---|
| Settlement Contract | On-chain | MUST satisfy all requirements of Sections 11–14. |
| core-checkout-engine | Checkout Engine | MUST expose merchant API over mTLS. MUST reconcile charges on confirmed events. |
| checkout-public-widget | Checkout Engine | MUST issue single-use Ephemeral Tokens. |
| ca-server | Checkout Engine | MUST issue mTLS certificates for merchant sessions. |
| login-server | Checkout Engine | MUST issue JWT tokens within mTLS sessions. |
| credentials-manager | Checkout Engine | MUST manage certificate lifecycle. |
| merchant-dashboard | Checkout Engine | SHOULD provide real-time order monitoring and fee reporting. |
| wallet-gateway | Broadcast Layer | MUST accept TransferRequest and BuyAcquiringPackRequest payloads. MUST maintain WebSocket connections. |
| broadcast-service | Broadcast Layer | MUST manage submission state transitions. MUST NOT treat broadcast without revert as final settlement. |
| broadcast-submitter | Broadcast Layer | MUST be the sole component that issues on-chain transactions. |
| balance-and-history | Broadcast Layer | SHOULD maintain real-time wallet balance views and deliver transfer notifications. |
| transfer-history | Event Indexing | MUST monitor the Settlement Contract for PermittedTransfer events. MUST wait for configurable confirmation depth before publishing. |
| basic-data-server | Shared Infrastructure | MUST expose a publicly accessible read-only API: supported token list, Service Provider registry, processor configuration. |
| Client Wallet | Client | MUST satisfy all wallet requirements of Section 6.6. |
5.3 Trust Boundaries
Fully trusted by the payer: the payer's own private key and the wallet software that manages it.
Trusted by the merchant within a verified mTLS session: the core-checkout-engine.
Trusted to relay faithfully, not trusted with funds: the Payment Processor and Relayer. They cannot forge payments — valid signatures are required.
Trusted as a shared public resource: the basic-data-server. No private state; public reference data only.
Trust-minimized — verified on-chain: the Settlement Contract. Its behavior is determined by deployed bytecode, verified by the blockchain network.
6. Component Specifications
6.1 Settlement Contract
The on-chain trust anchor. Responsibilities: verifying both signatures; preventing replay attacks via usedHashes; transferring payment amounts; computing and distributing Operator Fees and Acquiring Fees (all per-token); crediting principal to the beneficiary; registering Acquirers. Fully specified in Sections 11–14.
6.2 Checkout Engine
Manages the lifecycle of payment sessions from charge creation through reconciliation. The core-checkout-engine exposes an API to merchants over mTLS. The checkout-public-widget issues Ephemeral Tokens and delivers session parameters to wallets.
6.3 Broadcast Layer
Receives, validates, queues, submits, and provides real-time status for signed payment payloads. wallet-gateway is the external entry point. broadcast-service manages submission lifecycle. broadcast-submitter holds the Relayer account and is the sole component that issues on-chain transactions. balance-and-history maintains wallet balance views.
6.4 Event Indexing
transfer-history monitors the Settlement Contract for PermittedTransfer events. After configurable block confirmations, it publishes confirmed events to the Checkout Engine for charge reconciliation. It has no write authority over other components.
6.5 Basic Data Service
basic-data-server is a publicly accessible, read-only service providing: supported ERC-2612 stablecoins; registered Acquirers who opted in to public discovery; and Payment Processor public configuration including the Settlement Contract address.
6.6 Client Wallet
A conformant wallet MUST be able to: retrieve session details from the checkout widget via Ephemeral Token; read the payer's ERC-2612 nonce; construct and sign PermitParams using EIP-712; construct and sign PayWithPermitParams using EIP-712; assemble and submit a TransferRequest; maintain a WebSocket connection for status updates; present payment status clearly.
The wallet MUST NOT transmit the payer's private key or seed phrase to any remote service. All signing MUST be performed locally on the user's device.
7. Cryptographic Conventions
7.1 Signature Algorithm
All signatures use ECDSA over secp256k1, as specified in FIPS 186-4 — the same scheme used natively by the Ethereum protocol.
7.2 Typed Data Signing (EIP-712)
Every signature MUST be computed over a structured hash per EIP-712:
digest = keccak256(
0x1901
|| domainSeparator
|| hashStruct(message)
)
The Permit Signature MUST use the token contract's domain separator. The Binding Signature MUST use the Settlement Contract's domain separator. The two domains MUST always be distinct.
7.3 Signature Representation
| Field | Format | Required | Description |
|---|---|---|---|
hash | bytes32 — 66-char hex string | REQUIRED | EIP-712 digest that was signed. |
v | uint8 — integer | REQUIRED | Recovery identifier. MUST be 27 or 28. Values 0 or 1 MUST be normalized by adding 27. |
r | bytes32 — 66-char hex string | REQUIRED | ECDSA r component. |
s | bytes32 — 66-char hex string | REQUIRED | ECDSA s component. |
All hex strings MUST use 0x prefix and lowercase digits. Leading zero bytes MUST be preserved.
8. Data Structures
8.1 Signature Object — ERC20RelayerSig
Reused as permitSig and payWithPermitSig across all payload types.
| Field | Format | Required | Constraints |
|---|---|---|---|
hash | bytes32 hex string | REQUIRED | 0x + 64 lowercase hex chars. |
v | integer | REQUIRED | 27 or 28. Normalize 0→27, 1→28. Reject all other values. |
r | bytes32 hex string | REQUIRED | Full 32 bytes, zero-padded. |
s | bytes32 hex string | REQUIRED | Full 32 bytes, zero-padded. |
8.2 ERC-2612 Permit Parameters — PermitParams
| Field | Format | Required | Description |
|---|---|---|---|
owner | EVM address | REQUIRED | Token holder granting approval. |
spender | EVM address | REQUIRED | Address authorized to transfer tokens. Typically the Settlement Contract. |
value | uint256 | REQUIRED | Total token amount approved, including all fees. |
nonce | uint256 | REQUIRED | Current ERC-2612 nonce of the owner on the token contract. |
deadline | uint256 | REQUIRED | Permit expiry Unix timestamp (seconds). |
8.3 Payment Transfer Parameters — PayWithPermitParams
| Field | Format | Required | Description |
|---|---|---|---|
token | EVM address | REQUIRED | ERC-2612-compliant stablecoin contract address. |
beneficiary | EVM address | REQUIRED | Merchant's receiving address. |
orderReference | bytes16 — 34-char hex string | REQUIRED | 16-byte Order Reference. 0x + 32 hex chars. Zero bytes if absent. |
acquirerId | bytes16 — 34-char hex string | REQUIRED | 16-byte Acquirer ID. Use Zero-UUID if no acquirer. Always present. |
permitParams | PermitParams | REQUIRED | owner MUST match address recovered from payWithPermitSig. |
Note: orderReference and acquirerId are separate fields. They MUST NOT be concatenated. The Settlement Contract uses them independently — orderReference for event emission and charge reconciliation, acquirerId for fee routing.
8.4 Acquirer Registration Parameters — BuyAcquiringPackPermitParams
| Field | Format | Required | Description |
|---|---|---|---|
token | EVM address | REQUIRED | ERC-2612-compliant token for registration fee. Must be in acquiringAllowedTokens. |
feeValue | uint256 | REQUIRED | Registration fee. MUST equal permitParams.value and current acquiringPrice. |
acquiring | EVM address | REQUIRED | Wallet to register as Acquirer. Must not already be registered. |
acquiringFeeBps | uint256 | REQUIRED | Acquiring fee in basis points. Must not exceed maxAcquiringFeeBps for the token. |
permitParams | PermitParams | REQUIRED | value MUST equal feeValue. |
9. Submission Payloads
9.1 Two-Signature Pattern
Every submission carries exactly two signature objects:
| Signature | Signs | Verified by |
|---|---|---|
permitSig | ERC-2612 Permit typed-data | The ERC-2612 token contract on-chain |
payWithPermitSig | Full operation parameters | The Payment Processor off-chain and the Settlement Contract on-chain |
Neither signature alone is sufficient to execute a payment.
9.2 Payment Transfer Payload — TransferRequest
| Field | Required | Description |
|---|---|---|
payWithPermitParams | REQUIRED | Payment parameters as defined in Section 8.3. |
payWithPermitSig | REQUIRED | Binding Signature over payWithPermitParams. Recovered signer MUST equal permitParams.owner. |
permitSig | REQUIRED | Permit Signature. Forwarded verbatim to the Settlement Contract. |
payloadId | REQUIRED | Client-generated UUID- Idempotency key |
9.3 Acquirer Registration Payload — BuyAcquiringPackRequest
| Field | Required | Description |
|---|---|---|
buyAcquiringPackParams | REQUIRED | Registration parameters as defined in Section 8.4. |
payWithPermitSig | REQUIRED | Binding Signature over buyAcquiringPackParams. |
permitSig | REQUIRED | Permit Signature. Signed value MUST equal buyAcquiringPackParams.feeValue. |
10. The Acquiring Model
10.1 Overview
The Stablecoin Stack incorporates a first-class acquiring model allowing third-party participants — Acquirers — to distribute the payment service and earn a share of the processing fee. Acquirers are identified by a bytes16 UUID Acquirer ID. When a payer includes a non-zero Acquirer ID in a payment, the Settlement Contract distributes the Acquiring Fee to the Acquirer's balance atomically with the payment.
10.2 Acquirer Registration
Registration is performed by submitting a BuyAcquiringPackRequest. Requirements: payment of the acquiringPrice in an accepted stablecoin; selection of an Acquiring Fee in basis points, subject to the per-token maxAcquiringFeeBps ceiling; and the wallet address to be registered.
10.3 Fee Distribution
Every payment is subject to:
| Fee | Type | Applicability | Per-Token |
|---|---|---|---|
Base Fee (baseFeeAmount[token]) | Absolute, in token units | Every transfer | Yes |
Basis Points Fee (operatorBps[token]) | Percentage of principal | Every transfer | Yes |
Acquiring Fee (acquiringFeeBps[acquirer][token]) | Percentage of principal | Only when non-zero Acquirer ID | Yes |
The Operator Fee is the sum of the Base Fee and the Basis Points Fee, both evaluated for the specific token being transferred. The Basis Points Fee is applied to the principal amount.
When no Acquirer is involved, the payer MUST pass Zero-UUID as the acquirerId. This suppresses the Acquiring Fee without reverting.
10.4 Service Provider Discovery
An Acquirer MAY publish their information to the basic-data-server to appear as a Service Provider, enabling wallets to present them to payers.
11. Settlement Contract — State Variables
A conformant Settlement Contract MUST maintain the following state:
/// @notice Base fee per token, in token units. Charged on every transfer.
mapping(address => uint256) public baseFeeAmount;
/// @notice Operator fee in basis points per token.
/// Applied to the principal amount on every transfer.
mapping(address => uint256) public operatorFeeBps;
/// @notice Maximum acquiring fee in basis points per token.
/// Enforced at acquirer registration and update.
mapping(address => uint256) public maxAcquiringFeeBps;
/// @notice Acquiring fee in basis points per acquirer per token.
mapping(address => mapping(address => uint256)) public acquiringFeeBps;
/// @notice Internal token balances per participant.
/// Mapping: token address => participant address => amount.
mapping(address => mapping(address => uint256)) public balances;
/// @notice Registry of consumed Binding Signature hashes.
/// A hash present here MUST cause the transaction to revert.
mapping(bytes32 => bool) public usedHashes;
/// @notice Registry mapping Acquirer IDs to wallet addresses.
mapping(bytes16 => address) public acquirerWallets;
/// @notice Set of ERC-2612-compliant tokens accepted for acquirer registration.
mapping(IERC20Permit => bool) public acquiringAllowedTokens;
/// @notice Price in token units required to register as an Acquirer.
/// Per-token, keyed by token address.
mapping(address => uint256) public acquiringPrice;
Key design notes:
baseFeeAmountandoperatorFeeBpsare both per-token. A transfer's Operator Fee isbaseFeeAmount[token] + (principal * operatorFeeBps[token] / 10000).acquiringFeeBpsis per-acquirer per-token. An Acquirer may have different fee rates on different tokens.maxAcquiringFeeBpsis per-token. The ceiling for Acquirer fee rates on that token.acquiringPriceis per-token. The registration cost may differ by token.
12. Settlement Contract — Events
A conformant Settlement Contract MUST emit the following events:
/// @notice Emitted upon successful execution of a permitted token transfer.
event PermittedTransfer(
bytes32 indexed domainSeparator,
address indexed token,
address indexed payer,
address beneficiary,
uint256 value,
uint256 operatorFee,
uint256 acquiringFee,
bytes16 orderReference,
bytes16 acquirerId
);
/// @notice Emitted when a new Acquirer is registered.
event AcquirerCreated(
bytes16 indexed acquirerId,
address indexed wallet,
address indexed token,
uint256 feeBps
);
/// @notice Emitted when an Acquirer updates their fee.
event AcquiringFeeUpdated(
address indexed acquiring,
address indexed token,
uint256 feeBps
);
/// @notice Emitted when an acquiring fee is generated on a payment.
event CommissionGenerated(
bytes16 indexed acquirerId,
address indexed token,
uint256 amount
);
/// @notice Emitted when a participant withdraws their internal balance.
event Withdrawal(
address indexed owner,
address indexed beneficiary,
address indexed token,
uint256 amount
);
Notes:
PermittedTransferemitsorderReferenceandacquirerIdas separatebytes16fields, not concatenated. Thetransfer-historyservice usesorderReferencefor charge reconciliation;acquirerIdis retained for audit.operatorFeeandacquiringFeeare emitted separately to enable transparent fee attribution.
13. Settlement Contract — Fee Model
13.1 Operator Fee Structure
The Operator Fee for a given transfer is:
operatorFee = baseFeeAmount[token] + floor(principal * operatorFeeBps[token] / 10000)
Both components are configured per token. baseFeeAmount[token] provides a floor ensuring at minimum the cost of on-chain execution is recovered. operatorFeeBps[token] adds a percentage component scaled to the transfer value.
13.2 Acquiring Fee
The Acquiring Fee for a given transfer is:
acquiringFee = floor(principal * acquiringFeeBps[acquirerWallet][token] / 10000)
This is zero when acquirerId is Zero-UUID.
13.3 Total With Fees
totalWithFees = principal + operatorFee + acquiringFee
The PermitParams.value MUST equal totalWithFees.
13.4 calculateFees
function calculateFees(
address token,
uint256 principal,
bytes16 acquirerId
) public view returns (
uint256 operatorFee,
uint256 acquiringFee,
uint256 totalWithFees
)
| Parameter | Description |
|---|---|
token | The ERC-2612 stablecoin address. |
principal | The amount intended to reach the beneficiary, in token units. |
acquirerId | The Acquirer ID. Pass Zero-UUID if no acquirer. |
Returns the operatorFee, acquiringFee, and totalWithFees for the specified token and acquirer.
13.5 breakdownTransferAmount
function breakdownTransferAmount(
address token,
uint256 totalWithFees,
bytes16 acquirerId
) public view returns (
uint256 principal,
uint256 operatorFee,
uint256 acquiringFee
)
Decomposes a total amount into its constituent parts. Inverse of calculateFees.
14. Settlement Contract — Functions
14.1 transferWithPermit
Executes a complete stablecoin payment: verifies both signatures, collects the full amount from the payer, distributes fees, and credits the principal to the beneficiary.
function transferWithPermit(
IERC20Permit token,
address tokenOwner,
uint256 amount,
uint256 deadline,
uint8 v1, bytes32 r1, bytes32 s1,
uint8 v2, bytes32 r2, bytes32 s2,
address beneficiary,
bytes16 orderReference,
bytes16 acquirerId
) external
| Parameter | Description |
|---|---|
token | ERC-2612-compliant stablecoin. |
tokenOwner | Token holder. |
amount | Total amount inclusive of all fees. MUST equal PermitParams.value. |
deadline | Permit expiry. |
v1, r1, s1 | Permit Signature — validated by the ERC-2612 token contract. |
v2, r2, s2 | Binding Signature — validated by the Settlement Contract. |
beneficiary | Merchant's receiving address. |
orderReference | 16-byte order reference for reconciliation. |
acquirerId | 16-byte Acquirer ID. Pass Zero-UUID if no acquirer. |
MUST revert if: Binding Signature digest in usedHashes; recovered signer not authorized; deadline <= block.timestamp; token.permit() reverts; tokenOwner balance less than amount.
On success: Binding Signature digest recorded in usedHashes; amount transferred from tokenOwner; Operator Fee distributed to processor fee recipient; Acquiring Fee credited to Acquirer balance (if non-zero acquirerId); principal credited to beneficiary; PermittedTransfer emitted; CommissionGenerated emitted if acquiring fee applies.
14.2 buyAcquiringPack
Registers a new Acquirer. The payer and the wallet being registered may differ.
function buyAcquiringPack(
IERC20Permit token,
address payer,
address acquiring,
uint256 acquiringFeeBps_,
uint256 price,
uint256 deadline,
uint8 v1, bytes32 r1, bytes32 s1,
uint8 v2, bytes32 r2, bytes32 s2
) public
| Parameter | Description |
|---|---|
token | Token for fee. Must be in acquiringAllowedTokens. |
payer | Token holder paying the fee. |
acquiring | Wallet to register. Must not already be registered. |
acquiringFeeBps_ | Acquiring fee in basis points. Must not exceed maxAcquiringFeeBps[token]. |
price | Registration fee. MUST equal acquiringPrice[token]. |
deadline | Permit expiry. |
v1, r1, s1 | Permit Signature. |
v2, r2, s2 | Binding Signature. |
MUST revert if: token not in acquiringAllowedTokens; acquiringFeeBps_ exceeds maxAcquiringFeeBps[token]; price != acquiringPrice[token]; acquiring already registered; Binding Signature digest in usedHashes; token.permit() reverts; payer balance less than price.
On success: Acquirer registered with a new bytes16 Acquirer ID; AcquirerCreated emitted.
14.3 calculateFees
See Section 13.4.
14.4 breakdownTransferAmount
See Section 13.5.
14.5 getBalances
function getBalances(
address token,
address[] calldata users
) external view returns (uint256[] memory)
Returns internal balances for multiple participants in a single token. Response array is parallel to users.
function getBalances(
address[] calldata tokens,
address[] calldata users
) external view returns (uint256[][] memory)
Returns balances[i][j] = internal balance of users[j] for tokens[i].
14.6 getAcquiringWallet
function getAcquiringWallet(
bytes16 acquirerId
) public view returns (address)
Returns the wallet address registered under acquirerId, or the zero address if not found.
15. Validation Rules
A conformant Payment Processor MUST enforce all of the following before broadcasting. Any failure MUST result in immediate rejection.
15.1 Structural Validation
- All REQUIRED fields MUST be present and non-empty.
- All hex strings MUST have
0xprefix and consist of lowercase hexadecimal digits. - Address fields MUST be exactly 42 characters (
0x+ 40 hex digits). bytes32fields MUST be exactly 66 characters (0x+ 64 hex digits).bytes16fields MUST be exactly 34 characters (0x+ 32 hex digits).vMUST be in{0, 1, 27, 28}. Values 0 and 1 MUST be normalized to 27 and 28. All others MUST be rejected.- Numeric fields MUST represent non-negative integers in the
uint256range.
15.2 Semantic Validation
deadlineMUST be strictly greater than processor clock at time of receipt. Processors SHOULD apply a configurable tolerance (recommended: 30 seconds) for clock skew.nonceMUST match the current on-chain permit nonce ofowneron the specified token contract.payloadIdMUST be unique. The processor MUST reject otherwise.orderReferenceandacquirerIdMUST each be exactly 16 bytes (bytes16).- For
BuyAcquiringPackRequest:feeValueMUST equalpermitParams.value.
15.3 Cryptographic Validation
- The signer recovered from
payWithPermitSigMUST equalpermitParams.owner. - The processor SHOULD verify
permitSiglocally before broadcasting to avoid on-chain reversion. - The
hashfield in eachERC20RelayerSigMUST equal the digest recomputed from the accompanying parameters.
16. Submission Flow
16.1 Payment Transfer — Step by Step
- Checkout Engine issues a payment session:
orderReference,beneficiary,token, principal amount,acquirerId, anddeadline. - Client Wallet fetches the ERC-2612 nonce for
ownerfrom the token contract or via wallet-gateway. - Client Wallet calls
calculateFees(token, principal, acquirerId)to determine the Operator Fee and Acquiring Fee. ComputespermitValue = principal + operatorFee + acquiringFee. - Client Wallet constructs
PermitParams:owner,spender(Settlement Contract),value = permitValue,nonce,deadline. - Client Wallet signs the ERC-2612
Permittyped-data using EIP-712 against the token contract's domain separator →permitSig. - Client Wallet constructs
PayWithPermitParams:token,beneficiary,orderReference,acquirerId,permitParams. - Client Wallet signs
PayWithPermitParamsusing EIP-712 against the Settlement Contract's domain separator →payWithPermitSig. - Client Wallet generates a
payloadId(UUID) and assembles theTransferRequest. - Client Wallet submits the
TransferRequestto wallet-gateway over HTTPS. - Payment Processor validates per Section 15 and broadcasts the transaction on success.
- Settlement Contract executes
permit(),transferFrom(), and fee distribution atomically.
16.2 Acquirer Registration — Step by Step
- Client Wallet fetches
acquiringPrice[token]andmaxAcquiringFeeBps[token]from the Settlement Contract or via wallet-gateway. - Client Wallet fetches the ERC-2612 nonce.
- Client Wallet constructs
PermitParams:value = acquiringPrice[token]. - Client Wallet signs the
Permittyped-data →permitSig. - Client Wallet constructs
BuyAcquiringPackPermitParams:token,feeValue,acquiring,acquiringFeeBps_,permitParams. - Client Wallet signs
BuyAcquiringPackPermitParams→payWithPermitSig. - Client Wallet assembles and submits
BuyAcquiringPackRequest. - Settlement Contract executes registration and emits
AcquirerCreated.
17. End-to-End Payment Flow
Phase A — Session Creation
- Merchant Server sends charge creation request to the core-checkout-engine over mTLS: amount, token, beneficiary address, order metadata,
deadline. - core-checkout-engine generates a 16-byte
orderReference. Creates a charge session inawaiting-paymentstate. - core-checkout-engine returns a Checkout Widget URL and Ephemeral Token to the Merchant Server, which presents it to the payer.
Phase B — Payload Construction and Signing
- Client Wallet redeems the Ephemeral Token against the checkout widget to retrieve session parameters. Token is invalidated on first use.
- Wallet fetches ERC-2612 nonce and fee breakdown.
- Wallet constructs and signs
PermitParams(Permit Signature against token contract domain separator). - Wallet constructs and signs
PayWithPermitParams(Binding Signature against Settlement Contract domain separator).
Phase C — Submission and Off-Chain Validation
- Wallet assembles
TransferRequestand submits to wallet-gateway. WebSocket connection maintained for status updates. - broadcast-service enqueues the submission; wallet-gateway acknowledges receipt.
- Payment Processor validates per Section 15. Any failure causes immediate rejection.
Phase D — On-Chain Settlement
- broadcast-submitter calls
transferWithPermiton the Settlement Contract. - Settlement Contract verifies Binding Signature, checks
usedHashes, callspermit(), grants allowance. - Settlement Contract calls
transferFrom, distributes Operator Fee, distributes Acquiring Fee (if applicable), credits principal to beneficiary, records Binding Signature hash inusedHashes, emitsPermittedTransfer. All operations are atomic. - broadcast-service delivers a status update confirming the transaction was broadcast. This is not final settlement.
Phase E — Confirmation and Reconciliation
- transfer-history observes
PermittedTransferevent. After configurable block confirmations, publishes the confirmed event to core-checkout-engine. - core-checkout-engine matches
orderReferenceagainst its session registry and marks the charge settled. - core-checkout-engine dispatches settlement webhook to Merchant Server.
- balance-and-history delivers final settlement notification to wallet
18. Error Handling
| Error Category | Description |
|---|---|
STRUCTURAL_ERROR | Required field absent or encoding violated. Reject immediately; no on-chain activity. |
SEMANTIC_ERROR | Structurally valid but a business rule violated (expired deadline, nonce mismatch, repeated payloadId). |
CRYPTOGRAPHIC_ERROR | Signature cannot be verified or recovered signer does not match owner. MUST NOT disclose details aiding forgery. |
BROADCAST_ERROR | Payload passed all validation but on-chain transaction reverted. Record failure; MUST NOT mark session settled. |
19. Security Model
19.1 Threat Model
| Threat | Mitigation |
|---|---|
| Forged payment | ECDSA signature; only the payer's private key produces a valid signature |
| Replay attack | usedHashes in Settlement Contract; ERC-2612 nonce on token contract |
| Parameter substitution | Binding Signature covers all parameters including orderReference and acquirerId separately; modification invalidates the signature |
| Processor substituting beneficiary | Beneficiary embedded in Binding Signature |
| Expired permit | Settlement Contract enforces block.timestamp <= deadline |
| Relayer double-spend | Impossible without valid Binding Signature; Relayer has no signing authority |
| Merchant impersonation | mTLS between merchant servers and core-checkout-engine |
19.2 Dual-Signature Binding
The Permit Signature authorizes a token allowance. The Binding Signature authorizes the specific operation — token, beneficiary, amount, orderReference, and acquirerId. Neither alone is sufficient to execute a payment.
19.3 Replay Protection
Two independent layers: the ERC-2612 nonce (per owner, per token contract) and the usedHashes registry (per Binding Signature digest, in the Settlement Contract). Both MUST be active. They are complementary, not redundant.
19.4 Payer-Agnostic Execution
Any party holding both valid signatures can submit the transaction. This is acceptable because: signatures bind the payment to specific parameters; usedHashes prevents reuse; submitting on behalf of a payer is precisely what they authorized. Clients MUST submit payloads over TLS and MUST NOT persist signed payloads in environments accessible to untrusted parties.
19.5 Administrative Privilege Boundaries
The Administrator MUST be able to: set per-token baseFeeAmount, operatorFeeBps, maxAcquiringFeeBps, acquiringPrice; set the fee recipient address.
The Administrator MUST NOT be able to: transfer tokens held in participant balances; modify usedHashes; alter a registered Acquirer's wallet address or fee rate without their consent.
These constraints MUST be enforced at the contract code level and verified by independent audit.
20. Conformance
20.1 Fully Conformant Stack
A fully conformant deployment MUST include: a deployed Settlement Contract satisfying Sections 11–14; a Checkout Engine satisfying Section 6.2; a Broadcast Layer satisfying Sections 6.3 and 15; a transfer-history service; and a Basic Data Service. A conformant Client Wallet MUST satisfy Section 6.6.
20.2 Partial Conformance
Components MAY be implemented independently. Partial conformance MUST be declared explicitly with clear indication of which sections are and are not satisfied.
20.3 Companion Specifications
| Spec ID | Subject |
|---|---|
| FPSF-SS-002 | wallet-gateway Interface |
| FPSF-SS-003 | Checkout Engine API (planned) |
| FPSF-SS-004 | Broadcast Layer Protocol (planned) |
| FPSF-SS-005 | Client Wallet Certification (planned) |
21. Future Work
Offline Transaction Signing. Enabling payers to construct and sign a payment commitment without live network access. Priority for populations with limited connectivity.
Multi-Network Support. Extending conformance to non-EVM networks with equivalent programmable token standards.
Wallet Certification. A conformance test suite and certification programme for third-party wallet implementations.
orderReference field semantics. A future minor version will formalize the orderReference encoding and any extensions the merchant may embed.
22. Normative References
| Reference | Description |
|---|---|
| FPSF-CPD-001 v1.0.0 | Canonical Payment Definition |
| ERC-20 | Token Standard — Ethereum Improvement Proposal 20 |
| ERC-2612 | Permit Extension for EIP-20 Signed Approvals |
| EIP-712 | Typed Structured Data Hashing and Signing |
| ECDSA / FIPS 186-4 | Digital Signature Standard |
| RFC 2119 | Key words for use in RFCs to Indicate Requirement Levels |
| RFC 4648 | The Base16, Base32, and Base64 Data Encodings |
| RFC 8446 | The Transport Layer Security (TLS) Protocol Version 1.3 |
| RFC 8705 | OAuth 2.0 Mutual-TLS Client Authentication |
| Solidity 0.8.x | Smart Contract Programming Language |
| Semantic Versioning 2.0.0 | https://semver.org |
FPSF-SS-001 v1.0.0 · Draft · Fabric Payment Standards Foundation · Apache-2.0