Batched payout: the other real PayoutPolicy
Every chapter so far runs under PayoutPolicy.Immediate — Ishtaran's default, and the one every
worked example on this site uses: executeSettlement() pays Bob and Mercatto's commission the
same moment, by building a real SigningRequest against Mercatto's own execution wallet (see
Settlement and Split). There is a second real, public mode — Manual —
where executeSettlement() never builds a SigningRequest at all. It posts a real, balanced
Ledger entry (Reserved(payer) → Payable(beneficiary)) and completes immediately. The money only
actually moves later, when Mercatto explicitly creates a PayoutBatch.
When to use which
- Immediate — the common case, and the right default. A beneficiary is paid the moment their Settlement executes. Use this unless you have a specific reason to accumulate.
- Manual — useful when you want to batch many small Settlements into fewer, larger on-chain
broadcasts (lower total network cost), or when your own payout cadence is deliberately not
"the instant a Settlement happens" (e.g., a weekly seller payout run). A beneficiary's Payable
balance (
payout.getPayableSummary().accrued) can grow across many Settlements before anything is actually paid out.
Threshold and Scheduled also exist in the domain model (an automatic trigger once a balance
crosses a threshold, or on a cron schedule) but have no public route to trigger them yet — this
chapter, and this platform, only ever create a PayoutBatch with trigger = Manual.
Two bootstrap steps this chapter needs, neither needed anywhere else in this tutorial
PayoutPolicy itself is a Platform Owner decision, not something Mercatto's own Application API
Key or Member session can set — POST /v1/admin/organizations/{organizationId}/payout-policy,
authenticated with a Platform Owner API Key. No official SDK exposes this route on purpose: it is
not a Data Plane capability an integrator's backend calls at runtime, the same way none of the SDKs
expose the platform's global Pricing Policy configuration. Real, found in the same pass as this
chapter: before this route existed, ConfigurePayoutPolicyCommand had no HTTP path at all, public
or administrative — the entire batched-payout surface below was unreachable by anyone.
ExecutionSource is the wallet/address that pays for the batch's own broadcast — a genuinely
different concept from a beneficiary's ExecutionDestination (where they receive funds) and
from Mercatto's own execution wallet used for Settlement. Register one per
(Organization, Environment, AssetNetwork) before the first real PayoutBatch, the same bootstrap
category as NetworkCostPayerAccount (see Self-Custody).
Step by step
// 1. Platform Owner switches PayoutPolicy to Manual for this Organization/AssetNetwork.
await fetch(`${baseUrl}/v1/admin/organizations/${organizationId}/payout-policy`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Platform-Owner-Key': platformOwnerApiKey },
body: JSON.stringify({ assetNetworkId, mode: 3 /* Manual */ }),
});
// 2. A NetworkExecutionQuote can be previewed standalone, any time -- a pure read.
const preview = await mercatto.networkExecution.quote(
environmentId, assetNetworkId,
[{ destinationAddress, amount: '1', kind: NetworkOperationKind.TRANSFER, reference: 'preview' }],
NetworkCostPayer.INTEGRATOR,
);
// 3. Settlement accrues -- no SigningRequest, completes synchronously.
const executed = await mercatto.settlements.executeSettlement(transactionId);
const settlement = await mercatto.settlements.get(executed.settlementId);
// settlement.status.name === 'COMPLETED'; settlement.signingRequestId === null
const bobSummary = await mercatto.payout.getPayableSummary(bobAccountId, assetNetworkId);
// bobSummary.accrued > 0; bobSummary.paid === '0' -- nothing has moved yet
// 4. Register the ExecutionSource that will fund the batch's own broadcast.
await mercatto.executionSources.register(organizationId, environmentId, assetNetworkId, walletId, derivationReference, address);
// 5. Create the batch -- Manual trigger, explicit beneficiaries with real accrued Payable.
const created = await mercatto.payout.createBatch(organizationId, environmentId, assetNetworkId, [bobAccountId, mercattoRevenueAccountId]);
// created.payoutBatchId is null only when none of the given owners had positive Payable -- a legitimate no-op.
// 6. Sign every leg with the ExecutionSource wallet's own signer (never a beneficiary's, never
// Mercatto's Settlement wallet), submit, simulate confirmation (Sandbox), wait for Completed --
// the exact same self-custody signing protocol as Settlement/Withdrawal (see Self-Custody).
Once the batch reaches Completed, payout.getPayableSummary() reflects what changed:
accrued returns to 0 for every beneficiary in the batch, and paid grows by exactly what they
were owed — the same Delivered semantics as an Immediate Settlement's payout, just on a
different schedule.
Run it yourself
The full runnable version — same calls, same order, real HTTP, never mocked — is
examples/marketplace-mercatto/scenarios/payout-batch-manual.ts in the
platform repository (TypeScript SDK). It needs one thing
no other scenario in the catalog does: a real Platform Owner API Key
(MERCATTO_PLATFORM_OWNER_API_KEY) to perform step 1 above — see the scenario catalog's own
README.md for the exact setup.