Skip to content

feat: Implement FiatStrategy submit flow with order polling and relay execution#8347

Open
OGPoyraz wants to merge 5 commits intomainfrom
ogp/CONF-960
Open

feat: Implement FiatStrategy submit flow with order polling and relay execution#8347
OGPoyraz wants to merge 5 commits intomainfrom
ogp/CONF-960

Conversation

@OGPoyraz
Copy link
Copy Markdown
Member

@OGPoyraz OGPoyraz commented Mar 31, 2026

Explanation

Implements FiatStrategy submit flow with order polling and relay execution.

  • Implement submitFiatQuotes to poll the on-ramp order via RampsController:getOrder until completion, then re-quote and submit the relay leg with the settled crypto amount
  • Add order validation that verifies the completed order's asset and chain match the expected fiat asset before proceeding with relay
  • Update relay quote request filtering to support exact-input max amount requests used by the fiat submit re-quote path
  • Add orderCode field to TransactionFiatPayment and RampsControllerGetOrderAction to AllowedActions to enable order polling from the fiat strategy
  • Fail-closed on terminal order statuses (failed/cancelled), polling timeout with last-status reporting, asset mismatches, and invalid crypto amounts

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Touches quote submission logic that controls on-ramp order handling and subsequent Relay execution, so mistakes could block fiat-funded flows or submit with incorrect amounts. The change is well-guarded with validation, timeouts, and broad unit test coverage, lowering regression risk.

Overview
Implements the FiatStrategy submit flow: submitFiatQuotes now reads a stored orderCode, polls RampsController:getOrder until the order completes (or fails/times out), validates the completed order’s asset/chain and amount, then re-quotes Relay with the settled exact-input amount and submits via submitRelayQuotes.

Updates Relay quote request filtering to also keep exact-input max-amount requests (non-zero sourceTokenAmount) so the fiat re-quote path isn’t dropped, extends controller types/state to include TransactionFiatPayment.orderCode and permit RampsController:getOrder, and adds comprehensive unit tests covering success and fail-closed error cases.

Written by Cursor Bugbot for commit d149b0f. This will update automatically on new commits. Configure here.

@OGPoyraz OGPoyraz marked this pull request as ready for review March 31, 2026 09:04
@OGPoyraz OGPoyraz requested review from a team as code owners March 31, 2026 09:04
Comment on lines +218 to +219
/** Order identifier - `orderCode` specifically used as RampsService:getOrder parameter in normalized format (/providers/{provider}/orders/{id}). */
orderCode?: string;
Copy link
Copy Markdown
Member Author

@OGPoyraz OGPoyraz Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally we talked Ramps team to add metadata into fiat orders and also filtering mechanism.
Due to limitation of timeframe this may not be option to deliver besides headless ramp, hence we are adding orderCode (meaning order id) into TransactionFiatPayment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, orderId or rampsId to be more generic?

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Comment on lines +81 to +89
.filter((singleRequest) => {
const hasTargetMinimum = singleRequest.targetAmountMinimum !== '0';
const isPostQuote = Boolean(singleRequest.isPostQuote);
const isExactInputRequest =
Boolean(singleRequest.isMaxAmount) &&
new BigNumber(singleRequest.sourceTokenAmount).gt(0);

return hasTargetMinimum || isPostQuote || isExactInputRequest;
})
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fiat re-quote request could get dropped by this filter if targetAmountMinimum happened to be '0' - it's not a post-quote, so the second condition wouldn't save it. The isExactInputRequest check distinguishes real exact-input requests (positive source amount + max amount flag) from empty gas fee token requests (zero source amount), ensuring the fiat re-quote passes through.

@OGPoyraz OGPoyraz changed the title feat: Implement fiat strategy submit flow with order polling and relay execution feat: Implement FiatStrategy submit flow with order polling and relay execution Mar 31, 2026

const log = createModuleLogger(projectLogger, 'fiat-submit');

const ORDER_POLL_INTERVAL_MS = 1000;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, I added feature flags for this on Relay, could extend for fiat in future.

throw new Error('Missing wallet address for fiat submission');
}

const state = messenger.call('TransactionPayController:getState');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, should this be in the request to make it more "pure"?


while (true) {
const order = await messenger.call(
'RampsController:getOrder',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, does this trigger a network request in the RampsController?

Would it be more efficient if they owned the polling and we listened to a messenger event like RampsController:quoteStatusChange or RampsController:stateChange if it's updated in state?

const expectedChainId = expectedAssetId.split('/')[0];
const orderChainId = orderCrypto?.chainId?.toLowerCase();

if (orderAssetId && orderAssetId !== expectedAssetId) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh we already have an order ID? Is this generated by the provider or Ramps?

transactionId,
});

const relayQuotes = await getRelayQuotes({
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to validate the final quote to check the fees are within our original slippage compared to what we showed the user?

Or is that a separate PR?

Comment on lines +218 to +219
/** Order identifier - `orderCode` specifically used as RampsService:getOrder parameter in normalized format (/providers/{provider}/orders/{id}). */
orderCode?: string;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, orderId or rampsId to be more generic?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants