"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.getOriginalAssetSui = exports.getOriginalAssetAptos = exports.getOriginalAssetNear = exports.getOriginalAssetAlgorand = exports.getOriginalAssetSol = exports.getOriginalAssetSolana = exports.getOriginalAssetCosmWasm = exports.getOriginalAssetXpla = exports.getOriginalAssetInjective = exports.getOriginalAssetTerra = exports.getOriginalAssetEth = void 0;
const web3_js_1 = require("@solana/web3.js");
const ethers_1 = require("ethers");
const utils_1 = require("ethers/lib/utils");
const js_sha3_1 = require("js-sha3");
const algorand_1 = require("../algorand");
const cosmos_1 = require("../cosmos");
const address_1 = require("../cosmwasm/address");
const ethers_contracts_1 = require("../ethers-contracts");
const tokenBridge_1 = require("../solana/tokenBridge");
const sui_1 = require("../sui");
const terra_1 = require("../terra");
const utils_2 = require("../utils");
const bigint_1 = require("../utils/bigint");
const getIsWrappedAsset_1 = require("./getIsWrappedAsset");
/**
 * Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
 * @param tokenBridgeAddress
 * @param provider
 * @param wrappedAddress
 * @returns
 */
function getOriginalAssetEth(tokenBridgeAddress, provider, wrappedAddress, lookupChain) {
    return __awaiter(this, void 0, void 0, function* () {
        const isWrapped = yield getIsWrappedAsset_1.getIsWrappedAssetEth(tokenBridgeAddress, provider, wrappedAddress);
        if (isWrapped) {
            const token = ethers_contracts_1.TokenImplementation__factory.connect(wrappedAddress, provider);
            const chainId = (yield token.chainId()); // origin chain
            const assetAddress = yield token.nativeContract(); // origin address
            return {
                isWrapped: true,
                chainId,
                assetAddress: utils_1.arrayify(assetAddress),
            };
        }
        return {
            isWrapped: false,
            chainId: utils_2.coalesceChainId(lookupChain),
            assetAddress: utils_1.zeroPad(utils_1.arrayify(wrappedAddress), 32),
        };
    });
}
exports.getOriginalAssetEth = getOriginalAssetEth;
function getOriginalAssetTerra(client, wrappedAddress) {
    return __awaiter(this, void 0, void 0, function* () {
        return getOriginalAssetCosmWasm(client, wrappedAddress, utils_2.CHAIN_ID_TERRA);
    });
}
exports.getOriginalAssetTerra = getOriginalAssetTerra;
/**
 * Returns information about the asset
 * @param wrappedAddress Address of the asset in wormhole wrapped format (hex string)
 * @param client WASM api client
 * @returns Information about the asset
 */
function getOriginalAssetInjective(wrappedAddress, client) {
    return __awaiter(this, void 0, void 0, function* () {
        const chainId = utils_2.CHAIN_ID_INJECTIVE;
        if (address_1.isNativeCosmWasmDenom(chainId, wrappedAddress)) {
            return {
                isWrapped: false,
                chainId,
                assetAddress: utils_2.hexToUint8Array(address_1.buildTokenId(chainId, wrappedAddress)),
            };
        }
        try {
            const response = yield client.fetchSmartContractState(wrappedAddress, Buffer.from(JSON.stringify({
                wrapped_asset_info: {},
            })).toString("base64"));
            const parsed = utils_2.parseSmartContractStateResponse(response);
            return {
                isWrapped: true,
                chainId: parsed.asset_chain,
                assetAddress: new Uint8Array(Buffer.from(parsed.asset_address, "base64")),
            };
        }
        catch (_a) { }
        return {
            isWrapped: false,
            chainId: chainId,
            assetAddress: utils_2.hexToUint8Array(address_1.buildTokenId(chainId, wrappedAddress)),
        };
    });
}
exports.getOriginalAssetInjective = getOriginalAssetInjective;
function getOriginalAssetXpla(client, wrappedAddress) {
    return __awaiter(this, void 0, void 0, function* () {
        return getOriginalAssetCosmWasm(client, wrappedAddress, "xpla");
    });
}
exports.getOriginalAssetXpla = getOriginalAssetXpla;
function getOriginalAssetCosmWasm(client, wrappedAddress, lookupChain) {
    return __awaiter(this, void 0, void 0, function* () {
        const chainId = utils_2.coalesceCosmWasmChainId(lookupChain);
        if (address_1.isNativeCosmWasmDenom(chainId, wrappedAddress)) {
            return {
                isWrapped: false,
                chainId: chainId,
                assetAddress: chainId === utils_2.CHAIN_ID_TERRA
                    ? terra_1.buildNativeId(wrappedAddress)
                    : utils_2.hexToUint8Array(address_1.buildTokenId(chainId, wrappedAddress)),
            };
        }
        try {
            const result = yield client.wasm.contractQuery(wrappedAddress, {
                wrapped_asset_info: {},
            });
            if (result) {
                return {
                    isWrapped: true,
                    chainId: result.asset_chain,
                    assetAddress: new Uint8Array(Buffer.from(result.asset_address, "base64")),
                };
            }
        }
        catch (e) { }
        return {
            isWrapped: false,
            chainId: chainId,
            assetAddress: chainId === utils_2.CHAIN_ID_TERRA
                ? utils_1.zeroPad(cosmos_1.canonicalAddress(wrappedAddress), 32)
                : utils_2.hexToUint8Array(address_1.buildTokenId(chainId, wrappedAddress)),
        };
    });
}
exports.getOriginalAssetCosmWasm = getOriginalAssetCosmWasm;
/**
 * Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
 * @param connection
 * @param tokenBridgeAddress
 * @param mintAddress
 * @param [commitment]
 * @returns
 */
function getOriginalAssetSolana(connection, tokenBridgeAddress, mintAddress, commitment) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const mint = new web3_js_1.PublicKey(mintAddress);
            return tokenBridge_1.getWrappedMeta(connection, tokenBridgeAddress, mintAddress, commitment)
                .catch((_) => null)
                .then((meta) => {
                if (meta === null) {
                    return {
                        isWrapped: false,
                        chainId: utils_2.CHAIN_ID_SOLANA,
                        assetAddress: mint.toBytes(),
                    };
                }
                else {
                    return {
                        isWrapped: true,
                        chainId: meta.chain,
                        assetAddress: Uint8Array.from(meta.tokenAddress),
                    };
                }
            });
        }
        catch (_) {
            return {
                isWrapped: false,
                chainId: utils_2.CHAIN_ID_SOLANA,
                assetAddress: new Uint8Array(32),
            };
        }
    });
}
exports.getOriginalAssetSolana = getOriginalAssetSolana;
exports.getOriginalAssetSol = getOriginalAssetSolana;
/**
 * Returns an origin chain and asset address on {originChain} for a provided Wormhole wrapped address
 * @param client Algodv2 client
 * @param tokenBridgeId Application ID of the token bridge
 * @param assetId Algorand asset index
 * @returns wrapped wormhole information structure
 */
function getOriginalAssetAlgorand(client, tokenBridgeId, assetId) {
    return __awaiter(this, void 0, void 0, function* () {
        let retVal = {
            isWrapped: false,
            chainId: utils_2.CHAIN_ID_ALGORAND,
            assetAddress: new Uint8Array(),
        };
        retVal.isWrapped = yield getIsWrappedAsset_1.getIsWrappedAssetAlgorand(client, tokenBridgeId, assetId);
        if (!retVal.isWrapped) {
            retVal.assetAddress = utils_1.zeroPad(utils_1.arrayify(ethers_1.ethers.BigNumber.from(assetId)), 32);
            return retVal;
        }
        const assetInfo = yield client.getAssetByID(bigint_1.safeBigIntToNumber(assetId)).do();
        const lsa = assetInfo.params.creator;
        const dls = yield algorand_1.decodeLocalState(client, tokenBridgeId, lsa);
        const dlsBuffer = Buffer.from(dls);
        retVal.chainId = dlsBuffer.readInt16BE(92);
        retVal.assetAddress = new Uint8Array(dlsBuffer.slice(60, 60 + 32));
        return retVal;
    });
}
exports.getOriginalAssetAlgorand = getOriginalAssetAlgorand;
function getOriginalAssetNear(provider, tokenAccount, assetAccount) {
    return __awaiter(this, void 0, void 0, function* () {
        const retVal = {
            isWrapped: false,
            chainId: utils_2.CHAIN_ID_NEAR,
            assetAddress: new Uint8Array(),
        };
        retVal.isWrapped = getIsWrappedAsset_1.getIsWrappedAssetNear(tokenAccount, assetAccount);
        if (!retVal.isWrapped) {
            retVal.assetAddress = assetAccount
                ? utils_1.arrayify(utils_1.sha256(Buffer.from(assetAccount)))
                : utils_1.zeroPad(utils_1.arrayify("0x"), 32);
            return retVal;
        }
        const buf = yield utils_2.callFunctionNear(provider, tokenAccount, "get_original_asset", {
            token: assetAccount,
        });
        retVal.chainId = buf[1];
        retVal.assetAddress = utils_2.hexToUint8Array(buf[0]);
        return retVal;
    });
}
exports.getOriginalAssetNear = getOriginalAssetNear;
/**
 * Gets the origin chain ID and address of an asset on Aptos, given its fully qualified type.
 * @param client Client used to transfer data to/from Aptos node
 * @param tokenBridgePackageId Address of token bridge
 * @param fullyQualifiedType Fully qualified type of asset
 * @returns Original chain ID and address of asset
 */
function getOriginalAssetAptos(client, tokenBridgePackageId, fullyQualifiedType) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!utils_2.isValidAptosType(fullyQualifiedType)) {
            throw new Error("Invalid qualified type");
        }
        let originInfo;
        try {
            originInfo = (yield client.getAccountResource(fullyQualifiedType.split("::")[0], `${tokenBridgePackageId}::state::OriginInfo`)).data;
        }
        catch (_a) {
            return {
                isWrapped: false,
                chainId: utils_2.CHAIN_ID_APTOS,
                assetAddress: utils_2.hexToUint8Array(js_sha3_1.sha3_256(fullyQualifiedType)),
            };
        }
        if (!!originInfo) {
            // wrapped asset
            const chainId = parseInt(originInfo.token_chain.number);
            utils_2.assertChain(chainId);
            const assetAddress = utils_2.hexToUint8Array(originInfo.token_address.external_address.substring(2));
            return {
                isWrapped: true,
                chainId,
                assetAddress,
            };
        }
        else {
            // native asset
            return {
                isWrapped: false,
                chainId: utils_2.CHAIN_ID_APTOS,
                assetAddress: utils_2.hexToUint8Array(js_sha3_1.sha3_256(fullyQualifiedType)),
            };
        }
    });
}
exports.getOriginalAssetAptos = getOriginalAssetAptos;
function getOriginalAssetSui(provider, tokenBridgeStateObjectId, coinType) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!sui_1.isValidSuiType(coinType)) {
            throw new Error(`Invalid Sui type: ${coinType}`);
        }
        const res = yield sui_1.getTokenFromTokenRegistry(provider, tokenBridgeStateObjectId, coinType);
        const fields = sui_1.getFieldsFromObjectResponse(res);
        if (!fields) {
            throw new Error(`Token of type ${coinType} has not been registered with the token bridge`);
        }
        if (fields.value.type.includes(`wrapped_asset::WrappedAsset<${coinType}>`)) {
            return {
                isWrapped: true,
                chainId: Number(fields.value.fields.metadata.fields.token_chain),
                assetAddress: new Uint8Array(fields.value.fields.metadata.fields.token_address.fields.value.fields.data),
            };
        }
        else if (fields.value.type.includes(`native_asset::NativeAsset<${coinType}>`)) {
            return {
                isWrapped: false,
                chainId: utils_2.CHAIN_ID_SUI,
                assetAddress: new Uint8Array(fields.value.fields.token_address.fields.value.fields.data),
            };
        }
        throw new Error(`Unrecognized token metadata: ${JSON.stringify(fields, null, 2)}, ${coinType}`);
    });
}
exports.getOriginalAssetSui = getOriginalAssetSui;
