Skip to content

Commit 15a66a8

Browse files
mattgloryclaude
andcommitted
Add ALEX STX/ALEX arb receiver
Flash-borrows STX, swaps wSTX->ALEX->wSTX on ALEX amm-pool-v2-01 (pool factor u100000000), repays FlashStack with spread as profit. Contracts verified from live mainnet swap transactions 2026-05-12. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1b66865 commit 15a66a8

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

contracts/alex-arb-receiver.clar

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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

Comments
 (0)