KORAMP

Creating Transactions

End-to-end guide for creating transactions in KORAMP: quotes, Top Up and Sell orders, Xendit payment, crypto delivery, and testnet code samples.

Overview

Every KORAMP transaction goes through the same four stages:

  1. Quote — a locked price is created for an IDR or crypto amount.
  2. Order — an order is created by atomically consuming the quote (single use).
  3. Payment / deposit — the user pays (Top Up via Xendit QRIS) or sends crypto (Sell).
  4. Settlement — the platform sends crypto (Top Up) or pays out IDR to a bank (Sell).

All examples below use testnet networks and the base URL https://your-koramp-instance/api.

Test Prerequisites

  1. An EVM (MetaMask) or Solana (Phantom) wallet with a validated address format.
  2. Testnet networks added to the wallet:
NetworkChain IDTestnet
Solana—Devnet
Base84532Sepolia
BSC97Testnet
  1. Testnet faucets for gas (Base Sepolia, BSC Testnet) and SOL Devnet.
  2. A still-valid quote (auto-expires).

Step 1 — Create a Quote

A quote gives a locked price and an ID used when creating the order.

Top Up (user pays IDR):

bash
curl -X POST https://your-koramp-instance/api/quotes/topup \
  -H "Content-Type: application/json" \
  -d '{
    "asset": "ETH",
    "network": "BASE",
    "idrAmount": "500000"
  }'

Sell (user sells crypto):

bash
curl -X POST https://your-koramp-instance/api/quotes/sell \
  -H "Content-Type: application/json" \
  -d '{
    "asset": "ETH",
    "network": "BASE",
    "idrAmount": "500000"
  }'

Sample response (200):

json
{
  "quoteId": "3f8a1c2e-9b4d-4e5f-8a6b-7c8d9e0f1a2b",
  "type": "TOP_UP",
  "asset": "ETH",
  "network": "BASE",
  "marketPrice": "26500000.00",
  "exchangeRate": "26632500.00",
  "idrAmount": "500000.00",
  "cryptoAmount": "0.01877326",
  "serviceFee": "5000.00",
  "networkFee": "2500.00",
  "tax": "5500.00",
  "totalIdr": "500000.00",
  "expiresAt": "2026-10-18T10:30:00.000Z"
}

Single-use quote

Once used to create an order, the quote is marked consumed and cannot be reused. Save the quoteId — it's required when creating the order.

Step 2 — Create a Top Up Order

The Top Up order and the Xendit payment request are created server-side.

bash
curl -X POST https://your-koramp-instance/api/orders/topup \
  -H "Content-Type: application/json" \
  -d '{
    "walletAddress": "0x1234...abcd",
    "walletType": "EVM",
    "quoteId": "3f8a1c2e-9b4d-4e5f-8a6b-7c8d9e0f1a2b",
    "asset": "ETH",
    "network": "BASE"
  }'

Sample response (201):

json
{
  "order": {
    "publicId": "krm-8x4k2m9p",
    "status": "PAYMENT_PENDING",
    "walletAddress": "0x1234...abcd",
    "assetSymbol": "ETH",
    "network": "BASE",
    "cryptoAmount": "0.01877326",
    "totalIdr": 500000,
    "expiresAt": "2026-10-18T10:30:00.000Z"
  },
  "payment": {
    "status": "PENDING",
    "qrPayload": "00020101021226...",
    "requestedAmount": 500000
  }
}

Amount limits

Minimum and maximum order limits are set per asset and read from fee configuration. Out-of-range amounts are rejected at order creation.

Step 3 — Pay via Xendit QRIS

  1. Render the qrPayload string from the order response as a QR code in your app or checkout page.
  2. The user scans the QRIS code with their banking/e-wallet app and pays.
  3. Xendit sends a webhook to the KORAMP server when payment succeeds.
  4. The server independently verifies the amount, then triggers crypto delivery to the destination wallet.

Crypto delivery is handled automatically by the platform. You just monitor the status.

Step 4 — Monitor Delivery (Top Up)

Check order status:

bash
curl https://your-koramp-instance/api/orders/krm-8x4k2m9p

Re-check on-chain delivery (useful if status is stuck at CRYPTO_PROCESSING):

bash
curl -X POST https://your-koramp-instance/api/orders/krm-8x4k2m9p/check-delivery

Sample response once confirmed:

json
{
  "status": "COMPLETED",
  "txHash": "0xabc123...def456",
  "confirmations": 12
}

Sell Flow — Full Summary

  1. Create a SELL quote (Step 1).
  2. Create a Sell order:
bash
curl -X POST https://your-koramp-instance/api/orders/sell \
  -H "Content-Type: application/json" \
  -d '{
    "walletAddress": "0x1234...abcd",
    "walletType": "EVM",
    "quoteId": "3f8a1c2e-9b4d-4e5f-8a6b-7c8d9e0f1a2b",
    "asset": "ETH",
    "network": "BASE",
    "bankName": "BCA",
    "accountNumber": "1234567890",
    "accountName": "Budi Santoso"
  }'

The response contains depositAddress — the address the user sends crypto to.

json
{
  "order": {
    "publicId": "kms-3n7q1w5e",
    "status": "AWAITING_CRYPTO",
    "depositAddress": "0x9876...wxyz",
    "cryptoAmount": "0.01877326",
    "requiredConfirmations": 12,
    "expiresAt": "2026-10-18T11:00:00.000Z"
  }
}
  1. Send crypto from the user's wallet to depositAddress with an amount exactly equal to cryptoAmount.
  2. Submit the transaction hash (required for EVM networks for fast tracking):
bash
curl -X POST https://your-koramp-instance/api/orders/kms-3n7q1w5e/submit-tx \
  -H "Content-Type: application/json" \
  -d '{ "txHash": "0xabc123...def456" }'

The backend independently verifies the hash: sender, recipient, amount, and receipt status. Invalid hashes are rejected and logged to the audit log.

  1. Automatic payout — once on-chain confirmations reach requiredConfirmations, IDR is transferred to the bank account via Xendit Payout v3.

TypeScript Client Example

Helper sample to test the full flow from an app or script:

typescript
const BASE_URL = "https://your-koramp-instance/api";
 
async function createTopUpOrder(walletAddress: string) {
  const quote = await fetch(`${BASE_URL}/quotes/topup`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ asset: "ETH", network: "BASE", idrAmount: "500000" }),
  }).then((r) => r.json());
 
  const res = await fetch(`${BASE_URL}/orders/topup`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      walletAddress,
      walletType: "EVM",
      quoteId: quote.quoteId,
      asset: "ETH",
      network: "BASE",
    }),
  });
 
  if (!res.ok) throw new Error(`Order failed: ${res.status}`);
  return res.json(); // { order, payment }
}
 
async function pollUntilDone(publicId: string, tries = 20) {
  for (let i = 0; i < tries; i++) {
    const order = await fetch(`${BASE_URL}/orders/${publicId}`).then((r) => r.json());
    if (order.status === "COMPLETED") return order;
    if (order.status === "FAILED") throw new Error(order.failureReason ?? "Order failed");
    await new Promise((r) => setTimeout(r, 10_000));
  }
  throw new Error("Timeout waiting for settlement");
}
 
async function testDelivery(publicId: string) {
  const res = await fetch(`${BASE_URL}/orders/${publicId}/check-delivery`, { method: "POST" });
  return res.json(); // { status, txHash?, confirmations? }
}

Testnet Test Checklist

Use this checklist to test the end-to-end flow before production:

#ScenarioVerified
1Create a quote then a Top Up orderQuote consumed atomically; cannot be used twice.
2Pay the QRIS qrPayloadWebhook received; amount verified; crypto sent.
3Order stuck at CRYPTO_PROCESSINGcheck-delivery resumes without double-broadcast.
4Create a Sell orderUnique deposit address per order; bank details stored.
5Send crypto then submit-txHash verified on-chain (from/to/amount/receipt).
6Submit a tx hash from another walletRejected: from must match the order wallet.
7Repeat submit-tx with the same hashIdempotent — no double-processing.
8Fulfill confirmationsPayout triggered automatically via Xendit Payout v3.
9Let the order expireOrder ends with failureReason; doesn't hang.

Don't test on mainnet

All testing must be done on testnet (Solana Devnet, Base Sepolia, BSC Testnet). On-chain transfers are public and irreversible.

Order Statuses to Know

StatusMeaning
PAYMENT_PENDINGAwaiting Xendit QRIS payment (Top Up).
CRYPTO_PROCESSINGPayment received; crypto being sent/confirmed.
AWAITING_CRYPTOAwaiting crypto deposit from the user (Sell).
CRYPTO_DETECTEDDeposit seen on-chain, not enough confirmations yet.
CONFIRMINGConfirmations progressing toward requiredConfirmations.
CRYPTO_CONFIRMEDConfirmations sufficient; payout being processed.
COMPLETEDTransaction complete.
FAILED / PAYMENT_CREATE_FAILEDFailed; see failureReason for details.

Next Steps