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

# Fiat-to-Crypto and Crypto-to-Fiat

> Build on-ramp and off-ramp flows to convert between fiat currencies and cryptocurrencies

export const FeatureCardGrid = ({cols = 3, children}) => <div className={`not-prose feature-cards-grid feature-cards-cols-${cols}`}>
    {children}
  </div>;

export const FeatureCard = ({icon, title, children, href, linkHref, linkText, color, tag, tagPosition, layout, variant, iconSize}) => {
  const isHorizontal = layout === 'horizontal';
  const isFlat = variant === 'flat';
  const isLargeIcon = iconSize === 'lg';
  const isInlineTag = tagPosition === 'inline';
  const card = <div className={`feature-card ${href ? 'feature-card-link' : ''} ${!icon ? 'feature-card-no-icon' : ''} ${isHorizontal ? 'feature-card-horizontal' : ''} ${isFlat ? 'feature-card-flat' : ''} ${isLargeIcon ? 'feature-card-icon-lg' : ''}`}>
      {icon && <div className="feature-card-icon-wrapper">
          {color ? <div className="feature-card-icon" style={{
    WebkitMaskImage: `url(${icon})`,
    maskImage: `url(${icon})`,
    backgroundColor: color,
    width: '24px',
    height: '24px',
    WebkitMaskSize: 'contain',
    maskSize: 'contain',
    WebkitMaskRepeat: 'no-repeat',
    maskRepeat: 'no-repeat'
  }} /> : <img src={icon} alt="" className="feature-card-icon" />}
        </div>}
      <div className="feature-card-content">
        {isInlineTag ? <div className="feature-card-title-row">
            <span className="feature-card-title">{title}</span>
            {tag && <span className="feature-card-tag">{tag}</span>}
          </div> : <div className="feature-card-title">{title}</div>}
        <div className="feature-card-desc">{children}</div>
        {tag && !isInlineTag && <div className="feature-card-tag-row"><span className="feature-card-tag">{tag}</span></div>}
        {linkText && <div className="feature-card-link-row">
            {linkHref ? <a href={linkHref} className="feature-card-text-link" style={{
    color: color
  }}>
                {linkText}
              </a> : <span className="feature-card-text-link feature-card-coming-soon" style={{
    color: color,
    opacity: 0.6
  }}>
                {linkText}
              </span>}
          </div>}
      </div>
    </div>;
  return href ? <a href={href} className="feature-card-anchor">{card}</a> : card;
};

## Overview

Grid enables seamless conversion between fiat currencies and cryptocurrencies via the Lightning Network. Use quotes to lock exchange rates and get payment instructions for completing transfers.

**On-ramp (Fiat → Crypto):** User sends fiat → Grid detects payment → Crypto sent to wallet

**Off-ramp (Crypto → Fiat):** Execute quote → Grid processes crypto → Fiat sent to bank

## Prerequisites

* Customer created in Grid
* **On-ramps:** Destination crypto wallet (Spark address) + webhook endpoint
* **Off-ramps:** Internal account with crypto + external bank account registered

## On-ramp: Fiat to crypto

### Create a quote

First, create an external account for the crypto destination:

```bash cURL theme={null}
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/customers/external-accounts' \
  -H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET' \
  -H 'Content-Type: application/json' \
  -d '{
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "currency": "BTC",
    "accountInfo": {
      "accountType": "SPARK_WALLET",
      "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
    }
  }'
```

Then create a quote using the external account ID:

```bash cURL theme={null}
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
  -H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET' \
  -H 'Content-Type: application/json' \
  -d '{
    "source": {
      "sourceType": "REALTIME_FUNDING",
      "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
      "currency": "USD"
    },
    "destination": {
      "destinationType": "ACCOUNT",
      "accountId": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456"
    },
    "lockedCurrencySide": "RECEIVING",
    "lockedCurrencyAmount": 100000,
    "description": "Buy 0.001 BTC"
  }'
```

**Response includes payment instructions:**

```json theme={null}
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000006",
  "status": "PENDING",
  "createdAt": "2025-10-03T12:00:00Z",
  "expiresAt": "2025-10-03T12:05:00Z",
  "source": {
    "sourceType": "REALTIME_FUNDING",
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
    "currency": "USD"
  },
  "destination": {
    "destinationType": "ACCOUNT",
    "accountId": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456"
  },
  "sendingCurrency": { "code": "USD", "name": "United States Dollar", "symbol": "$", "decimals": 2 },
  "receivingCurrency": { "code": "BTC", "name": "Bitcoin", "symbol": "₿", "decimals": 8 },
  "totalSendingAmount": 6500,
  "totalReceivingAmount": 100000,
  "exchangeRate": 15.38,
  "feesIncluded": 200,
  "transactionId": "Transaction:019542f5-b3e7-1d02-0000-000000000007",
  "paymentInstructions": [
    {
      "accountOrWalletInfo": {
        "accountType": "US_ACCOUNT",
        "reference": "UMA-Q12345-REF",
        "accountNumber": "1234567890",
        "routingNumber": "021000021",
        "bankName": "Grid Settlement Bank"
      }
    },
    {
      "accountOrWalletInfo": {
        "accountType": "SOLANA_WALLET",
        "assetType": "USDC",
        "address": "4Nd1m6Qkq7RfKuE5vQ9qP9Tn6H94Ueqb4xXHzsAbd8Wg"
      }
    }
  ]
}
```

### Display payment instructions

```javascript theme={null}
function displayPaymentInstructions(quote) {
  const instructions = quote.paymentInstructions[0];

  return {
    amount: `$${(quote.totalSendingAmount / 100).toFixed(2)}`,
    bankName: instructions.accountOrWalletInfo.bankName,
    accountNumber: instructions.accountOrWalletInfo.accountNumber,
    routingNumber: instructions.accountOrWalletInfo.routingNumber,
    referenceCode: instructions.accountOrWalletInfo.reference, // User must include this
    expiresAt: quote.expiresAt,
    willReceive: `${quote.totalReceivingAmount / 100000000} BTC`,
  };
}
```

### Monitor completion

Grid sends `OUTGOING_PAYMENT.<STATUS>` webhooks as the transaction progresses:

```javascript theme={null}
app.post("/webhooks/grid", async (req, res) => {
  const { type, data } = req.body;

  if (type === "OUTGOING_PAYMENT.COMPLETED") {
    await notifyUser(data.customerId, {
      message: "Your Bitcoin purchase is complete!",
      amount: `${data.receivedAmount.amount / 100000000} BTC`,
    });
  } else if (type === "OUTGOING_PAYMENT.FAILED") {
    await notifyUser(data.customerId, {
      message: `Purchase failed: ${data.failureReason}`,
    });
    // Refund webhook (OUTGOING_PAYMENT.REFUND_*) will follow
  }

  res.status(200).json({ received: true });
});
```

See the [Transaction Lifecycle](/platform-overview/core-concepts/transaction-lifecycle) guide for all status transitions and refund handling.

## Off-ramp: Crypto to fiat

### Create and execute a quote

```javascript theme={null}
// 1. Create quote
const quote = await fetch("https://api.lightspark.com/grid/2025-10-13/quotes", {
  method: "POST",
  body: JSON.stringify({
    source: {
      sourceType: "ACCOUNT",
      accountId: "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123",
    },
    destination: {
      destinationType: "ACCOUNT",
      accountId: "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965",
    },
    lockedCurrencySide: "SENDING",
    lockedCurrencyAmount: 100000, // 0.001 BTC
    description: "Sell 0.001 BTC",
  }),
}).then((r) => r.json());

// 2. Execute quote
const result = await fetch(
  `https://api.lightspark.com/grid/2025-10-13/quotes/${quote.id}/execute`,
  {
    method: "POST",
    headers: { Authorization: `Basic ${credentials}` },
  }
).then((r) => r.json());
```

### Track completion

```javascript theme={null}
app.post("/webhooks/grid", async (req, res) => {
  const { type, data } = req.body;

  if (type === "OUTGOING_PAYMENT.COMPLETED") {
    await notifyUser(data.customerId, {
      message: "Your USD withdrawal is complete!",
      amount: `$${data.receivedAmount.amount / 100}`,
    });
  } else if (type === "OUTGOING_PAYMENT.FAILED") {
    await notifyUser(data.customerId, {
      message: `Withdrawal failed: ${data.failureReason}`,
    });
  }

  res.status(200).json({ received: true });
});
```

## Immediate execution

For instant on-ramps (e.g., reward payouts), use `immediatelyExecute: true` with an internal account source:

```bash cURL theme={null}
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/quotes' \
  -H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET' \
  -H 'Content-Type: application/json' \
  -d '{
    "source": {
      "sourceType": "ACCOUNT",
      "accountId": "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123"
    },
    "destination": {
      "destinationType": "ACCOUNT",
      "accountId": "ExternalAccount:b23dcbd6-dced-4ec4-b756-3c3a9ea3d456"
    },
    "lockedCurrencySide": "RECEIVING",
    "lockedCurrencyAmount": 100000,
    "immediatelyExecute": true
  }'
```

<Info>
  `immediatelyExecute` can only be used with sources that are either internal accounts or external accounts with direct pull functionality (e.g., ACH pull).
</Info>

## Best practices

<AccordionGroup>
  <Accordion title="Handle quote expiration">
    ```javascript theme={null}
    async function refreshQuoteIfNeeded(quote) {
      const expiresAt = new Date(quote.expiresAt);
      const now = new Date();

      if (expiresAt - now < 60000) {
        // Less than 1 minute left
        return await createNewQuote(quote.originalParams);
      }

      return quote;
    }
    ```
  </Accordion>

  <Accordion title="Settlement times by payment rail">
    ```javascript theme={null}
    const settlementTimes = {
      US_ACCOUNT: "1-3 business days (ACH)",
      WIRE: "Same day",
      SPARK_WALLET: "Instant",
      PIX: "Instant",
      SEPA: "1-2 business days",
    };
    ```
  </Accordion>

  <Accordion title="Handle failed transactions">
    ```javascript theme={null}
    if (transaction.status === "FAILED") {
      await notifyUser(transaction.customerId, {
        message: "Transaction failed",
        reason: transaction.failureReason,
        action: "retry",
      });

      if (transaction.failureReason === "QUOTE_EXPIRED") {
        await createNewQuote(transaction.originalParams);
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Next steps

<FeatureCardGrid cols={2}>
  <FeatureCard icon="/images/icons/wallet1.svg" title="Self-custody wallets" href="/ramps/conversion-flows/self-custody-wallets">
    Send crypto to user-controlled wallets
  </FeatureCard>

  <FeatureCard icon="/images/icons/bell.svg" title="Webhooks" href="/ramps/platform-tools/webhooks">
    Monitor transaction status
  </FeatureCard>

  <FeatureCard icon="/images/icons/code.svg" title="API Reference" href="/api-reference">
    Complete API documentation
  </FeatureCard>
</FeatureCardGrid>
