Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MIN-1548: Auto cancel expired orders worker #47

Merged
merged 11 commits into from
Nov 27, 2024
Next Next commit
init dex v2 worker
m1n999999 committed Nov 26, 2024
commit 8185861e1a1cd4ffb8fb1476d6c2ca185f1fae61
43 changes: 43 additions & 0 deletions examples/dex-v2-worker-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { BlockFrostAPI } from "@blockfrost/blockfrost-js";
import { Network } from "@minswap/lucid-cardano";

import { BlockfrostAdapter, NetworkId } from "../src";
import { DexV2Worker } from "../src/dex-v2-worker";
import { NetworkEnvironment } from "../src/types/network";
import { getBackendLucidInstance } from "../src/utils/lucid";

async function main(): Promise<void> {
const network: Network = "Preprod";
const blockfrostProjectId = "preprodSj4PM4LDOTa2BbfAY4XIEqASI9gKzOEz";
const blockfrostUrl = "https://cardano-preprod.blockfrost.io/api/v0";

const address =
"addr_test1qqf2dhk96l2kq4xh2fkhwksv0h49vy9exw383eshppn863jereuqgh2zwxsedytve5gp9any9jwc5hz98sd47rwfv40stc26fr";
const lucid = await getBackendLucidInstance(
network,
blockfrostProjectId,
blockfrostUrl,
address
);

const blockfrostAdapter = new BlockfrostAdapter(
NetworkId.TESTNET,
new BlockFrostAPI({
projectId: blockfrostProjectId,
network: "preprod",
})
);

const worker = new DexV2Worker({
networkEnv: NetworkEnvironment.TESTNET_PREPROD,
networkId: NetworkId.TESTNET,
lucid,
blockfrostAdapter,
privateKey:
"ed25519e_sk1pqs6ssazw755demuks2974mdwu6stz0uxpj543edm5cm0y96p9gk9lcv5jspdg3aq7wtv9r96uaru0rnu4qdm7lccarntjm22mtk72cm5cjrj",
});

await worker.start();
}

void main();
2 changes: 1 addition & 1 deletion examples/example.ts
Original file line number Diff line number Diff line change
@@ -36,8 +36,8 @@ import {
import { LbeV2 } from "../src/lbe-v2/lbe-v2";
import { Stableswap } from "../src/stableswap";
import { LbeV2Types } from "../src/types/lbe-v2";
import { Slippage } from "../src/utils/slippage.internal";
import { getBackendLucidInstance } from "../src/utils/lucid";
import { Slippage } from "../src/utils/slippage.internal";

const MIN: Asset = {
policyId: "e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72",
2 changes: 1 addition & 1 deletion examples/lbe-v2-worker-example.ts
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@ import { BlockFrostAPI } from "@blockfrost/blockfrost-js";
import { Network } from "@minswap/lucid-cardano";

import { BlockfrostAdapter, NetworkId } from "../src";
import { NetworkEnvironment } from "../src/types/network";
import { LbeV2Worker } from "../src/lbe-v2-worker/worker";
import { NetworkEnvironment } from "../src/types/network";
import { getBackendLucidInstance } from "../src/utils/lucid";

async function main(): Promise<void> {
54 changes: 54 additions & 0 deletions src/adapter.ts
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ import {
import { FactoryV2 } from "./types/factory";
import { LbeV2Types } from "./types/lbe-v2";
import { NetworkEnvironment, NetworkId } from "./types/network";
import { OrderV2 } from "./types/order";
import { PoolV1, PoolV2, StablePool } from "./types/pool";
import {
checkValidPoolOutput,
@@ -272,6 +273,7 @@ export class BlockfrostAdapter implements Adapter {
return latestBlock.slot ?? 0;
}

// MARK: DEX V1
public async getV1PoolInTx({
txHash,
}: GetPoolInTxParams): Promise<PoolV1.State | null> {
@@ -386,6 +388,7 @@ export class BlockfrostAdapter implements Adapter {
return [priceAB, priceBA];
}

// MARK: DEX V2
public async getAllV2Pools(): Promise<{
pools: PoolV2.State[];
errors: unknown[];
@@ -569,6 +572,57 @@ export class BlockfrostAdapter implements Adapter {
return null;
}

public async getAllV2Orders(): Promise<{
orders: OrderV2.State[];
errors: unknown[];
}> {
const v2Config = DexV2Constant.CONFIG[this.networkId];
const utxos = await this.blockFrostApi.addressesUtxosAll(
v2Config.orderScriptHashBech32
);

const orders: OrderV2.State[] = [];
const errors: unknown[] = [];
for (const utxo of utxos) {
try {
let order: OrderV2.State | undefined = undefined;
if (utxo.inline_datum !== null) {
order = new OrderV2.State(
this.networkId,
utxo.address,
{ txHash: utxo.tx_hash, index: utxo.output_index },
utxo.amount,
utxo.inline_datum
);
}
if (utxo.data_hash !== null) {
const orderDatum = await this.blockFrostApi.scriptsDatumCbor(
utxo.data_hash
);
order = new OrderV2.State(
this.networkId,
utxo.address,
{ txHash: utxo.tx_hash, index: utxo.output_index },
utxo.amount,
orderDatum.cbor
);
}
if (order === undefined) {
throw new Error(`Cannot find datum of Order V2, tx: ${utxo.tx_hash}`);
}

orders.push(order);
} catch (err) {
errors.push(err);
}
}
return {
orders: orders,
errors: errors,
};
}

// MARK: STABLESWAP
private async parseStablePoolState(
utxo: Responses["address_utxo_content"][0]
): Promise<StablePool.State> {
107 changes: 107 additions & 0 deletions src/dex-v2-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Lucid } from "@minswap/lucid-cardano";

import { BlockfrostAdapter, DexV2, DexV2Constant, OrderV2 } from ".";
import { NetworkEnvironment, NetworkId } from "./types/network";
import { runRecurringJob } from "./utils/job";

type DexV2WorkerConstructor = {
networkEnv: NetworkEnvironment;
networkId: NetworkId;
lucid: Lucid;
blockfrostAdapter: BlockfrostAdapter;
privateKey: string;
};

export class DexV2Worker {
private readonly networkEnv: NetworkEnvironment;
private readonly networkId: NetworkId;
private readonly lucid: Lucid;
private readonly blockfrostAdapter: BlockfrostAdapter;
private readonly privateKey: string;

constructor({
networkEnv,
networkId,
lucid,
blockfrostAdapter,
privateKey,
}: DexV2WorkerConstructor) {
this.networkEnv = networkEnv;
this.networkId = networkId;
this.lucid = lucid;
this.blockfrostAdapter = blockfrostAdapter;
this.privateKey = privateKey;
}

async start(): Promise<void> {
await runRecurringJob({
name: "lbe v2 batcher",
interval: 1000 * 30, // 30s
job: () => this.runWorker(),
});
}

async runWorker(): Promise<void> {
const { orders: allOrders } = await this.blockfrostAdapter.getAllV2Orders();
const currentSlot = await this.blockfrostAdapter.currentSlot();
const currentTime = this.lucid.utils.slotToUnixTime(currentSlot);
const expiredOrders: OrderV2.State[] = [];
const mapDatum: Record<string, string> = {};
for (const order of allOrders) {
const orderDatum = order.datum;
const expiredOptions = orderDatum.expiredOptions;
if (expiredOptions === undefined) {
continue;
}
if (expiredOptions.expiredTime >= BigInt(currentTime)) {
continue;
}
if (
expiredOptions.maxCancellationTip < DexV2Constant.DEFAULT_CANCEL_TIPS
) {
continue;
}

const receiverDatum = orderDatum.refundReceiverDatum;
if (receiverDatum.type === OrderV2.ExtraDatumType.INLINE_DATUM) {
let rawDatum: string | undefined = undefined;
try {
rawDatum = await this.blockfrostAdapter.getDatumByDatumHash(
receiverDatum.hash
);
// eslint-disable-next-line unused-imports/no-unused-vars
} catch (_err) {
continue;
}
mapDatum[receiverDatum.hash] = rawDatum;
}
expiredOrders.push(order);
if (expiredOrders.length === 20) {
break;
}
}
if (expiredOrders.length > 0) {
const orderUtxos = await this.lucid.utxosByOutRef(
expiredOrders.map((state) => ({
txHash: state.txIn.txHash,
outputIndex: state.txIn.index,
}))
);
const txComplete = await new DexV2(
this.lucid,
this.blockfrostAdapter
).cancelExpiredOrders({
orderUtxos: orderUtxos,
currentSlot,
extraDatumMap: mapDatum,
});

const signedTx = await txComplete
.signWithPrivateKey(this.privateKey)
.complete();

const txId = await signedTx.submit();
console.info(`Transaction submitted successfully: ${txId}`);
}
}
}
Loading