PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-xrp/src/lib
Просмотр файла: utils.ts
import {
BaseUtils,
InvalidAddressError,
InvalidTransactionError,
UnsupportedTokenError,
UtilsError,
} from '@bitgo/sdk-core';
import { BaseCoin, coins, XrpCoin } from '@bitgo/statics';
import * as querystring from 'querystring';
import * as rippleKeypairs from 'ripple-keypairs';
import * as url from 'url';
import * as xrpl from 'xrpl';
import { Amount, IssuedCurrencyAmount } from 'xrpl';
import { VALID_ACCOUNT_SET_FLAGS } from './constants';
import { Address, SignerDetails } from './iface';
import { KeyPair as XrpKeyPair } from './keyPair';
import assert from 'assert';
class Utils implements BaseUtils {
isValidAddress(address: string): boolean {
try {
const addressDetails = this.getAddressDetails(address);
return address === this.normalizeAddress(addressDetails);
} catch (e) {
return false;
}
}
isValidTransactionId(txId: string): boolean {
return this.isValidHex(txId);
}
isValidPublicKey(key: string): boolean {
try {
new XrpKeyPair({ pub: key });
return true;
} catch {
return false;
}
}
isValidPrivateKey(key: string): boolean {
try {
new XrpKeyPair({ prv: key });
return true;
} catch {
return false;
}
}
isValidSignature(signature: string): boolean {
return this.isValidHex(signature);
}
isValidBlockId(hash: string): boolean {
return this.isValidHex(hash);
}
isValidHex(hex: string): boolean {
return /^([a-fA-F0-9])+$/.test(hex);
}
/**
* Parse an address string into address and destination tag
*/
public getAddressDetails(address: string): Address {
const destinationDetails = url.parse(address);
const destinationAddress = destinationDetails.pathname;
if (!destinationAddress || !xrpl.isValidClassicAddress(destinationAddress)) {
throw new InvalidAddressError(`destination address "${destinationAddress}" is not valid`);
}
// there are no other properties like destination tags
if (destinationDetails.pathname === address) {
return {
address: address,
destinationTag: undefined,
};
}
if (!destinationDetails.query) {
throw new InvalidAddressError('no query params present');
}
const queryDetails = querystring.parse(destinationDetails.query);
if (!queryDetails.dt) {
// if there are more properties, the query details need to contain the destination tag property.
throw new InvalidAddressError('destination tag missing');
}
if (Array.isArray(queryDetails.dt)) {
// if queryDetails.dt is an array, that means dt was given multiple times, which is not valid
throw new InvalidAddressError(
`destination tag can appear at most once, but ${queryDetails.dt.length} destination tags were found`
);
}
const parsedTag = parseInt(queryDetails.dt, 10);
if (!Number.isSafeInteger(parsedTag)) {
throw new InvalidAddressError('invalid destination tag');
}
if (parsedTag > 0xffffffff || parsedTag < 0) {
throw new InvalidAddressError('destination tag out of range');
}
return {
address: destinationAddress,
destinationTag: parsedTag,
};
}
/**
* Construct a full, normalized address from an address and destination tag
*/
public normalizeAddress({ address, destinationTag }: Address): string {
if (typeof address !== 'string') {
throw new InvalidAddressError('invalid address, expected string');
}
if (typeof destinationTag === 'undefined' || destinationTag === null) {
return address;
}
if (!Number.isInteger(destinationTag)) {
throw new InvalidAddressError('invalid destination tag, expected integer');
}
if (destinationTag > 0xffffffff || destinationTag < 0) {
throw new InvalidAddressError('destination tag out of range');
}
return `${address}?dt=${destinationTag}`;
}
/**
* @param message hex encoded string
* @param privateKey
* return hex encoded signature string, throws if any of the inputs are invalid
*/
public signString(message: string, privateKey: string): string {
if (!this.isValidHex(message)) {
throw new UtilsError('message must be a hex encoded string');
}
if (!this.isValidPrivateKey(privateKey)) {
throw new UtilsError('invalid private key');
}
return rippleKeypairs.sign(message, privateKey);
}
/**
* @param message hex encoded string
* @param signature hex encooded signature string
* @param publicKey
* return boolean, throws if any of the inputs are invalid
*/
public verifySignature(message: string, signature: string, publicKey: string): boolean {
if (!this.isValidHex(message)) {
throw new UtilsError('message must be a hex encoded string');
}
if (!this.isValidSignature(signature)) {
throw new UtilsError('invalid signature');
}
if (!this.isValidPublicKey(publicKey)) {
throw new UtilsError('invalid public key');
}
try {
return rippleKeypairs.verify(message, signature, publicKey);
} catch (e) {
return false;
}
}
/**
* Check the raw transaction has a valid format in the blockchain context, throw otherwise.
*
* @param {string} rawTransaction - Transaction in hex string format
*/
public validateRawTransaction(rawTransaction: string): void {
if (!rawTransaction) {
throw new InvalidTransactionError('Invalid raw transaction: Undefined');
}
if (!this.isValidHex(rawTransaction)) {
throw new InvalidTransactionError('Invalid raw transaction: Hex string expected');
}
if (!this.isValidRawTransaction(rawTransaction)) {
throw new InvalidTransactionError('Invalid raw transaction');
}
}
/**
* Checks if raw transaction can be deserialized
*
* @param {string} rawTransaction - transaction in base64 string format
* @returns {boolean} - the validation result
*/
public isValidRawTransaction(rawTransaction: string): boolean {
try {
const jsonTx = xrpl.decode(rawTransaction);
xrpl.validate(jsonTx);
return true;
} catch (e) {
return false;
}
}
public validateAccountSetFlag(setFlag: number) {
if (typeof setFlag !== 'number') {
throw new UtilsError(`setFlag ${setFlag} is not valid`);
}
if (!VALID_ACCOUNT_SET_FLAGS.includes(setFlag)) {
throw new UtilsError(`setFlag ${setFlag} is not a valid account set flag`);
}
}
public validateSigner(signer: SignerDetails): void {
if (!signer.address) {
throw new UtilsError('signer must have an address');
}
if (!this.isValidAddress(signer.address)) {
throw new UtilsError(`signer address ${signer.address} is invalid`);
}
if (typeof signer.weight !== 'number' || signer.weight < 0) {
throw new UtilsError(`signer weight ${signer.weight} is not valid`);
}
}
/**
* Determines if the provided `amount` is for a token payment
*/
public isIssuedCurrencyAmount(amount: Amount): amount is IssuedCurrencyAmount {
return (
!!amount &&
typeof amount === 'object' &&
typeof amount.currency === 'string' &&
typeof amount.issuer === 'string' &&
typeof amount.value === 'string'
);
}
/**
* Get the associated XRP Currency details from token name. Throws an error if token is unsupported
* @param {string} tokenName - The token name
*/
public getXrpCurrencyFromTokenName(tokenName: string): xrpl.IssuedCurrency {
if (!coins.has(tokenName)) {
throw new UnsupportedTokenError(`${tokenName} is not supported`);
}
const token = coins.get(tokenName);
if (!token.isToken || !(token instanceof XrpCoin)) {
throw new UnsupportedTokenError(`${tokenName} is not an XRP token`);
}
return {
currency: token.currencyCode,
issuer: token.issuerAddress,
};
}
/**
* Decodes a serialized XRPL transaction.
*
* @param {string} txHex - The serialized transaction in hex.
* @returns {Object} - Decoded transaction object.
* @throws {Error} - If decoding fails or input is invalid.
*/
public decodeTransaction(txHex: string) {
if (typeof txHex !== 'string' || txHex.trim() === '') {
throw new Error('Invalid transaction hex. Expected a non-empty string.');
}
try {
return xrpl.decode(txHex);
} catch (error) {
throw new Error(`Failed to decode transaction: ${error.message}`);
}
}
/**
* Get the statics coin object matching a given Xrp token issuer address and currency code if it exists
*
* @param issuerAddress The token issuer address to match against
* @param currencyCode The token currency code to match against
* @returns statics BaseCoin object for the matching token
*/
public getXrpToken(issuerAddress, currencyCode): Readonly<BaseCoin> | undefined {
const tokens = coins.filter((coin) => {
if (coin instanceof XrpCoin) {
return coin.issuerAddress === issuerAddress && coin.currencyCode === currencyCode;
}
return false;
});
const tokensArray = tokens.map((token) => token);
if (tokensArray.length >= 1) {
// there should never be two tokens with the same issuer address and currency code, so we assert that here
assert(tokensArray.length === 1);
return tokensArray[0];
}
return undefined;
}
}
const utils = new Utils();
export default utils;
Выполнить команду
Для локальной разработки. Не используйте в интернете!