Add fresh account check#1177
Merged
Merged
Conversation
Reject registerRamp if any required ephemeral has on-chain history on a route-relevant chain (non-zero nonce/balance on Substrate/EVM, or a pre-existing Stellar account). Without this, the backend builds presigned transactions assuming nonce 0 and the ramp halts mid-execution after subsidies have already been spent. Route-to-network mapping mirrors the offramp/onramp transaction-builder dispatcher so only chains an ephemeral actually signs on are checked. Fails closed on RPC errors (SERVICE_UNAVAILABLE) since freshness cannot be presumed without on-chain data.
Add EphemeralNotFreshError and EphemeralFreshnessCheckError so partner clients can distinguish stale-ephemeral failures from other registerRamp errors and recover by retrying (the SDK regenerates ephemerals on every call). Document the retry pattern in the SDK README.
Add F-068 to FINDINGS.md (Medium, fixed), extend the ephemeral-accounts spec with invariant 9 + threat row + audit checklist item, and add invariant 10 + threat row + checklist item to transaction-validation.
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds route-aware freshness validation for client-submitted ephemeral accounts during ramp registration, with SDK error types/docs and security-spec updates for finding F-068.
Changes:
- Introduces
validateEphemeralAccountsFresh()and route-to-network mapping for ephemeral checks. - Wires freshness validation into
registerRampbefore transaction preparation. - Adds SDK error parsing/documentation and updates security specs/findings.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
apps/api/src/api/services/ramp/ephemeral-freshness.ts |
Adds freshness network mapping and on-chain account checks. |
apps/api/src/api/services/ramp/ephemeral-freshness.test.ts |
Adds unit coverage for mapping and freshness failures. |
apps/api/src/api/services/ramp/ramp.service.ts |
Calls freshness validation during ramp registration. |
packages/sdk/src/errors.ts |
Adds SDK freshness error classes and parser branches. |
packages/sdk/README.md |
Documents recoverable freshness errors. |
docs/security-spec/FINDINGS.md |
Adds F-068 as fixed and updates counts. |
docs/security-spec/03-ramp-engine/transaction-validation.md |
Adds freshness validation requirement and checklist entry. |
docs/security-spec/02-signing-keys/ephemeral-accounts.md |
Adds ephemeral freshness principle and mitigation note. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+211
to
+228
| async function assertEvmAccountFresh(address: string, network: EvmNetworks): Promise<void> { | ||
| let nonce: number; | ||
| try { | ||
| const client = EvmClientManager.getInstance().getClient(network); | ||
| nonce = await client.getTransactionCount({ address: address as `0x${string}` }); | ||
| } catch (error) { | ||
| throw new APIError({ | ||
| message: `Could not verify freshness of EVM ephemeral ${address} on ${network}: ${(error as Error).message}`, | ||
| status: httpStatus.SERVICE_UNAVAILABLE | ||
| }); | ||
| } | ||
|
|
||
| if (nonce !== 0) { | ||
| throw new APIError({ | ||
| message: `EVM ephemeral ${address} is not fresh on ${network} (nonce=${nonce}). A new, unused ephemeral account must be provided.`, | ||
| status: httpStatus.BAD_REQUEST | ||
| }); | ||
| } |
|
|
||
| if (quote.inputCurrency === FiatToken.EURC) { | ||
| if (toNetwork === Networks.AssetHub) { | ||
| pushEvmDedup(result, Networks.Polygon); |
Comment on lines
+372
to
+374
| const freshnessCheckMatch = errorMessage.match(/^Could not verify freshness of (Substrate|EVM|Stellar) ephemeral (\S+)/); | ||
| if (freshnessCheckMatch) { | ||
| return new EphemeralFreshnessCheckError(errorMessage, freshnessCheckMatch[1] as EphemeralChain, freshnessCheckMatch[2]); |
1. EVM freshness now checks both nonce AND balance (not just nonce), rejecting addresses that received native funds but never sent a tx. 2. Use sandbox-aware Polygon network (PolygonAmoy when sandboxEnabled) in the freshness mapping, matching the transaction builders. 3. Error middleware now only masks 500 messages in production, preserving 503 SERVICE_UNAVAILABLE messages so the SDK can parse freshness errors.
gianfra-t
approved these changes
May 29, 2026
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.
Adds checks to the register-ramp endpoint to ensure that the ephemeral accounts are unused.