StackSwap liquidity-token-v5k

Factory-deployed LP token template for StackSwap AMM pairs — implements SIP-010 with mint/burn/LP-state management

ContractSP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275.liquidity-token-v5k*
ProtocolStackSwap — DEX on Stacks
SourceVerified on-chain via Hiro API (/v2/contracts/source) — identical template across all liquidity-token-v5k* instances
Clarity Version1 (pre-Clarity 4 — uses as-contract, not as-contract?)
Lines of Code~170
Confidence🟡 MEDIUM — access control depends on external DAO contract (stackswap-dao-v5k) not fully reviewed here
DateFebruary 25, 2026
Related AuditStackSwap stackswap-swap-v5k (swap router)

Note: The requested contract identifier SP1Z92MPDQEWZXW36VX71Q25HKF5K2EPCJ304F275.liquidity-token-v5k04mmev0d does not exist on-chain. StackSwap deploys hundreds of identical LP token contracts (one per trading pair) with random suffixes. This audit covers the shared template code, verified against the deployed instance liquidity-token-v5kpj0x4qap.

0
Critical
1
High
2
Medium
1
Low
3
Informational

Overview

Each StackSwap trading pair gets its own LP token contract deployed from this template. The contract serves dual roles:

  1. SIP-010 fungible token — standard LP token that users receive when providing liquidity
  2. Pair state store — holds pool balances, fee balances, and token references (delegated from the swap router)

All privileged operations (mint, burn, transfer-token, set-lp-data, initialize) are gated behind a DAO lookup: (contract-call? .stackswap-dao-v5k get-qualified-name-by-name "swap"). This means the swap contract's identity is resolved dynamically at call time via the DAO registry rather than hardcoded.

Architecture

The LP token is the data layer for each pair — it stores balance-x, balance-y, shares-total, and fee balances as contract variables. The swap router (stackswap-swap-v5k) calls set-lp-data to update these after every swap/add/remove operation. This is an unusual pattern — most AMMs store pair state in the router itself.

Findings

HIGH

H-01: transfer-token allows arbitrary token drain via trait parameter

Location: transfer-token function

Description: The transfer-token function accepts any SIP-010 token as a trait parameter and transfers it from the LP contract's own balance to an arbitrary recipient. While access is restricted to the authorized swap contract, there is no validation that the token being transferred matches the pair's token-x or token-y.

(define-public (transfer-token (amount uint) (token <sip-010-token>) (to principal))
  (begin
    (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .stackswap-dao-v5k
      get-qualified-name-by-name "swap"))) (err ERR_DAO_ACCESS))
    (unwrap! (as-contract (contract-call? token transfer amount tx-sender to none))
      (err ERR_TOKEN_TRANSFER))
    (ok true)
  )
)

Impact: If any tokens besides token-x/token-y are accidentally sent to the LP contract (e.g., via airdrop or mistaken transfer), the swap contract can drain them. More critically, if the DAO registry is ever compromised to point "swap" at a malicious contract, any SIP-010 token held by the LP contract can be extracted — the lack of token validation widens the blast radius.

Additionally: The as-contract block (pre-Clarity 4) gives blanket asset authority. In Clarity 4, as-contract? with with-ft could restrict transfers to only the pair's specific tokens.

Recommendation: Add validation that (contract-of token) matches either (var-get token-x) or (var-get token-y). On Clarity 4, migrate to as-contract? with explicit with-ft allowances.

MEDIUM

M-01: DAO single-point-of-failure with unwrap-panic

Location: All privileged functions (mint, burn, transfer-token, initialize, initialize-swap, set-lp-data, set-fee-to-address)

Description: Every privileged function calls (unwrap-panic (contract-call? .stackswap-dao-v5k get-qualified-name-by-name "swap")). If the DAO contract is ever upgraded, becomes unreachable, or the "swap" key is removed from its registry, all these functions permanently revert with a panic — no error code, no recovery.

;; This pattern appears 7 times:
(asserts! (is-eq contract-caller
  (unwrap-panic (contract-call? .stackswap-dao-v5k get-qualified-name-by-name "swap")))
  (err ERR_DAO_ACCESS))

Impact: If the DAO lookup fails for any reason, the LP token becomes permanently frozen — no minting, burning, or state updates possible. Existing token holders can still transfer LP tokens but cannot redeem them for underlying assets.

Recommendation: Use unwrap! with a meaningful error code instead of unwrap-panic, allowing callers to handle the failure gracefully. Consider a fallback admin principal for emergency recovery.

MEDIUM

M-02: Pair state stored in LP token creates tight coupling and consistency risk

Location: set-lp-data, get-lp-data, and all balance/share variables

Description: The LP token stores pool state (balance-x, balance-y, shares-total, fee balances) that is conceptually owned by the swap router. The swap router updates this state via set-lp-data after operations. This means:

Impact: If the swap contract has a bug in its balance accounting (see swap audit), the LP token faithfully stores incorrect state. Users querying get-lp-data get misleading information about pool reserves.

Recommendation: This is an architectural concern rather than an exploitable bug. The pattern is intentional for StackSwap's design but increases systemic risk. Ideally, pool state would live in the router or be verified against actual token balances.

LOW

L-01: NULL_PRINCIPAL set to deployer, not a true null

Location: (define-constant NULL_PRINCIPAL tx-sender)

Description: NULL_PRINCIPAL is used as the initial value for token-x and token-y before initialization. However, it's set to tx-sender (the deployer), not an actual burn address or unspendable principal. If initialization is delayed, get-tokens returns the deployer's address as both token principals.

Impact: Low — any external system reading get-tokens before initialize-swap gets misleading data. The is-initialized and is-in-swap guards prevent actual operations on uninitialized pairs.

Recommendation: Use a well-known burn address or add a read-only function that checks initialization status.

INFO

I-01: No max supply on fungible token

Location: (define-fungible-token liquidity-token)

Description: The fungible token is defined without a max supply parameter. Minting is unlimited (subject to DAO access control). This is standard for LP tokens — supply is determined by liquidity deposits — but worth noting that supply has no protocol-level cap.

INFO

I-02: Pre-Clarity 4 — uses as-contract without asset restrictions

Location: transfer-token function

Description: The contract uses as-contract (Clarity 1) which grants blanket authority over all assets the contract holds. Clarity 4's as-contract? with explicit with-ft/with-stx allowances would limit the blast radius. Since this contract was deployed before Clarity 4, this is informational.

Recommendation: Future versions should migrate to Clarity 4 and use as-contract? with explicit asset allowances in transfer-token.

INFO

I-03: Verbose debug print statements in production

Location: mint, burn, initialize, initialize-swap

Description: Multiple print statements emit debug logs in production (e.g., (print "token-liquidity.mint"), (print (some contract-caller))). While these don't affect security, they increase transaction event noise and gas cost slightly.

Positive Patterns

Summary

The StackSwap LP token template is a reasonably well-structured contract that combines SIP-010 token functionality with AMM pair state management. The main concern is H-01: the transfer-token function doesn't validate the token parameter against the pair's actual tokens, widening the blast radius if the DAO registry is compromised. The unwrap-panic pattern (M-01) creates brittleness — a DAO misconfiguration could permanently freeze all LP operations across every pair.

The architecture of storing pair state in LP tokens (M-02) is an intentional design choice that enables upgradeability but increases coupling. This audit should be read alongside the StackSwap swap router audit for the full picture.