@portal-hq/web 3.17.0 → 3.18.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/lib/commonjs/index.js +62 -20
- package/lib/commonjs/index.test.js +51 -10
- package/lib/commonjs/integrations/ramps/index.js +2 -0
- package/lib/commonjs/integrations/ramps/meld/index.js +121 -0
- package/lib/commonjs/integrations/ramps/meld/index.test.js +162 -0
- package/lib/commonjs/integrations/ramps/noah/index.js +2 -2
- package/lib/commonjs/integrations/ramps/noah/index.test.js +11 -2
- package/lib/commonjs/mpc/index.js +199 -3
- package/lib/commonjs/mpc/index.test.js +634 -0
- package/lib/commonjs/provider/index.js +44 -2
- package/lib/commonjs/provider/index.test.js +186 -0
- package/lib/commonjs/rpc.test.js +162 -0
- package/lib/commonjs/shared/rpc/auth.js +29 -0
- package/lib/commonjs/shared/rpc/defaults.js +40 -0
- package/lib/commonjs/shared/types/index.js +1 -0
- package/lib/commonjs/shared/types/meld.js +2 -0
- package/lib/esm/index.js +59 -19
- package/lib/esm/index.test.js +51 -10
- package/lib/esm/integrations/ramps/index.js +2 -0
- package/lib/esm/integrations/ramps/meld/index.js +118 -0
- package/lib/esm/integrations/ramps/meld/index.test.js +157 -0
- package/lib/esm/integrations/ramps/noah/index.js +2 -2
- package/lib/esm/integrations/ramps/noah/index.test.js +11 -2
- package/lib/esm/mpc/index.js +199 -3
- package/lib/esm/mpc/index.test.js +635 -1
- package/lib/esm/provider/index.js +44 -2
- package/lib/esm/provider/index.test.js +186 -0
- package/lib/esm/rpc.test.js +157 -0
- package/lib/esm/shared/rpc/auth.js +25 -0
- package/lib/esm/shared/rpc/defaults.js +36 -0
- package/lib/esm/shared/types/index.js +1 -0
- package/lib/esm/shared/types/meld.js +1 -0
- package/noah-types.d.ts +17 -1
- package/package.json +1 -1
- package/src/__mocks/constants.ts +68 -0
- package/src/__mocks/portal/portal.ts +0 -1
- package/src/index.test.ts +90 -0
- package/src/index.ts +159 -7
- package/src/integrations/ramps/index.ts +3 -0
- package/src/integrations/ramps/meld/index.test.ts +189 -0
- package/src/integrations/ramps/meld/index.ts +149 -0
- package/src/integrations/ramps/noah/index.test.ts +11 -2
- package/src/integrations/ramps/noah/index.ts +5 -2
- package/src/mpc/index.test.ts +804 -7
- package/src/mpc/index.ts +247 -3
- package/src/provider/index.test.ts +233 -0
- package/src/provider/index.ts +64 -1
- package/src/rpc.test.ts +190 -0
- package/src/shared/rpc/auth.ts +27 -0
- package/src/shared/rpc/defaults.ts +41 -0
- package/src/shared/types/common.ts +24 -0
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/meld.ts +302 -0
- package/src/shared/types/noah.ts +191 -29
- package/types.d.ts +65 -3
package/src/rpc.test.ts
ADDED
|
@@ -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
|
|
@@ -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[]>
|