SDK ReferenceSchedule Operations

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 vault
  • recipients - array of { address: PublicKey, amount: bigint }
  • intervalSecs - seconds between executions
  • reservedAmount - total amount to reserve
  • perExecutionAmount - amount paid per execution cycle

Returns:

  • signature
  • scheduleId
  • merkleRoot

Interval constraints:

  • minimum: 3600 seconds (1 hour)
  • maximum: 2678400 seconds (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); // resume

Update 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
  • reservedAmount must 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:

  • employer
  • vault
  • status
  • intervalSecs
  • nextExecution
  • reservedAmount
  • perExecutionAmount
  • erJobId
  • merkleRoot
  • totalRecipients
  • paidCount
  • paidBitmap
  • lastExecutedBatch
  • batchStartTime
  • bump

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