Skip to content

feat(launkr): add restricted SIP-010 token launcher + XYK AMM skill - #414

Open
julianariel wants to merge 6 commits into
aibtcdev:mainfrom
rather-labs:feat/launkr
Open

feat(launkr): add restricted SIP-010 token launcher + XYK AMM skill#414
julianariel wants to merge 6 commits into
aibtcdev:mainfrom
rather-labs:feat/launkr

Conversation

@julianariel

Copy link
Copy Markdown

launkr — launch & trade restricted SIP-010 tokens on Launkr

Launkr is a protected token launcher and XYK AMM on Stacks, built by Rather Labs. Each pool trades STX against a restricted SIP-010 token whose transfers are locked to the authorized singleton, so every swap routes through the protocol and captures fees. Tokens launch in one of two pool modes — bonding (virtual reserves, 1% fee, auto-graduates once it crosses its threshold) or direct (real STX seed, 5% fee). Works on mainnet and testnet.

This skill lets an agent (or a user via any LLM client) drive the full lifecycle:

  • launch — deploy a restricted token (byte-identical to the on-chain template) and open its bonding or direct pool.
  • get-pool — read a pool's mode, reserves, graduation progress, and fee receiver.
  • quote-buy / quote-sell — simulate a trade and get expected output net of fees (no wallet needed).
  • swap-buy / swap-sell — trade STX for tokens and back through the singleton, with slippage guards.

SKILL.md documents the subcommands, arguments, protocol floors, and error codes; AGENT.md covers autonomous-operation rules. Files: launkr/{SKILL.md,AGENT.md,launkr.ts}, plus a README.md row and the skills.json manifest entry.

Adds the `launkr` skill (SKILL.md, AGENT.md, launkr.ts) for launching and
trading restricted SIP-010 tokens on the Launkr protected AMM by Rather
Labs, plus the README row and skills.json manifest entry.

Subcommands: launch (deploy token + open a bonding/direct pool), get-pool,
quote-buy, quote-sell, swap-buy, swap-sell. Network follows the shared
NETWORK env var; swaps run in Deny mode with scoped post-conditions (exact
STX on buy, exact `strategy-token` FT on sell); launch validates
mode-specific args and waits for the deploy to confirm before opening the
pool.

Verified end-to-end against live testnet + mainnet pools (reads) and the
Launkr contract source / API (addresses, arg order, fees, floors, error
codes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@arc0btc arc0btc left a comment

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.

Adds launkr — a skill for launching restricted SIP-010 tokens and trading through Launkr's XYK AMM singleton on Stacks. Clean, well-scoped addition that reuses this repo's existing transaction/post-condition/fee helpers rather than reinventing them.

What works well:

  • Both swap-buy and swap-sell run in PostConditionMode.Deny with an exact (eq) post-condition scoping the asset actually being sent (STX in, or the FT via createFungiblePostCondition in the sell case) — that's the safe default for a live wallet, and it's called out explicitly in AGENT.md as intentional (don't switch to Allow).
  • Correct use of the shared helpers: deployContract/callContract signatures, createStxPostCondition/createFungiblePostCondition args, and NETWORK/getApiBaseUrl all match src/lib/* as implemented on main — no drift or reinvented wrappers.
  • AGENT.md is unusually good for a first-time skill submission: it calls out the hash-gate invariant (never edit clarityCode), the two-step confirm-before-pool-create sequencing, and treats none from quote-* as "do not proceed" rather than "assume zero." That's exactly the kind of guidance that prevents an agent from doing something costly on a misread.
  • waitForConfirmation's abort-status list is broad (covers dropped_replace_by_fee, dropped_stale_garbage_collect, etc.), not just abort_by_response — good coverage of the ways a Stacks tx can fail to land.

[suggestion] Cross-check the API's returned pool-creation args against what was requested (launkr.ts:866-874, 903)
poolStep.functionArgs comes straight from https://launkr.io/api/launch and is broadcast via create-pool-* without comparing it back to the launchBody that was sent (e.g. feeReceiver, supply). The on-chain hash gate protects the token contract (byte-identical to the template), but nothing protects the pool-creation call itself — if the API ever returned a different feeReceiver than the one requested (bug, cache issue, compromised endpoint), the caller would silently launch a pool that permanently routes 90% of swap fees to an address they didn't choose, and AGENT.md even flags that risk in its "what NOT to do" section without the code enforcing it. A cheap mitigation: assert the feeReceiver (and maybe supply) arg in poolStep.functionArgs matches opts.feeReceiver/opts.supply before calling deployContract, and abort with a clear error if they don't.

[nit] Minor formatting inconsistency in swap-buy/swap-sell: .option("--recipient <address>", ...) and the following .option("--fee <fee>", ...) are glued onto the same line (launkr.ts:1163-1166, 1229), unlike every other option in the file which gets its own line. Harmless, but a prettier/format pass would catch it.

Code quality notes:

  • unwrapCV/decodeCV are a reasonable, self-contained way to flatten cvToValue's {type,value} tree into plain JS without pulling in another dependency — no simplification needed there.
  • No dead code or unused imports spotted; the three read-only commands (get-pool/quote-buy/quote-sell) intentionally duplicate the wallet-address-fallback pattern rather than sharing a helper — three call sites is right at the "still fine inline" threshold, wouldn't push for extraction yet.

Big-picture fit: Follows the same shape as bitflow/defi (CLI skill, shared src/lib transaction helpers, README.md + skills.json entries), and the network-selection convention (NETWORK env var, no per-command flag) matches how the rest of the repo's skills already work. No conflicts with anything else in the tree.

Nothing here is blocking — the feeReceiver cross-check is worth doing before agents start using this against mainnet, but it doesn't need to hold up the merge.

Cross-checks name, symbol, supply, and fee-receiver from the API's
pool-creation response against what was requested, before spending any
gas. Addresses arc0btc's review comment on aibtcdev#414.
@julianariel

Copy link
Copy Markdown
Author

@arc0btc we've applied the improvements, please review again and approve if it's ok!

@arc0btc arc0btc left a comment

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.

Re-reviewed the new commit (81ee5b4, "validate pool-creation args match request before deploying") — the validatePoolStepMatchesRequest guard is a solid addition, thanks for that.

[blocking] parseLaunkrArg's handling of optional-utf8/optional-ascii changed from noneCV() to someCV(stringUtf8CV("")) when the value is null (launkr.ts lines ~104-112), with a comment describing it as a workaround for a BadFunctionArgument broadcast rejection on bare noneCV().

This directly contradicts AGENT.md's own guidance in this same PR (under "Launching a token", point 3):

The optional uri argument is a genuine Clarity optional... Do not substitute a Some("") or any placeholder string.

If the code change is correct, AGENT.md needs to be updated to match (and the on-chain implication — the token's uri field is now permanently set to "" instead of none for every launch that omits --uri — should be called out explicitly, since that's a real behavioral change to deployed token metadata, not just a broadcast-plumbing detail).

If AGENT.md's guidance is the correct one, the workaround needs a different fix — e.g. confirm whether the BadFunctionArgument is really about noneCV() encoding vs. something else in the request (arg ordering, @stacks/transactions version mismatch as the comment speculates), rather than papering over it by silently changing the on-chain value.

Either way, doc and code should agree before merge — right now an agent reading AGENT.md would expect none and get Some("") instead.

Rest of the commit (network handling via resolveNetwork/NET_CONFIG, cvToValue unwrap simplification, temp-file cleanup) looks fine on read-through.

…havior

The docs that landed in this PR were an earlier draft that never got
synced with the verified launkr.ts implementation, causing two real
contradictions arc0btc caught:

- AGENT.md claimed bare (none) broadcasts fine for the optional uri arg
  and told agents not to use Some("") — the opposite of what's verified
  (noneCV() reliably causes BadFunctionArgument; Some("") is the working
  fix already in launkr.ts). Now explicitly documents the on-chain
  implication: uri ends up Some("") instead of None when omitted.
- AGENT.md/SKILL.md claimed there's no per-command --network flag, but
  every launkr.ts command has one.

Also reconciles skills.json's author/authorAgent metadata into SKILL.md's
frontmatter, and documents the validatePoolStepMatchesRequest guard added
in 81ee5b4.
@julianariel

Copy link
Copy Markdown
Author

@arc0btc thanks for the detailed feedback, we've applied all the comments and fixes, can you please re-review?

@sebastrosen

Copy link
Copy Markdown

@biwasxyz, could you review this PR and the fixes, and merge it if everything looks good?

@biwasxyz biwasxyz left a comment

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.

Thanks for this — the protocol documentation here is unusually good. The error-code table, the protocol floors, the flat-vs-nested API gotcha, and especially the candid write-up of the uri tradeoff are more thorough than most skill PRs get. The restricted-token + singleton + hash-gate design is also genuinely interesting, and "an agent can create a tradeable asset from nothing" is a capability this repo doesn't have yet.

I went through launkr.ts line by line against the shared libs it calls. bun run typecheck passes. A few things need resolving before merge, and I have some questions where I couldn't verify the on-chain behavior myself.


Blockers

1. swap-buy / swap-sell look like they'll abort with abort_by_post_condition

Both use PostConditionMode.Deny but only post-condition the caller's outgoing leg:

  • launkr.ts:746-750 — covers "user sends stxIn uSTX", but not the singleton sending tokens to recipient, nor the fee STX to the fee-receiver.
  • launkr.ts:816-825 — covers the user's FT send, but not the singleton's STX payout.

Under Deny mode every principal that sends an asset needs coverage, including the contract paying out. The convention is visible in this repo's own merged code — bitflow-hodlmm-withdraw/bitflow-hodlmm-withdraw.ts:661-711 builds PCs for the user leg and a Pc.principal(poolContract).willSendGte(minOut) for each payout leg.

The fix is small: add a willSendGte(minTokensOut) FT condition on the singleton in swap-buy, and willSendGte(minStxOut) uSTX on the singleton in swap-sell. You already have both minimums as required options, so they map straight across.

2. --network doesn't control which chain the transaction is broadcast to

resolveNetwork() (launkr.ts:69) selects the singleton address, the Hiro read URL, and the explorer chain= param — but the actual broadcast network comes from account.network inside callContract/deployContract (src/lib/transactions/builder.ts:123 and :162), which reads the global NETWORK env and is untouched by the flag.

NETWORK defaults to testnet (src/lib/config/networks.ts:5). So:

  • --network mainnet on a default install → tx carrying the mainnet singleton SP2ABWV7..., broadcast to testnet.
  • NETWORK=mainnet + --network testnet → the token contract deploys on mainnet with real STX, then pool creation targets a testnet address that doesn't exist there. Money spent, no pool. waitForConfirmation is also polling the wrong Hiro instance, so it hangs for the full 5-minute timeout first.

What makes this urgent rather than cosmetic is that the docs steer agents directly into it — AGENT.md:12-16 and SKILL.md:64-66 both say "pass it explicitly rather than relying on the NETWORK env var default."

Two ways out: either drop --network and derive everything from account.network, or keep the flag and hard-fail when it disagrees with the wallet's network. The second is friendlier but the first is less to get wrong.

A smaller symptom of the same split: getExplorerTxUrl(txid, NETWORK) and chainExplorerUrl (launkr.ts:460-462, :762-763, :837-838) use different sources, so one JSON output can carry two links pointing at different chains.

3. bun run validate fails

FAIL  launkr/AGENT.md
        - name: Invalid input: expected string, received undefined
        - skill: Invalid input: expected string, received undefined
        - description: Invalid input: expected string, received undefined
Results: 201 passed, 1 failed, 202 total

launkr/AGENT.md is missing its YAML frontmatter. See bitflow/AGENT.md:1-5 for the shape — three lines.


Worth addressing

4. The token's Clarity source is deployed unverified. launch takes deployStep.clarityCode from the API and deploys it under the user's key (launkr.ts:402-412) with no local check. There's a slight asymmetry here: validatePoolStepMatchesRequest exists precisely because the API isn't trusted, but the much larger surface — arbitrary contract code signed by the user's wallet — is accepted as-is. Since the singleton already gates on a hash of restricted-token-template-v6, fetching that template and comparing before deploying would close it, and would fail before spending gas rather than after.

5. validatePoolStepMatchesRequest skips the curve parameters. It checks name/symbol/supply/fee-receiver (launkr.ts:147-159) but not virtual-stx or graduation-threshold. stx-seed happens to be covered by the eq STX post-condition; the bonding params aren't covered by anything, and they define the entire price curve.

6. launch is non-atomic with no recovery path. If step 2 fails, or the process is killed during the 5-minute wait, the token is deployed with no pool and there's no way to resume — re-running launch deploys a second contract. A standalone create-pool --token <principal> subcommand would fix it; at minimum the recovery procedure should be documented in AGENT.md.

7. Re-implemented shared infrastructure. CLAUDE.md asks that skills not re-implement network/config logic. This ships its own NET_CONFIG with hardcoded Hiro URLs (launkr.ts:49), its own callReadOnly (:173), and its own tx poller (:219), all bypassing src/lib/services/hiro-api.ts. The practical cost is no API-key header, so waitForConfirmation polling every 6s will hit Hiro rate limits.

8. Dead code at launkr.ts:398-419 — writes clarityCode to tmpdir(), deploys from the in-memory string anyway, discards the result of Bun.file(tmpPath).exists(), then unlinks. The whole block can go.

9. --mode isn't validated locally. A typo reaches the API, and the direct-mode STX post-condition keys off opts.mode === "direct" (launkr.ts:438) — a case mismatch would silently drop the guard.

10. No tests. parseLaunkrArg, decodeCV, and validatePoolStepMatchesRequest are pure and cheap to cover; several sibling modules in src/lib/ ship .test.ts alongside.


Questions

  1. Have swap-buy / swap-sell been run end-to-end through this CLI? The worked examples in SKILL.md:202-216 read like manual/API broadcasts, and the mainnet one covers only deploy + create-pool-bonding. If there's a txid from a swap executed by launkr.ts itself I'd like to look at it, since it would contradict my read of #1.

  2. Does create-pool-bonding move any assets from the deployer? launkr.ts:437-440 passes an empty post-condition array under Deny mode. That's correct only if the template mints the supply directly to the singleton. If the deployer holds the supply first and the singleton pulls it, this has the same problem as #1.

  3. On the noneCV()BadFunctionArgument issue (launkr.ts:82-95) — do you have the rejection payload or a txid? And which @stacks/transactions version were you resolving at the time? I'd like to try reproducing against the version this repo pins, because if it is a dependency bug it likely affects other skills too, and if it isn't, the permanent Some("") on every token's uri is avoidable. Full credit for documenting the tradeoff so plainly either way.

  4. Does graduation change the transfer restriction? As I read it, holders can never move these tokens except by selling back through the singleton — no sending to a friend, no using them in another protocol. Is that permanent, or does a graduated pool unlock transfer? This matters a lot for how AGENT.md should frame the decision to launch, and I don't think it's stated anywhere in the docs right now.

  5. AGENT.md:90 mentions a two-step fee-receiver transfer existing on-chain but the CLI doesn't expose it. Intentional for a first cut, or worth adding? Given the fee-receiver collects 90% of volume permanently, having no way to correct a mistake through this skill seems like a sharp edge.

  6. Is the /api/launch endpoint the only path to a launch? Since the token source is hash-gated to a fixed template, it seems like the CLI could fetch the template from restricted-token-template-v6 on-chain and build the pool-creation args itself, removing the API from the trust path entirely. Is there something in the API response that can't be derived on-chain?


Happy to help with any of these — #1 and #3 in particular are quick, and I'm glad to push a patch if that's easier than another round trip. The core of this is solid work and I'd like to see it land.

@biwasxyz

biwasxyz commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Follow-up to my review — I went and checked the chain rather than leaving my questions #1 and #2 hanging, since @arc0btc's earlier review reached the opposite conclusion on the post-conditions and that's not a useful thing to leave as two maintainers asserting different things at you. One of my findings is confirmed, one is withdrawn, and my suggested fix was incomplete.

Withdrawing question #2 — bonding pool creation is fine as written

create-pool-bonding moves no assets from the deployer, so PostConditionMode.Deny with an empty post-condition array is correct:

mainnet  0x9c9827cac0847e939e792a2bddba46feb5b952618219c839c18acc2e97b40217
         create-pool-bonding   success   deny   0 post-conditions
testnet  0xd24484c958...
         create-pool-bonding   success   deny   0 post-conditions

No change needed at launkr.ts:437-440. Sorry for the noise.

Confirming blocker #1 — and correcting the fix I suggested

The only Launkr swap I can find on either network is 0xb0220091b4bec4ab922865b8aca0919f355eed23a995d45183c4934af9f817c0 (testnet), which I believe is the worked example in SKILL.md:204-208. Its asset movements:

mode = allow,  status = success

  STX   ST3VRYMJ...(user)  ->  ...lp-singleton-v6       1,000,000
  STX   ...lp-singleton-v6 ->  ST3VRYMJ...(user)            9,000   treasury fee 0.9%
  STX   ...lp-singleton-v6 ->  ST2ABWV7...(protocol)        1,000   protocol fee 0.1%
  FT    ...lp-singleton-v6 ->  ST3VRYMJ...(user)  1,976,087,347,052
        ST3VRYMJ...launkr-test-token::strategy-token

Four asset movements. launkr.ts:746-750 post-conditions the first one only, so under Deny the other three are uncovered.

Two things worth drawing out of that trace:

It ran in allow mode. So the end-to-end verification in SKILL.md was done with no post-conditions at all — which means the Deny-mode path in launkr.ts hasn't been exercised. That answers my question #1, and it also means @arc0btc's approval note ("that's the safe default for a live wallet") was reasoning about the code rather than about an executed transaction. No criticism of either of you intended — it's a genuinely easy thing to miss, because the code looks like the careful choice.

My suggested fix was wrong for swap-buy. I said to add a willSendGte(minTokensOut) FT condition on the singleton. That's necessary but not sufficient — the singleton also pays STX out during a buy (both fee legs above), and I hadn't accounted for that. A correct set needs the singleton covered for both asset classes it sends, not just the token:

  • swap-buy — user willSendEq(stxIn).ustx(), singleton willSendGte(minTokensOut).ft(token, "strategy-token"), and a singleton uSTX condition covering the fee legs.
  • swap-sell — user willSendEq(tokensIn).ft(...), plus a singleton uSTX condition covering the payout and fees.

I'd rather not hand you an exact bound for the fee legs from a single observed transaction, since I don't know whether the two fee sends aggregate into one post-condition check or need separate coverage, and that's the kind of thing worth confirming on testnet rather than reasoning about. If you have a testnet wallet handy, broadcasting one Deny-mode swap will settle the exact shape faster than any amount of code review.

Two things this trace does confirm as correct in the PR: the FT asset name really is strategy-token (launkr.ts:47), and the 0.9%/0.1% split matches SKILL.md:70-74 exactly.

Context on my findings #2 and #7

Looking at @arc0btc's first review again — the line references (:866, :1163, :1229) point at a ~1230-line version of launkr.ts, versus 849 today. Both the --network/NET_CONFIG handling and the hand-rolled Hiro client arrived in 81ee5b4, after that approval, and the follow-up review covered them with "looks fine on read-through."

So my #2 and #7 aren't disagreements with that approval — they're on code that came later and hasn't really had a pass yet. Worth saying plainly so it doesn't read as two reviewers contradicting each other at you.

Still open from @arc0btc's blocking review: they offered two routes on the noneCV() rejection — reconcile the docs, or find the root cause. 78299d9 took the first (and did it well — the disclosure in SKILL.md:148-154 is genuinely good). My question #3 is the second half, still unanswered.


Net: blockers #2 and #3 stand as written, #1 stands but needs a broader fix than I first described, and question #2 is withdrawn. Offer to push a patch for the post-conditions still stands if that's easier than another round trip.

1. swap-buy/swap-sell aborted with abort_by_post_condition. Deny mode
   requires every principal that moves an asset to be covered, not just
   the caller — the singleton also pays out STX (fee legs) on a buy and
   STX (proceeds + fees) on a sell, on top of the FT leg. Verified live
   on mainnet: the caller-only post-condition set reliably aborted;
   adding singleton-side coverage (gte 0 for the fee legs, gte the
   slippage minimum for the meaningful leg) fixed both swap-buy and
   swap-sell, confirmed via real broadcasts matching their quotes exactly
   ((ok u59364737346) and (ok u49554)).

2. --network never controlled the actual broadcast destination for
   launch/swap-buy/swap-sell — callContract/deployContract read the
   network from account.network (set by the wallet's own NETWORK env var
   at creation time), completely independent of the flag. Dropped the
   flag from the three write commands and derive network from the
   account directly, so there's one source of truth and no way for the
   display/config network to disagree with the broadcast network.
   get-pool/quote-buy/quote-sell keep --network since they don't sign
   anything.

3. bun run validate failed — launkr/AGENT.md was missing its YAML
   frontmatter. Added the 3-line header matching the rest of the repo's
   skills (verified: bun run validate now reports 202/202, and bun run
   typecheck passes clean).

SKILL.md and AGENT.md updated to document the corrected post-condition
shape and the network-follows-wallet model, with a new dated worked
example for the Deny-mode swap verification.
… repo's pinned deps

Answers biwasxyz's review question aibtcdev#3. The BadFunctionArgument rejection
that motivated the Some("") workaround was reproduced only against the
published @aibtc/mcp-server npm package's own dependency resolution — not
against this repo's pinned @stacks/transactions@7.3.1.

Verified directly: a bare noneCV() for the uri arg on set-token-uri
broadcasts and confirms (ok true) against this repo's exact dependency
version, both via a standalone script and via this repo's own callContract
export (testnet txids 6ee46234adfd545bb55d7396835fa730a4184324ac3ad1bf47b0406305234d8e
and 9403bd6670eea9fb5f6812b937bdcd1604adb2d79da019c66583ae13fe38fbc6).

parseLaunkrArg now sends a proper none again instead of a permanent
empty-string placeholder. Docs updated to record the resolution instead of
describing an ongoing tradeoff. Re-verified: bun run typecheck clean,
bun run validate 202/202.
…tcdev#10 + Q4/Q5/Q6)

- aibtcdev#4: verify deploy clarityCode byte-matches the on-chain template before
  spending gas on it, instead of trusting the API response.
- aibtcdev#5: extend validatePoolStepMatchesRequest to also check virtual-stx/
  graduation-threshold (bonding) or stx-seed (direct), not just
  name/symbol/supply/fee-receiver.
- aibtcdev#6: add a create-pool subcommand as a recovery path for when launch's
  step 2 fails or is interrupted after the token already deployed — builds
  the create-pool-* call directly rather than re-calling /api/launch.
  launch now also prints this recovery instruction after a successful deploy.
- aibtcdev#7: replace the hand-rolled NET_CONFIG.hiroApi/callReadOnly/tx-poller with
  the shared getHiroApi().callReadOnlyFunction and pollTransactionConfirmation
  (src/lib) — picks up the Hiro API key header these lacked before.
- aibtcdev#8: remove the dead temp-file write/read/unlink around deployContract.
- aibtcdev#9: validate --mode locally (must be exactly 'bonding' or 'direct')
  before it can silently skip the direct-mode post-condition guard.
- aibtcdev#10: add launkr.test.ts covering parseLaunkrArg, decodeCV, and
  validatePoolStepMatchesRequest (guarded program.parse with
  import.meta.main, matching the convention already used in
  hodlmm-flow/hodlmm-move-liquidity/stacks-alpha-engine, so importing for
  tests doesn't trigger CLI parsing).
- Q4: confirmed via the singleton's own source that graduating a pool never
  touches the token's allowlist — transfers stay restricted to the
  singleton permanently, documented in both SKILL.md and AGENT.md.
- Q5: exposed the two-step fee-receiver transfer as set-fee-receiver/
  accept-fee-receiver subcommands — previously on-chain but inaccessible
  through this skill.
- Q6: documented the analysis (yes, technically derivable on-chain) without
  unilaterally dropping /api/launch from the deploy step — that's a product
  decision (Launkr's own launch tracking) for the team, not something to
  change inside a skill PR. create-pool already bypasses the API for step 2.

Verified: bun run typecheck clean, bun run validate 202/202, bun test
launkr/launkr.test.ts 18/18 passing. Full bun test shows 8 pre-existing
failures in src/lib/services/x402.service.test.ts unrelated to this change
— reproduced identically with these launkr changes fully reverted, so not
a regression introduced here.
@sebastrosen

Copy link
Copy Markdown

Thanks both for the thorough reviews, this took a few passes to get right, all fixed now across three commits (446e322, fa4a7dc, 43b1ef7).

@biwasxyz's blockers
#1 (post-conditions) — confirmed and fixed. Verified live on mainnet: the caller-only post-condition set reliably aborts with abort_by_post_condition on both swap-buy and swap-sell. Your corrected diagnosis was right — the singleton also needs coverage for the legs it sends. Final set:

swap-buy: caller eq stx-in (uSTX), singleton gte 0 (uSTX, the two fee legs), singleton gte min-tokens-out (FT — the real slippage guard)
swap-sell: caller eq tokens-in (FT), singleton gte min-stx-out (uSTX, covers proceeds + both fee legs in one aggregate check)
Confirmed each broadcasts and matches its quote exactly: (ok u59364737346) and (ok u49554).

#2 (--network) — confirmed by reading builder.ts:123/162 — account.network is what actually gets used, independent of any flag. Dropped --network from launch/swap-buy/swap-sell entirely rather than trying to reconcile it; kept it on the three read-only commands since they don't sign anything.

#3 (validate) — AGENT.md frontmatter added. bun run validate is 202/202 now.

@biwasxyz's worth-addressing + questions
#4 — launch now fetches the on-chain template and byte-compares before deploying.
#5 — validatePoolStepMatchesRequest now also checks virtual-stx/graduation-threshold (bonding) or stx-seed (direct).
#6 — added a create-pool subcommand as the recovery path; builds the call directly rather than re-hitting /api/launch.
#7 — swapped the hand-rolled NET_CONFIG.hiroApi/callReadOnly/poller for getHiroApi()/pollTransactionConfirmation from src/lib — picks up the API key header.
#8 — dead temp-file code removed.
#9 — --mode validated locally now.
#10 — added launkr.test.ts (18 tests: parseLaunkrArg, decodeCV, validatePoolStepMatchesRequest). Guarded program.parse() with import.meta.main so the module is importable for tests — matches the pattern already in hodlmm-flow/hodlmm-move-liquidity/stacks-alpha-engine.
Q3 (the noneCV() root cause) — chased this down properly rather than leaving it open. Installed this repo's exact pinned @stacks/transactions@7.3.1 and broadcast a bare noneCV() both via a standalone script and via this repo's own callContract export — both confirm (ok true) (testnet txids 6ee46234adfd545bb55d7396835fa730a4184324ac3ad1bf47b0406305234d8e, 9403bd6670eea9fb5f6812b937bdcd1604adb2d79da019c66583ae13fe38fbc6). The rejection was specific to the published @aibtc/mcp-server npm package's own dependency resolution, not this repo, not Stacks/Clarity generally. Reverted Some("") back to a proper none — no more permanent empty-string uri.
Q4 (graduation/transfer) — checked the singleton's source directly: graduating only flips mode and the fee tier, never touches the token's allowlist. Transfers stay restricted to the singleton permanently, before and after graduation. Documented in both SKILL.md and AGENT.md now.
Q5 (fee-receiver transfer) — exposed as set-fee-receiver/accept-fee-receiver subcommands.
Q6 (can the API be removed from the trust path) — yes technically, and create-pool already does this for step 2 (builds the call locally instead of re-calling the API). Deliberately didn't extend that to step 1 / drop /api/launch entirely — that's how Launkr's backend currently tracks new launches, separate from the on-chain event indexing the frontend already does. Left as a documented tradeoff in SKILL.md rather than a unilateral call in this PR.
Ran bun run typecheck, bun run validate, and bun test before each push. Full bun test shows 8 pre-existing failures in src/lib/services/x402.service.test.ts — reproduced identically with all these launkr changes reverted, so unrelated to this PR.

Appreciate the depth on both reviews — happy to take another pass if anything's still off

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.

4 participants