Skip to content

feat(buy-sol) PR 3: let native SOL buys into the auction - #4989

Merged
squadgazzz merged 22 commits into
mainfrom
solana-autopilot/be-331-let-native-sol-buys-into-the-auction
Sep 30, 2026
Merged

squadgazzz merged 22 commits into
mainfrom
solana-autopilot/be-331-let-native-sol-buys-into-the-auction

Conversation

@squadgazzz

@squadgazzz squadgazzz commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Description

A native SOL buy puts the System Program ID where the buy mint goes, and its buy account is a plain wallet. The autopilot looks for a token account of the buy mint there, so it drops every native buy. It also has no price for them. #4986 lets the driver settle these orders, so this PR lets on-chain ones into the auction. Sponsored native buys still need the orderbook side in BE-332.

A few have to stay out, since a failed payout reverts the whole batch. The rent-exempt minimum for an account with no data is 890,880 lamports, and a smaller payout into an empty wallet fails. A small partial fill can hit the same limit, so partially fillable native buys stay out for now. A wSOL sell for native SOL would reach solvers as wSOL for wSOL, and EVM rejects the WETH to ETH case the same way.

Winner selection doesn't let two winners settle the same token pair, and on EVM it counts ETH as WETH for this check. Solana had no such mapping. Staging runs with 20 winners, so a native buy and a wSOL buy could both win and settle the same pair at different prices.

Merges after #4986. Stacked on #4982.

Changes

  • Native SOL prices at the denominator, like wSOL
  • A native buy passes the buy account check when its wallet is missing or owned by the System Program. Paying a program or a sysvar reverts the settlement, and a program-owned account would strand the lamports. Pending sponsored native buys get the check too, since their creation transaction creates no buy account
  • Native buys under the rent minimum, partially fillable native buys and wSOL sells are dropped from the cut, even when the account lookup fails. A new unpayable_native_buys counter tracks them
  • canonical_token maps native SOL to wSOL for the winner pair check

How to test

New unit tests.

Related issues

BE-331

@linear-code

linear-code Bot commented Sep 28, 2026

Copy link
Copy Markdown

BE-331

@squadgazzz squadgazzz changed the title solana-autopilot: let native SOL buys into the auction feat(buy-sol) PR 3: let native SOL buys into the auction Sep 29, 2026
@squadgazzz
squadgazzz marked this pull request as ready for review September 29, 2026 11:06
@squadgazzz
squadgazzz requested a review from a team as a code owner September 29, 2026 11:06
@claude

claude Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @squadgazzz's task in 3m 18s —— View job


Review

  • Read PR diff and changed files
  • Analyze correctness & edge cases
  • Post inline comments
  • Post summary

I reviewed the diff against origin/tiago/be-305-detect-missing-buy-token-accounts-for-solvers. This is a clean, well-documented, well-tested change and I found no blocking issues.

What I verified:

  • payable_orders runs before receivable_orders (provider.rs:173), so the rent-minimum / partial-fill / wSOL-sell exclusions hold even when the RPC account lookup fails — matching the PR description's guarantee.
  • The rent floor Rent::default().minimum_balance(0) = 890_880 lamports; the buy_amount >= min_payout check is safe for fill-or-kill orders since executed buy ≥ buy_amount, and partial fills are excluded outright. Boundary (890_880 kept, 890_879 dropped) is covered by tests.
  • The guard-match arm found if order.buys_native_sol() => found.is_none_or(receivable_wallet) correctly binds the whole Option<&Account>, falls through to the SPL arms for non-native orders, and treats a missing wallet as receivable — consistent with the "missing or System-Program-owned" rule.
  • canonical_token mapping of NATIVE_SOL → wSOL closes the winner-selection gap so a native buy and a wSOL buy can't both win the same pair.
  • is_sol prices both wSOL and native SOL at the denominator and keeps native SOL out of the maintained/refreshed set.

Minor observations (non-blocking, no change required):

  • payable_orders uses the hardcoded native_mint::ID for the wSOL-sell check while prices.rs uses the configured wrapped_native. On Solana these are always the same well-known mint, so this is fine — just noting the two sources.
  • Dropped native buys are tracked via the unpayable_native_buys counter + debug log but not via an OrderEventLabel::Filtered event. This mirrors the existing unreceivable_orders behavior, so it's consistent; worth keeping in mind if per-order "why wasn't my native buy solved" traceability becomes a support need.
    · solana-autopilot/be-331-let-native-sol-buys-into-the-auction

squadgazzz and others added 9 commits September 29, 2026 12:02
Every order the branch would keep costs the winning solver's keypair the
rent for an account its owner can close right after the fill, and no
engine prices that rent in yet. The expression stays next to the TODO so
turning it on is one edit.
"Only the owner's ATA can be created" is a domain rule, but it sat in the
blockchain adapter, which had to pull in `domain::Order` to apply it. The
adapter is back to handing out classified account states only.
Reinstates the filter 47b4494 removed. The autopilot's cut drops these
orders too, but it fails open when its own lookup fails, and then one
such order takes down the whole settlement it lands in.
The flag is a per-solve annotation, so carrying it on the domain order
forced every constructor and test fixture to set it. The resolution now
returns the uids and the DTO builder looks them up.
Every solver engine this driver hosts receives the same auction, so each
of them paid for its own getMultipleAccounts round trip in front of the
engine call. One shared slot now serves them all, and the engines that
arrive while the lookup is in flight wait for its result.
The paragraph on ResolvedSettlement was the only place describing the
whole instruction order; it comes back with the buy ATAs among the setup
accounts. The DTO TODOs now point at setupCostLamports, which keeps the
rent math out of the engines, and the openapi says what happens to an
order whose destination cannot be created.
This is the one place the solver keypair pays rent for someone else, and
the owner can close the account for the lamports right after the fill, so
the cost needs to be countable.
…token-accounts-for-solvers' into solana-autopilot/be-331-let-native-sol-buys-into-the-auction

# Conflicts:
#	crates/autopilot-svm/src/infra/provider.rs
Comment on lines +297 to +304
fn payable_orders(orders: Vec<Order>) -> Vec<Order> {
// TODO: use the cluster's rent, refreshed periodically. The SDK default is
// above it since SIMD-0437, so this floor also drops small payouts that
// would settle.
let min_payout = Rent::default().minimum_balance(0);
orders
.into_iter()
.filter(|order| {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since this fn owns the vec, I sussegt using Vec::retain instead of filter + collect(), which creates a second vector.

Base automatically changed from tiago/be-305-detect-missing-buy-token-accounts-for-solvers to main September 30, 2026 15:50
@squadgazzz
squadgazzz added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 6202c1e Sep 30, 2026
23 checks passed
@squadgazzz
squadgazzz deleted the solana-autopilot/be-331-let-native-sol-buys-into-the-auction branch September 30, 2026 18:11
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 30, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants