|
| 1 | +import { KwilSigner, NodeKwil, WebKwil, Types } from "@trufnetwork/kwil-js"; |
| 2 | +import { TransactionEvent, FeeDistribution, GetTransactionEventInput } from "../types/transaction"; |
| 3 | + |
| 4 | +/** |
| 5 | + * Database row structure returned from get_transaction_event action |
| 6 | + */ |
| 7 | +interface TransactionEventRow { |
| 8 | + tx_id: string; |
| 9 | + block_height: string | number; |
| 10 | + method: string; |
| 11 | + caller: string; |
| 12 | + fee_amount: string | number; |
| 13 | + fee_recipient?: string | null; |
| 14 | + metadata?: string | null; |
| 15 | + fee_distributions: string; |
| 16 | +} |
| 17 | + |
| 18 | +/** |
| 19 | + * TransactionAction provides methods for querying transaction ledger data |
| 20 | + */ |
| 21 | +export class TransactionAction { |
| 22 | + protected kwilClient: WebKwil | NodeKwil; |
| 23 | + protected kwilSigner: KwilSigner; |
| 24 | + |
| 25 | + constructor(kwilClient: WebKwil | NodeKwil, kwilSigner: KwilSigner) { |
| 26 | + this.kwilClient = kwilClient; |
| 27 | + this.kwilSigner = kwilSigner; |
| 28 | + } |
| 29 | + |
| 30 | + /** |
| 31 | + * Fetches detailed transaction information by transaction hash |
| 32 | + * |
| 33 | + * @param input Transaction query input containing tx hash |
| 34 | + * @returns Promise resolving to transaction event with fee details |
| 35 | + * @throws Error if transaction not found or query fails |
| 36 | + * |
| 37 | + * @example |
| 38 | + * ```typescript |
| 39 | + * const txAction = client.loadTransactionAction(); |
| 40 | + * const txEvent = await txAction.getTransactionEvent({ |
| 41 | + * txId: "0xabcdef123456..." |
| 42 | + * }); |
| 43 | + * console.log(`Method: ${txEvent.method}, Fee: ${txEvent.feeAmount} TRUF`); |
| 44 | + * ``` |
| 45 | + */ |
| 46 | + async getTransactionEvent(input: GetTransactionEventInput): Promise<TransactionEvent> { |
| 47 | + if (!input.txId || input.txId.trim() === "") { |
| 48 | + throw new Error("tx_id is required"); |
| 49 | + } |
| 50 | + |
| 51 | + const result = await this.kwilClient.call( |
| 52 | + { |
| 53 | + namespace: "main", |
| 54 | + name: "get_transaction_event", |
| 55 | + inputs: { |
| 56 | + $tx_id: input.txId, |
| 57 | + }, |
| 58 | + }, |
| 59 | + this.kwilSigner |
| 60 | + ); |
| 61 | + |
| 62 | + if (result.status !== 200) { |
| 63 | + throw new Error(`Failed to get transaction event: HTTP ${result.status}`); |
| 64 | + } |
| 65 | + |
| 66 | + if (!result.data?.result || result.data.result.length === 0) { |
| 67 | + throw new Error(`Transaction not found: ${input.txId}`); |
| 68 | + } |
| 69 | + |
| 70 | + const row = result.data.result[0] as TransactionEventRow; |
| 71 | + |
| 72 | + // Validate required fields |
| 73 | + if (!row.method || typeof row.method !== 'string' || row.method.trim() === '') { |
| 74 | + throw new Error(`Missing or invalid method field (tx: ${row.tx_id})`); |
| 75 | + } |
| 76 | + |
| 77 | + if (!row.caller || typeof row.caller !== 'string' || row.caller.trim() === '') { |
| 78 | + throw new Error(`Missing or invalid caller field (tx: ${row.tx_id})`); |
| 79 | + } |
| 80 | + |
| 81 | + if (row.fee_amount === null || row.fee_amount === undefined) { |
| 82 | + throw new Error(`Missing fee_amount field (tx: ${row.tx_id})`); |
| 83 | + } |
| 84 | + |
| 85 | + // Validate fee_amount is numeric (can be string or number) |
| 86 | + const feeAmount = typeof row.fee_amount === 'string' ? row.fee_amount : String(row.fee_amount); |
| 87 | + const feeAmountNum = Number(feeAmount); |
| 88 | + if (isNaN(feeAmountNum) || !Number.isFinite(feeAmountNum)) { |
| 89 | + throw new Error(`Invalid fee_amount (not numeric): ${row.fee_amount} (tx: ${row.tx_id})`); |
| 90 | + } |
| 91 | + if (feeAmountNum < 0) { |
| 92 | + throw new Error(`Invalid fee_amount (negative): ${row.fee_amount} (tx: ${row.tx_id})`); |
| 93 | + } |
| 94 | + |
| 95 | + // Parse fee_distributions string: "recipient1:amount1,recipient2:amount2" |
| 96 | + const feeDistributions: FeeDistribution[] = []; |
| 97 | + if (row.fee_distributions && row.fee_distributions !== "") { |
| 98 | + const parts = row.fee_distributions.split(","); |
| 99 | + for (const part of parts) { |
| 100 | + const trimmedPart = part.trim(); |
| 101 | + if (trimmedPart) { |
| 102 | + // Split only on first colon to handle addresses with colons |
| 103 | + const colonIndex = trimmedPart.indexOf(":"); |
| 104 | + if (colonIndex === -1) { |
| 105 | + throw new Error(`Invalid fee distribution format (missing colon): ${trimmedPart} (tx: ${row.tx_id})`); |
| 106 | + } |
| 107 | + |
| 108 | + const recipient = trimmedPart.substring(0, colonIndex).trim(); |
| 109 | + const amount = trimmedPart.substring(colonIndex + 1).trim(); |
| 110 | + |
| 111 | + if (!recipient || !amount) { |
| 112 | + throw new Error(`Invalid fee distribution entry (empty recipient or amount): ${trimmedPart} (tx: ${row.tx_id})`); |
| 113 | + } |
| 114 | + |
| 115 | + // Validate amount is numeric and non-negative |
| 116 | + const amt = Number(amount); |
| 117 | + if (isNaN(amt) || !Number.isFinite(amt)) { |
| 118 | + throw new Error(`Invalid fee distribution amount (not numeric): ${amount} (tx: ${row.tx_id})`); |
| 119 | + } |
| 120 | + if (amt < 0) { |
| 121 | + throw new Error(`Invalid fee distribution amount (negative): ${amount} (tx: ${row.tx_id})`); |
| 122 | + } |
| 123 | + |
| 124 | + feeDistributions.push({ recipient, amount }); |
| 125 | + } |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + // Validate block height |
| 130 | + const blockHeight = typeof row.block_height === 'number' |
| 131 | + ? row.block_height |
| 132 | + : parseInt(row.block_height, 10); |
| 133 | + if (!Number.isFinite(blockHeight) || blockHeight < 0) { |
| 134 | + throw new Error(`Invalid block height: ${row.block_height} (tx: ${row.tx_id})`); |
| 135 | + } |
| 136 | + |
| 137 | + return { |
| 138 | + txId: row.tx_id, |
| 139 | + blockHeight, |
| 140 | + method: row.method, |
| 141 | + caller: row.caller, |
| 142 | + feeAmount, |
| 143 | + feeRecipient: row.fee_recipient || undefined, |
| 144 | + metadata: row.metadata || undefined, |
| 145 | + feeDistributions, |
| 146 | + }; |
| 147 | + } |
| 148 | + |
| 149 | + /** |
| 150 | + * Creates a TransactionAction instance from an existing client and signer |
| 151 | + * |
| 152 | + * @param kwilClient The Kwil client (Web or Node) |
| 153 | + * @param kwilSigner The Kwil signer for authentication |
| 154 | + * @returns A new TransactionAction instance |
| 155 | + */ |
| 156 | + static fromClient(kwilClient: WebKwil | NodeKwil, kwilSigner: KwilSigner): TransactionAction { |
| 157 | + return new TransactionAction(kwilClient, kwilSigner); |
| 158 | + } |
| 159 | +} |
0 commit comments