@global-torque/invest-core 0.2.2
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 +14 -0
- package/LICENSE +21 -0
- package/NOTICE.md +11 -0
- package/README.md +76 -0
- package/SECURITY.md +9 -0
- package/SUPPORT.md +6 -0
- package/dist/node/app/config.js +173 -0
- package/dist/node/helpers/text.js +37 -0
- package/dist/node/markdown/tableWrap.js +23 -0
- package/package.json +113 -0
- package/src/accreditation/status.ts +100 -0
- package/src/analytics/__tests__/analyticsBody.test.ts +50 -0
- package/src/analytics/analyticsBody.ts +270 -0
- package/src/app/config.test.ts +79 -0
- package/src/app/config.ts +329 -0
- package/src/decimal/__tests__/canonicalDecimal.test.ts +58 -0
- package/src/decimal/canonicalDecimal.ts +154 -0
- package/src/evm/__tests__/walletInfo.test.ts +488 -0
- package/src/evm/walletInfo.ts +625 -0
- package/src/filer/__tests__/documentFormatter.test.ts +208 -0
- package/src/filer/__tests__/publicImage.test.ts +42 -0
- package/src/filer/documentFormatter.ts +195 -0
- package/src/filer/publicImage.ts +120 -0
- package/src/form-validation/__tests__/general.test.ts +78 -0
- package/src/form-validation/__tests__/investment.test.ts +100 -0
- package/src/form-validation/ajv.ts +109 -0
- package/src/form-validation/constants.ts +22 -0
- package/src/form-validation/general.ts +114 -0
- package/src/form-validation/index.ts +5 -0
- package/src/form-validation/investment.ts +65 -0
- package/src/form-validation/rules.ts +35 -0
- package/src/formatting/__tests__/buildInfo.test.ts +19 -0
- package/src/formatting/__tests__/dateTime.test.ts +24 -0
- package/src/formatting/__tests__/display.test.ts +30 -0
- package/src/formatting/buildInfo.ts +31 -0
- package/src/formatting/dateTime.ts +43 -0
- package/src/formatting/display.ts +24 -0
- package/src/helpers/arrays.ts +11 -0
- package/src/helpers/currency.ts +19 -0
- package/src/helpers/formatters/formatToDate.ts +47 -0
- package/src/helpers/formatters/formatToNumber.ts +39 -0
- package/src/helpers/formatters/formatToPhone.ts +13 -0
- package/src/helpers/general.ts +164 -0
- package/src/helpers/model.ts +87 -0
- package/src/helpers/numberFormatter.ts +4 -0
- package/src/helpers/text.ts +51 -0
- package/src/index.ts +22 -0
- package/src/investment/__tests__/status.test.ts +59 -0
- package/src/investment/rawAmount.test.ts +23 -0
- package/src/investment/rawAmount.ts +81 -0
- package/src/investment/status.ts +56 -0
- package/src/kyc/__tests__/kycAlert.formatter.test.ts +47 -0
- package/src/kyc/__tests__/kycAlert.test.ts +47 -0
- package/src/kyc/__tests__/thirdPartyScreen.test.ts +16 -0
- package/src/kyc/kycAlert.ts +47 -0
- package/src/kyc/status.ts +109 -0
- package/src/kyc/thirdPartyScreen.ts +28 -0
- package/src/markdown/tableWrap.ts +29 -0
- package/src/notifications/shareFields.ts +15 -0
- package/src/offer/__tests__/metrics.test.ts +67 -0
- package/src/offer/formatter.ts +559 -0
- package/src/offer/metrics.ts +83 -0
- package/src/onboarding/__tests__/intents.test.ts +83 -0
- package/src/onboarding/intents.ts +128 -0
- package/src/profiles/__tests__/formatting.test.ts +24 -0
- package/src/profiles/avatarInitial.ts +5 -0
- package/src/profiles/formatting.ts +38 -0
- package/src/repository/__tests__/formatterCache.test.ts +45 -0
- package/src/repository/formatterCache.ts +56 -0
- package/src/wallet/__tests__/auth.test.ts +102 -0
- package/src/wallet/__tests__/operationPresentation.test.ts +94 -0
- package/src/wallet/__tests__/setupError.test.ts +32 -0
- package/src/wallet/auth.ts +491 -0
- package/src/wallet/operationPresentation.ts +106 -0
- package/src/wallet/setupError.ts +50 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
export const EvmWalletStatusTypes = {
|
|
2
|
+
created: 'created',
|
|
3
|
+
verified: 'verified',
|
|
4
|
+
error: 'error',
|
|
5
|
+
error_retry: 'error_retry',
|
|
6
|
+
error_document: 'error_document',
|
|
7
|
+
error_pending: 'error_pending',
|
|
8
|
+
error_suspended: 'error_suspended',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type EvmWalletStatusTypes =
|
|
12
|
+
(typeof EvmWalletStatusTypes)[keyof typeof EvmWalletStatusTypes];
|
|
13
|
+
|
|
14
|
+
export type IEvmWalletBalancesMap = Record<string, {
|
|
15
|
+
id?: number | string;
|
|
16
|
+
offer_id?: number;
|
|
17
|
+
address: string;
|
|
18
|
+
amount: number | string;
|
|
19
|
+
symbol: string;
|
|
20
|
+
asset?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
icon?: string;
|
|
23
|
+
price_per_usd?: number | string;
|
|
24
|
+
price_per_usd_raw?: string;
|
|
25
|
+
amount_usd?: number;
|
|
26
|
+
chain?: string;
|
|
27
|
+
token_decimals?: number;
|
|
28
|
+
token_standard?: string;
|
|
29
|
+
is_native?: boolean;
|
|
30
|
+
balance_type?: 'rwa_asset' | 'tradable_crypto';
|
|
31
|
+
exchange_eligible?: boolean;
|
|
32
|
+
exchange_ineligible_reason?: string;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
export interface IEvmWalletBalances {
|
|
36
|
+
id?: number;
|
|
37
|
+
offer_id?: number;
|
|
38
|
+
asset?: string;
|
|
39
|
+
address: string;
|
|
40
|
+
amount: number;
|
|
41
|
+
symbol: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
icon?: string;
|
|
44
|
+
price_per_usd?: number | string;
|
|
45
|
+
price_per_usd_raw?: string;
|
|
46
|
+
amount_usd?: number;
|
|
47
|
+
chain?: string;
|
|
48
|
+
token_decimals?: number;
|
|
49
|
+
token_standard?: string;
|
|
50
|
+
is_native?: boolean;
|
|
51
|
+
balance_type?: 'rwa_asset' | 'tradable_crypto';
|
|
52
|
+
exchange_eligible?: boolean;
|
|
53
|
+
exchange_ineligible_reason?: string;
|
|
54
|
+
tokenValue?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type IEvmWalletBalancesInput = IEvmWalletBalancesMap | IEvmWalletBalances[];
|
|
58
|
+
|
|
59
|
+
export interface IEvmWalletChainAccount {
|
|
60
|
+
chain: string;
|
|
61
|
+
wallet_address: string;
|
|
62
|
+
chain_account_status?: string;
|
|
63
|
+
exchange_payout_token_address?: string;
|
|
64
|
+
exchange_payout_token_symbol?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface IEvmWalletDepositInstructions {
|
|
68
|
+
chain?: string;
|
|
69
|
+
address?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface IEvmWalletBalanceSummaryResponse {
|
|
73
|
+
amount_usd?: number | string;
|
|
74
|
+
token_count?: number | string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface IEvmWalletDataResponse<TTransaction = unknown> {
|
|
78
|
+
id: number;
|
|
79
|
+
wallet_id?: number;
|
|
80
|
+
profile_id?: number;
|
|
81
|
+
status: EvmWalletStatusTypes;
|
|
82
|
+
provider_name?: string;
|
|
83
|
+
turnkey_org_id?: string;
|
|
84
|
+
turnkey_sub_org_id?: string;
|
|
85
|
+
turnkey_user_id?: string;
|
|
86
|
+
turnkey_wallet_id?: string;
|
|
87
|
+
turnkey_account_id?: string;
|
|
88
|
+
balance: string;
|
|
89
|
+
inc_balance: number;
|
|
90
|
+
out_balance: number;
|
|
91
|
+
address: string;
|
|
92
|
+
chain?: string;
|
|
93
|
+
exchange_payout_token_address?: string;
|
|
94
|
+
exchange_payout_token_symbol?: string;
|
|
95
|
+
deposit_instructions?: IEvmWalletDepositInstructions;
|
|
96
|
+
chains?: IEvmWalletChainAccount[];
|
|
97
|
+
balances: IEvmWalletBalancesMap;
|
|
98
|
+
tradable_crypto_balance?: IEvmWalletBalanceSummaryResponse;
|
|
99
|
+
rwa_asset_balance?: IEvmWalletBalanceSummaryResponse;
|
|
100
|
+
transactions: TTransaction[];
|
|
101
|
+
created_at: string;
|
|
102
|
+
updated_at: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type IEvmWalletDataForFormatter<TTransaction = unknown> =
|
|
106
|
+
Omit<IEvmWalletDataResponse<TTransaction>, 'balances'> & {
|
|
107
|
+
balances?: IEvmWalletBalancesInput;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const EVM_WALLET_STABLECOIN_SYMBOLS = [
|
|
111
|
+
'USDC',
|
|
112
|
+
'USDT',
|
|
113
|
+
'DAI',
|
|
114
|
+
'BUSD',
|
|
115
|
+
'TUSD',
|
|
116
|
+
'USDP',
|
|
117
|
+
'FRAX',
|
|
118
|
+
'LUSD',
|
|
119
|
+
'SUSD',
|
|
120
|
+
'GUSD',
|
|
121
|
+
] as const;
|
|
122
|
+
const EVM_WALLET_STABLECOIN_SYMBOL_SET = new Set<string>(EVM_WALLET_STABLECOIN_SYMBOLS);
|
|
123
|
+
|
|
124
|
+
export interface EvmWalletBalanceTotalsBalance {
|
|
125
|
+
id?: number | string;
|
|
126
|
+
address?: string;
|
|
127
|
+
asset?: string;
|
|
128
|
+
name?: string;
|
|
129
|
+
icon?: string;
|
|
130
|
+
amount?: number | string;
|
|
131
|
+
balance?: number | string;
|
|
132
|
+
symbol?: string;
|
|
133
|
+
price_per_usd?: number | string;
|
|
134
|
+
amount_usd?: number | string;
|
|
135
|
+
chain?: string;
|
|
136
|
+
token_decimals?: number | string;
|
|
137
|
+
token_standard?: string;
|
|
138
|
+
is_native?: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type EvmWalletBalanceTotalsBalances =
|
|
142
|
+
| Record<string, EvmWalletBalanceTotalsBalance>
|
|
143
|
+
| EvmWalletBalanceTotalsBalance[];
|
|
144
|
+
|
|
145
|
+
export interface EvmWalletBalanceTotalsInput {
|
|
146
|
+
balances?: EvmWalletBalanceTotalsBalances;
|
|
147
|
+
exchange_payout_token_address?: string;
|
|
148
|
+
exchange_payout_token_symbol?: string;
|
|
149
|
+
tradable_crypto_balance?: IEvmWalletBalanceSummaryResponse;
|
|
150
|
+
rwa_asset_balance?: IEvmWalletBalanceSummaryResponse;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface IEvmWalletChainAccountResponse {
|
|
154
|
+
chain?: string;
|
|
155
|
+
wallet_address?: string;
|
|
156
|
+
chain_account_status?: string;
|
|
157
|
+
exchange_payout_token_address?: string;
|
|
158
|
+
exchange_payout_token_symbol?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface IEvmWalletInfoStatusResponse {
|
|
162
|
+
profile_id?: number;
|
|
163
|
+
wallet_id?: number;
|
|
164
|
+
wallet_status?: string;
|
|
165
|
+
provider_name?: string;
|
|
166
|
+
turnkey_org_id?: string;
|
|
167
|
+
turnkey_sub_org_id?: string;
|
|
168
|
+
turnkey_user_id?: string;
|
|
169
|
+
turnkey_wallet_id?: string;
|
|
170
|
+
turnkey_account_id?: string;
|
|
171
|
+
chain?: string;
|
|
172
|
+
wallet_address?: string;
|
|
173
|
+
chain_account_status?: string;
|
|
174
|
+
exchange_payout_token_address?: string;
|
|
175
|
+
exchange_payout_token_symbol?: string;
|
|
176
|
+
chains?: IEvmWalletChainAccountResponse[];
|
|
177
|
+
deposit_instructions?: {
|
|
178
|
+
chain?: string;
|
|
179
|
+
address?: string;
|
|
180
|
+
};
|
|
181
|
+
balances?: IEvmWalletInfoBalanceResponse[] | Record<string, IEvmWalletInfoBalanceResponse>;
|
|
182
|
+
tradable_crypto_balance?: IEvmWalletBalanceSummaryResponse;
|
|
183
|
+
rwa_asset_balance?: IEvmWalletBalanceSummaryResponse;
|
|
184
|
+
created_at?: string;
|
|
185
|
+
updated_at?: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export type IEvmWalletInfoApiResponse<TTransaction = unknown> =
|
|
189
|
+
| IEvmWalletDataResponse<TTransaction>
|
|
190
|
+
| IEvmWalletInfoStatusResponse;
|
|
191
|
+
|
|
192
|
+
export interface NormalizeEvmWalletInfoOptions {
|
|
193
|
+
stableCoinAddress?: string;
|
|
194
|
+
stableCoinSymbol?: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface IEvmWalletInfoBalanceResponse {
|
|
198
|
+
id?: number | string;
|
|
199
|
+
offer_id?: number | string;
|
|
200
|
+
asset?: string;
|
|
201
|
+
asset_name?: string;
|
|
202
|
+
asset_ticker?: string;
|
|
203
|
+
asset_image?: string;
|
|
204
|
+
symbol?: string;
|
|
205
|
+
address?: string;
|
|
206
|
+
amount?: number | string;
|
|
207
|
+
balance?: number | string;
|
|
208
|
+
name?: string;
|
|
209
|
+
icon?: string;
|
|
210
|
+
price_per_usd?: number | string;
|
|
211
|
+
price_usd?: number | string;
|
|
212
|
+
amount_usd?: number | string;
|
|
213
|
+
token_address?: string;
|
|
214
|
+
token_name?: string;
|
|
215
|
+
token_symbol?: string;
|
|
216
|
+
token_logo?: string;
|
|
217
|
+
token_decimals?: number | string;
|
|
218
|
+
token_standard?: string;
|
|
219
|
+
chain?: string;
|
|
220
|
+
is_native?: boolean;
|
|
221
|
+
balance_type?: 'rwa_asset' | 'tradable_crypto';
|
|
222
|
+
exchange_eligible?: boolean;
|
|
223
|
+
exchange_ineligible_reason?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const KNOWN_WALLET_STATUSES = new Set<string>(Object.values(EvmWalletStatusTypes));
|
|
227
|
+
|
|
228
|
+
const firstString = (...values: unknown[]): string =>
|
|
229
|
+
values
|
|
230
|
+
.map((value) => String(value ?? '').trim())
|
|
231
|
+
.find(Boolean)
|
|
232
|
+
?? '';
|
|
233
|
+
|
|
234
|
+
const toOptionalNumber = (value: unknown): number | undefined => {
|
|
235
|
+
if (value == null || value === '') return undefined;
|
|
236
|
+
const next = Number(value);
|
|
237
|
+
return Number.isFinite(next) ? next : undefined;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const toNumberOrNull = (value: unknown): number | null => {
|
|
241
|
+
if (value == null || value === '') return null;
|
|
242
|
+
const next = Number(value);
|
|
243
|
+
return Number.isFinite(next) ? next : null;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const getWalletBalanceSummaryAmount = (
|
|
247
|
+
summary?: IEvmWalletBalanceSummaryResponse,
|
|
248
|
+
): number | null => toNumberOrNull(summary?.amount_usd);
|
|
249
|
+
|
|
250
|
+
const getWalletBalancesArray = (
|
|
251
|
+
balances?: EvmWalletBalanceTotalsBalances,
|
|
252
|
+
): EvmWalletBalanceTotalsBalance[] => {
|
|
253
|
+
if (!balances) return [];
|
|
254
|
+
return Array.isArray(balances) ? balances : Object.values(balances);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const isStablecoinSymbol = (symbol: unknown): boolean => (
|
|
258
|
+
EVM_WALLET_STABLECOIN_SYMBOL_SET.has(String(symbol || '').toUpperCase())
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
export const calculateEvmWalletFundingBalance = (
|
|
262
|
+
data: EvmWalletBalanceTotalsInput,
|
|
263
|
+
): number => {
|
|
264
|
+
const balances = getWalletBalancesArray(data.balances);
|
|
265
|
+
const payoutTokenAddress = String(data.exchange_payout_token_address ?? '').trim().toLowerCase();
|
|
266
|
+
if (payoutTokenAddress) {
|
|
267
|
+
const payoutToken = balances.find(
|
|
268
|
+
balance => String(balance.address ?? '').trim().toLowerCase() === payoutTokenAddress,
|
|
269
|
+
);
|
|
270
|
+
const amount = toNumberOrNull(payoutToken?.amount);
|
|
271
|
+
return amount ?? 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const payoutTokenSymbol = String(data.exchange_payout_token_symbol ?? '').trim().toUpperCase();
|
|
275
|
+
if (payoutTokenSymbol) {
|
|
276
|
+
const payoutToken = balances.find(
|
|
277
|
+
balance => String(balance.symbol ?? '').trim().toUpperCase() === payoutTokenSymbol,
|
|
278
|
+
);
|
|
279
|
+
const amount = toNumberOrNull(payoutToken?.amount);
|
|
280
|
+
return amount ?? 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const summaryAmount = getWalletBalanceSummaryAmount(data.tradable_crypto_balance);
|
|
284
|
+
if (summaryAmount !== null) return summaryAmount;
|
|
285
|
+
|
|
286
|
+
return balances.reduce((sum, balance) => {
|
|
287
|
+
const isStablecoin = isStablecoinSymbol(balance.symbol);
|
|
288
|
+
const amount = Number(balance.amount ?? 0);
|
|
289
|
+
return isStablecoin ? sum + amount : sum;
|
|
290
|
+
}, 0);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const calculateEvmWalletRwaValue = (
|
|
294
|
+
data: EvmWalletBalanceTotalsInput,
|
|
295
|
+
): number => {
|
|
296
|
+
const summaryAmount = getWalletBalanceSummaryAmount(data.rwa_asset_balance);
|
|
297
|
+
if (summaryAmount !== null) return summaryAmount;
|
|
298
|
+
|
|
299
|
+
return getWalletBalancesArray(data.balances).reduce((sum, balance) => {
|
|
300
|
+
const isStablecoin = isStablecoinSymbol(balance.symbol);
|
|
301
|
+
const amount = Number(balance.amount ?? 0);
|
|
302
|
+
const pricePerUsd = Number(balance.price_per_usd ?? 0);
|
|
303
|
+
const valueUsd = amount * pricePerUsd;
|
|
304
|
+
return !isStablecoin ? sum + valueUsd : sum;
|
|
305
|
+
}, 0);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const normalizeWalletInfoBalances = (
|
|
309
|
+
balances?: IEvmWalletInfoStatusResponse['balances'],
|
|
310
|
+
): IEvmWalletDataResponse['balances'] => {
|
|
311
|
+
const balancesArray = Array.isArray(balances)
|
|
312
|
+
? balances
|
|
313
|
+
: Object.values(balances ?? {});
|
|
314
|
+
|
|
315
|
+
return balancesArray.reduce<IEvmWalletDataResponse['balances']>((acc, balance, index) => {
|
|
316
|
+
const address = firstString(balance?.address, balance?.token_address);
|
|
317
|
+
const name = firstString(
|
|
318
|
+
balance?.name,
|
|
319
|
+
balance?.token_name,
|
|
320
|
+
balance?.asset_name,
|
|
321
|
+
balance?.asset,
|
|
322
|
+
balance?.symbol,
|
|
323
|
+
balance?.token_symbol,
|
|
324
|
+
balance?.asset_ticker,
|
|
325
|
+
);
|
|
326
|
+
const symbol = firstString(
|
|
327
|
+
balance?.symbol,
|
|
328
|
+
balance?.token_symbol,
|
|
329
|
+
balance?.asset_ticker,
|
|
330
|
+
balance?.asset,
|
|
331
|
+
name,
|
|
332
|
+
);
|
|
333
|
+
const asset = firstString(balance?.asset, balance?.asset_ticker, balance?.token_symbol, symbol);
|
|
334
|
+
const icon = firstString(balance?.icon, balance?.token_logo, balance?.asset_image);
|
|
335
|
+
const pricePerUsdRaw = firstString(balance?.price_per_usd, balance?.price_usd);
|
|
336
|
+
const pricePerUsd = toOptionalNumber(pricePerUsdRaw);
|
|
337
|
+
const amountUsd = toOptionalNumber(balance?.amount_usd);
|
|
338
|
+
const id = toOptionalNumber(balance?.id);
|
|
339
|
+
const offerId = toOptionalNumber(balance?.offer_id);
|
|
340
|
+
const tokenDecimals = toOptionalNumber(balance?.token_decimals);
|
|
341
|
+
const tokenStandard = firstString(balance?.token_standard);
|
|
342
|
+
const chain = firstString(balance?.chain);
|
|
343
|
+
const amount = balance?.amount ?? balance?.balance ?? 0;
|
|
344
|
+
|
|
345
|
+
if (!address && !symbol) {
|
|
346
|
+
return acc;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const key = address || symbol || String(index);
|
|
350
|
+
acc[key] = {
|
|
351
|
+
...(id !== undefined ? { id } : {}),
|
|
352
|
+
...(offerId !== undefined ? { offer_id: offerId } : {}),
|
|
353
|
+
asset: asset || undefined,
|
|
354
|
+
address,
|
|
355
|
+
amount,
|
|
356
|
+
symbol,
|
|
357
|
+
name: name || symbol || undefined,
|
|
358
|
+
...(icon ? { icon } : {}),
|
|
359
|
+
...(pricePerUsd !== undefined ? { price_per_usd: pricePerUsd } : {}),
|
|
360
|
+
...(pricePerUsdRaw ? { price_per_usd_raw: pricePerUsdRaw } : {}),
|
|
361
|
+
...(amountUsd !== undefined ? { amount_usd: amountUsd } : {}),
|
|
362
|
+
...(chain ? { chain } : {}),
|
|
363
|
+
...(tokenDecimals !== undefined ? { token_decimals: tokenDecimals } : {}),
|
|
364
|
+
...(tokenStandard ? { token_standard: tokenStandard } : {}),
|
|
365
|
+
...(typeof balance?.is_native === 'boolean' ? { is_native: balance.is_native } : {}),
|
|
366
|
+
...(balance?.balance_type ? { balance_type: balance.balance_type } : {}),
|
|
367
|
+
...(typeof balance?.exchange_eligible === 'boolean'
|
|
368
|
+
? { exchange_eligible: balance.exchange_eligible }
|
|
369
|
+
: {}),
|
|
370
|
+
...(firstString(balance?.exchange_ineligible_reason)
|
|
371
|
+
? { exchange_ineligible_reason: firstString(balance.exchange_ineligible_reason) }
|
|
372
|
+
: {}),
|
|
373
|
+
};
|
|
374
|
+
return acc;
|
|
375
|
+
}, {});
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const reconcileConfiguredStableCoinBalance = (
|
|
379
|
+
balances: IEvmWalletDataResponse['balances'],
|
|
380
|
+
options: NormalizeEvmWalletInfoOptions,
|
|
381
|
+
): {
|
|
382
|
+
balances: IEvmWalletDataResponse['balances'];
|
|
383
|
+
amountUsdAdjustment: number;
|
|
384
|
+
} => {
|
|
385
|
+
const stableCoinAddress = firstString(options.stableCoinAddress).toLowerCase();
|
|
386
|
+
const stableCoinSymbol = firstString(options.stableCoinSymbol, 'USDC').toUpperCase();
|
|
387
|
+
if (!stableCoinAddress || !stableCoinSymbol) {
|
|
388
|
+
return { balances, amountUsdAdjustment: 0 };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let amountUsdAdjustment = 0;
|
|
392
|
+
let matched = false;
|
|
393
|
+
const reconciledBalances = Object.entries(balances).reduce<IEvmWalletDataResponse['balances']>(
|
|
394
|
+
(result, [key, balance]) => {
|
|
395
|
+
if (String(balance.address ?? '').trim().toLowerCase() !== stableCoinAddress) {
|
|
396
|
+
result[key] = balance;
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
matched = true;
|
|
401
|
+
const amount = toNumberOrNull(balance.amount) ?? 0;
|
|
402
|
+
const previousAmountUsd = toNumberOrNull(balance.amount_usd) ?? 0;
|
|
403
|
+
amountUsdAdjustment += amount - previousAmountUsd;
|
|
404
|
+
|
|
405
|
+
result[key] = {
|
|
406
|
+
...balance,
|
|
407
|
+
asset: stableCoinSymbol,
|
|
408
|
+
symbol: stableCoinSymbol,
|
|
409
|
+
name: stableCoinSymbol,
|
|
410
|
+
price_per_usd: 1,
|
|
411
|
+
price_per_usd_raw: '1',
|
|
412
|
+
amount_usd: amount,
|
|
413
|
+
balance_type: 'tradable_crypto',
|
|
414
|
+
};
|
|
415
|
+
return result;
|
|
416
|
+
},
|
|
417
|
+
{},
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
balances: matched ? reconciledBalances : balances,
|
|
422
|
+
amountUsdAdjustment,
|
|
423
|
+
};
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const normalizeWalletBalanceSummary = (
|
|
427
|
+
summary?: IEvmWalletBalanceSummaryResponse | null,
|
|
428
|
+
): IEvmWalletBalanceSummaryResponse | undefined => {
|
|
429
|
+
if (!summary) return undefined;
|
|
430
|
+
|
|
431
|
+
const tokenCount = toOptionalNumber(summary.token_count);
|
|
432
|
+
return {
|
|
433
|
+
amount_usd: String(summary.amount_usd ?? '0'),
|
|
434
|
+
token_count: tokenCount ?? 0,
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const adjustWalletBalanceSummary = (
|
|
439
|
+
summary: IEvmWalletBalanceSummaryResponse | undefined,
|
|
440
|
+
amountUsdAdjustment: number,
|
|
441
|
+
): IEvmWalletBalanceSummaryResponse | undefined => {
|
|
442
|
+
if (!summary || !Number.isFinite(amountUsdAdjustment) || amountUsdAdjustment === 0) {
|
|
443
|
+
return summary;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const currentAmount = toNumberOrNull(summary.amount_usd) ?? 0;
|
|
447
|
+
return {
|
|
448
|
+
...summary,
|
|
449
|
+
amount_usd: String(currentAmount + amountUsdAdjustment),
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const normalizeStatus = (
|
|
454
|
+
walletStatus?: string | null,
|
|
455
|
+
chainStatuses: string[] = [],
|
|
456
|
+
): EvmWalletStatusTypes => {
|
|
457
|
+
const normalizedWalletStatus = String(walletStatus ?? '').trim().toLowerCase();
|
|
458
|
+
if (KNOWN_WALLET_STATUSES.has(normalizedWalletStatus)) {
|
|
459
|
+
return normalizedWalletStatus as EvmWalletStatusTypes;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const knownChainStatus = chainStatuses.find((status) => KNOWN_WALLET_STATUSES.has(status));
|
|
463
|
+
if (knownChainStatus) {
|
|
464
|
+
return knownChainStatus as EvmWalletStatusTypes;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return EvmWalletStatusTypes.created;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
export const isEvmWalletLegacyResponse = <TTransaction = unknown>(
|
|
471
|
+
data: IEvmWalletInfoApiResponse<TTransaction>,
|
|
472
|
+
): data is IEvmWalletDataResponse<TTransaction> => (
|
|
473
|
+
'status' in data
|
|
474
|
+
&& 'balance' in data
|
|
475
|
+
&& 'transactions' in data
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
export const normalizeEvmWalletInfoResponse = <TTransaction = unknown>(
|
|
479
|
+
data: IEvmWalletInfoApiResponse<TTransaction>,
|
|
480
|
+
options: NormalizeEvmWalletInfoOptions = {},
|
|
481
|
+
): IEvmWalletDataResponse<TTransaction> => {
|
|
482
|
+
if (isEvmWalletLegacyResponse(data)) {
|
|
483
|
+
const reconciled = reconcileConfiguredStableCoinBalance(data.balances, options);
|
|
484
|
+
return reconciled.balances === data.balances
|
|
485
|
+
? data
|
|
486
|
+
: {
|
|
487
|
+
...data,
|
|
488
|
+
balances: reconciled.balances,
|
|
489
|
+
tradable_crypto_balance: adjustWalletBalanceSummary(
|
|
490
|
+
data.tradable_crypto_balance,
|
|
491
|
+
reconciled.amountUsdAdjustment,
|
|
492
|
+
),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const chains = Array.isArray(data.chains) ? data.chains : [];
|
|
497
|
+
const rootChain = String(data.chain ?? '').trim();
|
|
498
|
+
const rootWalletAddress = String(data.wallet_address ?? '').trim();
|
|
499
|
+
const rootChainAccountStatus = String(data.chain_account_status ?? '').trim().toLowerCase();
|
|
500
|
+
const normalizedInputChains = [
|
|
501
|
+
...chains,
|
|
502
|
+
...((rootChain || rootWalletAddress || rootChainAccountStatus) && !chains.some((chain) => (
|
|
503
|
+
String(chain.chain ?? '').trim().toLowerCase() === rootChain.toLowerCase()
|
|
504
|
+
))
|
|
505
|
+
? [{
|
|
506
|
+
chain: rootChain,
|
|
507
|
+
wallet_address: rootWalletAddress,
|
|
508
|
+
chain_account_status: rootChainAccountStatus,
|
|
509
|
+
exchange_payout_token_address: data.exchange_payout_token_address,
|
|
510
|
+
exchange_payout_token_symbol: data.exchange_payout_token_symbol,
|
|
511
|
+
}]
|
|
512
|
+
: []),
|
|
513
|
+
];
|
|
514
|
+
const chainStatuses = normalizedInputChains
|
|
515
|
+
.map((chain) => String(chain.chain_account_status ?? '').trim().toLowerCase())
|
|
516
|
+
.filter(Boolean);
|
|
517
|
+
const normalizedChains: IEvmWalletChainAccount[] = normalizedInputChains
|
|
518
|
+
.map((chain) => {
|
|
519
|
+
const chainAccountStatus = String(chain.chain_account_status ?? '').trim().toLowerCase();
|
|
520
|
+
const payoutTokenAddress = firstString(chain.exchange_payout_token_address);
|
|
521
|
+
const payoutTokenSymbol = firstString(chain.exchange_payout_token_symbol);
|
|
522
|
+
return {
|
|
523
|
+
chain: String(chain.chain ?? '').trim(),
|
|
524
|
+
wallet_address: String(chain.wallet_address ?? '').trim(),
|
|
525
|
+
...(chainAccountStatus ? { chain_account_status: chainAccountStatus } : {}),
|
|
526
|
+
...(payoutTokenAddress ? { exchange_payout_token_address: payoutTokenAddress } : {}),
|
|
527
|
+
...(payoutTokenSymbol ? { exchange_payout_token_symbol: payoutTokenSymbol } : {}),
|
|
528
|
+
};
|
|
529
|
+
})
|
|
530
|
+
.filter((chain) => Boolean(chain.chain));
|
|
531
|
+
const firstWalletAddress = normalizedChains
|
|
532
|
+
.map((chain) => String(chain.wallet_address ?? '').trim())
|
|
533
|
+
.find(Boolean)
|
|
534
|
+
?? '';
|
|
535
|
+
const depositInstructionAddress = String(data.deposit_instructions?.address ?? '').trim();
|
|
536
|
+
const address = firstWalletAddress
|
|
537
|
+
|| rootWalletAddress
|
|
538
|
+
|| depositInstructionAddress
|
|
539
|
+
|| '';
|
|
540
|
+
const depositInstructions = data.deposit_instructions
|
|
541
|
+
? {
|
|
542
|
+
chain: String(data.deposit_instructions.chain ?? '').trim() || undefined,
|
|
543
|
+
address: String(data.deposit_instructions.address ?? '').trim() || undefined,
|
|
544
|
+
}
|
|
545
|
+
: undefined;
|
|
546
|
+
const updatedAt = data.updated_at ?? data.created_at ?? new Date().toISOString();
|
|
547
|
+
const normalizedBalances = normalizeWalletInfoBalances(data.balances);
|
|
548
|
+
const reconciled = reconcileConfiguredStableCoinBalance(normalizedBalances, options);
|
|
549
|
+
const tradableCryptoBalance = adjustWalletBalanceSummary(
|
|
550
|
+
normalizeWalletBalanceSummary(data.tradable_crypto_balance),
|
|
551
|
+
reconciled.amountUsdAdjustment,
|
|
552
|
+
);
|
|
553
|
+
const rwaAssetBalance = normalizeWalletBalanceSummary(data.rwa_asset_balance);
|
|
554
|
+
const providerName = String(data.provider_name ?? '').trim();
|
|
555
|
+
const turnkeyOrgID = firstString(data.turnkey_org_id);
|
|
556
|
+
const turnkeySubOrgID = firstString(data.turnkey_sub_org_id);
|
|
557
|
+
const turnkeyUserID = firstString(data.turnkey_user_id);
|
|
558
|
+
const turnkeyWalletID = firstString(data.turnkey_wallet_id);
|
|
559
|
+
const turnkeyAccountID = firstString(data.turnkey_account_id);
|
|
560
|
+
|
|
561
|
+
return {
|
|
562
|
+
id: Number(data.profile_id ?? data.wallet_id ?? 0),
|
|
563
|
+
...(data.wallet_id != null ? { wallet_id: Number(data.wallet_id) } : {}),
|
|
564
|
+
status: normalizeStatus(data.wallet_status, chainStatuses),
|
|
565
|
+
...(providerName ? { provider_name: providerName } : {}),
|
|
566
|
+
...(turnkeyOrgID ? { turnkey_org_id: turnkeyOrgID } : {}),
|
|
567
|
+
...(turnkeySubOrgID ? { turnkey_sub_org_id: turnkeySubOrgID } : {}),
|
|
568
|
+
...(turnkeyUserID ? { turnkey_user_id: turnkeyUserID } : {}),
|
|
569
|
+
...(turnkeyWalletID ? { turnkey_wallet_id: turnkeyWalletID } : {}),
|
|
570
|
+
...(turnkeyAccountID ? { turnkey_account_id: turnkeyAccountID } : {}),
|
|
571
|
+
balance: '0',
|
|
572
|
+
inc_balance: 0,
|
|
573
|
+
out_balance: 0,
|
|
574
|
+
address,
|
|
575
|
+
...(rootChain ? { chain: rootChain } : {}),
|
|
576
|
+
...(firstString(data.exchange_payout_token_address)
|
|
577
|
+
? { exchange_payout_token_address: firstString(data.exchange_payout_token_address) }
|
|
578
|
+
: {}),
|
|
579
|
+
...(firstString(data.exchange_payout_token_symbol)
|
|
580
|
+
? { exchange_payout_token_symbol: firstString(data.exchange_payout_token_symbol) }
|
|
581
|
+
: {}),
|
|
582
|
+
deposit_instructions: depositInstructions,
|
|
583
|
+
chains: normalizedChains,
|
|
584
|
+
balances: reconciled.balances,
|
|
585
|
+
...(tradableCryptoBalance ? { tradable_crypto_balance: tradableCryptoBalance } : {}),
|
|
586
|
+
...(rwaAssetBalance ? { rwa_asset_balance: rwaAssetBalance } : {}),
|
|
587
|
+
transactions: [],
|
|
588
|
+
created_at: data.created_at ?? updatedAt,
|
|
589
|
+
updated_at: updatedAt,
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
export const extractDepositAddressFromWalletInfo = (
|
|
594
|
+
data: IEvmWalletInfoApiResponse,
|
|
595
|
+
): string => {
|
|
596
|
+
if (isEvmWalletLegacyResponse(data)) {
|
|
597
|
+
return String(data.address ?? '').trim();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const depositInstructionAddress = String(data.deposit_instructions?.address ?? '').trim();
|
|
601
|
+
if (depositInstructionAddress) {
|
|
602
|
+
return depositInstructionAddress;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const balances = Array.isArray(data.balances)
|
|
606
|
+
? data.balances
|
|
607
|
+
: Object.values(data.balances ?? {});
|
|
608
|
+
const usdcBalanceAddress = balances
|
|
609
|
+
.find((balance) => {
|
|
610
|
+
const asset = firstString(
|
|
611
|
+
balance?.asset,
|
|
612
|
+
balance?.symbol,
|
|
613
|
+
balance?.token_symbol,
|
|
614
|
+
balance?.asset_ticker,
|
|
615
|
+
).toUpperCase();
|
|
616
|
+
return asset === 'USDC';
|
|
617
|
+
});
|
|
618
|
+
const usdcAddress = firstString(usdcBalanceAddress?.address, usdcBalanceAddress?.token_address);
|
|
619
|
+
if (usdcAddress) {
|
|
620
|
+
return usdcAddress;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const normalizedWallet = normalizeEvmWalletInfoResponse(data);
|
|
624
|
+
return String(normalizedWallet.address ?? '').trim();
|
|
625
|
+
};
|