Skip to main content

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

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": {
      "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
      "currency": "USD"
    },
    "destination": {
      "externalAccountDetails": {
        "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
        "currency": "BTC",
        "beneficiary": {
          "counterPartyType": "INDIVIDUAL",
          "fullName": "John Doe",
          "email": "john@example.com"
        },
        "accountInfo": {
          "accountType": "SPARK_WALLET",
          "address": "spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu"
        }
      }
    },
    "lockedCurrencySide": "RECEIVING",
    "lockedCurrencyAmount": 100000,
    "description": "Buy 0.001 BTC"
  }'
Response includes payment instructions:
{
  "id": "Quote:019542f5-b3e7-1d02-0000-000000000006",
  "totalSendingAmount": 6500,
  "receivingAmount": 100000,
  "expiresAt": "2025-10-03T12:05:00Z",
  "paymentInstructions": [
    {
      "reference": "UMA-Q12345-REF",
      "bankAccountInfo": {
        "accountNumber": "1234567890",
        "routingNumber": "021000021",
        "bankName": "Grid Settlement Bank"
      }
    }
  ]
}

Display payment instructions

function displayPaymentInstructions(quote) {
  const instructions = quote.paymentInstructions[0];

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

Monitor completion

Grid sends a webhook when the transfer completes:
app.post("/webhooks/grid", async (req, res) => {
  const { type, transaction } = req.body;

  if (type === "OUTGOING_PAYMENT" && transaction.status === "COMPLETED") {
    await notifyUser(transaction.customerId, {
      message: "Your Bitcoin purchase is complete!",
      amount: `${transaction.receivedAmount.amount / 100000000} BTC`,
    });
  }

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

Off-ramp: Crypto to fiat

Create and execute a quote

// 1. Create quote
const quote = await fetch("https://api.lightspark.com/grid/2025-10-13/quotes", {
  method: "POST",
  body: JSON.stringify({
    source: {
      accountId: "InternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123",
    },
    destination: {
      accountId: "ExternalAccount:e85dcbd6-dced-4ec4-b756-3c3a9ea3d965",
      currency: "USD",
    },
    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

app.post("/webhooks/grid", async (req, res) => {
  const { type, transaction } = req.body;

  if (type === "OUTGOING_PAYMENT" && transaction.status === "COMPLETED") {
    await notifyUser(transaction.customerId, {
      message: "Your USD withdrawal is complete!",
      amount: `$${transaction.receivedAmount.amount / 100}`,
    });
  }

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

Immediate execution

For instant on-ramps (e.g., reward payouts), use immediatelyExecute: true:
const quote = await fetch("https://api.lightspark.com/grid/2025-10-13/quotes", {
  method: "POST",
  body: JSON.stringify({
    source: { customerId: "Customer:...", currency: "USD" },
    destination: {
      externalAccountDetails: {
        /* wallet details */
      },
    },
    lockedCurrencySide: "RECEIVING",
    lockedCurrencyAmount: 100000,
    immediatelyExecute: true,
  }),
}).then((r) => r.json());

Best practices

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;
}
const settlementTimes = {
  US_ACCOUNT: "1-3 business days (ACH)",
  WIRE: "Same day",
  SPARK_WALLET: "Instant",
  PIX: "Instant",
  SEPA: "1-2 business days",
};
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);
  }
}

Next steps

I