Introduction
Before multi-settlement, an intent named exactly one way to be paid: one
settlement asset, one amount, one destination. Multi-settlement lets a single
intent offer several settlement alternatives, and lets each alternative be a
bundle of legs that must all settle together.
The model is a menu: pick exactly one option, take all of it.
settlements (ONE-OF — the executor picks exactly one)
├── group 0 (ALL-OF — every leg settles, or none do)
│ ├── leg: 50 USD → seller's USD account
│ └── leg: 3 GBP → seller's GBP account
└── group 1 (ALL-OF)
└── leg: 45 EUR → seller's EUR account
Two terms are used throughout:
- settlement group — one alternative on the menu. All of its legs settle
atomically; there is no partial settlement of a group. - leg — one asset movement within a group: an asset, an amount, and the
accounts it moves between.
Supported on buying, selling and private offer intents. Issuance,
loan, transfer, redemption, request-for-transfer and move are unchanged.
The flow
- Declare — the intent owner creates an intent carrying a list of settlement
groups. - Select — the executor submits a flat list of the legs it will settle.
- Match — the router matches that leg-set against the declared groups. It
must match exactly one group, as a set, on both leg identity and amount. - Execute — the matched group is recorded on the execution plan as
selectedSettlement, and every leg gets its own settlement instructions.
Matching is set-based: leg order does not matter.
Where the fields live
The same concept appears under three different names, one per stage. This is the
most common source of confusion, so it is worth memorising:
| Stage | Endpoint / object | Field | Shape |
|---|---|---|---|
| Declare | create intent (buyingIntent, sellingIntent, privateOfferIntent) | settlements | array of groups, each { legs: [...] } — nested |
| Select | execute intent (buyingIntentExecution, sellingIntentExecution, privateOfferIntentExecution) | settlements | flat array of the legs being settled — not grouped |
| Result | execution plan → contract.contractDetails | selectedSettlement.legs | the one matched group, flattened |
The executor does not send a group index. It sends the legs, and the router
infers which group was chosen. That keeps the selection self-describing and
means a client cannot pick a group while paying something else.
Declaring a menu
A selling intent offering two alternatives — be paid in one bundle of USD +
GBP, or in EUR alone. The settlement asset is identified by the account the leg
pays into, not by the term:
{
"settlements": [
{ "legs": [
{ "settlementTerm": { "type": "fullSettlement", "amount": "50" },
"settlementInstruction": {
"destinationAccount": {
"account": { "type": "finId", "finId": "02e1...", "orgId": "bank-us" },
"asset": { "assetId": "bank-us:102:usd-asset" }
} } },
{ "settlementTerm": { "type": "fullSettlement", "amount": "3" },
"settlementInstruction": {
"destinationAccount": {
"account": { "type": "finId", "finId": "02e1...", "orgId": "bank-uk" },
"asset": { "assetId": "bank-uk:102:gbp-asset" }
} } }
] },
{ "legs": [
{ "settlementTerm": { "type": "fullSettlement", "amount": "45" },
"settlementInstruction": {
"destinationAccount": {
"account": { "type": "finId", "finId": "02e1...", "orgId": "bank-eu" },
"asset": { "assetId": "bank-eu:102:eur-asset" }
} } }
] }
]
}Selling and private-offer legs name a destination account — where the seller
gets paid. Buying legs are the mirror image: a buying leg names the buyer's
paying source account (settlementInstruction.sourceAccount), and leg
identity is keyed on it accordingly.
An empty settlements list (or a missing one, with no singular settlement
either) is valid and means free-of-payment: the asset moves and nothing is
paid.
Selecting at execution
To take group 0, the executor submits both of its legs, flat. Execution-side legs
use the shorter term / instruction names:
{
"settlements": [
{ "term": { "amount": "50" }, "instruction": { "...": "USD leg accounts" } },
{ "term": { "amount": "3" }, "instruction": { "...": "GBP leg accounts" } }
]
}Submitting only the USD leg is rejected — a group is all-or-nothing.
Rules and errors
Checked when the intent is created:
| Code | Rule |
|---|---|
7327 | A present group must declare at least one leg. |
7325 | No duplicate leg (same asset + destination) within one group. |
7326 | No two groups with an identical leg set. Groups are identified by which assets move where, never by amount — so two groups differing only in amount are the same option, and the second is rejected. |
4003 | All legs within one group must share a settlement strategy — all fullSettlement, or all partialSettlement, never mixed. |
Overlapping or subset groups across different groups are allowed. "USD+GBP" and
"USD alone" can both be on the menu.
Checked when the intent is executed:
| Code | Rule |
|---|---|
7324 | Execution must select exactly one group. Zero legs against a non-empty menu, or legs against an empty menu, both fail here. |
7321 | A declared leg of the selected group was not submitted. |
7322 | A submitted leg belongs to no declared group. |
7323 | A submitted leg's amount differs from the declared amount. |
7328 | The submitted leg-set matches no declared group, and no single group was close enough to name a more specific reason. |
For a partial (unit-priced) leg, the expected amount is the leg's declared
unitValue multiplied by the executed asset quantity, not a fixed figure.
Amounts are compared after exponent normalisation, so 50 and 50.00 are equal.
Backward compatibility
Nothing about single-settlement trades changes. A client that never sends
settlements keeps working exactly as before.
The behaviour worth knowing about is on the contract, because it is not
symmetrical:
| Trade | contractDetails.settlement | contractDetails.selectedSettlement |
|---|---|---|
| Single settlement (legacy shape) | present | present, one leg |
| Menu, any group selected | absent | present, one leg per leg of the group |
selectedSettlement is therefore the field to read in new integrations — it is
populated in both cases. Code that reads only the singular settlement will find
it missing on any menu-based trade.
Protocol version and signatures
Two gates apply, and both fail loudly rather than silently degrading:
- Protocol version 2, gated on the selected group. Multi-settlement is
advertised as theINTENT_FEATURE_MULTI_SETTLEMENTcapability, which requires
protocol version 2. The feature is inferred from the plan only when the
selected group has more than one leg, so a menu whose groups are all
single-leg still runs against a v1 counterparty. - Investor signature template v3, gated on the declared menu. Any intent
that declares one or more settlement groups must sign with template version 3
or above (hashlistoreip712) — even a single group with a single leg.
v3 is additive on top of v2: for every intent type other than buying /
selling / private offer it is byte-identical, and for those three it appends
trailing hash groups encoding the menu, so the whole menu is inside what the
investor signed. See Transaction Signature Schemes for the field-level
layout.
The two gates deliberately trigger on different things: the signature must cover
whatever was offered, while the protocol version only has to cover what is
actually executed.
Policies
Each leg is policy-matched on its own settlement asset, so a two-leg group can
settle under two different policies — a USD leg under a HashList policy and a
GBP leg under an EIP-712 one, each holding under its own terms.
For policies that must govern every leg without knowing the leg count up
front, the operational API adds two selector types, batch_account and
batch_asset. Their path traverses the repeated legs field, and that
traversal is the fan-out:
contract_details.private_offer.selected_settlement.legs.asset_instruction.source_account
One batch selector resolves to one account per leg. The execution-plan
translator replicates the instruction per leg, and each replica is matched
against its own leg, which makes a policy written this way independent of how
many legs a group happens to have. A path that names a singular field degrades
to a single result, so the same selector works for a one-leg group.
Appendix — contract details JSON
Below is a real contract.contractDetails from an executed buying trade with a
two-leg group (50 of a bank-us asset plus 3 of a bank-uk asset), retrieved
with GET /finapi/execution/{planId}:
{
"type": "buying",
"asset": {
"instruction": {
"destinationAccount": {
"account": {
"custodian": { "orgId": "ownera-custody" },
"finId": "02e154210ca0746c7739de6319ce1cdeab26ffbcfc58fe8cbe07f905fec6c2709c",
"orgId": "bank-us",
"type": "finId"
},
"asset": {
"id": "bank-us:102:9afb3e85-16f5-4a19-a61e-16c6ee570caf",
"ledgerIdentifier": {
"assetIdentifierType": "CAIP-19",
"network": "bank-us-vanilla",
"standard": "GBA",
"tokenId": "ebda20a6-4282-43b3-9fc0-09f360154829"
}
}
},
"sourceAccount": {
"account": {
"custodian": { "orgId": "ownera-custody" },
"finId": "03abdf71432b51a664f2f82b98f6450c0fb87489496afdfb798735d86e41cf3afc",
"orgId": "bank-us",
"type": "finId"
},
"asset": {
"id": "bank-us:102:9afb3e85-16f5-4a19-a61e-16c6ee570caf",
"ledgerIdentifier": {
"assetIdentifierType": "CAIP-19",
"network": "bank-us-vanilla",
"standard": "GBA",
"tokenId": "ebda20a6-4282-43b3-9fc0-09f360154829"
}
}
}
},
"term": { "amount": "10" }
},
"selectedSettlement": {
"legs": [
{
"instruction": {
"destinationAccount": {
"account": {
"custodian": { "orgId": "ownera-custody" },
"finId": "03abdf71432b51a664f2f82b98f6450c0fb87489496afdfb798735d86e41cf3afc",
"orgId": "bank-us",
"type": "finId"
},
"asset": {
"id": "bank-us:102:ac51ea85-705a-4aec-9ad0-c181520c1c75",
"ledgerIdentifier": {
"assetIdentifierType": "CAIP-19",
"network": "bank-us-vanilla",
"standard": "GBA",
"tokenId": "5073d2b3-72b7-4db3-8a6a-140e8c091f26"
}
}
},
"sourceAccount": {
"account": {
"custodian": { "orgId": "ownera-custody" },
"finId": "02e154210ca0746c7739de6319ce1cdeab26ffbcfc58fe8cbe07f905fec6c2709c",
"orgId": "bank-us",
"type": "finId"
},
"asset": {
"id": "bank-us:102:ac51ea85-705a-4aec-9ad0-c181520c1c75",
"ledgerIdentifier": {
"assetIdentifierType": "CAIP-19",
"network": "bank-us-vanilla",
"standard": "GBA",
"tokenId": "5073d2b3-72b7-4db3-8a6a-140e8c091f26"
}
}
}
},
"term": { "amount": "50" }
},
{
"instruction": {
"destinationAccount": {
"account": {
"custodian": { "orgId": "ownera-custody" },
"finId": "03abdf71432b51a664f2f82b98f6450c0fb87489496afdfb798735d86e41cf3afc",
"orgId": "bank-uk",
"type": "finId"
},
"asset": {
"id": "bank-uk:102:e48bccc7-276a-4264-8b5a-611837f7bdf3",
"ledgerIdentifier": {
"assetIdentifierType": "CAIP-19",
"network": "bank-us-vanilla",
"standard": "GBA",
"tokenId": "5073d2b3-72b7-4db3-8a6a-140e8c091f26"
}
}
},
"sourceAccount": {
"account": {
"custodian": { "orgId": "ownera-custody" },
"finId": "02e154210ca0746c7739de6319ce1cdeab26ffbcfc58fe8cbe07f905fec6c2709c",
"orgId": "bank-uk",
"type": "finId"
},
"asset": {
"id": "bank-uk:102:e48bccc7-276a-4264-8b5a-611837f7bdf3",
"ledgerIdentifier": {
"assetIdentifierType": "CAIP-19",
"network": "bank-us-vanilla",
"standard": "GBA",
"tokenId": "5073d2b3-72b7-4db3-8a6a-140e8c091f26"
}
}
}
},
"term": { "amount": "3" }
}
]
}
}investors sits alongside contractDetails on the contract and is unchanged by
multi-settlement — the same buyer/seller entries, each with its signature. Note
that a selling or private offer contract has the identical
selectedSettlement.legs shape; only the type discriminator and which side of
each leg carries the counterparty account differ.
