Schedule Operations
Create and manage payment schedules.
Create Schedule (Simple)
Use createScheduleFromRecipients for automatic Merkle-tree generation.
import { PublicKey } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
const tokenMint = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
const recipients = [
{ address: new PublicKey("recipient1..."), amount: 100_000n },
{ address: new PublicKey("recipient2..."), amount: 200_000n },
{ address: new PublicKey("recipient3..."), amount: 200_000n },
];
const { signature, scheduleId, merkleRoot } =
await client.createScheduleFromRecipients({
tokenMint,
recipients,
intervalSecs: 86400,
reservedAmount: new BN(10_000_000),
perExecutionAmount: new BN(500_000),
});Parameters:
tokenMint- token mint backing the vaultrecipients- array of{ address: PublicKey, amount: bigint }intervalSecs- seconds between executionsreservedAmount- total amount to reserveperExecutionAmount- amount paid per execution cycle
Returns:
signaturescheduleIdmerkleRoot
Interval constraints:
- minimum:
3600seconds (1 hour) - maximum:
2678400seconds (31 days)
Create Schedule (Advanced)
Use createSchedule when you want full control over IDs and Merkle generation.
import { PublicKey } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import { buildMerkleTree, generateScheduleId } from "@veil-dev/sdk";
const tokenMint = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
const scheduleId = generateScheduleId();
const erJobId = generateScheduleId();
const recipients = [
{ address: pubkey1, amount: 100_000n },
{ address: pubkey2, amount: 200_000n },
];
const { root } = buildMerkleTree(recipients);
const signature = await client.createSchedule({
tokenMint,
scheduleId,
intervalSecs: 86400,
reservedAmount: new BN(10_000_000),
perExecutionAmount: new BN(300_000),
merkleRoot: Array.from(root),
totalRecipients: recipients.length,
erJobId,
});Pause Schedule
Pause or resume a schedule.
import { getSchedulePda, getVaultPda } from "@veil-dev/sdk";
const [vaultPda] = getVaultPda(wallet.publicKey, tokenMint);
const [schedulePda] = getSchedulePda(vaultPda, scheduleId);
await client.pauseSchedule(schedulePda, true); // pause
await client.pauseSchedule(schedulePda, false); // resumeUpdate Schedule
Schedules can be edited only while paused. Use this when you need to replace recipients, change cadence, or adjust the reserved amount without cancelling and recreating the schedule.
import { BN } from "@coral-xyz/anchor";
const updatedRecipients = [
{ address: new PublicKey("recipient1..."), amount: 150_000n },
{ address: new PublicKey("recipient2..."), amount: 250_000n },
];
await client.pauseSchedule(schedulePda, true);
const { signature, merkleRoot } = await client.updateScheduleFromRecipients({
schedulePda,
recipients: updatedRecipients,
intervalSecs: 86400,
reservedAmount: new BN(8_000_000),
});
console.log("Schedule updated:", signature);
console.log("New Merkle root:", merkleRoot);
// Re-register the updated recipient payload with the coordinator before resuming.
await client.pauseSchedule(schedulePda, false);updateScheduleFromRecipients rebuilds the Merkle root and derives
perExecutionAmount from the sum of recipient amounts.
For manual control:
await client.updateSchedule(schedulePda, {
intervalSecs: 86400,
reservedAmount: new BN(8_000_000),
perExecutionAmount: new BN(400_000),
merkleRoot: Array.from(root),
totalRecipients: updatedRecipients.length,
});Rules:
- schedule status must be
Paused - no payout batch can be in progress
- recipient amount sum must match the per-execution amount
reservedAmountmust cover at least one full execution cycle- coordinator registration must be updated after editing so off-chain recipient data matches the new Merkle root
Cancel Schedule
Cancelling returns the schedule’s reserved balance to the vault’s available balance.
await client.cancelSchedule(schedulePda);Cancelled schedules cannot be resumed.
Get Schedule State
const schedule = await client.getSchedule(schedulePda);
if (schedule) {
console.log("Status:", schedule.status);
console.log("Next execution:", schedule.nextExecution.toString());
console.log("Reserved:", schedule.reservedAmount.toString());
console.log("Per cycle:", schedule.perExecutionAmount.toString());
console.log("Recipients:", schedule.totalRecipients);
console.log("Last batch:", schedule.lastExecutedBatch.toString());
}ScheduleAccount fields:
employervaultstatusintervalSecsnextExecutionreservedAmountperExecutionAmounterJobIdmerkleRoottotalRecipientspaidCountpaidBitmaplastExecutedBatchbatchStartTimebump
scheduleId is used to derive the PDA but is not stored inside ScheduleAccount.
PDA Helpers
Derive program addresses without making RPC calls.
import { getSchedulePda, getVaultPda } from "@veil-dev/sdk";
const [vaultPda] = getVaultPda(employerPubkey, tokenMint);
const [schedulePda] = getSchedulePda(vaultPda, scheduleId);Complete Flow
const { scheduleId } = await client.createScheduleFromRecipients({
tokenMint,
recipients: [
{ address: recipient1, amount: 100_000n },
{ address: recipient2, amount: 200_000n },
],
intervalSecs: 86400,
reservedAmount: new BN(2_000_000),
perExecutionAmount: new BN(300_000),
});
const [vaultPda] = getVaultPda(wallet.publicKey, tokenMint);
const [schedulePda] = getSchedulePda(vaultPda, scheduleId);
const schedule = await client.getSchedule(schedulePda);
console.log("Status:", schedule?.status);
await client.pauseSchedule(schedulePda, true);
await client.pauseSchedule(schedulePda, false);Dashboard Shortcuts
The dashboard adds a few non-SDK workflow features on top of this flow:
- CSV / Excel recipient imports
- local saved templates per wallet
- coordinator execution-history views