Problem Description
The Plugin SDK's ton.verifyPayment only enforces an upper age bound
(tx.secondsAgo > maxAgeMinutes * 60 rejects very old txs) but accepts any
transaction newer than that window. There is no requestTime/since lower
bound tying the transaction to the moment the payment was requested. The core
verifier does this (if (txTime < requestTime) continue); the SDK path does
not, so a prior unrelated payment of the right amount, made before the deal
existed, can satisfy a new request (the used_transactions table only prevents
re-consuming the same transaction twice, not a pre-existing one).
Location
src/sdk/ton.ts:198-247 (esp. :242 if (tx.secondsAgo > maxAgeMinutes * 60) continue; — upper bound only, no lower bound)
- Contrast core:
src/ton/payment-verifier.ts:98
(if (txTime < requestTime) continue)
How To Reproduce
- A user previously sent the exact required amount to the receiving address.
- A plugin calls
ton.verifyPayment for a new deal of the same amount.
- The old transaction (within
maxAge) is accepted as payment for the new deal.
Impact
Plugins relying on the SDK verifier can be tricked into accepting a stale/replayed
payment, enabling double-spend of a single on-chain transfer across multiple
deals — a direct financial risk.
Proposed Fix
- Add a required
since/requestTime parameter (or derive from the deal/request
creation time) and reject transactions older than it, matching the core
verifier semantics.
Regression Test
it("rejects transactions older than the request time", async () => {
const requestTime = now;
const oldTx = { utime: now - 60, value: amount, dest: address }; // before request, within maxAge
mockTransactions([oldTx]);
const res = await sdk.ton.verifyPayment({ address, amount, since: requestTime });
expect(res.verified).toBe(false);
});
Acceptance Criteria
Related Artifacts
- Report:
improvements/work4/AUDIT_V4_REPORT.md#work4-014
- Module:
src/sdk/ton.ts, src/ton/payment-verifier.ts
- Related: WORK4-013
Audit source: #521 · Prepared in PR: #522 · Finding ID: WORK4-014 (severity: medium)
Suggested labels: ["bug", "audit-finding-v4", "medium", "v3.0-blocker", "financial"] · Suggested milestone: v3.0 - Production Ready
Filed by the automation account, which lacks triage rights on this repo — maintainers please apply the labels/milestone above. Full template & reproduction: improvements/work4/issues/WORK4-014-sdk-verifypayment-missing-lower-time-bound.md.
Problem Description
The Plugin SDK's
ton.verifyPaymentonly enforces an upper age bound(
tx.secondsAgo > maxAgeMinutes * 60rejects very old txs) but accepts anytransaction newer than that window. There is no
requestTime/sincelowerbound tying the transaction to the moment the payment was requested. The core
verifier does this (
if (txTime < requestTime) continue); the SDK path doesnot, so a prior unrelated payment of the right amount, made before the deal
existed, can satisfy a new request (the
used_transactionstable only preventsre-consuming the same transaction twice, not a pre-existing one).
Location
src/sdk/ton.ts:198-247(esp.:242if (tx.secondsAgo > maxAgeMinutes * 60) continue;— upper bound only, no lower bound)src/ton/payment-verifier.ts:98(
if (txTime < requestTime) continue)How To Reproduce
ton.verifyPaymentfor a new deal of the same amount.maxAge) is accepted as payment for the new deal.Impact
Plugins relying on the SDK verifier can be tricked into accepting a stale/replayed
payment, enabling double-spend of a single on-chain transfer across multiple
deals — a direct financial risk.
Proposed Fix
since/requestTimeparameter (or derive from the deal/requestcreation time) and reject transactions older than it, matching the core
verifier semantics.
Regression Test
Acceptance Criteria
verifyPaymentrejects transactions earlier than the request time.Related Artifacts
improvements/work4/AUDIT_V4_REPORT.md#work4-014src/sdk/ton.ts,src/ton/payment-verifier.ts