import {default as xdr} from "./generated/stellar-xdr_generated";
import {Keypair} from "./keypair";
import {hash} from "./hashing";
import {StrKey} from "./strkey";
import {Hyper} from "js-xdr";
import {Asset} from "./asset";
import BigNumber from 'bignumber.js';
import {best_r} from "./util/continued_fraction";
import trimEnd from 'lodash/trimEnd';
import isUndefined from 'lodash/isUndefined';
import isString from 'lodash/isString';
import isNumber from 'lodash/isNumber';
import isFinite from 'lodash/isFinite';
import * as ops from './contr_operations/index';
const ONE = 10000000;
const MAX_INT64 = '9223372036854775807';
/**
* `ContrOperation` class represents [contrOperations] in Stellar network.
* Use one of static methods to create contract operations:
* * thuannd start
* * `{@link ContrOperation.check}`
* * `{@link ContrOperation.invoke}`
* * `{@link ContrOperation.query}`
* * `{@link ContrOperation.transfer}`
* * thuannd end
*
* @class ContrOperation
*/
export class ContrOperation {
/**
* Converts the XDR Contract Operation object to the opts object used to create the XDR
* contract operation.
* @param {xdr.ContrOperation} contrOperation - An XDR contract operation.
* @return {ContrOperation}
*/
static fromXDRObject(contrOperation) {
function accountIdtoAddress(accountId) {
return StrKey.encodeEd25519PublicKey(accountId.ed25519());
}
// thuannd start
function contractIdtoAddress(contractId) {
return StrKey.encodeContractKey(contractId.contract());
}
// thuannd end
let result = {};
let attrs = contrOperation.body().value();
switch (contrOperation.body().switch().name) {
case "check":
result.type = "check";
result.sourceAccId = accountIdtoAddress(attrs.sourceAccId());
result.op = attrs.op().toString();
result.amount = this._fromXDRAmount(attrs.amount());
break;
case "invoke":
result.type = "invoke";
result.newState = attrs.newState().toString();
break;
case "query":
result.type = "query";
result.data = attrs.data().toString();
break;
case "transfer":
result.destination = accountIdtoAddress(attrs.destination());
result.asset = Asset.fromOperation(attrs.asset());
result.amount = this._fromXDRAmount(attrs.amount());
break;
default:
throw new Error("Unknown contract opration");
}
return result;
}
static isValidAmount(value, allowZero = false) {
if (!isString(value)) {
return false;
}
let amount;
try {
amount = new BigNumber(value);
} catch (e) {
return false;
}
switch (true) {
// == 0
case !allowZero && amount.isZero():
// < 0
case amount.isNegative():
// > Max value
case amount.times(ONE).greaterThan(new BigNumber(MAX_INT64).toString()):
// Decimal places (max 7)
case amount.decimalPlaces() > 7:
// NaN or Infinity
case (amount.isNaN() || !amount.isFinite()):
return false;
default:
return true;
}
}
static constructAmountRequirementsError(arg) {
return `${arg} argument must be of type String, represent a positive number and have at most 7 digits after the decimal`;
}
/**
* Returns value converted to uint32 value or undefined.
* If `value` is not `Number`, `String` or `Undefined` then throws an error.
* Used in {@link Operation.setOptions}.
* @private
* @param {string} name Name of the property (used in error message only)
* @param {*} value Value to check
* @param {function(value, name)} isValidFunction Function to check other constraints (the argument will be a `Number`)
* @returns {undefined|Number}
* @private
*/
static _checkUnsignedIntValue(name, value, isValidFunction = null) {
if (isUndefined(value)) {
return undefined;
}
if (isString(value)) {
value = parseFloat(value);
}
switch (true) {
case !isNumber(value) || !isFinite(value) || value % 1 !== 0:
throw new Error(`${name} value is invalid`);
case value < 0:
throw new Error(`${name} value must be unsigned`);
case !isValidFunction || (isValidFunction && isValidFunction(value, name)):
return value;
default:
throw new Error(`${name} value is invalid`);
}
}
/**
* @private
*/
static _toXDRAmount(value) {
let amount = new BigNumber(value).mul(ONE);
return Hyper.fromString(amount.toString());
}
/**
* @private
*/
static _fromXDRAmount(value) {
return new BigNumber(value).div(ONE).toString();
}
/**
* @private
*/
static _fromXDRPrice(price) {
let n = new BigNumber(price.n());
return n.div(new BigNumber(price.d())).toString();
}
/**
* @private
*/
static _toXDRPrice(price) {
let xdrObject;
if (price.n && price.d) {
xdrObject = new xdr.Price(price);
} else {
price = new BigNumber(price);
let approx = best_r(price);
xdrObject = new xdr.Price({
n: parseInt(approx[0]),
d: parseInt(approx[1])
});
}
if (xdrObject.n() < 0 || xdrObject.d() < 0) {
throw new Error('price must be positive');
}
return xdrObject;
}
}
// Attach all imported operations as static methods on the Operation class
// thuannd start
ContrOperation.check = ops.check;
ContrOperation.invoke = ops.invoke;
ContrOperation.query = ops.query;
ContrOperation.transfer = ops.transfer;
// thuannd end