|
| 1 | +;; FlashStack - ALEX STX/ALEX Arbitrage Receiver |
| 2 | +;; |
| 3 | +;; Borrows STX from flashstack-stx-core, executes a STX -> ALEX -> STX |
| 4 | +;; round-trip on ALEX's AMM pool, repays FlashStack, keeps the spread. |
| 5 | +;; |
| 6 | +;; Live contracts verified from mainnet transactions 2026-05-12: |
| 7 | +;; ALEX AMM: SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01 |
| 8 | +;; wSTX token: SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wstx-v2 |
| 9 | +;; ALEX token: SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex |
| 10 | +;; Pool factor: u100000000 (wSTX/ALEX pool) |
| 11 | +;; |
| 12 | +;; Amount conversion: |
| 13 | +;; ALEX uses 8-decimal fixed-point (ONE_8 = 10^8). STX has 6 decimals. |
| 14 | +;; 1 microSTX = 100 wSTX-v2 units. So: dx = amount_microstx * 100. |
| 15 | +;; |
| 16 | +;; Arb opportunity: |
| 17 | +;; ALEX token accrues protocol revenue. When buy pressure builds before |
| 18 | +;; emissions or governance events, ALEX briefly trades above fair value. |
| 19 | +;; Flash-borrow STX, buy ALEX cheap, sell back for more STX, repay. |
| 20 | +;; |
| 21 | +;; Note: ALEX AMM has a blocklist check (is-blocklisted-or-default). |
| 22 | +;; New contracts are NOT blocked by default. If a runtime u403 error |
| 23 | +;; occurs, contact ALEX team to confirm the contract is permitted. |
| 24 | + |
| 25 | +(impl-trait 'SP3TGRVG7DKGFVRTTVGGS60S59R916FWB4DAB9STZ.stx-flash-receiver-trait.stx-flash-receiver-trait) |
| 26 | + |
| 27 | +;; ============================================= |
| 28 | +;; Constants |
| 29 | +;; ============================================= |
| 30 | + |
| 31 | +(define-constant CONTRACT-OWNER tx-sender) |
| 32 | + |
| 33 | +(define-constant ERR-NOT-OWNER (err u500)) |
| 34 | +(define-constant ERR-SWAP-FAILED (err u501)) |
| 35 | +(define-constant ERR-NO-PROFIT (err u502)) |
| 36 | +(define-constant ERR-REPAY-FAILED (err u503)) |
| 37 | + |
| 38 | +;; ALEX AMM pool v2 |
| 39 | +(define-constant ALEX-POOL 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-pool-v2-01) |
| 40 | +;; wSTX-v2: STX wrapper used as token-x in the ALEX pool |
| 41 | +(define-constant WSTX 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-wstx-v2) |
| 42 | +;; ALEX governance token: token-y in the pool |
| 43 | +(define-constant ALEX-TOKEN 'SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.token-alex) |
| 44 | +;; Pool factor identifying the wSTX/ALEX pool (confirmed from live swap txs) |
| 45 | +(define-constant ALEX-FACTOR u100000000) |
| 46 | +;; Conversion: 1 microSTX = 100 wSTX-v2 fixed-point units |
| 47 | +(define-constant WSTX-SCALE u100) |
| 48 | + |
| 49 | +;; ============================================= |
| 50 | +;; Flash Loan Callback |
| 51 | +;; ============================================= |
| 52 | + |
| 53 | +;; Called by flashstack-stx-core after sending (amount) microSTX to this contract. |
| 54 | +;; Executes STX -> ALEX -> STX round-trip and repays principal + fee. |
| 55 | +(define-public (execute-stx-flash (amount uint) (core principal)) |
| 56 | + (let ( |
| 57 | + (fee-bp (unwrap! (contract-call? 'SP20XD46NGAX05ZQZDKFYCCX49A3852BQABNP0VG5.flashstack-stx-core |
| 58 | + get-fee-basis-points) ERR-REPAY-FAILED)) |
| 59 | + (raw-fee (/ (* amount fee-bp) u10000)) |
| 60 | + (fee (if (> raw-fee u0) raw-fee u1)) |
| 61 | + (total-owed (+ amount fee)) |
| 62 | + ;; Convert microSTX to wSTX-v2 fixed-point for ALEX pool input |
| 63 | + (dx (* amount WSTX-SCALE)) |
| 64 | + ) |
| 65 | + ;; Leg 1: wSTX -> ALEX |
| 66 | + ;; The pool calls wSTX.transfer-fixed(dx, us, vault) which moves (amount) microSTX from us. |
| 67 | + ;; as-contract required: STX is held by this contract, not by the external caller. |
| 68 | + (unwrap! (as-contract (contract-call? ALEX-POOL swap-x-for-y |
| 69 | + WSTX ;; token-x: wSTX-v2 (STX wrapper) |
| 70 | + ALEX-TOKEN ;; token-y: ALEX governance token |
| 71 | + ALEX-FACTOR ;; pool factor (wSTX/ALEX pool identifier) |
| 72 | + dx ;; dx in wSTX-v2 fixed-point (= amount * 100) |
| 73 | + none ;; min-dy: no slippage floor; repay check below is the safety gate |
| 74 | + )) ERR-SWAP-FAILED) |
| 75 | + |
| 76 | + ;; Read ALEX balance received from leg 1 |
| 77 | + (let ((alex-bal (unwrap! |
| 78 | + (as-contract (contract-call? ALEX-TOKEN get-balance tx-sender)) |
| 79 | + ERR-SWAP-FAILED))) |
| 80 | + (asserts! (> alex-bal u0) ERR-SWAP-FAILED) |
| 81 | + |
| 82 | + ;; Leg 2: ALEX -> wSTX (back to STX) |
| 83 | + ;; Pool calls ALEX.transfer-fixed(alex-bal, us, vault) and sends us wSTX (= STX). |
| 84 | + (unwrap! (as-contract (contract-call? ALEX-POOL swap-y-for-x |
| 85 | + WSTX ;; token-x: wSTX-v2 (what we receive) |
| 86 | + ALEX-TOKEN ;; token-y: ALEX (what we spend) |
| 87 | + ALEX-FACTOR ;; pool factor |
| 88 | + alex-bal ;; dy: all ALEX we hold from leg 1 |
| 89 | + none ;; min-dx: no slippage floor; repay check enforces the minimum |
| 90 | + )) ERR-SWAP-FAILED) |
| 91 | + |
| 92 | + ;; Verify we received enough STX to cover repayment |
| 93 | + (let ((stx-bal (stx-get-balance (as-contract tx-sender)))) |
| 94 | + (asserts! (>= stx-bal total-owed) ERR-REPAY-FAILED) |
| 95 | + ;; Repay FlashStack: principal + fee |
| 96 | + (unwrap! (as-contract (stx-transfer? total-owed tx-sender core)) ERR-REPAY-FAILED) |
| 97 | + (ok true) |
| 98 | + ) |
| 99 | + ) |
| 100 | + ) |
| 101 | +) |
| 102 | + |
| 103 | +;; ============================================= |
| 104 | +;; Admin |
| 105 | +;; ============================================= |
| 106 | + |
| 107 | +(define-public (rescue-stx (amount uint) (to principal)) |
| 108 | + (begin |
| 109 | + (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER) |
| 110 | + (unwrap! (as-contract (stx-transfer? amount tx-sender to)) ERR-REPAY-FAILED) |
| 111 | + (ok true) |
| 112 | + ) |
| 113 | +) |
| 114 | + |
| 115 | +(define-public (rescue-alex (amount uint) (to principal)) |
| 116 | + (begin |
| 117 | + (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER) |
| 118 | + (unwrap! |
| 119 | + (as-contract (contract-call? ALEX-TOKEN transfer amount tx-sender to none)) |
| 120 | + ERR-REPAY-FAILED) |
| 121 | + (ok true) |
| 122 | + ) |
| 123 | +) |
| 124 | + |
| 125 | +;; ============================================= |
| 126 | +;; Read-only |
| 127 | +;; ============================================= |
| 128 | + |
| 129 | +;; Pre-flight profit estimate. |
| 130 | +;; This is a rough estimate based only on fee arithmetic -- actual profit |
| 131 | +;; depends on the pool price at execution time. Use as a sanity check only. |
| 132 | +;; bonus-bp: expected spread capture in basis points (e.g. u100 = 1%) |
| 133 | +(define-read-only (simulate (loan-amount uint) (spread-bp uint)) |
| 134 | + (let ( |
| 135 | + (raw-fee (/ (* loan-amount u5) u10000)) |
| 136 | + (fee (if (> raw-fee u0) raw-fee u1)) |
| 137 | + (spread (/ (* loan-amount spread-bp) u10000)) |
| 138 | + (profit (if (> spread fee) (- spread fee) u0)) |
| 139 | + ) |
| 140 | + { |
| 141 | + loan-amount: loan-amount, |
| 142 | + spread-bp: spread-bp, |
| 143 | + spread: spread, |
| 144 | + flash-fee: fee, |
| 145 | + net-profit: profit, |
| 146 | + profitable: (> spread fee), |
| 147 | + owed-to-core: (+ loan-amount fee), |
| 148 | + } |
| 149 | + ) |
| 150 | +) |
| 151 | + |
| 152 | +(define-read-only (get-stx-balance) |
| 153 | + (stx-get-balance (as-contract tx-sender)) |
| 154 | +) |
| 155 | + |
| 156 | +(define-read-only (get-alex-balance) |
| 157 | + (as-contract (contract-call? ALEX-TOKEN get-balance tx-sender)) |
| 158 | +) |
0 commit comments