FPSF-SS-002 — Core Concepts
Layer: Core Concepts · Audience: wallet developers, integration engineers For normative requirements, see the Formal Specification.
Connection Model
The wallet-gateway uses WebSocket as its sole transport because the core operations it serves are inherently asynchronous. Payment confirmation spans multiple network block intervals. WebSocket allows the gateway to push status updates as they occur, without the client needing to poll.
All operations — balance queries, fee lookups, payment submissions, real-time notifications — share the same connection. This keeps the wallet interface uniform and efficient.
Single connection per wallet. At any moment, only one active connection is permitted per wallet address. A new connection for the same address supersedes the previous one — the previous connection is closed with code 4001 / "superseded". This ensures the most recently authenticated client is always the active one.
Idle timeout. Connections are not held open indefinitely. If no message is received within the idle timeout (recommended: 5 minutes), the gateway closes the connection. Before closing, it sends a WebSocket ping frame; responding with pong resets the timer. Wallet clients that want long-lived connections SHOULD respond to pings.
Reconnection. From the gateway's perspective, every new WebSocket handshake is a fresh connection. After reconnecting, the wallet MUST re-register any subscriptions it requires and SHOULD query current balances to resynchronize.
Message Authentication
Every message sent to the gateway must be signed by the private key of the wallet address it claims to be from. There are no sessions, no tokens, and no cookies. The signature on the message is the credential.
The message envelope carries: the wallet address (callerAddress); a deadline after which the message is no longer valid; the operation and its parameters (payload); and an ECDSA signature over the message content.
The gateway verifies that the signature was produced by the private key corresponding to callerAddress. If it was, the message is accepted. If not — for any reason — the message is rejected immediately. No partial processing occurs.
Setting the deadline. The deadline is a Unix timestamp in seconds, set to a point in the near future (typically one to five minutes). This limits the window in which an intercepted signed message could be replayed. The gateway also maintains a short-lived cache of processed message hashes to detect and reject exact duplicates within the validity window.
What this means for wallet developers. Every message must be signed. Signing must happen locally on the device — private keys must never be sent to the gateway. Each message needs a fresh deadline. The hash field in the signature must be the EIP-712 digest of the specific message being sent.
Subscription Model
Two push notification channels are available: balance updates and transfer notifications. Both channels can be active simultaneously for any combination of tokens.
Subscriptions are scoped to the connection. They do not persist across connections, are not transferred to a new connection, and are not recoverable after a disconnect. When the gateway closes a connection — for any reason — all subscriptions on that connection are cancelled immediately.
A reconnecting client starts with no subscriptions and must re-register the ones it needs. After reconnecting, the client SHOULD also query current balances and recent history, since notifications delivered during the disconnection window were not received.
Balance updates. Sent whenever the balance of a subscribed token changes for the connected wallet. Useful for keeping balance displays current without polling.
Transfer notifications. Sent whenever a new confirmed transfer is indexed for the connected wallet on a subscribed token. This is the definitive signal of final settlement — not the SUCCESS submission status. A TRANSFER_NOTIFICATION arrives only after transfer-history has observed the on-chain PermittedTransfer event with the required block confirmation depth.
Wallet applications MUST NOT present a payment as complete based on the SUCCESS submission status alone. The correct pattern is to subscribe to transfers on the relevant token before submitting a payment, then wait for the TRANSFER_NOTIFICATION.
Fee Retrieval
The GET_FEES operation returns a BrokenDownAmount with three fields: operatorFee, acquiringFee, and totalWithFees. This reflects the per-token, two-component Operator Fee model defined in FPSF-SS-001 Section 13.
The acquirerId parameter in GET_FEES is a bytes16 value — the same separate field used in PayWithPermitParams. Pass Zero-UUID (0x00000000000000000000000000000000) if no Acquirer is involved.
The totalWithFees value returned is the exact amount that MUST be placed in PermitParams.value. Wallets MUST call GET_FEES (or calculateFees directly on the Settlement Contract) immediately before constructing each permit. Do not cache fee values between sessions — fee parameters may change.
Payment Submission and the Status Lifecycle
Accepted payment submissions pass through the following states, delivered as SUBMISSION_STATUS push notifications:
ENQUEUING → PENDING → BROADCASTING → SUCCESS or FAILURE
BROADCASTING means the Relayer submitted the transaction to the network. SUCCESS means the network accepted it without revert. Neither of these is final settlement.
Final settlement arrives as a TRANSFER_NOTIFICATION after the transfer-history service confirms the on-chain event. The wallet MUST subscribe to transfer notifications for the relevant token before submitting a payment to guarantee it receives this confirmation.
FPSF-SS-002 v1.0.0 · Draft · Fabric Payment Standards Foundation · Apache-2.0