# Direct Issuance Program

***

## 1. Overview

The Direct Issuance Program (DIP) enables discounted token purchases of Superstate equity instruments at oracle-derived pricing. It is composed of three on-chain contracts and a set of off-chain APIs for user onboarding and allowlist management.

**What partners can do with this integration:**

* Onboard users (individuals or entities) via API, passing KYC data
* Get users allowlisted for specific equity instruments
* Enable users to purchase equity tokens at a market-configured discount via `buyTheDip`

***

## 2. Architecture

```
                           Partner Backend
                          ┌─────────────────┐
                          │  KYC + Wallet   │
                          │  Collection     │
                          └────────┬────────┘
                                   │
                   ┌───────────────┼───────────────┐
                   │ HTTP API      │               │ On-chain
                   ▼               │               ▼
          ┌────────────────┐       │      ┌──────────────────┐
          │ Superstate     │       │      │ Ethereum Mainnet │
          │ Onboard API    │       │      │                  │
          │                │       │      │  ┌────────────┐  │
          │ POST /onboard  │       │      │  │ Allowlist  │  │
          │ POST /add-     │       │      │  │ (V4.0)     │  │
          │   allowlist    │       │      │  └────────────┘  │
          │ PUT /update    │       │      │         ▲        │
          └────────────────┘       │      │         │ reads  │
                                   │      │  ┌──────┴─────┐  │
                                   │      │  │ EquityToken│  │
                                   │      │  │ (Dippable) │──┼── User calls
                                   │      │  └──────┬─────┘  │     buyTheDip()
                                   │      │         │        │
                                   │      │  ┌──────▼─────┐  │
                                   │      │  │ DIP        │  │
                                   │      │  │ Contract   │  │
                                   │      │  └────────────┘  │
                                   │      └──────────────────┘
                                   │
                          ┌────────▼─────────┐
                          │  Partner User    │
                          │  (EOA Wallet)    │
                          └──────────────────┘
```

**Contract interaction flow for a purchase:**

```
User EOA ──► EquityToken.buyTheDip(marketId, paymentAmount, minOutAmount, USDC)
                │
                ├─► DIP.buyTheDip(marketId, buyer, paymentAmount, minOutAmount, USDC)
                │       │── Fetch Pyth oracle price
                │       │── Apply discount
                │       │── Validate circuit breakers
                │       │── Calculate output tokens
                │       │── Validate slippage (minOutAmount)
                │       │── Update totalPaymentReceived
                │       │── Auto-close market if capacity exhausted
                │       └── Return (actualPaymentAmount, instrumentAmount, recipient)
                │
                ├─► USDC.safeTransferFrom(buyer, recipient, actualPaymentAmount)
                └─► _mint(buyer, instrumentAmount)
```

***

## 3. Prerequisites & Setup

#### 3.1 API Key & Request Signing

Contact Superstate to receive an API key and secret for the External Onboard API. Partners will be registered as an `ExternalOnboardPartner`.

**Authentication uses HMAC-SHA256 request signing.** To implement request signing, either:

* Use the npm package: `@superstateinc/api-key-request`
* Or implement manually using the reference implementation: <https://github.com/superstateinc/request-with-api-key>

#### 3.2 Contract Addresses

Contract addresses for the specific equity instrument will be provided by Superstate at integration time. You will need:

<table><thead><tr><th width="236.7578125">Contract</th><th>Description</th></tr></thead><tbody><tr><td><strong>EquityToken (Proxy)</strong></td><td>The token contract users interact with for <code>buyTheDip</code> and <code>isAllowed</code></td></tr><tr><td><strong>DIP Contract</strong></td><td>Pricing engine for <code>calculateOutput</code> (users do not call <code>buyTheDip</code> on this directly)</td></tr><tr><td><strong>USDC</strong></td><td><code>0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48</code> (Ethereum Mainnet)</td></tr></tbody></table>

#### 3.3 Supported KYC Providers

When submitting onboarding requests, specify which KYC provider was used:

```
LexisNexis | Moodys | Sumsub | Persona | Seon | Jumio
Truiloo | Onfido | ComplyAdvantage | HyperVerge | FullCircl | Prove
```

***

## 4. User Onboarding API

The External Onboard API allows partners to create user entities in Superstate's system and get them allowlisted in a single flow.

**Full API documentation:** [Onboarding API](/integration-partners/onboarding-api.md)

**Base URL:** Provided by Superstate

**Authentication:** HMAC-SHA256 signed requests (see Section 3.1)

The sections below summarize the key endpoints. Refer to the API documentation above for the authoritative reference.

#### 4.1 Available Endpoints

<table><thead><tr><th width="111.5703125">Method</th><th>Endpoint</th><th>Purpose</th></tr></thead><tbody><tr><td><code>POST</code></td><td><code>/v1/accounts/onboard/evm</code></td><td>Create user entity and initiate allowlisting</td></tr><tr><td><code>POST</code></td><td><code>/v1/accounts/onboard/evm/add-allowlist</code></td><td>Add additional wallet to an existing user</td></tr><tr><td><code>PUT</code></td><td><code>/v1/accounts/onboard/update</code></td><td>Update user KYC information</td></tr><tr><td><code>POST</code></td><td><code>/v1/accounts/onboard/evm/mock</code></td><td>Test endpoint (no database writes, returns mock data)</td></tr></tbody></table>

#### 4.2 Key Concepts

* **`forInstrument`**: Pass the equity instrument symbol to get users allowlisted for that specific token. Required for DIP equity tokens.
* **`userData`**: User PII (name, address, SSN/TIN, email). If omitted, Superstate queries the KYC provider directly using `kycProviderId`.
* **`kycProvider` + `kycProviderId`**: Reference to the user's completed KYC verification in your provider's system.
* **Response**: All creation endpoints return `{ entityId, walletAddress, encodedTransaction }`. Store `entityId` for future API calls.
* **Add-allowlist**: Identify the existing user by either `email` or `existingWalletAddress` (exactly one, mutually exclusive). Only the partner that originally onboarded the entity can add wallets.
* **Mock endpoint**: Same request format as the real endpoint. Returns `entityId: 0` and an unbroadcastable transaction. Useful for integration testing.

***

## 5. Allowlisting

#### 5.1 How Allowlisting Works

Before a user can hold or purchase equity tokens, their wallet address must be allowlisted. Allowlisting is handled by Superstate's infrastructure as part of the onboarding flow (Section 4). Partners do not need to interact with the Allowlist contract directly.

#### 5.2 Checking Allowlist Status

Call `isAllowed` on the **EquityToken** contract to check if a user is ready to purchase:

```typescript
// viem
const isAllowed = await publicClient.readContract({
  address: EQUITY_TOKEN_ADDRESS,
  abi: parseAbi(['function isAllowed(address addr) external view returns (bool)']),
  functionName: 'isAllowed',
  args: [userAddress],
});

// ethers.js v6
const isAllowed = await equityToken.isAllowed(userAddress);
```

#### 5.3 Allowlist Integration with Onboarding

When using the External Onboard API with `forInstrument` specified:

1. Superstate creates the entity and initiates allowlisting
2. The response includes an `encodedTransaction` for the on-chain allowlist update
3. Once the allowlist transaction is confirmed, the user can interact with the token

The allowlist transaction is submitted by Superstate's infrastructure. Partners should poll `equityToken.isAllowed(userAddress)` to confirm readiness before enabling purchases.

***

## 6. DIP Contract Specification

#### 6.1 Market Lifecycle

```
                    createMarket()
                         │
                         ▼
                   ┌─────────────┐
                   │ Initialized │
                   └──────┬──────┘
                          │ setMarketState(Active)
                          ▼
         ┌──────── ┌─────────┐ ────────┐
         │         │  Active │         │
         │         └────┬────┘         │
         │              │              │
    setMarketState   auto-close   setMarketState
      (Paused)     (target met)   (Cancelled)
         │              │              │
         ▼              ▼              ▼
    ┌────────┐    ┌──────────┐   ┌───────────┐
    │ Paused │    │  Closed  │   │ Cancelled │
    └───┬────┘    │(terminal)│   │ (terminal)│
        │         └──────────┘   └───────────┘
        │ setMarketState(Active)
        └───────► Active
```

**States:**

<table><thead><tr><th width="186.2734375">State</th><th width="113.21875">Value</th><th>Description</th></tr></thead><tbody><tr><td><code>Initialized</code></td><td>0</td><td>Created but not yet accepting purchases</td></tr><tr><td><code>Active</code></td><td>1</td><td>Live. One active market per instrument enforced.</td></tr><tr><td><code>Paused</code></td><td>2</td><td>Temporarily suspended. Can be reactivated.</td></tr><tr><td><code>Closed</code></td><td>3</td><td>Terminal. Target reached or manually closed.</td></tr><tr><td><code>Cancelled</code></td><td>4</td><td>Terminal. Admin-terminated.</td></tr></tbody></table>

**Rules:**

* Only one market per instrument can be `Active` at any time
* Cannot transition to the same state
* Cannot transition back to `Initialized`
* `Closed` and `Cancelled` are terminal (no further transitions)
* Market auto-closes when remaining capacity < `minPayment`

#### 6.2 Market Configuration

```solidity
struct Config {
    string description;         // Human-readable market description
    address recipient;          // Address that receives payment tokens
    uint16 discountRate;        // Discount in basis points (0-10000 = 0%-100%)
    uint256 minPayment;         // Minimum payment per purchase (PAYMENT_TOKEN_DECIMALS)
    uint256 totalPaymentTarget; // Total raise goal (PAYMENT_TOKEN_DECIMALS)
    uint64 minPriceClamp;       // Circuit breaker lower bound (8 decimals)
    uint64 maxPriceClamp;       // Circuit breaker upper bound (8 decimals)
}
```

#### 6.3 Market Data Structure

```solidity
struct Market {
    bytes32 marketId;                        // Unique identifier
    bytes32 oraclePriceFeedId;              // Pyth price feed ID
    IERC20 paymentToken;                    // e.g., USDC
    uint8 oracleLatencyToleranceInSeconds;  // Max oracle staleness
    State state;                            // Current lifecycle state
    uint16 discountRate;                    // Basis points
    uint64 minPriceClamp;                   // 8 decimals
    IERC20 instrument;                      // The equity token
    uint64 maxPriceClamp;                   // 8 decimals
    address recipient;                      // Payment destination
    uint256 totalPaymentReceived;           // Cumulative (PAYMENT_TOKEN_DECIMALS)
    uint256 minPayment;                     // Per-purchase minimum
    uint256 totalPaymentTarget;             // Raise goal
    string description;                     // Market description
}
```

#### 6.4 Constants

<table><thead><tr><th width="246.0078125">Constant</th><th width="116.5234375">Value</th><th>Description</th></tr></thead><tbody><tr><td><code>BASIS_POINTS</code></td><td>10000</td><td>100% in basis points</td></tr><tr><td><code>PRICE_CLAMP_DECIMALS</code></td><td>8</td><td>Decimal precision for price clamp values</td></tr><tr><td><code>PAYMENT_TOKEN_DECIMALS</code></td><td>6</td><td>Decimal precision for USDC (set at deployment)</td></tr></tbody></table>

***

## 7. Executing a Purchase (`buyTheDip`)

#### 7.1 User-Facing Function

The user calls `buyTheDip` on the **EquityToken** contract (not the DIP contract directly):

```solidity
function buyTheDip(
    bytes32 marketId,      // Market to purchase from
    uint256 paymentAmount, // Amount of USDC to spend (6 decimals for USDC)
    uint256 minOutAmount,  // Minimum tokens to receive (slippage protection)
    IERC20 paymentToken    // USDC contract address
) external
```

#### 7.2 Step-by-Step Integration

{% stepper %}
{% step %}

#### Check allowlist status

```typescript
const isAllowed = await equityToken.read.isAllowed([userAddress]);
if (!isAllowed) {
  // User must be onboarded and allowlisted first
  throw new Error("User not allowlisted");
}
```

{% endstep %}

{% step %}

#### Get the active market ID

```typescript
const marketId = await dipContract.read.currentMarketForInstrument([
  equityTokenAddress
]);
if (marketId === "0x" + "0".repeat(64)) {
  throw new Error("No active market");
}
```

{% endstep %}

{% step %}

#### Preview the purchase (optional but recommended)

```typescript
const [actualPayment, outputTokens] = await dipContract.read.calculateOutput([
  marketId,
  paymentAmount,  // e.g., 1000_000000n for 1000 USDC
  6               // USDC decimals
]);
```

{% endstep %}

{% step %}

#### Approve USDC spending

The user must approve the **EquityToken contract** (not the DIP contract) to spend their USDC:

```typescript
const approveTx = await usdcContract.write.approve([
  equityTokenAddress,
  paymentAmount  // or type(uint256).max for infinite approval
]);
await waitForTransactionReceipt(approveTx);
```

{% endstep %}

{% step %}

#### Execute the purchase

```typescript
const purchaseTx = await equityToken.write.buyTheDip([
  marketId,
  paymentAmount,   // USDC amount (6 decimals)
  minOutAmount,    // Minimum tokens (apply slippage tolerance)
  usdcAddress
]);
const receipt = await waitForTransactionReceipt(purchaseTx);
```

{% endstep %}
{% endstepper %}

#### 7.3 Slippage Protection

Calculate `minOutAmount` using the preview and a slippage tolerance:

```typescript
const SLIPPAGE_BPS = 50; // 0.5%

const [_, expectedOutput] = await dipContract.read.calculateOutput([
  marketId, paymentAmount, 6
]);

const minOutAmount = expectedOutput * (10000n - BigInt(SLIPPAGE_BPS)) / 10000n;
```

The DIP contract will revert with `InsufficientOutput(actual, minRequired)` if the calculated output falls below `minOutAmount`.

#### 7.4 Partial Fills

If the `paymentAmount` exceeds the market's remaining capacity, the DIP contract automatically reduces it:

```
remaining = totalPaymentTarget - totalPaymentReceived
actualPaymentAmount = min(paymentAmount, remaining)
```

The user only pays `actualPaymentAmount` and only that amount of USDC is transferred. The excess stays in the user's wallet.

#### 7.5 Auto-Close Behavior

After each purchase, if the remaining capacity drops below `minPayment`, the market automatically transitions to `Closed`:

```
if totalPaymentReceived + minPayment > totalPaymentTarget:
    state = Closed
```

***

## 8. Querying Market State

#### 8.1 Read Functions

```solidity
// Get the active market ID for an instrument
function currentMarketForInstrument(IERC20 instrument) external view returns (bytes32);

// Get full market data (auto-generated from public mapping)
function markets(bytes32 marketId) external view returns (Market memory);

// Preview purchase output
function calculateOutput(
    bytes32 marketId,
    uint256 paymentAmount,
    uint8 paymentDecimals
) external view returns (uint256 actualPaymentAmount, uint256 outputAmount);

// Contract version
function version() external view returns (string memory);  // "1.0.1"
```

#### 8.2 Useful Derived Values

```typescript
// Remaining capacity
const market = await dipContract.read.markets([marketId]);
const remaining = market.totalPaymentTarget - market.totalPaymentReceived;

// Is market active?
const isActive = market.state === 1; // State.Active

// Progress percentage
const progress = (market.totalPaymentReceived * 10000n) / market.totalPaymentTarget;
```

***

## 9. Events & Indexing

#### 9.1 DIP Contract Events

**Purchase** — Emitted on every successful purchase:

```solidity
event Purchase(
    bytes32 indexed marketId,
    address indexed buyer,
    uint256 instrumentAmount,    // Tokens minted
    uint256 paymentAmount,       // USDC paid
    uint256 discountedPrice,     // Price after discount
    uint16 discountRate,         // Discount in bps
    uint256 minOutAmount         // Slippage parameter
);
```

**MarketCreated** — Emitted when a new market is created:

```solidity
event MarketCreated(
    bytes32 marketId,
    IERC20 indexed instrument,
    IERC20 indexed paymentToken
);
```

**MarketStateUpdated** — Emitted on state transitions (including auto-close):

```solidity
event MarketStateUpdated(
    bytes32 indexed marketId,
    State indexed prev,
    State indexed curr
);
```

**MarketConfigUpdated** — Emitted when market parameters change:

```solidity
event MarketConfigUpdated(
    bytes32 indexed marketId,
    Config config
);
```

#### 9.2 Token Events

Standard ERC20 `Transfer` events are emitted on the EquityToken when tokens are minted:

```solidity
event Transfer(address indexed from, address indexed to, uint256 value);
// from = address(0) for mints
```

#### 9.3 Indexing Recommendations

To track DIP purchases, index the `Purchase` event on the DIP contract. The `buyer` field is indexed for efficient filtering by user.

To detect market closures (including auto-close), watch for `MarketStateUpdated` events where `curr = 3` (Closed).

***

## 10. Error Reference

#### 10.1 DIP Contract Errors

| Error                                                     | When                 | Description                                               |
| --------------------------------------------------------- | -------------------- | --------------------------------------------------------- |
| `NotCurrentMarket()`                                      | `buyTheDip`          | Caller is not the instrument token of the active market   |
| `NotPaymentToken()`                                       | `buyTheDip`          | Payment token doesn't match market config                 |
| `InsufficientPayment()`                                   | `buyTheDip`          | Payment amount (after normalization) < `minPayment`       |
| `PriceOutOfBounds()`                                      | `buyTheDip`          | Discounted price outside `[minPriceClamp, maxPriceClamp]` |
| `InsufficientOutput(uint256 actual, uint256 minRequired)` | `buyTheDip`          | Output tokens < `minOutAmount` (slippage)                 |
| `PriceFeedExceedsMaxLatency()`                            | `buyTheDip`          | Oracle price is stale                                     |
| `PriceFeedInvalidOutput()`                                | `buyTheDip`          | Oracle returned invalid data                              |
| `ZeroActualPayment()`                                     | `buyTheDip`          | Calculated payment is zero after normalization            |
| `InvalidMarketId()`                                       | Various              | Market ID does not exist                                  |
| `AlreadyMarketClosedOrCancelled()`                        | Config/State changes | Market is in terminal state                               |

#### 10.2 EquityToken (Dippable) Errors

| Error                 | When        | Description                         |
| --------------------- | ----------- | ----------------------------------- |
| `ZeroPaymentAmount()` | `buyTheDip` | `paymentAmount` is 0                |
| `ZeroMinOutAmount()`  | `buyTheDip` | `minOutAmount` is 0                 |
| `DipContractNotSet()` | `buyTheDip` | DIP contract address not configured |

#### 10.3 Common ERC20 Errors

| Error                        | When        | Description                                          |
| ---------------------------- | ----------- | ---------------------------------------------------- |
| `ERC20InsufficientAllowance` | `buyTheDip` | User hasn't approved enough USDC for the EquityToken |
| `ERC20InsufficientBalance`   | `buyTheDip` | User doesn't have enough USDC                        |

#### 10.4 Allowlist Errors

| Error                     | When                   | Description                                                               |
| ------------------------- | ---------------------- | ------------------------------------------------------------------------- |
| `InsufficientPermissions` | `buyTheDip` / transfer | User's wallet is not allowlisted. Onboard via External Onboard API first. |

***

## 11. Decimal & Pricing Math

#### 11.1 Decimal Conventions

| Token/Value   | Decimals        | Example                                |
| ------------- | --------------- | -------------------------------------- |
| USDC          | 6               | 1000 USDC = `1000000000` (1000 \* 1e6) |
| EquityToken   | 6               | 100 tokens = `100000000` (100 \* 1e6)  |
| Price clamps  | 8               | $1.00 = `100000000` (1e8)              |
| Oracle price  | Variable (Pyth) | Normalized internally                  |
| Discount rate | Basis points    | 500 = 5% discount                      |

#### 11.2 Pricing Formula

```
1. oraclePrice = Pyth.getPriceUnsafe(oraclePriceFeedId).price
2. discountedPrice = oraclePrice * (BASIS_POINTS - discountRate) / BASIS_POINTS
3. Validate: minPriceClamp <= discountedPrice(8 decimals) <= maxPriceClamp
4. instrumentAmount = actualPaymentAmount * 10^instrumentDecimals / discountedPrice
```

**Example:**

* Oracle price: $10.00 per token
* Discount rate: 500 bps (5%)
* Discounted price: $10.00 \* (10000 - 500) / 10000 = $9.50
* Payment: 1000 USDC
* Tokens received: 1000 / 9.50 = \~105.26 tokens

#### 11.3 Payment Normalization

All payment amounts are normalized to `PAYMENT_TOKEN_DECIMALS` (6) internally:

* If USDC (6 decimals): no conversion needed
* If DAI (18 decimals): truncation occurs when normalizing down to 6 decimals

The `actualPaymentAmount` returned to the caller is denormalized back to the payment token's native decimals.

***

## 12. End-to-End Examples

#### 12.1 Full Integration Flow (TypeScript / viem)

```typescript
import { createPublicClient, createWalletClient, http, parseAbi } from 'viem';
import { mainnet } from 'viem/chains';

// --- Configuration (provided by Superstate) ---
const EQUITY_TOKEN_ADDRESS = '0x...';
const DIP_CONTRACT_ADDRESS = '0x...';
const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const SUPERSTATE_API_BASE = 'https://api.superstate.co';

// --- ABIs (minimal for integration) ---
const equityTokenAbi = parseAbi([
  'function buyTheDip(bytes32 marketId, uint256 paymentAmount, uint256 minOutAmount, address paymentToken) external',
  'function isAllowed(address addr) external view returns (bool)',
  'function balanceOf(address account) external view returns (uint256)',
]);

const dipAbi = parseAbi([
  'function currentMarketForInstrument(address instrument) external view returns (bytes32)',
  'function calculateOutput(bytes32 marketId, uint256 paymentAmount, uint8 paymentDecimals) external view returns (uint256 actualPaymentAmount, uint256 outputAmount)',
  'function markets(bytes32 marketId) external view returns (bytes32, bytes32, address, uint8, uint8, uint16, uint64, address, uint64, address, uint256, uint256, uint256, string)',
]);

const erc20Abi = parseAbi([
  'function approve(address spender, uint256 amount) external returns (bool)',
  'function allowance(address owner, address spender) external view returns (uint256)',
  'function balanceOf(address account) external view returns (uint256)',
]);

// --- Step 1: Onboard user via API ---
async function onboardUser(userData: object, walletAddress: string, kycProviderId: string) {
  const body = JSON.stringify({
    version: 1,
    userData,
    chainId: { id: 1 },
    walletAddress,
    kycProvider: 'Sumsub',
    kycProviderId,
    forInstrument: 'EQUITY_SYMBOL',
  });

  // Build HMAC-signed request using @superstateinc/api-key-request
  // See: https://github.com/superstateinc/request-with-api-key
  const response = await signedFetch(`${SUPERSTATE_API_BASE}/v1/accounts/onboard/evm`, {
    method: 'POST',
    body,
    apiKey: API_KEY,
    apiSecret: API_SECRET,
  });
  return await response.json();  // { entityId, walletAddress, encodedTransaction }
}

// --- Step 2: Wait for allowlisting ---
async function waitForAllowlist(userAddress: string): Promise<void> {
  while (true) {
    const isAllowed = await publicClient.readContract({
      address: EQUITY_TOKEN_ADDRESS,
      abi: equityTokenAbi,
      functionName: 'isAllowed',
      args: [userAddress],
    });
    if (isAllowed) return;
    await new Promise(r => setTimeout(r, 5000));  // Poll every 5s
  }
}

// --- Step 3: Execute DIP purchase ---
async function executePurchase(userAddress: string, usdcAmount: bigint) {
  const SLIPPAGE_BPS = 50n; // 0.5%

  // Get active market
  const marketId = await publicClient.readContract({
    address: DIP_CONTRACT_ADDRESS,
    abi: dipAbi,
    functionName: 'currentMarketForInstrument',
    args: [EQUITY_TOKEN_ADDRESS],
  });

  // Preview output
  const [actualPayment, expectedOutput] = await publicClient.readContract({
    address: DIP_CONTRACT_ADDRESS,
    abi: dipAbi,
    functionName: 'calculateOutput',
    args: [marketId, usdcAmount, 6],
  });

  const minOutAmount = expectedOutput * (10000n - SLIPPAGE_BPS) / 10000n;

  // Check & approve USDC
  const currentAllowance = await publicClient.readContract({
    address: USDC_ADDRESS,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [userAddress, EQUITY_TOKEN_ADDRESS],
  });

  if (currentAllowance < usdcAmount) {
    const approveTx = await walletClient.writeContract({
      address: USDC_ADDRESS,
      abi: erc20Abi,
      functionName: 'approve',
      args: [EQUITY_TOKEN_ADDRESS, usdcAmount],
    });
    await publicClient.waitForTransactionReceipt({ hash: approveTx });
  }

  // Execute purchase
  const purchaseTx = await walletClient.writeContract({
    address: EQUITY_TOKEN_ADDRESS,
    abi: equityTokenAbi,
    functionName: 'buyTheDip',
    args: [marketId, usdcAmount, minOutAmount, USDC_ADDRESS],
  });

  const receipt = await publicClient.waitForTransactionReceipt({ hash: purchaseTx });
  return receipt;
}
```

#### 12.2 Full Integration Flow (ethers.js v6)

```typescript
import { ethers } from 'ethers';

// --- Step 3 equivalent in ethers ---
async function executePurchaseEthers(
  signer: ethers.Signer,
  usdcAmount: bigint
) {
  const SLIPPAGE_BPS = 50n;

  const equityToken = new ethers.Contract(EQUITY_TOKEN_ADDRESS, equityTokenAbi, signer);
  const dipContract = new ethers.Contract(DIP_CONTRACT_ADDRESS, dipAbi, signer);
  const usdc = new ethers.Contract(USDC_ADDRESS, erc20Abi, signer);

  // Get active market
  const marketId = await dipContract.currentMarketForInstrument(EQUITY_TOKEN_ADDRESS);

  // Preview
  const [actualPayment, expectedOutput] = await dipContract.calculateOutput(
    marketId, usdcAmount, 6
  );
  const minOutAmount = expectedOutput * (10000n - SLIPPAGE_BPS) / 10000n;

  // Approve
  const allowance = await usdc.allowance(await signer.getAddress(), EQUITY_TOKEN_ADDRESS);
  if (allowance < usdcAmount) {
    const approveTx = await usdc.approve(EQUITY_TOKEN_ADDRESS, usdcAmount);
    await approveTx.wait();
  }

  // Purchase
  const tx = await equityToken.buyTheDip(marketId, usdcAmount, minOutAmount, USDC_ADDRESS);
  return await tx.wait();
}
```

{% hint style="info" %}
**Note on smart contract integration:** The `buyTheDip` function on EquityToken uses `msg.sender` as the buyer for both the USDC `transferFrom` and the token `mint`. This means users must call `buyTheDip` directly from their EOA wallet — router or proxy contracts are not supported. Partners should build their integration as a frontend that constructs and submits transactions on behalf of the user's wallet.
{% endhint %}

***

## Appendix A: Market Management (Reference)

These functions are **owner-only** (Superstate-managed) but documented here for completeness.

#### Create Market

```solidity
function createMarket(
    bytes32 marketId,
    IERC20 paymentToken,
    IERC20 instrument,
    bytes32 oraclePriceFeedId,
    uint8 oracleLatencyToleranceInSeconds,
    Config calldata config
) external onlyOwner
```

#### Update Market Config

```solidity
function setMarketConfig(bytes32 marketId, Config calldata newConfig) external onlyOwner
```

Cannot be called on `Closed` or `Cancelled` markets.

#### Transition Market State

```solidity
function setMarketState(bytes32 marketId, State nextState) external onlyOwner
```

Valid transitions:

* `Initialized → Active`
* `Active → Paused | Closed | Cancelled`
* `Paused → Active | Closed | Cancelled`


---

# Agent Instructions: 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://docs.superstate.com/integration-partners/direct-issuance-program.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.
