Limit Orders Guide
Create, sign, execute, and cancel limit orders with the Notus API.
Overview
Limit orders use the same swap endpoint as market swaps. Add minAmountOut to the /crypto/swap request to ask Notus for a limit-order quote, then sign both the returned userOperationHash and the limit-order typedData.
Steps
Install dependencies and initialize Wallet Account
Install viem to sign the UserOperation hash and the EIP-712 typed data returned for the limit order.
npm install viemimport { privateKeyToAccount } from 'viem/accounts'
const BASE_URL = 'https://api.notus.team/api/v1'
const API_KEY = '<api-key>'
const BRZ_POLYGON = '0x4ed141110f6eeeaba9a1df36d8c26f684d2475dc'
const USDC_POLYGON = '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359'
const privateKey = '0x<private-key>'
const account = privateKeyToAccount(privateKey)Register a Smart Wallet Address
Register the EOA that signs operations and retrieve the smart wallet address that will own the order.
async function setupSmartWallet() {
const FACTORY_ADDRESS = '0x0000000000400CdFef5E2714E63d8040b700BC24'
const externallyOwnedAccount = account.address
const res = await fetch(`${BASE_URL}/wallets/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
externallyOwnedAccount,
factory: FACTORY_ADDRESS,
salt: '0',
}),
})
if (!res.ok) {
throw new Error(await res.text())
}
const data = await res.json()
return {
smartWalletAddress: data.wallet.accountAbstraction,
externallyOwnedAccount,
}
}Create a Limit Order Quote
Call /crypto/swap with minAmountOut. This tells Notus to prepare a limit order instead of a market swap.
const { smartWalletAddress, externallyOwnedAccount } = await setupSmartWallet()
const { quotes } = await fetch(`${BASE_URL}/crypto/swap`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
amountIn: '5',
chainIdIn: 137,
chainIdOut: 137,
routeProfile: 'QUICKEST_QUOTE',
gasFeePaymentMethod: 'DEDUCT_FROM_AMOUNT',
payGasFeeToken: BRZ_POLYGON,
tokenIn: BRZ_POLYGON,
tokenOut: USDC_POLYGON,
walletAddress: smartWalletAddress,
toAddress: smartWalletAddress,
signerAddress: externallyOwnedAccount,
minAmountOut: '0.19069',
expiration: 2592000,
partialFillable: true,
metadata: {
orderType: 'limit',
requestId: 'limit_order_123',
},
}),
}).then((res) => res.json())
const quote = quotes[0]
if (!quote?.userOperationHash) {
throw new Error('No executable limit-order quote returned')
}minAmountOut is a decimal string in the output token. expiration is optional and is expressed in seconds from quote creation. partialFillable defaults to true.
Sign the UserOperation and Limit Order
The UserOperation signature authorizes the smart wallet operation. The typedData signature authorizes the limit order itself.
const userOperationSignature = await account.signMessage({
message: {
raw: quote.userOperationHash,
},
})
let limitOrderSignature
if (quote.typedData) {
limitOrderSignature = await account.signTypedData({
domain: quote.typedData.domain,
types: quote.typedData.types,
primaryType: quote.typedData.primaryType,
message: quote.typedData.message,
})
}For limit orders, limitOrderSignature is required whenever the quote includes typedData. It is separate from the UserOperation signature.
Execute the Limit Order
Submit both signatures to /crypto/execute-user-op. Use userOperationHash; quoteId is deprecated.
const executionResponse = await fetch(`${BASE_URL}/crypto/execute-user-op`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
userOperationHash: quote.userOperationHash,
signature: userOperationSignature,
limitOrderSignature,
}),
}).then((res) => res.json())
console.log(executionResponse)If the quote includes authorization.hash, sign that hash and include the resulting authorization field in the execute request. This is required for the first transaction of an EIP-7702 wallet.
Cancel a Limit Order
To cancel an active order, prepare the cancellation payload, sign the returned typed data, and execute the cancellation.
const transactionId = '<limit-order-transaction-id>'
const cancelPreparation = await fetch(
`${BASE_URL}/crypto/limit-orders/${transactionId}/cancel`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
walletAddress: smartWalletAddress,
chainId: 137,
payGasFeeToken: BRZ_POLYGON,
}),
},
).then((res) => res.json())
const cancelTypedData = cancelPreparation.signData[0].typedData
const cancelSignature = await account.signTypedData({
domain: cancelTypedData.domain,
types: cancelTypedData.types,
primaryType: cancelTypedData.primaryType,
message: cancelTypedData.message,
})
const cancelExecution = await fetch(
`${BASE_URL}/crypto/limit-orders/${transactionId}/cancel/execute`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': API_KEY,
},
body: JSON.stringify({
signature: cancelSignature,
}),
},
).then((res) => res.json())
console.log(cancelExecution)