From 7eede71ad0fc57bd0195b36d49b878a11205f615 Mon Sep 17 00:00:00 2001
From: Pavlo Syrotyna
Date: Tue, 3 Feb 2026 11:18:46 +0200
Subject: [PATCH] feat(blockchain-link): change stake type
---
packages/blockchain-link-types/src/common.ts | 9 +++-
.../blockchain-link-utils/src/blockfrost.ts | 52 +++++++++++++++----
2 files changed, 50 insertions(+), 11 deletions(-)
diff --git a/packages/blockchain-link-types/src/common.ts b/packages/blockchain-link-types/src/common.ts
index 008858346d..2ed30a29bb 100644
--- a/packages/blockchain-link-types/src/common.ts
+++ b/packages/blockchain-link-types/src/common.ts
@@ -153,7 +153,12 @@ export interface Transaction {
ethereumSpecific?: BlockbookTransaction['ethereumSpecific'];
internalTransfers: InternalTransfer[];
cardanoSpecific?: {
- subtype?: 'withdrawal' | 'stake_delegation' | 'stake_registration' | 'stake_deregistration';
+ subtype?:
+ | 'withdrawal'
+ | 'stake_delegation'
+ | 'stake_registration'
+ | 'stake_deregistration'
+ | 'governance_delegation';
withdrawal?: string;
deposit?: string;
};
@@ -314,7 +319,7 @@ export interface SubscriptionAccountInfo {
export type ChannelMessage = T & { id: number };
-export type StakeType = 'stake' | 'unstake' | 'claim';
+export type StakeType = 'stake' | 'unstake' | 'claim' | 'change-delegate';
export type TokenDetailByMint = {
[mint: string]: {
diff --git a/packages/blockchain-link-utils/src/blockfrost.ts b/packages/blockchain-link-utils/src/blockfrost.ts
index f7ef37c7ad..a6be175ab6 100644
--- a/packages/blockchain-link-utils/src/blockfrost.ts
+++ b/packages/blockchain-link-utils/src/blockfrost.ts
@@ -50,18 +50,47 @@ const hexToString = (input: string): string => {
return str;
};
-const getSubtype = (tx: Pick) => {
- const withdrawal = tx.txData.withdrawal_count > 0;
+const getSubtype = (
+ tx: Pick,
+ totalInput: BigNumberValue,
+ totalOutput: BigNumberValue,
+ allOutputsAreChange: boolean,
+) => {
+ const { withdrawal_count, stake_cert_count, delegation_count, deposit, fees } = tx.txData;
+
+ const withdrawal = withdrawal_count > 0;
if (withdrawal) {
return 'withdrawal';
}
- const registrations = tx.txData.stake_cert_count;
- const delegations = tx.txData.delegation_count;
- if (registrations === 0 && delegations === 0) return;
+ // governance_delegation is detected heuristically.
+ // Blockfrost txData does not expose governance (DRep) certificates, so we infer it as:
+ // - no withdrawals
+ // - no stake or pool delegation certificates
+ // - zero deposit
+ // - self transaction where totalInput === totalOutput + fee
+ // - all outputs go to change addresses (no value transfer)
+ // - non-zero fee
+ // This may still misclassify rare fee-only self transactions.
+ if (
+ withdrawal_count === 0 &&
+ stake_cert_count === 0 &&
+ delegation_count === 0 &&
+ new BigNumber(deposit || 0).isZero()
+ ) {
+ const fee = new BigNumber(fees || 0);
+ const isFeeOnly =
+ fee.gt(0) && new BigNumber(totalInput).eq(new BigNumber(totalOutput).plus(fee));
- if (registrations > 0) {
- if (new BigNumber(tx.txData.deposit).gt(0)) {
+ if (isFeeOnly && allOutputsAreChange) {
+ return 'governance_delegation';
+ }
+ }
+
+ if (stake_cert_count === 0 && delegation_count === 0) return;
+
+ if (stake_cert_count > 0) {
+ if (new BigNumber(deposit).gt(0)) {
// transaction could both register staking address and delegate stake at the same time. In that case we treat it as "stake registration"
return 'stake_registration';
}
@@ -69,7 +98,7 @@ const getSubtype = (tx: Pick) => {
return 'stake_deregistration';
}
- if (delegations > 0) {
+ if (delegation_count > 0) {
return 'stake_delegation';
}
};
@@ -225,6 +254,11 @@ export const transformTransaction = (
const internal = accountAddress ? filterTargets(accountAddress.change, outputs) : [];
const totalInput = inputs.reduce(sumVinVout, 0);
const totalOutput = outputs.reduce(sumVinVout, 0);
+ const allOutputsAreChange =
+ fullData &&
+ blockfrostTxData.txUtxos.outputs.every(o =>
+ accountAddress?.change.some(c => c.address === o.address),
+ );
if (outgoing.length === 0 && incoming.length === 0) {
type = 'unknown';
@@ -295,7 +329,7 @@ export const transformTransaction = (
tokens,
internalTransfers: [],
cardanoSpecific: {
- subtype: getSubtype(blockfrostTxData),
+ subtype: getSubtype(blockfrostTxData, totalInput, totalOutput, allOutputsAreChange),
withdrawal,
deposit,
},