AttestationRequestBuilder

Everything a person agrees to before an attestation request is submitted — the canonicalised bytes, their hash, the fee read for those exact bytes, the round, the wait and the bound proof owner.

import { AttestationRequestBuilder } from '@flarekit-dev/react-ui'

AttestationRequestBuilder shows the request before it is sent. The rule it keeps: nothing is offered that cannot be paid for or cannot produce a usable proof. A family this kit has no builder for, a body the verifier rejected, and a proof bound to an address that is not connected are all refusals shown here, rather than reverts discovered after gas.

Live#

The preview runs the gallery's own states. The submittable case and each refusal are distinct rendered states, not one blocked panel wearing several hats.

mock kit

XRPPayment

An XRPL payment, with the source r-address, memo data and destination tag surfaced directly. FAssets consumes this, not the chain-agnostic Payment type.
Attestation typeXRPPayment
SourcetestXRP XRP Ledger Testnet, via verifier group xrp
RequestBytes0xabababab…ababab 80 bytes, canonicalised by the verifier — priced and submitted exactly as shown.
Request hashkeccak2560x7e7e7e7e…7e7e7e Compare this, not the bytes.
Request fee0.000000000000001000 C2FLR Read for these exact bytes, and sent unchanged.
Verifierhttps://fdc-verifiers-testnet.flare.network/verifier/xrp/XRPPayment/prepareRequest
Proof providerhttps://ctn2-data-availability.flare.network Where the proof is retrieved once the round finalizes.
Expected round1415859 The voting round this request would be collected in.
Expected wait90180s The rest of the collecting round, plus one to finalize.
Proof ownerBound to0xa4b0…1bd9 Only this address can present the resulting proof.
Consumed byFAssets AssetManager.executeDirectMinting

Usage#

The panel is presentational. The host canonicalises the body at the verifier and reads the fee for those exact bytes, then hands both over — the component neither fetches nor signs.

import { createFdcClient, getRequestFee, xrpPaymentFamily } from '@flarekit-dev/core'
import { AttestationRequestBuilder } from '@flarekit-dev/react-ui'
import '@flarekit-dev/react-ui/styles.css'

// The verifier canonicalises the body; the chain prices those exact bytes.
const client = createFdcClient({ services, family: xrpPaymentFamily, source })
const prepared = await client.prepareRequest({ transactionId })
const value = await getRequestFee(reader, chainId, prepared.abiEncodedRequest)

export function Request({ row, account }) {
  return (
    <AttestationRequestBuilder
      row={row}
      sourceId={source.sourceId}
      prepared={prepared}
      fee={{ value, asset: 'C2FLR', decimals: 18 }}
      dataAvailabilityBaseUrl={client.dataAvailabilityBaseUrl}
      proofOwner={account}
      sender={account}
      onSubmit={submit}
    />
  )
}

Props#

PropTypeDefaultDescription
rowrequiredFamilyRowThe catalogue row this request targets, carrying the family, its compared status and the deployment's note. The row decides whether a builder exists and whether the family is served at all.
sourceIdrequiredstringWhich of the family's sources the request attests against. Named on screen with its chain and verifier group.
preparedPreparedRequestCore's own prepared request, present once the verifier has canonicalised the body. Carries the bytes, their keccak256 hash and the verifier that produced them. Without it the request is not submittable.
fee{ value: bigint; asset: string; decimals: number }Read from getRequestFee for these exact bytes, and sent unchanged. Undefined until read, which renders as Not read rather than a placeholder number.
feeUnavailableReasonstringWhy the fee could not be read, when it could not. Distinct from an unread fee: a read still in flight and a deployment that refused to price the request are different facts.
dataAvailabilityBaseUrlstringWhere the proof would be retrieved from once the round finalizes, so the wait names its endpoint.
expectedRangeSeconds{ min: bigint; max: bigint }The wait as a range. A single number would be wrong for almost every request — how long you wait depends on where in the collecting round you landed.
expectedRoundbigintThe voting round this request would be collected in.
proofOwnerstringThe address the request would bind the proof to, when the family binds one. Only that address can present the resulting proof.
senderstringThe connected account that would submit. Compared against the proof owner to catch a proof this account could not use.
errorSerializedErrorA rejected body or a refused precondition. Rendered as a state, never thrown at the component.
submittingbooleanfalseTrue while the host is sending the request. Disables the button and says so on it.
onSubmit() => voidCalled when the reader submits an unblocked request. Omit it and the panel is read-only.
theme'light' | 'dark'Overrides the inherited theme. Normally left unset — the widget follows data-theme.
classNamestringExtra class on the outer element, so a host layout can place the panel.

What it renders#

One panel of terms: the attestation type, the source with its chain and verifier group, the canonicalised bytes with their byte count, the request hash to compare instead of the bytes, the fee, the verifier and proof provider hosts, the expected round, the expected wait as a range, the bound proof owner, and the contract that consumes the proof.

The fee is always a read value carrying its asset and full precision, never a rounded or assumed one. Measured on 2026-08-04 it is 1000 wei on Coston2 and 20 FLR on Flare mainnet — 3 FLR there for ConfirmedBlockHeightExists — so a constant in this panel would be wrong on one of the two networks.

Below the terms sits the reason the request cannot go, when it cannot. That reason is decided once, so the disabled button, the sentence under it and the data-block attribute a test reads cannot disagree.

States#

Every state in the switcher above is imported from packages/react-ui/gallery/, one source of truth for both the gallery and these docs:

  • ready to submit — canonicalised, priced, bound to the connected account.
  • not canonicalised yet — the verifier has not returned bytes, so there is nothing to price and nothing to submit.
  • fee unavailable — the deployment declined to price the request. The verifier may still serve the route; the on-chain type and source pair is what refused. Nothing is submitted at a guessed price.
  • builder planned — the deployment serves the family and this kit ships no request builder for it. Listed because it exists, not because it works.
  • proof-owner mismatch — the request would bind one address and another is connected. Submitting would spend the fee for a proof this account cannot present.
  • invalid body — the verifier rejected the request and said why. The message is the verifier's, rendered as a state.
  • submitting — the request is being sent; the button says so and is disabled.

Mock to live#

The panel reads nothing itself, so nothing about it changes between networks. What changes is where createFdcClient and getRequestFee point, and both take their hosts and addresses from @flarekit-dev/contracts.

// Mock: the prepared request and the fee are values you hold.
<AttestationRequestBuilder row={row} sourceId="testXRP" prepared={PREPARED} fee={FEE} />

// Live: the same two values, read from the verifier and the chain.
<AttestationRequestBuilder row={row} sourceId={source.sourceId} prepared={prepared} fee={fee} />

What it will not do#

It will not offer a submit it knows cannot produce a usable proof, and it will not show a rounded or assumed fee — an unread fee reads Not read, never a placeholder number.

It also has no quota limited state. A verifier refusing for rate or volume reasons is a defined outcome, but no quota response has ever been observed from either verifier across this project's live runs. Rendering one would mean inventing a provider response and showing a message no verifier has sent, so the state is declared unbuilt rather than faked. One captured quota response — its status, body and headers — is all it needs.