diff --git a/src/dex-v2.ts b/src/dex-v2.ts index e8e0198..2890fe7 100644 --- a/src/dex-v2.ts +++ b/src/dex-v2.ts @@ -27,6 +27,24 @@ import { DexVersion } from "./batcher-fee-reduction/types.internal"; import { FactoryV2 } from "./types/factory"; import { NetworkEnvironment, NetworkId } from "./types/network"; import { lucidToNetworkEnv } from "./utils/network.internal"; +import { buildUtxoToStoreDatum } from "./utils/tx.internal"; + +export type V2CustomReceiver = { + refundReceiver: Address; + refundReceiverDatum?: { + type: + | OrderV2.ExtraDatumType.DATUM_HASH + | OrderV2.ExtraDatumType.INLINE_DATUM; + datum: string; + }; + successReceiver: Address; + successReceiverDatum?: { + type: + | OrderV2.ExtraDatumType.DATUM_HASH + | OrderV2.ExtraDatumType.INLINE_DATUM; + datum: string; + }; +}; /** * Options for building Pool V2 Creation transaction @@ -50,6 +68,7 @@ export type CreatePoolV2Options = { export type BulkOrdersOption = { sender: Address; + customReceiver?: V2CustomReceiver; orderOptions: OrderOptions[]; expiredOptions?: OrderV2.ExpirySetting; availableUtxos: UTxO[]; @@ -733,6 +752,7 @@ export class DexV2 { async createBulkOrdersTx({ sender, + customReceiver, orderOptions, expiredOptions, availableUtxos, @@ -760,6 +780,10 @@ export class DexV2 { }); const limitOrders: string[] = []; const lucidTx = this.lucid.newTx(); + const necessaryExtraDatums: { + receiver: Address; + datum: string; + }[] = []; for (let i = 0; i < orderOptions.length; i++) { const option = orderOptions[i]; const { type, lpAsset } = option; @@ -796,16 +820,65 @@ export class DexV2 { type: OrderV2.AuthorizationMethodType.SIGNATURE, hash: senderPaymentCred.hash, }; + + let successReceiver: Address = sender; + let successReceiverDatum: OrderV2.ExtraDatum = { + type: OrderV2.ExtraDatumType.NO_DATUM, + }; + let refundReceiver: Address = sender; + let refundReceiverDatum: OrderV2.ExtraDatum = { + type: OrderV2.ExtraDatumType.NO_DATUM, + }; + if (customReceiver) { + const { + successReceiver: customSuccessReceiver, + successReceiverDatum: customSuccessReceiverDatum, + refundReceiver: customRefundReceiver, + refundReceiverDatum: customRefundReceiverDatum, + } = customReceiver; + successReceiver = customSuccessReceiver; + refundReceiver = customRefundReceiver; + if (!customSuccessReceiverDatum) { + successReceiverDatum = { + type: OrderV2.ExtraDatumType.NO_DATUM, + }; + } else { + const datumHash = this.lucid.utils.datumToHash( + customSuccessReceiverDatum.datum + ); + successReceiverDatum = { + type: customSuccessReceiverDatum.type, + hash: datumHash, + }; + necessaryExtraDatums.push({ + receiver: successReceiver, + datum: customSuccessReceiverDatum.datum, + }); + } + if (!customRefundReceiverDatum) { + refundReceiverDatum = { + type: OrderV2.ExtraDatumType.NO_DATUM, + }; + } else { + const datumHash = this.lucid.utils.datumToHash( + customRefundReceiverDatum.datum + ); + refundReceiverDatum = { + type: customRefundReceiverDatum.type, + hash: datumHash, + }; + necessaryExtraDatums.push({ + receiver: refundReceiver, + datum: customRefundReceiverDatum.datum, + }); + } + } const orderDatum: OrderV2.Datum = { canceller: canceller, - refundReceiver: sender, - refundReceiverDatum: { - type: OrderV2.ExtraDatumType.NO_DATUM, - }, - successReceiver: sender, - successReceiverDatum: { - type: OrderV2.ExtraDatumType.NO_DATUM, - }, + refundReceiver: refundReceiver, + refundReceiverDatum: refundReceiverDatum, + successReceiver: successReceiver, + successReceiverDatum: successReceiverDatum, step: orderStep, lpAsset: lpAsset, maxBatcherFee: totalBatcherFee, @@ -843,6 +916,21 @@ export class DexV2 { if (composeTx) { lucidTx.compose(composeTx); } + for (const necessaryExtraDatum of necessaryExtraDatums) { + const utxoForStoringDatum = buildUtxoToStoreDatum( + this.lucid, + sender, + necessaryExtraDatum.receiver, + necessaryExtraDatum.datum + ); + if (utxoForStoringDatum) { + lucidTx.payToAddressWithData( + utxoForStoringDatum.address, + utxoForStoringDatum.outputData, + utxoForStoringDatum.assets + ); + } + } return lucidTx.complete(); } diff --git a/src/dex.ts b/src/dex.ts index 6635f1f..54512cf 100644 --- a/src/dex.ts +++ b/src/dex.ts @@ -21,6 +21,15 @@ import { import { NetworkEnvironment, NetworkId } from "./types/network"; import { OrderV1 } from "./types/order"; import { lucidToNetworkEnv } from "./utils/network.internal"; +import { buildUtxoToStoreDatum } from "./utils/tx.internal"; + +export type V1AndStableswapCustomReceiver = { + receiver: Address; + receiverDatum?: { + hash: string; + datum: string; + }; +}; /** * Common options for build Minswap transaction @@ -49,7 +58,6 @@ export type BuildCancelOrderOptions = { * @minimumLPReceived Minimum Received Amount you can accept after order is executed */ export type BuildDepositTxOptions = CommonOptions & { - // sender: Address; assetA: Asset; assetB: Asset; amountA: bigint; @@ -80,7 +88,6 @@ export type BuildZapInTxOptions = CommonOptions & { * @minimumAssetBReceived Minimum Received of Asset A in the Pool you can accept after order is executed */ export type BuildWithdrawTxOptions = CommonOptions & { - sender: Address; lpAsset: Asset; lpAmount: bigint; minimumAssetAReceived: bigint; @@ -95,7 +102,7 @@ export type BuildWithdrawTxOptions = CommonOptions & { * @expectedAmountOut The expected Amount of Asset Out you want to receive after order is executed */ export type BuildSwapExactOutTxOptions = CommonOptions & { - sender: Address; + customReceiver?: V1AndStableswapCustomReceiver; assetIn: Asset; assetOut: Asset; maximumAmountIn: bigint; @@ -111,7 +118,7 @@ export type BuildSwapExactOutTxOptions = CommonOptions & { * @isLimitOrder Define this order is Limit Order or not */ export type BuildSwapExactInTxOptions = CommonOptions & { - sender: Address; + customReceiver?: V1AndStableswapCustomReceiver; assetIn: Asset; amountIn: bigint; assetOut: Asset; @@ -137,6 +144,7 @@ export class Dex { ): Promise { const { sender, + customReceiver, assetIn, amountIn, assetOut, @@ -160,8 +168,8 @@ export class Dex { } const datum: OrderV1.Datum = { sender: sender, - receiver: sender, - receiverDatumHash: undefined, + receiver: customReceiver ? customReceiver.receiver : sender, + receiverDatumHash: customReceiver?.receiverDatum?.hash, step: { type: OrderV1.StepType.SWAP_EXACT_IN, desiredAsset: assetOut, @@ -186,6 +194,21 @@ export class Dex { } else { tx.attachMetadata(674, { msg: [MetadataMessage.SWAP_EXACT_IN_ORDER] }); } + if (customReceiver && customReceiver.receiverDatum) { + const utxoForStoringDatum = buildUtxoToStoreDatum( + this.lucid, + sender, + customReceiver.receiver, + customReceiver.receiverDatum.datum + ); + if (utxoForStoringDatum) { + tx.payToAddressWithData( + utxoForStoringDatum.address, + utxoForStoringDatum.outputData, + utxoForStoringDatum.assets + ); + } + } return await tx.complete(); } @@ -194,6 +217,7 @@ export class Dex { ): Promise { const { sender, + customReceiver, assetIn, assetOut, maximumAmountIn, @@ -218,8 +242,8 @@ export class Dex { } const datum: OrderV1.Datum = { sender: sender, - receiver: sender, - receiverDatumHash: undefined, + receiver: customReceiver ? customReceiver.receiver : sender, + receiverDatumHash: customReceiver?.receiverDatum?.hash, step: { type: OrderV1.StepType.SWAP_EXACT_OUT, desiredAsset: assetOut, @@ -229,7 +253,7 @@ export class Dex { depositADA: FIXED_DEPOSIT_ADA, }; - return await this.lucid + const tx = this.lucid .newTx() .payToContract( DexV1Constant.ORDER_BASE_ADDRESS[this.networkId], @@ -238,8 +262,25 @@ export class Dex { ) .payToAddress(sender, reductionAssets) .addSigner(sender) - .attachMetadata(674, { msg: [MetadataMessage.SWAP_EXACT_OUT_ORDER] }) - .complete(); + .attachMetadata(674, { msg: [MetadataMessage.SWAP_EXACT_OUT_ORDER] }); + + if (customReceiver && customReceiver.receiverDatum) { + const utxoForStoringDatum = buildUtxoToStoreDatum( + this.lucid, + sender, + customReceiver.receiver, + customReceiver.receiverDatum.datum + ); + if (utxoForStoringDatum) { + tx.payToAddressWithData( + utxoForStoringDatum.address, + utxoForStoringDatum.outputData, + utxoForStoringDatum.assets + ); + } + } + + return await tx.complete(); } async buildWithdrawTx(options: BuildWithdrawTxOptions): Promise { diff --git a/src/utils/tx.internal.ts b/src/utils/tx.internal.ts new file mode 100644 index 0000000..e8456c8 --- /dev/null +++ b/src/utils/tx.internal.ts @@ -0,0 +1,35 @@ +import { Address, Assets, Lucid, OutputData } from "lucid-cardano"; + +/** + * Return a Output that pay back to @sender and include @datum + * This function is used for @receiver of an order can be a script + * @param lucid + * @param sender + * @param receiver + * @param datum + */ +export function buildUtxoToStoreDatum( + lucid: Lucid, + sender: Address, + receiver: Address, + datum: string +): { + address: Address; + outputData: OutputData; + assets: Assets; +} | null { + const receivePaymentCred = + lucid.utils.getAddressDetails(receiver).paymentCredential; + // If receiver is not a script address, we no need to store this datum On-chain because it's useless + if (!receivePaymentCred || receivePaymentCred.type === "Key") { + return null; + } + + return { + address: sender, + assets: {}, + outputData: { + inline: datum, + }, + }; +} \ No newline at end of file