"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getEmitterAddressAndSequenceFromResponseSui = exports.getPackageId = exports.isValidSuiType = exports.isValidSuiAddress = exports.isSuiError = exports.getWrappedCoinType = exports.getTokenCoinType = exports.getTokenFromTokenRegistry = exports.getObjectFields = exports.getTableKeyType = exports.getPackageIdFromType = exports.getInnerType = exports.getFieldsFromObjectResponse = exports.executeTransactionBlock = void 0;
const sui_js_1 = require("@mysten/sui.js");
const utils_1 = require("../utils");
const executeTransactionBlock = (signer, transactionBlock) => __awaiter(void 0, void 0, void 0, function* () {
    // Let caller handle parsing and logging info
    transactionBlock.setGasBudget(100000000);
    return signer.signAndExecuteTransactionBlock({
        transactionBlock,
        options: {
            showInput: true,
            showEffects: true,
            showEvents: true,
            showObjectChanges: true,
        },
    });
});
exports.executeTransactionBlock = executeTransactionBlock;
const getFieldsFromObjectResponse = (object) => {
    var _a;
    const content = (_a = object.data) === null || _a === void 0 ? void 0 : _a.content;
    return content && content.dataType === "moveObject" ? content.fields : null;
};
exports.getFieldsFromObjectResponse = getFieldsFromObjectResponse;
const getInnerType = (type) => {
    if (!type)
        return null;
    const match = type.match(/<(.*)>/);
    if (!match || !exports.isValidSuiType(match[1])) {
        return null;
    }
    return match[1];
};
exports.getInnerType = getInnerType;
const getPackageIdFromType = (type) => {
    if (!exports.isValidSuiType(type))
        return null;
    const packageId = type.split("::")[0];
    if (!exports.isValidSuiAddress(packageId))
        return null;
    return packageId;
};
exports.getPackageIdFromType = getPackageIdFromType;
const getTableKeyType = (tableType) => {
    if (!tableType)
        return null;
    const match = tableType.match(/0x2::table::Table<(.*)>/);
    if (!match)
        return null;
    const [keyType] = match[1].split(",");
    if (!exports.isValidSuiType(keyType))
        return null;
    return keyType;
};
exports.getTableKeyType = getTableKeyType;
const getObjectFields = (provider, objectId) => __awaiter(void 0, void 0, void 0, function* () {
    if (!exports.isValidSuiAddress(objectId)) {
        throw new Error(`Invalid object ID: ${objectId}`);
    }
    const res = yield provider.getObject({
        id: objectId,
        options: {
            showContent: true,
        },
    });
    return exports.getFieldsFromObjectResponse(res);
});
exports.getObjectFields = getObjectFields;
const getTokenFromTokenRegistry = (provider, tokenBridgeStateObjectId, tokenType) => __awaiter(void 0, void 0, void 0, function* () {
    var _a, _b, _c, _d;
    if (!exports.isValidSuiType(tokenType)) {
        throw new Error(`Invalid Sui type: ${tokenType}`);
    }
    const tokenBridgeStateFields = yield exports.getObjectFields(provider, tokenBridgeStateObjectId);
    if (!tokenBridgeStateFields) {
        throw new Error(`Unable to fetch object fields from token bridge state. Object ID: ${tokenBridgeStateObjectId}`);
    }
    const tokenRegistryObjectId = (_c = (_b = (_a = tokenBridgeStateFields.token_registry) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.id) === null || _c === void 0 ? void 0 : _c.id;
    if (!tokenRegistryObjectId) {
        throw new Error("Unable to fetch token registry object ID");
    }
    const tokenRegistryPackageId = exports.getPackageIdFromType((_d = tokenBridgeStateFields.token_registry) === null || _d === void 0 ? void 0 : _d.type);
    if (!tokenRegistryObjectId) {
        throw new Error("Unable to fetch token registry package ID");
    }
    return provider.getDynamicFieldObject({
        parentId: tokenRegistryObjectId,
        name: {
            type: `${tokenRegistryPackageId}::token_registry::Key<${tokenType}>`,
            value: {
                dummy_field: false,
            },
        },
    });
});
exports.getTokenFromTokenRegistry = getTokenFromTokenRegistry;
const getTokenCoinType = (provider, tokenBridgeStateObjectId, tokenAddress, tokenChain) => __awaiter(void 0, void 0, void 0, function* () {
    var _e, _f, _g, _h, _j;
    const tokenBridgeStateFields = yield exports.getObjectFields(provider, tokenBridgeStateObjectId);
    if (!tokenBridgeStateFields) {
        throw new Error("Unable to fetch object fields from token bridge state");
    }
    const coinTypes = (_f = (_e = tokenBridgeStateFields === null || tokenBridgeStateFields === void 0 ? void 0 : tokenBridgeStateFields.token_registry) === null || _e === void 0 ? void 0 : _e.fields) === null || _f === void 0 ? void 0 : _f.coin_types;
    const coinTypesObjectId = (_h = (_g = coinTypes === null || coinTypes === void 0 ? void 0 : coinTypes.fields) === null || _g === void 0 ? void 0 : _g.id) === null || _h === void 0 ? void 0 : _h.id;
    if (!coinTypesObjectId) {
        throw new Error("Unable to fetch coin types");
    }
    const keyType = exports.getTableKeyType(coinTypes === null || coinTypes === void 0 ? void 0 : coinTypes.type);
    if (!keyType) {
        throw new Error("Unable to get key type");
    }
    try {
        // This call throws if the key doesn't exist in CoinTypes
        const coinTypeValue = yield provider.getDynamicFieldObject({
            parentId: coinTypesObjectId,
            name: {
                type: keyType,
                value: {
                    addr: [...tokenAddress],
                    chain: tokenChain,
                },
            },
        });
        const fields = exports.getFieldsFromObjectResponse(coinTypeValue);
        return (fields === null || fields === void 0 ? void 0 : fields.value) ? utils_1.ensureHexPrefix(fields.value) : null;
    }
    catch (e) {
        if (e.code === -32000 && ((_j = e.message) === null || _j === void 0 ? void 0 : _j.includes("RPC Error"))) {
            return null;
        }
        throw e;
    }
});
exports.getTokenCoinType = getTokenCoinType;
/**
 * Get the fully qualified type of a wrapped asset published to the given
 * package ID.
 *
 * All wrapped assets that are registered with the token bridge must satisfy
 * the requirement that module name is `coin` (source: https://github.com/wormhole-foundation/wormhole/blob/a1b3773ee42507122c3c4c3494898fbf515d0712/sui/token_bridge/sources/create_wrapped.move#L88).
 * As a result, all wrapped assets share the same module name and struct name,
 * since the struct name is necessarily `COIN` since it is a OTW.
 * @param coinPackageId packageId of the wrapped asset
 * @returns Fully qualified type of the wrapped asset
 */
const getWrappedCoinType = (coinPackageId) => {
    if (!exports.isValidSuiAddress(coinPackageId)) {
        throw new Error(`Invalid package ID: ${coinPackageId}`);
    }
    return `${coinPackageId}::coin::COIN`;
};
exports.getWrappedCoinType = getWrappedCoinType;
const isSuiError = (error) => {
    return (error && typeof error === "object" && "code" in error && "message" in error);
};
exports.isSuiError = isSuiError;
/**
 * This method validates any Sui address, even if it's not 32 bytes long, i.e.
 * "0x2". This differs from Mysten's implementation, which requires that the
 * given address is 32 bytes long.
 * @param address Address to check
 * @returns If given address is a valid Sui address or not
 */
const isValidSuiAddress = (address) => sui_js_1.isValidSuiAddress(sui_js_1.normalizeSuiAddress(address));
exports.isValidSuiAddress = isValidSuiAddress;
const isValidSuiType = (type) => {
    const tokens = type.split("::");
    if (tokens.length !== 3) {
        return false;
    }
    return exports.isValidSuiAddress(tokens[0]) && !!tokens[1] && !!tokens[2];
};
exports.isValidSuiType = isValidSuiType;
const getPackageId = (provider, objectId) => __awaiter(void 0, void 0, void 0, function* () {
    const fields = yield exports.getObjectFields(provider, objectId);
    if (fields && "upgrade_cap" in fields) {
        return fields.upgrade_cap.fields.package;
    }
    throw new Error("upgrade_cap not found");
});
exports.getPackageId = getPackageId;
// TODO: can we pass in the latest core bridge package Id after an upgrade?
// or do we have to use the first one?
// this is the same type that the guardian will look for
const getEmitterAddressAndSequenceFromResponseSui = (coreBridgePackageId, response) => {
    var _a;
    const eventType = `${coreBridgePackageId}::publish_message::WormholeMessage`;
    const event = (_a = response.events) === null || _a === void 0 ? void 0 : _a.find((e) => e.type === eventType);
    if (event === undefined) {
        throw new Error(`${eventType} event type not found`);
    }
    const { sender, sequence } = event.parsedJson || {};
    if (sender === undefined || sequence === undefined) {
        throw new Error("Can't find sender or sequence");
    }
    return { emitterAddress: sender.substring(2), sequence };
};
exports.getEmitterAddressAndSequenceFromResponseSui = getEmitterAddressAndSequenceFromResponseSui;
