Flash Loans
Borrow ADA from the ADA Harvest vault within a single Cardano transaction. Repayment is enforced on-chain by the Plutus v3 smart contract — no trust, no custodian.
How it works
ADA Harvest flash loans work natively in the Cardano eUTXO model. Everything happens in a single transaction:
- 1Fetch terms: Call GET https://api.ada-harvest.com/api/v1/flash-loan/terms to get the current vault UTXO, validator CBOR, fee rate, and loan cap.
- 2Build your tx: Spend the vault UTXO with the FlashLoan redeemer. Add your arbitrage / liquidation steps. Return vault input lovelace + fee to the vault address with the unchanged datum.
- 3Verify (optional): POST https://api.ada-harvest.com/api/v1/flash-loan/verify to dry-run repayment check before broadcasting.
- 4Submit: POST https://api.ada-harvest.com/api/v1/flash-loan/submit with your signed tx CBOR. The backend submits to the chain and logs the loan.
API Reference
/api/v1/flash-loan/termsPublicReturns everything needed to build a flash loan tx.
{
"success": true,
"data": {
"vaultAddress": "addr1...",
"validatorCbor": "590749...",
"vaultUtxo": {
"txHash": "abc123...",
"outputIndex": 0,
"lovelace": "5000000000",
"datumCbor": "d8799f..."
},
"feeBps": 30,
"minFeesBps": 5,
"maxFeesBps": 100,
"minLoanLovelace": "10000000",
"maxLoanLovelace": "2500000000",
"network": "mainnet"
}
}/api/v1/flash-loan/verifyPublicDry-run: checks that your tx returns enough ADA to the vault. Returns { valid, reason }.
// Body
{
"txCbor": "84a5...",
"borrower": "addr1...",
"amountLovelace": "100000000",
"feeBps": 30
}
// Response
{ "success": true, "data": { "valid": true } }/api/v1/flash-loan/submitPublicSubmit a signed flash loan tx. Returns txHash on success.
// Body
{
"txCbor": "84a5...",
"borrower": "addr1...",
"amountLovelace": "100000000",
"feeBps": 30 // optional, defaults to 30
}
// Response
{
"success": true,
"data": {
"txHash": "def456...",
"logId": "uuid",
"amountLovelace": "100000000",
"feeLovelace": "30000"
}
}SDK Quickstart
Import the FlashLoanClient from the SDK. It handles term fetching, tx construction, verification, and submission.
import { FlashLoanClient } from 'ada-harvest-sdk'; // npm install ada-harvest-sdk
import { Lucid, Blockfrost } from 'lucid-cardano';
// 1. Set up Lucid with your wallet
const lucid = await Lucid.new(
new Blockfrost('https://cardano-mainnet.blockfrost.io/api/v0', 'mainnetXXX'),
'Mainnet'
);
lucid.selectWalletFromSeed('your twelve word seed phrase...');
// 2. Create client pointing at the ADA Harvest API
const client = new FlashLoanClient('https://api.ada-harvest.com');
// 3. Fetch current terms
const terms = await client.getTerms();
console.log('Max loan:', terms.maxLoanLovelace, 'lovelace');
console.log('Fee:', terms.feeBps, 'bps');
// 4. Build and submit in one call
const result = await client.buildAndSubmit({
terms,
amountLovelace: '50000000', // 50 ADA
borrowerAddress: await lucid.wallet.address(),
lucid,
buildArbitrageTx: async (tx, loanedAda) => {
// Add your arbitrage steps here.
// tx is a Lucid TxBuilder with the vault input already added.
// loanedAda is a BigInt of lovelace available to you.
//
// Example: swap on Minswap, collect profit, return here.
// The SDK will append the vault repayment output after this callback.
return tx;
},
});
console.log('Flash loan confirmed:', result.txHash);
console.log('Fee paid:', Number(result.feeLovelace) / 1_000_000, 'ADA');Full example: DEX arbitrage
This example borrows 100 ADA, swaps on DEX A, swaps back on DEX B, keeps the spread as profit, and repays the vault. The TypeScript SDK wraps the complexity; the raw Python example below shows exactly what happens on-chain.
import { FlashLoanClient, calcRepaymentLovelace } from '@/lib/flash-loan';
import { Lucid, Blockfrost } from 'lucid-cardano';
const lucid = await Lucid.new(
new Blockfrost('https://cardano-preprod.blockfrost.io/api/v0', process.env.BLOCKFROST_KEY!),
'Preprod'
);
lucid.selectWalletFromSeed(process.env.SEED!);
const client = new FlashLoanClient('https://api.ada-harvest.com');
const terms = await client.getTerms();
const AMOUNT = 100_000_000n; // 100 ADA
const MIN_PROFIT = 1_000_000n; // abort if profit < 1 ADA
// Check the loan fits within the cap
if (AMOUNT > BigInt(terms.maxLoanLovelace)) {
throw new Error('Amount exceeds vault cap');
}
// Calculate how much we must repay (principal + fee)
const repayment = calcRepaymentLovelace(
BigInt(terms.vaultUtxo.lovelace),
AMOUNT,
terms.feeBps
);
const result = await client.buildAndSubmit({
terms,
amountLovelace: AMOUNT.toString(),
borrowerAddress: await lucid.wallet.address(),
lucid,
buildArbitrageTx: async (tx, loanedAda) => {
// ── Step 1: buy TOKEN on DEX A (ADA → TOKEN) ──────────────────────
// Replace with your actual DEX integration
const dexAOutput = await lucid
.newTx()
.payToAddress(DEX_A_ADDRESS, { lovelace: loanedAda })
// ... swap parameters
;
// ── Step 2: sell TOKEN on DEX B (TOKEN → ADA) ─────────────────────
// You should receive loanedAda + spread back as ADA
// ── Step 3: verify profit covers repayment + your minimum ──────────
// (In practice check reserves before building the tx)
// The SDK will automatically append the vault repayment output.
// Just return your partially-built tx.
return tx;
},
});
console.log('Arbitrage complete. txHash:', result.txHash);Python (pycardano) — raw on-chain example
This is the exact script we used to execute the first mainnet flash loan on 2 June 2026 (tx fea889fe...). It uses pycardano directly with a PlutusV3Script — no lucid dependency.
# pip install pycardano cbor2 requests
import hashlib, cbor2, requests, json
import pycardano.utils as pu, pycardano.txbuilder as tb
# Patch max_tx_fee so pycardano sizes collateral correctly
pu.max_tx_fee = lambda context, ref_script_size=0: 1_000_000
tb.max_tx_fee = lambda context, ref_script_size=0: 1_000_000
from pycardano import (
BlockFrostChainContext, Network, Address, PlutusV3Script,
TransactionBuilder, TransactionOutput, Redeemer, RawPlutusData,
TransactionWitnessSet, Transaction, VerificationKeyWitness, ExecutionUnits,
)
from pycardano.key import PaymentExtendedSigningKey
from pycardano.hash import ScriptHash
from pycardano.crypto.bip32 import bindings
from pycardano.utils import script_data_hash as sdh_fn
from pycardano.plutus import CostModels
from pycardano.serialization import NonEmptyOrderedSet
BF_KEY = 'mainnetXXXXXXXXXXXXXXX' # your Blockfrost mainnet key
BF_URL = 'https://cardano-mainnet.blockfrost.io/api/v0'
BORROWER = 'addr1q...' # your wallet address (must hold ADA for fees)
LOAN_ADA = 5_000_000 # 5 ADA
FEE_BPS = 50
# BIP32 extended payment key — extract from lucid: walletFromSeed(mnemonic).paymentKey
# Then: bech32-decode the ed25519e_sk1... string → 64 bytes: left(32) + right(32)
SK_LEFT = bytes.fromhex('your_32_byte_scalar_hex')
SK_RIGHT = bytes.fromhex('your_32_byte_nonce_hex')
PUB = bindings.crypto_scalarmult_ed25519_base_noclamp(SK_LEFT)
sk = PaymentExtendedSigningKey(payload=(SK_LEFT + SK_RIGHT) + PUB + b'\x00'*32)
vk = sk.to_verification_key()
context = BlockFrostChainContext(BF_KEY, network=Network.MAINNET)
borrower_addr = Address.from_primitive(BORROWER)
# 1. Fetch vault script CBOR from Blockfrost
r = requests.get(f'{BF_URL}/addresses/{borrower_addr}/utxos') # replace with vault addr
terms = requests.get('https://api.ada-harvest.com/api/v1/flash-loan/terms').json()['data']
vault_addr = terms['vaultAddress']
# Load PlutusV3 script
script_cbor_r = requests.get(
f'{BF_URL}/scripts/{terms["validatorCbor"][:56]}/cbor', # actually use /scripts/{hash}/cbor
headers={'project_id': BF_KEY})
# OR: use the validatorCbor directly from terms
outer = bytes.fromhex(terms['validatorCbor'])
inner = cbor2.loads(outer)
script_hash_hex = hashlib.blake2b(b'\x03' + cbor2.dumps(inner), digest_size=28).hexdigest()
script = PlutusV3Script(cbor2.dumps(inner))
vault_utxos = context.utxos(vault_addr)
v = max(vault_utxos, key=lambda u: u.output.amount.coin)
FEE_LOVELACE = (LOAN_ADA * FEE_BPS) // 10_000
repay = v.output.amount.coin + FEE_LOVELACE
# 2. Build redeemer: FlashLoan(amount, fee_bps) = Constr 9 = CBOR tag 1282
redeemer = Redeemer(
RawPlutusData(cbor2.CBORTag(1282, [LOAN_ADA, FEE_BPS])),
ExecutionUnits(mem=500_000, steps=100_000_000), # must cover script execution
)
# 3. Build transaction
builder = TransactionBuilder(context)
builder.add_input_address(borrower_addr)
for u in context.utxos(BORROWER): # add borrower UTxOs as collateral
builder.collaterals.append(u)
builder.add_script_input(v, script, None, redeemer) # None = inline datum on UTxO
builder.add_output(TransactionOutput(Address.from_primitive(vault_addr), repay, datum=v.output.datum))
tx_body = builder.build(change_address=borrower_addr)
# 4. Fix PPView hash (pycardano uses wrong CostModels key for PlutusV3)
correct_sdh = sdh_fn([redeemer], NonEmptyOrderedSet([]),
CostModels({2: context.protocol_param.cost_models['PlutusV3']}))
tx_body.script_data_hash = correct_sdh
# 5. Sign & submit
sig = sk.sign(bytes(tx_body.hash()))
tx = Transaction(tx_body, TransactionWitnessSet(
vkey_witnesses=[VerificationKeyWitness(vkey=vk, signature=sig)],
plutus_v3_script=[script],
redeemer=[redeemer],
))
r = requests.post(f'{BF_URL}/tx/submit',
headers={'project_id': BF_KEY, 'Content-Type': 'application/cbor'},
data=tx.to_cbor(), timeout=30)
print('txHash:', r.text)Security model
Flash loans are secured by the Plutus v3 validator, not by the backend. The backend is a convenience layer — borrowers can also build and submit transactions directly without using it.