import { PlatformContext, Wormhole, chainToPlatform, decimals, nativeChainIds, networkPlatformConfigs, } from '@wormhole-foundation/connect-sdk';
import { SolanaChain } from './chain';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { Connection, PublicKey, } from '@solana/web3.js';
import { SolanaAddress, SolanaZeroAddress } from './address';
import { _platform, } from './types';
import { isNative } from 'lodash';
/**
 * @category Solana
 */
export class SolanaPlatform extends PlatformContext {
    static _platform = _platform;
    constructor(network, config) {
        super(network, config ?? networkPlatformConfigs(network, SolanaPlatform._platform));
    }
    getRpc(chain, config = {
        commitment: 'confirmed',
        disableRetryOnRateLimit: true,
    }) {
        if (chain in this.config)
            return new Connection(this.config[chain].rpc, config);
        throw new Error('No configuration available for chain: ' + chain);
    }
    getChain(chain, rpc) {
        if (chain in this.config)
            return new SolanaChain(chain, this, rpc);
        throw new Error('No configuration available for chain: ' + chain);
    }
    static nativeTokenId(network, chain) {
        if (!SolanaPlatform.isSupportedChain(chain))
            throw new Error(`invalid chain: ${chain}`);
        return Wormhole.chainAddress(chain, SolanaZeroAddress);
    }
    static isNativeTokenId(network, chain, tokenId) {
        if (!this.isSupportedChain(chain))
            return false;
        if (tokenId.chain !== chain)
            return false;
        const native = this.nativeTokenId(network, chain);
        return native == tokenId;
    }
    static isSupportedChain(chain) {
        const platform = chainToPlatform(chain);
        return platform === SolanaPlatform._platform;
    }
    static async getDecimals(chain, rpc, token) {
        if (isNative(token))
            return decimals.nativeDecimals(SolanaPlatform._platform);
        let mint = await rpc.getParsedAccountInfo(new SolanaAddress(token).unwrap());
        if (!mint || !mint.value)
            throw new Error('could not fetch token details');
        const { decimals: numDecimals } = mint.value.data
            .parsed.info;
        return numDecimals;
    }
    static async getBalance(chain, rpc, walletAddress, token) {
        if (isNative(token))
            return BigInt(await rpc.getBalance(new PublicKey(walletAddress)));
        const splToken = await rpc.getTokenAccountsByOwner(new PublicKey(walletAddress), { mint: new SolanaAddress(token).unwrap() });
        if (!splToken.value[0])
            return null;
        const balance = await rpc.getTokenAccountBalance(splToken.value[0].pubkey);
        return BigInt(balance.value.amount);
    }
    static async getBalances(chain, rpc, walletAddress, tokens) {
        let native;
        if (tokens.includes('native')) {
            native = BigInt(await rpc.getBalance(new PublicKey(walletAddress)));
        }
        const splParsedTokenAccounts = await rpc.getParsedTokenAccountsByOwner(new PublicKey(walletAddress), {
            programId: new PublicKey(TOKEN_PROGRAM_ID),
        });
        const balancesArr = tokens.map((token) => {
            if (isNative(token)) {
                return { ['native']: native };
            }
            const addrString = new SolanaAddress(token).toString();
            const amount = splParsedTokenAccounts.value.find((v) => v?.account.data.parsed?.info?.mint === token)?.account.data.parsed?.info?.tokenAmount?.amount;
            if (!amount)
                return { [addrString]: null };
            return { [addrString]: BigInt(amount) };
        });
        return balancesArr.reduce((obj, item) => Object.assign(obj, item), {});
    }
    static async sendWait(chain, rpc, stxns, opts) {
        const { blockhash, lastValidBlockHeight } = await this.latestBlock(rpc);
        const txhashes = await Promise.all(stxns.map((stxn) => rpc.sendRawTransaction(stxn, 
        // Set the commitment level to match the rpc commitment level
        // otherwise, it defaults to finalized
        opts ?? { preflightCommitment: rpc.commitment })));
        const results = await Promise.all(txhashes.map((signature) => {
            return rpc.confirmTransaction({
                signature,
                blockhash,
                lastValidBlockHeight,
            }, rpc.commitment);
        }));
        const erroredTxs = results
            .filter((result) => result.value.err)
            .map((result) => result.value.err);
        if (erroredTxs.length > 0)
            throw new Error(`Failed to confirm transaction: ${erroredTxs}`);
        return txhashes;
    }
    static async latestBlock(rpc, commitment) {
        return rpc.getLatestBlockhash(commitment ?? rpc.commitment);
    }
    static async getLatestBlock(rpc) {
        return await rpc.getSlot();
    }
    static async getLatestFinalizedBlock(rpc) {
        const { lastValidBlockHeight } = await this.latestBlock(rpc, 'finalized');
        return lastValidBlockHeight;
    }
    static chainFromChainId(genesisHash) {
        const netChain = nativeChainIds.platformNativeChainIdToNetworkChain(SolanaPlatform._platform, genesisHash);
        if (!netChain)
            throw new Error(`No matching genesis hash to determine network and chain: ${genesisHash}`);
        const [network, chain] = netChain;
        return [network, chain];
    }
    static async chainFromRpc(rpc) {
        const gh = await rpc.getGenesisHash();
        return SolanaPlatform.chainFromChainId(gh);
    }
}
//# sourceMappingURL=platform.js.map