Skip to main content
This guide walks through building a payroll batch that pays N recipients on N chains, each with their preferred asset, in one signed submission.

Prerequisites

  • Tachyon SDK installed (sdk/installation)
  • A funding wallet with USDC on a supported source chain
  • A list of recipients with { address, chain, asset, amount }

1. Build the batch

import { Tachyon } from "@tachyon/sdk";

const tachyon = new Tachyon({ network: "testnet" });

const intents = await Promise.all(
  contributors.map((c) =>
    tachyon.intent.build({
      sourceChain: "arbitrum",
      destChain: c.chain,
      tokenIn: USDC_ARB,
      tokenOut: c.asset,
      amountIn: c.amount,
      minAmountOut: applySlippage(c.amount, 0.01),
      deadline: Math.floor(Date.now() / 1000) + 3600,
      recipient: c.address,
      metadata: { payrollPeriod: "2026-04", contributorId: c.id },
    })
  )
);
metadata is integrator-defined and never published on-chain. Use it to correlate intents with your internal records.

2. Sign once

const signedBatch = await wallet.signBatch(intents);
A single signature authorizes the entire batch. Tachyon validates each intent in the batch independently.

3. Submit

const { intentIds } = await tachyon.intent.submitBatch(signedBatch);
You receive an intentId for each intent in the batch, in the same order.
Today, this loops createIntent calls for each contributor. An atomic batch-submission path is planned so the whole batch lands in one on-chain transaction.

4. Track the batch

const results = new Map(intentIds.map((id) => [id, "pending"]));

intentIds.forEach((id) => {
  tachyon.intent.subscribe(id, (intent) => {
    results.set(id, intent.status);
    updateDashboard(results);
  });
});
Each intent settles independently. Some recipients may receive their payment seconds before others, payouts are not coordinated as an all-or-nothing batch.

5. Notify recipients (optional)

For better UX, send each recipient a settlement reference once their intent settles:
tachyon.intent.subscribe(id, async (intent) => {
  if (intent.status === "completed") {
    await notify(contributor.email, {
      settleTxHash: intent.settleTxHash,
      solveTxHash:  intent.solveTxHash,
      destChain:    contributor.chain,
      intentId:     id,
    });
  }
});
The recipient can verify the payment using their viewing key, no on-chain trace links it to your organization.

Recurring schedules

There is no on-chain “stream” object in Confidential Payroll. Schedule the batch from your own backend (cron, scheduler, etc.) on whatever cadence you need. Each cycle is an independent batch, which keeps confidentiality maximal because cycle N reveals nothing about cycle N+1.

Compliance and audit access

You can grant a finance team or auditor scoped viewing access to the entire batch (or to selected intents) at submission time. See viewing permissions.

Try it on testnet

Walk through a smaller version end-to-end.