@portal-hq/web 3.17.0 → 3.18.0-alpha.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.
Files changed (55) hide show
  1. package/lib/commonjs/index.js +62 -20
  2. package/lib/commonjs/index.test.js +51 -10
  3. package/lib/commonjs/integrations/ramps/index.js +2 -0
  4. package/lib/commonjs/integrations/ramps/meld/index.js +121 -0
  5. package/lib/commonjs/integrations/ramps/meld/index.test.js +162 -0
  6. package/lib/commonjs/integrations/ramps/noah/index.js +2 -2
  7. package/lib/commonjs/integrations/ramps/noah/index.test.js +11 -2
  8. package/lib/commonjs/mpc/index.js +199 -3
  9. package/lib/commonjs/mpc/index.test.js +634 -0
  10. package/lib/commonjs/provider/index.js +44 -2
  11. package/lib/commonjs/provider/index.test.js +186 -0
  12. package/lib/commonjs/rpc.test.js +162 -0
  13. package/lib/commonjs/shared/rpc/auth.js +29 -0
  14. package/lib/commonjs/shared/rpc/defaults.js +40 -0
  15. package/lib/commonjs/shared/types/index.js +1 -0
  16. package/lib/commonjs/shared/types/meld.js +2 -0
  17. package/lib/esm/index.js +59 -19
  18. package/lib/esm/index.test.js +51 -10
  19. package/lib/esm/integrations/ramps/index.js +2 -0
  20. package/lib/esm/integrations/ramps/meld/index.js +118 -0
  21. package/lib/esm/integrations/ramps/meld/index.test.js +157 -0
  22. package/lib/esm/integrations/ramps/noah/index.js +2 -2
  23. package/lib/esm/integrations/ramps/noah/index.test.js +11 -2
  24. package/lib/esm/mpc/index.js +199 -3
  25. package/lib/esm/mpc/index.test.js +635 -1
  26. package/lib/esm/provider/index.js +44 -2
  27. package/lib/esm/provider/index.test.js +186 -0
  28. package/lib/esm/rpc.test.js +157 -0
  29. package/lib/esm/shared/rpc/auth.js +25 -0
  30. package/lib/esm/shared/rpc/defaults.js +36 -0
  31. package/lib/esm/shared/types/index.js +1 -0
  32. package/lib/esm/shared/types/meld.js +1 -0
  33. package/noah-types.d.ts +17 -1
  34. package/package.json +3 -2
  35. package/src/__mocks/constants.ts +68 -0
  36. package/src/__mocks/portal/portal.ts +0 -1
  37. package/src/index.test.ts +90 -0
  38. package/src/index.ts +159 -7
  39. package/src/integrations/ramps/index.ts +3 -0
  40. package/src/integrations/ramps/meld/index.test.ts +189 -0
  41. package/src/integrations/ramps/meld/index.ts +149 -0
  42. package/src/integrations/ramps/noah/index.test.ts +11 -2
  43. package/src/integrations/ramps/noah/index.ts +5 -2
  44. package/src/mpc/index.test.ts +804 -7
  45. package/src/mpc/index.ts +247 -3
  46. package/src/provider/index.test.ts +233 -0
  47. package/src/provider/index.ts +64 -1
  48. package/src/rpc.test.ts +190 -0
  49. package/src/shared/rpc/auth.ts +27 -0
  50. package/src/shared/rpc/defaults.ts +41 -0
  51. package/src/shared/types/common.ts +24 -0
  52. package/src/shared/types/index.ts +1 -0
  53. package/src/shared/types/meld.ts +302 -0
  54. package/src/shared/types/noah.ts +191 -29
  55. package/types.d.ts +65 -3
@@ -0,0 +1,190 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+
5
+ import { isPortalHostedUrl } from './shared/rpc/auth'
6
+ import { buildDefaultRpcConfig, DEFAULT_RPC_HOST } from './shared/rpc/defaults'
7
+ import Portal from '.'
8
+
9
+ // ─── isPortalHostedUrl ───────────────────────────────────────────────────────
10
+
11
+ describe('isPortalHostedUrl', () => {
12
+ describe('trusted Portal domains', () => {
13
+ const trusted = [
14
+ 'https://web.portalhq.io/rpc/v1/eip155/1',
15
+ 'https://portalhq.io',
16
+ 'https://staging.portalhq.io/rpc/v1/eip155/1',
17
+ 'https://x.portalhq.io',
18
+ 'https://web.portalhq.dev/rpc/v1/eip155/1',
19
+ 'https://portalhq.dev',
20
+ 'https://sub.portalhq.dev',
21
+ 'https://portalhq-passkey.io',
22
+ 'https://sub.portalhq-passkey.io',
23
+ 'https://portalhq-passkey.dev',
24
+ 'https://sub.portalhq-passkey.dev',
25
+ ]
26
+
27
+ for (const url of trusted) {
28
+ it(`returns true for ${url}`, () => {
29
+ expect(isPortalHostedUrl(url)).toBe(true)
30
+ })
31
+ }
32
+ })
33
+
34
+ describe('untrusted / third-party domains', () => {
35
+ const untrusted = [
36
+ 'https://mainnet.infura.io/v3/KEY',
37
+ 'https://eth-mainnet.alchemyapi.io/v2/KEY',
38
+ 'https://rpc.ankr.com/eth',
39
+ 'https://notportalhq.io/rpc/v1/eip155/1',
40
+ 'https://portalhq.io.evil.com/rpc', // subdomain spoofing
41
+ 'https://evil-portalhq.io/rpc', // prefix attack
42
+ 'https://portalhq.iomalicious.com', // suffix attack
43
+ 'http://localhost:8545', // localhost — not trusted here (iframe-only)
44
+ 'http://127.0.0.1:8545',
45
+ ]
46
+
47
+ for (const url of untrusted) {
48
+ it(`returns false for ${url}`, () => {
49
+ expect(isPortalHostedUrl(url)).toBe(false)
50
+ })
51
+ }
52
+ })
53
+
54
+ it('returns false for an invalid URL', () => {
55
+ expect(isPortalHostedUrl('not-a-url')).toBe(false)
56
+ expect(isPortalHostedUrl('')).toBe(false)
57
+ })
58
+
59
+ it('handles trailing dot in hostname (valid DNS normalisation)', () => {
60
+ // 'web.portalhq.io.' is technically valid DNS — must still be trusted
61
+ expect(isPortalHostedUrl('https://web.portalhq.io./rpc/v1/eip155/1')).toBe(true)
62
+ })
63
+ })
64
+
65
+ // ─── buildDefaultRpcConfig ───────────────────────────────────────────────────
66
+
67
+ describe('buildDefaultRpcConfig', () => {
68
+ it('returns exactly 13 entries', () => {
69
+ expect(Object.keys(buildDefaultRpcConfig(DEFAULT_RPC_HOST))).toHaveLength(13)
70
+ })
71
+
72
+ it('includes all 11 expected EVM chains', () => {
73
+ const cfg = buildDefaultRpcConfig(DEFAULT_RPC_HOST)
74
+ const evmChains = [
75
+ 'eip155:1',
76
+ 'eip155:11155111',
77
+ 'eip155:137',
78
+ 'eip155:80002',
79
+ 'eip155:8453',
80
+ 'eip155:84532',
81
+ 'eip155:143',
82
+ 'eip155:10143',
83
+ 'eip155:10',
84
+ 'eip155:42161',
85
+ 'eip155:43114',
86
+ ]
87
+ for (const chain of evmChains) {
88
+ expect(cfg).toHaveProperty(chain)
89
+ }
90
+ })
91
+
92
+ it('includes both Solana chains', () => {
93
+ const cfg = buildDefaultRpcConfig(DEFAULT_RPC_HOST)
94
+ expect(cfg).toHaveProperty('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp')
95
+ expect(cfg).toHaveProperty('solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1')
96
+ })
97
+
98
+ it('does NOT include deprecated Polygon Mumbai (eip155:80001)', () => {
99
+ expect(buildDefaultRpcConfig(DEFAULT_RPC_HOST)).not.toHaveProperty('eip155:80001')
100
+ })
101
+
102
+ it('every URL follows the template https://{rpcHost}/rpc/v1/{namespace}/{reference}', () => {
103
+ const cfg = buildDefaultRpcConfig(DEFAULT_RPC_HOST)
104
+ for (const [chainId, url] of Object.entries(cfg)) {
105
+ const [namespace, reference] = chainId.split(':')
106
+ expect(url).toBe(`https://${DEFAULT_RPC_HOST}/rpc/v1/${namespace}/${reference}`)
107
+ }
108
+ })
109
+
110
+ it('uses the production host by default', () => {
111
+ const cfg = buildDefaultRpcConfig()
112
+ expect(cfg['eip155:1']).toBe('https://web.portalhq.io/rpc/v1/eip155/1')
113
+ })
114
+
115
+ it('uses a custom rpcHost when provided', () => {
116
+ const cfg = buildDefaultRpcConfig('web.portalhq.dev')
117
+ for (const url of Object.values(cfg)) {
118
+ expect(url).toContain('https://web.portalhq.dev/rpc/v1/')
119
+ }
120
+ expect(cfg['eip155:1']).toBe('https://web.portalhq.dev/rpc/v1/eip155/1')
121
+ })
122
+
123
+ it('does not contain the production host when a custom host is used', () => {
124
+ const cfg = buildDefaultRpcConfig('web.portalhq.dev')
125
+ for (const url of Object.values(cfg)) {
126
+ expect(url).not.toContain(DEFAULT_RPC_HOST)
127
+ }
128
+ })
129
+ })
130
+
131
+ // ─── Portal constructor — rpcConfig resolution ───────────────────────────────
132
+
133
+ describe('Portal constructor — rpcConfig resolution', () => {
134
+ it('generates 13 default chains when rpcConfig is omitted', () => {
135
+ const portal = new Portal({ apiKey: 'test-key' })
136
+ expect(Object.keys(portal.rpcConfig)).toHaveLength(13)
137
+ })
138
+
139
+ it('uses web.portalhq.io gateway URLs when rpcConfig is omitted', () => {
140
+ const portal = new Portal({ apiKey: 'test-key' })
141
+ expect(portal.rpcConfig['eip155:1']).toBe(
142
+ 'https://web.portalhq.io/rpc/v1/eip155/1',
143
+ )
144
+ })
145
+
146
+ it('treats {} identically to omitting rpcConfig', () => {
147
+ const portalA = new Portal({ apiKey: 'test-key' })
148
+ const portalB = new Portal({ apiKey: 'test-key', rpcConfig: {} })
149
+ expect(portalB.rpcConfig).toEqual(portalA.rpcConfig)
150
+ })
151
+
152
+ it('uses an explicit non-empty rpcConfig verbatim (no merge with defaults)', () => {
153
+ const custom = { 'eip155:1': 'https://my-node.example.com/eth' }
154
+ const portal = new Portal({ apiKey: 'test-key', rpcConfig: custom })
155
+ expect(portal.rpcConfig).toEqual(custom)
156
+ expect(Object.keys(portal.rpcConfig)).toHaveLength(1)
157
+ })
158
+
159
+ it('does not inject defaults alongside an explicit config', () => {
160
+ const custom = { 'eip155:1': 'https://my-node.example.com/eth' }
161
+ const portal = new Portal({ apiKey: 'test-key', rpcConfig: custom })
162
+ expect(portal.rpcConfig['eip155:137']).toBeUndefined()
163
+ })
164
+
165
+ it('uses a custom gatewayHost in the generated default URLs', () => {
166
+ const portal = new Portal({ apiKey: 'test-key', gatewayHost: 'web.portalhq.dev' })
167
+ expect(portal.rpcConfig['eip155:1']).toBe(
168
+ 'https://web.portalhq.dev/rpc/v1/eip155/1',
169
+ )
170
+ })
171
+
172
+ it('all default URLs contain the custom gatewayHost', () => {
173
+ const portal = new Portal({ apiKey: 'test-key', gatewayHost: 'web.portalhq.dev' })
174
+ for (const url of Object.values(portal.rpcConfig)) {
175
+ expect(url).toContain('https://web.portalhq.dev/rpc/v1/')
176
+ }
177
+ })
178
+
179
+ it('getRpcUrl returns the default URL for a standard chain', () => {
180
+ const portal = new Portal({ apiKey: 'test-key' })
181
+ expect(portal.getRpcUrl('eip155:1')).toBe(
182
+ 'https://web.portalhq.io/rpc/v1/eip155/1',
183
+ )
184
+ })
185
+
186
+ it('getRpcUrl throws for a chain not in the config', () => {
187
+ const portal = new Portal({ apiKey: 'test-key' })
188
+ expect(() => portal.getRpcUrl('eip155:99999')).toThrow()
189
+ })
190
+ })
@@ -0,0 +1,27 @@
1
+ const PORTAL_TRUSTED_DOMAINS = [
2
+ 'portalhq.io',
3
+ 'portalhq.dev',
4
+ 'portalhq-passkey.io',
5
+ 'portalhq-passkey.dev',
6
+ ]
7
+
8
+ /**
9
+ * Returns true iff the URL's hostname is owned by Portal.
10
+ *
11
+ * Guards Authorization header injection — prevents credential leakage to
12
+ * third-party RPC providers (Infura, Alchemy, etc.).
13
+ *
14
+ * Localhost trust is intentionally excluded: that is an iframe-only
15
+ * concern (dev CORS proxies) and is handled separately in the iframe.
16
+ */
17
+ export function isPortalHostedUrl(url: string): boolean {
18
+ try {
19
+ // Normalise: strip trailing dots from hostname (valid DNS but unusual)
20
+ const hostname = new URL(url).hostname.toLowerCase().replace(/\.$/, '')
21
+ return PORTAL_TRUSTED_DOMAINS.some(
22
+ (domain) => hostname === domain || hostname.endsWith(`.${domain}`),
23
+ )
24
+ } catch {
25
+ return false
26
+ }
27
+ }
@@ -0,0 +1,41 @@
1
+ import type { RpcConfig } from '../types/common'
2
+
3
+ /**
4
+ * Production Portal RPC gateway hostname.
5
+ * Staging equivalent: `web.portalhq.dev`.
6
+ * Used as the fallback when neither `gatewayHost` nor `host` is supplied.
7
+ */
8
+ export const DEFAULT_RPC_HOST = 'web.portalhq.io'
9
+
10
+ /**
11
+ * Builds the default Portal-managed RPC gateway config from a given gateway host.
12
+ *
13
+ * URL template: https://{rpcHost}/rpc/v1/{namespace}/{reference}
14
+ *
15
+ * Covers 13 chains — 11 EVM + 2 Solana. Polygon Amoy (eip155:80002) is used;
16
+ * the deprecated Mumbai (eip155:80001) is intentionally excluded.
17
+ *
18
+ * When rpcConfig is undefined or {} in the Portal constructor, this function is
19
+ * called automatically. An explicit non-empty rpcConfig always takes precedence
20
+ * verbatim — no merging with defaults.
21
+ */
22
+ export function buildDefaultRpcConfig(
23
+ rpcHost: string = DEFAULT_RPC_HOST,
24
+ ): RpcConfig {
25
+ const base = `https://${rpcHost}/rpc/v1`
26
+ return {
27
+ 'eip155:1': `${base}/eip155/1`,
28
+ 'eip155:11155111': `${base}/eip155/11155111`,
29
+ 'eip155:137': `${base}/eip155/137`,
30
+ 'eip155:80002': `${base}/eip155/80002`,
31
+ 'eip155:8453': `${base}/eip155/8453`,
32
+ 'eip155:84532': `${base}/eip155/84532`,
33
+ 'eip155:143': `${base}/eip155/143`,
34
+ 'eip155:10143': `${base}/eip155/10143`,
35
+ 'eip155:10': `${base}/eip155/10`,
36
+ 'eip155:42161': `${base}/eip155/42161`,
37
+ 'eip155:43114': `${base}/eip155/43114`,
38
+ 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': `${base}/solana/5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`,
39
+ 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1': `${base}/solana/EtWTRABZaYq6iMfeYKouRu166VU2xqa1`,
40
+ }
41
+ }
@@ -416,6 +416,30 @@ export interface BuiltTronTransaction {
416
416
  }
417
417
  }
418
418
 
419
+ export interface TronTypedDataDomain {
420
+ name?: string
421
+ version?: string
422
+ chainId?: number
423
+ verifyingContract?: string
424
+ salt?: string
425
+ }
426
+
427
+ export type TronTypedDataValue =
428
+ | string
429
+ | number
430
+ | boolean
431
+ | bigint
432
+ | TronTypedDataObject
433
+ | TronTypedDataValue[]
434
+
435
+ export type TronTypedDataObject = { [key: string]: TronTypedDataValue }
436
+
437
+ export interface TronTypedData {
438
+ domain: TronTypedDataDomain
439
+ types: Record<string, Array<{ name: string; type: string }>>
440
+ value: TronTypedDataObject
441
+ }
442
+
419
443
  // Simulation types
420
444
  export interface SimulateTransactionParam {
421
445
  to: string
@@ -17,6 +17,7 @@ export * from './yieldxyz'
17
17
  export * from './zero-x'
18
18
  export * from './lifi'
19
19
  export * from './noah'
20
+ export * from './meld'
20
21
  export * from './hypernative'
21
22
  export * from './blockaid'
22
23
 
@@ -0,0 +1,302 @@
1
+ import type { PortalApiSuccessEnvelope } from './noah'
2
+
3
+
4
+ export type MeldCustomerType = 'INDIVIDUAL' | 'BUSINESS'
5
+
6
+ export type MeldSessionType = 'BUY' | 'SELL' | 'TRANSFER'
7
+
8
+
9
+ export interface MeldCustomerName {
10
+ firstName?: string
11
+ lastName?: string
12
+ }
13
+
14
+ export interface MeldCustomerAddressDetails {
15
+ lineOne?: string
16
+ lineTwo?: string
17
+ city?: string
18
+ region?: string
19
+ postalCode?: string
20
+ country?: string
21
+ }
22
+
23
+ export interface MeldCustomerAddress {
24
+ id: string
25
+ customerId: string
26
+ type: 'BILLING' | 'SHIPPING' | 'RESIDENCE'
27
+ status: 'ACTIVE' | 'INACTIVE'
28
+ addressDetails: MeldCustomerAddressDetails
29
+ }
30
+
31
+ export interface MeldCustomer {
32
+ id: string
33
+ accountId: string
34
+ externalId: string
35
+ name?: MeldCustomerName
36
+ email?: string
37
+ phone?: string
38
+ dateOfBirth?: string
39
+ type?: MeldCustomerType
40
+ status?: 'ACTIVE' | 'INACTIVE'
41
+ addresses?: MeldCustomerAddress[]
42
+ serviceProviders?: Record<string, unknown>
43
+ }
44
+
45
+ export interface MeldSearchCustomerResponseData {
46
+ customers: MeldCustomer[]
47
+ count: number
48
+ remaining: number
49
+ }
50
+
51
+ export interface MeldRampIntelligence {
52
+ rampScore?: number | null
53
+ lowKyc?: boolean | null
54
+ previouslyUsed?: boolean | null
55
+ }
56
+
57
+ export interface MeldQuote {
58
+ transactionType: string
59
+ sourceAmount: number
60
+ sourceAmountWithoutFees?: number | null
61
+ sourceCurrencyCode: string
62
+ destinationAmount: number
63
+ destinationAmountWithoutFees?: number | null
64
+ destinationCurrencyCode: string
65
+ exchangeRate: number
66
+ transactionFee: number
67
+ networkFee?: number | null
68
+ partnerFee?: number | null
69
+ totalFee: number
70
+ fiatAmountWithoutFees?: number | null
71
+ paymentMethodType: string
72
+ serviceProvider: string
73
+ countryCode?: string
74
+ customerScore?: number | null
75
+ institutionName?: string | null
76
+ isNativeAvailable?: boolean | null
77
+ rampIntelligence?: MeldRampIntelligence | null
78
+ }
79
+
80
+ export interface MeldGetRetailQuoteResponseData {
81
+ quotes: MeldQuote[]
82
+ message?: string | null
83
+ error?: string | null
84
+ timestamp?: string | null
85
+ }
86
+
87
+ export interface MeldSessionData {
88
+ countryCode: string
89
+ serviceProvider: string
90
+ sourceCurrencyCode: string
91
+ /** String amount as required by the Meld widget API. Note: MeldGetRetailQuoteRequest.sourceAmount uses number. */
92
+ sourceAmount: string
93
+ destinationCurrencyCode: string
94
+ walletAddress?: string
95
+ walletTag?: string
96
+ paymentMethodType?: string
97
+ redirectUrl?: string
98
+ lockFields?: string[]
99
+ }
100
+
101
+ export interface MeldCreateRetailWidgetResponseData {
102
+ id: string
103
+ token: string
104
+ customerId: string
105
+ externalCustomerId: string
106
+ externalSessionId: string
107
+ widgetUrl: string
108
+ // serviceProviderWidgetUrl exists in the Meld API response but is stripped by the backend controller
109
+ }
110
+
111
+ export interface MeldTransaction {
112
+ id: string
113
+ sessionId: string
114
+ externalSessionId?: string | null
115
+ externalCustomerId?: string | null
116
+ status: string
117
+ transactionType: string
118
+ serviceProvider: string
119
+ sourceAmount?: number | null
120
+ sourceCurrencyCode?: string | null
121
+ destinationAmount?: number | null
122
+ destinationCurrencyCode?: string | null
123
+ destinationWalletAddress?: string | null
124
+ createdAt: string
125
+ updatedAt: string
126
+ paymentMethodType?: string | null
127
+ cryptoPurchaseDetails?: Record<string, unknown> | null
128
+ serviceProviderDetails?: Record<string, unknown> | null
129
+ [key: string]: unknown
130
+ }
131
+
132
+ export interface MeldSearchRetailTransactionsResponseData {
133
+ count: number
134
+ remaining: number
135
+ totalCount: number
136
+ transactions: MeldTransaction[]
137
+ }
138
+
139
+ export interface MeldGetRetailTransactionResponseData {
140
+ transaction: MeldTransaction
141
+ }
142
+
143
+
144
+ export interface MeldServiceProvider {
145
+ serviceProvider: string
146
+ name: string
147
+ status?: string
148
+ categories?: string[]
149
+ categoryStatuses?: Record<string, string>
150
+ websiteUrl?: string
151
+ customerSupportUrl?: string
152
+ logos?: {
153
+ dark?: string
154
+ light?: string
155
+ darkShort?: string
156
+ lightShort?: string
157
+ }
158
+ }
159
+
160
+ export interface MeldCountry {
161
+ countryCode: string
162
+ name: string
163
+ flagImageUrl?: string
164
+ regions?: { regionCode: string; name: string }[] | null
165
+ }
166
+
167
+ export interface MeldFiatCurrency {
168
+ currencyCode: string
169
+ name: string
170
+ symbolImageUrl?: string
171
+ }
172
+
173
+ export interface MeldCryptoCurrency {
174
+ currencyCode: string
175
+ name: string
176
+ chainCode?: string
177
+ chainName?: string
178
+ chainId?: string | null
179
+ contractAddress?: string
180
+ symbolImageUrl?: string
181
+ }
182
+
183
+ export interface MeldPaymentMethod {
184
+ paymentMethod: string
185
+ name: string
186
+ paymentType?: string
187
+ logos?: { dark?: string; light?: string }
188
+ }
189
+
190
+ export interface MeldCountryDefault {
191
+ countryCode: string
192
+ defaultCurrencyCode: string
193
+ defaultPaymentMethods: string[]
194
+ }
195
+
196
+ export interface MeldFiatCurrencyPurchaseLimit {
197
+ currencyCode: string
198
+ defaultAmount: number | null
199
+ minimumAmount: number
200
+ maximumAmount: number
201
+ }
202
+
203
+ export interface MeldCryptoCurrencySellLimit {
204
+ currencyCode: string
205
+ chainCode?: string | null
206
+ defaultAmount: number | null
207
+ minimumAmount: number
208
+ maximumAmount: number
209
+ }
210
+
211
+ export interface MeldKycLimitTier {
212
+ dailyLimit?: number | null
213
+ weeklyLimit?: number | null
214
+ monthlyLimit?: number | null
215
+ yearlyLimit?: number | null
216
+ transactionLimit?: number | null
217
+ }
218
+
219
+ export interface MeldKycFiatLevel {
220
+ currencyCode: string
221
+ level1?: MeldKycLimitTier
222
+ level2?: MeldKycLimitTier
223
+ level3?: MeldKycLimitTier
224
+ }
225
+
226
+
227
+ export interface MeldCreateCustomerRequest {
228
+ // externalId is auto-injected by the backend from the authenticated client identity; do not send it
229
+ name?: MeldCustomerName
230
+ email?: string
231
+ phone?: string
232
+ dateOfBirth?: string
233
+ type?: MeldCustomerType
234
+ }
235
+
236
+ export interface MeldGetRetailQuoteRequest {
237
+ countryCode: string
238
+ sourceCurrencyCode: string
239
+ destinationCurrencyCode: string
240
+ /** Numeric amount. Note: MeldSessionData.sourceAmount uses string for the same concept. */
241
+ sourceAmount: number
242
+ walletAddress?: string
243
+ // externalCustomerId is auto-injected by the backend from the authenticated client identity; do not send it
244
+ customerId?: string
245
+ paymentMethodType?: string
246
+ serviceProviders?: string[]
247
+ subdivision?: string
248
+ }
249
+
250
+ export interface MeldCreateRetailWidgetRequest {
251
+ sessionType: MeldSessionType
252
+ sessionData: MeldSessionData
253
+ externalSessionId?: string
254
+ // externalCustomerId is auto-injected by the backend from the authenticated client identity; do not send it
255
+ customerId?: string
256
+ bypassKyc?: boolean
257
+ }
258
+
259
+ /** externalCustomerIds is auto-injected by the backend; all other string query params are forwarded to Meld */
260
+ export interface MeldSearchRetailTransactionsParams {
261
+ status?: string
262
+ limit?: string
263
+ offset?: string
264
+ [key: string]: string | undefined
265
+ }
266
+
267
+ export interface MeldDiscoveryParams {
268
+ countryCode?: string
269
+ [key: string]: string | undefined
270
+ }
271
+
272
+
273
+ export type MeldCreateCustomerResponse =
274
+ PortalApiSuccessEnvelope<MeldCustomer>
275
+ export type MeldSearchCustomerResponse =
276
+ PortalApiSuccessEnvelope<MeldSearchCustomerResponseData>
277
+ export type MeldGetRetailQuoteResponse =
278
+ PortalApiSuccessEnvelope<MeldGetRetailQuoteResponseData>
279
+ export type MeldCreateRetailWidgetResponse =
280
+ PortalApiSuccessEnvelope<MeldCreateRetailWidgetResponseData>
281
+ export type MeldSearchRetailTransactionsResponse =
282
+ PortalApiSuccessEnvelope<MeldSearchRetailTransactionsResponseData>
283
+ export type MeldGetRetailTransactionResponse =
284
+ PortalApiSuccessEnvelope<MeldGetRetailTransactionResponseData>
285
+ export type MeldGetServiceProvidersResponse =
286
+ PortalApiSuccessEnvelope<MeldServiceProvider[]>
287
+ export type MeldGetCountriesResponse =
288
+ PortalApiSuccessEnvelope<MeldCountry[]>
289
+ export type MeldGetFiatCurrenciesResponse =
290
+ PortalApiSuccessEnvelope<MeldFiatCurrency[]>
291
+ export type MeldGetCryptoCurrenciesResponse =
292
+ PortalApiSuccessEnvelope<MeldCryptoCurrency[]>
293
+ export type MeldGetPaymentMethodsResponse =
294
+ PortalApiSuccessEnvelope<MeldPaymentMethod[]>
295
+ export type MeldGetDefaultsResponse =
296
+ PortalApiSuccessEnvelope<MeldCountryDefault[]>
297
+ export type MeldGetBuyLimitsResponse =
298
+ PortalApiSuccessEnvelope<MeldFiatCurrencyPurchaseLimit[]>
299
+ export type MeldGetSellLimitsResponse =
300
+ PortalApiSuccessEnvelope<MeldCryptoCurrencySellLimit[]>
301
+ export type MeldGetKycLimitsResponse =
302
+ PortalApiSuccessEnvelope<MeldKycFiatLevel[]>