Contract

ScripFacilitator

One contract. It settles a single invoice, binds it to a payee, and checks what actually arrived. Deployed and source-verified on Coston2.

Addresses

FXRP is never hardcoded in the code that runs: it is resolved through the Flare Contract Registry → AssetManagerFXRPfAsset(), so a redeployment of the token does not silently break anything. The address above is what that path returned.

0.8.24
Solidity, optimizer 200 runs
london
EVM target, no PUSH0 / MCOPY
744,486
Deployment gas
3,185
Runtime bytecode, bytes
Why london and not the default.

Solidity 0.8.24 targets cancun by default, which emits TSTORE and MCOPY; shanghai emits PUSH0. Flare's EVM is Avalanche-derived, and its support for those is not something to discover from a failed deployment. The deployed runtime bytecode was checked for all three and contains none.

Interface

struct PaymentIntent {
    bytes32 invoiceId;
    address payer;
    address payee;
    uint256 amount;
    uint256 deadline;
}

struct Signature { uint8 v; bytes32 r; bytes32 s; }

function settle(
    PaymentIntent calldata intent,
    Signature     calldata permitSig,   // over FXRP's EIP-712 domain
    Signature     calldata intentSig    // over this contract's domain
) external;

function intentDigest(PaymentIntent calldata intent) external view returns (bytes32);
function token()          external view returns (address);
function settled(bytes32) external view returns (bool);
function DOMAIN_SEPARATOR()        external view returns (bytes32);
function PAYMENT_INTENT_TYPEHASH() external view returns (bytes32);

event PaymentSettled(
    bytes32 indexed invoiceId,
    address indexed payer,
    address indexed payee,
    uint256 requested,
    uint256 delivered
);

The intent and both signatures are passed as calldata structs rather than as flat parameters. That is not stylistic: eleven flat arguments overflow the stack under legacy codegen, and structs fix it without turning on via-ir, which would change the codegen the london target was chosen to control.

Check the digest before you sign

intentDigest() is exposed so an offchain signer can assert its locally built digest matches the chain's, rather than discovering a mismatch as an unexplained revert. If the two disagree, every settle() fails with IntentNotSignedByPayer and the error names no cause. settle.ts makes this check before it signs anything.

const local = hashTypedData({
  domain: { name: "Scrip", version: "1", chainId: 114, verifyingContract: facilitator },
  types: { PaymentIntent: [
    { name: "invoiceId", type: "bytes32" },
    { name: "payer",     type: "address" },
    { name: "payee",     type: "address" },
    { name: "amount",    type: "uint256" },
    { name: "deadline",  type: "uint256" },
  ]},
  primaryType: "PaymentIntent",
  message: intent,
});

const onchain = await client.readContract({
  address: facilitator, abi, functionName: "intentDigest", args: [intent],
});

if (local !== onchain) throw new Error("do not sign - the domains disagree");

Custom errors

Every failure is a named error rather than a bare revert. Put these in your ABI so a revert decodes to a cause instead of a selector.

SelectorErrorMeans
0xaa2fd925 Expired The deadline passed before the transaction was mined. Re-sign and resubmit.
0xb196a44a AlreadySettled That invoice ID was already paid. IDs are single-use.
0x90f478ba IntentNotSignedByPayer The intent signature recovered to someone other than the payer - a substituted field, or a domain mismatch.
0x2a1b2dd8 InsufficientAllowance The permit did not land and no allowance was already in place.
0x9a8c96da Underdelivered The payee received less than the invoice - the token levied a transfer fee.
0xf0ad0d09 MalleableSignature The signature's s was in the upper half order. The signer is not normalising to EIP-2.
0x1f003d0a BadSignatureV v was neither 27 nor 28.
0x90b8ec18 TransferFailed transferFrom reverted or returned false.
0xe1ee996d ZeroPayee The payee was the zero address.

Properties it enforces

The first three are exercised as attacks, not asserted - see the evidence.