From 8321dce807d8debca7098e07728977d4d7d63d51 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Tue, 18 Jun 2024 19:02:26 +0700 Subject: [PATCH 1/8] wip --- src/batcher.ts | 417 +++++++++++++++++++++++++++++++++++++++++++++++++ src/types.ts | 2 + 2 files changed, 419 insertions(+) create mode 100644 src/batcher.ts diff --git a/src/batcher.ts b/src/batcher.ts new file mode 100644 index 0000000..459e353 --- /dev/null +++ b/src/batcher.ts @@ -0,0 +1,417 @@ +import { WarehouseBuilder, type WarehouseBuilderOptions } from "./build-tx"; +import { genWarehouseOptions } from "./tests/utils"; +import type { + Address, + BluePrintAsset, + MaestroSupportedNetworks, + ManagerDatum, + OrderDatum, + SellerDatum, + Translucent, + TreasuryDatum, + UTxO, + UnixTime, +} from "./types"; +import * as T from "@minswap/translucent"; +import { computeLPAssetName } from "./utils"; +import { MINIMUM_ORDER_COLLECTED, MINIMUM_SELLER_COLLECTED } from "./constants"; +import invariant from "@minswap/tiny-invariant"; + +function getParams() { + const network: MaestroSupportedNetworks = "Preprod"; + const maestroApiKey = process.argv[3]; + const seedPhase = process.argv[4]; + return { + network, + maestroApiKey, + seedPhase, + }; +} + +function getMapLbeIdUtxO< + T extends { baseAsset: BluePrintAsset; raiseAsset: BluePrintAsset }, +>(utxos: UTxO[], toDatum: (arg0: string) => T): Record { + const mapLbeIdUTxO: Record = {}; + for (const utxo of utxos) { + const { datum } = utxo; + const { baseAsset, raiseAsset } = toDatum(datum!); + const lbeId = computeLPAssetName( + baseAsset.policyId + baseAsset.assetName, + raiseAsset.policyId + raiseAsset.assetName + ); + if (mapLbeIdUTxO[lbeId]) { + mapLbeIdUTxO[lbeId] = [utxo]; + } else { + mapLbeIdUTxO[lbeId].push(utxo); + } + } + return mapLbeIdUTxO; +} + +type BatchLBEOptions = { + t: Translucent; + treasuryUTxO: UTxO; + managerUTxO?: UTxO; + sellerUTxOs: UTxO[]; + batcherUTxO: UTxO; + orderUTxOs: UTxO[]; + batcherAddress: Address; + current: UnixTime; +}; + +async function getLBEUTxO({ + batcherAddress, + t, + txHash, + builder, +}: { + batcherAddress: Address; + t: Translucent; + txHash: string; + builder: WarehouseBuilder; +}) { + let index = 0; + const outputs: { + treasuryUTxO?: UTxO; + managerUTxO?: UTxO; + batcherUTxO?: UTxO; + orderUTxOs: UTxO[]; + } = { + orderUTxOs: [], + }; + while (true) { + const utxo = (await t.utxosByOutRef([{ txHash, outputIndex: index }]))[0]; + if (utxo === undefined) { + return outputs; + } + + // handle + const { address, assets } = utxo; + if (address === batcherAddress) { + outputs.batcherUTxO = utxo; + } + const hash = T.paymentCredentialOf(address).hash; + if (hash === builder.orderHash && assets[builder.orderToken] === 1n) { + outputs.orderUTxOs.push(utxo); + } + if (hash === builder.managerHash && assets[builder.managerToken] === 1n) { + outputs.managerUTxO = utxo; + } + if (hash === builder.treasuryHash && assets[builder.treasuryToken] === 1n) { + outputs.treasuryUTxO = utxo; + } + ++index; + } +} + +async function buildCollectSellerTx({ + managerUTxO, + treasuryUTxO, + sellerUTxOs, + warehouseOptions, + managerDatum, + batcherUTxO, + batcherAddress, + t, +}: { + managerUTxO: T.UTxO; + managerDatum: ManagerDatum; + treasuryUTxO: T.UTxO; + sellerUTxOs: UTxO[]; + batcherUTxO: UTxO; + batcherAddress: Address; + warehouseOptions: WarehouseBuilderOptions; + t: Translucent; +}) { + const builder = new WarehouseBuilder(warehouseOptions); + builder.buildCollectSeller({ + treasuryRefInput: treasuryUTxO, + managerInput: managerUTxO, + sellerInputs: sellerUTxOs, + validFrom: Date.now() / 1000, + validTo: Date.now() / 1000 + 60 * 3, + }); + const tx = await builder.complete().collectFrom([batcherUTxO]).complete(); + const signedTx = await tx.sign().complete(); + const txHash = await signedTx.submit(); + await t.awaitTx(txHash, 1000); + const output = await getLBEUTxO({ t, batcherAddress, txHash, builder }); + return { + managerUTxO: output.managerUTxO!, + managerDatum: builder.fromDatumManager(output.managerUTxO!.datum!), + batcherUTxO: output.batcherUTxO!, + }; +} + +async function buildCollectManagerTx({ + managerUTxO, + treasuryUTxO, + warehouseOptions, + batcherUTxO, + batcherAddress, + t, +}: { + managerUTxO: T.UTxO; + treasuryUTxO: T.UTxO; + batcherUTxO: UTxO; + batcherAddress: Address; + warehouseOptions: WarehouseBuilderOptions; + t: Translucent; +}) { + const builder = new WarehouseBuilder(warehouseOptions); + builder.buildCollectManager({ + treasuryInput: treasuryUTxO, + managerInput: managerUTxO, + validFrom: Date.now() / 1000, + validTo: Date.now() / 1000 + 60 * 3, + }); + const tx = await builder.complete().collectFrom([batcherUTxO]).complete(); + const signedTx = await tx.sign().complete(); + const txHash = await signedTx.submit(); + await t.awaitTx(txHash, 1000); + const output = await getLBEUTxO({ t, batcherAddress, txHash, builder }); + return { + batcherUTxO: output.batcherUTxO!, + treasuryUTxO: output.treasuryUTxO!, + treasuryDatum: builder.fromDatumTreasury(output.treasuryUTxO!.datum!), + }; +} + +async function buildCollectOrdersTx({ + treasuryUTxO, + uncollectedOrderUTxOs, + warehouseOptions, + batcherUTxO, + batcherAddress, + t, +}: { + treasuryUTxO: T.UTxO; + uncollectedOrderUTxOs: UTxO[]; + batcherUTxO: UTxO; + batcherAddress: Address; + warehouseOptions: WarehouseBuilderOptions; + t: Translucent; +}) { + const builder = new WarehouseBuilder(warehouseOptions); + builder.buildCollectOrders({ + treasuryInput: treasuryUTxO, + orderInputs: uncollectedOrderUTxOs, + validFrom: Date.now() / 1000, + validTo: Date.now() / 1000 + 60 * 3, + }); + const tx = await builder.complete().collectFrom([batcherUTxO]).complete(); + const signedTx = await tx.sign().complete(); + const txHash = await signedTx.submit(); + await t.awaitTx(txHash, 1000); + const output = await getLBEUTxO({ t, batcherAddress, txHash, builder }); + return { + batcherUTxO: output.batcherUTxO!, + treasuryUTxO: output.treasuryUTxO!, + treasuryDatum: builder.fromDatumTreasury(output.treasuryUTxO!.datum!), + collectedOrderUTxOs: output.orderUTxOs, + }; +} + +// async function buildCreateAmmPoolTx({ +// treasuryUTxO, +// treasuryDatum, +// warehouseOptions, +// batcherUTxO, +// batcherAddress, +// t, +// }: { +// t: Translucent; +// treasuryUTxO: UTxO; +// treasuryDatum: TreasuryDatum; +// batcherUTxO: UTxO; +// batcherAddress: Address; +// warehouseOptions: WarehouseBuilderOptions; +// }) { +// const { baseAsset, raiseAsset } = treasuryDatum; +// const lpAssetName = computeLPAssetName( +// baseAsset.policyId + baseAsset.assetName, +// raiseAsset.policyId + raiseAsset.assetName +// ); +// const + +// } +async function batchLBE(options: BatchLBEOptions): Promise { + const { t, sellerUTxOs, orderUTxOs, batcherAddress } = options; + let treasuryUTxO = options.treasuryUTxO; + let managerUTxO = options.managerUTxO; + let batcherUTxO = options.batcherUTxO; + const warehouseOptions = await genWarehouseOptions(t); + + const builder = new WarehouseBuilder(warehouseOptions); + + let treasuryDatum = builder.fromDatumTreasury(treasuryUTxO.datum!); + if (treasuryDatum.isManagerCollected === false) { + invariant(managerUTxO); + let managerDatum = builder.fromDatumManager(managerUTxO.datum!); + while (managerDatum.sellerCount > 0) { + const outputs = await buildCollectSellerTx({ + managerUTxO, + treasuryUTxO, + sellerUTxOs: sellerUTxOs.splice( + 0, + Math.min( + Number(MINIMUM_SELLER_COLLECTED), + Number(managerDatum.sellerCount) + ) + ), + warehouseOptions, + managerDatum, + batcherUTxO, + batcherAddress, + t, + }); + managerUTxO = outputs.managerUTxO; + managerDatum = outputs.managerDatum; + batcherUTxO = outputs.batcherUTxO; + } + const outputs = await buildCollectManagerTx({ + managerUTxO, + treasuryUTxO, + warehouseOptions, + batcherUTxO, + batcherAddress, + t, + }); + batcherUTxO = outputs.batcherUTxO; + treasuryUTxO = outputs.treasuryUTxO; + treasuryDatum = outputs.treasuryDatum; + } + + if (treasuryDatum.totalLiquidity === 0n) { + const uncollectedOrderUTxOs: UTxO[] = []; + const collectedOrderUTxOs: UTxO[] = []; + // collect order + for (const utxo of orderUTxOs) { + const orderDatum = builder.fromDatumOrder(utxo.datum!); + if (orderDatum.isCollected) { + collectedOrderUTxOs.push(utxo); + } else { + uncollectedOrderUTxOs.push(utxo); + } + } + while ( + treasuryDatum.collectedFund === + treasuryDatum.reserveRaise + treasuryDatum.totalPenalty + ) { + const outputs = await buildCollectOrdersTx({ + treasuryUTxO, + uncollectedOrderUTxOs: uncollectedOrderUTxOs.splice( + 0, + Math.min( + uncollectedOrderUTxOs.length, + Number(MINIMUM_ORDER_COLLECTED) + ) + ), + warehouseOptions, + batcherUTxO, + batcherAddress, + t, + }); + batcherUTxO = outputs.batcherUTxO; + treasuryUTxO = outputs.treasuryUTxO; + treasuryDatum = outputs.treasuryDatum; + collectedOrderUTxOs.push(...outputs.collectedOrderUTxOs); + } + // TODO: create pool + } + // TODO: redeem LP +} + +async function runBatcher(t: Translucent): Promise { + const warehouseOptions = await genWarehouseOptions(t); + + const builder = new WarehouseBuilder(warehouseOptions); + const treasuryUTxOs = await t.utxosAtWithUnit( + { + type: "Script", + hash: builder.treasuryHash, + }, + builder.treasuryToken + ); + const managerUTxOs = await t.utxosAtWithUnit( + { + type: "Script", + hash: builder.managerHash, + }, + builder.managerToken + ); + const sellerUTxOs = await t.utxosAtWithUnit( + { + type: "Script", + hash: builder.sellerHash, + }, + builder.sellerToken + ); + const orderUTxOs = await t.utxosAtWithUnit( + { + type: "Script", + hash: builder.orderHash, + }, + builder.orderToken + ); + + const mapOrder: Record = getMapLbeIdUtxO( + orderUTxOs, + builder.fromDatumOrder + ); + const mapSeller: Record = getMapLbeIdUtxO( + sellerUTxOs, + builder.fromDatumSeller + ); + const mapManager: Record = getMapLbeIdUtxO( + managerUTxOs, + builder.fromDatumManager + ); + const currentDate = Date.now() / 1000; + const batcherAddress = await t.wallet.address(); + const batcherUTxO = (await t.wallet.getUtxos())[0]; + for (const utxo of treasuryUTxOs) { + const { datum } = utxo; + const treasuryDatum = builder.fromDatumTreasury(datum!); + const { + baseAsset, + raiseAsset, + collectedFund, + totalLiquidity, + endTime, + isCancelled, + } = treasuryDatum; + if (totalLiquidity !== 0n && collectedFund === 0n) { + // finished LBE + continue; + } + const lbeId = computeLPAssetName( + baseAsset.policyId + baseAsset.assetName, + raiseAsset.policyId + raiseAsset.assetName + ); + if (endTime < currentDate || isCancelled === true) { + await batchLBE({ + t, + treasuryUTxO: utxo, + managerUTxO: mapManager[lbeId][0] ?? undefined, + sellerUTxOs: mapSeller[lbeId] ?? [], + orderUTxOs: mapOrder[lbeId] ?? [], + batcherAddress, + batcherUTxO, + current: currentDate, + }); + } + } +} + +async function main(): Promise { + const { network, seedPhase, maestroApiKey } = getParams(); + const maestro = new T.Maestro({ network, apiKey: maestroApiKey }); + const t = await T.Translucent.new(maestro, network); + t.selectWalletFromSeed(seedPhase); + while (true) { + await runBatcher(t); + } +} + +void main(); diff --git a/src/types.ts b/src/types.ts index 7d403e9..c5c9094 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,6 +29,8 @@ export type Tx = T.Tx; export type Unit = T.Unit; export type UnixTime = T.UnixTime; export type UTxO = T.UTxO; +export type MaestroSupportedNetworks = T.MaestroSupportedNetworks; +export type Maestro = T.Maestro; export type AmmPoolDatum = FeedTypeAmmPool["_datum"]; export type FactoryDatum = FactoryValidateFactory["datum"]; From e558a7c17a8e79d7d89ba0b8354eb8e3e6ed20f1 Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Wed, 19 Jun 2024 11:45:33 +0700 Subject: [PATCH 2/8] use ref truthly ref script for batcher --- src/batcher.ts | 49 +++++++++++++++++++++++++--------------------- src/tests/utils.ts | 12 +++++++++--- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/batcher.ts b/src/batcher.ts index 459e353..3b05155 100644 --- a/src/batcher.ts +++ b/src/batcher.ts @@ -1,14 +1,10 @@ import { WarehouseBuilder, type WarehouseBuilderOptions } from "./build-tx"; -import { genWarehouseOptions } from "./tests/utils"; +import { genWarehouseOptions, hexToUtxo } from "./tests/utils"; import type { Address, BluePrintAsset, MaestroSupportedNetworks, - ManagerDatum, - OrderDatum, - SellerDatum, Translucent, - TreasuryDatum, UTxO, UnixTime, } from "./types"; @@ -16,6 +12,7 @@ import * as T from "@minswap/translucent"; import { computeLPAssetName } from "./utils"; import { MINIMUM_ORDER_COLLECTED, MINIMUM_SELLER_COLLECTED } from "./constants"; import invariant from "@minswap/tiny-invariant"; +import lbeV2Script from "./../lbe-v2-script.json"; function getParams() { const network: MaestroSupportedNetworks = "Preprod"; @@ -37,7 +34,7 @@ function getMapLbeIdUtxO< const { baseAsset, raiseAsset } = toDatum(datum!); const lbeId = computeLPAssetName( baseAsset.policyId + baseAsset.assetName, - raiseAsset.policyId + raiseAsset.assetName + raiseAsset.policyId + raiseAsset.assetName, ); if (mapLbeIdUTxO[lbeId]) { mapLbeIdUTxO[lbeId] = [utxo]; @@ -109,13 +106,11 @@ async function buildCollectSellerTx({ treasuryUTxO, sellerUTxOs, warehouseOptions, - managerDatum, batcherUTxO, batcherAddress, t, }: { managerUTxO: T.UTxO; - managerDatum: ManagerDatum; treasuryUTxO: T.UTxO; sellerUTxOs: UTxO[]; batcherUTxO: UTxO; @@ -256,11 +251,10 @@ async function batchLBE(options: BatchLBEOptions): Promise { 0, Math.min( Number(MINIMUM_SELLER_COLLECTED), - Number(managerDatum.sellerCount) - ) + Number(managerDatum.sellerCount), + ), ), warehouseOptions, - managerDatum, batcherUTxO, batcherAddress, t, @@ -304,8 +298,8 @@ async function batchLBE(options: BatchLBEOptions): Promise { 0, Math.min( uncollectedOrderUTxOs.length, - Number(MINIMUM_ORDER_COLLECTED) - ) + Number(MINIMUM_ORDER_COLLECTED), + ), ), warehouseOptions, batcherUTxO, @@ -324,48 +318,59 @@ async function batchLBE(options: BatchLBEOptions): Promise { async function runBatcher(t: Translucent): Promise { const warehouseOptions = await genWarehouseOptions(t); - + warehouseOptions.deployedValidators = { + treasuryValidator: hexToUtxo(lbeV2Script.treasuryRefInput), + managerValidator: hexToUtxo(lbeV2Script.managerRefInput), + sellerValidator: hexToUtxo(lbeV2Script.sellerRefInput), + orderValidator: hexToUtxo(lbeV2Script.orderRefInput), + factoryValidator: hexToUtxo(lbeV2Script.factoryRefInput), + }; + warehouseOptions.ammDeployedValidators = { + authenValidator: hexToUtxo(lbeV2Script.ammAuthenRefInput), + poolValidator: hexToUtxo(lbeV2Script.ammPoolRefInput), + factoryValidator: hexToUtxo(lbeV2Script.factoryRefInput), + }; const builder = new WarehouseBuilder(warehouseOptions); const treasuryUTxOs = await t.utxosAtWithUnit( { type: "Script", hash: builder.treasuryHash, }, - builder.treasuryToken + builder.treasuryToken, ); const managerUTxOs = await t.utxosAtWithUnit( { type: "Script", hash: builder.managerHash, }, - builder.managerToken + builder.managerToken, ); const sellerUTxOs = await t.utxosAtWithUnit( { type: "Script", hash: builder.sellerHash, }, - builder.sellerToken + builder.sellerToken, ); const orderUTxOs = await t.utxosAtWithUnit( { type: "Script", hash: builder.orderHash, }, - builder.orderToken + builder.orderToken, ); const mapOrder: Record = getMapLbeIdUtxO( orderUTxOs, - builder.fromDatumOrder + builder.fromDatumOrder, ); const mapSeller: Record = getMapLbeIdUtxO( sellerUTxOs, - builder.fromDatumSeller + builder.fromDatumSeller, ); const mapManager: Record = getMapLbeIdUtxO( managerUTxOs, - builder.fromDatumManager + builder.fromDatumManager, ); const currentDate = Date.now() / 1000; const batcherAddress = await t.wallet.address(); @@ -387,7 +392,7 @@ async function runBatcher(t: Translucent): Promise { } const lbeId = computeLPAssetName( baseAsset.policyId + baseAsset.assetName, - raiseAsset.policyId + raiseAsset.assetName + raiseAsset.policyId + raiseAsset.assetName, ); if (endTime < currentDate || isCancelled === true) { await batchLBE({ diff --git a/src/tests/utils.ts b/src/tests/utils.ts index 6f3cfc5..d7131ab 100644 --- a/src/tests/utils.ts +++ b/src/tests/utils.ts @@ -91,8 +91,8 @@ export const loadModule = async () => { }; export const DUMMY_SEED_UTXO: UTxO = { - txHash: "5428517bd92102ce1af705f8b66560d445e620aead488b47fb824426484912f8", - outputIndex: 0, + txHash: params.seedOutRef.txHash, + outputIndex: params.seedOutRef.outputIndex, assets: { lovelace: 5_000_000n }, address: "addr_test1vqtx6ahdpzm0nm9qa4wh4avhze8gv3j6jv6v3gmrukdxfrqm6m8d3", }; @@ -103,7 +103,13 @@ export const DUMMY_SEED_AMM_UTXO: UTxO = { assets: { lovelace: 5_000_000n }, address: "addr_test1vqtx6ahdpzm0nm9qa4wh4avhze8gv3j6jv6v3gmrukdxfrqm6m8d3", }; - +export function hexToUtxo(hexUtxo: string): UTxO { + let C = T.CModuleLoader.get; + let cUtxo = C.TransactionUnspentOutput.from_bytes(T.fromHex(hexUtxo)); + let utxo = T.coreToUtxo(cUtxo); + cUtxo.free(); + return utxo; +} const genAmmValidators = async (t: Translucent) => { (t.provider as Emulator).addUTxO(DUMMY_SEED_AMM_UTXO); const ammValidators = collectMinswapValidators({ From 365069e350f2df3c79653aa8849f3ab4fc28ec6d Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Wed, 19 Jun 2024 15:29:27 +0700 Subject: [PATCH 3/8] batcher handle happy flow --- src/batcher.ts | 486 +++++++++++++++++++++++++++++------------------- src/build-tx.ts | 5 + src/types.ts | 2 + 3 files changed, 301 insertions(+), 192 deletions(-) diff --git a/src/batcher.ts b/src/batcher.ts index 3b05155..32071f4 100644 --- a/src/batcher.ts +++ b/src/batcher.ts @@ -1,16 +1,22 @@ import { WarehouseBuilder, type WarehouseBuilderOptions } from "./build-tx"; import { genWarehouseOptions, hexToUtxo } from "./tests/utils"; import type { - Address, + AmmPoolDatum, BluePrintAsset, MaestroSupportedNetworks, + ManagerDatum, Translucent, + TreasuryDatum, UTxO, UnixTime, } from "./types"; import * as T from "@minswap/translucent"; -import { computeLPAssetName } from "./utils"; -import { MINIMUM_ORDER_COLLECTED, MINIMUM_SELLER_COLLECTED } from "./constants"; +import { calculateInitialLiquidity, computeLPAssetName } from "./utils"; +import { + MINIMUM_ORDER_COLLECTED, + MINIMUM_ORDER_REDEEMED, + MINIMUM_SELLER_COLLECTED, +} from "./constants"; import invariant from "@minswap/tiny-invariant"; import lbeV2Script from "./../lbe-v2-script.json"; @@ -25,6 +31,18 @@ function getParams() { }; } +let warehouse: { + treasuryUTxO: UTxO; + treasuryDatum: TreasuryDatum; + managerUTxO?: UTxO; + managerDatum?: ManagerDatum; + sellerUTxOs: UTxO[]; + uncollectedOrderUTxOs: UTxO[]; + collectedOrderUTxOs: UTxO[]; + warehouseOptions: WarehouseBuilderOptions; + t: Translucent; + lbeId: string; +}; function getMapLbeIdUtxO< T extends { baseAsset: BluePrintAsset; raiseAsset: BluePrintAsset }, >(utxos: UTxO[], toDatum: (arg0: string) => T): Record { @@ -45,24 +63,11 @@ function getMapLbeIdUtxO< return mapLbeIdUTxO; } -type BatchLBEOptions = { - t: Translucent; - treasuryUTxO: UTxO; - managerUTxO?: UTxO; - sellerUTxOs: UTxO[]; - batcherUTxO: UTxO; - orderUTxOs: UTxO[]; - batcherAddress: Address; - current: UnixTime; -}; - async function getLBEUTxO({ - batcherAddress, t, txHash, builder, }: { - batcherAddress: Address; t: Translucent; txHash: string; builder: WarehouseBuilder; @@ -71,7 +76,6 @@ async function getLBEUTxO({ const outputs: { treasuryUTxO?: UTxO; managerUTxO?: UTxO; - batcherUTxO?: UTxO; orderUTxOs: UTxO[]; } = { orderUTxOs: [], @@ -84,9 +88,6 @@ async function getLBEUTxO({ // handle const { address, assets } = utxo; - if (address === batcherAddress) { - outputs.batcherUTxO = utxo; - } const hash = T.paymentCredentialOf(address).hash; if (hash === builder.orderHash && assets[builder.orderToken] === 1n) { outputs.orderUTxOs.push(utxo); @@ -101,92 +102,52 @@ async function getLBEUTxO({ } } -async function buildCollectSellerTx({ - managerUTxO, - treasuryUTxO, - sellerUTxOs, - warehouseOptions, - batcherUTxO, - batcherAddress, - t, -}: { - managerUTxO: T.UTxO; - treasuryUTxO: T.UTxO; - sellerUTxOs: UTxO[]; - batcherUTxO: UTxO; - batcherAddress: Address; - warehouseOptions: WarehouseBuilderOptions; - t: Translucent; -}) { +async function buildCollectSellerTx({ sellerUTxOs }: { sellerUTxOs: UTxO[] }) { + const { treasuryUTxO, managerUTxO, warehouseOptions, t } = warehouse; const builder = new WarehouseBuilder(warehouseOptions); builder.buildCollectSeller({ treasuryRefInput: treasuryUTxO, - managerInput: managerUTxO, + managerInput: managerUTxO!, sellerInputs: sellerUTxOs, validFrom: Date.now() / 1000, validTo: Date.now() / 1000 + 60 * 3, }); - const tx = await builder.complete().collectFrom([batcherUTxO]).complete(); + const tx = await builder.complete().complete(); const signedTx = await tx.sign().complete(); const txHash = await signedTx.submit(); await t.awaitTx(txHash, 1000); - const output = await getLBEUTxO({ t, batcherAddress, txHash, builder }); - return { - managerUTxO: output.managerUTxO!, - managerDatum: builder.fromDatumManager(output.managerUTxO!.datum!), - batcherUTxO: output.batcherUTxO!, - }; + const output = await getLBEUTxO({ t, txHash, builder }); + warehouse.managerUTxO = output.managerUTxO!; + warehouse.managerDatum = builder.fromDatumManager(output.managerUTxO!.datum!); } -async function buildCollectManagerTx({ - managerUTxO, - treasuryUTxO, - warehouseOptions, - batcherUTxO, - batcherAddress, - t, -}: { - managerUTxO: T.UTxO; - treasuryUTxO: T.UTxO; - batcherUTxO: UTxO; - batcherAddress: Address; - warehouseOptions: WarehouseBuilderOptions; - t: Translucent; -}) { +async function buildCollectManagerTx() { + const { treasuryUTxO, managerUTxO, warehouseOptions, t } = warehouse; const builder = new WarehouseBuilder(warehouseOptions); builder.buildCollectManager({ treasuryInput: treasuryUTxO, - managerInput: managerUTxO, + managerInput: managerUTxO!, validFrom: Date.now() / 1000, validTo: Date.now() / 1000 + 60 * 3, }); - const tx = await builder.complete().collectFrom([batcherUTxO]).complete(); + const tx = await builder.complete().complete(); const signedTx = await tx.sign().complete(); const txHash = await signedTx.submit(); await t.awaitTx(txHash, 1000); - const output = await getLBEUTxO({ t, batcherAddress, txHash, builder }); - return { - batcherUTxO: output.batcherUTxO!, + const output = await getLBEUTxO({ t, txHash, builder }); + warehouse = { + ...warehouse, treasuryUTxO: output.treasuryUTxO!, treasuryDatum: builder.fromDatumTreasury(output.treasuryUTxO!.datum!), }; } async function buildCollectOrdersTx({ - treasuryUTxO, uncollectedOrderUTxOs, - warehouseOptions, - batcherUTxO, - batcherAddress, - t, }: { - treasuryUTxO: T.UTxO; uncollectedOrderUTxOs: UTxO[]; - batcherUTxO: UTxO; - batcherAddress: Address; - warehouseOptions: WarehouseBuilderOptions; - t: Translucent; }) { + const { warehouseOptions, treasuryUTxO, t } = warehouse; const builder = new WarehouseBuilder(warehouseOptions); builder.buildCollectOrders({ treasuryInput: treasuryUTxO, @@ -194,126 +155,275 @@ async function buildCollectOrdersTx({ validFrom: Date.now() / 1000, validTo: Date.now() / 1000 + 60 * 3, }); - const tx = await builder.complete().collectFrom([batcherUTxO]).complete(); + const tx = await builder.complete().complete(); const signedTx = await tx.sign().complete(); const txHash = await signedTx.submit(); await t.awaitTx(txHash, 1000); - const output = await getLBEUTxO({ t, batcherAddress, txHash, builder }); - return { - batcherUTxO: output.batcherUTxO!, - treasuryUTxO: output.treasuryUTxO!, - treasuryDatum: builder.fromDatumTreasury(output.treasuryUTxO!.datum!), - collectedOrderUTxOs: output.orderUTxOs, - }; + const output = await getLBEUTxO({ t, txHash, builder }); + warehouse.treasuryUTxO = output.treasuryUTxO!; + warehouse.treasuryDatum = builder.fromDatumTreasury( + output.treasuryUTxO!.datum!, + ); + warehouse.collectedOrderUTxOs = output.orderUTxOs; +} + +async function findFactoryUTxO( + t: Translucent, + builder: WarehouseBuilder, + lpAssetName: string, +): Promise { + const utxos = await t.utxosAtWithUnit( + { type: "Script", hash: builder.ammFactoryHash }, + builder.ammFactoryToken, + ); + for (const utxo of utxos) { + const { head, tail } = builder.fromDatumAmmFactory(utxo.datum!); + if (lpAssetName > head && lpAssetName < tail) { + return utxo; + } + } + throw Error(`Can not find factory for lpAssetName: ${lpAssetName}`); } -// async function buildCreateAmmPoolTx({ -// treasuryUTxO, -// treasuryDatum, -// warehouseOptions, -// batcherUTxO, -// batcherAddress, -// t, -// }: { -// t: Translucent; -// treasuryUTxO: UTxO; -// treasuryDatum: TreasuryDatum; -// batcherUTxO: UTxO; -// batcherAddress: Address; -// warehouseOptions: WarehouseBuilderOptions; -// }) { -// const { baseAsset, raiseAsset } = treasuryDatum; -// const lpAssetName = computeLPAssetName( -// baseAsset.policyId + baseAsset.assetName, -// raiseAsset.policyId + raiseAsset.assetName -// ); -// const +function sortPairAsset( + baseAsset: BluePrintAsset, + raiseAsset: BluePrintAsset, +): [BluePrintAsset, BluePrintAsset] { + if (baseAsset.policyId !== raiseAsset.policyId) { + if (baseAsset.policyId !== raiseAsset.policyId) { + return [baseAsset, raiseAsset]; + } + return [raiseAsset, baseAsset]; + } + if (baseAsset.assetName < raiseAsset.assetName) { + return [baseAsset, raiseAsset]; + } + return [raiseAsset, baseAsset]; +} +async function createAmmPool() { + const { treasuryUTxO, treasuryDatum, warehouseOptions, t } = warehouse; + const builder = new WarehouseBuilder(warehouseOptions); + const { + baseAsset, + raiseAsset, + poolAllocation, + maximumRaise, + minimumRaise, + reserveBase, + reserveRaise, + totalPenalty, + poolBaseFee, + } = treasuryDatum; + let finalReserveRaise = reserveRaise + totalPenalty; + if (maximumRaise && reserveRaise + totalPenalty > maximumRaise) { + finalReserveRaise = maximumRaise; + } + if (minimumRaise) { + invariant( + finalReserveRaise > minimumRaise, + "not raise enough to create pool and this LBE will be cancelled next round", + ); + } + const [assetA, assetB] = sortPairAsset(baseAsset, raiseAsset); + const [lbeReserveA, lbeReserveB] = + baseAsset.policyId === assetA.policyId && + baseAsset.assetName === assetA.assetName + ? [reserveBase, finalReserveRaise] + : [reserveBase, finalReserveRaise]; + const poolReserveA = (lbeReserveA * poolAllocation) / 100n; + const poolReserveB = (lbeReserveB * poolAllocation) / 100n; + const lpAssetName = computeLPAssetName( + assetA.policyId + assetA.assetName, + assetB.policyId + assetB.assetName, + ); -// } -async function batchLBE(options: BatchLBEOptions): Promise { - const { t, sellerUTxOs, orderUTxOs, batcherAddress } = options; - let treasuryUTxO = options.treasuryUTxO; - let managerUTxO = options.managerUTxO; - let batcherUTxO = options.batcherUTxO; - const warehouseOptions = await genWarehouseOptions(t); + const totalLpToken = calculateInitialLiquidity(poolReserveA, poolReserveB); + const ammPoolDatum: AmmPoolDatum = { + poolBatchingStakeCredential: { + Inline: [ + { + ScriptCredential: [ + t.utils.validatorToScriptHash( + builder.ammValidators.poolBatchingValidator, + ), + ], + }, + ], + }, + assetA: assetA, + assetB: assetB, + totalLiquidity: totalLpToken, + reserveA: poolReserveA, + reserveB: poolReserveB, + baseFeeANumerator: poolBaseFee, + baseFeeBNumerator: poolBaseFee, + feeSharingNumeratorOpt: null, + allowDynamicFee: false, + }; + const factoryUTxO = await findFactoryUTxO(t, builder, lpAssetName); + builder.buildCreateAmmPool({ + treasuryInput: treasuryUTxO, + ammFactoryInput: factoryUTxO, + ammPoolDatum: ammPoolDatum, + totalLiquidity: totalLpToken, + receiverA: lbeReserveA - poolReserveA, + receiverB: lbeReserveB - poolReserveB, + validFrom: Date.now() / 1000, + validTo: Date.now() / 1000 + 60 * 3, + }); + const tx = await builder.complete().complete(); + const signedTx = await tx.sign().complete(); + const txHash = await signedTx.submit(); + await t.awaitTx(txHash, 1000); + const output = await getLBEUTxO({ t, txHash, builder }); + warehouse.treasuryUTxO = output.treasuryUTxO!; + warehouse.treasuryDatum = builder.fromDatumTreasury( + output.treasuryUTxO!.datum!, + ); +} +async function buildRedeemOrdersTx({ orderUTxOs }: { orderUTxOs: UTxO[] }) { + const { treasuryUTxO, warehouseOptions, t } = warehouse; const builder = new WarehouseBuilder(warehouseOptions); + builder.buildRedeemOrders({ + treasuryInput: treasuryUTxO, + orderInputs: orderUTxOs, + validFrom: Date.now() / 1000, + validTo: Date.now() / 1000 + 60 * 3, + }); + const tx = await builder.complete().complete(); + const signedTx = await tx.sign().complete(); + const txHash = await signedTx.submit(); + await t.awaitTx(txHash, 1000); + const output = await getLBEUTxO({ t, txHash, builder }); + warehouse.treasuryUTxO = output.treasuryUTxO!; + warehouse.treasuryDatum = builder.fromDatumTreasury( + output.treasuryUTxO!.datum!, + ); +} - let treasuryDatum = builder.fromDatumTreasury(treasuryUTxO.datum!); - if (treasuryDatum.isManagerCollected === false) { - invariant(managerUTxO); - let managerDatum = builder.fromDatumManager(managerUTxO.datum!); - while (managerDatum.sellerCount > 0) { - const outputs = await buildCollectSellerTx({ - managerUTxO, - treasuryUTxO, - sellerUTxOs: sellerUTxOs.splice( - 0, - Math.min( - Number(MINIMUM_SELLER_COLLECTED), - Number(managerDatum.sellerCount), - ), - ), - warehouseOptions, - batcherUTxO, - batcherAddress, - t, +async function coutingPhase() { + if (warehouse.treasuryDatum.isManagerCollected === false) { + invariant(warehouse.managerUTxO); + while (warehouse.sellerUTxOs.length > 0) { + const collectAmount = Math.min( + Number(MINIMUM_SELLER_COLLECTED), + warehouse.sellerUTxOs.length, + ); + if ( + collectAmount < MINIMUM_SELLER_COLLECTED && + BigInt(collectAmount) !== warehouse.managerDatum?.sellerCount + ) { + throw Error( + `Can not find remaining sellers to collect in LBE ${warehouse.lbeId}`, + ); + } + await buildCollectSellerTx({ + sellerUTxOs: warehouse.sellerUTxOs.splice(0, collectAmount), }); - managerUTxO = outputs.managerUTxO; - managerDatum = outputs.managerDatum; - batcherUTxO = outputs.batcherUTxO; } - const outputs = await buildCollectManagerTx({ - managerUTxO, - treasuryUTxO, - warehouseOptions, - batcherUTxO, - batcherAddress, - t, + await buildCollectManagerTx(); + } + while (warehouse.uncollectedOrderUTxOs.length > 0) { + // TODO: invariant if batcher miss some uncollected orders + await buildCollectOrdersTx({ + uncollectedOrderUTxOs: warehouse.uncollectedOrderUTxOs.splice( + 0, + Math.min( + warehouse.uncollectedOrderUTxOs.length, + Number(MINIMUM_ORDER_COLLECTED), + ), + ), }); - batcherUTxO = outputs.batcherUTxO; - treasuryUTxO = outputs.treasuryUTxO; - treasuryDatum = outputs.treasuryDatum; } +} - if (treasuryDatum.totalLiquidity === 0n) { - const uncollectedOrderUTxOs: UTxO[] = []; - const collectedOrderUTxOs: UTxO[] = []; - // collect order - for (const utxo of orderUTxOs) { - const orderDatum = builder.fromDatumOrder(utxo.datum!); - if (orderDatum.isCollected) { - collectedOrderUTxOs.push(utxo); - } else { - uncollectedOrderUTxOs.push(utxo); - } - } - while ( - treasuryDatum.collectedFund === - treasuryDatum.reserveRaise + treasuryDatum.totalPenalty - ) { - const outputs = await buildCollectOrdersTx({ - treasuryUTxO, - uncollectedOrderUTxOs: uncollectedOrderUTxOs.splice( - 0, - Math.min( - uncollectedOrderUTxOs.length, - Number(MINIMUM_ORDER_COLLECTED), - ), +async function redeemLpTokenPhase() { + while (warehouse.collectedOrderUTxOs.length !== 0) { + // TODO: invariant if batcher miss some collected orders + await buildRedeemOrdersTx({ + orderUTxOs: warehouse.collectedOrderUTxOs.splice( + 0, + Math.min( + warehouse.collectedOrderUTxOs.length, + Number(MINIMUM_ORDER_REDEEMED), ), - warehouseOptions, - batcherUTxO, - batcherAddress, - t, - }); - batcherUTxO = outputs.batcherUTxO; - treasuryUTxO = outputs.treasuryUTxO; - treasuryDatum = outputs.treasuryDatum; - collectedOrderUTxOs.push(...outputs.collectedOrderUTxOs); + ), + }); + } +} + +async function refundPhase() { + // TODO: +} + +async function batchLBE(): Promise { + await coutingPhase(); + if (warehouse.treasuryDatum.isCancelled) { + // + await refundPhase(); + } else { + if (warehouse.treasuryDatum.totalLiquidity === 0n) { + await createAmmPool(); + } + await redeemLpTokenPhase(); + } +} + +async function cancelLBE() { + // TODO +} + +async function handleLBE(options: { + t: Translucent; + treasuryUTxO: UTxO; + managerUTxO?: UTxO; + sellerUTxOs: UTxO[]; + orderUTxOs: UTxO[]; + current: UnixTime; +}) { + const { t, treasuryUTxO, managerUTxO, sellerUTxOs, orderUTxOs, current } = + options; + const warehouseOptions = await genWarehouseOptions(t); + const builder = new WarehouseBuilder(warehouseOptions); + const { datum } = treasuryUTxO; + const treasuryDatum = builder.fromDatumTreasury(datum!); + const { endTime, isCancelled, baseAsset, raiseAsset } = treasuryDatum; + + const lbeId = computeLPAssetName( + baseAsset.policyId + baseAsset.assetName, + raiseAsset.policyId + raiseAsset.assetName, + ); + warehouse = { + treasuryUTxO, + treasuryDatum: treasuryDatum, + managerUTxO, + managerDatum: managerUTxO + ? builder.fromDatumManager(managerUTxO!.datum!) + : undefined, + sellerUTxOs, + uncollectedOrderUTxOs: [], + collectedOrderUTxOs: [], + t, + warehouseOptions, + lbeId, + }; + for (const utxo of orderUTxOs) { + const orderDatum = builder.fromDatumOrder(utxo.datum!); + if (orderDatum.isCollected) { + warehouse.collectedOrderUTxOs.push(utxo); + } else { + warehouse.uncollectedOrderUTxOs.push(utxo); } - // TODO: create pool } - // TODO: redeem LP + if (current < endTime) { + // check pool is exist + // => cancel LBE + await cancelLBE(); + } + if (endTime < current || isCancelled === true) { + await batchLBE(); + } } async function runBatcher(t: Translucent): Promise { @@ -373,19 +483,11 @@ async function runBatcher(t: Translucent): Promise { builder.fromDatumManager, ); const currentDate = Date.now() / 1000; - const batcherAddress = await t.wallet.address(); - const batcherUTxO = (await t.wallet.getUtxos())[0]; for (const utxo of treasuryUTxOs) { const { datum } = utxo; const treasuryDatum = builder.fromDatumTreasury(datum!); - const { - baseAsset, - raiseAsset, - collectedFund, - totalLiquidity, - endTime, - isCancelled, - } = treasuryDatum; + const { baseAsset, raiseAsset, collectedFund, totalLiquidity } = + treasuryDatum; if (totalLiquidity !== 0n && collectedFund === 0n) { // finished LBE continue; @@ -394,17 +496,17 @@ async function runBatcher(t: Translucent): Promise { baseAsset.policyId + baseAsset.assetName, raiseAsset.policyId + raiseAsset.assetName, ); - if (endTime < currentDate || isCancelled === true) { - await batchLBE({ + try { + await handleLBE({ t, treasuryUTxO: utxo, managerUTxO: mapManager[lbeId][0] ?? undefined, sellerUTxOs: mapSeller[lbeId] ?? [], orderUTxOs: mapOrder[lbeId] ?? [], - batcherAddress, - batcherUTxO, current: currentDate, }); + } catch (err) { + console.error(err); } } } diff --git a/src/build-tx.ts b/src/build-tx.ts index bd88e8b..9deccbf 100644 --- a/src/build-tx.ts +++ b/src/build-tx.ts @@ -43,6 +43,7 @@ import type { } from "./deploy-validators"; import type { Address, + AmmFactoryDatum, AmmPoolDatum, Assets, BluePrintAsset, @@ -1147,6 +1148,10 @@ export class WarehouseBuilder { return T.Data.to(datum, FeedTypeAmmPool._datum); } + fromDatumAmmFactory(rawDatum: string): AmmFactoryDatum { + return T.Data.from(rawDatum, AmmValidateFactory.datum); + } + calFinalReserveRaise(datum: TreasuryDatum) { if ( datum.maximumRaise && diff --git a/src/types.ts b/src/types.ts index 14f862e..2ed7a85 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,7 @@ import type { SellerValidateSellerSpending, TreasuryValidateTreasurySpending, } from "../plutus"; +import type { FactoryValidatorValidateFactory } from "../amm-plutus"; export type Address = T.Address; export type Assets = T.Assets; @@ -34,6 +35,7 @@ export type MaestroSupportedNetworks = T.MaestroSupportedNetworks; export type Credential = T.Credential; export type AmmPoolDatum = FeedTypeAmmPool["_datum"]; +export type AmmFactoryDatum = FactoryValidatorValidateFactory["datum"]; export type FactoryDatum = FactoryValidateFactory["datum"]; export type ManagerDatum = ManagerValidateManagerSpending["managerInDatum"]; export type OrderDatum = OrderValidateOrder["datum"]; From c57216963510d94fbe8f85a8368a4617523b0e2b Mon Sep 17 00:00:00 2001 From: Ha Quang Minh Date: Wed, 19 Jun 2024 15:52:25 +0700 Subject: [PATCH 4/8] refund --- src/batcher.ts | 64 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/src/batcher.ts b/src/batcher.ts index 32071f4..a0511a4 100644 --- a/src/batcher.ts +++ b/src/batcher.ts @@ -102,7 +102,7 @@ async function getLBEUTxO({ } } -async function buildCollectSellerTx({ sellerUTxOs }: { sellerUTxOs: UTxO[] }) { +async function collectSellers({ sellerUTxOs }: { sellerUTxOs: UTxO[] }) { const { treasuryUTxO, managerUTxO, warehouseOptions, t } = warehouse; const builder = new WarehouseBuilder(warehouseOptions); builder.buildCollectSeller({ @@ -121,7 +121,7 @@ async function buildCollectSellerTx({ sellerUTxOs }: { sellerUTxOs: UTxO[] }) { warehouse.managerDatum = builder.fromDatumManager(output.managerUTxO!.datum!); } -async function buildCollectManagerTx() { +async function collectManager() { const { treasuryUTxO, managerUTxO, warehouseOptions, t } = warehouse; const builder = new WarehouseBuilder(warehouseOptions); builder.buildCollectManager({ @@ -142,7 +142,7 @@ async function buildCollectManagerTx() { }; } -async function buildCollectOrdersTx({ +async function collectOrders({ uncollectedOrderUTxOs, }: { uncollectedOrderUTxOs: UTxO[]; @@ -282,7 +282,7 @@ async function createAmmPool() { ); } -async function buildRedeemOrdersTx({ orderUTxOs }: { orderUTxOs: UTxO[] }) { +async function redeemOrders({ orderUTxOs }: { orderUTxOs: UTxO[] }) { const { treasuryUTxO, warehouseOptions, t } = warehouse; const builder = new WarehouseBuilder(warehouseOptions); builder.buildRedeemOrders({ @@ -302,6 +302,26 @@ async function buildRedeemOrdersTx({ orderUTxOs }: { orderUTxOs: UTxO[] }) { ); } +async function refundOrders({ orderUTxOs }: { orderUTxOs: UTxO[] }) { + const { treasuryUTxO, warehouseOptions, t } = warehouse; + const builder = new WarehouseBuilder(warehouseOptions); + builder.buildRefundOrders({ + treasuryInput: treasuryUTxO, + orderInputs: orderUTxOs, + validFrom: Date.now() / 1000, + validTo: Date.now() / 1000 + 60 * 3, + }); + const tx = await builder.complete().complete(); + const signedTx = await tx.sign().complete(); + const txHash = await signedTx.submit(); + await t.awaitTx(txHash, 1000); + const output = await getLBEUTxO({ t, txHash, builder }); + warehouse.treasuryUTxO = output.treasuryUTxO!; + warehouse.treasuryDatum = builder.fromDatumTreasury( + output.treasuryUTxO!.datum!, + ); +} + async function coutingPhase() { if (warehouse.treasuryDatum.isManagerCollected === false) { invariant(warehouse.managerUTxO); @@ -318,15 +338,15 @@ async function coutingPhase() { `Can not find remaining sellers to collect in LBE ${warehouse.lbeId}`, ); } - await buildCollectSellerTx({ + await collectSellers({ sellerUTxOs: warehouse.sellerUTxOs.splice(0, collectAmount), }); } - await buildCollectManagerTx(); + await collectManager(); } while (warehouse.uncollectedOrderUTxOs.length > 0) { // TODO: invariant if batcher miss some uncollected orders - await buildCollectOrdersTx({ + await collectOrders({ uncollectedOrderUTxOs: warehouse.uncollectedOrderUTxOs.splice( 0, Math.min( @@ -341,7 +361,7 @@ async function coutingPhase() { async function redeemLpTokenPhase() { while (warehouse.collectedOrderUTxOs.length !== 0) { // TODO: invariant if batcher miss some collected orders - await buildRedeemOrdersTx({ + await redeemOrders({ orderUTxOs: warehouse.collectedOrderUTxOs.splice( 0, Math.min( @@ -354,13 +374,23 @@ async function redeemLpTokenPhase() { } async function refundPhase() { - // TODO: + while (warehouse.collectedOrderUTxOs.length !== 0) { + // TODO: invariant if batcher miss some collected orders + await refundOrders({ + orderUTxOs: warehouse.collectedOrderUTxOs.splice( + 0, + Math.min( + warehouse.collectedOrderUTxOs.length, + Number(MINIMUM_ORDER_REDEEMED), + ), + ), + }); + } } async function batchLBE(): Promise { await coutingPhase(); if (warehouse.treasuryDatum.isCancelled) { - // await refundPhase(); } else { if (warehouse.treasuryDatum.totalLiquidity === 0n) { @@ -388,7 +418,7 @@ async function handleLBE(options: { const builder = new WarehouseBuilder(warehouseOptions); const { datum } = treasuryUTxO; const treasuryDatum = builder.fromDatumTreasury(datum!); - const { endTime, isCancelled, baseAsset, raiseAsset } = treasuryDatum; + const { baseAsset, raiseAsset } = treasuryDatum; const lbeId = computeLPAssetName( baseAsset.policyId + baseAsset.assetName, @@ -416,12 +446,12 @@ async function handleLBE(options: { warehouse.uncollectedOrderUTxOs.push(utxo); } } - if (current < endTime) { - // check pool is exist - // => cancel LBE - await cancelLBE(); - } - if (endTime < current || isCancelled === true) { + + await cancelLBE(); + if ( + warehouse.treasuryDatum.endTime < current || + warehouse.treasuryDatum.isCancelled === true + ) { await batchLBE(); } } From 6ea538131b51aaa9a43427ef56f340d035cfdfb5 Mon Sep 17 00:00:00 2001 From: tony Date: Tue, 25 Jun 2024 16:02:17 +0700 Subject: [PATCH 5/8] make static functions --- src/batcher.ts | 35 +++--- src/build-tx.ts | 194 +++++++++++++++++++----------- src/cmd/batcher.ts | 47 ++++++++ src/cmd/hihi.ts | 141 ++++++++++++++++++++++ src/tests/close-event.test.ts | 6 +- src/tests/collect-manager.test.ts | 6 +- src/tests/collect-orders.test.ts | 6 +- src/tests/collect-sellers.test.ts | 30 +++-- src/tests/create-amm-pool.test.ts | 6 +- src/tests/create-treasury.test.ts | 18 +-- src/tests/example.test.ts | 4 +- src/tests/init-factory.test.ts | 4 +- src/tests/performance.test.ts | 21 ++-- src/tests/redeem-lp.test.ts | 11 +- src/tests/refund.test.ts | 12 +- src/tests/update-event.test.ts | 4 +- src/tests/using-seller.test.ts | 19 +-- src/tests/warehouse.ts | 10 +- 18 files changed, 417 insertions(+), 157 deletions(-) create mode 100644 src/cmd/batcher.ts create mode 100644 src/cmd/hihi.ts diff --git a/src/batcher.ts b/src/batcher.ts index a0511a4..28c6d60 100644 --- a/src/batcher.ts +++ b/src/batcher.ts @@ -21,7 +21,8 @@ import invariant from "@minswap/tiny-invariant"; import lbeV2Script from "./../lbe-v2-script.json"; function getParams() { - const network: MaestroSupportedNetworks = "Preprod"; + const network: MaestroSupportedNetworks = process + .argv[2] as MaestroSupportedNetworks; const maestroApiKey = process.argv[3]; const seedPhase = process.argv[4]; return { @@ -118,7 +119,9 @@ async function collectSellers({ sellerUTxOs }: { sellerUTxOs: UTxO[] }) { await t.awaitTx(txHash, 1000); const output = await getLBEUTxO({ t, txHash, builder }); warehouse.managerUTxO = output.managerUTxO!; - warehouse.managerDatum = builder.fromDatumManager(output.managerUTxO!.datum!); + warehouse.managerDatum = WarehouseBuilder.fromDatumManager( + output.managerUTxO!.datum!, + ); } async function collectManager() { @@ -138,7 +141,9 @@ async function collectManager() { warehouse = { ...warehouse, treasuryUTxO: output.treasuryUTxO!, - treasuryDatum: builder.fromDatumTreasury(output.treasuryUTxO!.datum!), + treasuryDatum: WarehouseBuilder.fromDatumTreasury( + output.treasuryUTxO!.datum!, + ), }; } @@ -161,7 +166,7 @@ async function collectOrders({ await t.awaitTx(txHash, 1000); const output = await getLBEUTxO({ t, txHash, builder }); warehouse.treasuryUTxO = output.treasuryUTxO!; - warehouse.treasuryDatum = builder.fromDatumTreasury( + warehouse.treasuryDatum = WarehouseBuilder.fromDatumTreasury( output.treasuryUTxO!.datum!, ); warehouse.collectedOrderUTxOs = output.orderUTxOs; @@ -177,7 +182,7 @@ async function findFactoryUTxO( builder.ammFactoryToken, ); for (const utxo of utxos) { - const { head, tail } = builder.fromDatumAmmFactory(utxo.datum!); + const { head, tail } = WarehouseBuilder.fromDatumAmmFactory(utxo.datum!); if (lpAssetName > head && lpAssetName < tail) { return utxo; } @@ -277,7 +282,7 @@ async function createAmmPool() { await t.awaitTx(txHash, 1000); const output = await getLBEUTxO({ t, txHash, builder }); warehouse.treasuryUTxO = output.treasuryUTxO!; - warehouse.treasuryDatum = builder.fromDatumTreasury( + warehouse.treasuryDatum = WarehouseBuilder.fromDatumTreasury( output.treasuryUTxO!.datum!, ); } @@ -297,7 +302,7 @@ async function redeemOrders({ orderUTxOs }: { orderUTxOs: UTxO[] }) { await t.awaitTx(txHash, 1000); const output = await getLBEUTxO({ t, txHash, builder }); warehouse.treasuryUTxO = output.treasuryUTxO!; - warehouse.treasuryDatum = builder.fromDatumTreasury( + warehouse.treasuryDatum = WarehouseBuilder.fromDatumTreasury( output.treasuryUTxO!.datum!, ); } @@ -317,7 +322,7 @@ async function refundOrders({ orderUTxOs }: { orderUTxOs: UTxO[] }) { await t.awaitTx(txHash, 1000); const output = await getLBEUTxO({ t, txHash, builder }); warehouse.treasuryUTxO = output.treasuryUTxO!; - warehouse.treasuryDatum = builder.fromDatumTreasury( + warehouse.treasuryDatum = WarehouseBuilder.fromDatumTreasury( output.treasuryUTxO!.datum!, ); } @@ -417,7 +422,7 @@ async function handleLBE(options: { const warehouseOptions = await genWarehouseOptions(t); const builder = new WarehouseBuilder(warehouseOptions); const { datum } = treasuryUTxO; - const treasuryDatum = builder.fromDatumTreasury(datum!); + const treasuryDatum = WarehouseBuilder.fromDatumTreasury(datum!); const { baseAsset, raiseAsset } = treasuryDatum; const lbeId = computeLPAssetName( @@ -429,7 +434,7 @@ async function handleLBE(options: { treasuryDatum: treasuryDatum, managerUTxO, managerDatum: managerUTxO - ? builder.fromDatumManager(managerUTxO!.datum!) + ? WarehouseBuilder.fromDatumManager(managerUTxO!.datum!) : undefined, sellerUTxOs, uncollectedOrderUTxOs: [], @@ -439,7 +444,7 @@ async function handleLBE(options: { lbeId, }; for (const utxo of orderUTxOs) { - const orderDatum = builder.fromDatumOrder(utxo.datum!); + const orderDatum = WarehouseBuilder.fromDatumOrder(utxo.datum!); if (orderDatum.isCollected) { warehouse.collectedOrderUTxOs.push(utxo); } else { @@ -502,20 +507,20 @@ async function runBatcher(t: Translucent): Promise { const mapOrder: Record = getMapLbeIdUtxO( orderUTxOs, - builder.fromDatumOrder, + WarehouseBuilder.fromDatumOrder, ); const mapSeller: Record = getMapLbeIdUtxO( sellerUTxOs, - builder.fromDatumSeller, + WarehouseBuilder.fromDatumSeller, ); const mapManager: Record = getMapLbeIdUtxO( managerUTxOs, - builder.fromDatumManager, + WarehouseBuilder.fromDatumManager, ); const currentDate = Date.now() / 1000; for (const utxo of treasuryUTxOs) { const { datum } = utxo; - const treasuryDatum = builder.fromDatumTreasury(datum!); + const treasuryDatum = WarehouseBuilder.fromDatumTreasury(datum!); const { baseAsset, raiseAsset, collectedFund, totalLiquidity } = treasuryDatum; if (totalLiquidity !== 0n && collectedFund === 0n) { diff --git a/src/build-tx.ts b/src/build-tx.ts index 184855d..9bc0789 100644 --- a/src/build-tx.ts +++ b/src/build-tx.ts @@ -192,11 +192,10 @@ export type BuildCloseEventOptions = { }; export class WarehouseBuilder { + // immutable t: Translucent; validators: Validators; deployedValidators: DeployedValidators; - tx: Tx; - tasks: Array<() => void> = []; // Validator Hash factoryHash: string; @@ -222,6 +221,22 @@ export class WarehouseBuilder { orderToken: string; sellerToken: string; + // AMM + ammValidators: MinswapValidators; + ammDeployedValidators: DeployMinswapValidators; + ammFactoryAddress: Address; + ammPoolAddress: Address; + ammAuthenHash: string; + ammPoolHash: string; + ammFactoryHash: string; + ammPoolToken: string; + ammFactoryToken: string; + // ----------------------------------- + + // mutable + tx: Tx; + tasks: Array<() => void> = []; + // Reference Input treasuryRefInput: UTxO | undefined; @@ -240,22 +255,12 @@ export class WarehouseBuilder { orderRedeemer: OrderRedeemer | undefined; mintRedeemer: MintRedeemer | undefined; - // AMM - ammValidators: MinswapValidators; - ammDeployedValidators: DeployMinswapValidators; - ammFactoryAddress: Address; - ammPoolAddress: Address; - ammAuthenHash: string; - ammPoolHash: string; - ammFactoryHash: string; - ammPoolToken: string; - ammFactoryToken: string; - // Internal Asset baseAsset: BluePrintAsset | undefined; raiseAsset: BluePrintAsset | undefined; lpAssetName: string | undefined; ammLpToken: string | undefined; + // ----------------------------------- constructor(options: WarehouseBuilderOptions) { const { @@ -340,6 +345,31 @@ export class WarehouseBuilder { return this.tx; } + public clean(): WarehouseBuilder { + this.tx = this.t.newTx(); + this.tasks = []; + this.treasuryRefInput = undefined; + this.treasuryInputs = []; + this.managerInputs = []; + this.factoryInputs = []; + this.orderInputs = []; + this.sellerInputs = []; + + this.factoryRedeemer = undefined; + this.treasuryRedeemer = undefined; + this.managerRedeemer = undefined; + this.sellerRedeemer = undefined; + this.orderRedeemer = undefined; + this.mintRedeemer = undefined; + + this.baseAsset = undefined; + this.raiseAsset = undefined; + this.lpAssetName = undefined; + this.ammLpToken = undefined; + + return this; + } + public buildInitFactory(options: BuildInitFactoryOptions): WarehouseBuilder { const { seedUtxo } = options; this.tasks.push( @@ -449,7 +479,7 @@ export class WarehouseBuilder { owner, } = options; invariant(managerUtxo.datum); - const managerInDatum = this.fromDatumManager(managerUtxo.datum); + const managerInDatum = WarehouseBuilder.fromDatumManager(managerUtxo.datum); const managerOutDatum = { ...managerInDatum, sellerCount: managerInDatum.sellerCount + addSellerCount, @@ -495,7 +525,7 @@ export class WarehouseBuilder { orderOutputDatums, } = options; invariant(sellerUtxo.datum); - const sellerInDatum = this.fromDatumSeller(sellerUtxo.datum); + const sellerInDatum = WarehouseBuilder.fromDatumSeller(sellerUtxo.datum); this.setInnerAssets(sellerInDatum.baseAsset, sellerInDatum.raiseAsset); let inputAmount = 0n; let inputPenaltyAmount = 0n; @@ -503,7 +533,7 @@ export class WarehouseBuilder { let outputPenaltyAmount = 0n; for (const o of orderInputs) { invariant(o.datum); - const datum = this.fromDatumOrder(o.datum); + const datum = WarehouseBuilder.fromDatumOrder(o.datum); inputAmount += datum.amount; inputPenaltyAmount += datum.penaltyAmount; } @@ -579,7 +609,7 @@ export class WarehouseBuilder { penaltyConfig, } = options; invariant(treasuryInput.datum); - const inDatum = this.fromDatumTreasury(treasuryInput.datum); + const inDatum = WarehouseBuilder.fromDatumTreasury(treasuryInput.datum); const treasuryOutDatum: TreasuryDatum = { ...inDatum, startTime: startTime ?? inDatum.startTime, @@ -617,7 +647,9 @@ export class WarehouseBuilder { const { treasuryInput, validFrom, validTo, ammFactoryRefInput, reason } = options; invariant(treasuryInput.datum); - const treasuryInDatum = this.fromDatumTreasury(treasuryInput.datum); + const treasuryInDatum = WarehouseBuilder.fromDatumTreasury( + treasuryInput.datum, + ); const treasuryOutDatum: TreasuryDatum = { ...treasuryInDatum, isCancelled: true, @@ -665,7 +697,9 @@ export class WarehouseBuilder { extraDatum, } = options; invariant(treasuryInput.datum); - const treasuryInDatum = this.fromDatumTreasury(treasuryInput.datum); + const treasuryInDatum = WarehouseBuilder.fromDatumTreasury( + treasuryInput.datum, + ); const totalLbeLPs = totalLiquidity - LP_COLATERAL; const receiverLP = (totalLbeLPs * (treasuryInDatum.poolAllocation - 50n)) / @@ -743,7 +777,9 @@ export class WarehouseBuilder { public buildCloseEvent(options: BuildCloseEventOptions): WarehouseBuilder { const { treasuryInput, factoryInputs, validFrom, validTo } = options; invariant(treasuryInput.datum); - const treasuryDatum = this.fromDatumTreasury(treasuryInput.datum); + const treasuryDatum = WarehouseBuilder.fromDatumTreasury( + treasuryInput.datum, + ); const innerFactoryRedeemer = { baseAsset: treasuryDatum.baseAsset, raiseAsset: treasuryDatum.raiseAsset, @@ -791,7 +827,9 @@ export class WarehouseBuilder { ): WarehouseBuilder { const { treasuryInput, orderInputs, validFrom, validTo } = options; invariant(treasuryInput.datum); - const treasuryInDatum = this.fromDatumTreasury(treasuryInput.datum); + const treasuryInDatum = WarehouseBuilder.fromDatumTreasury( + treasuryInput.datum, + ); this.setInnerAssets(treasuryInDatum.baseAsset, treasuryInDatum.raiseAsset); invariant(this.ammLpToken); const sortedOrders = sortUTxOs(orderInputs); @@ -813,7 +851,7 @@ export class WarehouseBuilder { ); for (const order of sortedOrders) { invariant(order.datum); - const datum = this.fromDatumOrder(order.datum); + const datum = WarehouseBuilder.fromDatumOrder(order.datum); const lpAmount = (datum.amount * treasuryInDatum.totalLiquidity) / treasuryInDatum.reserveRaise; @@ -877,12 +915,15 @@ export class WarehouseBuilder { ); return this; } + public buildRefundOrders( options: BuildRedeemOrdersOptions, ): WarehouseBuilder { const { treasuryInput, orderInputs, validFrom, validTo } = options; invariant(treasuryInput.datum); - const treasuryInDatum = this.fromDatumTreasury(treasuryInput.datum); + const treasuryInDatum = WarehouseBuilder.fromDatumTreasury( + treasuryInput.datum, + ); this.setInnerAssets(treasuryInDatum.baseAsset, treasuryInDatum.raiseAsset); invariant(this.ammLpToken); const sortedOrders = sortUTxOs(orderInputs); @@ -895,7 +936,9 @@ export class WarehouseBuilder { ); for (const order of sortedOrders) { invariant(order.datum); - const { penaltyAmount, amount, owner } = this.fromDatumOrder(order.datum); + const { penaltyAmount, amount, owner } = WarehouseBuilder.fromDatumOrder( + order.datum, + ); const assets: Record = { lovelace: ORDER_MIN_ADA, }; @@ -960,7 +1003,9 @@ export class WarehouseBuilder { ): WarehouseBuilder { const { treasuryInput, orderInputs, validFrom, validTo } = options; invariant(treasuryInput.datum); - const treasuryInDatum = this.fromDatumTreasury(treasuryInput.datum); + const treasuryInDatum = WarehouseBuilder.fromDatumTreasury( + treasuryInput.datum, + ); let treasuryOutDatum: TreasuryDatum = { ...treasuryInDatum, }; @@ -971,7 +1016,7 @@ export class WarehouseBuilder { for (const o of sortedOrders) { invariant(o.datum); const datum: OrderDatum = { - ...this.fromDatumOrder(o.datum), + ...WarehouseBuilder.fromDatumOrder(o.datum), isCollected: true, }; orderOutDatums.push(datum); @@ -1016,9 +1061,13 @@ export class WarehouseBuilder { ): WarehouseBuilder { const { treasuryInput, managerInput, validFrom, validTo } = options; invariant(treasuryInput.datum); - const treasuryInDatum = this.fromDatumTreasury(treasuryInput.datum); + const treasuryInDatum = WarehouseBuilder.fromDatumTreasury( + treasuryInput.datum, + ); invariant(managerInput.datum); - const managerInDatum = this.fromDatumManager(managerInput.datum); + const managerInDatum = WarehouseBuilder.fromDatumManager( + managerInput.datum, + ); const treasuryOutDatum = options.treasuryOutDatum ?? { ...treasuryInDatum, reserveRaise: managerInDatum.reserveRaise, @@ -1058,7 +1107,7 @@ export class WarehouseBuilder { const { treasuryRefInput, managerInput, sellerInputs, validFrom, validTo } = options; invariant(managerInput.datum); - const managerInDatum: ManagerDatum = this.fromDatumManager( + const managerInDatum = WarehouseBuilder.fromDatumManager( managerInput.datum, ); invariant( @@ -1070,7 +1119,7 @@ export class WarehouseBuilder { let totalPenalty = 0n; for (const seller of sellerInputs) { invariant(seller.datum); - const datum = this.fromDatumSeller(seller.datum); + const datum = WarehouseBuilder.fromDatumSeller(seller.datum); totalReserveRaise += datum.amount; totalPenalty += datum.penaltyAmount; } @@ -1104,7 +1153,7 @@ export class WarehouseBuilder { }, () => { for (const utxo of sellerInputs) { - const sellerDatum = this.fromDatumSeller(utxo.datum!); + const sellerDatum = WarehouseBuilder.fromDatumSeller(utxo.datum!); const assets = { lovelace: utxo.assets["lovelace"] - COLLECT_SELLER_COMMISSION, }; @@ -1122,78 +1171,78 @@ export class WarehouseBuilder { } /************************* PARSER *************************/ - fromDatumTreasury(rawDatum: string): TreasuryDatum { + static fromDatumTreasury(rawDatum: string): TreasuryDatum { return T.Data.from( rawDatum, TreasuryValidateTreasurySpending.treasuryInDatum, ); } - toDatumTreasury(datum: TreasuryDatum): string { + static toDatumTreasury(datum: TreasuryDatum): string { return T.Data.to(datum, TreasuryValidateTreasurySpending.treasuryInDatum); } - fromDatumFactory(rawDatum: string): FactoryDatum { + static fromDatumFactory(rawDatum: string): FactoryDatum { return T.Data.from(rawDatum, FactoryValidateFactory.datum); } - toDatumFactory(datum: FactoryDatum) { + static toDatumFactory(datum: FactoryDatum) { return T.Data.to(datum, FactoryValidateFactory.datum); } - fromDatumSeller(rawDatum: string): SellerDatum { + static fromDatumSeller(rawDatum: string): SellerDatum { return T.Data.from(rawDatum, SellerValidateSellerSpending.sellerInDatum); } - toDatumSeller(datum: SellerDatum): string { + static toDatumSeller(datum: SellerDatum): string { return T.Data.to(datum, SellerValidateSellerSpending.sellerInDatum); } - fromDatumOrder(rawDatum: string): OrderDatum { + static fromDatumOrder(rawDatum: string): OrderDatum { return T.Data.from(rawDatum, OrderValidateOrder.datum); } - toDatumOrder(datum: OrderDatum): string { + static toDatumOrder(datum: OrderDatum): string { return T.Data.to(datum, OrderValidateOrder.datum); } - fromDatumManager(rawDatum: string): ManagerDatum { + static fromDatumManager(rawDatum: string): ManagerDatum { return T.Data.from(rawDatum, ManagerValidateManagerSpending.managerInDatum); } - toDatumManager(datum: ManagerDatum): string { + static toDatumManager(datum: ManagerDatum): string { return T.Data.to(datum, ManagerValidateManagerSpending.managerInDatum); } - toRedeemerOrder(redeemer: OrderRedeemer): string { + static toRedeemerOrder(redeemer: OrderRedeemer): string { return T.Data.to(redeemer, OrderValidateOrder.redeemer); } - toRedeemerTreasury(redeemer: TreasuryRedeemer): string { + static toRedeemerTreasury(redeemer: TreasuryRedeemer): string { return T.Data.to(redeemer, TreasuryValidateTreasurySpending.redeemer); } - toRedeemerManager(redeemer: ManagerRedeemer): string { + static toRedeemerManager(redeemer: ManagerRedeemer): string { return T.Data.to(redeemer, ManagerValidateManagerSpending.redeemer); } - toRedeemerSellerSpend(redeemer: SellerRedeemer): string { + static toRedeemerSellerSpend(redeemer: SellerRedeemer): string { return T.Data.to(redeemer, SellerValidateSellerSpending.redeemer); } - toRedeemerFactory(redeemer: FactoryRedeemer): string { + static toRedeemerFactory(redeemer: FactoryRedeemer): string { return T.Data.to(redeemer, FactoryValidateFactory.redeemer); } - toRedeemerMinting(redeemer: MintRedeemer): string { + static toRedeemerMinting(redeemer: MintRedeemer): string { return T.Data.to(redeemer, FactoryValidateFactoryMinting.redeemer); } - toDatumAmmPool(datum: AmmPoolDatum): string { + static toDatumAmmPool(datum: AmmPoolDatum): string { return T.Data.to(datum, FeedTypeAmmPool._datum); } - fromDatumAmmFactory(rawDatum: string): AmmFactoryDatum { + static fromDatumAmmFactory(rawDatum: string): AmmFactoryDatum { return T.Data.from(rawDatum, AmmValidateFactory.datum); } @@ -1248,7 +1297,7 @@ export class WarehouseBuilder { .readFrom([this.deployedValidators["managerValidator"]]) .collectFrom( this.managerInputs, - this.toRedeemerManager(this.managerRedeemer), + WarehouseBuilder.toRedeemerManager(this.managerRedeemer), ); } @@ -1263,7 +1312,7 @@ export class WarehouseBuilder { .readFrom([this.deployedValidators["sellerValidator"]]) .collectFrom( this.sellerInputs, - this.toRedeemerSellerSpend(this.sellerRedeemer), + WarehouseBuilder.toRedeemerSellerSpend(this.sellerRedeemer), ); } @@ -1276,7 +1325,7 @@ export class WarehouseBuilder { .readFrom([this.deployedValidators["factoryValidator"]]) .collectFrom( this.factoryInputs, - this.toRedeemerFactory(this.factoryRedeemer), + WarehouseBuilder.toRedeemerFactory(this.factoryRedeemer), ); } @@ -1287,7 +1336,10 @@ export class WarehouseBuilder { invariant(this.orderRedeemer); this.tx .readFrom([this.deployedValidators["orderValidator"]]) - .collectFrom(this.orderInputs, this.toRedeemerOrder(this.orderRedeemer)); + .collectFrom( + this.orderInputs, + WarehouseBuilder.toRedeemerOrder(this.orderRedeemer), + ); } spendingTreasuryInput() { @@ -1299,7 +1351,7 @@ export class WarehouseBuilder { .readFrom([this.deployedValidators["treasuryValidator"]]) .collectFrom( this.treasuryInputs, - this.toRedeemerTreasury(this.treasuryRedeemer), + WarehouseBuilder.toRedeemerTreasury(this.treasuryRedeemer), ); } @@ -1316,7 +1368,7 @@ export class WarehouseBuilder { this.tx.payToAddressWithData( this.treasuryAddress, { - inline: this.toDatumTreasury(treasuryOutDatum), + inline: WarehouseBuilder.toDatumTreasury(treasuryOutDatum), }, assets, ); @@ -1415,7 +1467,7 @@ export class WarehouseBuilder { this.tx.payToAddressWithData( this.managerAddress, { - inline: this.toDatumManager(datum), + inline: WarehouseBuilder.toDatumManager(datum), }, { [this.managerToken]: 1n, @@ -1445,7 +1497,7 @@ export class WarehouseBuilder { this.tx.payToAddressWithData( this.orderAddress, { - inline: this.toDatumOrder(datum), + inline: WarehouseBuilder.toDatumOrder(datum), }, assets, ); @@ -1460,7 +1512,7 @@ export class WarehouseBuilder { this.tx.payToAddressWithData( this.sellerAddress, { - inline: this.toDatumSeller(datum), + inline: WarehouseBuilder.toDatumSeller(datum), }, { [this.sellerToken]: 1n, @@ -1475,7 +1527,7 @@ export class WarehouseBuilder { this.tx.payToAddressWithData( this.sellerAddress, { - inline: this.toDatumSeller(datum), + inline: WarehouseBuilder.toDatumSeller(datum), }, assets, ); @@ -1522,7 +1574,7 @@ export class WarehouseBuilder { this.tx.payToAddressWithData( this.factoryAddress, { - inline: this.toDatumFactory(datum), + inline: WarehouseBuilder.toDatumFactory(datum), }, { [this.factoryToken]: 1n, @@ -1545,7 +1597,9 @@ export class WarehouseBuilder { invariant(this.factoryInputs.length == 1); invariant(this.factoryInputs[0].datum); invariant(this.lpAssetName); - const factoryDatum = this.fromDatumFactory(this.factoryInputs[0].datum); + const factoryDatum = WarehouseBuilder.fromDatumFactory( + this.factoryInputs[0].datum, + ); const newFactoryHeadDatum: FactoryDatum = { head: factoryDatum.head, tail: this.lpAssetName, @@ -1563,8 +1617,8 @@ export class WarehouseBuilder { const [headInput, tailInput] = this.factoryInputs; invariant(headInput.datum); invariant(tailInput.datum); - const headDatum = this.fromDatumFactory(headInput.datum); - const tailDatum = this.fromDatumFactory(tailInput.datum); + const headDatum = WarehouseBuilder.fromDatumFactory(headInput.datum); + const tailDatum = WarehouseBuilder.fromDatumFactory(tailInput.datum); const newDatum = { head: headDatum.head, tail: tailDatum.tail, @@ -1601,7 +1655,7 @@ export class WarehouseBuilder { .withdraw( this.factoryRewardAddress, 0n, - this.toRedeemerMinting("ManageOrder"), + WarehouseBuilder.toRedeemerMinting("ManageOrder"), ); } @@ -1617,7 +1671,7 @@ export class WarehouseBuilder { { [this.treasuryToken]: amount, }, - this.toRedeemerMinting(this.mintRedeemer), + WarehouseBuilder.toRedeemerMinting(this.mintRedeemer), ); } @@ -1628,7 +1682,7 @@ export class WarehouseBuilder { { [this.managerToken]: amount, }, - this.toRedeemerMinting(this.mintRedeemer), + WarehouseBuilder.toRedeemerMinting(this.mintRedeemer), ); } @@ -1641,7 +1695,7 @@ export class WarehouseBuilder { { [this.sellerToken]: mintAmount, }, - this.toRedeemerMinting(this.mintRedeemer), + WarehouseBuilder.toRedeemerMinting(this.mintRedeemer), ); } @@ -1654,7 +1708,7 @@ export class WarehouseBuilder { { [this.orderToken]: mintAmount, }, - this.toRedeemerMinting(this.mintRedeemer), + WarehouseBuilder.toRedeemerMinting(this.mintRedeemer), ); } @@ -1683,7 +1737,7 @@ export class WarehouseBuilder { { [this.factoryToken]: amount, }, - this.toRedeemerMinting(redeemer), + WarehouseBuilder.toRedeemerMinting(redeemer), ); } @@ -1770,7 +1824,7 @@ export class WarehouseBuilder { .payToAddressWithData( this.ammPoolAddress, { - inline: this.toDatumAmmPool(poolDatum), + inline: WarehouseBuilder.toDatumAmmPool(poolDatum), }, poolAssets, ); diff --git a/src/cmd/batcher.ts b/src/cmd/batcher.ts new file mode 100644 index 0000000..510bdad --- /dev/null +++ b/src/cmd/batcher.ts @@ -0,0 +1,47 @@ +import { WarehouseBuilder } from "../build-tx"; +import type { BluePrintAsset, UTxO } from "../types"; +import { computeLPAssetName } from "../utils"; + +type LbeId = string; +type LbePairAsset = { baseAsset: BluePrintAsset; raiseAsset: BluePrintAsset }; + +namespace LbeId { + export function fromUtxo(options: { tag: "string"; utxo: UTxO }): LbeId { + let { tag, utxo } = options; + let innerFunc = ({ baseAsset, raiseAsset }: LbePairAsset) => + computeLPAssetName( + baseAsset.policyId + baseAsset.assetName, + raiseAsset.policyId + raiseAsset.assetName, + ); + let cases: Record LbePairAsset> = { + treasury: (u: UTxO) => WarehouseBuilder.fromDatumTreasury(u.datum!), + manager: (u: UTxO) => WarehouseBuilder.fromDatumManager(u.datum!), + seller: (u: UTxO) => WarehouseBuilder.fromDatumSeller(u.datum!), + order: (u: UTxO) => WarehouseBuilder.fromDatumOrder(u.datum!), + }; + return innerFunc(cases[tag](utxo)); + } +} + +type LbeUTxO = { + treasury: UTxO; + manager?: UTxO; + sellers: UTxO[]; + orders: UTxO[]; +}; + +class WarehouseBatcher { + builder: WarehouseBuilder; + mapLbeUTxO: Record = {}; + + constructor(builder: WarehouseBuilder) { + this.builder = builder; + } +} + +let main = async () => { + // get Map + // buildCollect things +}; + +main(); diff --git a/src/cmd/hihi.ts b/src/cmd/hihi.ts new file mode 100644 index 0000000..79ad8eb --- /dev/null +++ b/src/cmd/hihi.ts @@ -0,0 +1,141 @@ +import * as T from "@minswap/translucent"; + +import type { + CTransactionHash, + CTransactionOutputs, + CTransactionUnspentOutputs, + MaestroSupportedNetworks, + TxSigned, + UTxO, +} from "../types"; + +type InputId = string; +type InputIdentify = ( + txHash: CTransactionHash, + finalOutputs: CTransactionOutputs, +) => CTransactionUnspentOutputs; + +let submitTx = async (tx: string): Promise => { + console.log({ tx }); + const response = await fetch("https://dev-3.minswap.org/graphql", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: `{"query":"mutation SubmitTx($tx: String!) {submitTx(tx: $tx)}","variables":{"tx":"${tx}"}}`, + }); + const result = await response.text(); + return result; +}; + +type Chaining = { + mapInputs: Record; + inputIdentifyFuncs: Record; + buildTx: (mapInputs: Record) => Promise; + stopCondition: () => boolean; +}; + +let doChaining = async (options: Chaining) => { + let { mapInputs, inputIdentifyFuncs, buildTx, stopCondition } = options; + let C = T.CModuleLoader.get; + while (true) { + console.log("mapInputs", mapInputs); + if (stopCondition()) { + return; + } + let tx = await buildTx(mapInputs); + let debugTxHash = await submitTx(tx.toString()); + let transactionHash = tx.toHash(); + let txHash = C.TransactionHash.from_hex(transactionHash); + console.log({ + "debugTxHash ": debugTxHash, + "transactionHash ": transactionHash, + "txHash ": txHash.to_hex(), + }); + let finalOutputs = tx.txSigned.body().outputs(); + for (let [k, v] of Object.entries(inputIdentifyFuncs)) { + let coreUtxos = v(txHash, finalOutputs); + let utxos: UTxO[] = []; + for (let i = 0; i < coreUtxos.len(); i++) { + let coreUtxo = coreUtxos.get(i); + utxos.push(T.coreToUtxo(coreUtxo)); + } + mapInputs[k] = utxos; + } + } +}; + +let main = async () => { + await T.loadModule(); + await T.CModuleLoader.load(); + let network: MaestroSupportedNetworks = "Preprod"; + let maestroApiKey = "E0n5jUy4j40nhKCuB7LrYabTNieG0egu" ?? process.argv[2]; + let seedPhase = + "muffin spell cement resemble frame pupil grow gloom hawk wild item hungry polar ice maximum sport economy drop sun timber stone circle army jazz" ?? + process.argv[3]; + + const maestro = new T.Maestro({ network, apiKey: maestroApiKey }); + const t = await T.Translucent.new(maestro, network); + t.selectWalletFromSeed(seedPhase); + + let address = await t.wallet.address(); + let C = T.CModuleLoader.get; + + let inputIdentifyFuncs = { + root: (txHash: CTransactionHash, finalOutputs: CTransactionOutputs) => { + console.log("finalOutputs", finalOutputs.to_js_value()); + let result = C.TransactionUnspentOutputs.new(); + for (let i = 0; i <= finalOutputs.len(); i++) { + let output = finalOutputs.get(i); + console.log("output", output.to_js_value()); + let curAddress = output.address(); + if ( + curAddress.to_bech32() === address && + BigInt(output.amount().coin().to_str()) >= 500_000_000n + ) { + let input = C.TransactionInput.new( + txHash, + C.BigNum.from_str(i.toString()), + ); + result.add(C.TransactionUnspentOutput.new(input, output)); + break; + } + } + return result; + }, + }; + + let count = 0; + let stopCondition = () => { + console.log({ count }); + return count++ >= 4; + }; + + let mapInputs = { + root: (await t.utxosAt(address)).filter( + (u) => u.assets["lovelace"] >= 500_000_000n, + ), + }; + let buildTx = async ( + mapInputs: Record, + ): Promise => { + let txBuilder = t.newTx(); + txBuilder + .collectFrom(mapInputs["root"]) + .payToAddress(address, { lovelace: 3_000_000n }); + let tx = await txBuilder.complete(); + let signedTx = tx.sign(); + let txSigned = await signedTx.complete(); + console.log("txSigned", txSigned.txSigned.to_js_value()); + return txSigned; + }; + + let options: Chaining = { + mapInputs, + inputIdentifyFuncs, + buildTx, + stopCondition, + }; + await doChaining(options); +}; +main(); diff --git a/src/tests/close-event.test.ts b/src/tests/close-event.test.ts index 8a385ec..fe8fc42 100644 --- a/src/tests/close-event.test.ts +++ b/src/tests/close-event.test.ts @@ -25,7 +25,7 @@ beforeEach(async () => { }; let treasuryInput: UTxO = { address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), assets: { [builder.treasuryToken]: 1n, lovelace: TREASURY_MIN_ADA, @@ -56,7 +56,7 @@ beforeEach(async () => { [builder.factoryToken]: 1n, }, address: builder.factoryAddress, - datum: builder.toDatumFactory(factoryHeadDatum), + datum: WarehouseBuilder.toDatumFactory(factoryHeadDatum), }, { txHash: "00".repeat(32), @@ -67,7 +67,7 @@ beforeEach(async () => { [builder.factoryToken]: 1n, }, address: builder.factoryAddress, - datum: builder.toDatumFactory(factoryTailDatum), + datum: WarehouseBuilder.toDatumFactory(factoryTailDatum), }, ]; let options: BuildCloseEventOptions = { diff --git a/src/tests/collect-manager.test.ts b/src/tests/collect-manager.test.ts index cab2199..51a97ed 100644 --- a/src/tests/collect-manager.test.ts +++ b/src/tests/collect-manager.test.ts @@ -27,7 +27,7 @@ beforeEach(async () => { lovelace: 2_000_000n, }, address: builder.managerAddress, - datum: builder.toDatumManager(managerDatum), + datum: WarehouseBuilder.toDatumManager(managerDatum), }; const treasuryDatum: TreasuryDatum = { ...W.defaultTreasuryDatum, @@ -41,7 +41,7 @@ beforeEach(async () => { lovelace: TREASURY_MIN_ADA, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; const options: BuildCollectManagerOptions = { treasuryInput: treasuryInput, @@ -111,7 +111,7 @@ test("collect-manager | FAIL | LBE ID missmatch", async () => { }; const treasuryInput: UTxO = { ...W.treasuryInput, - datum: builder.toDatumTreasury(treasuryInDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryInDatum), }; const options = { ...W.options, diff --git a/src/tests/collect-orders.test.ts b/src/tests/collect-orders.test.ts index c9ae4aa..e2e85fe 100644 --- a/src/tests/collect-orders.test.ts +++ b/src/tests/collect-orders.test.ts @@ -40,7 +40,7 @@ async function genTestWarehouse() { lovelace: TREASURY_MIN_ADA, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; let orderDatum: OrderDatum = { ...warehouse.defaultOrderDatum, @@ -55,7 +55,7 @@ async function genTestWarehouse() { lovelace: ORDER_MIN_ADA + 2n * ORDER_COMMISSION + orderAmount, }, address: builder.orderAddress, - datum: builder.toDatumOrder(orderDatum), + datum: WarehouseBuilder.toDatumOrder(orderDatum), }; const options: BuildCollectOrdersOptions = { treasuryInput: treasuryInput, @@ -82,7 +82,7 @@ let remixTreasuryInput = (remixDatum: any): UTxO => { }; return { ...W.treasuryInput, - datum: W.builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; }; diff --git a/src/tests/collect-sellers.test.ts b/src/tests/collect-sellers.test.ts index c7dc166..6f9d89e 100644 --- a/src/tests/collect-sellers.test.ts +++ b/src/tests/collect-sellers.test.ts @@ -93,7 +93,7 @@ async function genTestWarehouse() { treasuryDatum.reserveBase, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; const managerDatum: ManagerDatum = { ...defaultManagerDatum, @@ -107,7 +107,7 @@ async function genTestWarehouse() { lovelace: MANAGER_MIN_ADA, }, address: builder.managerAddress, - datum: builder.toDatumManager(managerDatum), + datum: WarehouseBuilder.toDatumManager(managerDatum), }; const sellerDatums: SellerDatum[] = [ { @@ -175,7 +175,7 @@ function genSellerUTxO(datum: SellerDatum, builder: WarehouseBuilder): UTxO { lovelace: SELLER_MIN_ADA + BigInt(rand) * SELLER_COMMISSION, }, address: builder.sellerAddress, - datum: builder.toDatumSeller(datum), + datum: WarehouseBuilder.toDatumSeller(datum), }; } @@ -213,7 +213,10 @@ test("collect sellers | PASS | happy case 2", async () => { ); warehouse.options.managerInput = { ...warehouse.options.managerInput, - datum: builder.toDatumManager({ ...managerDatum, sellerCount: 4n }), + datum: WarehouseBuilder.toDatumManager({ + ...managerDatum, + sellerCount: 4n, + }), }; const builder1 = stupidManagerDatumOut({ reserveRaise: warehouse.expectedManagerDatumOut.reserveRaise + 1000n, @@ -228,12 +231,12 @@ test("collect sellers | PASS | happy case 3", async () => { // collect MINIMUM_SELLER_COLLECTED sellers let { builder, W, defaultSellerDatum, options, treasuryDatum } = warehouse; let managerDatum: ManagerDatum = { - ...builder.fromDatumManager(options.managerInput.datum!), + ...WarehouseBuilder.fromDatumManager(options.managerInput.datum!), sellerCount: 100n, }; let managerInput = { ...options.managerInput, - datum: builder.toDatumManager(managerDatum), + datum: WarehouseBuilder.toDatumManager(managerDatum), }; let sellerInputs: UTxO[] = []; for (let i = 0; i <= MINIMUM_SELLER_COLLECTED; i++) { @@ -294,7 +297,7 @@ test("collect sellers | FAIL | No treasury ref input", async () => { .readFrom([builder.deployedValidators["sellerValidator"]]) .collectFrom( builder.sellerInputs, - builder.toRedeemerSellerSpend(builder.sellerRedeemer), + WarehouseBuilder.toRedeemerSellerSpend(builder.sellerRedeemer), ); }; assertValidatorFail(builder); @@ -312,7 +315,7 @@ test("collect sellers | FAIL | No treasury ref input", async () => { .readFrom([builder.deployedValidators["sellerValidator"]]) .collectFrom( builder.sellerInputs, - builder.toRedeemerSellerSpend(builder.sellerRedeemer), + WarehouseBuilder.toRedeemerSellerSpend(builder.sellerRedeemer), ); }; assertValidatorFail(builder); @@ -420,7 +423,7 @@ test("collect sellers | FAIL | Seller Input: Invalid LBE ID", async () => { const { sellerInputs } = options; sellerInputs[0] = { ...sellerInputs[0], - datum: builder.toDatumSeller({ + datum: WarehouseBuilder.toDatumSeller({ ...sellerDatums[0], baseAsset: MINt, }), @@ -440,7 +443,10 @@ test("collect sellers | FAIL | Manager Input: Invalid LBE ID", async () => { ...options, managerInput: { ...managerInput, - datum: builder.toDatumManager({ ...managerDatum, baseAsset: MINt }), + datum: WarehouseBuilder.toDatumManager({ + ...managerDatum, + baseAsset: MINt, + }), }, }); assertValidatorFail(builder); @@ -468,12 +474,12 @@ test("collect sellers | FAIL | More than 1 manager input", async () => { test("collect sellers | FAIL | spam collect sellers", async () => { let { builder, options } = warehouse; let managerDatum: ManagerDatum = { - ...builder.fromDatumManager(options.managerInput.datum!), + ...WarehouseBuilder.fromDatumManager(options.managerInput.datum!), sellerCount: 100n, }; let managerInput = { ...options.managerInput, - datum: builder.toDatumManager(managerDatum), + datum: WarehouseBuilder.toDatumManager(managerDatum), }; options = { ...options, diff --git a/src/tests/create-amm-pool.test.ts b/src/tests/create-amm-pool.test.ts index 76afdb4..d7f4fea 100644 --- a/src/tests/create-amm-pool.test.ts +++ b/src/tests/create-amm-pool.test.ts @@ -73,7 +73,7 @@ async function genTestWarehouse() { treasuryDatum.reserveBase, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; const ammFactoryDatum: FactoryValidatorValidateFactory["datum"] = { @@ -157,7 +157,7 @@ async function buildTxWithStupidTreasuryDatum( treasuryDatum.reserveBase, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; builder.buildCreateAmmPool({ ...options, treasuryInput: treasuryUTxO }); await assertValidatorFail(builder); @@ -221,7 +221,7 @@ test("Create AMM Pool | FAIL | Invalid Treasury out value", async () => { builder.tx.payToAddressWithData( builder.treasuryAddress, { - inline: builder.toDatumTreasury(treasuryOutDatum), + inline: WarehouseBuilder.toDatumTreasury(treasuryOutDatum), }, createPoolAssets(), ); diff --git a/src/tests/create-treasury.test.ts b/src/tests/create-treasury.test.ts index ffb44b6..bcb3a10 100644 --- a/src/tests/create-treasury.test.ts +++ b/src/tests/create-treasury.test.ts @@ -34,7 +34,7 @@ beforeEach(async () => { lovelace: 2_000_000n, [builder.factoryToken]: 1n, }, - datum: builder.toDatumFactory(defaultFactoryDatum), + datum: WarehouseBuilder.toDatumFactory(defaultFactoryDatum), address: builder.factoryAddress, }; const owner = await t.wallet.address(); @@ -87,7 +87,7 @@ test("create-treasury | PASS | Penalty Config", async () => { test("create-treasury | PASS | Receiver Datum: DatumHash", async () => { let { defaultTreasuryDatum, options } = W; let builder: WarehouseBuilder = W.builder; - let extraDatum = builder.toDatumFactory({ head: "00", tail: "ff" }); + let extraDatum = WarehouseBuilder.toDatumFactory({ head: "00", tail: "ff" }); let extraDatumHash = builder.t.utils.datumToHash(extraDatum); let treasuryDatum: TreasuryDatum = { ...defaultTreasuryDatum, @@ -105,7 +105,7 @@ test("create-treasury | PASS | Receiver Datum: DatumHash", async () => { test("create-treasury | PASS | Receiver Datum: InlineDatum", async () => { let { defaultTreasuryDatum, options } = W; let builder: WarehouseBuilder = W.builder; - let extraDatum = builder.toDatumFactory({ head: "00", tail: "ff" }); + let extraDatum = WarehouseBuilder.toDatumFactory({ head: "00", tail: "ff" }); let extraDatumHash = builder.t.utils.datumToHash(extraDatum); let treasuryDatum: TreasuryDatum = { ...defaultTreasuryDatum, @@ -147,7 +147,7 @@ test("create-treasury | FAIL | have 2 Factory Inputs", async () => { builder.tasks.push(() => { builder.tx.collectFrom( [dummyFactoryInput], - builder.toRedeemerFactory(builder.factoryRedeemer), + WarehouseBuilder.toRedeemerFactory(builder.factoryRedeemer), ); }); assertValidatorFail(txBuilder); @@ -168,7 +168,7 @@ test("create-treasury | FAIL | have 3 Factory Outs", async () => { builder.tx.payToAddressWithData( builder.factoryAddress, { - inline: builder.toDatumFactory({ head: "00", tail: "ff" }), + inline: WarehouseBuilder.toDatumFactory({ head: "00", tail: "ff" }), }, { lovelace: 1_000_000n, @@ -183,7 +183,7 @@ test("create-treasury | FAIL | Factory Out Tail Datum incorrect!", async () => { let builder: WarehouseBuilder = W.builder; builder = builder.buildCreateTreasury(W.options); builder.tasks[1] = () => { - const factoryDatum = builder.fromDatumFactory( + const factoryDatum = WarehouseBuilder.fromDatumFactory( builder.factoryInputs[0].datum!, ); const newFactoryHeadDatum = { @@ -206,7 +206,7 @@ test("create-treasury | FAIL | Factory Out Head Datum incorrect!", async () => { let builder: WarehouseBuilder = W.builder; builder = builder.buildCreateTreasury(W.options); builder.tasks[1] = () => { - const factoryDatum = builder.fromDatumFactory( + const factoryDatum = WarehouseBuilder.fromDatumFactory( builder.factoryInputs[0].datum!, ); const newFactoryHeadDatum = { @@ -232,7 +232,7 @@ const remixManagerDatum = (remixDatum: any) => { builder.tx.payToAddressWithData( builder.managerAddress, { - inline: builder.toDatumManager({ + inline: WarehouseBuilder.toDatumManager({ ...W.defaultManagerDatum, ...remixDatum, }), @@ -266,7 +266,7 @@ const remixTreasuryValue = (assets: Assets) => { builder.tx.payToAddressWithData( builder.treasuryAddress, { - inline: builder.toDatumTreasury(W.defaultTreasuryDatum), + inline: WarehouseBuilder.toDatumTreasury(W.defaultTreasuryDatum), }, assets, ); diff --git a/src/tests/example.test.ts b/src/tests/example.test.ts index f2c46bf..02c4c3d 100644 --- a/src/tests/example.test.ts +++ b/src/tests/example.test.ts @@ -189,7 +189,7 @@ test("example flow", async () => { // create treasury const discoveryStartSlot = emulator.slot + 60 * 60; const discoveryEndSlot = discoveryStartSlot + 60 * 60 * 24 * 2; // 2 days - extraDatum = builder.toDatumFactory({ head: "00", tail: "ff" }); // dummy + extraDatum = WarehouseBuilder.toDatumFactory({ head: "00", tail: "ff" }); // dummy let extraDatumHash = t.utils.datumToHash(extraDatum); const treasuryDatum: TreasuryDatum = { factoryPolicyId: t.utils.validatorToScriptHash(validators.factoryValidator), @@ -435,7 +435,7 @@ test("example flow", async () => { ) .filter((u) => !u.scriptRef) .filter((u) => { - const datum = builder.fromDatumOrder(u.datum!); + const datum = WarehouseBuilder.fromDatumOrder(u.datum!); return datum.isCollected == false; }) as UTxO[]; maxCount = maxCount ?? orderUtxos.length; diff --git a/src/tests/init-factory.test.ts b/src/tests/init-factory.test.ts index 7fbba35..e55189f 100644 --- a/src/tests/init-factory.test.ts +++ b/src/tests/init-factory.test.ts @@ -94,7 +94,7 @@ test("init-factory | FAIL | missing Factory Token", async () => { builder.tx.payToAddressWithData( builder.factoryAddress, { - inline: builder.toDatumFactory(factoryDatum), + inline: WarehouseBuilder.toDatumFactory(factoryDatum), }, { lovelace: 2_000_000n, @@ -117,7 +117,7 @@ test("init-factory | FAIL | Factory Datum is not correct", async () => { builder.tx.payToAddressWithData( builder.factoryAddress, { - inline: builder.toDatumFactory(factoryDatum), + inline: WarehouseBuilder.toDatumFactory(factoryDatum), }, { [builder.factoryToken]: 1n, diff --git a/src/tests/performance.test.ts b/src/tests/performance.test.ts index 74aa474..2a19afb 100644 --- a/src/tests/performance.test.ts +++ b/src/tests/performance.test.ts @@ -1,8 +1,9 @@ import * as T from "@minswap/translucent"; -import type { - BuildCollectOrdersOptions, - BuildCollectSellersOptions, - BuildRedeemOrdersOptions, +import { + WarehouseBuilder, + type BuildCollectOrdersOptions, + type BuildCollectSellersOptions, + type BuildRedeemOrdersOptions, } from "../build-tx"; import type { Assets, @@ -44,7 +45,7 @@ test("performance | collect-seller", async () => { }; let managerInput = { ...warehouse.defaultManagerInput, - datum: builder.toDatumManager(managerDatum), + datum: WarehouseBuilder.toDatumManager(managerDatum), }; let sellerInputs: UTxO[] = []; for (let i = 0; i < MAX_COLLECT_SELLER_COUNT; i++) { @@ -98,7 +99,7 @@ test("performance | collect-order", async () => { }; let treasuryInput: UTxO = { ...warehouse.defaultTreasuryInput, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; let options: BuildCollectOrdersOptions = { validFrom, @@ -137,7 +138,7 @@ test("performance | refund-order", async () => { let utxo: UTxO = { ...warehouse.defaultOrderInput, outputIndex: warehouse.outputIndex++, - datum: builder.toDatumOrder(orderDatum), + datum: WarehouseBuilder.toDatumOrder(orderDatum), }; orderInputs.push(utxo); collectedFund += orderDatum.amount + orderDatum.penaltyAmount; @@ -157,7 +158,7 @@ test("performance | refund-order", async () => { let treasuryAssets = warehouse.defaultTreasuryInput.assets; let treasuryInput: UTxO = { ...warehouse.defaultTreasuryInput, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), assets: { ...treasuryAssets, [raiseAsset]: (treasuryAssets[raiseAsset] ?? 0n) + collectedFund, @@ -196,7 +197,7 @@ test("performance | redeem-order", async () => { let utxo: UTxO = { ...warehouse.defaultOrderInput, outputIndex: warehouse.outputIndex++, - datum: builder.toDatumOrder(orderDatum), + datum: WarehouseBuilder.toDatumOrder(orderDatum), }; orderInputs.push(utxo); collectedFund += orderDatum.amount + orderDatum.penaltyAmount; @@ -226,7 +227,7 @@ test("performance | redeem-order", async () => { }; let treasuryInput: UTxO = { ...warehouse.defaultTreasuryInput, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), assets: treasuryAssets, }; let options: BuildRedeemOrdersOptions = { diff --git a/src/tests/redeem-lp.test.ts b/src/tests/redeem-lp.test.ts index d785671..306a59d 100644 --- a/src/tests/redeem-lp.test.ts +++ b/src/tests/redeem-lp.test.ts @@ -71,7 +71,7 @@ async function genTestWarehouse() { [builder.ammLpToken!]: totalLiquidity, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; const orderInDatums: OrderDatum[] = [ { @@ -116,7 +116,7 @@ async function genTestWarehouse() { : 0n; const raiseAssetUnit = toUnit(raiseAsset.policyId, raiseAsset.assetName); for (const order of sortedOrders) { - const datum = builder.fromDatumOrder(order.datum!); + const datum = WarehouseBuilder.fromDatumOrder(order.datum!); const lpAmount = (datum.amount * treasuryDatum.totalLiquidity) / treasuryDatum.reserveRaise; @@ -165,7 +165,7 @@ function genOrderUTxO(datum: OrderDatum, builder: WarehouseBuilder): UTxO { lovelace: ORDER_MIN_ADA + ORDER_COMMISSION, }, address: builder.orderAddress, - datum: builder.toDatumOrder(datum), + datum: WarehouseBuilder.toDatumOrder(datum), }; } @@ -223,7 +223,10 @@ test("Redeem LP | FAIL | Not created pool yet", async () => { options.treasuryInput = { ...options.treasuryInput, assets: { ...options.treasuryInput.assets, [builder.ammLpToken!]: 0n }, - datum: builder.toDatumTreasury({ ...treasuryDatum, totalLiquidity: 0n }), + datum: WarehouseBuilder.toDatumTreasury({ + ...treasuryDatum, + totalLiquidity: 0n, + }), }; builder.buildRefundOrders(options); assertValidatorFail(builder); diff --git a/src/tests/refund.test.ts b/src/tests/refund.test.ts index 5fd6991..8011105 100644 --- a/src/tests/refund.test.ts +++ b/src/tests/refund.test.ts @@ -70,7 +70,7 @@ async function genTestWarehouse() { treasuryDatum.reserveBase, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; const orderInDatums: OrderDatum[] = [ { @@ -106,7 +106,7 @@ async function genTestWarehouse() { const raiseAssetUnit = toUnit(raiseAsset.policyId, raiseAsset.assetName); const sortedOrders = sortUTxOs(orderInputUTxOs); for (const order of sortedOrders) { - const { penaltyAmount, amount, owner } = builder.fromDatumOrder( + const { penaltyAmount, amount, owner } = WarehouseBuilder.fromDatumOrder( order.datum!, ); const assets: Record = { @@ -151,7 +151,7 @@ function genOrderUTxO(datum: OrderDatum, builder: WarehouseBuilder): UTxO { lovelace: ORDER_MIN_ADA + ORDER_COMMISSION, }, address: builder.orderAddress, - datum: builder.toDatumOrder(datum), + datum: WarehouseBuilder.toDatumOrder(datum), }; } @@ -207,7 +207,7 @@ test("Refund | FAIL | Not collected manager", async () => { const { builder, options, treasuryDatum } = warehouse; options.treasuryInput = { ...options.treasuryInput, - datum: builder.toDatumTreasury({ + datum: WarehouseBuilder.toDatumTreasury({ ...treasuryDatum, isManagerCollected: false, }), @@ -219,7 +219,7 @@ test("Refund | FAIL | Not collected all orders", async () => { const { builder, options, treasuryDatum } = warehouse; options.treasuryInput = { ...options.treasuryInput, - datum: builder.toDatumTreasury({ + datum: WarehouseBuilder.toDatumTreasury({ ...treasuryDatum, collectedFund: treasuryDatum.collectedFund - 10n, }), @@ -231,7 +231,7 @@ test("Refund | FAIL | Not collected all orders", async () => { const { builder, options, treasuryDatum } = warehouse; options.treasuryInput = { ...options.treasuryInput, - datum: builder.toDatumTreasury({ + datum: WarehouseBuilder.toDatumTreasury({ ...treasuryDatum, isCancelled: false, }), diff --git a/src/tests/update-event.test.ts b/src/tests/update-event.test.ts index 26868d4..e2a171a 100644 --- a/src/tests/update-event.test.ts +++ b/src/tests/update-event.test.ts @@ -25,7 +25,7 @@ async function genTestWarehouse() { lovelace: TREASURY_MIN_ADA + CREATE_POOL_COMMISSION, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; let options: BuildUpdateLBEOptions = { treasuryInput, @@ -67,7 +67,7 @@ test("Update LBE | FAIL | update when LBE is cancelled", async () => { }; treasuryInput = { ...treasuryInput, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; options = { ...options, diff --git a/src/tests/using-seller.test.ts b/src/tests/using-seller.test.ts index 63a09c9..ed2936d 100644 --- a/src/tests/using-seller.test.ts +++ b/src/tests/using-seller.test.ts @@ -91,7 +91,7 @@ async function genTestWarehouse() { treasuryDatum.reserveBase, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(treasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), }; const sellerDatum = { ...defaultSellerDatum, @@ -106,7 +106,7 @@ async function genTestWarehouse() { lovelace: SELLER_MIN_ADA + SELLER_COMMISSION * 5n, }, address: builder.sellerAddress, - datum: builder.toDatumSeller(sellerDatum), + datum: WarehouseBuilder.toDatumSeller(sellerDatum), }; const orderInDatums: OrderDatum[] = [ { @@ -190,7 +190,7 @@ function genOrderUTxO(datum: OrderDatum, builder: WarehouseBuilder): UTxO { datum.penaltyAmount, }, address: builder.orderAddress, - datum: builder.toDatumOrder(datum), + datum: WarehouseBuilder.toDatumOrder(datum), }; } @@ -242,7 +242,10 @@ test("using-seller | FAIL | update orders: LBE is cancelled", async () => { ...warehouse.options, treasuryRefInput: { ...treasuryUTxO, - datum: builder.toDatumTreasury({ ...treasuryDatum, isCancelled: true }), + datum: WarehouseBuilder.toDatumTreasury({ + ...treasuryDatum, + isCancelled: true, + }), }, }; builder.buildUsingSeller(options); @@ -321,7 +324,7 @@ test("using-seller | FAIL | update orders: Seller output don't have any seller t builder.tx.payToAddressWithData( builder.sellerAddress, { - inline: builder.toDatumSeller({ + inline: WarehouseBuilder.toDatumSeller({ ...sellerDatum, // new seller output datum amount: -1745n, @@ -348,7 +351,7 @@ test("using-seller | FAIL | update orders: Invalid order output value", async () builder.tx.payToAddressWithData( builder.orderAddress, { - inline: builder.toDatumOrder(datum), + inline: WarehouseBuilder.toDatumOrder(datum), }, assets, ); @@ -366,7 +369,7 @@ test("using-seller | FAIL | update orders: penalty_amount is less than 0", async test("using-seller | FAIL | update orders: Order's input LBE ID miss match", async () => { const { builder, options, orderInDatums } = warehouse; - options.orderInputs[0].datum = builder.toDatumOrder({ + options.orderInputs[0].datum = WarehouseBuilder.toDatumOrder({ ...orderInDatums[0], // MINt baseAsset: MINt, @@ -388,7 +391,7 @@ test("using-seller | FAIL | update orders: Order's output LBE ID miss match", as test("using-seller | FAIL | update orders: Seller's input LBE ID miss match", async () => { const { builder, options, sellerDatum } = warehouse; - options.sellerUtxo.datum = builder.toDatumSeller({ + options.sellerUtxo.datum = WarehouseBuilder.toDatumSeller({ ...sellerDatum, // MINt baseAsset: MINt, diff --git a/src/tests/warehouse.ts b/src/tests/warehouse.ts index e060fbc..47b5526 100644 --- a/src/tests/warehouse.ts +++ b/src/tests/warehouse.ts @@ -167,7 +167,7 @@ export const genWarehouse = async (maxTxSize?: number) => { assets: { [builder.ammPoolToken]: 1n, }, - datum: builder.toDatumAmmPool(ammPoolDatum), + datum: WarehouseBuilder.toDatumAmmPool(ammPoolDatum), txHash: "01".repeat(32), outputIndex: outputIndex++, }; @@ -186,7 +186,7 @@ export const genWarehouse = async (maxTxSize?: number) => { lovelace: MANAGER_MIN_ADA, }, address: builder.managerAddress, - datum: builder.toDatumManager(defaultManagerDatum), + datum: WarehouseBuilder.toDatumManager(defaultManagerDatum), }; let defaultSellerInput: UTxO = { @@ -197,7 +197,7 @@ export const genWarehouse = async (maxTxSize?: number) => { lovelace: SELLER_MIN_ADA, }, address: builder.sellerAddress, - datum: builder.toDatumSeller(defaultSellerDatum), + datum: WarehouseBuilder.toDatumSeller(defaultSellerDatum), }; let defaultOrderInput: UTxO = { @@ -208,7 +208,7 @@ export const genWarehouse = async (maxTxSize?: number) => { lovelace: ORDER_MIN_ADA, }, address: builder.orderAddress, - datum: builder.toDatumOrder(defaultOrderDatum), + datum: WarehouseBuilder.toDatumOrder(defaultOrderDatum), }; let defaultTreasuryInput: UTxO = { @@ -223,7 +223,7 @@ export const genWarehouse = async (maxTxSize?: number) => { )]: defaultTreasuryDatum.reserveBase, }, address: builder.treasuryAddress, - datum: builder.toDatumTreasury(defaultTreasuryDatum), + datum: WarehouseBuilder.toDatumTreasury(defaultTreasuryDatum), }; return { From 2f179e114cea91575f102732df9b7725d10cc6e3 Mon Sep 17 00:00:00 2001 From: tony Date: Wed, 26 Jun 2024 00:00:39 +0700 Subject: [PATCH 6/8] update batcher --- bun.lockb | Bin 172571 -> 168510 bytes package.json | 2 +- src/chaining.ts | 144 ++++++++++++++++ src/cmd/batcher.ts | 349 ++++++++++++++++++++++++++++++++++++-- src/tests/batcher.test.ts | 91 ++++++++++ src/tests/warehouse.ts | 10 +- src/types.ts | 10 +- src/utils.ts | 12 ++ 8 files changed, 595 insertions(+), 23 deletions(-) create mode 100644 src/chaining.ts create mode 100644 src/tests/batcher.test.ts diff --git a/bun.lockb b/bun.lockb index 8aecd97b6cc0866cd79ce6b11249e15dc31978e3..777bf88123d854bc8c272a487b4504797e7b51cf 100755 GIT binary patch delta 28370 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 delta 31098 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 MMsh4f@8(DTA2l+HSO5S3 diff --git a/package.json b/package.json index 7cba8b4..43b9806 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@minswap/tiny-invariant": "^1.2.0", - "@minswap/translucent": "0.0.6-minswap.17", + "@minswap/translucent": "link:@minswap/translucent", "rollup": "^4.14.1", "rollup-plugin-dts": "^6.1.0", "rollup-plugin-esbuild": "^6.1.1", diff --git a/src/chaining.ts b/src/chaining.ts new file mode 100644 index 0000000..b993bcd --- /dev/null +++ b/src/chaining.ts @@ -0,0 +1,144 @@ +import * as T from "@minswap/translucent"; + +import type { + CTransactionHash, + CTransactionOutputs, + CTransactionUnspentOutputs, + MaestroSupportedNetworks, + TxSigned, + UTxO, +} from "./types"; + +export type InputId = string; +export type InputIdentify = ( + txHash: CTransactionHash, + finalOutputs: CTransactionOutputs, +) => CTransactionUnspentOutputs; + +export type Chaining = { + mapInputs: Record; + inputIdentifyFuncs: Record; + buildTx: ( + mapInputs: Record, + extra: any, + ) => Promise; + stopCondition: () => boolean; + submitTx: (tx: string) => Promise; +}; + +export async function doChaining(options: Chaining, mutate?: any) { + let { mapInputs, inputIdentifyFuncs, buildTx, stopCondition, submitTx } = + options; + let C = T.CModuleLoader.get; + while (true) { + if (stopCondition()) { + return; + } + let tx = await buildTx(mapInputs, mutate); + await submitTx(tx.toString()); + let transactionHash = tx.toHash(); + let txHash = C.TransactionHash.from_hex(transactionHash); + let finalOutputs = tx.txSigned.body().outputs(); + for (let [k, v] of Object.entries(inputIdentifyFuncs)) { + let coreUtxos = v(txHash, finalOutputs); + let utxos: UTxO[] = []; + for (let i = 0; i < coreUtxos.len(); i++) { + let coreUtxo = coreUtxos.get(i); + utxos.push(T.coreToUtxo(coreUtxo)); + } + mapInputs[k] = utxos; + } + } +} + +let submitTx = async (tx: string): Promise => { + console.log({ tx }); + const response = await fetch("https://dev-3.minswap.org/graphql", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: `{"query":"mutation SubmitTx($tx: String!) {submitTx(tx: $tx)}","variables":{"tx":"${tx}"}}`, + }); + const result = await response.text(); + return result; +}; + +let main = async () => { + await T.loadModule(); + await T.CModuleLoader.load(); + let network: MaestroSupportedNetworks = "Preprod"; + let maestroApiKey = "E0n5jUy4j40nhKCuB7LrYabTNieG0egu" ?? process.argv[2]; + let seedPhase = + "muffin spell cement resemble frame pupil grow gloom hawk wild item hungry polar ice maximum sport economy drop sun timber stone circle army jazz" ?? + process.argv[3]; + + const maestro = new T.Maestro({ network, apiKey: maestroApiKey }); + const t = await T.Translucent.new(maestro, network); + t.selectWalletFromSeed(seedPhase); + + let address = await t.wallet.address(); + let C = T.CModuleLoader.get; + + let inputIdentifyFuncs = { + root: (txHash: CTransactionHash, finalOutputs: CTransactionOutputs) => { + console.log("finalOutputs", finalOutputs.to_js_value()); + let result = C.TransactionUnspentOutputs.new(); + for (let i = 0; i <= finalOutputs.len(); i++) { + let output = finalOutputs.get(i); + console.log("output", output.to_js_value()); + let curAddress = output.address(); + if ( + curAddress.to_bech32() === address && + BigInt(output.amount().coin().to_str()) >= 500_000_000n + ) { + let input = C.TransactionInput.new( + txHash, + C.BigNum.from_str(i.toString()), + ); + result.add(C.TransactionUnspentOutput.new(input, output)); + break; + } + } + return result; + }, + }; + + let count = 0; + let stopCondition = () => { + console.log({ count }); + return count++ >= 4; + }; + + let mapInputs = { + root: (await t.utxosAt(address)).filter( + (u) => u.assets["lovelace"] >= 500_000_000n, + ), + }; + let buildTx = async ( + mapInputs: Record, + ): Promise => { + let txBuilder = t.newTx(); + txBuilder + .collectFrom(mapInputs["root"]) + .payToAddress(address, { lovelace: 3_000_000n }); + let tx = await txBuilder.complete(); + let signedTx = tx.sign(); + let txSigned = await signedTx.complete(); + console.log("txSigned", txSigned.txSigned.to_js_value()); + return txSigned; + }; + + let options: Chaining = { + mapInputs, + inputIdentifyFuncs, + buildTx, + stopCondition, + submitTx: async (tx: string) => { + submitTx(tx); + }, + }; + await doChaining(options); +}; + +// main(); diff --git a/src/cmd/batcher.ts b/src/cmd/batcher.ts index 510bdad..73bb553 100644 --- a/src/cmd/batcher.ts +++ b/src/cmd/batcher.ts @@ -1,47 +1,360 @@ -import { WarehouseBuilder } from "../build-tx"; -import type { BluePrintAsset, UTxO } from "../types"; -import { computeLPAssetName } from "../utils"; +import * as T from "@minswap/translucent"; +import { + WarehouseBuilder, + type BuildCollectManagerOptions, + type BuildCollectSellersOptions, +} from "../build-tx"; +import { + doChaining, + type Chaining, + type InputId, + type InputIdentify, +} from "../chaining"; +import type { + Address, + BluePrintAsset, + CTransactionHash, + CTransactionOutputs, + Tx, + TxSigned, + UTxO, + Unit, +} from "../types"; +import { catchWrapper, computeLPAssetName } from "../utils"; +import { MINIMUM_SELLER_COLLECTED } from ".."; type LbeId = string; type LbePairAsset = { baseAsset: BluePrintAsset; raiseAsset: BluePrintAsset }; +type LbeValidator = "treasury" | "manager" | "seller" | "order"; +type LbePhase = + | "collectOrders" + | "collectSellers" + | "collectManager" + | "refundOrders" + | "redeemOrders" + | "createAmmPool"; +type LbeUTxO = { + treasury: UTxO; + manager?: UTxO; + sellers: UTxO[]; + orders: UTxO[]; +}; + +interface CollectSellersMapInput extends Record { + seed: UTxO[]; + manager: UTxO[]; +} namespace LbeId { - export function fromUtxo(options: { tag: "string"; utxo: UTxO }): LbeId { + export function fromUtxo(options: { tag: LbeValidator; utxo: UTxO }): LbeId { let { tag, utxo } = options; + + // return LbeId from LbePairAsset let innerFunc = ({ baseAsset, raiseAsset }: LbePairAsset) => computeLPAssetName( baseAsset.policyId + baseAsset.assetName, raiseAsset.policyId + raiseAsset.assetName, ); - let cases: Record LbePairAsset> = { + + let cases: Record LbePairAsset> = { treasury: (u: UTxO) => WarehouseBuilder.fromDatumTreasury(u.datum!), manager: (u: UTxO) => WarehouseBuilder.fromDatumManager(u.datum!), seller: (u: UTxO) => WarehouseBuilder.fromDatumSeller(u.datum!), order: (u: UTxO) => WarehouseBuilder.fromDatumOrder(u.datum!), }; + return innerFunc(cases[tag](utxo)); } } -type LbeUTxO = { - treasury: UTxO; - manager?: UTxO; - sellers: UTxO[]; - orders: UTxO[]; -}; +namespace LbePhase { + export function fromLbeUtxo(lbeUtxo: LbeUTxO): LbePhase | undefined { + let things: { checker: (lbeUtxo: LbeUTxO) => boolean; phase: LbePhase }[] = + [ + { checker: isCollectOrders, phase: "collectOrders" }, + { checker: isCollectSellers, phase: "collectSellers" }, + { checker: isCollectManager, phase: "collectManager" }, + { checker: isRefundOrders, phase: "refundOrders" }, + { checker: isRedeemOrders, phase: "redeemOrders" }, + { checker: isCreateAmmPool, phase: "createAmmPool" }, + ]; + for (let { checker, phase } of things) { + if (catchWrapper(checker, lbeUtxo, false)) { + return phase; + } + } + return undefined; + } + + function isCollectOrders(lbeUtxo: LbeUTxO) { + let treasuryDatum = WarehouseBuilder.fromDatumTreasury( + lbeUtxo.treasury.datum!, + ); + if (treasuryDatum.isCancelled) { + // collected Manager but not collect all funds yet + return ( + treasuryDatum.isManagerCollected && + treasuryDatum.collectedFund !== + treasuryDatum.reserveRaise + treasuryDatum.totalPenalty + ); + } else { + // collected Manager but not created pool yet. + return ( + treasuryDatum.isManagerCollected && treasuryDatum.totalLiquidity === 0n + ); + } + } -class WarehouseBatcher { + function isCollectSellers(lbeUtxo: LbeUTxO) { + let treasuryDatum = WarehouseBuilder.fromDatumTreasury( + lbeUtxo.treasury.datum!, + ); + let managerDatum = WarehouseBuilder.fromDatumManager( + lbeUtxo.manager!.datum!, + ); + // LBE is cancelled or Discovery phase ended + // Still have Sellers left + return ( + (treasuryDatum.isCancelled || treasuryDatum.endTime < Date.now()) && + managerDatum.sellerCount > 0n + ); + } + + function isCollectManager(lbeUtxo: LbeUTxO) { + let managerDatum = WarehouseBuilder.fromDatumManager( + lbeUtxo.manager!.datum!, + ); + // Collected all Sellers + return managerDatum.sellerCount === 0n; + } + + function isRefundOrders(lbeUtxo: LbeUTxO) { + let treasuryDatum = WarehouseBuilder.fromDatumTreasury( + lbeUtxo.treasury.datum!, + ); + return ( + treasuryDatum.isCancelled && + treasuryDatum.collectedFund === + treasuryDatum.reserveRaise + treasuryDatum.totalPenalty + ); + } + + function isRedeemOrders(lbeUtxo: LbeUTxO) { + let treasuryDatum = WarehouseBuilder.fromDatumTreasury( + lbeUtxo.treasury.datum!, + ); + return treasuryDatum.totalLiquidity > 0n && lbeUtxo.orders.length > 0; + } + + function isCreateAmmPool(lbeUtxo: LbeUTxO) { + let treasuryDatum = WarehouseBuilder.fromDatumTreasury( + lbeUtxo.treasury.datum!, + ); + return treasuryDatum.totalLiquidity === 0n && !treasuryDatum.isCancelled; + } +} + +export class WarehouseBatcher { builder: WarehouseBuilder; - mapLbeUTxO: Record = {}; + mapLbeUTxO: Record = {}; constructor(builder: WarehouseBuilder) { this.builder = builder; } + + async submitTx(tx: string) { + await this.builder.t.provider.submitTx(tx); + } + + // IDENTIFY + identifyCommon(address: Address): InputIdentify { + let innerFunc = ( + txHash: CTransactionHash, + finalOutputs: CTransactionOutputs, + ) => { + let C = T.CModuleLoader.get; + let result = C.TransactionUnspentOutputs.new(); + for (let i = 0; i <= finalOutputs.len(); i++) { + let output = finalOutputs.get(i); + let curAddress = output.address(); + if (curAddress.to_bech32() === address) { + let input = C.TransactionInput.new( + txHash, + C.BigNum.from_str(i.toString()), + ); + result.add(C.TransactionUnspentOutput.new(input, output)); + break; + } + } + return result; + }; + return innerFunc; + } + + // BUILDING + async buildCollectSellers( + mapInputs: CollectSellersMapInput, + extra: { sellers: UTxO[]; treasuryRefInput: UTxO }, + ): Promise { + let { sellers, treasuryRefInput } = extra; + this.builder.clean(); + let options: BuildCollectSellersOptions = { + treasuryRefInput, + managerInput: mapInputs.manager[0], + sellerInputs: sellers, + validFrom: Date.now(), + validTo: Date.now() + 3 * 60 * 60 * 1000, + }; + this.builder.tasks.push(() => { + this.builder.tx.collectFrom(mapInputs["seed"]); + }); + this.builder.buildCollectSeller(options); + let txBuilder = this.builder.complete(); + let tx = await txBuilder.complete(); + let signedTx = tx.sign(); + let txSigned = await signedTx.complete(); + return txSigned; + } + + collectSellersChaining(lbeUtxo: LbeUTxO, seed: UTxO): Chaining { + let stopCondition = () => { + return lbeUtxo.sellers.length === 0; + }; + let mapInputs: CollectSellersMapInput = { + seed: [seed], + treasury: [lbeUtxo.treasury], + manager: [lbeUtxo.manager!], + sellers: lbeUtxo.sellers, + }; + let inputIdentifyFuncs: Record = { + seed: this.identifyCommon(seed.address), + manager: this.identifyCommon(this.builder.managerAddress), + }; + let buildTx = async ( + args: Record, + extra: { sellers: UTxO[]; treasuryRefInput: UTxO }, + ) => { + let batchSellers = extra.sellers.splice( + 0, + Number(MINIMUM_SELLER_COLLECTED), + ); + return this.buildCollectSellers(args as CollectSellersMapInput, { + ...extra, + sellers: batchSellers, + }); + }; + return { + mapInputs, + stopCondition, + inputIdentifyFuncs, + buildTx, + submitTx: async (tx: string) => { + this.submitTx(tx); + }, + }; + } + + buildCollectManager(lbeUtxo: LbeUTxO, seed: UTxO): Tx { + this.builder.clean(); + let options: BuildCollectManagerOptions = { + treasuryInput: lbeUtxo.treasury, + managerInput: lbeUtxo.manager!, + validFrom: Date.now(), + validTo: Date.now() + 3 * 60 * 60 * 1000, + }; + this.builder.buildCollectManager(options); + this.builder.tasks.push(() => { + this.builder.tx.collectFrom([seed]); + }); + let tx = this.builder.complete(); + return tx; + } + + async batching() { + await this.buildMapLbeUTxO(); + for (let lbeUtxo of Object.values(this.mapLbeUTxO)) { + let phase = LbePhase.fromLbeUtxo(lbeUtxo); + if (phase) { + console.log(`doing phase ${phase}`); + let seed = ( + await this.builder.t.utxosAt(await this.builder.t.wallet.address()) + )[0]; + let chaining = this.collectSellersChaining(lbeUtxo, seed); + await doChaining(chaining, { + sellers: lbeUtxo.sellers, + treasuryRefInput: lbeUtxo.treasury, + }); + } + } + } + + async buildMapLbeUTxO() { + let things: { + tag: LbeValidator; + address: Address; + token: Unit; + cb: (lbeId: LbeId, utxo: UTxO) => void; // set mapLbeUTxO + }[] = [ + { + tag: "treasury", + address: this.builder.treasuryAddress, + token: this.builder.treasuryToken, + cb: (lbeId, utxo) => { + this.mapLbeUTxO[lbeId] = { + ...(this.mapLbeUTxO[lbeId] ?? {}), + treasury: utxo, + }; + }, + }, + { + tag: "manager", + address: this.builder.managerAddress, + token: this.builder.managerToken, + cb: (lbeId, utxo) => { + this.mapLbeUTxO[lbeId] = { + ...(this.mapLbeUTxO[lbeId] ?? {}), + manager: utxo, + }; + }, + }, + { + tag: "seller", + address: this.builder.sellerAddress, + token: this.builder.sellerToken, + cb: (lbeId, utxo) => { + let sellers = this.mapLbeUTxO[lbeId]?.sellers ?? []; + sellers.push(utxo); + this.mapLbeUTxO[lbeId] = { + ...(this.mapLbeUTxO[lbeId] ?? {}), + sellers, + }; + }, + }, + { + tag: "order", + address: this.builder.orderAddress, + token: this.builder.orderToken, + cb: (lbeId, utxo) => { + let orders = this.mapLbeUTxO[lbeId]?.orders ?? []; + orders.push(utxo); + this.mapLbeUTxO[lbeId] = { + ...(this.mapLbeUTxO[lbeId] ?? {}), + orders, + }; + }, + }, + ]; + for (let { address, tag, token, cb } of things) { + let utxos = await this.builder.t.utxosAtWithUnit(address, token); + for (let utxo of utxos) { + let lbeId = LbeId.fromUtxo({ tag, utxo }); + cb(lbeId, utxo); + } + } + } } -let main = async () => { - // get Map - // buildCollect things -}; +// let main = async () => { +// }; -main(); +// main(); diff --git a/src/tests/batcher.test.ts b/src/tests/batcher.test.ts new file mode 100644 index 0000000..4bbc9ef --- /dev/null +++ b/src/tests/batcher.test.ts @@ -0,0 +1,91 @@ +import * as T from "@minswap/translucent"; +import { WarehouseBuilder } from "../build-tx"; +import { WarehouseBatcher } from "../cmd/batcher"; +import type { ManagerDatum, TreasuryDatum } from "../types"; +import { loadModule } from "./utils"; +import { + genWarehouse, + skipToCountingPhase, + type GenWarehouse, +} from "./warehouse"; + +let warehouse: GenWarehouse; +let defaultMaxTxSize = T.PROTOCOL_PARAMETERS_DEFAULT.maxTxSize; + +const SELLER_COUNT = 3; + +beforeAll(async () => { + await loadModule(); +}); + +beforeEach(async () => { + warehouse = await genWarehouse(defaultMaxTxSize); +}); + +test("batcher | buildMapLbeUTxO", async () => { + let { builder, emulator } = warehouse; + + emulator.addUTxO(warehouse.defaultTreasuryInput); + emulator.addUTxO(warehouse.defaultManagerInput); + emulator.addUTxO(warehouse.defaultSellerInput); + emulator.addUTxO(warehouse.defaultOrderInput); + + let expectMapLbeUTxO = { + treasury: warehouse.defaultTreasuryInput, + manager: warehouse.defaultManagerInput, + sellers: [warehouse.defaultSellerInput], + orders: [warehouse.defaultOrderInput], + }; + let batcher = new WarehouseBatcher(builder); + await batcher.buildMapLbeUTxO(); + + expect(Object.keys(batcher.mapLbeUTxO).length).toEqual(1); + let mapLbeUTxO = Object.values(batcher.mapLbeUTxO)[0]; + expect(mapLbeUTxO).toEqual(expectMapLbeUTxO); +}); + +test("batcher | collect-sellers", async () => { + let { builder, emulator } = warehouse; + // IMPORTANT!!! Update LBE End Time + let treasuryDatum: TreasuryDatum = { + ...warehouse.defaultTreasuryDatum, + startTime: BigInt(Date.now() - 3 * 24 * 60 * 60 * 1000), + endTime: BigInt(Date.now() - 60 * 60 * 1000), + }; + let treasuryInput = { + ...warehouse.defaultTreasuryInput, + datum: WarehouseBuilder.toDatumTreasury(treasuryDatum), + }; + let managerDatum: ManagerDatum = { + ...warehouse.defaultManagerDatum, + sellerCount: BigInt(SELLER_COUNT), + }; + let managerInput = { + ...warehouse.defaultManagerInput, + datum: WarehouseBuilder.toDatumManager(managerDatum), + }; + let sellerInputs: T.UTxO[] = []; + for (let i = 0; i < SELLER_COUNT; i++) { + let utxo: T.UTxO = { + ...warehouse.defaultSellerInput, + outputIndex: warehouse.outputIndex++, + }; + sellerInputs.push(utxo); + } + + emulator.addUTxO(treasuryInput); + emulator.addUTxO(managerInput); + for (let s of sellerInputs) emulator.addUTxO(s); + skipToCountingPhase({ + t: builder.t, + e: emulator, + datum: treasuryDatum, + }); + + let batcher = new WarehouseBatcher(builder); + await batcher.batching(); + + // collect all sellers => Seller Address doesn't have any UTxO + let sellers = await warehouse.findSellers(); + expect(sellers.length).toEqual(0); +}); diff --git a/src/tests/warehouse.ts b/src/tests/warehouse.ts index 47b5526..9eed84c 100644 --- a/src/tests/warehouse.ts +++ b/src/tests/warehouse.ts @@ -67,7 +67,7 @@ export const genWarehouse = async (maxTxSize?: number) => { }; const emulator = new T.Emulator([ACCOUNT_0], protocolParameters); let t = await T.Translucent.new(emulator); - emulator.awaitBlock(10_000); // For validity ranges to be valid + // emulator.awaitBlock(10_000); // For validity ranges to be valid t.selectWalletFromPrivateKey(ACCOUNT_0.privateKey); const warehouseOptions = await genWarehouseOptions(t); @@ -178,6 +178,13 @@ export const genWarehouse = async (maxTxSize?: number) => { ) as UTxO; }; + let findSellers = async (): Promise => { + return await emulator.getUtxosWithUnit( + builder.sellerAddress, + builder.sellerToken, + ); + }; + let defaultManagerInput: UTxO = { txHash: "00".repeat(32), outputIndex: outputIndex++, @@ -240,6 +247,7 @@ export const genWarehouse = async (maxTxSize?: number) => { defaultOrderDatum, ammPoolInput, findTreasuryInput, + findSellers, ammPoolDatum, builder, outputIndex, diff --git a/src/types.ts b/src/types.ts index 2ed7a85..1d97989 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,8 +13,14 @@ import type { FactoryValidatorValidateFactory } from "../amm-plutus"; export type Address = T.Address; export type Assets = T.Assets; export type BluePrintAsset = { policyId: string; assetName: string }; +export type Credential = T.Credential; +export type CTransactionHash = T.CTransactionHash; +export type CTransactionOutputs = T.CTransactionOutputs; +export type CTransactionUnspentOutputs = T.CTransactionUnspentOutputs; export type Datum = T.Datum; export type Emulator = T.Emulator; +export type Maestro = T.Maestro; +export type MaestroSupportedNetworks = T.MaestroSupportedNetworks; export type Network = T.Network; export type OutputData = T.OutputData; export type OutRef = T.OutRef; @@ -27,12 +33,10 @@ export type Script = T.Script; export type ScriptRef = T.ScriptRef; export type Translucent = T.Translucent; export type Tx = T.Tx; +export type TxSigned = T.TxSigned; export type Unit = T.Unit; export type UnixTime = T.UnixTime; export type UTxO = T.UTxO; -export type Maestro = T.Maestro; -export type MaestroSupportedNetworks = T.MaestroSupportedNetworks; -export type Credential = T.Credential; export type AmmPoolDatum = FeedTypeAmmPool["_datum"]; export type AmmFactoryDatum = FactoryValidatorValidateFactory["datum"]; diff --git a/src/utils.ts b/src/utils.ts index 4fc99d5..7d92943 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -224,3 +224,15 @@ export function calculateInitialLiquidity( } return x; } + +export function catchWrapper( + func: (t: T) => R, + arg0: T, + defaultValue: R, +): R { + try { + return func(arg0); + } catch { + return defaultValue; + } +} From 2f13bc06595bcdaa93d88cac047058b73481ef4a Mon Sep 17 00:00:00 2001 From: dzung pham Date: Wed, 26 Jun 2024 10:07:40 +0700 Subject: [PATCH 7/8] pump --- bun.lockb | Bin 168510 -> 172563 bytes package.json | 2 +- src/types.ts | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index 777bf88123d854bc8c272a487b4504797e7b51cf..b00f4cce61c76ba98fe40056de6162103a54988a 100755 GIT binary patch delta 31089 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 delta 28272 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 diff --git a/package.json b/package.json index 43b9806..3938b97 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@minswap/tiny-invariant": "^1.2.0", - "@minswap/translucent": "link:@minswap/translucent", + "@minswap/translucent": "0.0.6-minswap.18", "rollup": "^4.14.1", "rollup-plugin-dts": "^6.1.0", "rollup-plugin-esbuild": "^6.1.1", diff --git a/src/types.ts b/src/types.ts index 1d97989..a67b7ed 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,6 +15,7 @@ export type Assets = T.Assets; export type BluePrintAsset = { policyId: string; assetName: string }; export type Credential = T.Credential; export type CTransactionHash = T.CTransactionHash; +export type CTransactionOutput = T.CTransactionOutput; export type CTransactionOutputs = T.CTransactionOutputs; export type CTransactionUnspentOutputs = T.CTransactionUnspentOutputs; export type Datum = T.Datum; From c49576a7a92820140bfe17ee9b87db77cfaf7ecf Mon Sep 17 00:00:00 2001 From: dzung pham Date: Wed, 26 Jun 2024 14:03:02 +0700 Subject: [PATCH 8/8] wip --- src/constants.ts | 7 +++++++ src/tests/example.test.ts | 8 +------- src/tests/redeem-lp.test.ts | 3 +++ src/tests/warehouse.ts | 1 + 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 08b0d9a..8e526ec 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -25,3 +25,10 @@ export const MAX_PENALTY_RATE = 25n; export const MINIMUM_SELLER_COLLECTED = 20n; export const MINIMUM_ORDER_COLLECTED = 30n; export const MINIMUM_ORDER_REDEEMED = 30n; + +// Batching +export const MAX_COLLECT_SELLERS = 30; +export const MAX_COLLECT_ORDERS = 50; +export const MAX_REDEEM_ORDERS = 50; +export const MAX_REFUND_ORDERS = 50; +export const MAX_SEEDS = 100; diff --git a/src/tests/example.test.ts b/src/tests/example.test.ts index 02c4c3d..4cd20f2 100644 --- a/src/tests/example.test.ts +++ b/src/tests/example.test.ts @@ -151,13 +151,7 @@ beforeEach(async () => { ammDeployedValidators = await deployMinswapValidators(t, ammValidators); // registerStake - await quickSubmitBuilder(emulator)({ - txBuilder: t - .newTx() - .registerStake( - t.utils.validatorToRewardAddress(validators.sellerValidator), - ), - }); + // Need to register Stake Factory Validator because it supports WithdrawFrom await quickSubmitBuilder(emulator)({ txBuilder: t .newTx() diff --git a/src/tests/redeem-lp.test.ts b/src/tests/redeem-lp.test.ts index 306a59d..aee6ac5 100644 --- a/src/tests/redeem-lp.test.ts +++ b/src/tests/redeem-lp.test.ts @@ -190,6 +190,9 @@ function attachValueToInput(value: Assets): void { ]); }); } + +test("Redeem LP | PASS | penalty orders", async () => {}); + test("Redeem LP | FAIL | No treasury input", async () => { const { builder, options, treasuryUTxO } = warehouse; builder.buildRedeemOrders(options); diff --git a/src/tests/warehouse.ts b/src/tests/warehouse.ts index 9eed84c..d7ff174 100644 --- a/src/tests/warehouse.ts +++ b/src/tests/warehouse.ts @@ -72,6 +72,7 @@ export const genWarehouse = async (maxTxSize?: number) => { const warehouseOptions = await genWarehouseOptions(t); // registerStake + // Need to register Stake Factory Validator because it supports WithdrawFrom await quickSubmitBuilder(emulator)({ txBuilder: t .newTx()