Skip to content

[feat]: optional receiver on swap and swap2 - #121

Open
seekdaseek wants to merge 1 commit into
MeteoraAg:mainfrom
seekdaseek:feat/swap-receiver
Open

seekdaseek wants to merge 1 commit into
MeteoraAg:mainfrom
seekdaseek:feat/swap-receiver

Conversation

@seekdaseek

Copy link
Copy Markdown

Summary

swap, swap2 and swap2WithTransferHook derive both token accounts for owner, so a swap can only deliver to the wallet that pays. The program itself has no such limit: the swap accounts take any output_token_account for the output mint, and the first-buy builders already expose this as receiver.

We use it on mainnet. nuntius (a Seeker app for recurring, capped backing of DBC launches) runs one transaction per period: a capped pull from the backer through the Solana Subscriptions program, then a DBC swap whose output goes straight to the backer's own token account, with nuntius's executor signing and holding nothing. Because the SDK cannot express that swap, we build the instruction by hand. Example, 6 Oct 2026: https://explorer.solana.com/tx/383VkA2U1MGPb5rkGSv6AWdq1u9HoG6NDZDcdC5d3p5gEiq1MYb6doqWAicDrRyUpz1Y722mDTUD1Ddb8A5X5Fdf

Changes

  • SwapParams and Swap2Params take an optional receiver?: PublicKey. When set, the output account is the receiver's associated token account, created by payer (or owner) if missing. The input account and the signer stay owner; the receiver does not sign.
  • prepareTokenAccounts takes an optional owner for the token B account. Its other caller is unchanged.
  • SOL unwrapping: when the output is SOL and the receiver is not owner, the receiver keeps wrapped SOL, because closing its account would need its signature. The owner's own wrapped SOL is still closed when SOL is the input.
  • Without receiver (or with receiver equal to owner) the built transaction is the same as before: same accounts, same pre and post instructions.
  • docs.md and an Unreleased CHANGELOG entry. I left the version number to you.

Tests

tests/swapReceiver.test.ts, on the local validator with the repository's fixtures:

  • swap exact-in with a receiver: the receiver's base account holds the output, the owner has no base account, the receiver holds no SOL and only the owner signed.
  • swap2 exact-in, the same.
  • swap2 exact-out: the receiver gets exactly amountOut.
  • swap2 selling base for SOL with a receiver: the owner's base balance drops by the amount in, the receiver's wrapped SOL account holds the output, and the transaction succeeds without the owner having a wrapped SOL account.

Controls:

  • The four new tests fail against main (a28b723): the output goes to the owner.
  • With receiver in place but the old unwrap rule, the sell test fails with InvalidAccountData on the close instruction, because the owner's wrapped SOL account does not exist.

Validation at a28b723 plus this commit, pnpm 11.3.0 and the frozen lockfile (Node 24.16 locally; CI pins 22.13):

  • full suite with the CI validator command: 25 files, 142 tests passed
  • pnpm -w run check-types: passed
  • pnpm -w run lint: passed; the one warning is in unchanged buildCurve.ts
  • pnpm -w run format:check: passed
  • pnpm build: passed

#117 reworks the client and public API. If it lands first, I am glad to rebase this onto it.

The output goes to the receiver's associated token account; the input still comes from the
owner, who still signs. Without receiver the transaction is unchanged.
seekdaseek added a commit to seekdaseek/nuntius that referenced this pull request Oct 6, 2026
…c-bonding-curve-sdk#121)

The receiver gap in both SDKs, checked in DBC main and cp-amm 1.5.1 as shipped, with the two
mainnet buys that use it; the keeper table's "matches"; no event for the migration itself;
web3.js v1 under the SDKs; and the smaller things we hit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant