Source Code

;; title: swap-router
;; version: 1.0.0
;; summary: Multi-DEX swap router
;; description: Route swaps across DEXes for best price - Clarity 4

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NO-ROUTE (err u6200))
(define-constant ERR-SLIPPAGE (err u6201))

;; DEX identifiers
(define-constant DEX-ALEX u1)
(define-constant DEX-STACKSWAP u2)
(define-constant DEX-VELAR u3)
(define-constant DEX-ARKADIKO u4)

;; Data Variables
(define-data-var total-routed-swaps uint u0)
(define-data-var total-savings uint u0)

;; Data Maps - Using stacks-block-time for Clarity 4
(define-map routed-swaps uint {
  user: principal,
  token-in: principal,
  token-out: principal,
  amount-in: uint,
  amount-out: uint,
  route: (list 5 uint),  ;; List of DEX IDs used
  gas-saved: uint,
  timestamp: uint  ;; Clarity 4: Unix timestamp
})

(define-map dex-prices { dex: uint, pair: (string-ascii 40) } {
  price: uint,
  last-updated: uint  ;; Clarity 4: Unix timestamp
})

;; Public Functions

(define-public (execute-best-swap
  (token-in principal)
  (token-out principal)
  (amount-in uint)
  (min-amount-out uint))
  (let (
    (swap-id (var-get total-routed-swaps))
    (best-route (find-best-route token-in token-out amount-in))
    (amount-out (unwrap! best-route ERR-NO-ROUTE))
  )
    (asserts! (>= amount-out min-amount-out) ERR-SLIPPAGE)

    (map-set routed-swaps swap-id {
      user: tx-sender,
      token-in: token-in,
      token-out: token-out,
      amount-in: amount-in,
      amount-out: amount-out,
      route: (list DEX-ALEX),
      gas-saved: u0,
      timestamp: stacks-block-time
    })

    (var-set total-routed-swaps (+ swap-id u1))

    (print {
      event: "routed-swap",
      swap-id: swap-id,
      amount-out: amount-out,
      timestamp: stacks-block-time
    })

    (ok amount-out)
  )
)

(define-public (update-dex-price
  (dex uint)
  (pair (string-ascii 40))
  (price uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) (err u401))

    (map-set dex-prices { dex: dex, pair: pair } {
      price: price,
      last-updated: stacks-block-time
    })

    (ok true)
  )
)

;; Private Functions

(define-private (find-best-route
  (token-in principal)
  (token-out principal)
  (amount-in uint))
  (let (
    (alex-quote (get-dex-quote DEX-ALEX token-in token-out amount-in))
    (stackswap-quote (get-dex-quote DEX-STACKSWAP token-in token-out amount-in))
    (velar-quote (get-dex-quote DEX-VELAR token-in token-out amount-in))
    (arkadiko-quote (get-dex-quote DEX-ARKADIKO token-in token-out amount-in))
    (best-quote (get-max-quote (list alex-quote stackswap-quote velar-quote arkadiko-quote)))
  )
    (ok best-quote)
  )
)

(define-private (get-dex-quote (dex-id uint) (token-in principal) (token-out principal) (amount-in uint))
  (match (map-get? dex-prices { dex: dex-id, pair: "STX-USDA" })
    price-data (/ (* amount-in (get price price-data)) u1000000)
    (/ (* amount-in u995) u1000)  ;; Default 0.5% fee
  )
)

(define-private (get-max-quote (quotes (list 10 uint)))
  (fold max-of-two quotes u0)
)

(define-private (max-of-two (a uint) (b uint))
  (if (> a b) a b)
)

(define-public (execute-multi-hop-swap
  (route (list 5 uint))
  (token-in principal)
  (token-out principal)
  (amount-in uint)
  (min-amount-out uint))
  (let (
    (swap-id (var-get total-routed-swaps))
    (amount-out (calculate-multi-hop-output route amount-in))
  )
    (asserts! (>= amount-out min-amount-out) ERR-SLIPPAGE)

    (map-set routed-swaps swap-id {
      user: tx-sender,
      token-in: token-in,
      token-out: token-out,
      amount-in: amount-in,
      amount-out: amount-out,
      route: route,
      gas-saved: u0,
      timestamp: stacks-block-time
    })

    (var-set total-routed-swaps (+ swap-id u1))

    (print {
      event: "multi-hop-swap",
      swap-id: swap-id,
      route: route,
      amount-out: amount-out,
      timestamp: stacks-block-time
    })

    (ok amount-out)
  )
)

(define-private (calculate-multi-hop-output (route (list 5 uint)) (amount-in uint))
  (fold apply-hop-fee route amount-in)
)

(define-private (apply-hop-fee (dex-id uint) (amount uint))
  (/ (* amount u997) u1000)  ;; 0.3% fee per hop
)

;; Read-Only Functions

(define-read-only (get-routed-swap (swap-id uint))
  (map-get? routed-swaps swap-id)
)

(define-read-only (get-dex-price (dex uint) (pair (string-ascii 40)))
  (map-get? dex-prices { dex: dex, pair: pair })
)

(define-read-only (quote-best-route
  (token-in principal)
  (token-out principal)
  (amount-in uint))
  (find-best-route token-in token-out amount-in)
)

(define-read-only (get-total-savings)
  (var-get total-savings)
)

;; Clarity 4 Enhanced Functions

;; 1. Clarity 4: principal-destruct? - Validate DEX and token principals
(define-read-only (validate-token-principal (token principal))
  (principal-destruct? token)
)

;; 2. Clarity 4: int-to-utf8 - Format swap amounts and routes
(define-read-only (format-swap-amount (amount uint))
  (ok (int-to-utf8 amount))
)

;; 3. Clarity 4: string-to-uint? - Parse DEX identifiers from strings
(define-read-only (parse-dex-id (dex-str (string-ascii 10)))
  (match (string-to-uint? dex-str)
    dex-id (if (<= dex-id u4) (ok dex-id) (err u997))
    (err u998)
  )
)

;; 4. Clarity 4: burn-block-height - Track swap routing with Bitcoin time
(define-read-only (get-swap-timestamps)
  (ok {
    stacks-time: stacks-block-time,
    burn-time: burn-block-height
  })
)

Functions (17)

FunctionAccessArgs
execute-best-swappublictoken-in: principal, token-out: principal, amount-in: uint, min-amount-out: uint
update-dex-pricepublicdex: uint, pair: (string-ascii 40
find-best-routeprivatetoken-in: principal, token-out: principal, amount-in: uint
get-dex-quoteprivatedex-id: uint, token-in: principal, token-out: principal, amount-in: uint
get-max-quoteprivatequotes: (list 10 uint
max-of-twoprivatea: uint, b: uint
execute-multi-hop-swappublicroute: (list 5 uint
calculate-multi-hop-outputprivateroute: (list 5 uint
apply-hop-feeprivatedex-id: uint, amount: uint
get-routed-swapread-onlyswap-id: uint
get-dex-priceread-onlydex: uint, pair: (string-ascii 40
quote-best-routeread-onlytoken-in: principal, token-out: principal, amount-in: uint
get-total-savingsread-only
validate-token-principalread-onlytoken: principal
format-swap-amountread-onlyamount: uint
parse-dex-idread-onlydex-str: (string-ascii 10
get-swap-timestampsread-only