@robertprp/intents-sdk
Version:
Shogun Network Intent-based cross-chain swaps SDK
144 lines (123 loc) • 4.74 kB
text/typescript
// src/core/orders/dca-single-chain.ts
import { ChainID, chainIdToChainTypeMap, isEvmChain, type SupportedChain } from '../../chains.js';
import { ValidationError } from '../../errors/index.js';
import type { ApiResponse } from '../../types/api.js';
import type { DcaSingleChainPreparedData, DcaSingleChainUserIntentRequest } from '../../types/intent.js';
import { DcaSingleChainOrderValidator } from '../../utils/order-validator.js';
import { BaseSDK } from '../sdk.js';
import { getEVMDcaSingleChainOrderTypedData } from '../evm/order-signature.js';
import { type ExtraTransfer } from './common.js';
import { getSolanaDcaSingleChainOrderInstructions } from '../solana/dca/create-order.js';
export type CreateDcaSingleChainOrderParams = {
chainId: SupportedChain;
tokenIn: string;
tokenOut: string;
destinationAddress: string;
startTime: number;
amountInPerInterval: bigint;
totalIntervals: number;
intervalDuration: number;
amountOutMin?: bigint;
extraTransfers?: ExtraTransfer[];
deadline: number;
};
export class DcaSingleChainOrder {
public user: string;
public chainId: SupportedChain;
public tokenIn: string;
public tokenOut: string;
public destinationAddress: string;
public startTime: number;
public amountInPerInterval: bigint | number;
public totalIntervals: number;
public intervalDuration: number;
public amountOutMin: bigint | number;
public extraTransfers?: ExtraTransfer[];
public deadline: number;
private constructor(params: CreateDcaSingleChainOrderParams & { user: string }) {
this.user = params.user;
this.chainId = params.chainId;
this.tokenIn = params.tokenIn;
this.tokenOut = params.tokenOut;
this.destinationAddress = params.destinationAddress;
this.startTime = params.startTime;
this.amountInPerInterval = params.amountInPerInterval;
this.totalIntervals = params.totalIntervals;
this.intervalDuration = params.intervalDuration;
this.amountOutMin = params.amountOutMin ?? 1n;
this.extraTransfers = params.extraTransfers;
this.deadline = params.deadline;
}
public static async create(input: CreateDcaSingleChainOrderParams & { user: string }): Promise<DcaSingleChainOrder> {
new DcaSingleChainOrderValidator().validateOrder(input);
const order = new DcaSingleChainOrder({
...input,
user: input.user,
});
if (isEvmChain(order.chainId)) {
return order;
}
const preparedRandomData = order.getRandomPreparedData();
const intentRequest = order.toIntentRequest(preparedRandomData);
await BaseSDK.validateDcaSingleChainOrder(intentRequest);
return order;
}
/// This is needed because API requires the prepared data to be sent
/// In the cases of Solana and Sui, if we want the real data, we must send the order on-chain before validating.
/// And that is something we cannot do before validating the order on the API side.
private getRandomPreparedData(): DcaSingleChainPreparedData {
const chainId = this.chainId;
const chainType = chainIdToChainTypeMap[chainId];
const randomNumber = String(Math.floor(Math.random() * 10000000));
switch (chainType) {
case 'EVM': {
return {
nonce: randomNumber,
signature: '0x0000000000000000000000000000000000000000000000000000000000000000',
};
}
case 'Solana': {
return {
secretNumber: randomNumber,
orderPubkey: 'DFNAjFAvS4GF98Tp1kiyLvEHM3wjGXibCfF86nnmhuVc',
};
}
case 'Sui': {
throw new ValidationError('DCA orders are not supported on Sui');
}
default: {
throw new Error('Chain type not supported');
}
}
}
public getTotalAmountIn(): bigint {
return BigInt(this.amountInPerInterval) * BigInt(this.totalIntervals);
}
public toIntentRequest(preparedData: DcaSingleChainPreparedData): DcaSingleChainUserIntentRequest {
const sourceChainType = chainIdToChainTypeMap[this.chainId];
return {
genericData: this,
chainSpecificData: {
[sourceChainType]: preparedData,
},
};
}
public sendToAuctioneer(preparedData: DcaSingleChainPreparedData): Promise<ApiResponse> {
return BaseSDK.sendDcaSingleChainOrder({
order: this,
preparedData,
});
}
public async toEVMTypedData() {
if (!isEvmChain(this.chainId)) {
throw new ValidationError('Chain id is not an Ethereum compatible chain');
}
return getEVMDcaSingleChainOrderTypedData(this);
}
public async toSolanaInstructionsByteArray() {
if (this.chainId !== ChainID.Solana) {
throw new ValidationError('Chain id is not Solana');
}
return getSolanaDcaSingleChainOrderInstructions(this);
}
}