@taquito/michelson-encoder
Version: 
converts michelson data and types into convenient JS/TS objects
1,529 lines (1,516 loc) • 133 kB
JavaScript
import stringify from 'fast-json-stable-stringify';
import { TaquitoError, InvalidViewParameterError } from '@taquito/core';
import BigNumber from 'bignumber.js';
import { b58DecodeAddress, validateAddress, ValidationResult, encodeAddress, splitAddress, compareArrays, stripHexPrefix, encodeKey, validatePublicKey, b58DecodePublicKey, encodeKeyHash, validateKeyHash, b58DecodePublicKeyHash, validateSignature, validateChain } from '@taquito/utils';
import elliptic from 'elliptic';
var _a$1;
/**
 *  @category Error
 *  @description Error that indicates an invalid map type being passed or used
 */
class InvalidMapTypeError extends TaquitoError {
    constructor(mapType, reason) {
        super();
        this.mapType = mapType;
        this.reason = reason;
        this.message = `The map type '${JSON.stringify(mapType)}' is invalid. Reason: ${reason}.`;
        this.name = 'InvalidMapTypeError';
    }
}
// Retrieve a unique symbol associated with the key from the environment
// Used in order to identify all object that are of type MichelsonMap even if they come from different module
const michelsonMapTypeSymbol = Symbol.for('taquito-michelson-map-type-symbol');
/**
 *
 * @throws {@link InvalidMapTypeError} when the argument passed to mapType is not a valid map type
 */
function validateMapType(value) {
    if (!('prim' in value)) {
        throw new InvalidMapTypeError(value, `Missing 'prim' field`);
    }
    if (!['map', 'big_map'].includes(value.prim)) {
        throw new InvalidMapTypeError(value, `The prim field should be 'map' or 'big_map'`);
    }
    if (!('args' in value)) {
        throw new InvalidMapTypeError(value, `Missing 'args' field`);
    }
    if (!Array.isArray(value.args)) {
        throw new InvalidMapTypeError(value, `The 'args' field should be an array`);
    }
    if (value.args.length !== 2) {
        throw new InvalidMapTypeError(value, `The 'args' field should have 2 elements`);
    }
}
/**
 *  @category Error
 *  @description Error that indicates a map type mismatch, where an attempt to set a key or value in a Map doesn't match the defined type of the Map
 */
class MapTypecheckError extends TaquitoError {
    constructor(value, type, objectType, reason) {
        super();
        this.value = value;
        this.type = type;
        this.reason = reason;
        this.name = 'MapTypecheckError';
        this.message = `The ${objectType} provided: ${JSON.stringify(value)} is not compatible with the expected michelson type: ${JSON.stringify(type)}. Reason: ${JSON.stringify(reason)}.`;
        this.name = 'MapTypecheckError';
    }
}
/**
 * @description Michelson Map is an abstraction over the michelson native map. It supports complex Pair as key
 */
class MichelsonMap {
    // Used to check if an object is a michelson map.
    // Using instanceof was not working for project that had multiple instance of taquito dependencies
    // as the class constructor is different
    static isMichelsonMap(obj) {
        return obj && obj[michelsonMapTypeSymbol] === true;
    }
    /**
     * @param mapType If specified key and value will be type-checked before being added to the map
     *
     * @example new MichelsonMap({ prim: "map", args: [{prim: "string"}, {prim: "int"}]})
     */
    constructor(mapType) {
        this.valueMap = new Map();
        this.keyMap = new Map();
        this[_a$1] = true;
        if (mapType) {
            this.setType(mapType);
        }
    }
    setType(mapType) {
        validateMapType(mapType);
        this.keySchema = new Schema(mapType.args[0]);
        this.valueSchema = new Schema(mapType.args[1]);
    }
    removeType() {
        this.keySchema = undefined;
        this.valueSchema = undefined;
    }
    static fromLiteral(obj, mapType) {
        const map = new MichelsonMap(mapType);
        Object.keys(obj).forEach((key) => {
            map.set(key, obj[key]);
        });
        return map;
    }
    typecheckKey(key) {
        if (!this.keySchema) {
            return;
        }
        this.keySchema.Typecheck(key);
    }
    typecheckValue(value) {
        if (!this.valueSchema) {
            return;
        }
        this.valueSchema.Typecheck(value);
    }
    /**
     * @throws {@link MapTypecheckError} when the argument passed does not match the expected schema for value
     */
    assertTypecheckValue(value) {
        try {
            this.typecheckValue(value);
        }
        catch (e) {
            throw new MapTypecheckError(value, this.valueSchema, 'value', e);
        }
    }
    /**
     * @throws {@link MapTypecheckError} when the argument passed does not match the expected schema for key
     */
    assertTypecheckKey(key) {
        try {
            this.typecheckKey(key);
        }
        catch (e) {
            throw new MapTypecheckError(key, this.keySchema, 'key', e);
        }
    }
    serializeDeterministically(key) {
        return stringify(key);
    }
    *keys() {
        for (const [key] of this.entries()) {
            yield key;
        }
    }
    *values() {
        for (const [, value] of this.entries()) {
            yield value;
        }
    }
    *entries() {
        for (const key of this.valueMap.keys()) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            yield [this.keyMap.get(key), this.valueMap.get(key)];
        }
    }
    get(key) {
        this.assertTypecheckKey(key);
        const strKey = this.serializeDeterministically(key);
        return this.valueMap.get(strKey);
    }
    /**
     *
     * @description Set a key and a value in the MichelsonMap. If the key already exists, override the current value.
     *
     * @example map.set("myKey", "myValue") // Using a string as key
     *
     * @example map.set({0: "test", 1: "test1"}, "myValue") // Using a pair as key
     *
     * @warn The same key can be represented in multiple ways, depending on the type of the key. This duplicate key situation will cause a runtime error (duplicate key) when sending the map data to the Tezos RPC node.
     *
     * For example, consider a contract with a map whose key is of type boolean.  If you set the following values in MichelsonMap: map.set(false, "myValue") and map.set(null, "myValue").
     *
     * You will get two unique entries in the MichelsonMap. These values will both be evaluated as falsy by the MichelsonEncoder and ultimately rejected by the Tezos RPC.
     */
    set(key, value) {
        this.assertTypecheckKey(key);
        this.assertTypecheckValue(value);
        const strKey = this.serializeDeterministically(key);
        this.keyMap.set(strKey, key);
        this.valueMap.set(strKey, value);
    }
    delete(key) {
        this.assertTypecheckKey(key);
        this.keyMap.delete(this.serializeDeterministically(key));
        this.valueMap.delete(this.serializeDeterministically(key));
    }
    has(key) {
        this.assertTypecheckKey(key);
        const strKey = this.serializeDeterministically(key);
        return this.keyMap.has(strKey) && this.valueMap.has(strKey);
    }
    clear() {
        this.keyMap.clear();
        this.valueMap.clear();
    }
    get size() {
        return this.keyMap.size;
    }
    forEach(cb) {
        for (const [key, value] of this.entries()) {
            cb(value, key, this);
        }
    }
}
_a$1 = michelsonMapTypeSymbol;
/**
 *  @category Error
 *  @description Error that indicates a failure when encoding invalid or incorrect data (e.g. if an address is expected but a number is received)
 */
class TokenValidationError extends TaquitoError {
    constructor(value, token, baseMessage) {
        super();
        this.value = value;
        this.token = token;
        this.name = 'TokenValidationError';
        const annot = this.token.annot();
        const annotText = annot ? `[${annot}] ` : '';
        this.message = `${annotText}${baseMessage}`;
    }
}
class Token {
    /**
     * @description Gets the strategy used for field numbering in Token execute/encode/decode to convert Michelson values to/from javascript objects, returns a value of type {@link FieldNumberingStrategy} that controls how field numbers are calculated
     */
    static get fieldNumberingStrategy() {
        return Token._fieldNumberingStrategy;
    }
    /**
     * @description Sets the strategy used for field numbering in Token execute/encode/decode to convert Michelson values to/from javascript objects, accepts a value of type {@link FieldNumberingStrategy} that controls how field numbers are calculated
     */
    static set fieldNumberingStrategy(value) {
        Token._fieldNumberingStrategy = value;
    }
    constructor(val, idx, fac, parentTokenType) {
        this.val = val;
        this.idx = idx;
        this.fac = fac;
        this.parentTokenType = parentTokenType;
        this.createToken = this.fac;
    }
    typeWithoutAnnotations() {
        const handleMichelsonExpression = (val) => {
            if (typeof val === 'object') {
                if (Array.isArray(val)) {
                    const array = val;
                    return array.map((item) => handleMichelsonExpression(item));
                }
                const extended = val;
                if (extended.args) {
                    return {
                        prim: extended.prim,
                        args: extended.args.map((x) => handleMichelsonExpression(x)),
                    };
                }
                else {
                    return {
                        prim: extended.prim,
                    };
                }
            }
            return val;
        };
        const handleMichelsonExtended = (val) => {
            if (val.args) {
                return {
                    prim: val.prim,
                    args: val.args.map((x) => handleMichelsonExpression(x)),
                };
            }
            else {
                return {
                    prim: val.prim,
                };
            }
        };
        return handleMichelsonExtended(this.val);
    }
    annot() {
        return (Array.isArray(this.val.annots) && this.val.annots.length > 0
            ? this.val.annots[0]
            : String(this.idx)).replace(/(%|:)(_Liq_entry_)?/, '');
    }
    hasAnnotations() {
        return Array.isArray(this.val.annots) && this.val.annots.length;
    }
    get tokenVal() {
        return this.val;
    }
    ExtractSignature() {
        return [[this.ExtractSchema()]];
    }
}
Token._fieldNumberingStrategy = 'Latest';
class ComparableToken extends Token {
    compare(o1, o2) {
        return o1 < o2 ? -1 : o1 > o2 ? 1 : 0;
    }
}
/**
 *  @category Error
 *  @description Error that indicates a failure happening when parsing encoding/executing Big Map types
 */
class BigMapValidationError extends TokenValidationError {
    constructor(value, token, message) {
        super(value, token, message);
        this.value = value;
        this.token = token;
        this.name = 'BigMapValidationError';
    }
}
class BigMapToken extends Token {
    constructor(val, idx, fac) {
        super(val, idx, fac);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
    }
    get ValueSchema() {
        return this.createToken(this.val.args[1], 0);
    }
    get KeySchema() {
        return this.createToken(this.val.args[0], 0);
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return {
            big_map: {
                key: this.KeySchema.ExtractSchema(),
                value: this.ValueSchema.ExtractSchema(),
            },
        };
    }
    generateSchema() {
        return {
            __michelsonType: BigMapToken.prim,
            schema: {
                key: this.KeySchema.generateSchema(),
                value: this.ValueSchema.generateSchema(),
            },
        };
    }
    /**
     * @throws {@link BigMapValidationError}
     */
    validate(value) {
        if (!MichelsonMap.isMichelsonMap(value)) {
            throw new BigMapValidationError(value, this, `Value ${JSON.stringify(value)} is not a MichelsonMap`);
        }
    }
    objLitToMichelsonMap(val) {
        if (val instanceof MichelsonMap)
            return val;
        if (typeof val === 'object') {
            if (Object.keys(val).length === 0) {
                return new MichelsonMap();
            }
            else {
                return MichelsonMap.fromLiteral(val);
            }
        }
        return val;
    }
    /**
     * @throws {@link BigMapValidationError}
     */
    Encode(args) {
        const val = this.objLitToMichelsonMap(args.pop());
        this.validate(val);
        return Array.from(val.keys())
            .sort((a, b) => this.KeySchema.compare(a, b))
            .map((key) => {
            return {
                prim: 'Elt',
                args: [this.KeySchema.EncodeObject(key), this.ValueSchema.EncodeObject(val.get(key))],
            };
        });
    }
    /**
     * @throws {@link BigMapValidationError}
     */
    EncodeObject(args, semantic) {
        const val = this.objLitToMichelsonMap(args);
        this.validate(val);
        if (semantic && semantic[BigMapToken.prim]) {
            return semantic[BigMapToken.prim](val, this.val);
        }
        return Array.from(val.keys())
            .sort((a, b) => this.KeySchema.compare(a, b))
            .map((key) => {
            return {
                prim: 'Elt',
                args: [this.KeySchema.EncodeObject(key), this.ValueSchema.EncodeObject(val.get(key))],
            };
        });
    }
    /**
     * @throws {@link InvalidMapTypeError} when the argument passed to val is an array but not a valid map type
     * @throws {@link BigMapValidationError} when the value is invalid
     */
    Execute(val, semantic) {
        if (semantic && semantic[BigMapToken.prim]) {
            return semantic[BigMapToken.prim](val, this.val);
        }
        if (Array.isArray(val)) {
            // Athens is returning an empty array for big map in storage
            // Internal: In taquito v5 it is still used to decode big map diff (as if they were a regular map)
            const map = new MichelsonMap(this.val);
            val.forEach((current) => {
                map.set(this.KeySchema.ToKey(current.args[0]), this.ValueSchema.Execute(current.args[1]));
            });
            return map;
        }
        else if ('int' in val) {
            // Babylon is returning an int with the big map id in contract storage
            return val.int;
        }
        else {
            throw new BigMapValidationError(val, this, `Big map is expecting either an array (Athens) or an object with an int property (Babylon). Got ${JSON.stringify(val)}`);
        }
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (BigMapToken.prim === tokenToFind) {
            tokens.push(this);
        }
        this.KeySchema.findAndReturnTokens(tokenToFind, tokens);
        this.ValueSchema.findAndReturnTokens(tokenToFind, tokens);
        return tokens;
    }
}
BigMapToken.prim = 'big_map';
/**
 *  @category Error
 *  @description Error that indicates a failure happening when parsing encoding/executing an OrToken
 */
class OrValidationError extends TokenValidationError {
    constructor(value, token, message) {
        super(value, token, message);
        this.value = value;
        this.token = token;
        this.name = 'OrValidationError';
    }
}
class OrToken extends ComparableToken {
    constructor(val, idx, fac, parentTokenType) {
        super(val, idx, fac, parentTokenType);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
        this.parentTokenType = parentTokenType;
    }
    Encode(args) {
        const label = args[args.length - 1];
        const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
        let keyCount = 1;
        if (leftToken instanceof OrToken) {
            keyCount = Object.keys(leftToken.ExtractSchema()).length;
        }
        const rightToken = this.createToken(this.val.args[1], this.getIdxForChildren() + keyCount, 'Or');
        if (String(leftToken.annot()) === String(label) && !(leftToken instanceof OrToken)) {
            args.pop();
            return { prim: 'Left', args: [leftToken.Encode(args)] };
        }
        else if (String(rightToken.annot()) === String(label) && !(rightToken instanceof OrToken)) {
            args.pop();
            return { prim: 'Right', args: [rightToken.Encode(args)] };
        }
        else {
            if (leftToken instanceof OrToken) {
                const val = leftToken.Encode(args);
                if (val) {
                    return { prim: 'Left', args: [val] };
                }
            }
            if (rightToken instanceof OrToken) {
                const val = rightToken.Encode(args);
                if (val) {
                    return { prim: 'Right', args: [val] };
                }
            }
            return null;
        }
    }
    ExtractSignature() {
        const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
        let keyCount = 1;
        if (leftToken instanceof OrToken) {
            keyCount = Object.keys(leftToken.ExtractSchema()).length;
        }
        const rightToken = this.createToken(this.val.args[1], this.getIdxForChildren() + keyCount, 'Or');
        const newSig = [];
        if (leftToken instanceof OrToken) {
            newSig.push(...leftToken.ExtractSignature());
        }
        else {
            for (const sig of leftToken.ExtractSignature()) {
                newSig.push([leftToken.annot(), ...sig]);
            }
        }
        if (rightToken instanceof OrToken) {
            newSig.push(...rightToken.ExtractSignature());
        }
        else {
            for (const sig of rightToken.ExtractSignature()) {
                newSig.push([rightToken.annot(), ...sig]);
            }
        }
        return newSig;
    }
    /**
     * @throws {@link OrValidationError}
     */
    EncodeObject(args, semantic) {
        this.validateJavascriptObject(args);
        const label = Object.keys(args)[0];
        const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
        let keyCount = 1;
        if (leftToken instanceof OrToken) {
            keyCount = Object.keys(leftToken.ExtractSchema()).length;
        }
        const rightToken = this.createToken(this.val.args[1], this.getIdxForChildren() + keyCount, 'Or');
        if (String(leftToken.annot()) === String(label) && !(leftToken instanceof OrToken)) {
            return { prim: 'Left', args: [leftToken.EncodeObject(args[label], semantic)] };
        }
        else if (String(rightToken.annot()) === String(label) && !(rightToken instanceof OrToken)) {
            return { prim: 'Right', args: [rightToken.EncodeObject(args[label], semantic)] };
        }
        else {
            if (leftToken instanceof OrToken) {
                const val = leftToken.EncodeObject(args, semantic);
                if (val) {
                    return { prim: 'Left', args: [val] };
                }
            }
            if (rightToken instanceof OrToken) {
                const val = rightToken.EncodeObject(args, semantic);
                if (val) {
                    return { prim: 'Right', args: [val] };
                }
            }
            return null;
        }
    }
    /**
     * @throws {@link OrValidationError}
     */
    validateJavascriptObject(args) {
        if (typeof args !== 'object' ||
            Array.isArray(args) ||
            args === null ||
            Object.keys(args).length !== 1) {
            throw new OrValidationError(args, this, `EncodeObject expects an object with a single key but got: ${JSON.stringify(args)}`);
        }
    }
    /**
     * @throws {@link OrValidationError}
     */
    Execute(val, semantics) {
        const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
        let keyCount = 1;
        if (leftToken instanceof OrToken) {
            keyCount = Object.keys(leftToken.ExtractSchema()).length;
        }
        const rightToken = this.createToken(this.val.args[1], this.getIdxForChildren() + keyCount, 'Or');
        if (val.prim === 'Right') {
            if (rightToken instanceof OrToken) {
                return rightToken.Execute(val.args[0], semantics);
            }
            else {
                return {
                    [rightToken.annot()]: rightToken.Execute(val.args[0], semantics),
                };
            }
        }
        else if (val.prim === 'Left') {
            if (leftToken instanceof OrToken) {
                return leftToken.Execute(val.args[0], semantics);
            }
            return {
                [leftToken.annot()]: leftToken.Execute(val.args[0], semantics),
            };
        }
        else {
            throw new OrValidationError(val, this, `Was expecting Left or Right prim but got: ${JSON.stringify(val.prim)}`);
        }
    }
    traversal(getLeftValue, getRightValue, concat) {
        const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
        let keyCount = 1;
        let leftValue;
        if (leftToken instanceof OrToken) {
            leftValue = getLeftValue(leftToken);
            keyCount = Object.keys(leftToken.ExtractSchema()).length;
        }
        else {
            leftValue = { [leftToken.annot()]: getLeftValue(leftToken) };
        }
        const rightToken = this.createToken(this.val.args[1], this.getIdxForChildren() + keyCount, 'Or');
        let rightValue;
        if (rightToken instanceof OrToken) {
            rightValue = getRightValue(rightToken);
        }
        else {
            rightValue = { [rightToken.annot()]: getRightValue(rightToken) };
        }
        const res = concat(leftValue, rightValue);
        return res;
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return this.traversal((leftToken) => leftToken.ExtractSchema(), (rightToken) => rightToken.ExtractSchema(), (leftValue, rightValue) => (Object.assign(Object.assign({}, leftValue), rightValue)));
    }
    generateSchema() {
        return {
            __michelsonType: OrToken.prim,
            schema: this.traversal((leftToken) => {
                if (leftToken instanceof OrToken) {
                    return leftToken.generateSchema().schema;
                }
                else {
                    return leftToken.generateSchema();
                }
            }, (rightToken) => {
                if (rightToken instanceof OrToken) {
                    return rightToken.generateSchema().schema;
                }
                else {
                    return rightToken.generateSchema();
                }
            }, (leftValue, rightValue) => (Object.assign(Object.assign({}, leftValue), rightValue))),
        };
    }
    findToken(label) {
        const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
        let keyCount = 1;
        if (leftToken instanceof OrToken) {
            keyCount = Object.keys(leftToken.ExtractSchema()).length;
        }
        const rightToken = this.createToken(this.val.args[1], this.getIdxForChildren() + keyCount, 'Or');
        if (String(leftToken.annot()) === String(label) &&
            !(leftToken instanceof OrToken) &&
            leftToken instanceof ComparableToken) {
            return leftToken;
        }
        else if (String(rightToken.annot()) === String(label) &&
            !(rightToken instanceof OrToken) &&
            rightToken instanceof ComparableToken) {
            return rightToken;
        }
        else {
            if (leftToken instanceof OrToken) {
                const tok = leftToken.findToken(label);
                if (tok) {
                    return tok;
                }
            }
            if (rightToken instanceof OrToken) {
                const tok = rightToken.findToken(label);
                if (tok) {
                    return tok;
                }
            }
            return null;
        }
    }
    compare(val1, val2) {
        const labelVal1 = Object.keys(val1)[0];
        const labelVal2 = Object.keys(val2)[0];
        if (labelVal1 === labelVal2) {
            const token = this.findToken(labelVal1);
            if (token instanceof ComparableToken) {
                return token.compare(val1[labelVal1], val2[labelVal1]);
            }
        }
        else {
            const encoded1 = JSON.stringify(this.EncodeObject(val1));
            const encoded2 = JSON.stringify(this.EncodeObject(val2));
            return encoded1 < encoded2 ? -1 : 1;
        }
    }
    ToKey(val) {
        return this.Execute(val);
    }
    ToBigMapKey(val) {
        return {
            key: this.EncodeObject(val),
            type: this.typeWithoutAnnotations(),
        };
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (OrToken.prim === tokenToFind) {
            tokens.push(this);
        }
        this.traversal((leftToken) => leftToken.findAndReturnTokens(tokenToFind, tokens), (rightToken) => rightToken.findAndReturnTokens(tokenToFind, tokens), (leftValue, rightValue) => (Object.assign(Object.assign({}, leftValue), rightValue)));
        return tokens;
    }
    getIdxForChildren() {
        if (Token.fieldNumberingStrategy === 'Legacy') {
            return this.idx;
        }
        return this.parentTokenType === 'Or' ? this.idx : 0;
    }
}
OrToken.prim = 'or';
/**
 *  @category Error
 *  @description Error that indicates in invalid token argument being passed
 */
class TokenArgumentValidationError extends TaquitoError {
    constructor(message) {
        super(message);
        this.message = message;
        this.name = 'TokenArgumentValidationError';
    }
}
/**
 *  @category Error
 *  @description Error that indicates a failure occurring when doing a comparison of tokens
 */
class TokenComparisonError extends TaquitoError {
    constructor(val1, val2) {
        super();
        this.val1 = val1;
        this.val2 = val2;
        this.name = 'TokenComparisonError';
        this.message = `Tokens ${JSON.stringify(val1)} and ${JSON.stringify(val2)} are not comparable`;
    }
}
// collapse comb pair
/**
 * @throws {@link TokenArgumentValidationError}
 */
function collapse$1(val, prim = PairToken.prim) {
    if (Array.isArray(val)) {
        return collapse$1({
            prim: prim,
            args: val,
        }, prim);
    }
    if (val.args === undefined) {
        throw new TokenArgumentValidationError(`The value ${JSON.stringify(val)} is an invalid PairToken with no arguments, a pair must have two or more arguments.`);
    }
    if (val.args.length > 2) {
        return [
            val.args[0],
            {
                prim: prim,
                args: val.args.slice(1),
            },
        ];
    }
    return [val.args[0], val.args[1]];
}
class PairToken extends ComparableToken {
    constructor(val, idx, fac, parentTokenType) {
        super(Array.isArray(val)
            ? {
                prim: PairToken.prim,
                args: val,
            }
            : val.prim
                ? val
                : {
                    prim: PairToken.prim,
                    args: val,
                }, idx, fac, parentTokenType);
    }
    args() {
        // collapse comb pair
        return collapse$1(this.val);
    }
    tokens() {
        let cnt = 0;
        return this.args().map((a) => {
            const tok = this.createToken(a, this.getIdxForChildren() + cnt, 'Pair');
            if (tok instanceof PairToken) {
                cnt += Object.keys(tok.ExtractSchema()).length;
            }
            else {
                cnt++;
            }
            return tok;
        });
    }
    Encode(args) {
        return {
            prim: 'Pair',
            args: this.tokens().map((t) => t.Encode(args)),
        };
    }
    ExtractSignature() {
        const args = this.args();
        const leftToken = this.createToken(args[0], this.getIdxForChildren(), 'Pair');
        let keyCount = 1;
        if (leftToken instanceof OrToken) {
            keyCount = Object.keys(leftToken.ExtractSchema()).length;
        }
        const rightToken = this.createToken(args[1], this.getIdxForChildren() + keyCount, 'Pair');
        const newSig = [];
        for (const leftSig of leftToken.ExtractSignature()) {
            for (const rightSig of rightToken.ExtractSignature()) {
                newSig.push([...leftSig, ...rightSig]);
            }
        }
        return newSig;
    }
    ToBigMapKey(val) {
        return {
            key: this.EncodeObject(val),
            type: this.typeWithoutAnnotations(),
        };
    }
    ToKey(val) {
        return this.Execute(val);
    }
    EncodeObject(args, semantic) {
        const [leftToken, rightToken] = this.tokens();
        let leftValue;
        if (leftToken instanceof PairToken && !leftToken.hasAnnotations()) {
            leftValue = args;
        }
        else {
            leftValue = args[leftToken.annot()];
        }
        let rightValue;
        if (rightToken instanceof PairToken && !rightToken.hasAnnotations()) {
            rightValue = args;
        }
        else {
            rightValue = args[rightToken.annot()];
        }
        return {
            prim: 'Pair',
            args: [
                leftToken.EncodeObject(leftValue, semantic),
                rightToken.EncodeObject(rightValue, semantic),
            ],
        };
    }
    traversal(getLeftValue, getRightValue) {
        const args = this.args();
        const leftToken = this.createToken(args[0], this.getIdxForChildren(), 'Pair');
        let keyCount = 1;
        let leftValue;
        if (leftToken instanceof PairToken && !leftToken.hasAnnotations()) {
            leftValue = getLeftValue(leftToken);
            if (leftToken instanceof PairToken) {
                keyCount = Object.keys(leftToken.ExtractSchema()).length;
            }
        }
        else {
            leftValue = { [leftToken.annot()]: getLeftValue(leftToken) };
        }
        const rightToken = this.createToken(args[1], this.getIdxForChildren() + keyCount, 'Pair');
        let rightValue;
        if (rightToken instanceof PairToken && !rightToken.hasAnnotations()) {
            rightValue = getRightValue(rightToken);
        }
        else {
            rightValue = { [rightToken.annot()]: getRightValue(rightToken) };
        }
        const res = Object.assign(Object.assign({}, leftValue), rightValue);
        return res;
    }
    Execute(val, semantics) {
        const args = collapse$1(val, 'Pair');
        return this.traversal((leftToken) => leftToken.Execute(args[0], semantics), (rightToken) => rightToken.Execute(args[1], semantics));
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return this.traversal((leftToken) => leftToken.ExtractSchema(), (rightToken) => rightToken.ExtractSchema());
    }
    generateSchema() {
        return {
            __michelsonType: PairToken.prim,
            schema: this.traversal((leftToken) => {
                if (leftToken instanceof PairToken && !leftToken.hasAnnotations()) {
                    return leftToken.generateSchema().schema;
                }
                else {
                    return leftToken.generateSchema();
                }
            }, (rightToken) => {
                if (rightToken instanceof PairToken && !rightToken.hasAnnotations()) {
                    return rightToken.generateSchema().schema;
                }
                else {
                    return rightToken.generateSchema();
                }
            }),
        };
    }
    /**
     * @throws {@link TokenComparisonError}
     */
    compare(val1, val2) {
        const [leftToken, rightToken] = this.tokens();
        const getValue = (token, args) => {
            if (token instanceof PairToken && !token.hasAnnotations()) {
                return args;
            }
            else {
                return args[token.annot()];
            }
        };
        if (leftToken instanceof ComparableToken && rightToken instanceof ComparableToken) {
            const result = leftToken.compare(getValue(leftToken, val1), getValue(leftToken, val2));
            if (result === 0) {
                return rightToken.compare(getValue(rightToken, val1), getValue(rightToken, val2));
            }
            return result;
        }
        throw new TokenComparisonError(val1, val2);
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (PairToken.prim === tokenToFind) {
            tokens.push(this);
        }
        this.tokens().map((t) => t.findAndReturnTokens(tokenToFind, tokens));
        return tokens;
    }
    getIdxForChildren() {
        if (Token.fieldNumberingStrategy === 'Legacy') {
            return this.idx;
        }
        return this.parentTokenType === 'Pair' ? this.idx : 0;
    }
}
PairToken.prim = 'pair';
/**
 *  @category Error
 *  @description Error that indicates a failure happening when parsing encoding/executing Nat
 */
class NatValidationError extends TokenValidationError {
    constructor(value, token, message) {
        super(value, token, message);
        this.value = value;
        this.token = token;
        this.name = 'NatValidationError';
    }
}
class NatToken extends ComparableToken {
    constructor(val, idx, fac) {
        super(val, idx, fac);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
    }
    Execute(val) {
        return new BigNumber(val[Object.keys(val)[0]]);
    }
    /**
     * @throws {@link NatValidationError}
     */
    Encode(args) {
        const val = args.pop();
        this.validate(val);
        return { int: new BigNumber(val).toFixed() };
    }
    /**
     * @throws {@link NatValidationError}
     */
    validate(val) {
        const bigNumber = new BigNumber(val);
        if (bigNumber.isNaN()) {
            throw new NatValidationError(val, this, `Value is not a number: ${JSON.stringify(val)}`);
        }
        if (bigNumber.isNegative()) {
            throw new NatValidationError(val, this, `Value cannot be negative: ${JSON.stringify(val)}`);
        }
    }
    /**
     * @throws {@link NatValidationError}
     */
    EncodeObject(val, semantic) {
        this.validate(val);
        if (semantic && semantic[NatToken.prim]) {
            return semantic[NatToken.prim](val);
        }
        return { int: new BigNumber(val).toFixed() };
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return NatToken.prim;
    }
    generateSchema() {
        return {
            __michelsonType: NatToken.prim,
            schema: NatToken.prim,
        };
    }
    ToBigMapKey(val) {
        return {
            key: { int: String(val) },
            type: { prim: NatToken.prim },
        };
    }
    ToKey({ int }) {
        return new BigNumber(int);
    }
    compare(nat1, nat2) {
        const o1 = Number(nat1);
        const o2 = Number(nat2);
        if (o1 === o2) {
            return 0;
        }
        return o1 < o2 ? -1 : 1;
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (NatToken.prim === tokenToFind) {
            tokens.push(this);
        }
        return tokens;
    }
}
NatToken.prim = 'nat';
class StringToken extends ComparableToken {
    constructor(val, idx, fac) {
        super(val, idx, fac);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
    }
    Execute(val) {
        return val[Object.keys(val)[0]];
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return StringToken.prim;
    }
    generateSchema() {
        return {
            __michelsonType: StringToken.prim,
            schema: StringToken.prim,
        };
    }
    Encode(args) {
        const val = args.pop();
        return { string: val };
    }
    EncodeObject(val, semantic) {
        if (semantic && semantic[StringToken.prim]) {
            return semantic[StringToken.prim](val);
        }
        return { string: val };
    }
    ToKey({ string }) {
        return string;
    }
    ToBigMapKey(val) {
        return {
            key: { string: val },
            type: { prim: StringToken.prim },
        };
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (StringToken.prim === tokenToFind) {
            tokens.push(this);
        }
        return tokens;
    }
}
StringToken.prim = 'string';
/**
 *  @category Error
 *  @description Error that indicates a failure happening when parsing encoding/executing an Address
 */
class AddressValidationError extends TokenValidationError {
    constructor(value, token, message) {
        super(value, token, message);
        this.value = value;
        this.token = token;
        this.name = 'AddressValidationError';
    }
}
class AddressToken extends ComparableToken {
    constructor(val, idx, fac) {
        super(val, idx, fac);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
    }
    ToBigMapKey(val) {
        const decoded = b58DecodeAddress(val);
        return {
            key: { bytes: decoded },
            type: { prim: 'bytes' },
        };
    }
    /**
     * @throws {@link AddressValidationError}
     */
    validate(value) {
        if (typeof value !== 'string') {
            throw new AddressValidationError(value, this, 'Type error');
        }
        if (validateAddress(value) !== ValidationResult.VALID) {
            throw new AddressValidationError(value, this, `Address is not valid: ${JSON.stringify(value)}`);
        }
    }
    /**
     * @throws {@link AddressValidationError}
     */
    Encode(args) {
        const val = args.pop();
        this.validate(val);
        return { string: val };
    }
    /**
     * @throws {@link AddressValidationError}
     */
    EncodeObject(val, semantic) {
        this.validate(val);
        if (semantic && semantic[AddressToken.prim]) {
            return semantic[AddressToken.prim](val);
        }
        return { string: val };
    }
    /**
     * @throws {@link AddressValidationError}
     */
    Execute(val) {
        if (val.string) {
            return val.string;
        }
        if (!val.bytes) {
            throw new AddressValidationError(val, this, `cannot be missing both string and bytes: ${JSON.stringify(val)}`);
        }
        return encodeAddress(val.bytes);
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return AddressToken.prim;
    }
    generateSchema() {
        return {
            __michelsonType: AddressToken.prim,
            schema: AddressToken.prim,
        };
    }
    /**
     * @throws {@link AddressValidationError}
     */
    ToKey({ bytes, string }) {
        if (string) {
            return string;
        }
        if (!bytes) {
            throw new AddressValidationError({ bytes, string }, this, `cannot be missing both string and bytes ${JSON.stringify({ string, bytes })}`);
        }
        return encodeAddress(bytes);
    }
    compare(address1, address2) {
        const [addr1, endpoint1] = splitAddress(address1);
        const [addr2, endpoint2] = splitAddress(address2);
        const ep1 = endpoint1 || '';
        const ep2 = endpoint2 || '';
        // binary type tag actually reflects the expected prefix order
        const bytes1 = b58DecodeAddress(addr1, 'array');
        const bytes2 = b58DecodeAddress(addr2, 'array');
        const res = compareArrays(bytes1, bytes2);
        if (res === 0) {
            return ep1 < ep2 ? -1 : ep1 > ep2 ? 1 : 0;
        }
        else {
            return res;
        }
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (AddressToken.prim === tokenToFind) {
            tokens.push(this);
        }
        return tokens;
    }
}
AddressToken.prim = 'address';
/**
 *  @category Error
 *  @description Error that indicates a failure happening when parsing encoding/executing a Map
 */
class MapValidationError extends TokenValidationError {
    constructor(value, token, message) {
        super(value, token, message);
        this.value = value;
        this.token = token;
        this.name = 'MapValidationError';
    }
}
class MapToken extends Token {
    constructor(val, idx, fac) {
        super(val, idx, fac);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
    }
    get ValueSchema() {
        return this.createToken(this.val.args[1], 0);
    }
    get KeySchema() {
        return this.createToken(this.val.args[0], 0);
    }
    /**
     * @throws {@link MapValidationError}
     */
    validate(value) {
        if (!MichelsonMap.isMichelsonMap(value)) {
            throw new MapValidationError(value, this, `Value ${JSON.stringify(value)} is not a valid MichelsonMap`);
        }
    }
    Execute(val, semantics) {
        const map = new MichelsonMap(this.val);
        val.forEach((current) => {
            map.set(this.KeySchema.ToKey(current.args[0]), this.ValueSchema.Execute(current.args[1], semantics));
        });
        return map;
    }
    objLitToMichelsonMap(val) {
        if (val instanceof MichelsonMap)
            return val;
        if (typeof val === 'object') {
            if (Object.keys(val).length === 0) {
                return new MichelsonMap();
            }
            else {
                return MichelsonMap.fromLiteral(val);
            }
        }
        return val;
    }
    /**
     * @throws {@link MapValidationError}
     */
    Encode(args) {
        const val = this.objLitToMichelsonMap(args.pop());
        this.validate(val);
        return Array.from(val.keys())
            .sort((a, b) => this.KeySchema.compare(a, b))
            .map((key) => {
            return {
                prim: 'Elt',
                args: [this.KeySchema.EncodeObject(key), this.ValueSchema.EncodeObject(val.get(key))],
            };
        });
    }
    /**
     * @throws {@link MapValidationError}
     */
    EncodeObject(args, semantic) {
        const val = this.objLitToMichelsonMap(args);
        this.validate(val);
        if (semantic && semantic[MapToken.prim]) {
            return semantic[MapToken.prim](val);
        }
        return Array.from(val.keys())
            .sort((a, b) => this.KeySchema.compare(a, b))
            .map((key) => {
            return {
                prim: 'Elt',
                args: [this.KeySchema.EncodeObject(key), this.ValueSchema.EncodeObject(val.get(key))],
            };
        });
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return {
            map: {
                key: this.KeySchema.ExtractSchema(),
                value: this.ValueSchema.ExtractSchema(),
            },
        };
    }
    generateSchema() {
        return {
            __michelsonType: MapToken.prim,
            schema: {
                key: this.KeySchema.generateSchema(),
                value: this.ValueSchema.generateSchema(),
            },
        };
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (MapToken.prim === tokenToFind) {
            tokens.push(this);
        }
        this.KeySchema.findAndReturnTokens(tokenToFind, tokens);
        this.ValueSchema.findAndReturnTokens(tokenToFind, tokens);
        return tokens;
    }
}
MapToken.prim = 'map';
class BoolToken extends ComparableToken {
    constructor(val, idx, fac) {
        super(val, idx, fac);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
    }
    Execute(val) {
        return String(val.prim).toLowerCase() === 'true' ? true : false;
    }
    Encode(args) {
        const val = args.pop();
        return { prim: val ? 'True' : 'False' };
    }
    EncodeObject(val, semantic) {
        if (semantic && semantic[BoolToken.prim]) {
            return semantic[BoolToken.prim](val);
        }
        return { prim: val ? 'True' : 'False' };
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return BoolToken.prim;
    }
    generateSchema() {
        return {
            __michelsonType: BoolToken.prim,
            schema: BoolToken.prim,
        };
    }
    ToBigMapKey(val) {
        return {
            key: this.EncodeObject(val),
            type: { prim: BoolToken.prim },
        };
    }
    ToKey(val) {
        return this.EncodeObject(val);
    }
    compare(val1, val2) {
        if ((val1 && val2) || (!val1 && !val2)) {
            return 0;
        }
        else if (val1) {
            return 1;
        }
        else {
            return -1;
        }
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (BoolToken.prim === tokenToFind) {
            tokens.push(this);
        }
        return tokens;
    }
}
BoolToken.prim = 'bool';
/**
 *  @category Error
 *  @description Error that indicates a failure happening when parsing encoding/executing a Contract
 */
class ContractValidationError extends TokenValidationError {
    constructor(value, token, message) {
        super(value, token, message);
        this.value = value;
        this.token = token;
        this.name = 'ContractValidationError';
    }
}
class ContractToken extends Token {
    constructor(val, idx, fac) {
        super(val, idx, fac);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
    }
    /**
     * @throws {@link ContractValidationError}
     */
    validate(value) {
        if (typeof value !== 'string') {
            throw new ContractValidationError(value, this, 'Type error');
        }
        // tz1,tz2 and tz3 seems to be valid contract values (for Unit contract)
        if (validateAddress(value) !== ValidationResult.VALID) {
            throw new ContractValidationError(value, this, `Value ${JSON.stringify(value)} is not a valid contract address.`);
        }
        return null;
    }
    /**
     * @throws {@link ContractValidationError}
     */
    Execute(val) {
        if (val.string) {
            return val.string;
        }
        if (!val.bytes) {
            throw new ContractValidationError(val, this, `Value ${JSON.stringify(val)} is not a valid contract address. must contain bytes or string.`);
        }
        return encodeAddress(val.bytes);
    }
    /**
     * @throws {@link ContractValidationError}
     */
    Encode(args) {
        const val = args.pop();
        this.validate(val);
        return { string: val };
    }
    /**
     * @throws {@link ContractValidationError}
     */
    EncodeObject(val, semantic) {
        this.validate(val);
        if (semantic && semantic[ContractToken.prim]) {
            return semantic[ContractToken.prim](val);
        }
        return { string: val };
    }
    /**
     * @deprecated ExtractSchema has been deprecated in favor of generateSchema
     *
     */
    ExtractSchema() {
        return ContractToken.prim;
    }
    generateSchema() {
        const valueSchema = this.createToken(this.val.args[0], 0);
        return {
            __michelsonType: ContractToken.prim,
            schema: {
                parameter: this.val.args[0] ? valueSchema.generateSchema() : {},
            },
        };
    }
    findAndReturnTokens(tokenToFind, tokens) {
        if (ContractToken.prim === tokenToFind) {
            tokens.push(this);
        }
        return tokens;
    }
}
ContractToken.prim = 'contract';
/**
 *  @category Error
 *  @description Error that indicates a failure happening when parsing encoding/executing a List
 */
class ListValidationError extends TokenValidationError {
    constructor(value, token, message) {
        super(value, token, message);
        this.value = value;
        this.token = token;
        this.name = 'ListValidationError';
    }
}
class ListToken extends Token {
    constructor(val, idx, fac) {
        super(val, idx, fac);
        this.val = val;
        this.idx = idx;
        this.fac = fac;
    }
    get valueSchema() {
        return this.createToken(this.val.args[0], this.idx);
    }
    /**
     * @throws {@link ListValidationError}
     */
    validate(value) {
        if (!Array.isArray(value)) {
            throw new ListValidationError(value, this, `Value ${JSON.stringify(value)} is not a valid array`);
        }
    }
    /**
     * @throws {@link ListValidationError}
     */
    Encode(args) {
        const val = args.pop();
        this.validate(val);
        const schema = this.createToken(this.val.args[0], 0);
        return val.reduce((prev, current) => {
            return [...prev, schema.EncodeObject(current)];
        }, []);
    }
    /**
     * @throws {@link ListValidationError}
     */
    Execute(val, semantics) {
        const schema = this.createToken(this.val.args[0], 0);
        this.validate(val);
        return val.reduce((prev, current) => {
            return [...prev, schema.Execute(current, semantics)];
        }, []);
    }
    /**
     * @throws {@link ListValidationError}
     */
    EncodeObject(args, semantic) {
        const schema = this.createToken(this.val.args[0], 0);
        this.validate(args);
        if (semantic && semantic[ListToken.prim]) {
            return semantic[ListToken.prim](args);
        }
        return args.reduce((prev, current) => {
            return [...prev, schema.EncodeObject(current)];
        }, []);
    }