From 54c8da5fd1fe1b5b7e380c2d34645a07d41b8cdc Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Tue, 25 Nov 2025 16:17:40 -0800 Subject: [PATCH 1/7] Update auth endpoints to /v1/headless/auth/wallet paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update challenge and verify endpoints to use the new headless API paths: - /v1/auth/wallet/challenge → /v1/headless/auth/wallet/challenge - /v1/auth/wallet/verify → /v1/headless/auth/wallet/verify --- src/plugins/ramps/infinite/infiniteApi.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/plugins/ramps/infinite/infiniteApi.ts b/src/plugins/ramps/infinite/infiniteApi.ts index 9647950e25a..1b0a2599a4b 100644 --- a/src/plugins/ramps/infinite/infiniteApi.ts +++ b/src/plugins/ramps/infinite/infiniteApi.ts @@ -171,7 +171,7 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { getChallenge: async (publicKey: string) => { if (!USE_DUMMY_DATA.getChallenge) { const response = await fetchInfinite( - `/v1/auth/wallet/challenge?publicKey=${publicKey}`, + `/v1/headless/auth/wallet/challenge?publicKey=${publicKey}`, { headers: makeHeaders() } @@ -199,11 +199,14 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { verifySignature: async params => { if (!USE_DUMMY_DATA.verifySignature) { - const response = await fetchInfinite('/v1/auth/wallet/verify', { - method: 'POST', - headers: makeHeaders(), - body: JSON.stringify(params) - }) + const response = await fetchInfinite( + '/v1/headless/auth/wallet/verify', + { + method: 'POST', + headers: makeHeaders(), + body: JSON.stringify(params) + } + ) const data = await response.text() const authResponse = asInfiniteAuthResponse(data) From 68750967758dd688ebff46c07f39283bc100394c Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Tue, 25 Nov 2025 16:26:49 -0800 Subject: [PATCH 2/7] Update customer request/response schema and add getKycLink endpoint - Flatten customer request schema: remove nested 'data' wrapper - Remove residentialAddress from request (no longer supported) - Remove kycLinkUrl and usedPersonaKyc from response - New customers now start with PENDING status instead of ACTIVE - Add getKycLink API method for obtaining KYC verification links - Update kycWorkflow to use new getKycLink endpoint --- src/plugins/ramps/infinite/infiniteApi.ts | 49 +++++++++++-- .../ramps/infinite/infiniteApiTypes.ts | 73 +++++++++++-------- .../ramps/infinite/workflows/kycWorkflow.ts | 36 ++++----- 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/src/plugins/ramps/infinite/infiniteApi.ts b/src/plugins/ramps/infinite/infiniteApi.ts index 1b0a2599a4b..81e97f4b3c7 100644 --- a/src/plugins/ramps/infinite/infiniteApi.ts +++ b/src/plugins/ramps/infinite/infiniteApi.ts @@ -13,6 +13,7 @@ import { asInfiniteCustomerAccountsResponse, asInfiniteCustomerResponse, asInfiniteErrorResponse, + asInfiniteKycLinkResponse, asInfiniteKycStatusResponse, asInfiniteQuoteResponse, asInfiniteTosResponse, @@ -30,6 +31,7 @@ import { type InfiniteCustomerAccountsResponse, type InfiniteCustomerRequest, type InfiniteCustomerResponse, + type InfiniteKycLinkResponse, type InfiniteKycStatus, type InfiniteKycStatusResponse, type InfiniteQuoteResponse, @@ -50,6 +52,7 @@ const USE_DUMMY_DATA: Record = { getTransferStatus: false, createCustomer: false, getKycStatus: false, + getKycLink: false, getTos: false, getCustomerAccounts: false, addBankAccount: false, @@ -456,7 +459,7 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { return asInfiniteCustomerResponse(data) } - // Dummy response - updated with UUID format + // Dummy response - new customers start with PENDING status const dummyResponse: InfiniteCustomerResponse = { customer: { id: `9b0d801f-41ac-4269-abec-${Date.now() @@ -464,13 +467,10 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { .padStart(12, '0') .substring(0, 12)}`, type: params.type === 'individual' ? 'INDIVIDUAL' : 'BUSINESS', - status: 'ACTIVE', + status: 'PENDING', countryCode: params.countryCode, createdAt: new Date().toISOString() - }, - schemaDocumentUploadUrls: null, - kycLinkUrl: `http://localhost:5223/v1/kyc?session=${Date.now()}&callback=edge%3A%2F%2Fkyc-complete`, - usedPersonaKyc: true + } } return dummyResponse @@ -523,6 +523,43 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { return dummyResponse }, + getKycLink: async (customerId: string, redirectUrl: string) => { + // Check if we need to authenticate + if (authState.token == null || isTokenExpired()) { + throw new Error('Authentication required') + } + + if (!USE_DUMMY_DATA.getKycLink) { + const response = await fetchInfinite( + `/v1/headless/customers/${customerId}/kyc-link?redirectUrl=${encodeURIComponent( + redirectUrl + )}`, + { + headers: makeHeaders({ includeAuth: true }) + } + ) + + const data = await response.text() + return asInfiniteKycLinkResponse(data) + } + + // Dummy response + const dummyResponse: InfiniteKycLinkResponse = { + url: `https://infinite.dev/kyc?session=kyc_sess_${Date.now()}&redirect=${encodeURIComponent( + redirectUrl + )}`, + organizationName: 'Test Organization', + branding: { + primaryColor: '#8B9388', + secondaryColor: '#2C2E2A', + logoUrl: 'https://example.com/logo.png', + companyName: 'Test Company' + } + } + + return dummyResponse + }, + getTos: async (customerId: string) => { // Check if we need to authenticate if (authState.token == null || isTokenExpired()) { diff --git a/src/plugins/ramps/infinite/infiniteApiTypes.ts b/src/plugins/ramps/infinite/infiniteApiTypes.ts index 48175a377b0..2cb2ada65b5 100644 --- a/src/plugins/ramps/infinite/infiniteApiTypes.ts +++ b/src/plugins/ramps/infinite/infiniteApiTypes.ts @@ -170,39 +170,28 @@ export const asInfiniteCustomerStatus = asValue( ) export type InfiniteCustomerStatus = ReturnType -// Customer request +// Customer request - flattened structure (no nested data object) export const asInfiniteCustomerRequest = asObject({ type: asInfiniteCustomerType, countryCode: asString, - data: asObject({ - personalInfo: asOptional( - asObject({ - firstName: asString, - lastName: asString - }) - ), - companyInformation: asOptional( - asObject({ - legalName: asString, - website: asOptional(asString) - }) - ), - contactInformation: asObject({ - email: asString - }), - residentialAddress: asOptional( - asObject({ - streetLine1: asString, - streetLine2: asOptional(asString), - city: asString, - state: asString, - postalCode: asString - }) - ) - }) + contactInformation: asObject({ + email: asString + }), + personalInfo: asOptional( + asObject({ + firstName: asString, + lastName: asString + }) + ), + companyInformation: asOptional( + asObject({ + legalName: asString, + website: asOptional(asString) + }) + ) }) -// Customer response +// Customer response - kycLinkUrl and usedPersonaKyc removed, use getKycLink endpoint export const asInfiniteCustomerResponse = asJSON( asObject({ customer: asObject({ @@ -211,10 +200,7 @@ export const asInfiniteCustomerResponse = asJSON( status: asInfiniteCustomerStatus, countryCode: asString, createdAt: asString - }), - schemaDocumentUploadUrls: asOptional(asNull), - kycLinkUrl: asString, - usedPersonaKyc: asBoolean + }) }) ) @@ -287,6 +273,22 @@ export const asInfiniteKycStatusResponse = asJSON( }) ) +// KYC Link response - separate endpoint from customer creation +export const asInfiniteKycLinkResponse = asJSON( + asObject({ + url: asString, + organizationName: asOptional(asString), + branding: asOptional( + asObject({ + primaryColor: asOptional(asString), + secondaryColor: asOptional(asString), + logoUrl: asOptional(asString), + companyName: asOptional(asString) + }) + ) + }) +) + // TOS types export const asInfiniteTosStatus = asValue( 'pending', @@ -393,6 +395,9 @@ export type InfiniteCustomerAccountsResponse = ReturnType< export type InfiniteKycStatusResponse = ReturnType< typeof asInfiniteKycStatusResponse > +export type InfiniteKycLinkResponse = ReturnType< + typeof asInfiniteKycLinkResponse +> export type InfiniteCountriesResponse = ReturnType< typeof asInfiniteCountriesResponse > @@ -477,6 +482,10 @@ export interface InfiniteApi { params: InfiniteCustomerRequest ) => Promise getKycStatus: (customerId: string) => Promise + getKycLink: ( + customerId: string, + redirectUrl: string + ) => Promise getTos: (customerId: string) => Promise // Bank account methods diff --git a/src/plugins/ramps/infinite/workflows/kycWorkflow.ts b/src/plugins/ramps/infinite/workflows/kycWorkflow.ts index 7ea83c41263..b99e4044bc9 100644 --- a/src/plugins/ramps/infinite/workflows/kycWorkflow.ts +++ b/src/plugins/ramps/infinite/workflows/kycWorkflow.ts @@ -57,28 +57,19 @@ export const kycWorkflow = async (params: Params): Promise => { headerTitle: lstrings.ramp_plugin_kyc_title, onSubmit: async (contactInfo: KycFormData) => { try { - // Create customer profile + // Create customer profile with flattened schema const customerResponse = await infiniteApi .createCustomer({ type: 'individual', countryCode: 'US', - data: { - personalInfo: { - firstName: contactInfo.firstName, - lastName: contactInfo.lastName - }, - companyInformation: undefined, - contactInformation: { - email: contactInfo.email - }, - residentialAddress: { - streetLine1: contactInfo.address1, - streetLine2: contactInfo.address2, - city: contactInfo.city, - state: contactInfo.state, - postalCode: contactInfo.postalCode - } - } + contactInformation: { + email: contactInfo.email + }, + personalInfo: { + firstName: contactInfo.firstName, + lastName: contactInfo.lastName + }, + companyInformation: undefined }) .catch((error: unknown) => { return { error } @@ -134,10 +125,13 @@ export const kycWorkflow = async (params: Params): Promise => { await vault.createAddressInfo(addressInfo) } - // Inject deeplink callback into KYC URL - const kycUrl = new URL(customerResponse.kycLinkUrl) + // Get KYC link from separate endpoint const callbackUrl = `https://deep.edge.app/ramp/buy/${pluginId}` - kycUrl.searchParams.set('callback', callbackUrl) + const kycLinkResponse = await infiniteApi.getKycLink( + customerResponse.customer.id, + callbackUrl + ) + const kycUrl = new URL(kycLinkResponse.url) // Open KYC webview with close detection let hasResolved = false From 917b81d86ee3f966cb098e8dc8eff7f132cb0275 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Tue, 25 Nov 2025 16:32:12 -0800 Subject: [PATCH 3/7] Update KYC status values to new Infinite format - Change status values: PENDING, IN_REVIEW, ACTIVE, NEED_ACTIONS, REJECTED - Add sessionStatus field to KYC status response - Update kycWorkflow status checks for new format - ACTIVE replaces 'approved' as the completed status - PENDING replaces 'not_started'/'incomplete' --- src/plugins/ramps/infinite/infiniteApi.ts | 9 ++-- .../ramps/infinite/infiniteApiTypes.ts | 17 ++++---- .../ramps/infinite/workflows/kycWorkflow.ts | 41 +++++++++---------- 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/plugins/ramps/infinite/infiniteApi.ts b/src/plugins/ramps/infinite/infiniteApi.ts index 81e97f4b3c7..af50abda624 100644 --- a/src/plugins/ramps/infinite/infiniteApi.ts +++ b/src/plugins/ramps/infinite/infiniteApi.ts @@ -496,8 +496,8 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { return kycStatusResponse } - // Dummy response - return 'under_review' initially, then 'approved' after 2 seconds - let kycStatus: InfiniteKycStatus = 'under_review' + // Dummy response - return 'IN_REVIEW' initially, then 'ACTIVE' after 2 seconds + let kycStatus: InfiniteKycStatus = 'IN_REVIEW' // Check if we've seen this customer before if (!kycApprovalTimers.has(customerId)) { @@ -507,15 +507,16 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { // Check if 2 seconds have passed const approvalTime = kycApprovalTimers.get(customerId)! if (Date.now() >= approvalTime) { - kycStatus = 'approved' + kycStatus = 'ACTIVE' } } const dummyResponse: InfiniteKycStatusResponse = { customerId, kycStatus, + sessionStatus: kycStatus === 'ACTIVE' ? 'COMPLETED' : 'IN_PROGRESS', kycCompletedAt: - kycStatus === 'approved' ? new Date().toISOString() : undefined + kycStatus === 'ACTIVE' ? new Date().toISOString() : undefined } authState.kycStatus = dummyResponse.kycStatus diff --git a/src/plugins/ramps/infinite/infiniteApiTypes.ts b/src/plugins/ramps/infinite/infiniteApiTypes.ts index 2cb2ada65b5..a047c8ab394 100644 --- a/src/plugins/ramps/infinite/infiniteApiTypes.ts +++ b/src/plugins/ramps/infinite/infiniteApiTypes.ts @@ -251,16 +251,13 @@ export const asInfiniteCustomerAccountsResponse = asJSON( }) ) -// KYC Status types (from Bridge) +// KYC Status types (Infinite format) export const asInfiniteKycStatus = asValue( - 'not_started', - 'incomplete', - 'awaiting_ubo', - 'under_review', - 'approved', - 'rejected', - 'paused', - 'offboarded' + 'PENDING', + 'IN_REVIEW', + 'ACTIVE', + 'NEED_ACTIONS', + 'REJECTED' ) export type InfiniteKycStatus = ReturnType @@ -268,8 +265,8 @@ export const asInfiniteKycStatusResponse = asJSON( asObject({ customerId: asString, kycStatus: asInfiniteKycStatus, + sessionStatus: asOptional(asString), kycCompletedAt: asOptional(asString) - // Note: approvedLimit removed in new API }) ) diff --git a/src/plugins/ramps/infinite/workflows/kycWorkflow.ts b/src/plugins/ramps/infinite/workflows/kycWorkflow.ts index b99e4044bc9..536fa30a2a9 100644 --- a/src/plugins/ramps/infinite/workflows/kycWorkflow.ts +++ b/src/plugins/ramps/infinite/workflows/kycWorkflow.ts @@ -29,17 +29,14 @@ export const kycWorkflow = async (params: Params): Promise => { if (customerId != null) { const kycStatus = await infiniteApi.getKycStatus(customerId) - // If already approved, we're done - no scene shown - if (kycStatus.kycStatus === 'approved') { + // If already approved (ACTIVE), we're done - no scene shown + if (kycStatus.kycStatus === 'ACTIVE') { return } - // If not_started or incomplete, show KYC form - if ( - kycStatus.kycStatus !== 'not_started' && - kycStatus.kycStatus !== 'incomplete' - ) { - // For all other statuses (under_review, awaiting_ubo, etc.), show pending scene + // If PENDING, show KYC form + if (kycStatus.kycStatus !== 'PENDING') { + // For all other statuses (IN_REVIEW, NEED_ACTIONS, etc.), show pending scene await showKycPendingScene( navigationFlow, infiniteApi, @@ -50,7 +47,7 @@ export const kycWorkflow = async (params: Params): Promise => { } } - // Show KYC form for new customers or those with not_started/incomplete status + // Show KYC form for new customers or those with PENDING status const userSubmittedKycForm = await new Promise((resolve, reject) => { navigationFlow.navigate('kycForm', { @@ -181,12 +178,12 @@ export const kycWorkflow = async (params: Params): Promise => { // Get current KYC status after form submission const currentKycStatus = await infiniteApi.getKycStatus(customerId) - // If already approved after form submission, we're done - if (currentKycStatus.kycStatus === 'approved') { + // If already approved (ACTIVE) after form submission, we're done + if (currentKycStatus.kycStatus === 'ACTIVE') { return } - // Show pending scene for non-approved statuses + // Show pending scene for non-ACTIVE statuses await showKycPendingScene( navigationFlow, infiniteApi, @@ -264,7 +261,7 @@ const kycStatusToSceneStatus = ( kycStatus: InfiniteKycStatus ): RampPendingSceneStatus => { switch (kycStatus) { - case 'approved': { + case 'ACTIVE': { // KYC is approved, stop polling and continue workflow. // The next scene will use navigation.replace to replace this verification scene return { @@ -272,23 +269,25 @@ const kycStatusToSceneStatus = ( message: lstrings.ramp_kyc_approved_message } } - case 'not_started': - case 'incomplete': - // KYC is flow needs to be completed + case 'PENDING': + // KYC flow needs to be started/completed return { isChecking: false, message: lstrings.ramp_kyc_incomplete_message } - case 'awaiting_ubo': - case 'under_review': + case 'IN_REVIEW': // KYC is still pending, continue polling return { isChecking: true, message: lstrings.ramp_kyc_pending_message } - case 'rejected': - case 'paused': - case 'offboarded': { + case 'NEED_ACTIONS': + // Additional information required + return { + isChecking: false, + message: lstrings.ramp_kyc_additional_info_required + } + case 'REJECTED': { // Throw error instead of returning it throw new I18nError( lstrings.ramp_kyc_error_title, From b3c8448523f203e5980f747880e1326b979a1ac5 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Tue, 25 Nov 2025 16:33:26 -0800 Subject: [PATCH 4/7] Update customer accounts metadata field name Change bridgeAccountId to externalAccountId in customer accounts response to match new API format that abstracts away the underlying provider. --- src/plugins/ramps/infinite/infiniteApi.ts | 2 +- src/plugins/ramps/infinite/infiniteApiTypes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/ramps/infinite/infiniteApi.ts b/src/plugins/ramps/infinite/infiniteApi.ts index af50abda624..f6e6fddd9a7 100644 --- a/src/plugins/ramps/infinite/infiniteApi.ts +++ b/src/plugins/ramps/infinite/infiniteApi.ts @@ -626,7 +626,7 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { holderName: account.accountName, createdAt: new Date().toISOString(), metadata: { - bridgeAccountId: `ext_acct_${Date.now()}`, + externalAccountId: `ext_acct_${Date.now()}`, verificationStatus: account.verificationStatus } })), diff --git a/src/plugins/ramps/infinite/infiniteApiTypes.ts b/src/plugins/ramps/infinite/infiniteApiTypes.ts index a047c8ab394..0da2de670df 100644 --- a/src/plugins/ramps/infinite/infiniteApiTypes.ts +++ b/src/plugins/ramps/infinite/infiniteApiTypes.ts @@ -242,7 +242,7 @@ export const asInfiniteCustomerAccountsResponse = asJSON( holderName: asString, createdAt: asString, metadata: asObject({ - bridgeAccountId: asEither(asString, asNull), + externalAccountId: asEither(asString, asNull), verificationStatus: asString }) }) From cf686b8b1ef5da8ba4f91ffc795d34bfce480b3a Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Tue, 25 Nov 2025 16:37:20 -0800 Subject: [PATCH 5/7] Remove TOS workflow, types, API method, and locale strings The TOS (Terms of Service) workflow is no longer part of the new Infinite API: - Delete tosWorkflow.ts - Remove tosWorkflow import and call from infiniteRampPlugin.ts - Remove asInfiniteTosStatus, InfiniteTosStatus, asInfiniteTosResponse types - Remove getTos method from InfiniteApi interface and implementation - Remove ramp_tos_* locale strings from en_US.ts and all JSON files --- src/locales/en_US.ts | 8 -- src/locales/strings/de.json | 7 -- src/locales/strings/enUS.json | 7 -- src/locales/strings/es.json | 7 -- src/locales/strings/esMX.json | 7 -- src/locales/strings/fr.json | 7 -- src/locales/strings/it.json | 7 -- src/locales/strings/ja.json | 7 -- src/locales/strings/kaa.json | 7 -- src/locales/strings/ko.json | 7 -- src/locales/strings/pt.json | 7 -- src/locales/strings/ru.json | 7 -- src/locales/strings/vi.json | 7 -- src/locales/strings/zh.json | 7 -- src/plugins/ramps/infinite/infiniteApi.ts | 33 ------ .../ramps/infinite/infiniteApiTypes.ts | 20 ---- .../ramps/infinite/infiniteRampPlugin.ts | 7 -- .../ramps/infinite/workflows/tosWorkflow.ts | 106 ------------------ 18 files changed, 265 deletions(-) delete mode 100644 src/plugins/ramps/infinite/workflows/tosWorkflow.ts diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 617988d33c9..67d22d6b1fb 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -2339,14 +2339,6 @@ const strings = { ramp_kyc_not_approved: 'KYC verification was not completed. Please try again.', ramp_kyc_rejected: 'KYC verification was rejected. Please contact support.', - ramp_tos_status_accepted: 'Terms of service was accepted', - ramp_tos_status_pending: 'Terms of service acceptance is pending', - ramp_tos_status_not_required: 'Terms of service is not required', - ramp_tos_pending_title: 'Terms of Service', - ramp_tos_pending_message: 'Please wait while we check the status.', - ramp_tos_error_title: 'Terms of Service Error', - ramp_tos_timeout_message: - 'Terms of service acceptance timed out. Please try again.', ramp_kyc_additional_info_required: 'Additional information is required for KYC verification.', ramp_kyc_unknown_status: 'Unknown verification status.', diff --git a/src/locales/strings/de.json b/src/locales/strings/de.json index bc5c9e4c51d..6cc86fe827e 100644 --- a/src/locales/strings/de.json +++ b/src/locales/strings/de.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/enUS.json b/src/locales/strings/enUS.json index 6dd15164287..3263cd03458 100644 --- a/src/locales/strings/enUS.json +++ b/src/locales/strings/enUS.json @@ -1829,13 +1829,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/es.json b/src/locales/strings/es.json index 4b4802e607e..222e6a6d2d5 100644 --- a/src/locales/strings/es.json +++ b/src/locales/strings/es.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/esMX.json b/src/locales/strings/esMX.json index 00501eda9b6..ae86bf098b1 100644 --- a/src/locales/strings/esMX.json +++ b/src/locales/strings/esMX.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/fr.json b/src/locales/strings/fr.json index 642e95d4f17..5ce300f158a 100644 --- a/src/locales/strings/fr.json +++ b/src/locales/strings/fr.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/it.json b/src/locales/strings/it.json index f7310e513ff..16c6c5074b8 100644 --- a/src/locales/strings/it.json +++ b/src/locales/strings/it.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Errore di verifica", "ramp_kyc_not_approved": "La verifica di KYC non è stata completata. Riprova.", "ramp_kyc_rejected": "La verifica di KYC è stata respinta. Contatta l'assistenza.", - "ramp_tos_status_accepted": "Condizioni di servizio accettate", - "ramp_tos_status_pending": "I termini di accettazione del servizio sono in attesa", - "ramp_tos_status_not_required": "I termini di servizio non sono richiesti", - "ramp_tos_pending_title": "Termini di servizio", - "ramp_tos_pending_message": "Attendere mentre controlliamo lo stato.", - "ramp_tos_error_title": "Errore di termini di servizio", - "ramp_tos_timeout_message": "Termini di accettazione del servizio scaduti. Per favore riprova.", "ramp_kyc_additional_info_required": "Per la verifica di KYC sono necessarie ulteriori informazioni.", "ramp_kyc_unknown_status": "Stato di verifica sconosciuto.", "ramp_signup_failed_title": "Iscrizione non riuscita", diff --git a/src/locales/strings/ja.json b/src/locales/strings/ja.json index edb968b9dc3..f3c96728f41 100644 --- a/src/locales/strings/ja.json +++ b/src/locales/strings/ja.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/kaa.json b/src/locales/strings/kaa.json index a58593e0eca..63e2c0609de 100644 --- a/src/locales/strings/kaa.json +++ b/src/locales/strings/kaa.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/ko.json b/src/locales/strings/ko.json index f750bd7ea9d..a38d57e880d 100644 --- a/src/locales/strings/ko.json +++ b/src/locales/strings/ko.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/pt.json b/src/locales/strings/pt.json index 081eb2c853d..7983037cf44 100644 --- a/src/locales/strings/pt.json +++ b/src/locales/strings/pt.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/ru.json b/src/locales/strings/ru.json index 9f3a8d57dd1..522bd6d5ef2 100644 --- a/src/locales/strings/ru.json +++ b/src/locales/strings/ru.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/vi.json b/src/locales/strings/vi.json index cd0e1b193cc..6e77b0cbfca 100644 --- a/src/locales/strings/vi.json +++ b/src/locales/strings/vi.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/locales/strings/zh.json b/src/locales/strings/zh.json index ae0270975d2..e07f415df9b 100644 --- a/src/locales/strings/zh.json +++ b/src/locales/strings/zh.json @@ -1828,13 +1828,6 @@ "ramp_kyc_error_title": "Verification Error", "ramp_kyc_not_approved": "KYC verification was not completed. Please try again.", "ramp_kyc_rejected": "KYC verification was rejected. Please contact support.", - "ramp_tos_status_accepted": "Terms of service was accepted", - "ramp_tos_status_pending": "Terms of service acceptance is pending", - "ramp_tos_status_not_required": "Terms of service is not required", - "ramp_tos_pending_title": "Terms of Service", - "ramp_tos_pending_message": "Please wait while we check the status.", - "ramp_tos_error_title": "Terms of Service Error", - "ramp_tos_timeout_message": "Terms of service acceptance timed out. Please try again.", "ramp_kyc_additional_info_required": "Additional information is required for KYC verification.", "ramp_kyc_unknown_status": "Unknown verification status.", "ramp_signup_failed_title": "Failed to Sign Up", diff --git a/src/plugins/ramps/infinite/infiniteApi.ts b/src/plugins/ramps/infinite/infiniteApi.ts index f6e6fddd9a7..11a463d63e7 100644 --- a/src/plugins/ramps/infinite/infiniteApi.ts +++ b/src/plugins/ramps/infinite/infiniteApi.ts @@ -16,7 +16,6 @@ import { asInfiniteKycLinkResponse, asInfiniteKycStatusResponse, asInfiniteQuoteResponse, - asInfiniteTosResponse, asInfiniteTransferResponse, type AuthState, type InfiniteApi, @@ -35,7 +34,6 @@ import { type InfiniteKycStatus, type InfiniteKycStatusResponse, type InfiniteQuoteResponse, - type InfiniteTosResponse, type InfiniteTransferResponse } from './infiniteApiTypes' @@ -53,7 +51,6 @@ const USE_DUMMY_DATA: Record = { createCustomer: false, getKycStatus: false, getKycLink: false, - getTos: false, getCustomerAccounts: false, addBankAccount: false, getCountries: false, @@ -561,36 +558,6 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { return dummyResponse }, - getTos: async (customerId: string) => { - // Check if we need to authenticate - if (authState.token == null || isTokenExpired()) { - throw new Error('Authentication required') - } - - if (!USE_DUMMY_DATA.getTos) { - const response = await fetchInfinite( - `/v1/headless/customers/${customerId}/tos`, - { - headers: makeHeaders({ includeAuth: true }) - } - ) - - const data = await response.text() - return asInfiniteTosResponse(data) - } - - // Dummy response - const dummyResponse: InfiniteTosResponse = { - tosUrl: `https://api.infinite.dev/v1/headless/tos?session=dummy_${Date.now()}&customerId=${customerId}`, - status: Math.random() > 0.5 ? 'accepted' : 'pending', - acceptedAt: null, - customerName: 'Test User', - email: 'test@example.com' - } - - return dummyResponse - }, - // Bank account methods getCustomerAccounts: async (customerId: string) => { diff --git a/src/plugins/ramps/infinite/infiniteApiTypes.ts b/src/plugins/ramps/infinite/infiniteApiTypes.ts index 0da2de670df..3dfb9cb5346 100644 --- a/src/plugins/ramps/infinite/infiniteApiTypes.ts +++ b/src/plugins/ramps/infinite/infiniteApiTypes.ts @@ -286,24 +286,6 @@ export const asInfiniteKycLinkResponse = asJSON( }) ) -// TOS types -export const asInfiniteTosStatus = asValue( - 'pending', - 'accepted', - 'not_required' -) -export type InfiniteTosStatus = ReturnType - -export const asInfiniteTosResponse = asJSON( - asObject({ - tosUrl: asString, - status: asInfiniteTosStatus, - acceptedAt: asEither(asString, asNull), - customerName: asEither(asString, asNull), - email: asEither(asString, asNull) - }) -) - // Countries response export const asInfiniteCountriesResponse = asJSON( asObject({ @@ -402,7 +384,6 @@ export type InfiniteCurrenciesResponse = ReturnType< typeof asInfiniteCurrenciesResponse > export type InfiniteErrorResponse = ReturnType -export type InfiniteTosResponse = ReturnType // Custom error class for API errors export class InfiniteApiError extends Error { @@ -483,7 +464,6 @@ export interface InfiniteApi { customerId: string, redirectUrl: string ) => Promise - getTos: (customerId: string) => Promise // Bank account methods getCustomerAccounts: ( diff --git a/src/plugins/ramps/infinite/infiniteRampPlugin.ts b/src/plugins/ramps/infinite/infiniteRampPlugin.ts index 94225e44591..f20bbed41cb 100644 --- a/src/plugins/ramps/infinite/infiniteRampPlugin.ts +++ b/src/plugins/ramps/infinite/infiniteRampPlugin.ts @@ -41,7 +41,6 @@ import { authenticateWorkflow } from './workflows/authenticateWorkflow' import { bankAccountWorkflow } from './workflows/bankAccountWorkflow' import { confirmationWorkflow } from './workflows/confirmationWorkflow' import { kycWorkflow } from './workflows/kycWorkflow' -import { tosWorkflow } from './workflows/tosWorkflow' const pluginId = 'infinite' const partnerIcon = `${EDGE_CONTENT_SERVER_URI}/infinite.png` @@ -667,12 +666,6 @@ export const infiniteRampPlugin: RampPluginFactory = ( vault }) - // User needs to accept TOS - await tosWorkflow({ - infiniteApi, - navigationFlow - }) - // Ensure we have a bank account const bankAccountResult = await bankAccountWorkflow({ infiniteApi, diff --git a/src/plugins/ramps/infinite/workflows/tosWorkflow.ts b/src/plugins/ramps/infinite/workflows/tosWorkflow.ts deleted file mode 100644 index a0bfae59fa4..00000000000 --- a/src/plugins/ramps/infinite/workflows/tosWorkflow.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { RampPendingSceneStatus } from '../../../../components/scenes/RampPendingScene' -import { lstrings } from '../../../../locales/strings' -import { ExitError } from '../../utils/exitUtils' -import { openWebView } from '../../utils/webViewUtils' -import type { InfiniteApi, InfiniteTosStatus } from '../infiniteApiTypes' -import type { NavigationFlow } from '../utils/navigationFlow' - -interface Params { - infiniteApi: InfiniteApi - navigationFlow: NavigationFlow -} - -export const tosWorkflow = async (params: Params): Promise => { - const { infiniteApi, navigationFlow } = params - const authState = infiniteApi.getAuthState() - - // Ensure we have a customer ID - const customerId = authState.customerId - if (customerId == null) { - throw new ExitError('Customer ID is missing') - } - - // Get TOS status - const tosResponse = await infiniteApi.getTos(customerId) - - // If TOS is already accepted or not required, skip - if ( - tosResponse.status === 'accepted' || - tosResponse.status === 'not_required' - ) { - return - } - - // Show TOS in webview if pending - if (tosResponse.status === 'pending' && tosResponse.tosUrl !== '') { - await new Promise((resolve, reject) => { - openWebView({ - url: tosResponse.tosUrl, - onClose: () => { - resolve() - return true // Allow close - } - }).catch(reject) - }) - - await new Promise((resolve, reject) => { - const startTime = Date.now() - const stepOffThreshold = 60000 // 1 minute - - // Navigate to pending scene to check status - navigationFlow.navigate('rampPending', { - title: lstrings.ramp_tos_pending_title, - initialStatus: tosStatusToSceneStatus('pending'), - onStatusCheck: async () => { - // Check if we've exceeded the timeout threshold - if (Date.now() - startTime > stepOffThreshold) { - return { - isChecking: false, - message: lstrings.ramp_tos_timeout_message - } - } - - const updatedTos = await infiniteApi.getTos(customerId) - - if ( - updatedTos.status === 'accepted' || - updatedTos.status === 'not_required' - ) { - resolve() - } - return tosStatusToSceneStatus(updatedTos.status) - }, - onCancel: () => { - reject(new ExitError('User canceled the Terms of Service screen')) - }, - onClose: () => { - navigationFlow.goBack() - reject(new ExitError('Terms of Service not accepted')) - } - }) - }) - } -} - -// Helper function to convert TOS status to scene status -const tosStatusToSceneStatus = ( - status: InfiniteTosStatus -): RampPendingSceneStatus => { - switch (status) { - case 'accepted': - return { - isChecking: false, - message: lstrings.ramp_tos_status_accepted - } - case 'pending': - return { - isChecking: true, - message: lstrings.ramp_tos_pending_message - } - case 'not_required': - return { - isChecking: false, - message: lstrings.ramp_tos_status_not_required - } - } -} From 51b76c39a1b463f9fd388a1eb284bd686e2189d2 Mon Sep 17 00:00:00 2001 From: Jon Tzeng Date: Wed, 26 Nov 2025 14:12:32 -0800 Subject: [PATCH 6/7] WIP --- src/plugins/ramps/infinite/infiniteApi.ts | 47 +- .../ramps/infinite/infiniteRampPlugin.ts | 836 +++++++++--------- .../workflows/authenticateWorkflow.ts | 15 + .../infinite/workflows/bankAccountWorkflow.ts | 9 +- .../ramps/infinite/workflows/kycWorkflow.ts | 15 + src/plugins/ramps/utils/exitUtils.ts | 6 +- src/util/corePlugins.ts | 200 ++--- 7 files changed, 624 insertions(+), 504 deletions(-) diff --git a/src/plugins/ramps/infinite/infiniteApi.ts b/src/plugins/ramps/infinite/infiniteApi.ts index 11a463d63e7..6efc3d920dd 100644 --- a/src/plugins/ramps/infinite/infiniteApi.ts +++ b/src/plugins/ramps/infinite/infiniteApi.ts @@ -127,6 +127,12 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { .join('') : '' + // Always log API calls for debugging + console.log( + `Infinite API: ${init?.method ?? 'GET'} ${urlStr}`, + init?.body != null ? JSON.parse(init.body as string) : '' + ) + if (ENV.DEBUG_VERBOSE_LOGGING) { console.log( `curl -X ${init?.method ?? 'GET'}${headersStr} '${urlStr}'${ @@ -135,7 +141,11 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { ) } + console.log( + `Infinite API: Awaiting fetch for ${init?.method ?? 'GET'} ${urlStr}...` + ) const response = await fetch(url, init) + console.log(`Infinite API: Fetch returned with status ${response.status}`) if (!response.ok) { const data = await response.text() @@ -258,7 +268,13 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { }) const data = await response.text() - return asInfiniteQuoteResponse(data) + console.log('Infinite API: Quote raw response:', data) + try { + return asInfiniteQuoteResponse(data) + } catch (err: unknown) { + console.error('Infinite API: Failed to parse quote response:', err) + throw err + } } // Dummy response - handle both source amount and target amount @@ -645,7 +661,18 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { headers: makeHeaders() }) const data = await response.text() - return asInfiniteCountriesResponse(data) + console.log('Infinite API: Countries raw response length:', data.length) + try { + return asInfiniteCountriesResponse(data) + } catch (err: unknown) { + console.error( + 'Infinite API: Failed to parse countries response:', + err, + 'Raw data:', + data.substring(0, 500) + ) + throw err + } } // Dummy response @@ -673,7 +700,21 @@ export const makeInfiniteApi = (config: InfiniteApiConfig): InfiniteApi => { headers: makeHeaders({ includeAuth: true }) }) const data = await response.text() - return asInfiniteCurrenciesResponse(data) + console.log( + 'Infinite API: Currencies raw response length:', + data.length + ) + try { + return asInfiniteCurrenciesResponse(data) + } catch (err: unknown) { + console.error( + 'Infinite API: Failed to parse currencies response:', + err, + 'Raw data:', + data.substring(0, 500) + ) + throw err + } } // Dummy response diff --git a/src/plugins/ramps/infinite/infiniteRampPlugin.ts b/src/plugins/ramps/infinite/infiniteRampPlugin.ts index f20bbed41cb..c972a839ba9 100644 --- a/src/plugins/ramps/infinite/infiniteRampPlugin.ts +++ b/src/plugins/ramps/infinite/infiniteRampPlugin.ts @@ -299,456 +299,496 @@ export const infiniteRampPlugin: RampPluginFactory = ( }, fetchQuotes: async (request: RampQuoteRequest): Promise => { - // Global constraints pre-check for quote requests - const quoteConstraintOk = validateRampQuoteRequest( - pluginId, - request, - DEFAULT_PAYMENT_TYPE - ) - if (!quoteConstraintOk) return [] - - const currencyPluginId = request.wallet.currencyInfo.pluginId - - // Extract max amount flags - const isMaxAmount = - 'max' in request.amountQuery || - 'maxExchangeAmount' in request.amountQuery - const maxAmountLimit = - 'maxExchangeAmount' in request.amountQuery - ? request.amountQuery.maxExchangeAmount - : undefined - - // Get the Infinite network name - const infiniteNetwork = getInfiniteNetwork(currencyPluginId) - if (infiniteNetwork == null) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'assetUnsupported' + try { + console.log('Infinite: fetchQuotes starting', { + direction: request.direction, + fiatCurrencyCode: request.fiatCurrencyCode, + displayCurrencyCode: request.displayCurrencyCode, + regionCode: request.regionCode }) - } - // Get countries and currencies from API - const [countries, currenciesData] = await Promise.all([ - getCountriesWithCache(), - getNormalizedCurrenciesWithCache() - ]) - const normalizedCurrencies = currenciesData.normalized - const currencies = currenciesData.raw - - // Verify country and fiat currency support (supports EU aggregate via memberStates) - const country = findCountry(countries, request.regionCode.countryCode) - - const cleanFiatCode = removeIsoPrefix( - request.fiatCurrencyCode - ).toUpperCase() - - if (country == null) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'regionRestricted', - displayCurrencyCode: request.displayCurrencyCode - }) - } + // Global constraints pre-check for quote requests + const quoteConstraintOk = validateRampQuoteRequest( + pluginId, + request, + DEFAULT_PAYMENT_TYPE + ) + if (!quoteConstraintOk) return [] - if (!country.supportedFiatCurrencies.includes(cleanFiatCode)) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'fiatUnsupported', - fiatCurrencyCode: cleanFiatCode, - paymentMethod: 'bank', - pluginDisplayName - }) - } + const currencyPluginId = request.wallet.currencyInfo.pluginId - // Check if payment methods are available for the direction - const paymentMethods = - request.direction === 'buy' - ? country.supportedPaymentMethods.onRamp - : country.supportedPaymentMethods.offRamp + // Extract max amount flags + const isMaxAmount = + 'max' in request.amountQuery || + 'maxExchangeAmount' in request.amountQuery + const maxAmountLimit = + 'maxExchangeAmount' in request.amountQuery + ? request.amountQuery.maxExchangeAmount + : undefined - if (paymentMethods.length === 0) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'paymentUnsupported' - }) - } + // Get the Infinite network name + const infiniteNetwork = getInfiniteNetwork(currencyPluginId) + if (infiniteNetwork == null) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'assetUnsupported' + }) + } - // Get the currency config for this pluginId - const currencyConfig = account.currencyConfig[currencyPluginId] - if (currencyConfig == null) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'assetUnsupported' - }) - } + // Get countries and currencies from API + const [countries, currenciesData] = await Promise.all([ + getCountriesWithCache(), + getNormalizedCurrenciesWithCache() + ]) + const normalizedCurrencies = currenciesData.normalized + const currencies = currenciesData.raw - // Get the contract address for the crypto asset - const contractAddress = getContractAddress( - currencyConfig, - request.tokenId - ) - const lookupKey = contractAddress?.toLowerCase() ?? 'native' - - // Look up the crypto currency in our normalized map - const pluginCurrencies = normalizedCurrencies[currencyPluginId] - if (pluginCurrencies == null) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'assetUnsupported' - }) - } + // Verify country and fiat currency support (supports EU aggregate via memberStates) + const country = findCountry(countries, request.regionCode.countryCode) - const targetCurrency = pluginCurrencies[lookupKey] - if (targetCurrency == null) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'assetUnsupported' - }) - } + const cleanFiatCode = removeIsoPrefix( + request.fiatCurrencyCode + ).toUpperCase() - // Verify crypto currency supports the direction and country - const directionSupported = - (request.direction === 'buy' && targetCurrency.supportsOnRamp) || - (request.direction === 'sell' && targetCurrency.supportsOffRamp) + if (country == null) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'regionRestricted', + displayCurrencyCode: request.displayCurrencyCode + }) + } - if (!directionSupported) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'assetUnsupported' - }) - } + if (!country.supportedFiatCurrencies.includes(cleanFiatCode)) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'fiatUnsupported', + fiatCurrencyCode: cleanFiatCode, + paymentMethod: 'bank', + pluginDisplayName + }) + } - const supportedCountries = - request.direction === 'buy' - ? targetCurrency.onRampCountries - : targetCurrency.offRampCountries + // Check if payment methods are available for the direction + const paymentMethods = + request.direction === 'buy' + ? country.supportedPaymentMethods.onRamp + : country.supportedPaymentMethods.offRamp - if ( - supportedCountries != null && - !supportedCountries.includes(country.code) - ) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'regionRestricted', - displayCurrencyCode: request.displayCurrencyCode - }) - } + if (paymentMethods.length === 0) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'paymentUnsupported' + }) + } - const amountType = request.amountType - const isFiatAmountType = amountType === 'fiat' - const isCryptoAmountType = !isFiatAmountType + // Get the currency config for this pluginId + const currencyConfig = account.currencyConfig[currencyPluginId] + if (currencyConfig == null) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'assetUnsupported' + }) + } - // Get fiat currency for limit checking and max amount determination - const fiatCurrency = currencies.currencies.find( - c => c.code === cleanFiatCode && c.type === 'fiat' - ) + // Get the contract address for the crypto asset + const contractAddress = getContractAddress( + currencyConfig, + request.tokenId + ) + const lookupKey = contractAddress?.toLowerCase() ?? 'native' - if (fiatCurrency == null) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'fiatUnsupported', - fiatCurrencyCode: cleanFiatCode, - paymentMethod: 'bank', - pluginDisplayName - }) - } + // Look up the crypto currency in our normalized map + const pluginCurrencies = normalizedCurrencies[currencyPluginId] + if (pluginCurrencies == null) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'assetUnsupported' + }) + } - const parseAmountString = (value?: string): number | undefined => { - if (value == null) return undefined - const parsed = parseFloat(value) - return Number.isFinite(parsed) ? parsed : undefined - } + const targetCurrency = pluginCurrencies[lookupKey] + if (targetCurrency == null) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'assetUnsupported' + }) + } - const maxAmountLimitValue = parseAmountString(maxAmountLimit) - const minFiatAmount = parseAmountString(fiatCurrency.minAmount) - const maxFiatAmount = parseAmountString(fiatCurrency.maxAmount) - const minCryptoAmount = parseAmountString(targetCurrency.minAmount) - const maxCryptoAmount = parseAmountString(targetCurrency.maxAmount) + // Verify crypto currency supports the direction and country + const directionSupported = + (request.direction === 'buy' && targetCurrency.supportsOnRamp) || + (request.direction === 'sell' && targetCurrency.supportsOffRamp) - let fiatAmount: number | undefined - let cryptoAmount: number | undefined + if (!directionSupported) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'assetUnsupported' + }) + } - const amountString = - 'exchangeAmount' in request.amountQuery - ? request.amountQuery.exchangeAmount - : undefined + const supportedCountries = + request.direction === 'buy' + ? targetCurrency.onRampCountries + : targetCurrency.offRampCountries - const assertNumber = (value: number | undefined): value is number => - value != null && Number.isFinite(value) + if ( + supportedCountries != null && + !supportedCountries.includes(country.code) + ) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'regionRestricted', + displayCurrencyCode: request.displayCurrencyCode + }) + } - if (isMaxAmount) { - if (isFiatAmountType) { - if (!assertNumber(maxFiatAmount)) return [] - if (assertNumber(maxAmountLimitValue)) { - fiatAmount = Math.min(maxFiatAmount, maxAmountLimitValue) + const amountType = request.amountType + const isFiatAmountType = amountType === 'fiat' + const isCryptoAmountType = !isFiatAmountType - if (assertNumber(minFiatAmount) && fiatAmount < minFiatAmount) { + // Get fiat currency for limit checking and max amount determination + const fiatCurrency = currencies.currencies.find( + c => c.code === cleanFiatCode && c.type === 'fiat' + ) + + if (fiatCurrency == null) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'fiatUnsupported', + fiatCurrencyCode: cleanFiatCode, + paymentMethod: 'bank', + pluginDisplayName + }) + } + + const parseAmountString = (value?: string): number | undefined => { + if (value == null) return undefined + const parsed = parseFloat(value) + return Number.isFinite(parsed) ? parsed : undefined + } + + const maxAmountLimitValue = parseAmountString(maxAmountLimit) + const minFiatAmount = parseAmountString(fiatCurrency.minAmount) + const maxFiatAmount = parseAmountString(fiatCurrency.maxAmount) + const minCryptoAmount = parseAmountString(targetCurrency.minAmount) + const maxCryptoAmount = parseAmountString(targetCurrency.maxAmount) + + let fiatAmount: number | undefined + let cryptoAmount: number | undefined + + const amountString = + 'exchangeAmount' in request.amountQuery + ? request.amountQuery.exchangeAmount + : undefined + + const assertNumber = (value: number | undefined): value is number => + value != null && Number.isFinite(value) + + if (isMaxAmount) { + if (isFiatAmountType) { + if (!assertNumber(maxFiatAmount)) return [] + if (assertNumber(maxAmountLimitValue)) { + fiatAmount = Math.min(maxFiatAmount, maxAmountLimitValue) + + if (assertNumber(minFiatAmount) && fiatAmount < minFiatAmount) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'underLimit', + errorAmount: minFiatAmount, + displayCurrencyCode: request.fiatCurrencyCode + }) + } + } else { + fiatAmount = maxFiatAmount + } + } else { + if (!assertNumber(maxCryptoAmount)) return [] + let amountToUse = maxCryptoAmount + if (assertNumber(maxAmountLimitValue)) { + amountToUse = Math.min(amountToUse, maxAmountLimitValue) + } + cryptoAmount = amountToUse + + if ( + assertNumber(minCryptoAmount) && + cryptoAmount < minCryptoAmount + ) { throw new FiatProviderError({ providerId: pluginId, errorType: 'underLimit', - errorAmount: minFiatAmount, - displayCurrencyCode: request.fiatCurrencyCode + errorAmount: minCryptoAmount, + displayCurrencyCode: request.displayCurrencyCode }) } - } else { - fiatAmount = maxFiatAmount } } else { - if (!assertNumber(maxCryptoAmount)) return [] - let amountToUse = maxCryptoAmount - if (assertNumber(maxAmountLimitValue)) { - amountToUse = Math.min(amountToUse, maxAmountLimitValue) + if (amountString == null) { + return [] } - cryptoAmount = amountToUse - - if (assertNumber(minCryptoAmount) && cryptoAmount < minCryptoAmount) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'underLimit', - errorAmount: minCryptoAmount, - displayCurrencyCode: request.displayCurrencyCode - }) + const parsedAmount = parseFloat(amountString) + if (!Number.isFinite(parsedAmount) || parsedAmount <= 0) { + return [] } - } - } else { - if (amountString == null) { - return [] - } - const parsedAmount = parseFloat(amountString) - if (!Number.isFinite(parsedAmount) || parsedAmount <= 0) { - return [] - } - if (isFiatAmountType) { - fiatAmount = parsedAmount + if (isFiatAmountType) { + fiatAmount = parsedAmount - if (assertNumber(minFiatAmount) && fiatAmount < minFiatAmount) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'underLimit', - errorAmount: minFiatAmount, - displayCurrencyCode: request.fiatCurrencyCode - }) - } - - if (assertNumber(maxFiatAmount) && fiatAmount > maxFiatAmount) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'overLimit', - errorAmount: maxFiatAmount, - displayCurrencyCode: request.fiatCurrencyCode - }) - } - } else { - cryptoAmount = parsedAmount - - if (assertNumber(minCryptoAmount) && cryptoAmount < minCryptoAmount) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'underLimit', - errorAmount: minCryptoAmount, - displayCurrencyCode: request.displayCurrencyCode - }) - } - - if (assertNumber(maxCryptoAmount) && cryptoAmount > maxCryptoAmount) { - throw new FiatProviderError({ - providerId: pluginId, - errorType: 'overLimit', - errorAmount: maxCryptoAmount, - displayCurrencyCode: request.displayCurrencyCode - }) - } - } - } - - if (isFiatAmountType && fiatAmount == null) return [] - if (isCryptoAmountType && cryptoAmount == null) return [] - - // Fetch quote from API - const flow: InfiniteQuoteFlow = - request.direction === 'buy' ? 'ONRAMP' : 'OFFRAMP' - - const sourceParams = - request.direction === 'buy' - ? isFiatAmountType - ? { asset: cleanFiatCode, amount: fiatAmount! } - : { asset: cleanFiatCode } - : isCryptoAmountType - ? { - asset: targetCurrency.currencyCode, - network: infiniteNetwork, - amount: cryptoAmount! - } - : { - asset: targetCurrency.currencyCode, - network: infiniteNetwork + if (assertNumber(minFiatAmount) && fiatAmount < minFiatAmount) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'underLimit', + errorAmount: minFiatAmount, + displayCurrencyCode: request.fiatCurrencyCode + }) } - const targetParams = - request.direction === 'buy' - ? { - asset: targetCurrency.currencyCode, - network: infiniteNetwork, - ...(isCryptoAmountType ? { amount: cryptoAmount! } : {}) + if (assertNumber(maxFiatAmount) && fiatAmount > maxFiatAmount) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'overLimit', + errorAmount: maxFiatAmount, + displayCurrencyCode: request.fiatCurrencyCode + }) } - : isFiatAmountType - ? { asset: cleanFiatCode, amount: fiatAmount! } - : { asset: cleanFiatCode } - - const quoteParams = { - flow, - source: sourceParams, - target: targetParams - } - - const quoteResponse = await infiniteApi.createQuote(quoteParams) + } else { + cryptoAmount = parsedAmount - const responseCryptoAmount = - request.direction === 'buy' - ? quoteResponse.target.amount - : quoteResponse.source.amount - const responseFiatAmount = - request.direction === 'buy' - ? quoteResponse.source.amount - : quoteResponse.target.amount + if ( + assertNumber(minCryptoAmount) && + cryptoAmount < minCryptoAmount + ) { + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'underLimit', + errorAmount: minCryptoAmount, + displayCurrencyCode: request.displayCurrencyCode + }) + } - // Convert to RampQuoteResult - map based on direction - const quote: RampQuote = { - pluginId, - partnerIcon, - pluginDisplayName, - displayCurrencyCode: request.displayCurrencyCode, - cryptoAmount: (responseCryptoAmount ?? 0).toString(), - isEstimate: false, - fiatCurrencyCode: request.fiatCurrencyCode, - fiatAmount: (responseFiatAmount ?? 0).toString(), - direction: request.direction, - regionCode: request.regionCode, - paymentType: 'wire', // Infinite uses wire bank transfers - expirationDate: - quoteResponse.expiresAt != null - ? new Date(quoteResponse.expiresAt) - : new Date(Date.now() + 5 * 60 * 1000), // Default 5 minutes if not provided - settlementRange: { - min: { value: 1, unit: 'days' }, - max: { value: 3, unit: 'days' } - }, - approveQuote: async ( - approveParams: RampApproveQuoteParams - ): Promise => { - await handleExitErrorsGracefully(async () => { - const { coreWallet } = approveParams - - // Navigation flow utility shared across workflows to coordinate - const navigationFlow = makeNavigationFlow(navigation) - - // DEV ONLY: Clear auth key if amount is exactly 404 if ( - ENABLE_DEV_TESTING_CAPABILITIES && - `exchangeAmount` in request.amountQuery && - request.amountQuery.exchangeAmount === '404' + assertNumber(maxCryptoAmount) && + cryptoAmount > maxCryptoAmount ) { - await _devOnlyClearAuthKey(account, pluginId) + throw new FiatProviderError({ + providerId: pluginId, + errorType: 'overLimit', + errorAmount: maxCryptoAmount, + displayCurrencyCode: request.displayCurrencyCode + }) } + } + } - // Authenticate with Infinite - await authenticateWorkflow({ - infiniteApi, - privateKey: await getPrivateKey() - }) + if (isFiatAmountType && fiatAmount == null) return [] + if (isCryptoAmountType && cryptoAmount == null) return [] + + // Fetch quote from API + const flow: InfiniteQuoteFlow = + request.direction === 'buy' ? 'ONRAMP' : 'OFFRAMP' + + const sourceParams = + request.direction === 'buy' + ? isFiatAmountType + ? { asset: cleanFiatCode, amount: fiatAmount! } + : { asset: cleanFiatCode } + : isCryptoAmountType + ? { + asset: targetCurrency.currencyCode, + network: infiniteNetwork, + amount: cryptoAmount! + } + : { + asset: targetCurrency.currencyCode, + network: infiniteNetwork + } - // User needs to complete KYC - await kycWorkflow({ - infiniteApi, - navigationFlow, - pluginId, - vault - }) + const targetParams = + request.direction === 'buy' + ? { + asset: targetCurrency.currencyCode, + network: infiniteNetwork, + ...(isCryptoAmountType ? { amount: cryptoAmount! } : {}) + } + : isFiatAmountType + ? { asset: cleanFiatCode, amount: fiatAmount! } + : { asset: cleanFiatCode } - // Ensure we have a bank account - const bankAccountResult = await bankAccountWorkflow({ - infiniteApi, - navigationFlow, - vault - }) + const quoteParams = { + flow, + source: sourceParams, + target: targetParams + } - // Get fresh quote before confirmation using existing params - const freshQuote = await infiniteApi.createQuote(quoteParams) + console.log('Infinite: Creating quote with params:', quoteParams) + const quoteResponse = await infiniteApi.createQuote(quoteParams) + console.log('Infinite: Quote response:', quoteResponse) + + const responseCryptoAmount = + request.direction === 'buy' + ? quoteResponse.target.amount + : quoteResponse.source.amount + const responseFiatAmount = + request.direction === 'buy' + ? quoteResponse.source.amount + : quoteResponse.target.amount + + // Convert to RampQuoteResult - map based on direction + const quote: RampQuote = { + pluginId, + partnerIcon, + pluginDisplayName, + displayCurrencyCode: request.displayCurrencyCode, + cryptoAmount: (responseCryptoAmount ?? 0).toString(), + isEstimate: false, + fiatCurrencyCode: request.fiatCurrencyCode, + fiatAmount: (responseFiatAmount ?? 0).toString(), + direction: request.direction, + regionCode: request.regionCode, + paymentType: 'wire', // Infinite uses wire bank transfers + expirationDate: + quoteResponse.expiresAt != null + ? new Date(quoteResponse.expiresAt) + : new Date(Date.now() + 5 * 60 * 1000), // Default 5 minutes if not provided + settlementRange: { + min: { value: 1, unit: 'days' }, + max: { value: 3, unit: 'days' } + }, + approveQuote: async ( + approveParams: RampApproveQuoteParams + ): Promise => { + await handleExitErrorsGracefully(async () => { + console.log('Infinite: approveQuote starting') + const { coreWallet } = approveParams + + // Navigation flow utility shared across workflows to coordinate + const navigationFlow = makeNavigationFlow(navigation) + + // DEV ONLY: Clear auth key if amount is exactly 404 + if ( + ENABLE_DEV_TESTING_CAPABILITIES && + `exchangeAmount` in request.amountQuery && + request.amountQuery.exchangeAmount === '404' + ) { + await _devOnlyClearAuthKey(account, pluginId) + } - // Show confirmation screen - const result = await confirmationWorkflow( - { + // Authenticate with Infinite + console.log('Infinite: Starting authenticateWorkflow') + await authenticateWorkflow({ infiniteApi, - navigationFlow - }, - { - source: { - amount: - request.direction === 'buy' - ? freshQuote.source.amount.toString() - : freshQuote.target.amount.toString(), - currencyCode: cleanFiatCode - }, - target: { - amount: - request.direction === 'buy' - ? freshQuote.target.amount.toString() - : freshQuote.source.amount.toString(), - currencyCode: request.displayCurrencyCode - }, - request, - freshQuote, - coreWallet, - bankAccountId: bankAccountResult.bankAccountId, - flow, - infiniteNetwork, - cleanFiatCode - } - ) + privateKey: await getPrivateKey() + }) + console.log('Infinite: authenticateWorkflow completed') - if (!result.confirmed || result.transfer == null) { - return - } + // User needs to complete KYC + console.log('Infinite: Starting kycWorkflow') + await kycWorkflow({ + infiniteApi, + navigationFlow, + pluginId, + vault + }) + console.log('Infinite: kycWorkflow completed') - // Log the success event based on direction - if (request.direction === 'buy') { - onLogEvent('Buy_Success', { - conversionValues: { - conversionType: 'buy', - sourceFiatCurrencyCode: request.fiatCurrencyCode, - sourceFiatAmount: freshQuote.source.amount.toString(), - destAmount: new CryptoAmount({ - currencyConfig: coreWallet.currencyConfig, - tokenId: request.tokenId, - exchangeAmount: freshQuote.target.amount.toString() - }), - fiatProviderId: pluginId, - orderId: result.transfer.id - } + // Ensure we have a bank account + console.log('Infinite: Starting bankAccountWorkflow') + const bankAccountResult = await bankAccountWorkflow({ + infiniteApi, + navigationFlow, + vault }) - } else { - onLogEvent('Sell_Success', { - conversionValues: { - conversionType: 'sell', - destFiatCurrencyCode: request.fiatCurrencyCode, - destFiatAmount: freshQuote.target.amount.toString(), - sourceAmount: new CryptoAmount({ - currencyConfig: coreWallet.currencyConfig, - tokenId: request.tokenId, - exchangeAmount: freshQuote.source.amount.toString() - }), - fiatProviderId: pluginId, - orderId: result.transfer.id + console.log( + 'Infinite: bankAccountWorkflow completed', + bankAccountResult + ) + + // Get fresh quote before confirmation using existing params + console.log( + 'Infinite: Getting fresh quote with params:', + quoteParams + ) + const freshQuote = await infiniteApi.createQuote(quoteParams) + console.log('Infinite: Fresh quote received:', freshQuote) + + // Show confirmation screen + console.log('Infinite: Starting confirmationWorkflow') + const result = await confirmationWorkflow( + { + infiniteApi, + navigationFlow + }, + { + source: { + amount: + request.direction === 'buy' + ? freshQuote.source.amount.toString() + : freshQuote.target.amount.toString(), + currencyCode: cleanFiatCode + }, + target: { + amount: + request.direction === 'buy' + ? freshQuote.target.amount.toString() + : freshQuote.source.amount.toString(), + currencyCode: request.displayCurrencyCode + }, + request, + freshQuote, + coreWallet, + bankAccountId: bankAccountResult.bankAccountId, + flow, + infiniteNetwork, + cleanFiatCode } - }) - } - }) - }, - closeQuote: async (): Promise => {} - } + ) + + if (!result.confirmed || result.transfer == null) { + return + } - return [quote] + // Log the success event based on direction + if (request.direction === 'buy') { + onLogEvent('Buy_Success', { + conversionValues: { + conversionType: 'buy', + sourceFiatCurrencyCode: request.fiatCurrencyCode, + sourceFiatAmount: freshQuote.source.amount.toString(), + destAmount: new CryptoAmount({ + currencyConfig: coreWallet.currencyConfig, + tokenId: request.tokenId, + exchangeAmount: freshQuote.target.amount.toString() + }), + fiatProviderId: pluginId, + orderId: result.transfer.id + } + }) + } else { + onLogEvent('Sell_Success', { + conversionValues: { + conversionType: 'sell', + destFiatCurrencyCode: request.fiatCurrencyCode, + destFiatAmount: freshQuote.target.amount.toString(), + sourceAmount: new CryptoAmount({ + currencyConfig: coreWallet.currencyConfig, + tokenId: request.tokenId, + exchangeAmount: freshQuote.source.amount.toString() + }), + fiatProviderId: pluginId, + orderId: result.transfer.id + } + }) + } + }) + }, + closeQuote: async (): Promise => {} + } + + console.log('Infinite: fetchQuotes returning quote successfully') + return [quote] + } catch (err: unknown) { + console.error('Infinite: fetchQuotes error:', err) + throw err + } } } diff --git a/src/plugins/ramps/infinite/workflows/authenticateWorkflow.ts b/src/plugins/ramps/infinite/workflows/authenticateWorkflow.ts index 384a17f7f03..49f22d89f9e 100644 --- a/src/plugins/ramps/infinite/workflows/authenticateWorkflow.ts +++ b/src/plugins/ramps/infinite/workflows/authenticateWorkflow.ts @@ -11,26 +11,41 @@ export const authenticateWorkflow = async (params: Params): Promise => { // Check if already authenticated if (infiniteApi.isAuthenticated()) { + console.log('Infinite Auth: Already authenticated, skipping') return } + console.log('Infinite Auth: Starting authentication flow') + // Get public key from private key const publicKey = infiniteApi.getPublicKeyFromPrivate(privateKey) + console.log('Infinite Auth: Public key:', publicKey) // Get challenge + console.log('Infinite Auth: Requesting challenge') const challengeResponse = await infiniteApi.getChallenge(publicKey) + console.log('Infinite Auth: Challenge received:', { + nonce: challengeResponse.nonce, + expiresAt: challengeResponse.expires_at_iso + }) // Sign the challenge message const signature = infiniteApi.signChallenge( challengeResponse.message, privateKey ) + console.log( + 'Infinite Auth: Message signed, signature:', + signature.slice(0, 20) + '...' + ) // Verify signature + console.log('Infinite Auth: Verifying signature') await infiniteApi.verifySignature({ public_key: publicKey, signature, nonce: challengeResponse.nonce, platform: 'mobile' }) + console.log('Infinite Auth: Authentication successful') } diff --git a/src/plugins/ramps/infinite/workflows/bankAccountWorkflow.ts b/src/plugins/ramps/infinite/workflows/bankAccountWorkflow.ts index 9740ac9142b..ac712bf33a1 100644 --- a/src/plugins/ramps/infinite/workflows/bankAccountWorkflow.ts +++ b/src/plugins/ramps/infinite/workflows/bankAccountWorkflow.ts @@ -17,22 +17,29 @@ interface Result { export const bankAccountWorkflow = async (params: Params): Promise => { const { infiniteApi, navigationFlow, vault } = params + console.log('Infinite Bank: Starting workflow') const authState = infiniteApi.getAuthState() + console.log('Infinite Bank: Auth state customerId:', authState.customerId) // Get existing bank accounts using customer ID if (authState.customerId != null) { + console.log('Infinite Bank: Fetching customer accounts') const customerAccounts = await infiniteApi.getCustomerAccounts( authState.customerId ) + console.log('Infinite Bank: Customer accounts:', customerAccounts) if (customerAccounts.accounts.length > 0) { // Use the first bank account const bankAccountId = customerAccounts.accounts[0].id console.log( - `[bankAccountWorkflow] Using existing bank account: ${bankAccountId}` + `Infinite Bank: Using existing bank account: ${bankAccountId}` ) return { bankAccountId } } + console.log('Infinite Bank: No existing accounts, will show form') + } else { + console.log('Infinite Bank: No customer ID, will show form') } const bankAccountId = await new Promise((resolve, reject) => { diff --git a/src/plugins/ramps/infinite/workflows/kycWorkflow.ts b/src/plugins/ramps/infinite/workflows/kycWorkflow.ts index 536fa30a2a9..607572f1738 100644 --- a/src/plugins/ramps/infinite/workflows/kycWorkflow.ts +++ b/src/plugins/ramps/infinite/workflows/kycWorkflow.ts @@ -23,19 +23,29 @@ interface Params { export const kycWorkflow = async (params: Params): Promise => { const { infiniteApi, navigationFlow, pluginId, vault } = params + console.log('Infinite KYC: Starting workflow') let customerId = infiniteApi.getAuthState().customerId + console.log('Infinite KYC: Customer ID from auth state:', customerId) // If we have a customer ID, check KYC status first if (customerId != null) { + console.log('Infinite KYC: Checking KYC status for customer:', customerId) const kycStatus = await infiniteApi.getKycStatus(customerId) + console.log('Infinite KYC: Status response:', kycStatus) // If already approved (ACTIVE), we're done - no scene shown if (kycStatus.kycStatus === 'ACTIVE') { + console.log('Infinite KYC: Already ACTIVE, skipping') return } // If PENDING, show KYC form if (kycStatus.kycStatus !== 'PENDING') { + console.log( + 'Infinite KYC: Status is', + kycStatus.kycStatus, + '- showing pending scene' + ) // For all other statuses (IN_REVIEW, NEED_ACTIONS, etc.), show pending scene await showKycPendingScene( navigationFlow, @@ -45,6 +55,11 @@ export const kycWorkflow = async (params: Params): Promise => { ) return } + console.log('Infinite KYC: Status is PENDING, showing KYC form') + } else { + console.log( + 'Infinite KYC: No customer ID, will show KYC form for new customer' + ) } // Show KYC form for new customers or those with PENDING status diff --git a/src/plugins/ramps/utils/exitUtils.ts b/src/plugins/ramps/utils/exitUtils.ts index 2c60a681275..d98eef642a8 100644 --- a/src/plugins/ramps/utils/exitUtils.ts +++ b/src/plugins/ramps/utils/exitUtils.ts @@ -31,12 +31,14 @@ export const handleExitErrorsGracefully = async ( ): Promise => { try { await fn() - } catch (error) { + } catch (error: unknown) { // Handle graceful exit - don't propagate the error if (error instanceof ExitError) { + console.log('Ramp workflow: Graceful exit:', error.message) return } - // Re-throw all other errors to be handled up the stack + // Log and re-throw all other errors to be handled up the stack + console.error('Ramp workflow error:', error) throw error } } diff --git a/src/util/corePlugins.ts b/src/util/corePlugins.ts index 9b3e63d6641..1ca4f511960 100644 --- a/src/util/corePlugins.ts +++ b/src/util/corePlugins.ts @@ -4,114 +4,114 @@ import { ENV } from '../env' export const currencyPlugins: EdgeCorePluginsInit = { // // edge-currency-accountbased: - abstract: ENV.ABSTRACT_INIT, - algorand: ENV.ALGORAND_INIT, - amoy: ENV.AMOY_INIT, - arbitrum: ENV.ARBITRUM_INIT, - avalanche: ENV.AVALANCHE_INIT, - axelar: ENV.AXELAR_INIT, - base: ENV.BASE_INIT, - binance: true, - binancesmartchain: ENV.BINANCE_SMART_CHAIN_INIT, - bobevm: true, - botanix: ENV.BOTANIX_INIT, - cardano: ENV.CARDANO_INIT, - cardanotestnet: ENV.CARDANO_TESTNET_INIT, - celo: ENV.CELO_INIT, - coreum: ENV.COREUM_INIT, - cosmoshub: ENV.COSMOSHUB_INIT, - ecash: ENV.ECASH_INIT, - eos: true, - ethereum: ENV.ETHEREUM_INIT, - ethereumclassic: true, - ethereumpow: ENV.ETHEREUM_POW_INIT, - fantom: ENV.FANTOM_INIT, - filecoin: ENV.FILECOIN_INIT, - filecoinfevm: ENV.FILECOINFEVM_INIT, - filecoinfevmcalibration: ENV.FILECOINFEVM_CALIBRATION_INIT, - fio: ENV.FIO_INIT, - hedera: ENV.HEDERA_INIT, - holesky: ENV.HOLESKY_INIT, - hyperevm: ENV.HYPEREVM_INIT, - liberland: ENV.LIBERLAND_INIT, - liberlandtestnet: false, - optimism: ENV.OPTIMISM_INIT, - osmosis: ENV.OSMOSIS_INIT, - piratechain: true, - polkadot: ENV.POLKADOT_INIT, - polygon: ENV.POLYGON_INIT, - pulsechain: ENV.PULSECHAIN_INIT, - ripple: true, - rsk: ENV.RSK_INIT, - sepolia: ENV.SEPOLIA_INIT, - solana: ENV.SOLANA_INIT, - sonic: ENV.SONIC_INIT, - stellar: true, - sui: true, - telos: true, - tezos: true, - thorchainrune: ENV.THORCHAIN_INIT, - thorchainrunestagenet: ENV.THORCHAIN_INIT, - ton: ENV.TON_INIT, - tron: true, - wax: true, - zano: true, + // abstract: ENV.ABSTRACT_INIT, + // algorand: ENV.ALGORAND_INIT, + // amoy: ENV.AMOY_INIT, + // arbitrum: ENV.ARBITRUM_INIT, + // avalanche: ENV.AVALANCHE_INIT, + // axelar: ENV.AXELAR_INIT, + // base: ENV.BASE_INIT, + // binance: true, + // binancesmartchain: ENV.BINANCE_SMART_CHAIN_INIT, + // bobevm: true, + // botanix: ENV.BOTANIX_INIT, + // cardano: ENV.CARDANO_INIT, + // cardanotestnet: ENV.CARDANO_TESTNET_INIT, + // celo: ENV.CELO_INIT, + // coreum: ENV.COREUM_INIT, + // cosmoshub: ENV.COSMOSHUB_INIT, + // ecash: ENV.ECASH_INIT, + // eos: true, + // ethereum: ENV.ETHEREUM_INIT, + // ethereumclassic: true, + // ethereumpow: ENV.ETHEREUM_POW_INIT, + // fantom: ENV.FANTOM_INIT, + // filecoin: ENV.FILECOIN_INIT, + // filecoinfevm: ENV.FILECOINFEVM_INIT, + // filecoinfevmcalibration: ENV.FILECOINFEVM_CALIBRATION_INIT, + // fio: ENV.FIO_INIT, + // hedera: ENV.HEDERA_INIT, + // holesky: ENV.HOLESKY_INIT, + // hyperevm: ENV.HYPEREVM_INIT, + // liberland: ENV.LIBERLAND_INIT, + // liberlandtestnet: false, + // optimism: ENV.OPTIMISM_INIT, + // osmosis: ENV.OSMOSIS_INIT, + // piratechain: true, + // polkadot: ENV.POLKADOT_INIT, + // polygon: ENV.POLYGON_INIT, + // pulsechain: ENV.PULSECHAIN_INIT, + // ripple: true, + // rsk: ENV.RSK_INIT, + // sepolia: ENV.SEPOLIA_INIT, + // solana: ENV.SOLANA_INIT, + // sonic: ENV.SONIC_INIT, + // stellar: true, + // sui: true, + // telos: true, + // tezos: true, + // thorchainrune: ENV.THORCHAIN_INIT, + // thorchainrunestagenet: ENV.THORCHAIN_INIT, + // ton: ENV.TON_INIT, + // tron: true, + // wax: true, + // zano: true, zcash: true, - zksync: ENV.ZKSYNC_INIT, - // edge-currency-bitcoin: - bitcoin: ENV.BITCOIN_INIT, - bitcoincash: ENV.BITCOINCASH_INIT, - bitcoincashtestnet: false, - bitcoingold: true, - bitcoingoldtestnet: false, - bitcoinsv: true, - bitcointestnet: true, - bitcointestnet4: true, - dash: ENV.DASH_INIT, - digibyte: ENV.DIGIBYTE_INIT, - dogecoin: ENV.DOGE_INIT, - eboost: true, - feathercoin: true, - groestlcoin: ENV.GROESTLCOIN_INIT, - litecoin: ENV.LITECOIN_INIT, - pivx: ENV.PIVX_INIT, - qtum: true, - ravencoin: true, - smartcash: true, - ufo: true, - vertcoin: true, - zcoin: ENV.ZCOIN_INIT, - // edge-currency-monero: - monero: ENV.MONERO_INIT + // zksync: ENV.ZKSYNC_INIT, + // edge-currency-bitcoin:, + bitcoin: ENV.BITCOIN_INIT + // bitcoincash: ENV.BITCOINCASH_INIT, + // bitcoincashtestnet: false, + // bitcoingold: true, + // bitcoingoldtestnet: false, + // bitcoinsv: true, + // bitcointestnet: true, + // bitcointestnet4: true, + // dash: ENV.DASH_INIT, + // digibyte: ENV.DIGIBYTE_INIT, + // dogecoin: ENV.DOGE_INIT, + // eboost: true, + // feathercoin: true, + // groestlcoin: ENV.GROESTLCOIN_INIT, + // litecoin: ENV.LITECOIN_INIT, + // pivx: ENV.PIVX_INIT, + // qtum: true, + // ravencoin: true, + // smartcash: true, + // ufo: true, + // vertcoin: true, + // zcoin: ENV.ZCOIN_INIT, + // edge-currency-monero:, + // monero: ENV.MONERO_INIT } export const swapPlugins = { // Centralized Swaps - changehero: ENV.CHANGEHERO_INIT, - changenow: ENV.CHANGE_NOW_INIT, - exolix: ENV.EXOLIX_INIT, - godex: ENV.GODEX_INIT, - lifi: ENV.LIFI_INIT, - letsexchange: ENV.LETSEXCHANGE_INIT, - sideshift: ENV.SIDESHIFT_INIT, - swapuz: ENV.SWAPUZ_INIT, + // changehero: ENV.CHANGEHERO_INIT, + // changenow: ENV.CHANGE_NOW_INIT, + // exolix: ENV.EXOLIX_INIT, + // godex: ENV.GODEX_INIT, + // lifi: ENV.LIFI_INIT, + // letsexchange: ENV.LETSEXCHANGE_INIT, + // sideshift: ENV.SIDESHIFT_INIT, + // swapuz: ENV.SWAPUZ_INIT, // Defi Swaps - bridgeless: true, - rango: ENV.RANGO_INIT, - spookySwap: false, - mayaprotocol: ENV.MAYA_PROTOCOL_INIT, - thorchain: ENV.THORCHAIN_INIT, - swapkit: ENV.SWAPKIT_INIT, - tombSwap: ENV.TOMB_SWAP_INIT, - unizen: false, - velodrome: true, - xrpdex: ENV.XRPDEX_INIT, - '0xgasless': ENV['0XGASLESS_INIT'], + // bridgeless: true, + rango: ENV.RANGO_INIT + // spookySwap: false, + // mayaprotocol: ENV.MAYA_PROTOCOL_INIT, + // thorchain: ENV.THORCHAIN_INIT, + // swapkit: ENV.SWAPKIT_INIT, + // tombSwap: ENV.TOMB_SWAP_INIT, + // unizen: false, + // velodrome: true, + // xrpdex: ENV.XRPDEX_INIT, + // '0xgasless': ENV['0XGASLESS_INIT'], - cosmosibc: true, - fantomsonicupgrade: true, - transfer: true + // cosmosibc: true, + // fantomsonicupgrade: true, + // transfer: true } export const allPlugins = { From 5a68072c36bebc43568d1fffa4135374ab755917 Mon Sep 17 00:00:00 2001 From: Sam Holmes Date: Tue, 23 Dec 2025 14:08:07 -0700 Subject: [PATCH 7/7] fix(infinite): redirect to KYC webview for PENDING customers When a customer with PENDING KYC status already exists, skip the KYC form and redirect directly to the KYC webview. The form is now only shown for new customers without a customerId. Extract openKycWebView helper to reuse webview logic in both the PENDING status handling and new customer form submission flows. --- .../ramps/infinite/workflows/kycWorkflow.ts | 104 ++++++++++++------ 1 file changed, 70 insertions(+), 34 deletions(-) diff --git a/src/plugins/ramps/infinite/workflows/kycWorkflow.ts b/src/plugins/ramps/infinite/workflows/kycWorkflow.ts index 607572f1738..3868ff94915 100644 --- a/src/plugins/ramps/infinite/workflows/kycWorkflow.ts +++ b/src/plugins/ramps/infinite/workflows/kycWorkflow.ts @@ -39,29 +39,53 @@ export const kycWorkflow = async (params: Params): Promise => { return } - // If PENDING, show KYC form - if (kycStatus.kycStatus !== 'PENDING') { + // If PENDING, redirect directly to KYC webview (skip form since customer exists) + if (kycStatus.kycStatus === 'PENDING') { console.log( 'Infinite KYC: Status is', kycStatus.kycStatus, '- showing pending scene' ) // For all other statuses (IN_REVIEW, NEED_ACTIONS, etc.), show pending scene + + await openKycWebView(infiniteApi, customerId, pluginId) + + // Check status after webview closes + const currentKycStatus = await infiniteApi.getKycStatus(customerId) + if (currentKycStatus.kycStatus === 'ACTIVE') { + return + } + + // Show pending scene for non-ACTIVE statuses await showKycPendingScene( navigationFlow, infiniteApi, customerId, - kycStatus.kycStatus + currentKycStatus.kycStatus ) return } - console.log('Infinite KYC: Status is PENDING, showing KYC form') - } else { + + // For all other statuses (IN_REVIEW, NEED_ACTIONS, etc.), show pending scene console.log( - 'Infinite KYC: No customer ID, will show KYC form for new customer' + 'Infinite KYC: Status is', + kycStatus.kycStatus, + '- showing pending scene' + ) + await showKycPendingScene( + navigationFlow, + infiniteApi, + customerId, + kycStatus.kycStatus ) + return } + // Show KYC form only for new customers (no customerId) + console.log( + 'Infinite KYC: No customer ID, will show KYC form for new customer' + ) + // Show KYC form for new customers or those with PENDING status const userSubmittedKycForm = await new Promise((resolve, reject) => { @@ -137,36 +161,13 @@ export const kycWorkflow = async (params: Params): Promise => { await vault.createAddressInfo(addressInfo) } - // Get KYC link from separate endpoint - const callbackUrl = `https://deep.edge.app/ramp/buy/${pluginId}` - const kycLinkResponse = await infiniteApi.getKycLink( + // Open KYC webview + await openKycWebView( + infiniteApi, customerResponse.customer.id, - callbackUrl + pluginId ) - const kycUrl = new URL(kycLinkResponse.url) - - // Open KYC webview with close detection - let hasResolved = false - await openWebView({ - url: kycUrl.toString(), - deeplink: { - direction: 'buy', - providerId: pluginId, - handler: () => { - if (!hasResolved) { - hasResolved = true - resolve(true) - } - } - }, - onClose: () => { - if (!hasResolved) { - hasResolved = true - resolve(true) - } - return true // Allow close - } - }) + resolve(true) } catch (err) { reject(new ExitError('KYC failed')) throw err @@ -311,3 +312,38 @@ const kycStatusToSceneStatus = ( } } } + +// Helper function to open KYC webview +const openKycWebView = async ( + infiniteApi: InfiniteApi, + customerId: string, + pluginId: string +): Promise => { + const callbackUrl = `https://deep.edge.app/ramp/buy/${pluginId}` + const kycLinkResponse = await infiniteApi.getKycLink(customerId, callbackUrl) + const kycUrl = new URL(kycLinkResponse.url) + + await new Promise((resolve, reject) => { + let hasResolved = false + openWebView({ + url: kycUrl.toString(), + deeplink: { + direction: 'buy', + providerId: pluginId, + handler: () => { + if (!hasResolved) { + hasResolved = true + resolve() + } + } + }, + onClose: () => { + if (!hasResolved) { + hasResolved = true + resolve() + } + return true // Allow close + } + }).catch(reject) + }) +}