@deserialize/evm-limit-sdk
Version:
TypeScript client for LimitOrderWithPermit2 smart contract
311 lines • 11.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LimitOrderMaker = void 0;
const ethers_1 = require("ethers");
const LimitOrderClient_1 = require("./LimitOrderClient");
/**
* SDK for creating and managing limit orders (Maker side)
* Can be used in both frontend and backend environments
*/
class LimitOrderMaker {
constructor(client, signer, serverUrl = 'https://limit-engine.deserialize.xyz/api/v1') {
this.client = client;
this.signer = signer;
this.serverUrl = serverUrl;
}
/**
* Create a complete limit order with all signatures
* @param params Order parameters
* @returns Signed order ready to be submitted
*/
async createOrder(params) {
const makerAddress = await this.signer.getAddress();
// Build complete order
const order = {
maker: makerAddress,
makerToken: params.makerToken,
takerToken: params.takerToken,
makerAmount: params.makerAmount,
takerAmount: params.takerAmount,
salt: params.salt || (0, LimitOrderClient_1.generateOrderSalt)(),
expiry: params.expiry || (0, LimitOrderClient_1.createOrderExpiry)(86400) // 24 hours default
};
// Handle native token wrapping if needed
let finalOrder = { ...order };
if (order.makerToken.toLowerCase() === this.client.NATIVE_TOKEN.toLowerCase()) {
console.log('🔄 Wrapping native token...');
const wrapTx = this.client.getWrapNativeTransaction(order.makerAmount.toString());
const wrapReceipt = await this.signer.sendTransaction(wrapTx);
await wrapReceipt.wait();
console.log('✅ Native token wrapped');
finalOrder.makerToken = this.client.W_NATIVE_ADDRESS;
}
// Check and handle Permit2 approval
await this.ensurePermit2Approval(finalOrder.makerToken);
// Get signing data
const { orderDataToSign, permit2DataToSign } = this.client.getCreateOrderMessageData(finalOrder);
// Sign both messages
console.log('📝 Signing order...');
const orderSignature = await this.signer.signTypedData(orderDataToSign.domain, orderDataToSign.types, orderDataToSign.value);
console.log('📝 Signing Permit2...');
const permit2Signature = await this.signer.signTypedData(permit2DataToSign.domain, permit2DataToSign.types, permit2DataToSign.value);
// Get order hash
const orderHash = await this.client.getOrderHash(finalOrder);
const signedOrder = {
order: finalOrder,
orderSignature,
permit2Signature,
orderHash,
createdAt: Date.now()
};
console.log('✅ Order created and signed:', orderHash);
return signedOrder;
}
/**
* Submit a signed order to the external server
* @param signedOrder The signed order to submit
*/
async submitOrder(signedOrder) {
return this.saveOrderToServer(signedOrder);
}
/**
* Create and submit an order in one step
* @param params Order parameters
*/
async createAndSubmitOrder(params) {
const signedOrder = await this.createOrder(params);
const submitResponse = await this.submitOrder(signedOrder);
if (!submitResponse.success) {
throw new Error(`Failed to submit order: ${submitResponse.error}`);
}
return { signedOrder, submitResponse };
}
/**
* Cancel an order
* @param orderHash The order hash to cancel
* @param salt The order salt to cancel on-chain
*/
async cancelOrder(orderHash, salt) {
console.log('🚫 Cancelling order on-chain...');
const tx = await this.client.cancelOrder(salt, this.signer);
await tx.wait();
console.log('✅ Order cancelled on-chain');
// Also notify the server
try {
await this.notifyServerOfCancellation(orderHash, tx.hash);
}
catch (error) {
console.warn('⚠️ Failed to notify server of cancellation:', error);
}
return tx;
}
/**
* Check if an order has been filled
* @param orderHash The order hash
*/
async isOrderFilled(orderHash) {
// Check on-chain first
const onChainFilled = await this.client.isOrderFilled(orderHash);
if (onChainFilled)
return true;
// Also check server
try {
const serverOrder = await this.getOrderFromServer(orderHash);
return serverOrder?.status === 'FILLED';
}
catch (error) {
return false;
}
}
/**
* Check if an order has been cancelled
* @param salt The order salt
*/
async isOrderCancelled(salt) {
const makerAddress = await this.signer.getAddress();
return await this.client.isCancelled(makerAddress, salt);
}
/**
* Get all filled orders for the maker
*/
async getMyFilledOrders(fromBlock = 0) {
const makerAddress = await this.signer.getAddress();
return await this.client.getOrderFilledEvents(fromBlock, 'latest', {
maker: makerAddress
});
}
/**
* Get maker's orders from server
*/
async getMyOrders(status, limit = 50) {
try {
const makerAddress = await this.signer.getAddress();
const url = new URL(`${this.serverUrl}/makers/${makerAddress}/orders`);
if (status)
url.searchParams.append('status', status);
url.searchParams.append('limit', limit.toString());
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result.data || [];
}
catch (error) {
console.error('Failed to fetch orders from server:', error.message);
throw error;
}
}
/**
* Get order details from server
*/
async getOrderFromServer(orderHash) {
try {
const response = await fetch(`${this.serverUrl}/orders/${orderHash}`);
if (!response.ok) {
if (response.status === 404)
return null;
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result.data;
}
catch (error) {
console.error('Failed to get order from server:', error.message);
return null;
}
}
/**
* Ensure Permit2 has approval for the token
* @param tokenAddress The token address
*/
async ensurePermit2Approval(tokenAddress) {
const makerAddress = await this.signer.getAddress();
const isApproved = await this.isPermit2Approved(tokenAddress);
if (!isApproved) {
console.log('🔄 Approving Permit2...');
const approveTx = await this.approvePermit2(tokenAddress);
await approveTx.wait();
console.log('✅ Permit2 approved');
}
}
/**
* Check if Permit2 is approved for a token
*/
async isPermit2Approved(tokenAddress) {
const ERC20_ABI = [
"function allowance(address owner, address spender) external view returns (uint256)"
];
const token = new ethers_1.ethers.Contract(tokenAddress, ERC20_ABI, this.signer);
const makerAddress = await this.signer.getAddress();
const allowance = await token.allowance(makerAddress, this.client.PERMIT2_CONTRACT);
return allowance > 0;
}
/**
* Approve Permit2 for a token
*/
async approvePermit2(tokenAddress) {
const ERC20_ABI = [
"function approve(address spender, uint256 amount) external returns (bool)"
];
const token = new ethers_1.ethers.Contract(tokenAddress, ERC20_ABI, this.signer);
return await token.approve(this.client.PERMIT2_CONTRACT, ethers_1.ethers.MaxUint256);
}
/**
* Save order to external server
*/
async saveOrderToServer(signedOrder) {
console.log('💾 Saving order to server...');
try {
const response = await fetch(`${this.serverUrl}/orders`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
order: {
maker: signedOrder.order.maker,
makerToken: signedOrder.order.makerToken,
takerToken: signedOrder.order.takerToken,
makerAmount: signedOrder.order.makerAmount.toString(),
takerAmount: signedOrder.order.takerAmount.toString(),
salt: signedOrder.order.salt.toString(),
expiry: signedOrder.order.expiry.toString(),
},
orderSignature: signedOrder.orderSignature,
permit2Signature: signedOrder.permit2Signature,
orderHash: signedOrder.orderHash,
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('✅ Order saved to server:', result.data?.orderHash);
return {
success: true,
orderId: result.data?.orderHash || signedOrder.orderHash,
};
}
catch (error) {
console.error('❌ Failed to save order to server:', error.message);
return {
success: false,
error: error.message,
};
}
}
/**
* Notify server that order was cancelled
*/
async notifyServerOfCancellation(orderHash, transactionHash) {
try {
const makerAddress = await this.signer.getAddress();
const response = await fetch(`${this.serverUrl}/orders/${orderHash}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
maker: makerAddress,
transactionHash,
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log('✅ Server notified of cancellation');
}
catch (error) {
console.error('Failed to notify server:', error.message);
throw error;
}
}
/**
* Get the maker address
*/
async getMakerAddress() {
return await this.signer.getAddress();
}
/**
* Get the LimitOrderClient instance
*/
getClient() {
return this.client;
}
/**
* Set server URL
*/
setServerUrl(url) {
this.serverUrl = url;
}
/**
* Get current server URL
*/
getServerUrl() {
return this.serverUrl;
}
}
exports.LimitOrderMaker = LimitOrderMaker;
//# sourceMappingURL=LimitMaker.js.map