- Let users create recurring buy campaigns (DCA) without sharing private keys or giving up custody.
- Execute swaps via Jupiter across top Solana liquidity venues.
- Keep backend minimal while ensuring reliability via monitoring + retries.
-
Pattern A (Delegate Allowance): user grants a capped SPL-token delegate allowance for the campaign input (USDC/WSOL).
-
Swaps use Jupiter routing.
-
Bought assets are sent to the user’s ATA for the output mint.
-
DCA executions are monitored and retried up to max_retries attempts (configurable).
-
Detect and react to:
- delegate overwritten
- allowance exhausted
- insufficient funds (user moved funds away)
On-chain program
- Stores campaign state + enforces rules (mints, cadence, per-step amount, destination ATA, etc.).
- Validates readiness (due, active, delegate/allowance/funds checks).
- Performs per-step accounting and emits events for analytics.
Automation
- TukTuk task(s) trigger
execute_step(campaign)on schedule (daily). - Each step is idempotent and safe to retry.
Minimal backend
-
Observability + reliability layer:
- monitors due steps, tx outcomes, and campaign health
- retries failed steps up to max_retries
- builds/refreshes Jupiter quote/route if needed
-
Indexes on-chain events + tx memos to power user metrics.
Frontend
- Campaign creation/start/cancel
- Progress + “gamified” stats dashboard (streaks, avg execution price, best route labels, total DCA’d, etc.)
Campaign account
owner(user wallet)input_mint(USDC or WSOL)output_mint(e.g., xavier)funding_token_account(user-owned token account; delegate set to program/campaign PDA)destination_ata(user-owned ATA foroutput_mint)start_ts,cadence_secs(e.g., 86400),steps_total,steps_executedper_step_amount(or derived from total / days)allowance_expected_total(for sanity checks)status:Active | Paused | Cancelled | Completed | Error(code)next_due_tsretry_count_current_step(optional; can be off-chain only)
Events
CampaignStartedStepExecuted(in_amount, out_amount, route info hash/label, ts)StepFailed(error code, ts)CampaignCancelledCampaignPaused(reason: delegate_overwritten / allowance_exhausted / insufficient_funds)
-
User selects:
- input token (USDC/WSOL), total amount, duration (days), output token
- optional settings: slippage, max_retries, “catch-up” behavior
-
Frontend prepares:
- destination ATA =
ATA(user, output_mint)(create if missing; payer = executor/backend) - a dedicated funding token account per campaign (recommended to avoid delegate conflicts)
- destination ATA =
-
create_campaign(...)initializes the campaign state. -
User performs ApproveChecked:
- delegate = campaign/program PDA
- allowance = total campaign amount
-
TukTuk task is registered to call
execute_step(campaign)daily.
- Validates campaign is
Activeandnow >= next_due_ts. - Validates delegate/allowance/funds (see “Detection”).
- Pulls today’s spend from user funding token account via delegate.
- Swaps via Jupiter.
- Sends output to
destination_ata(user ATA). - Updates
steps_executed,next_due_ts, status transitions.
- User clicks “Cancel”.
-
cancel_campaign(campaign):- sets status to
Cancelled - prevents further steps from executing
- sets status to
- Recommended: user also calls Revoke on their funding token account delegate (or sets allowance to 0).
- TukTuk task should be disabled/removed (or it can keep calling and get a clean “Cancelled” failure code).
- Campaign account state: authoritative status + counters + next due time.
- Program events: step-by-step metrics (amounts, timestamps, failures).
- Transaction memos (optional): searchable tags like
dca:v1 campaign=<PDA> step=<n>.
- Progress:
steps_executed / steps_total, next run time, remaining budget (derived) - Execution stats: avg buy price (computed), total acquired, best/worst day, slippage realized
- Reliability: success rate, retry counts, last error reason, current “streak”
- Route stats: most-used route labels (from Jupiter quote metadata or backend logs)
Backend/TukTuk operator logic
-
For each due step:
- submit tx
- if dropped/expired: resubmit (same idempotent call)
- if swap failed: re-quote via Jupiter and retry up to max_retries
-
After max_retries failures for the same step:
- mark campaign
Paused(orError) with reason code - notify user in UI
- mark campaign
Idempotency rule: a step can be executed at most once for a given step_index.
Detect
-
Read funding token account state:
delegate != expected_campaign_pdaORdelegated_amount == 0Response
-
Set campaign
Paused(delegate_overwritten) -
UI prompts user to re-approve delegate allowance.
Detect
delegated_amount < required_step_amount(or < remaining required) Response- Set campaign
Paused(allowance_exhausted) - UI prompts user to top-up allowance (approve additional) or end campaign.
Detect
- funding token account balance < required_step_amount Response
- Set campaign
Paused(insufficient_funds) - UI prompts user to refill funding account.
-
Program enforces:
- fixed
owner, mints, cadence, destination ATA, and max spend per step - no executor can redirect output away from the user ATA
- fixed
-
Users retain custody:
- funds remain in user token account until a due step executes under capped allowance
- user can revoke anytime by removing delegate allowance