Milestone Payments¶
Milestone payments enable project-based compensation where payments are released as work deliverables are completed. Unlike subscriptions with fixed recurring amounts, milestones allow for variable payment amounts tied to specific project phases or deliverables.
Overview¶
Milestone payments are ideal for:
- Freelance projects with multiple deliverables
- Software development contracts with phased delivery
- Consulting engagements with milestone-based progress
- Content creation with episode/release-based payments
- Any project where payment should follow completion, not time
Key Features¶
- Up to 4 milestones per payment policy
- Flexible release conditions: time-based, manual approval, or automatic
- Variable amounts per milestone
- Absolute timestamps for predictable scheduling
- Progress tracking with current milestone status
- Escrow mechanism ensuring funds are available when due
Creating Milestone Payments¶
Basic Example¶
import { Tributary } from "@tributary-so/sdk";
import { BN } from "@coral-xyz/anchor";
// Initialize SDK
const sdk = new Tributary(connection, wallet);
// Define project milestones
const milestoneAmounts = [
new BN(100000000), // $100 - Project planning & design
new BN(200000000), // $200 - Core development
new BN(150000000), // $150 - Testing & deployment
new BN(50000000), // $50 - Post-launch support
];
const currentTime = Math.floor(Date.now() / 1000);
const milestoneTimestamps = [
new BN(currentTime + 86400 * 7), // 1 week: Planning complete
new BN(currentTime + 86400 * 21), // 3 weeks: Development complete
new BN(currentTime + 86400 * 35), // 5 weeks: Testing complete
new BN(currentTime + 86400 * 49), // 7 weeks: Support period complete
];
// Create milestone payment policy
const instructions = await sdk.createMilestonePaymentPolicy(
tokenMint, // Token to pay with (e.g., USDC)
recipient, // Who receives the payments
gateway, // Payment gateway for processing
milestoneAmounts, // Array of payment amounts
milestoneTimestamps, // When each milestone is payable
0, // Release condition: 0=time-based
[] // Memo (empty for this example)
);
// Execute the transaction
const tx = new Transaction().add(...instructions);
const signature = await provider.sendAndConfirm(tx);
Release Conditions¶
Milestone payments use a bitmap to configure release conditions. This allows flexible combinations of time-based and authorization requirements.
Bitmap Format¶
The release_condition field is an 8-bit value where each bit represents a different requirement:
| Bit | Value | Constant | Description |
|---|---|---|---|
| 0 | 1 | RELEASE_DUE_DATE |
Due date must be reached before release |
| 1 | 2 | RELEASE_GATEWAY |
Gateway authority must sign |
| 2 | 4 | RELEASE_OWNER |
Policy owner must sign |
| 3 | 8 | RELEASE_RECIPIENT |
Recipient must sign |
Important: Bits 1-3 are mutually exclusive - at most one of these signer requirements can be set. A value of 0 means no restrictions.
Common Combinations¶
| Value | Binary | Due Date | Signer | Behavior |
|---|---|---|---|---|
| 0 | 0b0000 | ❌ | None | Anyone can trigger anytime |
| 1 | 0b0001 | ✅ | None | Anyone can trigger after due date |
| 2 | 0b0010 | ❌ | Gateway | Gateway authority must sign |
| 3 | 0b0011 | ✅ | Gateway | Gateway authority + due date |
| 4 | 0b0100 | ❌ | Owner | Policy owner must sign |
| 5 | 0b0101 | ✅ | Owner | Policy owner + due date |
| 8 | 0b1000 | ❌ | Recipient | Recipient must sign |
| 9 | 0b1001 | ✅ | Recipient | Recipient + due date |
How It Works¶
1. Policy Creation¶
- User creates milestone payment policy with amounts and timestamps
- Total escrow amount is calculated and delegated to the protocol
- Funds remain in user's wallet but are approved for spending
2. Milestone Execution¶
- When a milestone timestamp is reached (or manually approved)
- Protocol transfers the milestone amount to the recipient
- Progress advances to the next milestone
- Process repeats until all milestones are complete
3. Completion¶
- After the final milestone is paid, the policy is automatically paused
- Any remaining escrow can be reclaimed by adjusting approvals
Smart Contract Details¶
PolicyType::Milestone Structure¶
Milestone {
milestone_amounts: [u64; 4], // Amount for each milestone
milestone_timestamps: [i64; 4], // Absolute timestamps
current_milestone: u8, // Next milestone to process (0-3)
release_condition: u8, // Bitmap: bit0=check due date, bits1-3=signer
total_milestones: u8, // How many milestones (1-4)
escrow_amount: u64, // Total amount escrowed
padding: [u8; 53], // 128-byte alignment
}
Bitmap Constants¶
pub const RELEASE_DUE_DATE: u8 = 0b0001; // Bit 0: Check due date before release
pub const RELEASE_GATEWAY: u8 = 0b0010; // Bit 1: Gateway authority must sign
pub const RELEASE_OWNER: u8 = 0b0100; // Bit 2: Policy owner must sign
pub const RELEASE_RECIPIENT: u8 = 0b1000; // Bit 3: Recipient must sign
Execution Logic¶
Milestone payments follow this execution flow:
- Validate timing/approval based on release condition bitmap
- Calculate payment amount from current milestone
- Transfer funds from user to recipient
- Update progress (increment current_milestone)
- Check completion (pause if all milestones done)
Best Practices¶
For Project Owners (Recipients)¶
- Set realistic timelines - Allow buffer time for unexpected delays
- Use clear milestone definitions - Make completion criteria unambiguous
- Consider manual approval for quality assurance on critical deliverables
- Communicate progress to keep payers informed
For Clients (Payers)¶
- Review milestone definitions carefully before approving
- Monitor progress and communicate any concerns early
- Understand escrow amounts - total funds needed upfront
- Plan for tax implications of milestone-based payments
For Developers¶
- Use descriptive memos to identify specific milestones
- Test with small amounts on devnet first
- Handle timezone considerations for timestamp accuracy
- Monitor policy status to track payment progress
Example Use Cases¶
Freelance Web Development¶
// 4-week web development project
const milestones = [
new BN(50000000), // Week 1: Design & planning ($50)
new BN(100000000), // Week 2: Frontend development ($100)
new BN(100000000), // Week 3: Backend integration ($100)
new BN(50000000), // Week 4: Testing & deployment ($50)
];
const timestamps = [
new BN(startTime + 86400 * 7), // End of week 1
new BN(startTime + 86400 * 14), // End of week 2
new BN(startTime + 86400 * 21), // End of week 3
new BN(startTime + 86400 * 28), // End of week 4
];
Content Creation Series¶
// 10-episode podcast series
const episodePayments = Array(10).fill(new BN(10000000)); // $10 per episode
const episodeTimestamps = Array.from(
{ length: 10 },
(_, i) => new BN(startTime + 86400 * 7 * (i + 1)) // Weekly releases
);
Consulting Retainer¶
// Monthly consulting with deliverables
const monthlyMilestones = [
new BN(500000000), // Month 1: Strategy & planning ($500)
new BN(400000000), // Month 2: Implementation ($400)
new BN(300000000), // Month 3: Optimization ($300)
];
const monthlyTimestamps = [
new BN(startTime + 86400 * 30), // End of month 1
new BN(startTime + 86400 * 60), // End of month 2
new BN(startTime + 86400 * 90), // End of month 3
];
Integration with Existing Systems¶
Milestone payments integrate seamlessly with Tributary's existing infrastructure:
- Same approval mechanism as subscriptions
- Compatible with all gateways and fee structures
- Uses existing PDA structure for accounts
- Supports all token types via SPL integration
- Query APIs work identically for milestone policies
Monitoring & Management¶
Querying Milestone Status¶
// Get milestone policy details
const policy = await sdk.getPaymentPolicy(policyPda);
console.log("Current milestone:", policy.policyType.milestone.currentMilestone);
console.log("Total milestones:", policy.policyType.milestone.totalMilestones);
console.log("Escrow amount:", policy.policyType.milestone.escrowAmount);
// Check if next milestone is due
const nextMilestoneTime =
policy.policyType.milestone.milestoneTimestamps[
policy.policyType.milestone.currentMilestone
];
const isDue = Date.now() / 1000 >= nextMilestoneTime.toNumber();
Manual Milestone Execution¶
For manual approval milestones, execute payments as needed:
// Execute next milestone manually
const executeInstructions = await sdk.executePayment(
policyPda,
recipient,
tokenMint,
gateway
);
const tx = new Transaction().add(...executeInstructions);
await provider.sendAndConfirm(tx);
Milestone payments provide a powerful, flexible alternative to traditional subscriptions, enabling trust-minimized project-based compensation on Solana.