Skip to content

Implement a slippage-protected multi-buy function that atomically purchases keys across multiple creators in a single transaction with all-or-nothing semantics #717

Description

@Chucks1093

Summary

Investors who want to diversify across multiple creators must submit one transaction per creator, leaving them exposed to price movements between each submission. This issue implements a multi_buy function that accepts a vector of (creator, amount, max_price_per_key) tuples, executes all purchases atomically in a single Soroban transaction, and panics with a full rollback if any single leg's price exceeds its max_price — guaranteeing either all legs execute or none do.

Scope

1. Function signature

pub fn multi_buy(
    env: Env,
    buyer: Address,
    legs: Vec<MultiBuyLeg>,  // { creator: Address, amount: u32, max_price: i128 }
    global_deadline_ledger: u32,  // panic if current ledger > deadline
) -> Vec<MultiBuyResult>  // { creator, amount, total_cost, new_supply }

2. Pre-flight validation

  • Panic with legs_empty if the legs vector is empty
  • Panic with too_many_legs if the vector exceeds 10 entries
  • Panic with deadline_passed if env.ledger().sequence() > global_deadline_ledger
  • Panic with duplicate_creator if any creator appears more than once in the legs vector
  • Verify the buyer has sufficient total XLM balance to cover the worst-case cost of all legs (sum of amount * max_price per leg) before executing any purchase

3. Atomic execution

  • Execute legs in the order provided; for each leg call the existing single-buy logic (bonding curve cost, fee split, supply update, holder count update, TTL extension)
  • If any leg's computed cost exceeds its max_price, panic immediately — Soroban's transaction atomicity guarantees all prior legs in the same transaction are rolled back
  • Emit a KeyPurchased event per leg (reuse the existing event format)
  • Emit a single MultiBuyCompleted event at the end with buyer, leg_count, total_cost, and ledger

4. Fee handling

  • Fees for each leg are computed and split independently per leg (protocol treasury and creator recipient may differ per creator)
  • Total XLM deducted from the buyer equals the sum of all per-leg gross costs (including fees)

5. Unit and integration tests

  • Test 3 legs all within max_price — assert all three creators' supplies increment and events emitted
  • Test 3 legs where the second leg's price exceeds max_price — assert all three creators' supplies are unchanged (full rollback)
  • Test duplicate creator in legs — assert panic with duplicate_creator
  • Test legs vector length 11 — assert panic with too_many_legs
  • Test global_deadline_ledger in the past — assert panic with deadline_passed
  • Test buyer balance insufficient for worst-case cost — assert panic with insufficient_funds before any supply is mutated

Acceptance Criteria

  • All legs execute atomically — any failure rolls back all prior legs
  • Slippage protection per leg with max_price
  • MultiBuyCompleted event emitted only when all legs succeed
  • Pre-flight duplicate creator and leg count validation
  • Global deadline prevents stale order execution
  • Buyer balance pre-checked against worst-case total cost

ETA: 24 hours


Coordinate on Telegram

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardThird CampaignCampaign: Third Campaign

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions