UNPKG

turtlecoin-wallet-backend

Version:

[![NPM](https://nodei.co/npm/turtlecoin-wallet-backend.png?compact=true)](https://npmjs.org/package/turtlecoin-wallet-backend)

263 lines (262 loc) 12.4 kB
"use strict"; // Copyright (c) 2018-2020, Zpalmtree // // Please see the included LICENSE file for more information. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.validatePaymentID = exports.validateMixin = exports.validateAmount = exports.validateOurAddresses = exports.validateIntegratedAddresses = exports.validateDestinations = exports.validateAddress = exports.validateAddresses = void 0; const _ = require("lodash"); const turtlecoin_utils_1 = require("turtlecoin-utils"); const WalletError_1 = require("./WalletError"); const Config_1 = require("./Config"); const Assert_1 = require("./Assert"); /** * @param addresses The addresses to validate * @param integratedAddressesAllowed Should we allow integrated addresses? * @param config * * Verifies that the addresses given are valid. * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error */ function validateAddresses(addresses, integratedAddressesAllowed, config = new Config_1.Config()) { return __awaiter(this, void 0, void 0, function* () { Assert_1.assertArray(addresses, 'addresses'); Assert_1.assertBoolean(integratedAddressesAllowed, 'integratedAddressesAllowed'); const tempConfig = Config_1.MergeConfig(config); const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; for (const address of addresses) { try { /* Verify address lengths are correct */ if (address.length !== config.standardAddressLength && address.length !== config.integratedAddressLength) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_WRONG_LENGTH); } /* Verify every address character is in the base58 set */ if (![...address].every((x) => alphabet.includes(x))) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_NOT_BASE58); } const parsed = yield turtlecoin_utils_1.Address.fromAddress(address, tempConfig.addressPrefix); /* Verify it's not an integrated, if those aren't allowed */ if (parsed.paymentId.length !== 0 && !integratedAddressesAllowed) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_IS_INTEGRATED); } } catch (err) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_NOT_VALID, err.toString()); } } return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.SUCCESS); }); } exports.validateAddresses = validateAddresses; /** * Verifies that the address given is valid. * * Example: * ```javascript * const address = 'TRTLv2txGW8daTunmAVV6dauJgEv1LezM2Hse7EUD5c11yKHsNDrzQ5UWNRmu2ToQVhDcr82ZPVXy4mU5D7w9RmfR747KeXD3UF'; * const isValid = await validateAddress(address, false) * * console.log(`Address at ${address} is valid?`, isValid ? 'yes' : 'no'); * ``` * * @param address The address to validate. * @param integratedAddressAllowed Should an integrated address be allowed? * @param config * * @returns Returns true if the address is valid, otherwise returns false * */ function validateAddress(address, integratedAddressAllowed, config) { return __awaiter(this, void 0, void 0, function* () { const err = yield validateAddresses(new Array(address), integratedAddressAllowed, Config_1.MergeConfig(config)); return err.errorCode === WalletError_1.WalletErrorCode.SUCCESS; }); } exports.validateAddress = validateAddress; /** * Validate the amounts being sent are valid, and the addresses are valid. * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateDestinations(destinations, config = new Config_1.Config()) { return __awaiter(this, void 0, void 0, function* () { const tempConfig = Config_1.MergeConfig(config); if (destinations.length === 0) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NO_DESTINATIONS_GIVEN); } const destinationAddresses = []; for (const [destination, amount] of destinations) { if (amount === 0) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.AMOUNT_IS_ZERO); } if (amount < 0) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NEGATIVE_VALUE_GIVEN); } if (!Number.isInteger(amount)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NON_INTEGER_GIVEN); } destinationAddresses.push(destination); } /* Validate the addresses, integrated addresses allowed */ return validateAddresses(destinationAddresses, true, tempConfig); }); } exports.validateDestinations = validateDestinations; /** * Validate that the payment ID's included in integrated addresses are valid. * * You should have already called validateAddresses() before this function * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateIntegratedAddresses(destinations, paymentID, config = new Config_1.Config()) { return __awaiter(this, void 0, void 0, function* () { const tempConfig = Config_1.MergeConfig(config); for (const [destination] of destinations) { if (destination.length !== tempConfig.integratedAddressLength) { continue; } /* Extract the payment ID */ const parsedAddress = yield turtlecoin_utils_1.Address.fromAddress(destination, tempConfig.addressPrefix); if (paymentID === '') { paymentID = parsedAddress.paymentId; } else if (paymentID !== parsedAddress.paymentId) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.CONFLICTING_PAYMENT_IDS); } } return WalletError_1.SUCCESS; }); } exports.validateIntegratedAddresses = validateIntegratedAddresses; /** * Validate the the addresses given are both valid, and exist in the subwallet * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateOurAddresses(addresses, subWallets, config = new Config_1.Config()) { return __awaiter(this, void 0, void 0, function* () { const tempConfig = Config_1.MergeConfig(config); const error = yield validateAddresses(addresses, false, tempConfig); if (!_.isEqual(error, WalletError_1.SUCCESS)) { return error; } for (const address of addresses) { const parsedAddress = yield turtlecoin_utils_1.Address.fromAddress(address, tempConfig.addressPrefix); const keys = subWallets.getPublicSpendKeys(); if (!keys.includes(parsedAddress.spend.publicKey)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.ADDRESS_NOT_IN_WALLET, `The address given (${address}) does not exist in the wallet ` + `container, but it is required to exist for this operation.`); } } return WalletError_1.SUCCESS; }); } exports.validateOurAddresses = validateOurAddresses; /** * Validate that the transfer amount + fee is valid, and we have enough balance * Note: Does not verify amounts are positive / integer, validateDestinations * handles that. * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateAmount(destinations, fee, subWalletsToTakeFrom, subWallets, currentHeight, config = new Config_1.Config()) { return __awaiter(this, void 0, void 0, function* () { const tempConfig = Config_1.MergeConfig(config); if (!fee.isFeePerByte && !fee.isFixedFee) { throw new Error('Programmer error: Fee type not specified!'); } /* Using a fee per byte, and doesn't meet the min fee per byte requirement. */ if (fee.isFeePerByte && fee.feePerByte < tempConfig.minimumFeePerByte) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.FEE_TOO_SMALL); } /* Cannot have a non integer fixed fee */ if (fee.isFixedFee && !Number.isInteger(fee.fixedFee)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NON_INTEGER_GIVEN); } /* Get available balance, given the source addresses */ const [availableBalance] = yield subWallets.getBalance(currentHeight, subWalletsToTakeFrom); /* Get the sum of the transaction */ let totalAmount = _.sumBy(destinations, ([, amount]) => amount); /* Can only accurately calculate if we've got enough funds for the tx if * using a fixed fee. If using a fee per byte, we'll verify when constructing * the transaction. */ if (fee.isFixedFee) { totalAmount += fee.fixedFee; } if (totalAmount > availableBalance) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NOT_ENOUGH_BALANCE); } /* Can't send more than 2^64 (Granted, that is larger than the entire supply, but you can still try ;) */ if (totalAmount >= Math.pow(2, 64)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.WILL_OVERFLOW); } return WalletError_1.SUCCESS; }); } exports.validateAmount = validateAmount; /** * Validates mixin is valid and in allowed range * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error * * @hidden */ function validateMixin(mixin, height, config = new Config_1.Config()) { const tempConfig = Config_1.MergeConfig(config); if (mixin < 0) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NEGATIVE_VALUE_GIVEN); } if (!Number.isInteger(mixin)) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.NON_INTEGER_GIVEN); } const [minMixin, maxMixin] = tempConfig.mixinLimits.getMixinLimitsByHeight(height); if (mixin < minMixin) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.MIXIN_TOO_SMALL, `The mixin value given (${mixin}) is lower than the minimum mixin ` + `allowed (${minMixin})`); } if (mixin > maxMixin) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.MIXIN_TOO_BIG, `The mixin value given (${mixin}) is greater than the maximum mixin ` + `allowed (${maxMixin})`); } return WalletError_1.SUCCESS; } exports.validateMixin = validateMixin; /** * Validates the payment ID is valid (or an empty string) * * @returns Returns SUCCESS if valid, otherwise a WalletError describing the error */ function validatePaymentID(paymentID, allowEmptyString = true) { Assert_1.assertString(paymentID, 'paymentID'); Assert_1.assertBoolean(allowEmptyString, 'allowEmptyString'); if (paymentID === '' && allowEmptyString) { return WalletError_1.SUCCESS; } if (paymentID.length !== 64) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.PAYMENT_ID_WRONG_LENGTH); } if (paymentID.match(new RegExp(/[a-zA-Z0-9]{64}/)) === null) { return new WalletError_1.WalletError(WalletError_1.WalletErrorCode.PAYMENT_ID_INVALID); } return WalletError_1.SUCCESS; } exports.validatePaymentID = validatePaymentID;