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

# Automotive Prepayment — Karma Automotive

> **See also:** [API Reference Examples](/contracts-sdk/examples/overview) — executable TypeScript walkthroughs.

## The Business

**Karma Automotive** sells luxury electric vehicles. Customers place prepayment deposits when ordering a vehicle, with the full balance due before delivery. If the vehicle is not delivered within the agreed timeframe (e.g. 6 months), the customer is entitled to a full refund of their deposit.

## Why Oak?

Karma Automotive needs:

* **Time-constrained escrow** — funds are locked with a hard deadline; if delivery doesn't happen by the deadline, the buyer is automatically protected
* **Structured payments** — line items for base price, options packages, and delivery fees
* **Automatic expiry protection** — after the deadline + claim delay, expired funds can be swept back to the buyer
* **Transparent fee tracking** — dealer fees, protocol fees, all visible on-chain
* **Trust for high-value transactions** — a \$50,000+ vehicle deposit requires stronger guarantees than a traditional wire transfer

## Oak Contract Used

| Contract                           | Purpose                                                                                                                                                                                                                                       |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **CampaignInfoFactory**            | Creates the CampaignInfo contract that holds NFT receipts and the accepted token list                                                                                                                                                         |
| **TreasuryFactory**                | Deploys the TimeConstrainedPaymentTreasury clone linked to the CampaignInfo                                                                                                                                                                   |
| **TimeConstrainedPaymentTreasury** | Identical to PaymentTreasury in its SDK interface (both use `oak.paymentTreasury()`), but enforces launch-time and deadline constraints on-chain. After the deadline passes and the claim delay expires, `claimExpiredFunds` becomes callable |

### Multi-token support

Like **PaymentTreasury**, the time-constrained variant is **multi-token**: **`paymentToken`** must be accepted for the campaign, and all pending / confirmed / fee accounting is **per token address** in that token's decimals. The Karma example uses **USDT** only as a familiar stablecoin; deposits and `claimNonGoalLineItems` can use **any accepted ERC-20** from the campaign whitelist. Whitelist source: **`GlobalParams`** (`getTokensForCurrency` / owner `addTokenToCurrency`); per-campaign cache: **`campaign.getAcceptedTokens()`**.

## Roles

| Role                                 | Who                     | On-Chain Functions                                                                                                                                                                                                                               |
| ------------------------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Platform Admin**                   | Karma's ordering system | `createPayment`, `createPaymentBatch`, `confirmPayment`, `confirmPaymentBatch`, `cancelPayment`, `claimRefund(paymentId, address)` (non-NFT), `claimExpiredFunds`, `claimNonGoalLineItems`, `pauseTreasury`, `unpauseTreasury`, `cancelTreasury` |
| **Platform Admin or Campaign Owner** | Karma or dealer         | `withdraw`, `cancelTreasury`                                                                                                                                                                                                                     |
| **Buyer**                            | Vehicle customer        | ERC-20 `approve`, `processCryptoPayment`, `claimRefundSelf(paymentId)` (NFT payments)                                                                                                                                                            |
| **Dealer (Campaign Owner)**          | Karma dealership        | Receives funds after `withdraw`                                                                                                                                                                                                                  |
| **Protocol Admin**                   | Oak protocol            | Receives protocol fees (via `disburseFees`)                                                                                                                                                                                                      |
| **Any caller**                       | Anyone                  | `disburseFees`, all read functions (`getPaymentData`, `getRaisedAmount`, `getExpectedAmount`, `paused`, etc.)                                                                                                                                    |

> **Note on time constraints:** Unlike the standard PaymentTreasury, `createPayment`, `createPaymentBatch`, `processCryptoPayment`, `cancelPayment`, `confirmPayment`, and `confirmPaymentBatch` must be called while the current time is within `launchTime` … `deadline + bufferTime` (per `TimestampChecker`). `claimRefund`, `claimRefundSelf`, `claimExpiredFunds`, `disburseFees`, `withdraw`, and `claimNonGoalLineItems` require the current time to be **after** `launchTime` (they use `_checkTimeIsGreater()`).

## Integration Flow

### Step 1: Create a CampaignInfo contract

> **Role: Any caller** — `createCampaign` is permissionless.

Before deploying a TimeConstrainedPaymentTreasury, Karma needs a CampaignInfo contract. This holds NFT receipts for crypto payments and defines the accepted token list.

```typescript theme={null}
import {
  createOakContractsClient, keccak256, toHex,
  getCurrentTimestamp, addDays, CHAIN_IDS, CAMPAIGN_INFO_FACTORY_EVENTS,
} from "@oaknetwork/contracts-sdk";

const oak = createOakContractsClient({
  chainId: CHAIN_IDS.CELO_TESTNET_SEPOLIA,
  rpcUrl: process.env.RPC_URL,
  privateKey: process.env.KARMA_PLATFORM_KEY as `0x${string}`,
});

const factory = oak.campaignInfoFactory(CAMPAIGN_INFO_FACTORY_ADDRESS);

const platformHash = keccak256(toHex("karma-automotive"));
const identifierHash = keccak256(toHex("karma-gs6-preorders-2026"));
const now = getCurrentTimestamp();

const txHash = await factory.createCampaign({
  creator: KARMA_ADMIN_ADDRESS,
  identifierHash,
  selectedPlatformHash: [platformHash],
  campaignData: {
    launchTime: now,
    deadline: addDays(now, 180),    // 6-month delivery window
    goalAmount: 0n,
    currency: toHex("USD", { size: 32 }),
  },
  nftName: "Karma GS-6 Deposits",
  nftSymbol: "KGS6",
  nftImageURI: "ipfs://QmXyz.../karma-gs6.png",
  contractURI: "ipfs://QmXyz.../metadata.json",
});

const receipt = await oak.waitForReceipt(txHash);

let campaignInfoAddress: `0x${string}` | undefined;
for (const log of receipt.logs) {
  try {
    const decoded = factory.events.decodeLog({
      topics: log.topics as [`0x${string}`, ...`0x${string}`[]],
      data: log.data as `0x${string}`,
    });
    if (decoded.eventName === CAMPAIGN_INFO_FACTORY_EVENTS.CampaignCreated) {
      campaignInfoAddress = decoded.args?.campaignInfoAddress as `0x${string}`;
      break;
    }
  } catch { /* log from a different contract */ }
}
```

### Step 2: Deploy the TimeConstrainedPaymentTreasury

> **Role: Any caller** — `deploy` on TreasuryFactory is permissionless (the implementation must have been registered and approved during platform onboarding).

Karma deploys a TimeConstrainedPaymentTreasury linked to the CampaignInfo from Step 1. The time constraints (launch time and deadline) are enforced on-chain by the contract.

```typescript theme={null}
import { TREASURY_FACTORY_EVENTS } from "@oaknetwork/contracts-sdk";

const treasuryFactory = oak.treasuryFactory(TREASURY_FACTORY_ADDRESS);

const deployTxHash = await treasuryFactory.deploy(
  platformHash,
  campaignInfoAddress!,
  3n, // TimeConstrainedPaymentTreasury implementation ID
);

const deployReceipt = await oak.waitForReceipt(deployTxHash);

let treasuryAddress: `0x${string}` | undefined;
for (const log of deployReceipt.logs) {
  try {
    const decoded = treasuryFactory.events.decodeLog({
      topics: log.topics as [`0x${string}`, ...`0x${string}`[]],
      data: log.data as `0x${string}`,
    });
    if (decoded.eventName === TREASURY_FACTORY_EVENTS.TreasuryDeployed) {
      treasuryAddress = decoded.args?.treasuryAddress as `0x${string}`;
      break;
    }
  } catch { /* log from a different contract */ }
}

// TimeConstrainedPaymentTreasury uses the same SDK entity as PaymentTreasury
const treasury = oak.paymentTreasury(treasuryAddress!);
```

### Step 3: Customer orders a vehicle — two independent payment flows

James orders a Karma GS-6 electric sedan with the Performance Package. The total prepayment is \$52,500 broken down into line items.

Karma supports two payment methods — they are **not** sequential steps:

#### Flow A: Off-chain / fiat payment (`createPayment`)

> **Role: Platform Admin** — only the platform admin can create payment records. Must be called within the time window (`launchTime` to `deadline + bufferTime`).

Karma's system creates a payment record on-chain. The **`createPayment` transaction does not pull ERC-20 from the buyer's wallet** — it records the order and pending accounting. James pays through off-chain rails (wire transfer, dealership financing, etc.). Before `confirmPayment`, **the treasury must hold enough of the payment token on-chain** (for example the platform deposits stablecoins after settlement). The contract checks the treasury balance when confirming.

```typescript theme={null}
const orderId = toHex("karma-order-GS6-2026-0415", { size: 32 });
const buyerId = toHex("customer-james-091", { size: 32 });
const itemId = toHex("karma-gs6-performance", { size: 32 });

const lineItems = [
  { typeId: toHex("vehicle-base",      { size: 32 }), amount: 45_000_000000n },  // $45,000 USDT (6 decimals)
  { typeId: toHex("performance-pkg",   { size: 32 }), amount: 5_500_000000n },  // $5,500
  { typeId: toHex("delivery-fee",      { size: 32 }), amount: 2_000_000000n },  // $2,000
];

const externalFees = [
  { feeType: toHex("dealer-processing", { size: 32 }), feeAmount: 500_000000n },  // $500
];

const totalAmount = 52_500_000000n; // $52,500 USDT
// Delivery deadline: 6 months from now
const expiration = BigInt(Math.floor(Date.now() / 1000) + 180 * 86400);

await treasury.simulate.createPayment(
  orderId, buyerId, itemId, USDT_TOKEN_ADDRESS,
  totalAmount, expiration, lineItems, externalFees,
);

const txHash = await treasury.createPayment(
  orderId, buyerId, itemId, USDT_TOKEN_ADDRESS,
  totalAmount, expiration, lineItems, externalFees,
);
await oak.waitForReceipt(txHash);
```

After `createPayment`, **fund the treasury** with the agreed token amount before calling `confirmPayment` (operational path is product-specific).

##### Confirm after delivery (platform admin)

> **Role: Platform Admin** — `confirmPayment` must still be within the launch…deadline+buffer window.

The GS-6 is manufactured and delivered to James. After the treasury holds the required tokens and Karma verifies delivery (still within the time window):

```typescript theme={null}
await treasury.simulate.confirmPayment(orderId, JAMES_WALLET_ADDRESS);
const confirmTx = await treasury.confirmPayment(orderId, JAMES_WALLET_ADDRESS);
await oak.waitForReceipt(confirmTx);
```

#### Flow B: On-chain crypto payment (`processCryptoPayment`)

> **Role: Any caller** — `processCryptoPayment` is permissionless, but the buyer must first approve the treasury to transfer their ERC-20 tokens. Must be called within the time window.

This is a **standalone operation** — it creates the payment record AND transfers ERC-20 tokens in a single transaction. It does **not** require or complete a prior `createPayment` call. An NFT is minted to James as proof of payment.

James transfers the full prepayment amount. Before the treasury can pull funds, James must grant an ERC-20 allowance:

```typescript theme={null}
import { erc20Abi } from "viem";

const usdt = { address: USDT_TOKEN_ADDRESS, abi: erc20Abi };

// James approves the treasury to spend $52,500 USDT
const approveTx = await walletClient.writeContract({
  ...usdt,
  functionName: "approve",
  args: [TIME_CONSTRAINED_TREASURY_ADDRESS, totalAmount],
});
await publicClient.waitForTransactionReceipt({ hash: approveTx });
```

Now the payment can be processed:

```typescript theme={null}
const txHash = await treasury.processCryptoPayment(
  orderId, itemId, JAMES_WALLET_ADDRESS, USDT_TOKEN_ADDRESS,
  totalAmount, lineItems, externalFees,
);
await oak.waitForReceipt(txHash);
```

### Step 4: Monitor the order status

> **Role: Any caller** — all read functions are public.

Karma's dashboard tracks the prepayment and treasury health.

```typescript theme={null}
// Read the specific order
const paymentData = await treasury.getPaymentData(orderId);
// Flow A (createPayment): paymentData.isConfirmed === false until Flow A confirm in Step 3
// Flow B (processCryptoPayment): paymentData.isConfirmed === true after Step 3 Flow B
// paymentData.expiration — the delivery deadline (Flow A); crypto payments use expiration 0 on-chain

// Treasury-level metrics
const [raised, available, expected] = await oak.multicall([
  () => treasury.getRaisedAmount(),
  () => treasury.getAvailableRaisedAmount(),
  () => treasury.getExpectedAmount(),
]);
```

### Step 5 (Success): Vehicle delivered — disburse and withdraw

> **Any caller** for `disburseFees` (after `launchTime`). **Platform Admin or Campaign Owner** for `withdraw` (after `launchTime`). For **Flow A**, you already called `confirmPayment` under Step 3 after delivery; for **Flow B**, the payment was confirmed when `processCryptoPayment` ran—do not call `confirmPayment` here.

```typescript theme={null}
const feeTx = await treasury.disburseFees();
await oak.waitForReceipt(feeTx);

const withdrawTx = await treasury.withdraw();
await oak.waitForReceipt(withdrawTx);
```

### Step 5 (Failure): Claim window after deadline — platform sweeps expired funds

> **Role: Platform Admin** — only the platform admin can call `claimExpiredFunds`. Callable only after `campaignDeadline + platformClaimDelay`, and only after `launchTime` (time-constrained variant).

If the vehicle is not delivered and funds remain in the treasury past the campaign deadline plus the configured claim delay, Karma's backend can sweep idle balances on-chain. The contract transfers swept amounts to the **platform admin** and **protocol admin** addresses (see `ExpiredFundsClaimed`). Consumer-facing refunds to James are then handled by Karma's policy and ops (off-chain settlement or a follow-on transfer), not by a single `claimExpiredFunds` transfer directly to the buyer wallet in the base contract logic.

```typescript theme={null}
// After INFO.getDeadline() + INFO.getPlatformClaimDelay(PLATFORM_HASH) has passed:
const txHash = await treasury.claimExpiredFunds();
await oak.waitForReceipt(txHash);
```

This is the core value of the **TimeConstrainedPaymentTreasury** — the **claim window** is enforced on-chain, so idle balances cannot sit forever without a defined recovery path.

### Alternative: Refunds before or after the claim window

**A) Cancel unconfirmed off-chain payment (before `confirmPayment`):**

> **Role: Platform Admin** for `cancelPayment` (within the launch…deadline+buffer window). This clears pending accounting only; **it does not automatically return ERC-20** already sent to the treasury—handle recovery operationally if you deposited before cancelling.

```typescript theme={null}
await treasury.cancelPayment(orderId);
```

**B) Refund a confirmed off-chain payment (non-NFT):**

> **Role: Platform Admin** for `claimRefund(paymentId, refundAddress)` (after `launchTime`). This refunds a confirmed payment where no NFT was minted. The contract verifies the payment is confirmed and has `tokenId == 0`.

```typescript theme={null}
await treasury.claimRefund(orderId, JAMES_WALLET_ADDRESS);
```

**C) Refund — NFT-backed crypto payment:**

> **Role: Any caller (NFT owner)** — `claimRefundSelf(paymentId)` looks up the current NFT owner, burns the NFT, and sends the refundable amount to that owner (after `launchTime`). No prior `cancelPayment` is needed — crypto payments cannot be cancelled via `cancelPayment` (they are auto-confirmed on creation).

Before calling `claimRefundSelf`, the NFT owner must approve the treasury to manage the NFT. All pledge NFTs live on the **CampaignInfo** contract (not the treasury itself), so approval uses the CampaignInfo SDK entity:

```typescript theme={null}
const campaign = oak.campaignInfo(CAMPAIGN_INFO_ADDRESS);
await campaign.approve(TIME_CONSTRAINED_TREASURY_ADDRESS, tokenId);

await treasury.claimRefundSelf(orderId);
```

### Claim non-goal line items

> **Role: Platform Admin** — only the platform admin can claim non-goal line items (after `launchTime`).

If the prepayment used line items that do not count toward the campaign goal (e.g., processing fees), the platform admin can claim accumulated non-goal balances per token.

```typescript theme={null}
const txHash = await treasury.claimNonGoalLineItems(USDT_TOKEN_ADDRESS);
await oak.waitForReceipt(txHash);
```

### Batch operations

> **Role: Platform Admin** — batch create and confirm are platform-admin-only (within the launch…deadline+buffer window).

```typescript theme={null}
const txHash = await treasury.createPaymentBatch(
  paymentIds, buyerIds, itemIds, tokens, amounts, expirations,
  lineItemsArray, externalFeesArray,
);
await oak.waitForReceipt(txHash);

const txHash2 = await treasury.confirmPaymentBatch(paymentIds, buyerAddresses);
await oak.waitForReceipt(txHash2);
```

### Pause, unpause, or cancel the treasury

**Pause / unpause:**

> **Role: Platform Admin** — only the platform admin can pause and unpause.

```typescript theme={null}
const pauseTx = await treasury.pauseTreasury(toHex("compliance-hold", { size: 32 }));
await oak.waitForReceipt(pauseTx);

const unpauseTx = await treasury.unpauseTreasury(toHex("hold-cleared", { size: 32 }));
await oak.waitForReceipt(unpauseTx);
```

**Cancel the treasury permanently:**

> **Role: Platform Admin or Campaign Owner** — either party can cancel (same override pattern as `PaymentTreasury`).

```typescript theme={null}
const txHash = await treasury.cancelTreasury(toHex("program-ended", { size: 32 }));
await oak.waitForReceipt(txHash);
```

## Architecture Diagram

**Karma Automotive Prepayment Flow**

```mermaid theme={null}
sequenceDiagram
    participant Customer as Customer (James)
    participant Karma as Karma (Platform Admin)
    participant Treasury as TimeConstrainedTreasury

    Customer->>Karma: Order GS-6

    rect rgb(40, 40, 60)
    Note over Customer,Treasury: Flow A — Off-chain / fiat payment
    Karma->>Treasury: createPayment(...)<br/>[Platform Admin, in window]
    Note over Treasury: Order recorded<br/>(no pull from buyer)
    Customer->>Karma: Pays off-chain (wire, financing)
    Karma->>Treasury: Tokens to treasury<br/>(deposit / bridge / ops)
    Note over Treasury: Balance must cover confirm
    Customer->>Karma: Vehicle delivered
    Karma->>Treasury: confirmPayment(...)<br/>[Platform Admin, in window]
    Note over Treasury: Pending → confirmed
    end

    rect rgb(40, 60, 40)
    Note over Customer,Treasury: Flow B — On-chain crypto payment
    Customer->>Treasury: ERC-20 approve()
    Note over Treasury: Treasury approved
    Customer->>Treasury: processCryptoPayment(...)<br/>[Any caller, in window]
    Note over Treasury: Pull + confirmed + NFT
    end

    rect rgb(60, 60, 40)
    Note over Customer,Treasury: Fees & withdraw (both flows)
    Karma->>Treasury: disburseFees()<br/>[Any caller, after launch]
    Note over Treasury: Fees → Protocol + Platform
    Karma->>Treasury: withdraw()<br/>[Admin or Owner, after launch]
    Note over Treasury: Dealer paid
    end

    rect rgb(60, 40, 40)
    Note over Customer,Treasury: Failure / late path
    Karma->>Treasury: claimExpiredFunds()<br/>[Platform Admin, after deadline + claim delay]
    Note over Treasury: Swept → Platform + Protocol
    Karma-->>Customer: Policy refund (ops / off-chain follow-up)
    Customer->>Treasury: claimRefundSelf(paymentId)<br/>[Any caller, after launch]
    Note over Treasury: Refund → NFT owner
    end
```

## Key Takeaways

* **ERC-20 approval is required** — James must `approve` the treasury before `processCryptoPayment` can pull tokens
* **`createPayment` path** — fund the treasury before `confirmPayment` (`createPayment` does not pull from the buyer)
* **`processCryptoPayment` path** — do not call `confirmPayment` afterward; use `disburseFees` / `withdraw` when appropriate
* **Multi-token** — use any **accepted** `paymentToken` for the campaign; balances and sweeps are per ERC-20
* **Time gates are enforced on-chain** — create/confirm/cancel/pay paths must occur within `launchTime` … `deadline + bufferTime`; refunds, fee disbursement, withdrawal, non-goal claims, and expired sweeps require time **after** `launchTime`
* **Same SDK interface** as PaymentTreasury — `oak.paymentTreasury()` works for both; behavior differs in the deployed contract bytecode
* **`claimExpiredFunds()`** is platform-admin-only and only after `deadline + platformClaimDelay`; on-chain recipients are the platform and protocol admins — align customer refunds with your product policy
* **Role-based access** — matches PaymentTreasury for admin-only writes; `withdraw` is platform admin or campaign owner; `disburseFees` is permissionless
* **Three cancellation/refund paths** — `cancelPayment` deletes unconfirmed off-chain records (no on-chain refund); `claimRefund(paymentId, address)` refunds confirmed non-NFT payments (platform admin); `claimRefundSelf(paymentId)` refunds crypto/NFT payments directly (NFT owner, no prior cancel needed; requires prior ERC-721 approval on CampaignInfo)
* **High-value transactions** benefit from deterministic rules instead of informal wire holds
* **Line items** provide a clear audit trail (base price vs. options vs. delivery)
* **Batch, pause, cancel, and `claimNonGoalLineItems`** behave like PaymentTreasury but inherit the same time checks from `TimeConstrainedPaymentTreasury`
