# Durian PAY

## 🤝 OTC & Payment Requests

> **Peer-to-peer trades and requests, with atomic settlement and guaranteed exits.**

Durian Social provides three distinct money-movement primitives, each backed by its own smart contract logic:

1. **Payment Request** — "Please pay me 50 kUSDT"
2. **Direct Payment** — "Here's 50 kUSDT" (with optional reference to a request)
3. **OTC Escrow** — "I'll trade you 1,000 MYTOKEN for 100 kUSDT, binding for 3 days"

***

### 📩 Payment Request

**Contract:** `DurianSocial_V1_2.sol` · Function: `createPayRequest(...)`

Ask a counterparty to pay you in any token (native KUB, kUSDT, kUSDC, or any ERC-20).

```solidity
createPayRequest(
  address to,        // who you're asking
  address token,     // what token (0x0 = native KUB)
  uint256 amount,
  string  content    // purpose tag, ≤ 20 bytes (e.g. "invoice #123")
) payable
```

**Fee:** `requestFee` (0.001–0.4 KUB, default **0.02 KUB**)

#### Lifecycle

```
┌─ Pending (0) ──────── recipient has not yet decided ────────┐
│                                                             │
│   Requester ────► to-pay address    on-chain event:         │
│                                     PayRequestCreated       │
│                                                             │
├─ Fulfilled (1) ────── recipient paid via fulfillPayRequest(id)
│                       · native KUB via .call{value}         │
│                       · ERC20 via transferFrom + balance    │
│                         delta guard (V1.2 fix)              │
│                       · event PayRequestFulfilled           │
│                                                             │
└─ Cancelled (2) ────── requester withdrew via cancelPayRequest(id)
                        · allowed even when contract paused   │
                        · event PayRequestCancelled           │
```

Once Fulfilled or Cancelled, the request state is **terminal** — it cannot be reopened or re-paid.

***

### 💸 Direct Payment

**Contract:** `DurianSocial_V1_2.sol` · Function: `sendPayment(...)`

Send tokens peer-to-peer, optionally linked to an existing Payment Request ID:

```solidity
sendPayment(
  address to,
  address token,         // 0x0 = native KUB
  uint256 amount,
  string  content,       // memo, ≤ 20 bytes (e.g. "salary", "tip")
  uint256 refRequestId   // optional: PayRequest you're satisfying (0 = standalone)
) payable
```

**No additional fee** — the full `amount` goes to the recipient. Gas only.

#### Event

```solidity
event Payment(
  address indexed from,
  address indexed to,
  uint256 indexed id,
  address token,
  uint256 amount,
  string  content,
  uint256 refRequestId,
  uint64  timestamp
);
```

The `refRequestId` lets indexers link informal "tip paid for invoice #123" flows to the original request, even if the recipient didn't use `fulfillPayRequest`.

***

### 🤝 OTC Escrow — Atomic Peer Swaps

**Contract:** `DurianMoneyTransfer_V1_2.sol`

The big one. OTC escrow lets two parties **propose and execute a token-for-token swap** with:

* **Escrow security** — proposer's funds locked until accept / decline / expire.
* **Atomic settlement** — both sides move or neither does.
* **Guaranteed exit paths** — cancel, decline, or keeper-triggered expiry. No deadlocks.
* **Audit-proofed** — FoT-token rejection, native-send fallback, fee snapshotting (V1.2).

#### 1. Create an Offer

```solidity
createOtc(
  address counterparty,
  address offerToken,  uint256 offerAmount,   // what you lock
  address wantToken,   uint256 wantAmount,    // what you want in return
  string  purpose,                             // memo, ≤ 20 bytes
  uint64  expiresAt,                           // 1 hour – 30 days; default 3 days
  uint256 replyTo                              // optional threading ref
) payable
```

**Proposer pays at creation time:**

```
if offerToken == NATIVE:
  msg.value == createFee + keeperTip + offerAmount

else (ERC-20):
  msg.value == createFee + keeperTip
  offerToken pulled via transferFrom with balance-delta guard
```

**Fees snapshot at creation (V1.2):**

| Field        | Default                   | Bounds                          |
| ------------ | ------------------------- | ------------------------------- |
| `createFee`  | 0.06 KUB                  | 0.06–0.2 KUB                    |
| `keeperTip`  | 0.01 KUB                  | 0–0.5 KUB                       |
| `halfFeePpm` | 250 ppm (0.025% per side) | 0–12,500 ppm (0–1.25% per side) |

**Why snapshot?** If the owner changes fees later, in-flight offers still settle at the rate that was in force when the proposer committed. Fixes audit finding **M2**.

#### 2. Accept — Atomic Settlement

```solidity
acceptOtc(uint256 id) payable
```

All-or-nothing execution inside one transaction:

1. Counterparty pays `wantAmount` in `wantToken` (native KUB or ERC-20).
2. Proposer receives `offerAmount − feeOffer`.
3. Counterparty receives `wantAmount − feeWant`.
4. Treasury receives `feeOffer + feeWant`.

Fees computed using the snapshot `halfFeePpmAtCreate`:

```
feeOffer = (offerAmount × halfPpm) / 1,000,000
feeWant  = (wantAmount  × halfPpm) / 1,000,000
```

If **any step reverts** (token transfer fails, recipient rejects native send, slippage, etc.), the whole accept tx reverts — both sides keep their funds intact.

#### 3. Exit Paths — Three Ways Out

| Action           | Who                 | When              | Outcome                                      |
| ---------------- | ------------------- | ----------------- | -------------------------------------------- |
| `declineOtc(id)` | Counterparty only   | Before accept     | Proposer refunded 100%                       |
| `cancelOtc(id)`  | Proposer only       | Before accept     | Proposer refunded 100%                       |
| `expireOtc(id)`  | **Anyone** (keeper) | After `expiresAt` | Proposer refunded. Keeper earns `keeperTip`. |

**Keepers** are incentive-aligned. Anyone running a bot that monitors the contract can call `expireOtc` on timed-out offers and pocket the 0.01 KUB tip — keeping the book clean for everyone else.

#### 4. Pull-Pattern Fallback for Native KUB (V1.2 M1 Fix)

Before V1.2, if a proposer was a smart-contract wallet that rejected native KUB, **every exit path** would revert on the final `.call{value}` — deadlocking the escrow forever.

**V1.2 fix:** If native send fails, the funds are queued in `pendingWithdrawals`. The recipient claims them later:

```solidity
claimPending() external
```

State always advances. Nobody's funds ever get stuck.

***

### Governance (DurianMoneyTransfer\_V1\_2)

| Function                                    | Bounds                                                          | Default            |
| ------------------------------------------- | --------------------------------------------------------------- | ------------------ |
| `setFees(createFee, keeperTip, halfFeePpm)` | createFee 0.06–0.2 · keeperTip 0–0.5 · halfFeePpm 0–12,500      | 0.06 / 0.01 / 250  |
| `pause()` / `unpause()`                     | Blocks create + accept. Users retain cancel / decline / expire. | Unpaused           |
| `setTreasury(address)`                      | Owner only                                                      | Set at deploy      |
| `transferOwnership(address)`                | 2-step                                                          | Deployer initially |

**Immutable caps:**

* `CREATE_FEE_MIN = 0.06 KUB` · `CREATE_FEE_MAX = 0.2 KUB`
* `KEEPER_TIP_MAX = 0.5 KUB`
* `HALF_FEE_PPM_MAX = 12,500` (= 1.25% per side, 2.5% round-trip)
* `MIN_EXPIRY = 1 hour` · `MAX_EXPIRY = 30 days` · `DEFAULT_EXPIRY = 3 days`

### Security — V1.2 Audit Fixes

All four findings from the audit round preceding V1.2 (March 2026) were addressed:

| Finding                          | Issue                                                                                                   | Fix                                                                                            |
| -------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| **\[C1]** Atomicity break        | Malicious ERC-20 returns `true` from `transferFrom` without moving balance, desyncing escrow accounting | **Balance-delta guard** in `_pullToken()` — revert if received < claimed                       |
| **\[H1]** FoT token trap         | Fee-on-transfer tokens trap escrow forever (stored `claimed`, payout reverts because actual < claimed)  | Same balance-delta check rejects FoT at pull time                                              |
| **\[M1]** Native-reject deadlock | Proposer as contract-wallet rejecting `.call{value}` → all exits revert                                 | **Pull-pattern fallback** — failed sends queued in `pendingWithdrawals`; state always advances |
| **\[M2]** Fee governance desync  | `halfFeePpm` read live at accept → owner changes retroactively affect in-flight offers                  | **Snapshot `halfFeePpmAtCreate`** into escrow struct at `createOtc`                            |

All fixes verified in on-chain bytecode.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://durianandfun.gitbook.io/durianfun/durian-social/durian-chat/durian-pay.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
