Calculated from recorded token losses using historical USD prices at the incident time.
0x3e43e80a28c4c0250c81e25463fb632c5b04436b4272fa303ef4b14f11413be30x812d7eA19bEaece7F921Cdea36ebc882E4850980BSCThe seed transaction 0x73685f84c9bca7b5539af9efb31dfa993218b066afcef544818e4edf3f3ccdd8 on BNB Chain was only a disclosure message. It did not execute exploit logic on-chain, but it pointed at two contracts, including FlapOracle 0x812d7eA19bEaece7F921Cdea36ebc882E4850980. Independent validation confirms a deterministic ACT on that FlapOracle vault: any unprivileged caller can trigger a HEARTBEAT AI request and make the vault pay the configured model fee from protocol funds.
The root cause is straightforward. FlapOracle spends BNB on FlapAIProvider.reason() directly from its public receive() and fallback() path. Because _tryTriggerAI() has no owner, guardian, keeper, or allowlist gate, any arbitrary EOA can reach _sendAIRequest() whenever the public trigger conditions are satisfied and force the vault to transfer one model fee to FlapAIProvider 0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39.
FlapOracle and OpenCLAWVault are AI-driven buyback vaults on BNB Chain. Their design allows inbound native-token transfers to call internal automation logic through receive() and fallback(). When the vault is active, has enough BNB, is out of cooldown, and _checkTrigger() returns a non-empty trigger such as HEARTBEAT, the vault creates a new AI request and prepays the configured provider fee in native BNB.
The provider side is FlapAIProvider, a verified proxy at with implementation . Request creation is public to any consumer contract that calls with a sufficient fee. Fulfillment and refund operations are role-gated, but the exploit validated here does not need either of those paths. The measurable loss happens at request creation time, when the vault prepays .
0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E390x18ee4e5dcf1ab09bd8164fcce72e1139c4972122reason()0.01 BNBAt the validated pre-state, block 86109472, FlapOracle had no pending request, was not paused, was not locked, had timeUntilNextAction() == 0, held 855248632576440837 wei, and used model 0 priced at 10000000000000000 wei. That state means the public trigger surface was live before any attacker transaction arrived.
The issue is an attack-surface bug, not a private-key or admin compromise. Protocol-owned BNB can be spent from an unauthenticated entrypoint. The violated invariant is that unprivileged third parties must not be able to decide when the vault pays for AI requests using protocol funds. In FlapOracle, that invariant fails because receive() and fallback() both call _tryTriggerAI() without checking who triggered the transfer.
Once _tryTriggerAI() runs, the remaining gates are only operational conditions: enough gas, no pending request, vault balance above minBalanceToAct, cooldown elapsed, and a non-empty trigger string from _checkTrigger(). When those conditions hold, _sendAIRequest() reads the provider model fee and immediately executes provider.reason{value: fee}(MODEL_ID, prompt, NUM_CHOICES). That external call spends BNB owned by the vault. No trusted keeper boundary exists between the public caller and the fee-paying external call.
The verified vault code shows the breakpoint directly:
receive() external payable { _tryTriggerAI(); }
fallback() external payable { _tryTriggerAI(); }
function _tryTriggerAI() internal {
if (gasleft() < 200_000 || _lastRequestId != 0 || paused) return;
if (address(this).balance < minBalanceToAct) return;
if (block.timestamp < lastActionTimestamp + cooldownSeconds) return;
string memory reason = _checkTrigger();
if (bytes(reason).length == 0) return;
_sendAIRequest(reason);
}
function _sendAIRequest(string memory triggerReason) internal {
IFlapAIProvider provider = IFlapAIProvider(_getFlapAIProvider());
uint256 fee = provider.getModel(MODEL_ID).price;
if (address(this).balance < fee + minBalanceToAct) return;
_lastRequestId = provider.reason{value: fee}(MODEL_ID, prompt, NUM_CHOICES);
emit AIRequestSent(_lastRequestId, address(this).balance, triggerReason);
}
The provider code explains why the external call is sufficient to realize the loss:
function reason(uint256 modelId, string calldata prompt, uint8 numOfChoices)
external
payable
returns (uint256 requestId)
{
if (!_modelRegistered[modelId]) revert FlapAIProviderModelNotRegistered(modelId);
Model storage model = _models[modelId];
if (!model.enabled) revert FlapAIProviderModelNotEnabled(modelId);
if (msg.value < model.price) revert FlapAIProviderInsufficientFee(msg.value, model.price);
requestId = _nextRequestId++;
_requests[requestId] = Request({ consumer: msg.sender, feePaid: uint128(msg.value), ... });
emit FlapAIProviderRequestMade(requestId, msg.sender, modelId, prompt, numOfChoices, msg.value);
}
The validated ACT starts from BNB Chain block 86109472. At that block the vault already met the public trigger prerequisites: lastRequestId == 0, paused == false, locked == false, timeUntilNextAction() == 0, model 0 was enabled at a price of 10000000000000000 wei, and the vault balance was well above minBalanceToAct. No privileged setup was required by the attacker.
The observed public transaction is 0x3e43e80a28c4c0250c81e25463fb632c5b04436b4272fa303ef4b14f11413be3 in block 86109473, sent by EOA 0x5dd5A8B3fE32159A44D2f0C7F3f52482F0E25A5c. On-chain, the value reaches FlapOracle through a helper route, but that detail is not essential to exploitability. The fork reproduction shows the same predicate can be realized by a fresh arbitrary EOA sending 1 wei directly to the vault, which proves the helper contract is not part of the root cause.
The collector trace for 0x3e43... shows the critical sequence:
0x812d7eA19bEaece7F921Cdea36ebc882E4850980::fallback{value: 18558212430120766}()
-> 0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39::getModel(0)
-> 0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39::reason{value: 10000000000000000}(...)
-> emit AIRequestSent(requestId=237, trigger=HEARTBEAT)
The balance-diff artifact independently confirms the asset movement that defines success. The provider proxy balance moved from 0 to 10000000000000000 wei, while the vault’s net change reflects incoming dust plus the outgoing fee. The observed sender paid only gas:
{
"sender": {
"address": "0x5dd5a8b3fe32159a44d2f0c7f3f52482f0e25a5c",
"before_wei": "822213347114482536",
"after_wei": "822191830064482536",
"delta_wei": "-21517050000000"
},
"provider": {
"address": "0xaee3a7ca6fe6b53f6c32a3e8407ec5a9df8b7e39",
"before_wei": "0",
"after_wei": "10000000000000000",
"delta_wei": "10000000000000000"
}
}
The fork reproduction then removes any ambiguity about exploitability. At the exact pre-state block, a different arbitrary EOA sent 1 wei directly to FlapOracle and caused the vault to create request 237, credit the provider with 10000000000000000 wei, and reduce the vault balance by the fee net of the dust input. That reproduction demonstrates the ACT predicate semantically rather than merely replaying the historical helper path.
The adversary strategy is single-transaction permissionless griefing. The attacker waits until the public trigger gates are open, then sends a low-value transaction to the vault. The call enters receive() or fallback(), causing _tryTriggerAI() to evaluate the public conditions. When _checkTrigger() returns HEARTBEAT, the vault itself constructs the prompt and prepays the model fee.
The observed on-chain example uses tx 0x3e43e80a28c4c0250c81e25463fb632c5b04436b4272fa303ef4b14f11413be3. Its first effect relevant to the vulnerability is that a public transaction reaches FlapOracle’s fallback path. Its second effect is the fee spend: the vault calls FlapAIProvider.reason() and records requestId 237, while emitting AIRequestSent(237, HEARTBEAT). The attack does not require ownership changes, callbacks, or post-processing. The loss is realized as soon as the request is created.
The lifecycle can be summarized as:
receive()/fallback()._tryTriggerAI() accepts the call because the vault is active, funded, idle, and has no pending request._sendAIRequest() reads model 0 price and sends 0.01 BNB to FlapAIProvider.reason().AIRequestSent.The directly validated loss is one provider model fee per successful trigger. At the validated block, that fee was 10000000000000000 wei, or 0.01 BNB, paid from FlapOracle balance to FlapAIProvider. The issue is repeatable whenever the same public trigger gates reopen, so the impact is recurring protocol-fund depletion rather than a one-off isolated transfer.
The measured loss for the validated exploit instance is:
0x812d7eA19bEaece7F921Cdea36ebc882E485098010000000000000000 wei0xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E39The sender’s own balance delta in the observed transaction was only -21517050000000 wei from gas, which reinforces that this is a non-monetary ACT where the attacker can impose protocol cost without needing to fund the fee themselves.
0x73685f84c9bca7b5539af9efb31dfa993218b066afcef544818e4edf3f3ccdd80x3e43e80a28c4c0250c81e25463fb632c5b04436b4272fa303ef4b14f11413be30x812d7eA19bEaece7F921Cdea36ebc882E48509800xaEe3a7Ca6fe6b53f6c32a3e8407eC5A9dF8B7E390x18ee4e5dcf1ab09bd8164fcce72e1139c4972122OracleBuyback.sol0x3e43... showing getModel(0), reason{value: 0.01 BNB}, and AIRequestSent(237, HEARTBEAT)0x3e43... showing provider gain of 10000000000000000 wei812d_permissionless_heartbeat_trigger proving the same state transition from a fresh arbitrary EOA at block 86109472