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());
    });
};
import { builder, getObjectType, isValidSuiAddress as isValidFullSuiAddress, normalizeSuiAddress, } from "@mysten/sui.js";
import { ensureHexPrefix } from "../utils";
import { SuiRpcValidationError } from "./error";
const MAX_PURE_ARGUMENT_SIZE = 16 * 1024;
const UPGRADE_CAP_TYPE = "0x2::package::UpgradeCap";
export const uint8ArrayToBCS = (arr) => builder.ser("vector<u8>", arr, { maxSize: MAX_PURE_ARGUMENT_SIZE }).toBytes();
export 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,
        },
    });
});
// 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
export const getEmitterAddressAndSequenceFromResponseSui = (originalCoreBridgePackageId, response) => {
    var _a;
    const wormholeMessageEventType = `${originalCoreBridgePackageId}::publish_message::WormholeMessage`;
    const event = (_a = response.events) === null || _a === void 0 ? void 0 : _a.find((e) => isSameType(e.type, wormholeMessageEventType));
    if (event === undefined) {
        throw new Error(`${wormholeMessageEventType} 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 };
};
export 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;
};
export const getInnerType = (type) => {
    if (!type)
        return null;
    const match = type.match(/<(.*)>/);
    if (!match || !isValidSuiType(match[1])) {
        return null;
    }
    return match[1];
};
export const getObjectFields = (provider, objectId) => __awaiter(void 0, void 0, void 0, function* () {
    if (!isValidSuiAddress(objectId)) {
        throw new Error(`Invalid object ID: ${objectId}`);
    }
    const res = yield provider.getObject({
        id: objectId,
        options: {
            showContent: true,
        },
    });
    return getFieldsFromObjectResponse(res);
});
export const getOriginalPackageId = (provider, stateObjectId) => __awaiter(void 0, void 0, void 0, function* () {
    var _a;
    return (_a = getObjectType(yield provider.getObject({
        id: stateObjectId,
        options: { showContent: true },
    }))) === null || _a === void 0 ? void 0 : _a.split("::")[0];
});
export const getOwnedObjectId = (provider, owner, type) => __awaiter(void 0, void 0, void 0, function* () {
    var _b, _c;
    // Upgrade caps are a special case
    if (isSameType(type, UPGRADE_CAP_TYPE)) {
        throw new Error("`getOwnedObjectId` should not be used to get the object ID of an `UpgradeCap`. Use `getUpgradeCapObjectId` instead.");
    }
    try {
        const res = yield provider.getOwnedObjects({
            owner,
            filter: { StructType: type },
            options: {
                showContent: true,
            },
        });
        if (!res || !res.data) {
            throw new SuiRpcValidationError(res);
        }
        const objects = res.data.filter((o) => { var _a; return (_a = o.data) === null || _a === void 0 ? void 0 : _a.objectId; });
        if (objects.length === 1) {
            return (_c = (_b = objects[0].data) === null || _b === void 0 ? void 0 : _b.objectId) !== null && _c !== void 0 ? _c : null;
        }
        else if (objects.length > 1) {
            const objectsStr = JSON.stringify(objects, null, 2);
            throw new Error(`Found multiple objects owned by ${owner} of type ${type}. This may mean that we've received an unexpected response from the Sui RPC and \`worm\` logic needs to be updated to handle this. Objects: ${objectsStr}`);
        }
        else {
            return null;
        }
    }
    catch (error) {
        // Handle 504 error by using findOwnedObjectByType method
        const is504HttpError = `${error}`.includes("504 Gateway Time-out");
        if (error && is504HttpError) {
            return getOwnedObjectIdPaginated(provider, owner, type);
        }
        else {
            throw error;
        }
    }
});
export const getOwnedObjectIdPaginated = (provider, owner, type, cursor) => __awaiter(void 0, void 0, void 0, function* () {
    var _d, _e;
    const res = yield provider.getOwnedObjects({
        owner,
        filter: undefined,
        cursor: cursor || undefined,
        options: {
            showType: true,
        },
    });
    if (!res || !res.data) {
        throw new SuiRpcValidationError(res);
    }
    const object = res.data.find((d) => { var _a; return isSameType(((_a = d.data) === null || _a === void 0 ? void 0 : _a.type) || "", type); });
    if (!object && res.hasNextPage) {
        return getOwnedObjectIdPaginated(provider, owner, type, res.nextCursor);
    }
    else if (!object && !res.hasNextPage) {
        return null;
    }
    else {
        return (_e = (_d = object === null || object === void 0 ? void 0 : object.data) === null || _d === void 0 ? void 0 : _d.objectId) !== null && _e !== void 0 ? _e : null;
    }
});
/**
 * @param provider
 * @param objectId Core or token bridge state object ID
 * @returns The latest package ID for the provided state object
 */
export function getPackageId(provider, objectId) {
    var _a, _b;
    return __awaiter(this, void 0, void 0, function* () {
        let currentPackage;
        let nextCursor;
        do {
            const dynamicFields = yield provider.getDynamicFields({
                parentId: objectId,
                cursor: nextCursor,
            });
            currentPackage = dynamicFields.data.find((field) => field.name.type.endsWith("CurrentPackage"));
            nextCursor = dynamicFields.hasNextPage ? dynamicFields.nextCursor : null;
        } while (nextCursor && !currentPackage);
        if (!currentPackage) {
            throw new Error("CurrentPackage not found");
        }
        const fields = yield getObjectFields(provider, currentPackage.objectId);
        const packageId = (_b = (_a = fields === null || fields === void 0 ? void 0 : fields.value) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.package;
        if (!packageId) {
            throw new Error("Unable to get current package");
        }
        return packageId;
    });
}
export const getPackageIdFromType = (type) => {
    if (!isValidSuiType(type))
        return null;
    const packageId = type.split("::")[0];
    if (!isValidSuiAddress(packageId))
        return null;
    return packageId;
};
export const getTableKeyType = (tableType) => {
    if (!tableType)
        return null;
    const match = trimSuiType(tableType).match(/0x2::table::Table<(.*)>/);
    if (!match)
        return null;
    const [keyType] = match[1].split(",");
    if (!isValidSuiType(keyType))
        return null;
    return keyType;
};
export const getTokenCoinType = (provider, tokenBridgeStateObjectId, tokenAddress, tokenChain) => __awaiter(void 0, void 0, void 0, function* () {
    var _f, _g, _h, _j;
    const tokenBridgeStateFields = yield getObjectFields(provider, tokenBridgeStateObjectId);
    if (!tokenBridgeStateFields) {
        throw new Error("Unable to fetch object fields from token bridge state");
    }
    const coinTypes = (_g = (_f = tokenBridgeStateFields === null || tokenBridgeStateFields === void 0 ? void 0 : tokenBridgeStateFields.token_registry) === null || _f === void 0 ? void 0 : _f.fields) === null || _g === void 0 ? void 0 : _g.coin_types;
    const coinTypesObjectId = (_j = (_h = coinTypes === null || coinTypes === void 0 ? void 0 : coinTypes.fields) === null || _h === void 0 ? void 0 : _h.id) === null || _j === void 0 ? void 0 : _j.id;
    if (!coinTypesObjectId) {
        throw new Error("Unable to fetch coin types");
    }
    const keyType = getTableKeyType(coinTypes === null || coinTypes === void 0 ? void 0 : coinTypes.type);
    if (!keyType) {
        throw new Error("Unable to get key type");
    }
    const response = yield provider.getDynamicFieldObject({
        parentId: coinTypesObjectId,
        name: {
            type: keyType,
            value: {
                addr: [...tokenAddress],
                chain: tokenChain,
            },
        },
    });
    if (response.error) {
        if (response.error.code === "dynamicFieldNotFound") {
            return null;
        }
        throw new Error(`Unexpected getDynamicFieldObject response ${response.error}`);
    }
    const fields = getFieldsFromObjectResponse(response);
    return (fields === null || fields === void 0 ? void 0 : fields.value) ? trimSuiType(ensureHexPrefix(fields.value)) : null;
});
export const getTokenFromTokenRegistry = (provider, tokenBridgeStateObjectId, tokenType) => __awaiter(void 0, void 0, void 0, function* () {
    var _k, _l, _m, _o;
    if (!isValidSuiType(tokenType)) {
        throw new Error(`Invalid Sui type: ${tokenType}`);
    }
    const tokenBridgeStateFields = yield getObjectFields(provider, tokenBridgeStateObjectId);
    if (!tokenBridgeStateFields) {
        throw new Error(`Unable to fetch object fields from token bridge state. Object ID: ${tokenBridgeStateObjectId}`);
    }
    const tokenRegistryObjectId = (_m = (_l = (_k = tokenBridgeStateFields.token_registry) === null || _k === void 0 ? void 0 : _k.fields) === null || _l === void 0 ? void 0 : _l.id) === null || _m === void 0 ? void 0 : _m.id;
    if (!tokenRegistryObjectId) {
        throw new Error("Unable to fetch token registry object ID");
    }
    const tokenRegistryPackageId = getPackageIdFromType((_o = tokenBridgeStateFields.token_registry) === null || _o === void 0 ? void 0 : _o.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,
            },
        },
    });
});
/**
 * This function returns the object ID of the `UpgradeCap` that belongs to the
 * given package and owner if it exists.
 *
 * Structs created by the Sui framework such as `UpgradeCap`s all have the same
 * type (e.g. `0x2::package::UpgradeCap`) and have a special field, `package`,
 * we can use to differentiate them.
 * @param provider Sui RPC provider
 * @param owner Address of the current owner of the `UpgradeCap`
 * @param packageId ID of the package that the `UpgradeCap` was created for
 * @returns The object ID of the `UpgradeCap` if it exists, otherwise `null`
 */
export const getUpgradeCapObjectId = (provider, owner, packageId) => __awaiter(void 0, void 0, void 0, function* () {
    var _p, _q;
    const res = yield provider.getOwnedObjects({
        owner,
        filter: { StructType: padSuiType(UPGRADE_CAP_TYPE) },
        options: {
            showContent: true,
        },
    });
    if (!res || !res.data) {
        throw new SuiRpcValidationError(res);
    }
    const objects = res.data.filter((o) => {
        var _a, _b, _c, _d, _e, _f;
        return ((_a = o.data) === null || _a === void 0 ? void 0 : _a.objectId) &&
            ((_c = (_b = o.data) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.dataType) === "moveObject" &&
            normalizeSuiAddress((_f = (_e = (_d = o.data) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.fields) === null || _f === void 0 ? void 0 : _f.package) ===
                normalizeSuiAddress(packageId);
    });
    if (objects.length === 1) {
        // We've found the object we're looking for
        return (_q = (_p = objects[0].data) === null || _p === void 0 ? void 0 : _p.objectId) !== null && _q !== void 0 ? _q : null;
    }
    else if (objects.length > 1) {
        const objectsStr = JSON.stringify(objects, null, 2);
        throw new Error(`Found multiple upgrade capabilities owned by ${owner} from package ${packageId}. Objects: ${objectsStr}`);
    }
    else {
        return null;
    }
});
/**
 * 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
 */
export const getWrappedCoinType = (coinPackageId) => {
    if (!isValidSuiAddress(coinPackageId)) {
        throw new Error(`Invalid package ID: ${coinPackageId}`);
    }
    return `${coinPackageId}::coin::COIN`;
};
export const isSameType = (a, b) => {
    try {
        return trimSuiType(a) === trimSuiType(b);
    }
    catch (e) {
        return false;
    }
};
export const isSuiError = (error) => {
    return (error && typeof error === "object" && "code" in error && "message" in error);
};
/**
 * 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
 */
export const isValidSuiAddress = (address) => isValidFullSuiAddress(normalizeSuiAddress(address));
export const isValidSuiType = (type) => {
    const tokens = type.split("::");
    if (tokens.length !== 3) {
        return false;
    }
    return isValidSuiAddress(tokens[0]) && !!tokens[1] && !!tokens[2];
};
/**
 * Unlike `trimSuiType`, this method does not modify nested types, it just pads
 * the top-level type.
 * @param type
 * @returns
 */
export const padSuiType = (type) => {
    const tokens = type.split("::");
    if (tokens.length < 3 || !isValidSuiAddress(tokens[0])) {
        throw new Error(`Invalid Sui type: ${type}`);
    }
    return [normalizeSuiAddress(tokens[0]), ...tokens.slice(1)].join("::");
};
/**
 * This method removes leading zeroes for types in order to normalize them
 * since some types returned from the RPC have leading zeroes and others don't.
 */
export const trimSuiType = (type) => type.replace(/(0x)(0*)/g, "0x");
