@xmr-core/xmr-mymonero-libs
Version:
A collection of utilities for building ontop of a MyMonero compatible API
202 lines (173 loc) • 4.82 kB
text/typescript
import {
AddressTransactions,
AddressTransactionsTx,
NormalizedTransaction,
SpentOutput,
AddressInfo,
} from "./types";
import { Omit } from "../../types";
import { BigInt } from "@xmr-core/biginteger";
import { formatMoney } from "@xmr-core/xmr-money";
import { Output } from "@xmr-core/xmr-transaction";
export function isKeyImageEqual({ key_image }: SpentOutput, keyImage: string) {
return key_image === keyImage;
}
//#region addressInfo
export function normalizeAddressInfo(data: AddressInfo): Required<AddressInfo> {
data.spent_outputs = data.spent_outputs || [];
const defaultObj: Required<AddressInfo> = {
total_sent: "0",
total_received: "0",
locked_funds: "0",
start_height: 0,
scanned_block_height: 0,
scanned_height: 0,
transaction_height: 0,
blockchain_height: 0,
rates: {},
spent_outputs: [] as SpentOutput[],
};
return { ...defaultObj, ...data };
}
//#endregion addressInfo
//#region parseAddressTransactions
export function normalizeAddressTransactions(
data: AddressTransactions,
): Required<AddressTransactions> {
data.transactions = data.transactions || [];
const defaultObj: Required<AddressTransactions> = {
scanned_height: 0,
scanned_block_height: 0,
start_height: 0,
transaction_height: 0,
blockchain_height: 0,
transactions: [] as AddressTransactionsTx[],
total_received: "0",
};
return { ...defaultObj, ...data };
}
export function normalizeTransaction(
tx: AddressTransactionsTx,
): NormalizedTransaction {
tx.spent_outputs = tx.spent_outputs || [];
const defaultObj: Omit<NormalizedTransaction, "timestamp"> = {
amount: new BigInt(0),
approx_float_amount: 0,
hash: "",
height: 0,
id: 0,
mempool: false,
coinbase: false,
mixin: 0,
spent_outputs: [] as SpentOutput[],
total_received: "0",
total_sent: new BigInt(0),
unlock_time: 0,
payment_id: "",
};
const mergedObj = {
...defaultObj,
...tx,
total_sent: tx.total_sent
? new BigInt(tx.total_sent)
: defaultObj.total_sent,
timestamp: new Date(tx.timestamp),
};
return mergedObj;
}
/**
*
* @description Validates that the sum of total received and total sent is greater than 1
* @param {NormalizedTransaction} { total_received, total_sent}
*/
export function zeroTransactionAmount({
total_received,
total_sent,
}: NormalizedTransaction) {
return new BigInt(total_received).add(total_sent).compare(0) <= 0;
}
export function calculateTransactionAmount({
total_received,
total_sent,
}: NormalizedTransaction) {
return new BigInt(total_received).subtract(total_sent);
}
export function estimateTransactionAmount({ amount }: NormalizedTransaction) {
return parseFloat(formatMoney(amount));
}
/**
*
* @description If the transaction is:
* 1. An outgoing transaction, e.g. it is sending moneroj to an address rather than receiving it
* 2. And contains an encrypted (8 byte) payment id
*
* Then, it is removed from the transaction object, because the server (MyMonero) can't filter out short (encrypted) pids on outgoing txs
* @export
* @param {NormalizedTransaction} tx
*/
export function removeEncryptedPaymentIDs(tx: NormalizedTransaction) {
const { payment_id, approx_float_amount } = tx;
const outgoingTxWithEncPid =
payment_id && payment_id.length === 16 && approx_float_amount < 0;
if (outgoingTxWithEncPid) {
delete tx.payment_id;
}
}
/**
* @description Sort transactions based on the following cases where higher priorities
* gain a lower index value:
*
* 1. Transactions that are in the mempool gain priority
* 2. Otherwise, sort by id, where the higher ID has higher priority
*
* @export
* @param {AddressTransactionsTx[]} transactions
* @returns
*/
export function sortTransactions(transactions: NormalizedTransaction[]) {
return transactions.sort((a, b) => {
if (a.mempool && !b.mempool) {
return -1; // a first
} else if (b.mempool) {
return 1; // b first
}
// both mempool - fall back to .id compare
return b.id - a.id;
});
}
//#endregion parseAddressTransactions
//#region parseUnspentOuts
export function validateUnspentOutput(out: Output, index: number) {
if (!out) {
throw Error(`unspent_output at index ${index} was null`);
}
}
export function validateSpendKeyImages(keyImgs: string[], index: number) {
if (!keyImgs) {
throw Error(
`spend_key_images of unspent_output at index ${index} was null`,
);
}
}
export function validUnspentOutput(out: Output, index: number) {
if (out) {
return true;
} else {
console.warn(
`This unspent output at i ${index} was undefined! Skipping.`,
);
return false;
}
}
export function validTxPubKey(out: Output) {
if (out.tx_pub_key) {
return true;
} else {
console.warn(
"This unspent out was missing a tx_pub_key! Skipping.",
out,
);
return false;
}
}
//#endregion parseUnspentOuts