[READY] add Alfredpay (USD/MXN/COP/ARS) on/off-ramp support to SDK#1173
Conversation
✅ 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. |
ebma
left a comment
There was a problem hiding this comment.
@Sharqiewicz does this branch already have the latest changes from staging? I can see that it's using stellar ephemerals, from which we want to move away from. Please adjust that in the new files and fix the type issues and merge conflicts.
There was a problem hiding this comment.
Pull request overview
This PR extends @vortexfi/sdk beyond BRL-only ramps by adding Alfredpay-based USD/MXN/COP onramp and offramp support, including SDK routing, handler implementation, new quote/additional-data types, typed errors, regression tests, and README examples.
Changes:
- Added
AlfredpayHandlerand wiredVortexSdk.registerRamp()/updateRamp()to route Alfredpay flows viaisAlfredpayToken(...). - Expanded SDK type system to include Alfredpay quotes and (register/update) additional-data mappings.
- Added Alfredpay-specific error types + API error parsing, plus a new regression test suite and README usage examples.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/sdk/test/alfredpayHandler.test.ts | Adds regression tests for Alfredpay handler behavior, routing, and update semantics. |
| packages/sdk/src/VortexSdk.ts | Routes USD/MXN/COP ramps to AlfredpayHandler for register/update flows. |
| packages/sdk/src/types.ts | Introduces Alfredpay quote/additional-data types and updates conditional type mappings. |
| packages/sdk/src/handlers/AlfredpayHandler.ts | Implements Alfredpay onramp/offramp register + offramp update flow (ephemerals + signing + update). |
| packages/sdk/src/errors.ts | Adds Alfredpay-specific error classes and API error parsing hooks. |
| packages/sdk/README.md | Documents Alfredpay onramp/offramp usage patterns for SDK consumers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export interface AlfredpayOnrampAdditionalData { | ||
| destinationAddress: string; | ||
| fiatAccountId: string; | ||
| walletAddress?: string; | ||
| sessionId?: string; |
| const result = await handler.registerAlfredpayOnramp("quote_1", { | ||
| destinationAddress: WALLET, | ||
| fiatAccountId: FIAT_ACCOUNT, | ||
| walletAddress: WALLET | ||
| }); |
| export class MissingAlfredpayOnrampParametersError extends AlfredpayOnrampError { | ||
| constructor() { | ||
| super("Parameters destinationAddress and fiatAccountId are required for Alfredpay onramp", 400); | ||
| this.name = "MissingAlfredpayOnrampParametersError"; | ||
| } |
| const { rampProcess } = await sdk.registerRamp(quote, { | ||
| destinationAddress: "0x1234567890123456789012345678901234567890", | ||
| fiatAccountId: "<the user's Alfredpay fiat account id>", | ||
| walletAddress: "0x1234567890123456789012345678901234567890" | ||
| }); |
ffe9b44 to
e7296aa
Compare
Add Mykobo invariant 23 (derive email from the effective user's profile, match any client-supplied email, require APPROVED KYC before issuing payment instructions), two threat-vector rows (cross-user EUR identity via body email; unverified EUR ramp / KYC bypass), and an audit-checklist item. Generalize quote-lifecycle invariant 15 to cover the Mykobo email alongside the BRL tax ID and Alfredpay customer id.
The staging merge left two competing copies of the provider-identity invariants (numbered 14–17 then 14–16 again). The incoming variant described an Alfredpay "anonymous estimate + ANONYMOUS_ALFREDPAY_QUOTE_ID" flow that does not exist in the code — the engines hard-reject via requireAlfredpayEffectiveUserId. Drop the stale trio, keep the code-accurate invariants (Alfredpay quote-creation reject; provider-identity derivation incl. Mykobo email; same-principal rule), renumber to 15/16/17, and fix the dangling cross-references in the invariant, threat-vector, and audit-checklist sections.
0263b5e to
2359c2f
Compare
8b6121f to
46e0b63
Compare
…ove unused imports)
… tracking customer id User-gating Alfredpay quote creation broke the web-app funnel twice over: the swap form requests quotes anonymously for USD/MXN/COP/ARS (all enabled), and even logged-in users without completed Alfredpay KYC could not quote - yet the app's KYC flow only starts after a quote is confirmed, so first-time users could never onboard (chicken-and-egg). Gating quotes is also unnecessary for security: Alfredpay quote requests carry the customerId only inside the free-form tracking 'metadata' object (staging has always sent "unknown" there for anonymous users); the validated top-level customerId exists only on order creation. Orders are created exclusively at ramp registration, which stays strictly KYC-gated via resolveAlfredpayCustomerId. Quote engines now use the non-throwing resolveAlfredpayQuoteCustomerId: the user's real customer id when a KYC-completed customer resolves (keeps provider-side attribution), the "anonymous" sentinel otherwise. The SDK mirrors this split: createQuote no longer requires a secretKey (anonymous rate discovery), while registerRamp fails fast without one since every corridor now requires a user-linked key at registration.
…gistration
The 403 guard ('anonymous quote cannot be registered by an authenticated
caller') made anonymous quotes unregistrable by anyone, breaking the normal
web-app funnel: the frontend creates the quote before login and only
re-creates it when less than 60% of its 10-minute lifetime remains, so any
user completing login+KYC within ~4 minutes registered the original
anonymous quote and got a hard 403. quoteLocked widget flows never refresh
and failed deterministically.
Claiming an anonymous quote is not an escalation: it carries no owner, and
provider identity (taxId / alfredPayId / Mykobo email) is always derived
from the claimer's own KYC records, never from the quote or request body.
The guards that matter remain: 403 when the quote belongs to a *different*
user, 400 when no effective user resolves.
Updates the ADR and security spec to document the claiming semantics and
the registration policy of this fix series.
…eniaAccountForRamp
resolveAveniaAccountForRamp was dead code: prepareOfframpBrlTransactions
re-implemented its derive-then-compare logic inline, and the onramp path
silently ignored a client-supplied additionalData.taxId altogether. That
contradicted the documented deprecation contract ('mismatches against the
derived taxId are rejected') and left the two BRL register paths behaving
differently for no reason.
Both paths now go through resolveAveniaAccountForRamp(userId, taxId), so a
provided taxId that does not match the Avenia profile bound to the
authenticated user is rejected with 400 on onramp and offramp alike.
Also renames avena-account.ts -> avenia-account.ts (the provider is
Avenia) and aligns the BRLA security spec with the actual enforcement.
Secret-key validation narrows by keyPrefix (first 8 chars), but that prefix is the constant sk_live_/sk_test_ for every secret key, so every auth request bcrypt-compares (cost 10) against ALL active secret keys in the environment. The self-serve POST /v1/api-keys endpoint made that scan user-growable: any OTP-signup user could mint unlimited pairs and degrade auth latency for the whole system. createUserApiKey now rejects with 409 once a user holds MAX_ACTIVE_KEYS_PER_USER (10) active keys (revocation frees slots), caps expiresAt at 2 years, and creates the public/secret pair inside a single transaction so a failed second insert cannot leave an orphaned half. The api-keys security spec previously claimed the prefix lookup 'bounds the cost of bcrypt comparisons' - corrected to describe the real linear-scan behavior, the new cap, and the O(1)-lookup key-format change worth making later.
/brla/getUser and /brla/getUserRemainingLimit now require an effective user, but usePreRampCheck fires on the Confirm click - which happens before the ramp machine routes to login (CheckAuth). For a logged-out visitor the pre-check therefore hit a 400, rampWithinLimits returned false, and every BRL submission was blocked. The pre-check is only a UX nicety (an early limit warning); the backend enforces limits authoritatively at ramp registration. Skip it when no auth token is present and let the flow continue to login/KYC.
…ssages The server no longer emits 'email must be provided for Mykobo (EUR) offramp' (the email is now derived from the effective user's profile), and the onramp parameter message dropped 'email' - so parseAPIError matched dead strings and returned generic VortexSdkError for the new KYC-gate responses. Remove the dead mappings, match the current onramp message, and map the three resolveMykoboCustomerForUser rejections (KYC not approved, email mismatch, missing profile) to a typed MykoboKycRequiredError so callers can branch on it the same way they can for Alfredpay.
Two test files were not updated when this branch changed the behavior they cover, so they failed on the branch even before the follow-up fixes: - brla.controller.test.ts still asserted the pre-branch contract (taxId required, unlinked partner keys allowed to read any taxId, anonymous-owned subaccount records rejected with 409). Rewritten for the effective-user model: anonymous and unlinked-partner lookups get 400, user-linked keys read their own taxId, and authenticated callers claim anonymous subaccount records instead of conflicting. - ramp.service.get-ramp-status.test.ts hardcoded flowVariant 'monerium', so getRampStatus returned null (and every assertion failed) on any machine with FLOW_VARIANT=mykobo. Use config.flowVariant like the register-auth test does, making the test environment-independent.
Move the 'METHOD /path (status):' prefix from ApiError.message to error.name so it still groups errors by endpoint in Sentry without leaking into error text shown to users (e.g. the quote card).
…sufficientBalanceError
…-support # Conflicts: # apps/frontend/src/services/api/api-client.ts
Previously the SDK only supported BRL on/off-ramps. This adds USD, MXN, COP, and ARS support in both directions — onramp and offramp — using the BRL integration as the reference pattern.
Closes #1161.
What changed
Core feature (packages/sdk)