Contract: SM1FKXGNZJWSTWDWXQZJNF7B5TV5ZB235JTCXYXKD.dlmm-swap-router-v-1-1
Source reviewed: https://api.hiro.so/v2/contracts/source/SM1FKXGNZJWSTWDWXQZJNF7B5TV5ZB235JTCXYXKD/dlmm-swap-router-v-1-1
Reviewer: TinyOps Studio LLC
Review date: 2026-06-02
This review covers the router contract only, with a narrow supporting check of the hard-coded Bitflow core contract because this router delegates all swap execution to SP1PFR4V08H1RAZXREBGFFQ59WB739XM8VVGTFSEA.dlmm-core-v-1-1.
The router source is 352 lines and defines eight public entrypoints, seven private helpers, and no mutable storage. I reviewed every public entrypoint, fold helper, slippage check, external call, and arithmetic update path.
Disclosure handling: I did not identify any high or critical severity issue. No mandatory private disclosure was triggered before this public submission.
| Line | Item | Purpose |
|---|---|---|
| 5 | dlmm-pool-trait |
User-supplied pool trait used to read active bin data and call pool transfer helpers through core. |
| 6 | sip-010-trait |
User-supplied SIP-010 token traits passed through to core swap calls. |
| Lines | Constant | Purpose |
|---|---|---|
| 9-19 | ERR_* constants |
Router-local error responses for fold result, slippage, minimum amount, active-bin data, empty swap lists, result-list overflow, bin-id bounds, step bounds, and step-index slicing. |
| 22-23 | MIN_BIN_ID, MAX_BIN_ID |
Signed expected-bin-id range: -500 to 500. |
| 26-27 | MIN_STEPS, MAX_STEPS |
Simple-range swap step count bounds: 1 to 319. |
| 30-44 | STEP_INDEX_RANGE |
Static list of indices 0 through 319 used as a bounded fold counter for range swaps. |
The router defines no data-var and no data-map. It does not mutate local state. All durable pool state and token movements occur in the delegated pool/core contracts.
| Function | Lines | Authority | Preconditions / Asserts | State Mutations | External Calls / Transfers |
|---|---|---|---|---|---|
swap-multi |
48-59 | Open public entrypoint. No owner/admin gate. | swaps list non-empty at line 55; cumulative unfavorable bin movement must be <= max-unfavorable-bins at line 56. Each fold leg validates expected bin and per-leg minimum output in fold-swap-multi. |
No router-local state. | Delegates to fold-swap-multi, which reads pool active bin at line 174 and calls core swap-x-for-y / swap-y-for-x at lines 178-179. |
swap-x-for-y-same-multi |
62-77 | Open public entrypoint. | swaps non-empty at line 72; cumulative unfavorable bins <= max-unfavorable-bins at line 73; total y output >= min-y-amount-total at line 74. Each active leg validates expected bin and per-leg min output. |
No router-local state. | Uses fold-swap-x-for-y-same-multi, reads active bin at line 205, calls core swap-x-for-y at line 208. |
swap-y-for-x-same-multi |
80-95 | Open public entrypoint. | swaps non-empty at line 90; cumulative unfavorable bins <= max-unfavorable-bins at line 91; total x output >= min-x-amount-total at line 92. Each active leg validates expected bin and per-leg min output. |
No router-local state. | Uses fold-swap-y-for-x-same-multi, reads active bin at line 243, calls core swap-y-for-x at line 246. |
swap-simple-multi |
98-107 | Open public entrypoint. | swaps list non-empty at line 104. Per-leg max-steps and min-received are enforced by the range helpers. |
No router-local state. | Uses fold-swap-simple-multi, which dispatches to simple range helpers at lines 280-282. |
swap-x-for-y-simple-multi |
110-116 | Open wrapper. | Inherits checks from swap-x-for-y-simple-range-multi; uses MAX_STEPS. |
No router-local state. | Calls swap-x-for-y-simple-range-multi at line 115. |
swap-y-for-x-simple-multi |
119-125 | Open wrapper. | Inherits checks from swap-y-for-x-simple-range-multi; uses MAX_STEPS. |
No router-local state. | Calls swap-y-for-x-simple-range-multi at line 124. |
swap-x-for-y-simple-range-multi |
128-142 | Open public entrypoint. | max-steps must be 1..319 at line 134; STEP_INDEX_RANGE slice must exist at line 135; total y output must be >= min-dy at line 139. |
No router-local state. | Folds fold-swap-x-for-y-simple-multi; each active iteration reads active bin at line 302 and calls core swap-x-for-y at line 303. |
swap-y-for-x-simple-range-multi |
145-159 | Open public entrypoint. | max-steps must be 1..319 at line 151; STEP_INDEX_RANGE slice must exist at line 152; total x output must be >= min-dx at line 156. |
No router-local state. | Folds fold-swap-y-for-x-simple-multi; each active iteration reads active bin at line 332 and calls core swap-y-for-x at line 333. |
| Helper | Lines | Notes |
|---|---|---|
fold-swap-multi |
161-188 | Per-leg expected-bin validation, active-bin read, core swap delegation, per-leg min-received, result append, unfavorable-bin accumulation. |
fold-swap-x-for-y-same-multi |
190-226 | Continues while remaining x input is positive, validates expected bin, delegates x-to-y swap, subtracts consumed input, accumulates output and unfavorable bins. |
fold-swap-y-for-x-same-multi |
228-264 | Same as above for y-to-x. |
fold-swap-simple-multi |
266-287 | Runs up to 5 independent simple-range swap records; each record supplies its own amount. |
fold-swap-x-for-y-simple-multi |
289-317 | Uses STEP_INDEX_RANGE as a loop counter; ignores the bin-id counter argument and reads current active bin each iteration. |
fold-swap-y-for-x-simple-multi |
319-347 | Same as above for y-to-x. |
abs-int |
350-352 | Converts signed bin delta to unsigned unfavorable-bin count. |
The router itself does not call SIP-010 transfer, stx-transfer?, or pool-transfer. Token movement is delegated through the hard-coded core contract. In the supporting core source, swap-x-for-y sets caller to tx-sender and transfers x tokens from caller to the pool, then transfers y tokens from the pool to caller. swap-y-for-x mirrors that direction.
| Router function | Token movement that can occur | Caller post-conditions to attach |
|---|---|---|
swap-multi |
For each swap record, either x token is spent for y token, or y token is spent for x token, depending on x-for-y. The amount spent and received are returned per leg. |
For every token pair in the route, cap the maximum debit of the input token from the caller; require at least the intended output token credit. Include separate post-conditions per distinct token and pool path because the list can contain heterogeneous pools/tokens. |
swap-x-for-y-same-multi |
One x token input budget is consumed across one or more pools; y output accumulates across successful legs. | Cap total x token debit to the amount argument. Require total y token credit at or above min-y-amount-total. |
swap-y-for-x-same-multi |
One y token input budget is consumed across one or more pools; x output accumulates across successful legs. | Cap total y token debit to the amount argument. Require total x token credit at or above min-x-amount-total. |
swap-simple-multi |
Up to five independent simple swaps. Each record supplies its own token traits, amount, direction, min received, and max steps. | Attach per-record input debit caps and output minimums. Do not assume record N uses output from record N-1 unless the caller intentionally funds each record that way. |
swap-x-for-y-simple-multi |
Single pool x-to-y swap across up to MAX_STEPS active-bin iterations. |
Cap x debit to x-amount; require y credit at or above min-dy. |
swap-y-for-x-simple-multi |
Single pool y-to-x swap across up to MAX_STEPS active-bin iterations. |
Cap y debit to y-amount; require x credit at or above min-dx. |
swap-x-for-y-simple-range-multi |
Single pool x-to-y swap across up to caller-selected max-steps. |
Cap x debit to x-amount; require y credit at or above min-dy; set max-steps low enough for the caller's intended price path. |
swap-y-for-x-simple-range-multi |
Single pool y-to-x swap across up to caller-selected max-steps. |
Cap y debit to y-amount; require x credit at or above min-dx; set max-steps low enough for the caller's intended price path. |
| Surface | Authority Model | Notes |
|---|---|---|
| Router public entrypoints | Fully open. | This is appropriate for a stateless router. There are no owner ops, pause switches, admin lists, or local privileged principals in the router. |
| Core delegation | Hard-coded to SP1PFR4V08H1RAZXREBGFFQ59WB739XM8VVGTFSEA.dlmm-core-v-1-1. |
The router does not accept an arbitrary core principal, which avoids caller-selected execution cores. |
| Pool and token traits | Caller supplied, but passed to the hard-coded core. | The router does not validate the trait principals locally; core validation is the critical defense. In the supporting core source, token traits and managed-pool status are asserted before transfer. |
| Oracle dependencies | None directly in this router. | Active-bin data and pricing are read from pool/core calls. |
| Pause / kill switches | None directly in this router. | Any pool-disable or core-management behavior is external to this router. |
| Check | Result |
|---|---|
tx-sender vs contract-caller in router |
No tx-sender or contract-caller usage in the router. The core uses tx-sender as the token-transfer caller; callers should use post-conditions accordingly. |
unwrap-panic / unwrap-err-panic |
Not used in the router. Router uses unwrap! with explicit errors. |
| Arithmetic overflow / underflow | No high-risk arithmetic found in router-local logic. Some defensive checks could make core-return assumptions explicit before subtracting consumed input, described in BF-RTR-004. |
as-contract / principal escalation |
Not used. |
| Trait conformance | Uses DLMM pool and SIP-010 traits. The router depends on core validation that the supplied token traits match the pool's tokens and that the pool is managed by the expected core. |
| State mutation | No local state mutation. |
| Result-list bounds | Uses as-max-len? on appended result lists at lines 180, 210, 248, and 283. |
| ID | Severity | Function | Line | Finding | Recommended fix |
|---|---|---|---|---|---|
| BF-RTR-001 | Low | swap-simple-multi / fold-swap-simple-multi |
98, 266-285 | swap-simple-multi may read like a chained multi-pool route, but the helper executes up to five independent swap records and each record supplies its own amount. Output from one record is not fed into the next record. A caller or integration that assumes automatic hop chaining could overfund later legs or build weaker post-conditions. |
Document this function as an independent batch of simple swaps, or add a separate chained-route helper whose next input is derived from prior output. Wallet/UI integrations should label it as batch execution, not automatic path chaining. |
| BF-RTR-002 | Low | swap-x-for-y-simple-range-multi, swap-y-for-x-simple-range-multi |
128-157, 289-347 | The simple-range variants do not accept expected-bin-id or max-unfavorable-bins; they rely on max-steps plus minimum output. That is safe when callers set tight min-dy / min-dx, but it removes the explicit active-bin drift guard present in swap-multi and same-pair multi functions. |
Either document that simple-range callers must use tight min-output and post-conditions, or add optional expected-bin / unfavorable-bin bounds for parity with the other multi-swap APIs. |
| BF-RTR-003 | Informational | Core delegation from router folds | 178-179, 208, 246, 303, 333 | The router accepts caller-supplied pool and token traits and does not prevalidate token-pool matching before calling the hard-coded core. This is acceptable only because the core validates the supplied token traits and managed-pool status before transfer. | Keep the hard-coded core principal. Document this trust boundary. If future versions support pluggable cores, require equivalent token/pool validation before any transfer path. |
| BF-RTR-004 | Informational | Same-pair and simple fold helpers | 211, 249, 304, 334 | The router subtracts (get in swap-result) from the remaining input without a router-local assertion that the core consumed no more than the remaining amount. With the current hard-coded core this should be an invariant and any impossible value would revert on underflow, but the router would emit a generic arithmetic failure rather than a domain-specific error. |
Add explicit asserts! (<= (get in swap-result) remaining-amount) checks before subtraction for clearer failure semantics and defense in depth. |
- Low:
swap-simple-multiis batch-style, not a chained multi-hop route; documentation/UI should make that explicit. - Low: simple-range swaps do not expose the active-bin drift guard used by the expected-bin APIs; callers must rely on tight min-output and post-conditions.
- Informational: router safety depends on the hard-coded core validating caller-supplied pool/token traits before transfers; keep that trust boundary documented.