import { XdrLargeInt } from './xdr_large_int';
/**
* Provides an easier way to manipulate large numbers for Stellar operations.
*
* You can instantiate this "**s**mart **c**ontract integer" value either from
* bigints, strings, or numbers (whole numbers, or this will throw).
*
* If you need to create a native BigInt from a list of integer "parts" (for
* example, you have a series of encoded 32-bit integers that represent a larger
* value), you can use the lower level abstraction {@link XdrLargeInt}. For
* example, you could do `new XdrLargeInt('u128', bytes...).toBigInt()`.
*
* @example
* import { xdr, ScInt, scValToBigInt } from "@stellar/stellar-base";
*
* // You have an ScVal from a contract and want to parse it into JS native.
* const value = xdr.ScVal.fromXDR(someXdr, "base64");
* const bigi = scValToBigInt(value); // grab it as a BigInt
* let sci = new ScInt(bigi);
*
* sci.toNumber(); // gives native JS type (w/ size check)
* sci.toBigInt(); // gives the native BigInt value
* sci.toU64(); // gives ScValType-specific XDR constructs (with size checks)
*
* // You have a number and want to shove it into a contract.
* sci = ScInt(0xdeadcafebabe);
* sci.toBigInt() // returns 244838016400062n
* sci.toNumber() // throws: too large
*
* // Pass any to e.g. a Contract.call(), conversion happens automatically
* // regardless of the initial type.
* const scValU128 = sci.toU128();
* const scValI256 = sci.toI256();
* const scValU64 = sci.toU64();
*
* // Lots of ways to initialize:
* ScInt("123456789123456789")
* ScInt(123456789123456789n);
* ScInt(1n << 140n);
* ScInt(-42);
* ScInt(scValToBigInt(scValU128)); // from above
*
* // If you know the type ahead of time (accessing `.raw` is faster than
* // conversions), you can specify the type directly (otherwise, it's
* // interpreted from the numbers you pass in):
* const i = ScInt(123456789n, { type: "u256" });
*
* // For example, you can use the underlying `sdk.U256` and convert it to an
* // `xdr.ScVal` directly like so:
* const scv = new xdr.ScVal.scvU256(i.raw);
*
* // Or reinterpret it as a different type (size permitting):
* const scv = i.toI64();
*
* @param {number|bigint|string} value - a single, integer-like value which will
* be interpreted in the smallest appropriate XDR type supported by Stellar
* (64, 128, or 256 bit integer values). signed values are supported, though
* they are sanity-checked against `opts.type`. if you need 32-bit values,
* you can construct them directly without needing this wrapper, e.g.
* `xdr.ScVal.scvU32(1234)`.
*
* @param {object} [opts] - an optional object controlling optional parameters
* @param {string} [opts.type] - force a specific data type. the type choices
* are: 'i64', 'u64', 'i128', 'u128', 'i256', and 'u256' (default: the
* smallest one that fits the `value`)
*
* @throws {RangeError} if the `value` is invalid (e.g. floating point), too
* large (i.e. exceeds a 256-bit value), or doesn't fit in the `opts.type`
* @throws {TypeError} on missing parameters, or if the "signedness" of `opts`
* doesn't match input `value`, e.g. passing `{type: 'u64'}` yet passing -1n
* @throws {SyntaxError} if a string `value` can't be parsed as a big integer
*/
export class ScInt extends XdrLargeInt {
constructor(value, opts) {
const signed = value < 0;
let type = opts?.type ?? '';
if (type.startsWith('u') && signed) {
throw TypeError(`specified type ${opts.type} yet negative (${value})`);
}
// If unspecified, we make a best guess at the type based on the bit length
// of the value, treating 64 as a minimum and 256 as a maximum.
if (type === '') {
type = signed ? 'i' : 'u';
const bitlen = nearestBigIntSize(value);
switch (bitlen) {
case 64:
case 128:
case 256:
type += bitlen.toString();
break;
default:
throw RangeError(
`expected 64/128/256 bits for input (${value}), got ${bitlen}`
);
}
}
super(type, value);
}
}
function nearestBigIntSize(bigI) {
// Note: Even though BigInt.toString(2) includes the negative sign for
// negative values (???), the following is still accurate, because the
// negative sign would be represented by a sign bit.
const bitlen = bigI.toString(2).length;
return [64, 128, 256].find((len) => bitlen <= len) ?? bitlen;
}