> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Meld buy, sell, and transfer

> Use portal.ramps.meld in the React Native SDK to embed Meld buy, sell, and transfer sessions through the Portal Client API.

The React Native SDK exposes [Meld](/integrations/On-Off-Ramp/meld) buy, sell, and transfer crypto through `portal.ramps.meld`. Each method issues HTTP requests through `portal.api` to Portal's Meld integration on the [Client API](/apis/quickstart) using your client API key. You do not call Meld's servers directly from the app.

<Note>
  For one-time dashboard setup and webhook configuration, see [Meld integration overview](/integrations/On-Off-Ramp/meld). For the end-to-end widget flow using raw HTTP calls, see the [White-Label API guide](/integrations/On-Off-Ramp/meld-widget).
</Note>

## Prerequisites

* An [initialized `Portal` client](./getting-started) with a wallet and API access.
* Meld enabled for your Portal environment and [configured in the dashboard](/integrations/On-Off-Ramp/meld).

## Architecture

| Layer            | Role                                                                                                             |
| ---------------- | ---------------------------------------------------------------------------------------------------------------- |
| Your app         | Calls `portal.ramps.meld.*`                                                                                      |
| React Native SDK | The Meld module at `portal.ramps.meld` issues authenticated HTTP requests through `portal.api` to the Portal API |
| Portal API       | `POST/GET …/api/v3/clients/me/integrations/meld/...` proxied to Meld with the Meld API key                       |
| Meld             | Aggregated on/off-ramp network; iframe-based widget session                                                      |

Prefer `portal.ramps.meld` over lower-level APIs. The SDK types for requests and responses are exported from `@portal-hq/core` (see the [React Native SDK reference](/sdks/react-native/reference/core) section **portal.ramps.meld (Meld)**).

## Types and responses

Successful Client API responses use an envelope `{ data: T, metadata?: Record<string, unknown> }`. Methods on `portal.ramps.meld` return `Promise` of that envelope (for example `MeldCreateRetailWidgetResponse` is `{ data: { widgetUrl: string, ... } }`).

Throwing or rejected promises usually indicate network errors, TLS issues, or API error payloads returned by the SDK — handle them with `try/catch` like other async Portal calls.

***

## Customer methods

### createCustomer

Creates a Meld customer record for the current Portal client. This is optional — the widget collects identity inline. Pre-creating is useful when you want to pre-fill KYC fields or track multiple sessions against a persistent customer record.

```typescript theme={null}
import { Portal } from '@portal-hq/core'

const { data } = await portal.ramps.meld.createCustomer({
  name: { firstName: 'Jane', lastName: 'Doe' },
  email: 'jane@example.com',
  phone: '+15551234567',
  dateOfBirth: '1990-01-15',
  type: 'INDIVIDUAL',
})

console.log(data.id)          // Meld customer ID — pass as `customerId` on createRetailWidget
console.log(data.externalId)  // Portal-derived external ID
```

**Signature**

```typescript theme={null}
public async createCustomer(
  request: MeldCreateCustomerRequest
): Promise<MeldCreateCustomerResponse>
```

| Parameter             | Type                         | Required | Description                                   |
| --------------------- | ---------------------------- | -------- | --------------------------------------------- |
| `request.name`        | `MeldCustomerName`           | No       | `{ firstName?: string; lastName?: string }`   |
| `request.email`       | `string`                     | No       | Customer email address.                       |
| `request.phone`       | `string`                     | No       | E.164 phone number.                           |
| `request.dateOfBirth` | `string`                     | No       | ISO date string (for example `"1990-01-15"`). |
| `request.type`        | `"INDIVIDUAL" \| "BUSINESS"` | No       | Customer type.                                |

**Returns** — `MeldCreateCustomerResponse`: `{ data: MeldCustomer }`.

<Note>
  If a customer already exists for the current Portal client, the Portal API returns `409 Conflict` with the existing customer ID at `details.meldCustomerId`. Retrieve the full record with `searchCustomer()` instead.
</Note>

***

### searchCustomer

Returns the Meld customer record(s) associated with the current Portal client.

```typescript theme={null}
const { data } = await portal.ramps.meld.searchCustomer()

if (data.customers.length > 0) {
  const customerId = data.customers[0].id
  console.log('Existing customer:', customerId)
}
```

**Signature**

```typescript theme={null}
public async searchCustomer(): Promise<MeldSearchCustomerResponse>
```

**Returns** — `MeldSearchCustomerResponse`: `{ data: { customers: MeldCustomer[]; count: number; remaining: number } }`.

`data.customers` will be empty if no customer has been created for this Portal client yet.

***

## Retail methods

### getRetailQuote

Fetches live pricing across Meld's provider network before opening the widget. Returns one `MeldQuote` per available provider.

```typescript theme={null}
const { data } = await portal.ramps.meld.getRetailQuote({
  countryCode: 'US',
  sourceCurrencyCode: 'USD',
  destinationCurrencyCode: 'USDC',
  sourceAmount: 100,
})

const best = data.quotes[0]
console.log(`${best.serviceProvider}: ${best.destinationAmount} USDC for $${best.sourceAmount}`)
console.log('Total fee:', best.totalFee)
```

**Signature**

```typescript theme={null}
public async getRetailQuote(
  request: MeldGetRetailQuoteRequest
): Promise<MeldGetRetailQuoteResponse>
```

| Parameter                         | Type       | Required | Description                                                                                                 |
| --------------------------------- | ---------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `request.countryCode`             | `string`   | Yes      | ISO-3166-1 alpha-2 country code (for example `"US"`).                                                       |
| `request.sourceCurrencyCode`      | `string`   | Yes      | Source currency (fiat for buy; crypto [Meld Code](https://www.meld.io/coverage/cryptocurrencies) for sell). |
| `request.destinationCurrencyCode` | `string`   | Yes      | Destination currency.                                                                                       |
| `request.sourceAmount`            | `number`   | Yes      | Amount of source currency to convert.                                                                       |
| `request.walletAddress`           | `string`   | No       | Destination wallet address — informs provider eligibility.                                                  |
| `request.customerId`              | `string`   | No       | Meld customer ID when pre-created.                                                                          |
| `request.paymentMethodType`       | `string`   | No       | Filter to a specific payment method (for example `"CREDIT_DEBIT_CARD"`).                                    |
| `request.serviceProviders`        | `string[]` | No       | Restrict the quote to specific providers.                                                                   |
| `request.subdivision`             | `string`   | No       | State/region code when required (for example US states).                                                    |

**Returns** — `MeldGetRetailQuoteResponse`: `{ data: { quotes: MeldQuote[]; message?: string; error?: string; timestamp?: string | null } }`.

Each `MeldQuote` includes required fields `serviceProvider`, `transactionType`, `sourceAmount`, `sourceCurrencyCode`, `destinationAmount`, `destinationCurrencyCode`, `exchangeRate`, `transactionFee`, `totalFee`, `paymentMethodType`; and optional fields `sourceAmountWithoutFees`, `destinationAmountWithoutFees`, `networkFee`, `partnerFee`, `fiatAmountWithoutFees`, `countryCode`, `customerScore`, `institutionName`, `isNativeAvailable`, and `rampIntelligence`.

***

### createRetailWidget

Creates a Meld widget session and returns a `widgetUrl`. Open the URL in a WebView (for example [`react-native-webview`](/sdks/react-native/guide/build-a-webview)) or the system browser to let the user complete the buy/sell/transfer flow.

```typescript theme={null}
import { Linking } from 'react-native'

const { data } = await portal.ramps.meld.createRetailWidget({
  sessionType: 'BUY',
  sessionData: {
    countryCode: 'US',
    serviceProvider: 'TRANSAK',        // Example provider; use a value returned by getRetailQuote
    sourceCurrencyCode: 'USD',
    sourceAmount: '100',               // String-encoded decimal
    destinationCurrencyCode: 'USDC',
    walletAddress: await portal.address,
    redirectUrl: 'https://yourapp.example/meld/return',
  },
})

// Open in system browser
await Linking.openURL(data.widgetUrl)
```

**Signature**

```typescript theme={null}
public async createRetailWidget(
  request: MeldCreateRetailWidgetRequest
): Promise<MeldCreateRetailWidgetResponse>
```

| Parameter                   | Type                            | Required | Description                                                              |
| --------------------------- | ------------------------------- | -------- | ------------------------------------------------------------------------ |
| `request.sessionType`       | `"BUY" \| "SELL" \| "TRANSFER"` | Yes      | Flow direction.                                                          |
| `request.sessionData`       | `MeldSessionData`               | Yes      | Session configuration (see below).                                       |
| `request.externalSessionId` | `string`                        | No       | Your reference for this session — recorded on Meld's transaction.        |
| `request.customerId`        | `string`                        | No       | Meld customer ID from `createCustomer` or `searchCustomer`.              |
| `request.bypassKyc`         | `boolean`                       | No       | Skip KYC where allowed by the provider and jurisdiction. Use cautiously. |

**`MeldSessionData` fields**

| Field                     | Type       | Required | Description                                                           |
| ------------------------- | ---------- | -------- | --------------------------------------------------------------------- |
| `countryCode`             | `string`   | Yes      | ISO-3166-1 alpha-2 country code.                                      |
| `serviceProvider`         | `string`   | Yes      | Provider chosen from the quote response.                              |
| `sourceCurrencyCode`      | `string`   | Yes      | Source currency.                                                      |
| `sourceAmount`            | `string`   | Yes      | Source amount as a string-encoded decimal.                            |
| `destinationCurrencyCode` | `string`   | Yes      | Destination currency.                                                 |
| `walletAddress`           | `string`   | No       | Wallet address. Pre-fills the widget.                                 |
| `walletTag`               | `string`   | No       | Memo or destination tag for chains that require it.                   |
| `paymentMethodType`       | `string`   | No       | Pre-selects the payment method.                                       |
| `lockFields`              | `string[]` | No       | Field names the user cannot modify (for example `["walletAddress"]`). |
| `redirectUrl`             | `string`   | No       | URL Meld redirects to when the user finishes the flow.                |

**Returns** — `MeldCreateRetailWidgetResponse`: `{ data: { id: string; token: string; widgetUrl: string; customerId: string; externalCustomerId: string; externalSessionId: string } }`.

<Warning>
  `widgetUrl` embeds a single-use session token. Do not persist or reuse it. Refer to [Meld's Provider UI Launch Implementation guide](https://docs.meld.io/docs/whitelabel-api-guide#provider-ui-launch-implementation) for iframe sizing guidance and supported `lockFields` values.
</Warning>

***

### searchRetailTransactions

Lists Meld retail transactions for the current Portal client with optional filtering.

```typescript theme={null}
const { data } = await portal.ramps.meld.searchRetailTransactions()

console.log(`${data.count} of ${data.totalCount} transactions`)
data.transactions.forEach(tx => {
  console.log(tx.id, tx.status, tx.transactionType)
})

// With filters
const pending = await portal.ramps.meld.searchRetailTransactions({
  status: 'PENDING',
  limit: '20',
  offset: '0',
})
```

**Signature**

```typescript theme={null}
public async searchRetailTransactions(
  params?: MeldSearchRetailTransactionsParams
): Promise<MeldSearchRetailTransactionsResponse>
```

| Parameter       | Type     | Required | Description                                                          |
| --------------- | -------- | -------- | -------------------------------------------------------------------- |
| `params.status` | `string` | No       | Filter by transaction status (for example `"SETTLED"`, `"PENDING"`). |
| `params.limit`  | `string` | No       | Maximum number of results to return.                                 |
| `params.offset` | `string` | No       | Number of results to skip for pagination.                            |

<Note>
  These query parameters are defined in the SDK type and sent as query strings to the Portal API. Backend filtering support is subject to the Portal API version in use; confirm availability in the [Client API reference](/apis/client/api-reference-link).
</Note>

**Returns** — `MeldSearchRetailTransactionsResponse`: `{ data: { transactions: MeldTransaction[]; count: number; remaining: number; totalCount: number } }`.

***

### getRetailTransaction

Fetches a single retail transaction by its Meld transaction ID.

```typescript theme={null}
const { data } = await portal.ramps.meld.getRetailTransaction('tx_01HAAAA')

console.log(data.transaction.status)
console.log(data.transaction.destinationAmount, data.transaction.destinationCurrencyCode)
```

**Signature**

```typescript theme={null}
public async getRetailTransaction(id: string): Promise<MeldGetRetailTransactionResponse>
```

| Parameter | Type     | Required | Description          |
| --------- | -------- | -------- | -------------------- |
| `id`      | `string` | Yes      | Meld transaction ID. |

**Returns** — `MeldGetRetailTransactionResponse`: `{ data: { transaction: MeldTransaction } }`.

***

### getRetailTransactionBySession

Fetches the retail transaction associated with a widget session ID.

```typescript theme={null}
const sessionId = data.id  // From createRetailWidget response

const { data: txData } = await portal.ramps.meld.getRetailTransactionBySession(sessionId)
console.log(txData.transaction.status)
```

**Signature**

```typescript theme={null}
public async getRetailTransactionBySession(
  sessionId: string
): Promise<MeldGetRetailTransactionResponse>
```

| Parameter   | Type     | Required | Description                                                     |
| ----------- | -------- | -------- | --------------------------------------------------------------- |
| `sessionId` | `string` | Yes      | Meld session ID from `createRetailWidget` response (`data.id`). |

**Returns** — `MeldGetRetailTransactionResponse`: `{ data: { transaction: MeldTransaction } }`.

<Note>
  Prefer Meld webhooks over polling for transaction lifecycle updates. Configure them in [Meld Dashboard](https://dashboard.meld.io/) — see [Webhooks](/integrations/On-Off-Ramp/meld-webhooks).
</Note>

***

## Discovery methods

Discovery endpoints return Meld's current catalog of supported countries, currencies, payment methods, and limits. Call them at startup or lazily before populating dropdowns.

<Tip>
  Cache discovery responses client-side — countries, currencies, and payment methods change rarely and re-querying on every screen mount adds unnecessary latency.
</Tip>

All discovery methods accept an optional `MeldDiscoveryParams` object. It accepts `countryCode` and any additional filter keys Meld documents.

### getServiceProviders

```typescript theme={null}
const { data } = await portal.ramps.meld.getServiceProviders({ countryCode: 'US' })

data.forEach(provider => {
  console.log(provider.serviceProvider, provider.name, provider.categories)
})
```

**Signature**

```typescript theme={null}
public async getServiceProviders(
  params?: MeldDiscoveryParams
): Promise<MeldGetServiceProvidersResponse>
```

**Returns** — `MeldGetServiceProvidersResponse`: `{ data: MeldServiceProvider[] }`.

Each `MeldServiceProvider` includes: `serviceProvider`, `name`, `status`, `categories`, `categoryStatuses`, `websiteUrl`, `customerSupportUrl`, and `logos` (`dark`, `light`, `darkShort`, `lightShort` variants).

***

### getCountries

```typescript theme={null}
const { data } = await portal.ramps.meld.getCountries()

data.forEach(country => {
  console.log(country.countryCode, country.name)
})
```

**Signature**

```typescript theme={null}
public async getCountries(
  params?: MeldDiscoveryParams
): Promise<MeldGetCountriesResponse>
```

**Returns** — `MeldGetCountriesResponse`: `{ data: MeldCountry[] }`.

Each `MeldCountry` includes: `countryCode`, `name`, `flagImageUrl`, and optional `regions: { regionCode, name }[]`.

***

### getFiatCurrencies

```typescript theme={null}
const { data } = await portal.ramps.meld.getFiatCurrencies({ countryCode: 'US' })
```

**Signature**

```typescript theme={null}
public async getFiatCurrencies(
  params?: MeldDiscoveryParams
): Promise<MeldGetFiatCurrenciesResponse>
```

**Returns** — `MeldGetFiatCurrenciesResponse`: `{ data: MeldFiatCurrency[] }`.

Each `MeldFiatCurrency` includes: `currencyCode`, `name`, `symbolImageUrl`.

***

### getCryptoCurrencies

```typescript theme={null}
const { data } = await portal.ramps.meld.getCryptoCurrencies()

// Each token has a chain-specific Meld Code (currencyCode)
data.forEach(crypto => {
  console.log(crypto.currencyCode, crypto.name, crypto.chainCode)
})
```

**Signature**

```typescript theme={null}
public async getCryptoCurrencies(
  params?: MeldDiscoveryParams
): Promise<MeldGetCryptoCurrenciesResponse>
```

**Returns** — `MeldGetCryptoCurrenciesResponse`: `{ data: MeldCryptoCurrency[] }`.

Each `MeldCryptoCurrency` includes: `currencyCode`, `name`, `chainCode`, `chainName`, `chainId`, `contractAddress`, `symbolImageUrl`.

<Note>
  Meld doesn't accept a separate chain parameter — each token has a distinct `currencyCode` per chain (a "Meld Code"). Pass the same code from the quote into the widget session so pricing and settlement match.
</Note>

***

### getPaymentMethods

```typescript theme={null}
const { data } = await portal.ramps.meld.getPaymentMethods({ countryCode: 'US' })
```

**Signature**

```typescript theme={null}
public async getPaymentMethods(
  params?: MeldDiscoveryParams
): Promise<MeldGetPaymentMethodsResponse>
```

**Returns** — `MeldGetPaymentMethodsResponse`: `{ data: MeldPaymentMethod[] }`.

Each `MeldPaymentMethod` includes: `paymentMethod`, `name`, `paymentType`, and optional `logos`.

***

### getDefaults

Returns the default fiat currency and payment methods per country.

```typescript theme={null}
const { data } = await portal.ramps.meld.getDefaults({ countryCode: 'US' })

const usDefaults = data.find(d => d.countryCode === 'US')
console.log(usDefaults?.defaultCurrencyCode, usDefaults?.defaultPaymentMethods)
```

**Signature**

```typescript theme={null}
public async getDefaults(
  params?: MeldDiscoveryParams
): Promise<MeldGetDefaultsResponse>
```

**Returns** — `MeldGetDefaultsResponse`: `{ data: MeldCountryDefault[] }`.

Each `MeldCountryDefault` includes: `countryCode`, `defaultCurrencyCode`, `defaultPaymentMethods: string[]`.

***

### getBuyLimits

Returns minimum, maximum, and default purchase amounts per fiat currency.

```typescript theme={null}
const { data } = await portal.ramps.meld.getBuyLimits({ countryCode: 'US' })

const usd = data.find(l => l.currencyCode === 'USD')
console.log(`USD buy: $${usd?.minimumAmount} – $${usd?.maximumAmount}`)
```

**Signature**

```typescript theme={null}
public async getBuyLimits(
  params?: MeldDiscoveryParams
): Promise<MeldGetBuyLimitsResponse>
```

**Returns** — `MeldGetBuyLimitsResponse`: `{ data: MeldFiatCurrencyPurchaseLimit[] }`.

Each `MeldFiatCurrencyPurchaseLimit` includes: `currencyCode`, `defaultAmount`, `minimumAmount`, `maximumAmount`.

***

### getSellLimits

Returns minimum, maximum, and default sell amounts per crypto currency.

```typescript theme={null}
const { data } = await portal.ramps.meld.getSellLimits()
```

**Signature**

```typescript theme={null}
public async getSellLimits(
  params?: MeldDiscoveryParams
): Promise<MeldGetSellLimitsResponse>
```

**Returns** — `MeldGetSellLimitsResponse`: `{ data: MeldCryptoCurrencySellLimit[] }`.

Each `MeldCryptoCurrencySellLimit` includes: `currencyCode`, `chainCode`, `defaultAmount`, `minimumAmount`, `maximumAmount`.

***

### getKycLimits

Returns transaction limits per KYC tier and fiat currency. Useful for displaying tier requirements before starting KYC.

```typescript theme={null}
const { data } = await portal.ramps.meld.getKycLimits()

const usd = data.find(l => l.currencyCode === 'USD')
console.log('Level 1 daily limit:', usd?.level1?.dailyLimit)
console.log('Level 2 monthly limit:', usd?.level2?.monthlyLimit)
```

**Signature**

```typescript theme={null}
public async getKycLimits(
  params?: MeldDiscoveryParams
): Promise<MeldGetKycLimitsResponse>
```

**Returns** — `MeldGetKycLimitsResponse`: `{ data: MeldKycFiatLevel[] }`.

Each `MeldKycFiatLevel` includes: `currencyCode` and optional `level1`, `level2`, `level3` of type `MeldKycLimitTier`, each containing `dailyLimit`, `weeklyLimit`, `monthlyLimit`, `yearlyLimit`, and `transactionLimit`.

***

## End-to-end example

The following example shows the recommended buy flow: discover, quote, create a widget session, and then look up the transaction status after the user returns.

```typescript theme={null}
import { Portal } from '@portal-hq/core'
import { Linking } from 'react-native'

async function startMeldBuyFlow(portal: Portal) {
  // 1. Discover — get buy limits and available providers for US
  const limitsRes = await portal.ramps.meld.getBuyLimits({ countryCode: 'US' })
  const usdLimit = limitsRes.data.find(l => l.currencyCode === 'USD')
  console.log(`Min: $${usdLimit?.minimumAmount}, Max: $${usdLimit?.maximumAmount}`)

  // 2. Quote
  const quoteRes = await portal.ramps.meld.getRetailQuote({
    countryCode: 'US',
    sourceCurrencyCode: 'USD',
    destinationCurrencyCode: 'USDC',
    sourceAmount: 100,
    walletAddress: await portal.address,
  })
  const best = quoteRes.data.quotes[0]

  // 3. Create widget session
  const widgetRes = await portal.ramps.meld.createRetailWidget({
    sessionType: 'BUY',
    sessionData: {
      countryCode: 'US',
      serviceProvider: best.serviceProvider,
      sourceCurrencyCode: 'USD',
      sourceAmount: '100',
      destinationCurrencyCode: 'USDC',
      walletAddress: await portal.address,
    },
  })

  // 4. Open widget in system browser
  await Linking.openURL(widgetRes.data.widgetUrl)

  // 5. After the user returns, look up the transaction
  const txRes = await portal.ramps.meld.getRetailTransactionBySession(widgetRes.data.id)
  console.log('Transaction status:', txRes.data.transaction.status)
}
```

***

## Error handling

Wrap calls in `try/catch`. Portal forwards Meld's HTTP status codes (4xx/5xx) and preserves the upstream error message.

Common Portal-side errors:

* `400 Meld integration is not enabled` — turn on the Meld integration for the current Portal environment in the [Portal Dashboard](https://app.portalhq.io).
* `400 Meld API key is not configured for this environment` — paste a valid Meld API key into the integration config.
* `409 Conflict` (on `createCustomer`) — a Meld customer already exists for this Portal client. Call `searchCustomer()` to retrieve it.

## Related documentation

* [React Native SDK reference — `@portal-hq/core`](/sdks/react-native/reference/core) (section **portal.ramps.meld (Meld)**)
* [Meld integration overview](/integrations/On-Off-Ramp/meld)
* [White-Label API guide](/integrations/On-Off-Ramp/meld-widget)
* [Webhooks](/integrations/On-Off-Ramp/meld-webhooks)
