@metamask/ramps-controller 26.2.0 → 27.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -1
- package/dist/NeoBankService-method-action-types.d.ts +38 -3
- package/dist/NeoBankService-method-action-types.d.ts.map +1 -1
- package/dist/NeoBankService-method-action-types.js.map +1 -1
- package/dist/NeoBankService.d.ts +49 -4
- package/dist/NeoBankService.d.ts.map +1 -1
- package/dist/NeoBankService.js +176 -8
- package/dist/NeoBankService.js.map +1 -1
- package/dist/RampsController-method-action-types.d.ts +24 -8
- package/dist/RampsController-method-action-types.d.ts.map +1 -1
- package/dist/RampsController-method-action-types.js.map +1 -1
- package/dist/RampsController.d.ts +73 -30
- package/dist/RampsController.d.ts.map +1 -1
- package/dist/RampsController.js +284 -88
- package/dist/RampsController.js.map +1 -1
- package/dist/autoramp-types.d.ts +29 -0
- package/dist/autoramp-types.d.ts.map +1 -1
- package/dist/autoramp-types.js.map +1 -1
- package/dist/autorampAccount.d.ts +21 -0
- package/dist/autorampAccount.d.ts.map +1 -1
- package/dist/autorampAccount.js +38 -0
- package/dist/autorampAccount.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/RampsController.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseController } from '@metamask/base-controller';
|
|
2
|
-
import { BrokenCircuitError } from '@metamask/controller-utils';
|
|
2
|
+
import { BrokenCircuitError, HttpError } from '@metamask/controller-utils';
|
|
3
3
|
import { BigNumber } from 'bignumber.js';
|
|
4
4
|
import { applyAutorampRemoteStatus, AutorampStatus, createAutorampAccount, markAutorampNotified, } from './autorampAccount.js';
|
|
5
5
|
import { getHeadlessProviderAllowlist, isHeadlessAllProvidersEnabled, normalizeHeadlessProviderId, } from './featureFlags.js';
|
|
@@ -81,9 +81,9 @@ export const RAMPS_CONTROLLER_REQUIRED_CONTROLLER_ACTIONS = [
|
|
|
81
81
|
'AuthenticationController:getSessionProfile',
|
|
82
82
|
'AuthenticationController:isSignedIn',
|
|
83
83
|
'KeyringController:signPersonalMessage',
|
|
84
|
-
'KycController:getSessionStatusForVendor',
|
|
85
84
|
'KycController:getProviderFlowStatus',
|
|
86
|
-
'KycController:
|
|
85
|
+
'KycController:readSessionStatus',
|
|
86
|
+
'KycController:fetchSessionStatusOnce',
|
|
87
87
|
'KycController:hasCompletedVendorDisclaimers',
|
|
88
88
|
'KycController:hasCompletedSessionDisclaimers',
|
|
89
89
|
'KycController:clearState',
|
|
@@ -92,6 +92,15 @@ export const RAMPS_CONTROLLER_REQUIRED_CONTROLLER_ACTIONS = [
|
|
|
92
92
|
'UserStorageController:performGetStorageAllFeatureEntries',
|
|
93
93
|
'UserStorageController:performBatchSetStorage',
|
|
94
94
|
];
|
|
95
|
+
/**
|
|
96
|
+
* `finalStatus` values that end a KYC session. Mirrors
|
|
97
|
+
* `TERMINAL_SESSION_STATUSES` in `@metamask/kyc-controller`.
|
|
98
|
+
*/
|
|
99
|
+
const TERMINAL_VBA_KYC_FINAL_STATUSES = new Set([
|
|
100
|
+
'approved',
|
|
101
|
+
'rejected',
|
|
102
|
+
'retry',
|
|
103
|
+
]);
|
|
95
104
|
/**
|
|
96
105
|
* KYC vocabulary used on the VBA onboarding snapshot. `'none'` means no
|
|
97
106
|
* session exists yet (or the vendor returned an unrecognized status).
|
|
@@ -112,13 +121,21 @@ export const VBA_PROVIDER_FLOW_STATUSES = [
|
|
|
112
121
|
];
|
|
113
122
|
/**
|
|
114
123
|
* Autoramp setup progress after KYC has been approved.
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
124
|
+
*
|
|
125
|
+
* `'needs_wallet_registration'` means KYC is approved, this wallet has no
|
|
126
|
+
* persisted registration row, and there is no usable autoramp.
|
|
127
|
+
* `'needs_source_currency'` means KYC is approved, the registration row is
|
|
128
|
+
* `active` or `disabled`, and there is no usable autoramp.
|
|
129
|
+
* `'in_progress'` is reserved for hosts that observe an in-flight hydrate.
|
|
130
|
+
* {@link RampsController.hydrateVbaOnboarding} returns `'ready'`,
|
|
131
|
+
* `'needs_wallet_registration'`, `'needs_source_currency'`, or
|
|
132
|
+
* `'retryable_failure'` once an approved read settles.
|
|
118
133
|
*/
|
|
119
134
|
export const VBA_AUTORAMP_STATUSES = [
|
|
120
135
|
'not_ready',
|
|
121
136
|
'in_progress',
|
|
137
|
+
'needs_wallet_registration',
|
|
138
|
+
'needs_source_currency',
|
|
122
139
|
'ready',
|
|
123
140
|
'retryable_failure',
|
|
124
141
|
];
|
|
@@ -143,6 +160,98 @@ function toVbaKycStatus(value) {
|
|
|
143
160
|
}
|
|
144
161
|
return 'none';
|
|
145
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Maps a KYC session onto the snapshot vocabulary.
|
|
165
|
+
*
|
|
166
|
+
* Relay approval (`kycStatus: 'approved'`) counts even when `finalStatus` is
|
|
167
|
+
* still `pending`. A non-approved session whose provider flow was submitted,
|
|
168
|
+
* or whose status is pending, is `pending`.
|
|
169
|
+
*
|
|
170
|
+
* @param session - Persisted or freshly fetched KYC session.
|
|
171
|
+
* @param providerFlowStatus - Durable provider-flow outcome.
|
|
172
|
+
* @returns The snapshot KYC status.
|
|
173
|
+
*/
|
|
174
|
+
function resolveVbaKycStatus(session, providerFlowStatus) {
|
|
175
|
+
if (session.finalStatus === 'approved' || session.kycStatus === 'approved') {
|
|
176
|
+
return 'approved';
|
|
177
|
+
}
|
|
178
|
+
const mapped = toVbaKycStatus(session.finalStatus);
|
|
179
|
+
if (mapped === 'rejected' || mapped === 'retry') {
|
|
180
|
+
return mapped;
|
|
181
|
+
}
|
|
182
|
+
if (session.finalStatus === 'pending' ||
|
|
183
|
+
session.kycStatus === 'pending' ||
|
|
184
|
+
providerFlowStatus === 'submitted') {
|
|
185
|
+
return 'pending';
|
|
186
|
+
}
|
|
187
|
+
return mapped;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Idempotency key for a standing fiat to mUSD autoramp on Monad.
|
|
191
|
+
*
|
|
192
|
+
* The source fiat, customer, and wallet keep retries of one create on the
|
|
193
|
+
* same key (`brl-musd-monad` versus `usd-musd-monad`). Rejected or cancelled
|
|
194
|
+
* route ids are appended, sorted, so a replacement create is a new key and
|
|
195
|
+
* MoonPay does not replay the terminal route.
|
|
196
|
+
*
|
|
197
|
+
* @param sourceCurrencyCode - Fiat code from the create body. Trimmed and lowercased here.
|
|
198
|
+
* @param customerId - MoonPay customer id.
|
|
199
|
+
* @param walletAddress - Money Account address that receives mUSD.
|
|
200
|
+
* @param terminalAutorampIds - Ids of rejected or cancelled routes for this wallet.
|
|
201
|
+
* @returns Idempotency-Key value.
|
|
202
|
+
*/
|
|
203
|
+
function buildMusdAutorampIdempotencyKey(sourceCurrencyCode, customerId, walletAddress, terminalAutorampIds) {
|
|
204
|
+
const fiatPrefix = sourceCurrencyCode.trim().toLowerCase();
|
|
205
|
+
const base = `${fiatPrefix}-musd-monad:${customerId}:${walletAddress.trim().toLowerCase()}`;
|
|
206
|
+
if (terminalAutorampIds.length === 0) {
|
|
207
|
+
return base;
|
|
208
|
+
}
|
|
209
|
+
return `${base}:${[...terminalAutorampIds].sort().join(':')}`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Reads the first fiat `source_currencies` code from an autoramp create body.
|
|
213
|
+
*
|
|
214
|
+
* @param request - Caller-supplied create payload.
|
|
215
|
+
* @returns Lowercased fiat code, or `null` when the body has none.
|
|
216
|
+
*/
|
|
217
|
+
function readCreateAutorampFiatCode(request) {
|
|
218
|
+
const sourceCurrencies = request.source_currencies;
|
|
219
|
+
if (!Array.isArray(sourceCurrencies)) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
for (const currency of sourceCurrencies) {
|
|
223
|
+
if (!currency || typeof currency !== 'object') {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const record = currency;
|
|
227
|
+
if (record.type !== 'Fiat' || typeof record.code !== 'string') {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const code = record.code.trim().toLowerCase();
|
|
231
|
+
if (code.length > 0) {
|
|
232
|
+
return code;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Reads the destination wallet from an autoramp create body.
|
|
239
|
+
*
|
|
240
|
+
* @param request - Caller-supplied create payload.
|
|
241
|
+
* @returns Trimmed recipient address, or `null` when it is missing.
|
|
242
|
+
*/
|
|
243
|
+
function readCreateAutorampWalletAddress(request) {
|
|
244
|
+
const recipient = request.recipient_account;
|
|
245
|
+
if (!recipient || typeof recipient !== 'object') {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const { address } = recipient;
|
|
249
|
+
if (typeof address !== 'string') {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const trimmed = address.trim();
|
|
253
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
254
|
+
}
|
|
146
255
|
/**
|
|
147
256
|
* Distinguishes an already-materialized {@link AutorampAccount} from the
|
|
148
257
|
* create-fields shape accepted by {@link RampsController.addAutoramp}.
|
|
@@ -271,6 +380,18 @@ const rampsControllerMetadata = {
|
|
|
271
380
|
includeInStateLogs: true,
|
|
272
381
|
usedInUi: true,
|
|
273
382
|
},
|
|
383
|
+
moneyAccountWalletRegistrations: {
|
|
384
|
+
persist: true,
|
|
385
|
+
includeInDebugSnapshot: true,
|
|
386
|
+
includeInStateLogs: true,
|
|
387
|
+
usedInUi: true,
|
|
388
|
+
},
|
|
389
|
+
vbaAutorampListFetchedAt: {
|
|
390
|
+
persist: true,
|
|
391
|
+
includeInDebugSnapshot: true,
|
|
392
|
+
includeInStateLogs: false,
|
|
393
|
+
usedInUi: true,
|
|
394
|
+
},
|
|
274
395
|
providerAutoSelected: {
|
|
275
396
|
persist: true,
|
|
276
397
|
includeInDebugSnapshot: true,
|
|
@@ -321,6 +442,8 @@ export function getDefaultRampsControllerState() {
|
|
|
321
442
|
},
|
|
322
443
|
orders: [],
|
|
323
444
|
autoramps: [],
|
|
445
|
+
moneyAccountWalletRegistrations: [],
|
|
446
|
+
vbaAutorampListFetchedAt: null,
|
|
324
447
|
providerAutoSelected: false,
|
|
325
448
|
};
|
|
326
449
|
}
|
|
@@ -2371,17 +2494,31 @@ export class RampsController extends BaseController {
|
|
|
2371
2494
|
*
|
|
2372
2495
|
* The vendor `customer_id` is resolved via
|
|
2373
2496
|
* {@link RampsController.resolveAutorampCustomerId} and injected into the
|
|
2374
|
-
* request (any caller-supplied `customer_id` is overwritten).
|
|
2375
|
-
*
|
|
2376
|
-
*
|
|
2377
|
-
*
|
|
2497
|
+
* request (any caller-supplied `customer_id` is overwritten). The caller
|
|
2498
|
+
* supplies the fiat body. When that body names a source fiat and a
|
|
2499
|
+
* recipient wallet, the idempotency key is derived from that fiat
|
|
2500
|
+
* (`brl-musd-monad` or `usd-musd-monad`), the customer, the wallet, and
|
|
2501
|
+
* rejected or cancelled autoramp ids. A caller-supplied key is used only
|
|
2502
|
+
* when the body has no source fiat or recipient address.
|
|
2503
|
+
*
|
|
2504
|
+
* @param request - CreateAutoramp payload, including the source fiat.
|
|
2505
|
+
* @param options - Optional idempotency key forwarded when the body does
|
|
2506
|
+
* not identify a fiat route.
|
|
2378
2507
|
* @param options.idempotencyKey - Value sent as `Idempotency-Key`.
|
|
2379
2508
|
* @returns The created/updated local {@link AutorampAccount}.
|
|
2380
2509
|
*/
|
|
2381
2510
|
async createAutoramp(request, options = {}) {
|
|
2382
2511
|
const customerId = await this.resolveAutorampCustomerId();
|
|
2512
|
+
const fiatCode = readCreateAutorampFiatCode(request);
|
|
2513
|
+
const walletAddress = readCreateAutorampWalletAddress(request);
|
|
2514
|
+
const requestOptions = fiatCode && walletAddress
|
|
2515
|
+
? {
|
|
2516
|
+
...options,
|
|
2517
|
+
idempotencyKey: buildMusdAutorampIdempotencyKey(fiatCode, customerId, walletAddress, this.#terminalAutorampIdsForWallet(walletAddress)),
|
|
2518
|
+
}
|
|
2519
|
+
: options;
|
|
2383
2520
|
const body = { ...request, customer_id: customerId };
|
|
2384
|
-
const remote = await this.messenger.call('NeoBankService:createAutoramp', body,
|
|
2521
|
+
const remote = await this.messenger.call('NeoBankService:createAutoramp', body, requestOptions);
|
|
2385
2522
|
return this.#applyAutorampRemoteSnapshot(remote);
|
|
2386
2523
|
}
|
|
2387
2524
|
/**
|
|
@@ -2479,7 +2616,7 @@ export class RampsController extends BaseController {
|
|
|
2479
2616
|
};
|
|
2480
2617
|
const existingResult = await resolveLookup();
|
|
2481
2618
|
if (existingResult) {
|
|
2482
|
-
return existingResult;
|
|
2619
|
+
return this.#finishMoneyAccountWalletRegistration(address, existingResult);
|
|
2483
2620
|
}
|
|
2484
2621
|
let idempotencyKey = createIdempotencyKey();
|
|
2485
2622
|
let lastMessage;
|
|
@@ -2514,7 +2651,7 @@ export class RampsController extends BaseController {
|
|
|
2514
2651
|
idempotencyKey,
|
|
2515
2652
|
});
|
|
2516
2653
|
machine = transitionWalletRegistration(machine, { type: 'SUBMIT_OK' });
|
|
2517
|
-
return result;
|
|
2654
|
+
return this.#finishMoneyAccountWalletRegistration(address, result);
|
|
2518
2655
|
}
|
|
2519
2656
|
catch (error) {
|
|
2520
2657
|
if (!(error instanceof WalletRegistrationError)) {
|
|
@@ -2557,7 +2694,7 @@ export class RampsController extends BaseController {
|
|
|
2557
2694
|
machine.status === 'checkThenRetry') {
|
|
2558
2695
|
const reconciledResult = await resolveLookup();
|
|
2559
2696
|
if (reconciledResult) {
|
|
2560
|
-
return reconciledResult;
|
|
2697
|
+
return this.#finishMoneyAccountWalletRegistration(address, reconciledResult);
|
|
2561
2698
|
}
|
|
2562
2699
|
}
|
|
2563
2700
|
if (machine.status !== 'signing') {
|
|
@@ -2567,23 +2704,37 @@ export class RampsController extends BaseController {
|
|
|
2567
2704
|
}
|
|
2568
2705
|
}
|
|
2569
2706
|
/**
|
|
2570
|
-
*
|
|
2571
|
-
* Money Account (wallet registration + autoramp). Hosts map the returned
|
|
2707
|
+
* Reads VBA onboarding facts. Hosts map the returned
|
|
2572
2708
|
* {@link VbaOnboardingSnapshot} onto their own funnel; this method does not
|
|
2573
2709
|
* name screens.
|
|
2574
2710
|
*
|
|
2575
|
-
*
|
|
2576
|
-
*
|
|
2711
|
+
* The read uses the persisted KYC session. `refreshKyc` performs one
|
|
2712
|
+
* session-status GET when a session id is already stored and `finalStatus`
|
|
2713
|
+
* is not terminal. `refreshAutoramps` lists autoramps when KYC is approved,
|
|
2714
|
+
* unless this wallet already has a usable route. It does not sign, post a
|
|
2715
|
+
* self-hosted address, create an autoramp, or start KYC polling.
|
|
2716
|
+
*
|
|
2717
|
+
* Overlapping calls share one run.
|
|
2577
2718
|
*
|
|
2578
2719
|
* @param params - VBA onboarding parameters.
|
|
2579
2720
|
* @param params.walletAddress - Monad Money Account wallet address.
|
|
2721
|
+
* @param params.refreshKyc - When true, fetch session status once for a
|
|
2722
|
+
* non-terminal persisted session. A failed GET keeps that session. Defaults
|
|
2723
|
+
* to false.
|
|
2724
|
+
* @param params.refreshAutoramps - When true, replace the autoramp cursor
|
|
2725
|
+
* from `GET /neobank/autoramps` unless a usable route is already stored.
|
|
2726
|
+
* Defaults to false.
|
|
2580
2727
|
* @returns Independent KYC and autoramp facts for the current customer.
|
|
2581
2728
|
*/
|
|
2582
|
-
async hydrateVbaOnboarding({ walletAddress, }) {
|
|
2729
|
+
async hydrateVbaOnboarding({ walletAddress, refreshKyc = false, refreshAutoramps = false, }) {
|
|
2583
2730
|
if (this.#vbaOnboardingHydrationPromise) {
|
|
2584
2731
|
return await this.#vbaOnboardingHydrationPromise;
|
|
2585
2732
|
}
|
|
2586
|
-
const hydrationPromise = this.#hydrateVbaOnboarding(
|
|
2733
|
+
const hydrationPromise = this.#hydrateVbaOnboarding({
|
|
2734
|
+
walletAddress,
|
|
2735
|
+
refreshKyc,
|
|
2736
|
+
refreshAutoramps,
|
|
2737
|
+
});
|
|
2587
2738
|
this.#vbaOnboardingHydrationPromise = hydrationPromise;
|
|
2588
2739
|
try {
|
|
2589
2740
|
return await hydrationPromise;
|
|
@@ -2594,46 +2745,39 @@ export class RampsController extends BaseController {
|
|
|
2594
2745
|
}
|
|
2595
2746
|
}
|
|
2596
2747
|
}
|
|
2597
|
-
async #hydrateVbaOnboarding(walletAddress) {
|
|
2598
|
-
|
|
2599
|
-
//
|
|
2600
|
-
//
|
|
2601
|
-
//
|
|
2602
|
-
//
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
session = this.messenger.call('KycController:refreshSessionStatus');
|
|
2612
|
-
sessionFromCache = true;
|
|
2748
|
+
async #hydrateVbaOnboarding({ walletAddress, refreshKyc, refreshAutoramps, }) {
|
|
2749
|
+
let session = this.messenger.call('KycController:readSessionStatus');
|
|
2750
|
+
// A persisted session can outlive the identity that created it. Reusing it
|
|
2751
|
+
// makes session-scoped calls fail closed. Discard it before any fetch, with
|
|
2752
|
+
// the registration rows and autoramp cursor it left behind, since neither
|
|
2753
|
+
// is keyed by profile.
|
|
2754
|
+
if (session && !(await this.#isVbaSessionOwnedByCurrentProfile(session))) {
|
|
2755
|
+
this.messenger.call('KycController:clearState');
|
|
2756
|
+
this.update((state) => {
|
|
2757
|
+
state.moneyAccountWalletRegistrations = [];
|
|
2758
|
+
state.autoramps = [];
|
|
2759
|
+
state.vbaAutorampListFetchedAt = null;
|
|
2760
|
+
});
|
|
2761
|
+
return { ...EMPTY_VBA_ONBOARDING_SNAPSHOT };
|
|
2613
2762
|
}
|
|
2614
|
-
|
|
2763
|
+
if (refreshKyc &&
|
|
2764
|
+
session?.id &&
|
|
2765
|
+
!TERMINAL_VBA_KYC_FINAL_STATUSES.has(session.finalStatus)) {
|
|
2615
2766
|
try {
|
|
2616
|
-
session = await this.messenger.call('KycController:
|
|
2767
|
+
session = await this.messenger.call('KycController:fetchSessionStatusOnce', session.id);
|
|
2617
2768
|
}
|
|
2618
|
-
catch {
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2769
|
+
catch (error) {
|
|
2770
|
+
if (error instanceof HttpError && error.httpStatus === 404) {
|
|
2771
|
+
this.messenger.call('KycController:clearState');
|
|
2772
|
+
return { ...EMPTY_VBA_ONBOARDING_SNAPSHOT };
|
|
2773
|
+
}
|
|
2774
|
+
// The persisted session is already in hand. A transient status GET
|
|
2775
|
+
// must not reject the read.
|
|
2622
2776
|
}
|
|
2623
2777
|
}
|
|
2624
2778
|
if (!session) {
|
|
2625
2779
|
return { ...EMPTY_VBA_ONBOARDING_SNAPSHOT };
|
|
2626
2780
|
}
|
|
2627
|
-
// A persisted session can outlive the identity that created it — e.g. a new
|
|
2628
|
-
// wallet created over an install that still holds a previous customer's
|
|
2629
|
-
// session. Reusing it makes the backend reject every session-scoped call
|
|
2630
|
-
// (owner mismatch), dead-ending the user. Verify ownership up front against
|
|
2631
|
-
// the signed-in profile and, on a mismatch, discard the stale session.
|
|
2632
|
-
if (sessionFromCache &&
|
|
2633
|
-
!(await this.#isVbaSessionOwnedByCurrentProfile(session))) {
|
|
2634
|
-
this.messenger.call('KycController:clearState');
|
|
2635
|
-
return { ...EMPTY_VBA_ONBOARDING_SNAPSHOT };
|
|
2636
|
-
}
|
|
2637
2781
|
const vendorDisclaimersComplete = await this.messenger.call('KycController:hasCompletedVendorDisclaimers');
|
|
2638
2782
|
const sessionDisclaimersComplete = await this.messenger.call('KycController:hasCompletedSessionDisclaimers');
|
|
2639
2783
|
const providerFlowStatus = this.messenger.call('KycController:getProviderFlowStatus');
|
|
@@ -2642,15 +2786,7 @@ export class RampsController extends BaseController {
|
|
|
2642
2786
|
vendorDisclaimersComplete,
|
|
2643
2787
|
sessionDisclaimersComplete,
|
|
2644
2788
|
providerFlowStatus,
|
|
2645
|
-
|
|
2646
|
-
// is made, while the vendor-side `finalStatus` can lag at `'pending'` until
|
|
2647
|
-
// the vendor record is finalized (and in sandbox may never advance). Treat
|
|
2648
|
-
// either signal as approval so a relay-approved user is not stranded on the
|
|
2649
|
-
// pending screen. This mirrors the UKYC backend, whose own "already
|
|
2650
|
-
// approved" check is `finalStatus === 'approved' || kycStatus === 'approved'`.
|
|
2651
|
-
kycStatus: session.finalStatus === 'approved' || session.kycStatus === 'approved'
|
|
2652
|
-
? 'approved'
|
|
2653
|
-
: toVbaKycStatus(session.finalStatus),
|
|
2789
|
+
kycStatus: resolveVbaKycStatus(session, providerFlowStatus),
|
|
2654
2790
|
autorampStatus: 'not_ready',
|
|
2655
2791
|
};
|
|
2656
2792
|
if (snapshot.kycStatus !== 'approved') {
|
|
@@ -2659,38 +2795,98 @@ export class RampsController extends BaseController {
|
|
|
2659
2795
|
if (!walletAddress.trim()) {
|
|
2660
2796
|
throw new Error('walletAddress is required after KYC acceptance.');
|
|
2661
2797
|
}
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
const registration = await this.registerMoneyAccountWallet({
|
|
2669
|
-
address: walletAddress,
|
|
2670
|
-
});
|
|
2671
|
-
if (registration.type === 'lookupUnavailable') {
|
|
2672
|
-
throw registration.error;
|
|
2798
|
+
if (this.#hasUsableAutorampForWallet(walletAddress)) {
|
|
2799
|
+
return { ...snapshot, autorampStatus: 'ready' };
|
|
2800
|
+
}
|
|
2801
|
+
if (refreshAutoramps) {
|
|
2802
|
+
try {
|
|
2803
|
+
await this.#replaceAutorampCursorFromList();
|
|
2673
2804
|
}
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
for (const autoramp of remoteAutoramps) {
|
|
2677
|
-
this.#applyAutorampRemoteSnapshot(autoramp);
|
|
2805
|
+
catch {
|
|
2806
|
+
return { ...snapshot, autorampStatus: 'retryable_failure' };
|
|
2678
2807
|
}
|
|
2679
|
-
this
|
|
2680
|
-
|
|
2681
|
-
});
|
|
2682
|
-
const normalizedWalletAddress = walletAddress.toLowerCase();
|
|
2683
|
-
const hasUsableAutoramp = this.state.autoramps.some((autoramp) => autoramp.walletAddress.toLowerCase() === normalizedWalletAddress &&
|
|
2684
|
-
autoramp.status !== AutorampStatus.Rejected &&
|
|
2685
|
-
autoramp.status !== AutorampStatus.Cancelled);
|
|
2686
|
-
if (!hasUsableAutoramp) {
|
|
2687
|
-
await this.createAutoramp({});
|
|
2808
|
+
if (this.#hasUsableAutorampForWallet(walletAddress)) {
|
|
2809
|
+
return { ...snapshot, autorampStatus: 'ready' };
|
|
2688
2810
|
}
|
|
2689
2811
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2812
|
+
const registration = this.state.moneyAccountWalletRegistrations.find((row) => row.walletAddress.trim().toLowerCase() ===
|
|
2813
|
+
walletAddress.trim().toLowerCase());
|
|
2814
|
+
if (!registration) {
|
|
2815
|
+
return { ...snapshot, autorampStatus: 'needs_wallet_registration' };
|
|
2692
2816
|
}
|
|
2693
|
-
return { ...snapshot, autorampStatus: '
|
|
2817
|
+
return { ...snapshot, autorampStatus: 'needs_source_currency' };
|
|
2818
|
+
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Writes the registration row after a successful register outcome.
|
|
2821
|
+
* `lookupUnavailable` writes nothing.
|
|
2822
|
+
*
|
|
2823
|
+
* @param address - Money Account wallet address.
|
|
2824
|
+
* @param result - Registration outcome.
|
|
2825
|
+
* @returns The same outcome.
|
|
2826
|
+
*/
|
|
2827
|
+
#finishMoneyAccountWalletRegistration(address, result) {
|
|
2828
|
+
if (result.type === 'lookupUnavailable') {
|
|
2829
|
+
return result;
|
|
2830
|
+
}
|
|
2831
|
+
const normalized = address.trim().toLowerCase();
|
|
2832
|
+
const status = result.type === 'registeredDisabled' ? 'disabled' : 'active';
|
|
2833
|
+
const row = {
|
|
2834
|
+
walletAddress: normalized,
|
|
2835
|
+
status,
|
|
2836
|
+
updatedAt: Date.now(),
|
|
2837
|
+
};
|
|
2838
|
+
this.update((state) => {
|
|
2839
|
+
const index = state.moneyAccountWalletRegistrations.findIndex((existing) => existing.walletAddress.trim().toLowerCase() === normalized);
|
|
2840
|
+
if (index === -1) {
|
|
2841
|
+
state.moneyAccountWalletRegistrations.push(row);
|
|
2842
|
+
}
|
|
2843
|
+
else {
|
|
2844
|
+
state.moneyAccountWalletRegistrations[index] = row;
|
|
2845
|
+
}
|
|
2846
|
+
});
|
|
2847
|
+
return result;
|
|
2848
|
+
}
|
|
2849
|
+
/**
|
|
2850
|
+
* Ids of rejected or cancelled autoramps for a wallet, used in create keys.
|
|
2851
|
+
*
|
|
2852
|
+
* @param walletAddress - Destination wallet address.
|
|
2853
|
+
* @returns Terminal autoramp ids for that wallet.
|
|
2854
|
+
*/
|
|
2855
|
+
#terminalAutorampIdsForWallet(walletAddress) {
|
|
2856
|
+
const normalized = walletAddress.trim().toLowerCase();
|
|
2857
|
+
return this.state.autoramps
|
|
2858
|
+
.filter((autoramp) => autoramp.walletAddress.toLowerCase() === normalized &&
|
|
2859
|
+
(autoramp.status === AutorampStatus.Rejected ||
|
|
2860
|
+
autoramp.status === AutorampStatus.Cancelled))
|
|
2861
|
+
.map((autoramp) => autoramp.id);
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* Whether this wallet has an autoramp that is not Rejected or Cancelled.
|
|
2865
|
+
*
|
|
2866
|
+
* @param walletAddress - Destination wallet address.
|
|
2867
|
+
* @returns Whether a usable route is already on the cursor.
|
|
2868
|
+
*/
|
|
2869
|
+
#hasUsableAutorampForWallet(walletAddress) {
|
|
2870
|
+
const normalized = walletAddress.trim().toLowerCase();
|
|
2871
|
+
return this.state.autoramps.some((autoramp) => autoramp.walletAddress.toLowerCase() === normalized &&
|
|
2872
|
+
autoramp.status !== AutorampStatus.Rejected &&
|
|
2873
|
+
autoramp.status !== AutorampStatus.Cancelled);
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Replaces the autoramp cursor from `GET /neobank/autoramps` and records
|
|
2877
|
+
* `vbaAutorampListFetchedAt` only after that GET succeeds.
|
|
2878
|
+
*/
|
|
2879
|
+
async #replaceAutorampCursorFromList() {
|
|
2880
|
+
const customerId = await this.resolveAutorampCustomerId();
|
|
2881
|
+
const remoteAutoramps = await this.messenger.call('NeoBankService:getAutoramps', { customerId });
|
|
2882
|
+
const remoteAutorampIds = new Set(remoteAutoramps.map((autoramp) => autoramp.id));
|
|
2883
|
+
for (const autoramp of remoteAutoramps) {
|
|
2884
|
+
this.#applyAutorampRemoteSnapshot(autoramp);
|
|
2885
|
+
}
|
|
2886
|
+
this.update((state) => {
|
|
2887
|
+
state.autoramps = state.autoramps.filter((autoramp) => remoteAutorampIds.has(autoramp.id));
|
|
2888
|
+
state.vbaAutorampListFetchedAt = Date.now();
|
|
2889
|
+
});
|
|
2694
2890
|
}
|
|
2695
2891
|
/**
|
|
2696
2892
|
* Whether a persisted KYC session belongs to the currently signed-in profile.
|