From 4cae9868e323c49f32162a5da97396330bb7b9d1 Mon Sep 17 00:00:00 2001 From: Hillaryhardy <73938620+unixwhisperer@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:57:59 +0300 Subject: [PATCH] fix(web): receiver snippet requires the owner's own flash-loan in the callback Follow-up to #136, per Matt's review. The core calls execute-stx-flash without as-contract, so tx-sender is whoever called flash-loan. With only the arm check, a stranger could call flash-loan with the exact armed amount before the owner's own transaction landed. The callback now also asserts tx-sender == owner (err u403), the same check #140 added to hk-sbtc-real-receiver-v2. Checked in simnet, with the snippet extracted from both pages and pointed at the local core: before this change the stranger's front-run returned (ok true); after it, it returns (err u403). The owner's own loan still succeeds, and the arm stays single-use (err u402). --- web/src/app/(app)/receivers/page.tsx | 6 +++++- web/src/app/page.tsx | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web/src/app/(app)/receivers/page.tsx b/web/src/app/(app)/receivers/page.tsx index f8280e1..0a245b2 100644 --- a/web/src/app/(app)/receivers/page.tsx +++ b/web/src/app/(app)/receivers/page.tsx @@ -108,7 +108,9 @@ export default function ReceiversPage() { ;; only the core may call it and it repays a hardcoded core (F-10), ;; and only a loan the owner armed can run (C7). Anyone can call ;; flash-loan naming this receiver; without the arm, they could make -;; it run your strategy and spend its fee buffer. +;; it run your strategy and spend its fee buffer. The core calls back +;; without as-contract, so tx-sender is whoever called flash-loan: the +;; callback requires the owner, so nobody can front-run an armed loan. ;; Clarity 3: Clarity 4 replaces as-contract with as-contract?. (impl-trait 'SP3TGRVG7DKGFVRTTVGGS60S59R916FWB4DAB9STZ.stx-flash-receiver-trait.stx-flash-receiver-trait) @@ -135,6 +137,8 @@ export default function ReceiversPage() { (begin (asserts! (is-eq contract-caller FLASH-CORE) (err u401)) (asserts! (is-eq core FLASH-CORE) (err u401)) + ;; only the owner's own flash-loan can use the arm + (asserts! (is-eq tx-sender (var-get owner)) (err u403)) ;; a loan the owner didn't arm stops here; the arm is single-use (asserts! (is-eq (var-get armed) (some amount)) (err u402)) (var-set armed none) diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index 932f32c..a30ff8a 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -254,7 +254,9 @@ export default function LandingPage() { ;; only the core may call it and it repays a hardcoded core (F-10), ;; and only a loan the owner armed can run (C7). Anyone can call ;; flash-loan naming this receiver; without the arm, they could make -;; it run your strategy and spend its fee buffer. +;; it run your strategy and spend its fee buffer. The core calls back +;; without as-contract, so tx-sender is whoever called flash-loan: the +;; callback requires the owner, so nobody can front-run an armed loan. ;; Clarity 3: Clarity 4 replaces as-contract with as-contract?. (impl-trait 'SP3TGRVG7DKGFVRTTVGGS60S59R916FWB4DAB9STZ.stx-flash-receiver-trait.stx-flash-receiver-trait) @@ -281,6 +283,8 @@ export default function LandingPage() { (begin (asserts! (is-eq contract-caller FLASH-CORE) (err u401)) (asserts! (is-eq core FLASH-CORE) (err u401)) + ;; only the owner's own flash-loan can use the arm + (asserts! (is-eq tx-sender (var-get owner)) (err u403)) ;; a loan the owner didn't arm stops here; the arm is single-use (asserts! (is-eq (var-get armed) (some amount)) (err u402)) (var-set armed none)