@stellar/stellar-sdk
Version:
A library for working with the Stellar network, including communication with the Horizon and Soroban RPC servers.
284 lines (281 loc) • 10.2 kB
JavaScript
import { Buffer } from 'buffer';
import types from './generated/curr_generated.js';
import { Keypair } from './keypair.js';
import { Address } from './address.js';
import { Contract } from './contract.js';
import { scValToBigInt } from './numbers/index.js';
import { XdrLargeInt } from './numbers/xdr_large_int.js';
import { ScInt } from './numbers/sc_int.js';
function nativeToScVal(val, opts = {}) {
switch (typeof val) {
case "object": {
if (val === null) {
return types.ScVal.scvVoid();
}
if (val instanceof types.ScVal) {
return val;
}
if (val instanceof Address) {
return val.toScVal();
}
if (val instanceof Keypair) {
return nativeToScVal(val.publicKey(), { type: "address" });
}
if (val instanceof Contract) {
return val.address().toScVal();
}
if (val instanceof Uint8Array || Buffer.isBuffer(val)) {
const copy = Buffer.from(val);
switch (opts?.type ?? "bytes") {
case "bytes":
return types.ScVal.scvBytes(copy);
case "symbol":
return types.ScVal.scvSymbol(copy);
case "string":
return types.ScVal.scvString(copy);
default:
throw new TypeError(
`invalid type (${JSON.stringify(opts.type)}) specified for bytes-like value`
);
}
}
if (Array.isArray(val)) {
return types.ScVal.scvVec(
val.map((v, idx) => {
if (Array.isArray(opts.type)) {
return nativeToScVal(
v,
// only include a `{ type: ... }` if it's present (safer than
// `{type: undefined}`)
{
...opts.type.length > idx && {
type: opts.type[idx]
}
}
);
}
return nativeToScVal(v, opts);
})
);
}
if (val instanceof Map) {
let uniformKeyType = null;
let uniformValType = null;
let perKeySpec = null;
if (Array.isArray(opts.type)) {
if (opts.type.length > 2) {
throw new TypeError(
`expected a [keyType, valType] pair for a Map, got ${JSON.stringify(opts.type)}`
);
}
[uniformKeyType = null, uniformValType = null] = opts.type;
} else if (typeof opts.type === "object" && opts.type !== null) {
perKeySpec = opts.type;
} else if (opts.type !== void 0) {
throw new TypeError(
`invalid type (${JSON.stringify(opts.type)}) specified for a Map`
);
}
const entries = [];
for (const [k, v] of val) {
let keyType = uniformKeyType;
let valType = uniformValType;
if (perKeySpec && typeof k === "string" && Object.hasOwn(perKeySpec, k)) {
[keyType = null, valType = null] = perKeySpec[k] ?? [];
}
entries.push(
new types.ScMapEntry({
key: nativeToScVal(k, keyType ? { type: keyType } : {}),
val: nativeToScVal(v, valType ? { type: valType } : {})
})
);
}
return types.scvSortedMap(entries);
}
if (Object.getPrototypeOf(val) !== Object.prototype) {
throw new TypeError(
`cannot interpret ${val.constructor?.name} value as ScVal (${JSON.stringify(val)})`
);
}
const mapTypeSpec = opts?.type ?? {};
return types.ScVal.scvMap(
Object.entries(val).sort(([key1], [key2]) => key1 < key2 ? -1 : key1 > key2 ? 1 : 0).map(([k, v]) => {
const [keyType, valType] = Object.hasOwn(mapTypeSpec, k) ? mapTypeSpec[k] ?? [null, null] : [null, null];
const keyOpts = keyType ? { type: keyType } : {};
const valOpts = valType ? { type: valType } : {};
return new types.ScMapEntry({
key: nativeToScVal(k, keyOpts),
val: nativeToScVal(v, valOpts)
});
})
);
}
case "number":
case "bigint": {
const bigintVal = BigInt(val);
switch (opts?.type) {
case "u32":
if (bigintVal < BigInt(types.Uint32.MIN_VALUE) || bigintVal > BigInt(types.Uint32.MAX_VALUE)) {
throw new TypeError(`invalid value (${val}) for type u32`);
}
return types.ScVal.scvU32(Number(val));
case "i32":
if (bigintVal < -BigInt(types.Int32.MIN_VALUE) || bigintVal > BigInt(types.Int32.MAX_VALUE)) {
throw new TypeError(`invalid value (${val}) for type i32`);
}
return types.ScVal.scvI32(Number(val));
}
return new ScInt(val, { type: opts?.type }).toScVal();
}
case "string": {
const optType = opts?.type ?? "string";
switch (optType) {
case "string":
return types.ScVal.scvString(val);
case "symbol":
return types.ScVal.scvSymbol(val);
case "address":
return new Address(val).toScVal();
case "u32": {
const bigintVal = BigInt(val);
if (bigintVal < BigInt(types.Uint32.MIN_VALUE) || bigintVal > BigInt(types.Uint32.MAX_VALUE)) {
throw new TypeError(`invalid value (${val}) for type u32`);
}
return types.ScVal.scvU32(Number(bigintVal));
}
case "i32": {
const bigintVal = BigInt(val);
if (bigintVal < -BigInt(types.Int32.MIN_VALUE) || bigintVal > BigInt(types.Int32.MAX_VALUE)) {
throw new TypeError(`invalid value (${val}) for type i32`);
}
return types.ScVal.scvI32(Number(bigintVal));
}
default:
if (XdrLargeInt.isType(optType)) {
return new XdrLargeInt(optType, val).toScVal();
}
throw new TypeError(
`invalid type (${JSON.stringify(opts.type)}) specified for string value`
);
}
}
case "boolean":
return types.ScVal.scvBool(val);
case "undefined":
return types.ScVal.scvVoid();
case "function":
return nativeToScVal(val());
default:
throw new TypeError(
`failed to convert typeof ${typeof val} (${JSON.stringify(val)})`
);
}
}
function scValToNative(scv) {
switch (scv.switch().value) {
case types.ScValType.scvVoid().value:
return null;
// these can be converted to bigints directly
case types.ScValType.scvU64().value:
case types.ScValType.scvI64().value:
return scv.value().toBigInt();
// these can be parsed by internal abstractions note that this can also
// handle the above two cases, but it's not as efficient (another
// type-check, parsing, etc.)
case types.ScValType.scvU128().value:
case types.ScValType.scvI128().value:
case types.ScValType.scvU256().value:
case types.ScValType.scvI256().value:
return scValToBigInt(scv);
case types.ScValType.scvVec().value:
return (scv.vec() ?? []).map(scValToNative);
case types.ScValType.scvAddress().value:
return Address.fromScVal(scv).toString();
case types.ScValType.scvMap().value:
return Object.fromEntries(
(scv.map() ?? []).map((entry) => [
scValToNative(entry.key()),
scValToNative(entry.val())
])
);
// these return the primitive type directly
case types.ScValType.scvBool().value:
case types.ScValType.scvU32().value:
case types.ScValType.scvI32().value:
case types.ScValType.scvBytes().value:
return scv.value();
// Symbols are limited to [a-zA-Z0-9_]+, so we can safely make ascii strings
//
// Strings, however, are "presented" as strings and we treat them as such
// (in other words, string = bytes with a hint that it's text). If the user
// encoded non-printable bytes in their string value, that's on them.
//
// Note that we assume a utf8 encoding (ascii-compatible). For other
// encodings, you should probably use bytes anyway. If it cannot be decoded,
// the raw bytes are returned.
case types.ScValType.scvSymbol().value: {
const v = scv.sym();
if (Buffer.isBuffer(v) || ArrayBuffer.isView(v) && typeof v !== "string") {
try {
return new TextDecoder().decode(v);
} catch {
return new Uint8Array(v.buffer);
}
}
return v;
}
case types.ScValType.scvString().value: {
const v = scv.str();
if (Buffer.isBuffer(v) || ArrayBuffer.isView(v) && typeof v !== "string") {
try {
return new TextDecoder().decode(v);
} catch {
return new Uint8Array(v.buffer);
}
}
return v;
}
// these can be converted to bigint
case types.ScValType.scvTimepoint().value:
case types.ScValType.scvDuration().value:
return scv.value().toBigInt();
case types.ScValType.scvError().value:
switch (scv.error().switch().value) {
// Distinguish errors from the user contract.
case types.ScErrorType.sceContract().value:
return { type: "contract", code: scv.error().contractCode() };
default: {
const err = scv.error();
return {
type: "system",
code: err.code().value,
value: err.code().name
};
}
}
// in the fallthrough case, just return the underlying value directly
default:
return scv.value();
}
}
function scvSortedMap(items) {
const sorted = Array.from(items).sort((a, b) => {
const nativeA = scValToNative(a.key());
const nativeB = scValToNative(b.key());
switch (typeof nativeA) {
case "number":
case "bigint":
if (nativeA === nativeB) return 0;
return nativeA < nativeB ? -1 : 1;
default: {
const strA = nativeA.toString();
const strB = nativeB.toString();
return strA < strB ? -1 : strA > strB ? 1 : 0;
}
}
});
return types.ScVal.scvMap(sorted);
}
types.scvSortedMap = scvSortedMap;
export { nativeToScVal, scValToNative, scvSortedMap };
//# sourceMappingURL=scval.js.map