# Integration Guide

A general, repo-agnostic guide to integrating the **Epoch Intent SDK** with **Miden** in any TypeScript dapp. Two flows are supported:

* **Miden → EVM** — user spends a Miden token, receives an EVM token.
* **EVM → Miden** — user spends an EVM token, receives a Miden token.

***

### 1. Install

```bash
pnpm add @epoch-protocol/epoch-intents-sdk
pnpm add wagmi viem @rainbow-me/rainbowkit @tanstack/react-query
pnpm add @miden-sdk/miden-sdk @miden-sdk/miden-wallet-adapter-base \\\\
        @miden-sdk/miden-wallet-adapter-react @miden-sdk/react
```

Both SDKs ship WASM. With Vite, add `vite-plugin-wasm` and `vite-plugin-top-level-await`, and **do not set COOP/COEP** headers on the dev server (they break Miden's gRPC-Web transport).

***

### 2. Initialize the SDK

The SDK needs an allocator URL and a viem-compatible `walletClient` from wagmi.

#### 2.1 Cross-chain (Miden → EVM)

When the **input** is a Miden token, override the wallet client's chain id to the Miden virtual chain id (`999999999`) so the SDK does not try to EVM-route the input:

```tsx
import { useEffect, useState } from 'react';
import { useWalletClient } from 'wagmi';

const MIDEN_VIRTUAL_CHAIN_ID = 999999999;

export function useMidenSourceSDK() {
  const { data: walletClient } = useWalletClient();
  const [sdk, setSdk] = useState<any>(null);

  useEffect(() => {
    if (!walletClient) {
      setSdk(null);
      return;
    }
    let cancelled = false;
    import('@epoch-protocol/epoch-intents-sdk').then(({ EpochIntentSDK }) => {
      if (cancelled) return;
      const midenWalletClient = {
        ...walletClient,
        chain: { ...(walletClient.chain ?? {}), id: MIDEN_VIRTUAL_CHAIN_ID },
      };
      setSdk(new EpochIntentSDK({
        apiBaseUrl: import.meta.env.VITE_ALLOCATOR_URL ?? '<http://localhost:3000>',
        walletClient: midenWalletClient,
      }));
    });
    return () => { cancelled = true; };
  }, [walletClient]);

  return { sdk, isReady: !!sdk };
}
```

#### 2.2 Withdraw (EVM → Miden)

Pass the wagmi `walletClient` **unchanged** — the input is on a real EVM chain, so its real chain id must reach the SDK.

```tsx
new EpochIntentSDK({ apiBaseUrl, walletClient });
```

#### 2.3 Why `useEffect` and not `useMemo`

The dynamic `import()` and the resulting `setState` are side effects. React Strict Mode may skip a `useMemo` body during the second render of a development double-mount, leaving `sdk` undefined.

| Field                   | Cross-chain        | Withdraw          |
| ----------------------- | ------------------ | ----------------- |
| `apiBaseUrl`            | Allocator base URL | Same              |
| `walletClient.chain.id` | `999999999`        | Real EVM chain id |

***

### 3. Intent lifecycle

Both flows go through the same four-stage pipeline:

```
getTaskData ──▶ getIntentQuote ──▶ solveIntent ──▶ poll /miden/status
```

| Step             | Input                                                               | Output                                                 | Side effects                                                                 |
| ---------------- | ------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------- |
| `getTaskData`    | Encoded params object                                               | `{ taskTypeString, intentData }`                       | None                                                                         |
| `getIntentQuote` | `taskTypeString`, `intentData`, `sponsorAddress`                    | `IntentQuoteResult` (`tokenIn`, `tokenOut`, `success`) | None                                                                         |
| `solveIntent`    | quote + `collateralType` (+ `createMidenP2IDNote` for Miden source) | `solveResult` with `intentNonce`, transactions, etc.   | Locks Miden note (Miden→EVM) or returns EVM transactions to sign (EVM→Miden) |
| `pollStatus`     | `userAddress`, `intentNonce`                                        | `{ evmCompleted, midenConsumed, … }`                   | None                                                                         |

Recommended UX: **quote-then-confirm**. Show the user the required deposit (`quote.tokenIn`) and only lock funds when they click confirm. Pass the cached quote into `solveIntent` via `quoteResult` to skip a redundant `getTaskData` round-trip.

***

### 4. Miden → EVM

User holds a Miden token, wants an ERC-20 on an EVM chain. The user creates a **public P2IDE** note targeting the allocator. The solver pays the EVM recipient. The allocator consumes the note. P2IDE is recallable after `midenReclaimHeight`blocks if the intent stalls.

#### 4.1 Encode task data

```tsx
import { CollateralType } from '@epoch-protocol/epoch-intents-sdk/dist/types';

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
const ZERO_HASH = '0x' + '0'.repeat(64);

const taskDataParams = {
  taskType: 'gettokenout' as const,
  intentData: {
    isNative: false,
    depositTokenAddress: ZERO_ADDRESS,        // Miden source — zero address (source identity is in extraData)
    tokenInAmount: '0',                        // reverse quote: backend computes input from minTokenOut
    outputTokenAddress: '0x...',               // ERC-20 on the destination EVM chain
    minTokenOut: '10000000',                   // base units floor
    destinationChainId: '11155111',            // EVM chain id (Sepolia in this example)
    protocolHashIdentifier: ZERO_HASH,
    recipient: evmRecipient,                   // EVM address that receives the output
  },
  extraDataTypestring:
    'string midenSourceAccount,string midenFaucetId,string midenNoteType,' +
    'string midenNoteId,uint256 midenReclaimHeight',
  extraData: {
    midenSourceAccount: midenAccountIdHex,     // user's Miden account, canonical hex
    midenFaucetId: midenFaucetIdHex,           // canonical hex
    midenNoteType: 'P2IDE',                    // recallable
    midenNoteId: '',                           // populated by the SDK after note creation
    midenReclaimHeight: String(currentMidenBlock + 1000n),  // absolute block; remaining window must be >= MIDEN_MIN_RECLAIM_BLOCKS (default 1000)
  },
};
```

> **Important.** `depositTokenAddress` must be the zero address. The Miden source identity belongs in `extraData.midenSourceAccount`. Putting the source account in `depositTokenAddress` will be rejected by the allocator.

> **Reclaim window — enforced.** The allocator validates the **on-chain** P2IDE reclaim height and rejects the intent if the remaining window is too small. The minimum is configured by the allocator via `MIDEN_MIN_RECLAIM_BLOCKS` and **defaults to `1000` blocks**. Compute the absolute reclaim height as `currentMidenBlock + N` where `N >= 1000`. A literal value like `1000` is interpreted as an absolute block and will fail validation once the chain passes that height (or be skipped, leaving the user no recall window). Always derive from the latest Miden block height at note-creation time.

#### 4.2 Quote

```tsx
const { taskTypeString, intentData } = await sdk.getTaskData(taskDataParams);
const quote = await sdk.getIntentQuote({
  sponsorAddress: evmRecipient,
  taskTypeString,
  intentData,
  isNative: false,
});
if (!quote.success) throw new Error(quote.error ?? 'Quote failed');

// quote.tokenIn = required Miden base units to satisfy minTokenOut
```

#### 4.3 Solve

The SDK invokes `createMidenP2IDNote(faucetId, amount, allocatorId)` mid-flight (see §6). The callback creates the P2IDE note via your wallet adapter and returns `{ success: true, noteId }`.

```tsx
const solveResult = await sdk.solveIntent({
  isNative: false,
  sponsorAddress: evmRecipient,
  taskTypeString,
  intentData,
  quoteResult: quote,                          // cached quote — skips a re-quote round-trip
  collateralType: CollateralType.Miden,
  midenFaucetId: midenFaucetIdHex,
  midenSourceAccount: midenAccountIdHex,
  createMidenP2IDNote,
});

// solveResult.intentNonce → status polling key
```

#### 4.4 Field reference

| Field                          | Value                                                                                                                           | Meaning                                                                                                                            |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `depositTokenAddress`          | Zero address                                                                                                                    | Miden source — actual identity is in `extraData`                                                                                   |
| `tokenInAmount`                | `'0'` (reverse) or base units (forward)                                                                                         | See §3                                                                                                                             |
| `outputTokenAddress`           | ERC-20 on destination chain                                                                                                     | What the user receives                                                                                                             |
| `minTokenOut`                  | Base units, decimal string                                                                                                      | Floor on the output the user accepts                                                                                               |
| `destinationChainId`           | EVM chain id as string                                                                                                          | e.g. `'11155111'` for Sepolia                                                                                                      |
| `protocolHashIdentifier`       | Zero hash unless using a named protocol                                                                                         | 32 bytes                                                                                                                           |
| `recipient`                    | EVM address                                                                                                                     | Receives the output token                                                                                                          |
| `extraData.midenSourceAccount` | Canonical hex                                                                                                                   | User's Miden account                                                                                                               |
| `extraData.midenFaucetId`      | Canonical hex                                                                                                                   | Miden faucet / asset id                                                                                                            |
| `extraData.midenNoteType`      | `'P2IDE'`                                                                                                                       | Encumbered, recallable                                                                                                             |
| `extraData.midenReclaimHeight` | Absolute Miden block number, must satisfy `reclaimHeight - currentBlock >= 1000` (allocator default `MIDEN_MIN_RECLAIM_BLOCKS`) | After this block, the user can reclaim a stuck note. Allocator rejects intents whose on-chain reclaim window is below the minimum. |
| `collateralType`               | `CollateralType.Miden`                                                                                                          | Routes through the P2IDE callback                                                                                                  |

***

### 5. EVM → Miden

User spends an ERC-20 on an EVM chain to receive a Miden P2ID note. No Miden-side signing — the solver writes the destination note. P2ID is final (non-recallable).

#### 5.1 Encode task data

```tsx
const MIDEN_DESTINATION_CHAIN_ID = 999999999;

const taskDataParams = {
  taskType: 'gettokenout' as const,
  intentData: {
    isNative: false,
    depositTokenAddress: erc20Address,                // ERC-20 the user is spending
    tokenInAmount: '0',                                // reverse quote
    outputTokenAddress: '0x' + '0'.repeat(40),         // zero address — output is on Miden
    minTokenOut: minMidenBaseUnits,                    // floor in Miden base units
    destinationChainId: String(MIDEN_DESTINATION_CHAIN_ID),
    protocolHashIdentifier: ZERO_HASH,
    recipient: evmSourceAddress,                       // EVM address — refund target if intent fails
  },
  extraDataTypestring:
    'string midenRecipientAccount,string midenFaucetId,string midenNoteType',
  extraData: {
    midenRecipientAccount: midenRecipientHex,
    midenFaucetId: midenFaucetIdHex,
    midenNoteType: 'P2ID',                             // non-recallable
  },
};
```

#### 5.2 Quote + solve

```tsx
const { taskTypeString, intentData } = await sdk.getTaskData(taskDataParams);
const quote = await sdk.getIntentQuote({
  sponsorAddress: evmSourceAddress,
  taskTypeString,
  intentData,
  isNative: false,
});
if (!quote.success) throw new Error(quote.error ?? 'Quote failed');

const solveResult = await sdk.solveIntent({
  isNative: false,
  sponsorAddress: evmSourceAddress,
  taskTypeString,
  intentData,
  quoteResult: quote,
  collateralType: CollateralType.EVM,                  // SDK handles the Compact deposit
  // No createMidenP2IDNote — solver delivers the destination Miden P2ID note.
});
```

`solveResult` contains the EVM transactions the user needs to sign (typically `approve` + Compact deposit, depending on the token). Submit them via your wallet client.

#### 5.3 Field reference

| Field                             | Value                | Meaning                                   |
| --------------------------------- | -------------------- | ----------------------------------------- |
| `depositTokenAddress`             | ERC-20 address       | Token the user spends                     |
| `outputTokenAddress`              | Zero address         | Output is on Miden                        |
| `destinationChainId`              | `'999999999'`        | Miden virtual chain id                    |
| `recipient`                       | `evmSourceAddress`   | Refund target on the EVM side             |
| `collateralType`                  | `CollateralType.EVM` | SDK handles Compact deposit               |
| `extraData.midenRecipientAccount` | Canonical hex        | Miden account that receives the P2ID note |
| `extraData.midenNoteType`         | `'P2ID'`             | Final, non-recallable                     |

***

### 6. `createMidenP2IDNote` callback

When `collateralType === CollateralType.Miden`, the SDK delegates resource-locking back to your dapp.

```tsx
type CreateMidenP2IDNote = (
  faucetId: string,    // Miden faucet hex id
  amount: string,      // base units, decimal string
  allocatorId: string, // target Miden account (the allocator)
) => Promise<{ success: true; noteId: string } | { success: false; error: string }>;
```

Reference implementation using `@miden-sdk/miden-wallet-adapter-base`:

```tsx
import { SendTransaction } from '@miden-sdk/miden-wallet-adapter-base';
import { useMidenFiWallet } from '@miden-sdk/miden-wallet-adapter-react';

const { requestSend, waitForTransaction } = useMidenFiWallet();

const createMidenP2IDNote: CreateMidenP2IDNote = async (faucetId, amount, allocatorId) => {
  const value = BigInt(amount);
  if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
    return { success: false, error: 'Amount exceeds JS safe integer for wallet adapter' };
  }
  const txId = await requestSend(new SendTransaction(
    midenAccountIdHex,
    allocatorId,
    faucetId,
    'public',                                          // public so the allocator can read + consume
    Number(value),
  ));
  const finalized = await waitForTransaction(txId, 120_000);
  const noteId = finalized.outputNotes?.[0]?.id().toString();
  return noteId
    ? { success: true, noteId }
    : { success: false, error: `No output note id for tx ${txId}` };
};
```

Hard requirements:

* **`'public'` note type.** Private notes cannot be consumed by the allocator.
* **Output-note id is non-optional.** The allocator binds the intent to this note id.
* **Settle before returning.** `waitForTransaction` blocks until finalization on Miden. Returning early breaks allocator verification.
* **Reject amounts above `Number.MAX_SAFE_INTEGER`.** `SendTransaction` accepts a JS `number` for `amount`. Above `2^53 − 1`, low bits drop — never silently truncate.
* **Return, do not throw, on Miden-side failures.** Throwing aborts the whole intent without giving the SDK a chance to clean up.

***

### 7. Track intent status

After `solveIntent` returns, poll the allocator:

```tsx
const ALLOCATOR_URL = import.meta.env.VITE_ALLOCATOR_URL ?? '<http://localhost:3000>';

async function pollStatus(userAddress: string, intentNonce: string) {
  const res = await fetch(
    `${ALLOCATOR_URL}/miden/status/${encodeURIComponent(userAddress)}/${encodeURIComponent(intentNonce)}`,
  );
  if (!res.ok) return null;
  return res.json() as Promise<{
    evmCompleted: boolean;
    evmTransactionHash?: string;
    midenConsumed: boolean;
    midenConsumeError?: string;
    retryCount?: number;
  }>;
}
```

* **Cadence:** every **5 seconds**.
* **Stop:** when `evmCompleted && midenConsumed`.
* **Tear down:** clear the interval on unmount; do not leak polling across navigation.
* **`midenConsumeError` after `evmCompleted: true`** is a settlement edge case the allocator is retrying — surface but do not treat as terminal.
* **`userAddress`** is the EVM address: the **recipient** in Miden→EVM, the **source** in EVM→Miden. In both cases this is the value of `intentData.recipient`.

```tsx
const userAddress = intentData.recipient;
const intentNonce = solveResult.intentNonce;
```

***

### 8. Identifier normalization

Miden ids appear in four forms; the allocator expects canonical hex:

```tsx
import { AccountId, Address } from '@miden-sdk/miden-sdk';

export function normalizeMidenIdToHex(raw: string): string {
  const id = raw.trim();
  if (id.startsWith('0x') || id.startsWith('0X')) {
    return AccountId.fromHex(id).toString();
  }
  if (/^[0-9a-fA-F]+$/.test(id) && id.length % 2 === 0) {
    return AccountId.fromHex('0x' + id).toString();
  }
  if (id.includes('_')) {
    return Address.fromBech32(id).accountId().toString();
  }
  return AccountId.fromBech32(id).toString();
}
```

Normalize **before** assembling `intentData` and `extraData`. The allocator does not auto-detect bech32.

***

### 9. Decimals & amounts

Pass amounts to the SDK in **base units** (decimal strings):

| Field                          | Format                                 | Source  |
| ------------------------------ | -------------------------------------- | ------- |
| `tokenInAmount`                | Base units, or `'0'` for reverse quote | Caller  |
| `minTokenOut`                  | Base units                             | Caller  |
| `quoteResult.tokenIn`          | Base units                             | Backend |
| `quoteResult.tokenOut`         | Base units                             | Backend |
| `createMidenP2IDNote` `amount` | Base units                             | SDK     |

Use `useAssetMetadata(faucetId)` from `@miden-sdk/react` for canonical Miden faucet decimals. The backend may echo `midenFaucetDecimals` on `quoteResult` — treat it as advisory only.

**Never `parseUnits` an integer string.** `parseUnits('1099993', 6)` returns `1099993000000` — a 1,000,000× error. Integer strings are already base units:

```tsx
import { formatUnits, parseUnits } from 'viem';

function formatBaseUnits(raw: string, decimals: number): string {
  if (!raw || raw === '0') return 'calculated at execution';
  if (/^\\\\d+\\\\.\\\\d+$/.test(raw)) {
    return formatUnits(parseUnits(raw, decimals), decimals);
  }
  return formatUnits(BigInt(raw), decimals);
}
```


---

# 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.epochprotocol.xyz/epoch-miden-integration/integration-guide.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.
