Velar univ2-path2

Multi-hop swap router for the Velar DEX โ€” supports 2-to-5 token paths via Uniswap V2-style AMM pools

ContractSP1Y5YSTAHZ88XYK1VPDH24GY0HPX5J4JECTMY4A1.univ2-path2
ProtocolVelar โ€” Uniswap V2-style DEX on Stacks
SourceVerified on-chain via Hiro API (/v2/contracts/source)
Clarity VersionPre-Clarity 4 (publish height 153315)
Lines of Code~150
Confidence๐ŸŸข HIGH โ€” simple routing contract, all logic visible; delegates actual swaps to univ2-router
DateFebruary 25, 2026
0
Critical
0
High
1
Medium
2
Low
3
Info

Overview

univ2-path2 is a multi-hop swap routing contract for the Velar DEX. It composes single-hop swaps through univ2-router.swap-exact-tokens-for-tokens to support paths of 2 to 5 tokens. The contract includes its own copy of the constant-product AMM formula (get-amount-out) for computing expected outputs and providing read-only quote functions.

Architecture

Trust Assumptions

Findings

MEDIUM

M-01: Zero output amount not validated โ€” dust swaps silently lose funds

Location: swap-args โ†’ get-amount-out

Description: The get-amount-out function uses integer division, which truncates to zero for very small inputs relative to reserves. When this happens, swap-args sets amt-out-min: u0, and the swap proceeds โ€” the user spends their input tokens and receives nothing in return.

;; get-amount-out can return 0 for dust amounts:
;; (/ (* amt-in-adjusted reserve-out)
;;    (+ reserve-in amt-in-adjusted))
;; โ†’ 0 when amt-in-adjusted * reserve-out < reserve-in + amt-in-adjusted

;; swap-args only validates input, not output:
(asserts! (and (> amt-in u0)) err-preconditions)
;; Missing: (asserts! (> amt-out u0) err-postconditions)

Impact: Users sending very small amounts (dust) will lose their tokens with zero output. While unlikely for intentional swaps, this can occur in multi-hop paths where intermediate outputs shrink through compounding rounding losses. Bots or contracts composing with this router could trigger this inadvertently.

Recommendation: Add output validation in swap-args:

(asserts! (> amt-out u0) err-postconditions)
LOW

L-01: unwrap-panic in swap-args produces unhelpful errors

Location: swap-args function โ€” two unwrap-panic calls

Description: swap-args uses unwrap-panic for both lookup-pool and get-pool-id results. If a pool doesn't exist for the given token pair, the transaction aborts with a runtime panic rather than returning a descriptive error code.

(let ((res (unwrap-panic
             (contract-call? .univ2-core lookup-pool
              (contract-of token-in) (contract-of token-out))))
      (id  (unwrap-panic
             (contract-call? .univ2-core get-pool-id ...))))

Impact: Users and integrators see an opaque runtime error instead of a meaningful error code when attempting to swap through a non-existent pool. This complicates debugging and error handling for downstream contracts.

Recommendation: Replace unwrap-panic with unwrap! and a descriptive error constant (e.g., (err u2003) for "pool not found").

LOW

L-02: Unused ids parameter in get-amount-out-4

Location: get-amount-out-4

Description: The function accepts an (ids (list 4 uint)) parameter that is never referenced in the function body. The other quote functions (get-amount-out-3, get-amount-out-5) do not have this parameter, suggesting it was left in by mistake.

(define-read-only
    (get-amount-out-4
     (amt-in   uint)
     (token-a <ft-trait>)
     (token-b <ft-trait>)
     (token-c <ft-trait>)
     (token-d <ft-trait>)
     (ids     (list 4 uint)))  ;; <-- never used
    ...)

Impact: Callers must supply a meaningless parameter. Since the contract is deployed and immutable, this cannot be changed, but integrators should be aware they can pass any list value.

Recommendation: If redeploying, remove the unused parameter. Document for integrators that ids is ignored.

INFO

I-01: Pre-Clarity 4 contract

Description: Deployed before Clarity 4. This contract doesn't directly hold or transfer assets (it delegates to univ2-router), so the lack of as-contract? is not a direct risk here. However, it cannot use contract-hash? for on-chain verification.

Recommendation: If the router is ever redeployed, use Clarity 4.

INFO

I-02: No transaction deadline mechanism

Description: Unlike Uniswap V2's router (which accepts a deadline parameter), this contract has no mechanism to expire stale transactions. A signed transaction could sit in the mempool and execute much later when market conditions have changed unfavorably.

Impact: Users relying on the amt-out-min parameter are still protected against slippage, but may receive a worse-than-expected price if market conditions shift between signing and execution.

Recommendation: Add an optional block-height deadline parameter: (asserts! (<= block-height deadline) (err u2004))

INFO

I-03: Per-hop slippage uses exact calculated output (zero tolerance)

Description: do-swap computes the expected output via get-amount-out and passes it as amt-out-min to the router โ€” zero slippage tolerance per hop. This works if the router's internal formula is identical. Any discrepancy (rounding, fee calculation differences) would cause every swap to fail.

Impact: If univ2-router uses even a slightly different formula or rounding strategy than the local get-amount-out, all swaps through this contract would revert. In practice, the formula appears to be consistent (the router likely uses the same constant-product formula), but this is a fragile coupling.

Recommendation: This is by design and works as long as the formulas match. Document the dependency for future maintainers.

Positive Observations

Related Contracts