feat: Payment Request Expiration with Smart Contract Enforcement (#460)#558
Open
rindicomfort wants to merge 2 commits into
Open
feat: Payment Request Expiration with Smart Contract Enforcement (#460)#558rindicomfort wants to merge 2 commits into
rindicomfort wants to merge 2 commits into
Conversation
…ement (Smartdevs17#460) - Add contracts/soroban/payment-request/ — Soroban expiration contract - create_request: configurable TTL (60s–90d), stores expiresAt on-ledger - pay: enforces expiration via env.ledger().timestamp() + grace period - expire_request: callable by anyone after deadline (lazy expiry) - cancel_request: requester-only cancellation - renew_request: creates new request from expired/cancelled with new TTL - Events: req_crtd, req_paid, req_expd, req_cncl, req_rnwd - Admin: initialize, set_grace_period - Persistent storage with TTL bumps; Soroban SDK 21.7.6 - Update contracts/evm/contracts/PaymentRequestExpiry.sol — EVM enforcement - createRequest: payer (open or specific), ERC-20 or native ETH, TTL bounds - pay: reverts with RequestIsExpired if block.timestamp > expiresAt + grace - expireRequest: permissionless sweep after deadline - cancelRequest: requester-only - renewRequest: creates new request from expired/cancelled with new rate - defaultGracePeriod: admin-configurable (default 60s) - Events: RequestCreated, RequestPaid, RequestExpired, RequestCancelled, RequestRenewed - Add backend/src/services/payments/expiration.ts — backend enforcement - createRequest: validates TTL, persists expiresAt to DB - assertNotExpired: guards before chain relay; includes GRACE_PERIOD_MS buffer - markPaid / cancelRequest helpers - renewRequest: optional rateMultiplier for new exchange rate - listRequests: paginated with status filter (all/pending/paid/expired/cancelled) - sweepExpired: batched DB sweep, transitions pending→expired, sends notifications - startExpirationCron: BullMQ cron every 2 minutes via Redis - Typed errors: PaymentRequestExpiredError, NotFoundError, AlreadyPaidError - Add Prisma schema: PaymentRequest model - Fields: expiresAt, expiredAt, paidAt, contractRequestId, status enum - Indexes: (tenantId,status), expiresAt, (status,expiresAt) for sweep query - Add migration SQL: payment_requests table + indexes - Add frontend components - PaymentRequestExpirationBadge: live countdown ticker (updates every 1s) red (<5min), yellow (<30min), blue (>30min), expired/paid/cancelled badges - PaymentRequestList: dashboard table with status filter tabs + Renew/Cancel actions Closes Smartdevs17#460
|
@rindicomfort is attempting to deploy a commit to the smartdevs17's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@rindicomfort Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements issue #460 — time-bound payment requests enforced at the Soroban contract level, EVM contract level, backend service level, and frontend dashboard.
Closes #460
Changes
contracts/soroban/payment-request/— Soroban contractcreate_request— configurable TTL (60 s – 90 days), payer address (open or specific), SEP-41 token, memo; storesexpires_aton-ledgerpay— callsenv.ledger().timestamp()and reverts withRequestIsExpiredif past deadline + grace; executes SEP-41 cross-contract transfer on successexpire_request— permissionless; anyone can sweep stale requests after their deadlinecancel_request— requester-only cancellationrenew_request— creates a new request from expired/cancelled with new amount + TTL; emitsreq_rnwdevent linking old and new IDsreq_crtd,req_paid,req_expd,req_cncl,req_rnwdcontracts/evm/contracts/PaymentRequestExpiry.sol— EVM contractcreateRequest— ERC-20 or native ETH, open or designated payer, TTL 60 s – 90 dayspay— checksblock.timestamp > expiresAt + gracePeriod, lazily marks expired and reverts withRequestIsExpiredexpireRequest— permissionless sweepcancelRequest— requester-onlyrenewRequest— new amount + TTL from expired/cancelled request;RequestRenewedevent with old→new ID linksetDefaultGracePeriod— owner-only; default 60 s (mitigates ±15 s block timestamp variance)RequestIsExpired,RequestNotExpiredYet,UnauthorizedPayer,InvalidTtl, etc.backend/src/services/payments/expiration.ts— Backend servicecreateRequest— validates TTL, persistsexpiresAtto databaseassertNotExpired— pre-relay guard: rejects expired/cancelled/paid requests; appliesGRACE_PERIOD_MS = 60_000buffer to match on-chain gracesweepExpired— batched (100/run) DB sweep; transitionspending→expired, sends notifications to requester + payerstartExpirationCron— BullMQ repeating job every 2 minutes via Redis; 3 retries with exponential backoffrenewRequest— optionalrateMultiplierfor new exchange rate on renewallistRequests— paginated withstatusfilter (all / pending / paid / expired / cancelled) for dashboardPaymentRequestExpiredError,PaymentRequestNotFoundError,AlreadyPaidError,CancelledErrorPrisma schema
PaymentRequestmodelexpiresAt,expiredAt,paidAt,contractRequestId,status(enum)(tenantId, status),expiresAt,(status, expiresAt)for sweep query performanceMigration SQL
payment_requeststable with all expiration fields and indexesFrontend components
PaymentRequestExpirationBadge— live 1-second countdown ticker; colour-coded urgency (blue > 30 min, yellow < 30 min, red < 5 min); Expired / Paid / Cancelled static badges; accessibletitlewith full timestampPaymentRequestList— dashboard table with filter tabs (All / Pending / Paid / Expired / Cancelled) + per-row counts; Renew button for expired/cancelled; Cancel button for pending; accessible withrole,aria-pressed,focus-visibleAcceptance Criteria
paychecksenv.ledger().timestamp()paychecksblock.timestamp + gracePeriodassertNotExpiredguard + grace buffersendExpirationNotificationsin sweep looprenewRequestwith optionalrateMultiplierPaymentRequestListfilter tabsEdge Cases
toLocaleString()for display; BullMQ cron runs in UTCset_grace_period(Soroban) /setDefaultGracePeriod(EVM); backendGRACE_PERIOD_MSmatchesassertNotExpired+ DB status check prevents double-pay; on-chain status is authoritative