Skip to main content

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.