From 576c734a53219212c433a03e92a28713c0937cb1 Mon Sep 17 00:00:00 2001 From: "a.dacapo21" Date: Mon, 16 Mar 2026 23:18:01 +0200 Subject: [PATCH 1/2] feat: add CIP-20 transaction metadata to all write tools Attach CIP-20 (label 674) human-readable metadata to every unsigned transaction built through buildUnsignedTx. Each transaction now includes "Indigo Protocol: {type}" and a description line, making protocol operations identifiable on-chain via block explorers. Centralize .complete() in tx-builder.ts so metadata is attached after the TxBuilder is returned from each tool's buildFn callback but before finalization. Remove .complete() calls from all 27 write tool callbacks across 10 files. --- src/tools/cdp-liquidation-tools.ts | 12 ++++------ src/tools/cdp-mint-burn-tools.ts | 6 ++--- src/tools/cdp-write-tools.ts | 12 ++++------ src/tools/leverage-cdp-tools.ts | 3 +-- src/tools/rob-write-tools.ts | 15 +++++-------- src/tools/sp-request-tools.ts | 6 ++--- src/tools/stability-pool-write-tools.ts | 9 +++----- src/tools/staking-reward-tools.ts | 3 +-- src/tools/staking-write-tools.ts | 9 +++----- src/utils/tx-builder.ts | 30 ++++++++++++++++++++++--- 10 files changed, 52 insertions(+), 53 deletions(-) diff --git a/src/tools/cdp-liquidation-tools.ts b/src/tools/cdp-liquidation-tools.ts index 9131cb5..2d00fee 100644 --- a/src/tools/cdp-liquidation-tools.ts +++ b/src/tools/cdp-liquidation-tools.ts @@ -119,7 +119,7 @@ export function registerCdpLiquidationTools(server: McpServer): void { findTreasuryUtxo(params, lucid), ]); - const txBuilder = await liquidateCdp( + return liquidateCdp( cdpOutRef, stabilityPoolUtxo, collectorUtxo, @@ -127,7 +127,6 @@ export function registerCdpLiquidationTools(server: McpServer): void { params, lucid ); - return txBuilder.complete(); }, { type: 'liquidate_cdp', @@ -187,7 +186,7 @@ export function registerCdpLiquidationTools(server: McpServer): void { findInterestOracleUtxo(iAssetResult.datum, lucid), ]); - const txBuilder = await redeemCdp( + return redeemCdp( BigInt(amount), cdpOutRef, iAssetResult.utxo, @@ -199,7 +198,6 @@ export function registerCdpLiquidationTools(server: McpServer): void { lucid, currentSlot ); - return txBuilder.complete(); }, { type: 'redeem_cdp', @@ -250,7 +248,7 @@ export function registerCdpLiquidationTools(server: McpServer): void { findInterestOracleUtxo(iAssetResult.datum, lucid), ]); - const txBuilder = await freezeCdp( + return freezeCdp( cdpOutRef, iAssetResult.utxo, priceOracleUtxo, @@ -259,7 +257,6 @@ export function registerCdpLiquidationTools(server: McpServer): void { lucid, currentSlot ); - return txBuilder.complete(); }, { type: 'freeze_cdp', @@ -307,8 +304,7 @@ export function registerCdpLiquidationTools(server: McpServer): void { async (lucid) => { const params = await getSystemParams(); - const txBuilder = await mergeCdps(cdpOutRefs, params, lucid); - return txBuilder.complete(); + return mergeCdps(cdpOutRefs, params, lucid); }, { type: 'merge_cdps', diff --git a/src/tools/cdp-mint-burn-tools.ts b/src/tools/cdp-mint-burn-tools.ts index bf1622f..542e7e2 100644 --- a/src/tools/cdp-mint-burn-tools.ts +++ b/src/tools/cdp-mint-burn-tools.ts @@ -131,7 +131,7 @@ export function registerCdpMintBurnTools(server: McpServer): void { findInterestOracleUtxo(iAssetUtxo, lucid), ]); - const txBuilder = await mintCdp( + return mintCdp( BigInt(amount), cdpOutRef, iAssetUtxo, @@ -144,7 +144,6 @@ export function registerCdpMintBurnTools(server: McpServer): void { lucid, currentSlot ); - return txBuilder.complete(); }, { type: 'mint_cdp', @@ -201,7 +200,7 @@ export function registerCdpMintBurnTools(server: McpServer): void { findInterestOracleUtxo(iAssetUtxo, lucid), ]); - const txBuilder = await burnCdp( + return burnCdp( BigInt(amount), cdpOutRef, iAssetUtxo, @@ -214,7 +213,6 @@ export function registerCdpMintBurnTools(server: McpServer): void { lucid, currentSlot ); - return txBuilder.complete(); }, { type: 'burn_cdp', diff --git a/src/tools/cdp-write-tools.ts b/src/tools/cdp-write-tools.ts index 4f9b666..9d726c4 100644 --- a/src/tools/cdp-write-tools.ts +++ b/src/tools/cdp-write-tools.ts @@ -157,7 +157,7 @@ export function registerCdpWriteTools(server: McpServer): void { findInterestOracleUtxo(iAssetResult.datum, lucid), ]); - const txBuilder = await openCdp( + return openCdp( BigInt(collateralAmount), BigInt(mintAmount), params, @@ -169,7 +169,6 @@ export function registerCdpWriteTools(server: McpServer): void { lucid, currentSlot ); - return txBuilder.complete(); }, { type: 'open_cdp', @@ -226,7 +225,7 @@ export function registerCdpWriteTools(server: McpServer): void { findInterestOracleUtxo(iAssetResult.datum, lucid), ]); - const txBuilder = await depositCdp( + return depositCdp( BigInt(amount), cdpOutRef, iAssetResult.utxo, @@ -239,7 +238,6 @@ export function registerCdpWriteTools(server: McpServer): void { lucid, currentSlot ); - return txBuilder.complete(); }, { type: 'deposit_cdp', @@ -296,7 +294,7 @@ export function registerCdpWriteTools(server: McpServer): void { findInterestOracleUtxo(iAssetResult.datum, lucid), ]); - const txBuilder = await withdrawCdp( + return withdrawCdp( BigInt(amount), cdpOutRef, iAssetResult.utxo, @@ -309,7 +307,6 @@ export function registerCdpWriteTools(server: McpServer): void { lucid, currentSlot ); - return txBuilder.complete(); }, { type: 'withdraw_cdp', @@ -365,7 +362,7 @@ export function registerCdpWriteTools(server: McpServer): void { findInterestOracleUtxo(iAssetResult.datum, lucid), ]); - const txBuilder = await closeCdp( + return closeCdp( cdpOutRef, iAssetResult.utxo, priceOracleUtxo, @@ -377,7 +374,6 @@ export function registerCdpWriteTools(server: McpServer): void { lucid, currentSlot ); - return txBuilder.complete(); }, { type: 'close_cdp', diff --git a/src/tools/leverage-cdp-tools.ts b/src/tools/leverage-cdp-tools.ts index fc91c7d..ffd2089 100644 --- a/src/tools/leverage-cdp-tools.ts +++ b/src/tools/leverage-cdp-tools.ts @@ -151,7 +151,7 @@ export function registerLeverageCdpTools(server: McpServer): void { findInterestOracleUtxo(iAssetResult.datum, lucid), ]); - const txBuilder = await leverageCdpWithLrp( + return leverageCdpWithLrp( leverage, BigInt(baseCollateral), priceOracleUtxo, @@ -164,7 +164,6 @@ export function registerLeverageCdpTools(server: McpServer): void { allLrps, currentSlot ); - return txBuilder.complete(); }, { type: 'leverage_cdp', diff --git a/src/tools/rob-write-tools.ts b/src/tools/rob-write-tools.ts index 92b1f5c..d4e99bf 100644 --- a/src/tools/rob-write-tools.ts +++ b/src/tools/rob-write-tools.ts @@ -34,14 +34,13 @@ export function registerRobWriteTools(server: McpServer): void { const params = await getSystemParams(); const assetTokenName = fromText(asset); const maxPriceDecimal = parseMaxPrice(maxPrice); - const txBuilder = await openLrp( + return openLrp( assetTokenName, BigInt(lovelacesAmount), maxPriceDecimal, lucid, params ); - return txBuilder.complete(); }, { type: 'open_rob', @@ -81,8 +80,7 @@ export function registerRobWriteTools(server: McpServer): void { async (lucid) => { const params = await getSystemParams(); const robOutRef = { txHash: robTxHash, outputIndex: robOutputIndex }; - const txBuilder = await cancelLrp(robOutRef, params, lucid); - return txBuilder.complete(); + return cancelLrp(robOutRef, params, lucid); }, { type: 'cancel_rob', @@ -131,14 +129,13 @@ export function registerRobWriteTools(server: McpServer): void { const robOutRef = { txHash: robTxHash, outputIndex: robOutputIndex }; const newMaxPriceDecimal = newMaxPrice !== undefined ? parseMaxPrice(newMaxPrice) : undefined; - const txBuilder = await adjustLrp( + return adjustLrp( lucid, robOutRef, BigInt(lovelacesAdjustAmount), newMaxPriceDecimal, params ); - return txBuilder.complete(); }, { type: 'adjust_rob', @@ -184,8 +181,7 @@ export function registerRobWriteTools(server: McpServer): void { async (lucid) => { const params = await getSystemParams(); const robOutRef = { txHash: robTxHash, outputIndex: robOutputIndex }; - const txBuilder = await claimLrp(lucid, robOutRef, params); - return txBuilder.complete(); + return claimLrp(lucid, robOutRef, params); }, { type: 'claim_rob', @@ -255,14 +251,13 @@ export function registerRobWriteTools(server: McpServer): void { txHash: iassetTxHash, outputIndex: iassetOutputIndex, }; - const txBuilder = await redeemLrp( + return redeemLrp( redemptionRobsData, priceOracleOutRef, iassetOutRef, lucid, params ); - return txBuilder.complete(); }, { type: 'redeem_rob', diff --git a/src/tools/sp-request-tools.ts b/src/tools/sp-request-tools.ts index 99d0d33..771434d 100644 --- a/src/tools/sp-request-tools.ts +++ b/src/tools/sp-request-tools.ts @@ -106,7 +106,7 @@ export function registerSpRequestTools(server: McpServer): void { findCollectorUtxo(params, lucid), ]); - const txBuilder = await processSpRequest( + return processSpRequest( asset, stabilityPoolUtxo, accountUtxo, @@ -117,7 +117,6 @@ export function registerSpRequestTools(server: McpServer): void { lucid, collectorUtxo ); - return txBuilder.complete(); }, { type: 'process_sp_request', @@ -169,8 +168,7 @@ export function registerSpRequestTools(server: McpServer): void { if (!accountUtxo) throw new Error('Account UTxO not found on chain'); const params = await getSystemParams(); - const txBuilder = await annulRequest(accountUtxo, params, lucid); - return txBuilder.complete(); + return annulRequest(accountUtxo, params, lucid); }, { type: 'annul_sp_request', diff --git a/src/tools/stability-pool-write-tools.ts b/src/tools/stability-pool-write-tools.ts index 1becbe5..ffa12d7 100644 --- a/src/tools/stability-pool-write-tools.ts +++ b/src/tools/stability-pool-write-tools.ts @@ -20,8 +20,7 @@ export function registerStabilityPoolWriteTools(server: McpServer): void { address, async (lucid) => { const params = await getSystemParams(); - const txBuilder = await createSpAccount(asset, BigInt(amount), params, lucid); - return txBuilder.complete(); + return createSpAccount(asset, BigInt(amount), params, lucid); }, { type: 'create_sp_account', @@ -70,14 +69,13 @@ export function registerStabilityPoolWriteTools(server: McpServer): void { if (!accountUtxo) throw new Error('Account UTxO not found on chain'); const params = await getSystemParams(); - const txBuilder = await adjustSpAccount( + return adjustSpAccount( asset, BigInt(amount), accountUtxo, params, lucid ); - return txBuilder.complete(); }, { type: 'adjust_sp_account', @@ -128,8 +126,7 @@ export function registerStabilityPoolWriteTools(server: McpServer): void { if (!accountUtxo) throw new Error('Account UTxO not found on chain'); const params = await getSystemParams(); - const txBuilder = await closeSpAccount(accountUtxo, params, lucid); - return txBuilder.complete(); + return closeSpAccount(accountUtxo, params, lucid); }, { type: 'close_sp_account', diff --git a/src/tools/staking-reward-tools.ts b/src/tools/staking-reward-tools.ts index ad5b585..cc8520b 100644 --- a/src/tools/staking-reward-tools.ts +++ b/src/tools/staking-reward-tools.ts @@ -26,13 +26,12 @@ export function registerStakingRewardTools(server: McpServer): void { async (lucid) => { const params = await getSystemParams(); const stakingManagerOutput = await findStakingManager(params, lucid); - const txBuilder = await distributeAda( + return distributeAda( stakingManagerOutput.utxo, collectorTxHashes, params, lucid ); - return txBuilder.complete(); }, { type: 'distribute_staking_rewards', diff --git a/src/tools/staking-write-tools.ts b/src/tools/staking-write-tools.ts index 3fc57ca..59319b9 100644 --- a/src/tools/staking-write-tools.ts +++ b/src/tools/staking-write-tools.ts @@ -24,13 +24,12 @@ export function registerStakingWriteTools(server: McpServer): void { async (lucid) => { const params = await getSystemParams(); const stakingManagerOutput = await findStakingManager(params, lucid); - const txBuilder = await openStakingPosition( + return openStakingPosition( BigInt(amount), params, lucid, stakingManagerOutput.utxo ); - return txBuilder.complete(); }, { type: 'open_staking_position', @@ -78,7 +77,7 @@ export function registerStakingWriteTools(server: McpServer): void { outputIndex: positionOutputIndex, }; const currentSlot = lucid.currentSlot(); - const txBuilder = await adjustStakingPosition( + return adjustStakingPosition( positionOutRef, BigInt(amount), params, @@ -86,7 +85,6 @@ export function registerStakingWriteTools(server: McpServer): void { currentSlot, stakingManagerOutput.utxo ); - return txBuilder.complete(); }, { type: 'adjust_staking_position', @@ -136,14 +134,13 @@ export function registerStakingWriteTools(server: McpServer): void { outputIndex: positionOutputIndex, }; const currentSlot = lucid.currentSlot(); - const txBuilder = await closeStakingPosition( + return closeStakingPosition( positionOutRef, params, lucid, currentSlot, stakingManagerOutput.utxo ); - return txBuilder.complete(); }, { type: 'close_staking_position', diff --git a/src/utils/tx-builder.ts b/src/utils/tx-builder.ts index f6f6ab6..a2bb25b 100644 --- a/src/utils/tx-builder.ts +++ b/src/utils/tx-builder.ts @@ -1,10 +1,28 @@ -import type { LucidEvolution, TxSignBuilder } from '@lucid-evolution/lucid'; +import type { LucidEvolution, TxBuilder } from '@lucid-evolution/lucid'; import type { UnsignedTxResult, TxSummary } from '../types/tx-types.js'; import { getLucid } from './lucid-provider.js'; +/** + * CIP-20 metadata label for transaction messages. + * See: https://cips.cardano.org/cip/CIP-20 + */ +const CIP20_METADATA_LABEL = 674; + +/** + * Build CIP-20 metadata message lines from a TxSummary. + * Each line is capped at 64 bytes (CIP-20 requirement). + */ +function buildCip20Message(summary: TxSummary): string[] { + const lines: string[] = [ + `Indigo Protocol: ${summary.type}`, + summary.description, + ]; + return lines.map((line) => (line.length > 64 ? line.slice(0, 64) : line)); +} + export async function buildUnsignedTx( address: string, - buildFn: (lucid: LucidEvolution) => Promise, + buildFn: (lucid: LucidEvolution) => Promise, summary: TxSummary ): Promise { const lucid = await getLucid(); @@ -12,7 +30,13 @@ export async function buildUnsignedTx( const utxos = await lucid.utxosAt(address); lucid.selectWallet.fromAddress(address, utxos); - const tx = await buildFn(lucid); + const txBuilder = await buildFn(lucid); + + txBuilder.attachMetadata(CIP20_METADATA_LABEL, { + msg: buildCip20Message(summary), + }); + + const tx = await txBuilder.complete(); return { unsignedTx: tx.toCBOR(), From 1c0d4151570612a295d23b85a8150aca56174a07 Mon Sep 17 00:00:00 2001 From: "a.dacapo21" Date: Mon, 16 Mar 2026 23:33:05 +0200 Subject: [PATCH 2/2] style: fix Prettier formatting in modified files --- src/tools/cdp-mint-burn-tools.ts | 2 +- src/tools/rob-write-tools.ts | 16 ++-------------- src/tools/stability-pool-write-tools.ts | 8 +------- src/tools/staking-reward-tools.ts | 7 +------ src/tools/staking-write-tools.ts | 7 +------ src/utils/tx-builder.ts | 5 +---- 6 files changed, 7 insertions(+), 38 deletions(-) diff --git a/src/tools/cdp-mint-burn-tools.ts b/src/tools/cdp-mint-burn-tools.ts index 542e7e2..4a29c8f 100644 --- a/src/tools/cdp-mint-burn-tools.ts +++ b/src/tools/cdp-mint-burn-tools.ts @@ -131,7 +131,7 @@ export function registerCdpMintBurnTools(server: McpServer): void { findInterestOracleUtxo(iAssetUtxo, lucid), ]); - return mintCdp( + return mintCdp( BigInt(amount), cdpOutRef, iAssetUtxo, diff --git a/src/tools/rob-write-tools.ts b/src/tools/rob-write-tools.ts index d4e99bf..2cb9ba7 100644 --- a/src/tools/rob-write-tools.ts +++ b/src/tools/rob-write-tools.ts @@ -34,13 +34,7 @@ export function registerRobWriteTools(server: McpServer): void { const params = await getSystemParams(); const assetTokenName = fromText(asset); const maxPriceDecimal = parseMaxPrice(maxPrice); - return openLrp( - assetTokenName, - BigInt(lovelacesAmount), - maxPriceDecimal, - lucid, - params - ); + return openLrp(assetTokenName, BigInt(lovelacesAmount), maxPriceDecimal, lucid, params); }, { type: 'open_rob', @@ -251,13 +245,7 @@ export function registerRobWriteTools(server: McpServer): void { txHash: iassetTxHash, outputIndex: iassetOutputIndex, }; - return redeemLrp( - redemptionRobsData, - priceOracleOutRef, - iassetOutRef, - lucid, - params - ); + return redeemLrp(redemptionRobsData, priceOracleOutRef, iassetOutRef, lucid, params); }, { type: 'redeem_rob', diff --git a/src/tools/stability-pool-write-tools.ts b/src/tools/stability-pool-write-tools.ts index ffa12d7..4428619 100644 --- a/src/tools/stability-pool-write-tools.ts +++ b/src/tools/stability-pool-write-tools.ts @@ -69,13 +69,7 @@ export function registerStabilityPoolWriteTools(server: McpServer): void { if (!accountUtxo) throw new Error('Account UTxO not found on chain'); const params = await getSystemParams(); - return adjustSpAccount( - asset, - BigInt(amount), - accountUtxo, - params, - lucid - ); + return adjustSpAccount(asset, BigInt(amount), accountUtxo, params, lucid); }, { type: 'adjust_sp_account', diff --git a/src/tools/staking-reward-tools.ts b/src/tools/staking-reward-tools.ts index cc8520b..a9594d7 100644 --- a/src/tools/staking-reward-tools.ts +++ b/src/tools/staking-reward-tools.ts @@ -26,12 +26,7 @@ export function registerStakingRewardTools(server: McpServer): void { async (lucid) => { const params = await getSystemParams(); const stakingManagerOutput = await findStakingManager(params, lucid); - return distributeAda( - stakingManagerOutput.utxo, - collectorTxHashes, - params, - lucid - ); + return distributeAda(stakingManagerOutput.utxo, collectorTxHashes, params, lucid); }, { type: 'distribute_staking_rewards', diff --git a/src/tools/staking-write-tools.ts b/src/tools/staking-write-tools.ts index 59319b9..ce1343c 100644 --- a/src/tools/staking-write-tools.ts +++ b/src/tools/staking-write-tools.ts @@ -24,12 +24,7 @@ export function registerStakingWriteTools(server: McpServer): void { async (lucid) => { const params = await getSystemParams(); const stakingManagerOutput = await findStakingManager(params, lucid); - return openStakingPosition( - BigInt(amount), - params, - lucid, - stakingManagerOutput.utxo - ); + return openStakingPosition(BigInt(amount), params, lucid, stakingManagerOutput.utxo); }, { type: 'open_staking_position', diff --git a/src/utils/tx-builder.ts b/src/utils/tx-builder.ts index a2bb25b..9ba3409 100644 --- a/src/utils/tx-builder.ts +++ b/src/utils/tx-builder.ts @@ -13,10 +13,7 @@ const CIP20_METADATA_LABEL = 674; * Each line is capped at 64 bytes (CIP-20 requirement). */ function buildCip20Message(summary: TxSummary): string[] { - const lines: string[] = [ - `Indigo Protocol: ${summary.type}`, - summary.description, - ]; + const lines: string[] = [`Indigo Protocol: ${summary.type}`, summary.description]; return lines.map((line) => (line.length > 64 ? line.slice(0, 64) : line)); }