Miden → EVM Lending Integration
Use TaskType.ProtocolInteraction with Miden P2IDE note collateral to fund an earn / lending deposit on an EVM chain (e.g. dummy-lending on Sepolia) while the user pays from Miden testnet USDC.
This guide is written for Miden wallet / dApp integrators. It mirrors the production flow implemented in the Epoch Intent Widget earn mode.
What the user experience looks like
User connects an EVM wallet (intent sponsor / recipient on the lending market).
User connects a Miden wallet and selects Miden USDC as the funding source.
App requests a quote from the Epoch allocator (smallocator).
User confirms; app creates a P2IDE note on Miden paying the quoted amount to the Epoch allocator account.
Epoch SIO consumes the note, fronts EVM liquidity, runs swap/bridge (if needed), and executes the lending deposit on the destination chain.
App polls intent status until complete.
There is no EVM ERC-20 approval for Miden-funded deposits. Collateral is the Miden note, not Compact on-chain registration.
Architecture
Your app
Builds intent data, overrides origin chain to Miden, implements createMidenP2IDNote
Epoch allocator
Quote (/checkIfDepositNeeded), submit (/compact), status
SIO
Multi-leg routing: FillerSwapAndBridge → protocol-interaction (deposit)
Miden note
User collateral; recipient must be allocator P2ID account from GET /miden-recipient
Prerequisites
EVM wallet
Sponsor address on the intent (recipient = same user EVM address)
Miden wallet
Must support sending a public P2IDE note to a hex account id
Epoch allocator URL
Testnet e.g. http://localhost:3000 or hosted allocator
Packages
@epoch-protocol/epoch-intents-sdk, @epoch-protocol/epoch-commons-sdk, viem
Network
Testnet only today (dummy-lending + Miden testnet)
Install:
Critical constants
MIDEN_VIRTUAL_CHAIN_ID
999999999
Origin chain id for all Miden→EVM allocator requests
Miden USDC faucet id
0xfc90f0f4da30e51168453b60eafed7
Default testnet faucet (6 decimals)
Miden USDC decimals
6
Always use 6 for tokenInAmount and P2ID note amount
EVM tokenIn sentinel
0x0000000000000000000000000000000000000000
Signals Miden source (not native ETH)
P2IDE reclaim window
Set when creating the Miden note
Not part of the signed witness; configured on note creation
Amount rule: Miden-side amounts are always in 6-decimal atomic units (1 USDC = 1_000_000). EVM market underlyings may use 18 decimals; Epoch converts internally during quoting. Do not scale Miden amounts to 18 decimals yourself.
Protocol hash
For dummy-lending test markets:
For 1delta mainnet markets, use keccak256(toBytes("1delta")) instead.
Market identifier (marketUid)
marketUid)Lending markets are addressed by a colon-delimited uid embedded in extraData.marketUid:
Example (USDC market on Ethereum Sepolia):
Prefix
DUMMY_LENDING → routes to dummy-lending earn solver
chainId
Chain where the lending market lives (11155111, 84532, …)
underlyingTokenAddress
ERC-20 deposited into the vault (lowercase ok)
destinationChainId in intentData must match the market chain. outputTokenAddress / payAsset must match the market underlying.
Intent fields (lending deposit)
Task type
TaskType.ProtocolInteraction
Core intentData fields
intentData fieldsdepositTokenAddress
0x0000000000000000000000000000000000000000
tokenInAmount
User input in Miden 6-dec atomic units, e.g. parseUnits("1", 6)
outputTokenAddress
Market underlying ERC-20
minTokenOut
"0" (forward quote from user Miden input)
destinationChainId
Market chain id string, e.g. "11155111"
protocolHashIdentifier
keccak256(toBytes("dummy-lending"))
recipient
User EVM address (sponsor)
extraData schema (lending + Miden)
extraData schema (lending + Miden)Miden bridge intents use field inclusion, not an exact suffix string. The witness must declare and include the required Miden → EVM fields (midenSourceAccount, midenFaucetId, midenNoteType, midenNoteId). Earn / lending fields may appear before or after the Miden block.
Use the canonical constants exported from @epoch-protocol/epoch-intents-sdk:
Witness hashing: Miden and earn fields use
string(andbool/uint256where declared) in the typestring. The allocator hashes witness data with EIP-712, matching client signing. The samewitnessTypeStringis forwarded through quote (/checkIfDepositNeeded), compact submission, and SIO routing.
midenSourceAccount
Before quote — user's Miden account id (hex, 15 bytes)
midenFaucetId
Before quote — faucet id for the asset sent in the note (hex, 15 bytes)
midenNoteType
Before quote — use P2IDE for reclaimable collateral notes
midenNoteId
After P2IDE creation — SDK writes this into intentData before /compact
Origin chain override (required)
The allocator SDK uses walletClient.chain.id as the intent origin chain. For Miden deposits you must override it to 999999999 even when the EVM wallet is connected to Sepolia:
Without this, the allocator treats the flow as Sepolia → Sepolia instead of Miden → Sepolia.
Headless SDK flow (full example)
createMidenP2IDNote contract
createMidenP2IDNote contractThe SDK calls your callback only after quote confirms resourceLockRequired. Parameters:
faucetId
string
Normalized hex faucet id
amount
string
Atomic Miden units (6 decimals) — usually quote.tokenIn after reverse sizing
allocatorAccountId
string
From GET {allocator}/miden-recipient — must be note recipient
Return { success: true, noteId: "<note_id_string>" } or { success: false, error: "..." }.
The SDK fetches the allocator id automatically via GET /miden-recipient before invoking your callback.
Widget integration (optional)
If you prefer a pre-built UI, use @epoch-protocol/epoch-intent-widget in mode="earn" and inject a Miden adapter:
The widget handles origin-chain override, intent encoding, quote debouncing, and status polling internally.
Allocator HTTP surface
POST
/checkIfDepositNeeded
Quote only — returns resourceLockRequired, path, tokenIn, tokenOut
GET
/miden-recipient
{ midenP2IDRecipientAccountId } — P2IDE payee
POST
/compact
Submit intent after note creation
GET
/intent/status/:address/:nonce
Poll execution (nonce is decimal string from submit response)
Quote request body includes "chainId": "999999999" for Miden-origin intents.
Quote response semantics
resourceLockRequired
true
transactions
[] at quote time (no EVM txs until submit)
tokenIn
Miden collateral in 6-dec atomic units (may include routing buffer)
tokenOut
Expected amount credited on EVM side (market underlying decimals)
path
Multi-leg route, typically FillerSwapAndBridge → protocol-interaction
Display quote.tokenIn formatted with 6 decimals to the user before submit.
P2IDE note checklist
Two-wallet model
Miden
Miden testnet
Holds USDC faucet balance; signs P2IDE send
EVM
Any (Sepolia, Base Sepolia, …)
Intent sponsor address; not used for token approval in Miden flow
The EVM wallet does not need to be on the destination market chain for quoting, but must be connected for SDK initialization.
Local development stack
smallocator (allocator)
3000
epoch-sio
8080
dex-solver
3002
dummy-lending-solver
3012
dummy-lending positions API
4024
Point the widget / SDK at api.baseUrl: http://localhost:3000.
Common failures
Route shows Sepolia → Sepolia
Missing walletClient.chain.id = 999999999 override
P2ID amount ~10¹² USDC
Miden amount expressed in 18-dec instead of 6-dec
NO_QUOTE_AVAILABLE / undefined.includes
Wrong earn solver (lending-solver vs dummy-lending-solver)
Note validation failed
P2IDE recipient ≠ /miden-recipient account
Stuck after quote, no Miden tx
Expected — user must confirm submit; note is created in solveIntent
NO_VALID_TREASURY_CANDIDATE
EVM inventory/treasury not funded on destination chain
Related docs
Protocol Interaction — general
TaskType.ProtocolInteractionpatternsSwap & Bridge — routing leg before the deposit
Quickstart — SDK bootstrap
Support
Contact the Epoch team for allocator URLs, MIDEN_ALLOCATOR_ACCOUNT_ID alignment, and graph registration of new lending protocols or Miden faucet assets.
Last updated