This guide explains how to integrate with the Haiku Intents API. It also includes a complete client-side example for executing transactions, handling approvals, and signing Permit2 data.
This example demonstrates how to complete the following actions:
Request an intent quote with /quoteIntent
Handle token approvals for the Permit2 contract.
Sign Permit2 sub-allowance data.
[Optional] Sign the post-bridge operation if required
Construct the final transaction using /solveIntent
Execute the transaction using a Web3 wallet.
import { ethers, JsonRpcProvider } from "ethers";
const userAddress = "0x<YOUR_ADDRESS>";
const userPrivateKey ="<YOUR_PRIVATE_KEY>";
const RPC_MAIN = "https://arbitrum.llamarpc.com";
async function main() {
const provider = new JsonRpcProvider(RPC_MAIN);
const wallet = new ethers.Wallet(userPrivateKey, provider);
const haikuApiBaseUrl = "https://api.haiku.trade/v1";
const headers = {
"Content-Type": "application/json",
"api-key": "<your_haiku_api_key>",
};
// Fetch a quote for your Intent
const quoteIntentPayload = {
intent: {
receiver: userAddress,
slippage: 0.003, // 0.3% max slippage
input_positions: {
"arb:0xaf88d065e77c8cc2239327c5edb3a432268e5831": "1", // $1 USDC(Arb)
"arb:0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9": "0.3", // $0.30 USDT(Arb)
},
target_weights: {
"arb:0x82af49447d8a07e3bd95bd0d56f35241523fbab1": 1 // 100% WETH(Arb)
},
},
};
console.log("Fetching a quote for the Intent...");
const quoteRes = await fetch(`${haikuApiBaseUrl}/quoteIntent`, {
method: "POST",
headers: headers,
body: JSON.stringify(quoteIntentPayload),
});
const quoteData = await quoteRes.json();
console.log("Quote Data: ", JSON.stringify(quoteData));
// Process any ERC20 Approvals needed for the transaction
if (quoteData.approvals.length > 0) {
console.log("Granting one-time ERC20 Approvals to centralised Permit2 contract...");
await Promise.all(
quoteData.approvals.map((approval) =>
wallet.sendTransaction({ to: approval.to, data: approval.data })
)
);
}
// Sign any Permit2 Data needed for the transaction
let permitSignature = "";
if (quoteData.permit2Datas) {
console.log("Granting Permit2 allowance to Haiku Router to facilitate this trade...");
// Helper code to convert BigNumber 'amount' values to string for EthersV6
let permit2Values = quoteData.permit2Datas.values;
if (Array.isArray(permit2Values.details)) {
// Multiple input token case
permit2Values.details = permit2Values.details.map(detail => ({
...detail,
amount: BigInt(detail.amount.hex).toString(),
}));
} else if (permit2Values.details && permit2Values.details.amount) {
// Single input token case
permit2Values.details.amount = BigInt(permit2Values.details.amount.hex).toString();
}
// Sign the Permit2 data to grant allowances for this trade
permitSignature = await wallet.signTypedData(
quoteData.permit2Datas.domain,
quoteData.permit2Datas.types,
quoteData.permit2Datas.values
);
}
// If the quote involves a complex post-bridge transaction, we sign for the destination bridge
let userSignature;
if (data.isComplexBridge) {
const { unsignedTypeV4Digest } = data.destinationBridge;
userSignature = await wallet._signTypedData(
unsignedTypeV4Digest.domain,
unsignedTypeV4Digest.types,
unsignedTypeV4Digest.values
);
console.log(
isValidSignature({
domain: unsignedTypeV4Digest.domain,
types: unsignedTypeV4Digest.types,
message: unsignedTypeV4Digest.values,
signature: userSignature,
expectedAddress: wallet.address,
})
);
}
const quoteId = data.quoteId;
const solveIntentPayload = {
quoteId,
userSignature,
permit2Signature,
};
// Build and send the Intent transaction
console.log("Building the unsigned EVM transaction to execute the Intent...");
const solveRes = await fetch(`${haikuApiBaseUrl}/solveIntent`, {
method: "POST",
headers: headers,
body: JSON.stringify(solveIntentPayload),
});
const unsignedTransactionRequest = await solveRes.json();
console.log("Unsigned transaction received, signing and broadcasting on chain...");
await wallet.sendTransaction(unsignedTransactionRequest);
console.log("Completed, have a nice day :)");
}
main().catch(console.error);
ERC-20 Approvals: Are required once for each input token per Permit2 contract.
Developer notes
Permit2 Documentation: Details regarding the model.