@ledgerhq/coin-cardano
Version:
Ledger Cardano Coin integration
288 lines • 10.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isProtocolParamsValid = exports.isValidNumString = exports.getBech32PoolId = exports.decodeTokenName = exports.isHexString = exports.getMemoFromTx = exports.getAccountChange = exports.isTestnet = exports.getOperationType = exports.getAccountStakeCredential = exports.getTokenDiff = exports.mergeTokens = exports.getEpoch = exports.getTTL = exports.getAbsoluteSlot = exports.isValidAddress = exports.getBaseAddress = exports.getCredentialKey = exports.getExtendedPublicKeyFromHex = exports.getBipPathString = exports.getBipPath = exports.isAlreadyStaking = exports.canStake = exports.getBipPathFromString = void 0;
const constants_1 = require("./constants");
const typhonjs_1 = require("@stricahq/typhonjs");
const types_1 = require("./types");
const bip32ed25519_1 = require("@stricahq/bip32ed25519");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const networks_1 = require("./networks");
const groupBy_1 = __importDefault(require("lodash/groupBy"));
const cryptoassets_1 = require("@ledgerhq/cryptoassets");
const ShelleyTypeAddress_1 = __importDefault(require("@stricahq/typhonjs/dist/address/ShelleyTypeAddress"));
const bech32_1 = __importDefault(require("bech32"));
/**
* returns BipPath object with account, chain and index field for cardano
*
* @param {string} path
*/
function getBipPathFromString(path) {
const regEx = new RegExp(`^${constants_1.CARDANO_PURPOSE}'/${constants_1.CARDANO_COIN_TYPE}'/(\\d*)'/([012])/(\\d*)`);
const result = path.match(regEx);
if (result === null) {
throw new Error("Invalid derivation path");
}
return getBipPath({
account: parseInt(result[1]),
chain: parseInt(result[2]),
index: parseInt(result[3]),
});
}
exports.getBipPathFromString = getBipPathFromString;
/**
*
* @returns true if the account can stake, false otherwise
*/
function canStake(account) {
return account.balance.gt(0);
}
exports.canStake = canStake;
/**
*
* @returns true if account is staked, false otherwise
*/
function isAlreadyStaking(account) {
return !!account.cardanoResources?.delegation?.poolId;
}
exports.isAlreadyStaking = isAlreadyStaking;
/**
* returns complete bipPath with purpose, coin, account, chain and index for cardano
*/
function getBipPath({ account, chain, index, }) {
return {
purpose: constants_1.CARDANO_PURPOSE,
coin: constants_1.CARDANO_COIN_TYPE,
account,
chain,
index,
};
}
exports.getBipPath = getBipPath;
/**
* returns bipPathString from account, chain and index for cardano
*/
function getBipPathString({ account, chain, index, }) {
return `${constants_1.CARDANO_PURPOSE}'/${constants_1.CARDANO_COIN_TYPE}'/${account}'/${chain}/${index}`;
}
exports.getBipPathString = getBipPathString;
function getExtendedPublicKeyFromHex(keyHex) {
return bip32ed25519_1.Bip32PublicKey.fromBytes(Buffer.from(keyHex, "hex"));
}
exports.getExtendedPublicKeyFromHex = getExtendedPublicKeyFromHex;
function getCredentialKey(accountKey, path) {
const keyBytes = accountKey.derive(path.chain).derive(path.index).toPublicKey().hash();
const pubKeyHex = keyBytes.toString("hex");
return {
key: pubKeyHex,
path,
};
}
exports.getCredentialKey = getCredentialKey;
/**
* returns cardano base address by paymentKey and stakeKey
*/
function getBaseAddress({ networkId, paymentCred, stakeCred, }) {
const paymentCredential = {
hash: Buffer.from(paymentCred.key, "hex"),
type: typhonjs_1.types.HashType.ADDRESS,
bipPath: paymentCred.path,
};
const stakeCredential = {
hash: Buffer.from(stakeCred.key, "hex"),
type: typhonjs_1.types.HashType.ADDRESS,
bipPath: stakeCred.path,
};
return new typhonjs_1.address.BaseAddress(networkId, paymentCredential, stakeCredential);
}
exports.getBaseAddress = getBaseAddress;
/**
* Returns true if address is a valid
*
* @param {string} address
*/
const isValidAddress = (address, networkId) => {
if (!address)
return false;
try {
const cardanoAddress = typhonjs_1.utils.getAddressFromString(address);
if (cardanoAddress instanceof ShelleyTypeAddress_1.default) {
const addressNetworkId = Number(cardanoAddress.getHex().toLowerCase().charAt(1));
if (addressNetworkId !== networkId) {
return false;
}
}
}
catch (error) {
return false;
}
return true;
};
exports.isValidAddress = isValidAddress;
const getAbsoluteSlot = function (networkName, time) {
const networkParams = (0, networks_1.getNetworkParameters)(networkName);
const byronChainEndSlots = networkParams.shelleyStartEpoch * networkParams.byronSlotsPerEpoch;
const byronChainEndTime = byronChainEndSlots * networkParams.byronSlotDuration;
const shelleyChainTime = time.getTime() - networkParams.chainStartTime - byronChainEndTime;
const shelleyChainSlots = Math.floor(shelleyChainTime / networkParams.shelleySlotDuration);
return byronChainEndSlots + shelleyChainSlots;
};
exports.getAbsoluteSlot = getAbsoluteSlot;
/**
* Returns the time to live for transaction
*
* @returns {number}
*/
function getTTL(networkName) {
return (0, exports.getAbsoluteSlot)(networkName, new Date()) + constants_1.TTL_GAP;
}
exports.getTTL = getTTL;
function getEpoch(networkName, time) {
const networkParams = (0, networks_1.getNetworkParameters)(networkName);
const chainTime = time.getTime() - networkParams.chainStartTime;
const epoch = Math.floor(chainTime / (networkParams.shelleySlotsPerEpoch * networkParams.shelleySlotDuration));
return epoch;
}
exports.getEpoch = getEpoch;
function mergeTokens(tokens) {
return Object.values((0, groupBy_1.default)(tokens, t => `${t.policyId}${t.assetName}`)).map(similarTokens => ({
policyId: similarTokens[0].policyId,
assetName: similarTokens[0].assetName,
amount: similarTokens.reduce((total, token) => total.plus(token.amount), new bignumber_js_1.default(0)),
}));
}
exports.mergeTokens = mergeTokens;
/**
* @param { Array<TyphonTypes.Token> } b
* @param { Array<TyphonTypes.Token> } a
* @returns a - b
*/
function getTokenDiff(a, b) {
return mergeTokens(a.concat(b.map(t => ({ ...t, amount: t.amount.negated() })))).filter(t => !t.amount.eq(0));
}
exports.getTokenDiff = getTokenDiff;
function getAccountStakeCredential(xpub, index) {
const accountXPubKey = getExtendedPublicKeyFromHex(xpub);
const keyPath = getCredentialKey(accountXPubKey, getBipPath({
account: index,
chain: types_1.StakeChain.stake,
index: constants_1.STAKING_ADDRESS_INDEX,
}));
return {
key: keyPath.key,
path: keyPath.path,
};
}
exports.getAccountStakeCredential = getAccountStakeCredential;
function getOperationType({ valueChange, fees, }) {
return valueChange.isNegative()
? valueChange.absoluteValue().eq(fees)
? "FEES"
: "OUT"
: valueChange.isPositive()
? "IN"
: "NONE";
}
exports.getOperationType = getOperationType;
function isTestnet(currency) {
return (0, cryptoassets_1.getCryptoCurrencyById)(currency.id).isTestnetFor ? true : false;
}
exports.isTestnet = isTestnet;
function getAccountChange(t, accountCredentialsMap) {
let accountInputAda = new bignumber_js_1.default(0);
const accountInputTokens = [];
t.inputs.forEach(i => {
if (accountCredentialsMap[i.paymentKey]) {
accountInputAda = accountInputAda.plus(i.value);
accountInputTokens.push(...i.tokens.map(t => ({
assetName: t.assetName,
policyId: t.policyId,
amount: new bignumber_js_1.default(t.value),
})));
}
});
let accountOutputAda = new bignumber_js_1.default(0);
const accountOutputTokens = [];
t.outputs.forEach(o => {
if (accountCredentialsMap[o.paymentKey]) {
accountOutputAda = accountOutputAda.plus(o.value);
accountOutputTokens.push(...o.tokens.map(t => ({
assetName: t.assetName,
policyId: t.policyId,
amount: new bignumber_js_1.default(t.value),
})));
}
});
return {
ada: accountOutputAda.minus(accountInputAda),
tokens: getTokenDiff(accountOutputTokens, accountInputTokens),
};
}
exports.getAccountChange = getAccountChange;
function getMemoFromTx(tx) {
let memo;
const metadataValue = tx.metadata?.data.find(m => m.label === constants_1.MEMO_LABEL.toString());
if (metadataValue) {
try {
const parsedValue = JSON.parse(metadataValue.value);
if (parsedValue.msg && Array.isArray(parsedValue.msg) && parsedValue.msg.length) {
memo = parsedValue.msg.join(", ");
}
// eslint-disable-next-line no-empty
}
catch (e) { }
}
return memo;
}
exports.getMemoFromTx = getMemoFromTx;
function isHexString(value) {
const regExp = /^[0-9a-fA-F]+$/;
return regExp.test(value);
}
exports.isHexString = isHexString;
function decodeTokenName(assetName) {
if (assetName.length > 0) {
const bytes = [...Buffer.from(assetName, "hex")];
if (bytes.filter(byte => byte <= 32 || byte >= 127).length === 0) {
return String.fromCharCode(...bytes);
}
}
return assetName;
}
exports.decodeTokenName = decodeTokenName;
function getBech32PoolId(poolId, networkName) {
const networkParams = (0, networks_1.getNetworkParameters)(networkName);
const words = bech32_1.default.toWords(Buffer.from(poolId, "hex"));
const encoded = bech32_1.default.encode(networkParams.poolIdPrefix, words, 1000);
return encoded;
}
exports.getBech32PoolId = getBech32PoolId;
function isValidNumString(value) {
if (typeof value !== "string" && typeof value !== "number")
return false;
if (isNaN(Number(value)))
return false;
if (new bignumber_js_1.default(value).isNaN())
return false;
return true;
}
exports.isValidNumString = isValidNumString;
function isProtocolParamsValid(pp) {
const paramsRequiredCheck = [
pp.minFeeA,
pp.minFeeB,
pp.stakeKeyDeposit,
pp.lovelacePerUtxoWord,
pp.collateralPercent,
pp.priceSteps,
pp.priceMem,
pp.maxTxSize,
pp.maxValueSize,
pp.utxoCostPerByte,
];
return paramsRequiredCheck.every(isValidNumString);
}
exports.isProtocolParamsValid = isProtocolParamsValid;
//# sourceMappingURL=logic.js.map