import fs from 'fs';
import fetch from 'node-fetch';
import { promisify  } from 'util';
import * as evm from './evm';
import * as anchor from '@project-serum/anchor';
import { Solana as SolanaTypes} from '../chains/solana/target/types/solana';
import { findProgramAddressSync } from '@project-serum/anchor/dist/cjs/utils/pubkey';
import { 
    attestFromSolana, 
    createWrappedOnSolana, 
    getEmitterAddressEth,
    getEmitterAddressSolana, 
    getForeignAssetSolana, 
    parseSequenceFromLogSolana, 
    postVaaSolanaWithRetry, 
    setDefaultWasm, 
    tryNativeToUint8Array,
    importCoreWasm, 
    transferFromSolana,
    redeemOnSolana
} from '@certusone/wormhole-sdk';
import * as byteify from 'byteify';
import keccak256 from "keccak256";
import {
    getAccount,
    getOrCreateAssociatedTokenAccount,
    createMint,
    TOKEN_PROGRAM_ID,
    approve,
    createWrappedNativeAccount,
    closeAccount
} from '@solana/spl-token'
import {
    PROGRAM_ID as metaplexProgramID,
} from '@metaplex-foundation/mpl-token-metadata';
import { parseUnits } from 'ethers/lib/utils';

const exec = promisify(require('child_process').exec);
const config = JSON.parse(fs.readFileSync('./xdapp.config.json').toString());
const IDL = JSON.parse(fs.readFileSync('./chains/solana/target/idl/solana.json').toString());
const CONTRACT_ADDRESS = "BHz6MJGvo8PJaBFqaxyzgJYdY6o8h1rBgsRrUmnHCU9k";

export async function deploy(src: string){
    const rpc = config.networks[src].rpc;
    const core = config.networks[src].bridgeAddress;
    const token = config.networks[src].tokenBridgeAddress;
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString()))));
    const connection = new anchor.web3.Connection(rpc);

    // Request Airdrop for saved keypair
    console.log("Requesting Funds...");
    await connection.requestAirdrop(srcKey.publicKey, 1e9*1000); //request 1000 SOL
    await new Promise((r) => setTimeout(r, 20000)); //wait for the airdrop to go through
    console.log(`${srcKey.publicKey.toString()} balance: `, await (connection.getBalance(srcKey.publicKey)));

    //Compile and Deploy Solana Code
    console.log("Compiling and deploying solana code takes a min, hang on...");
    const cmd = `cd chains/solana && solana config set -u ${rpc} -k keypairs/id.json && anchor build && anchor deploy --program-name solana --program-keypair keypairs/solana-keypair.json && exit`
    
    const {stdout, stderr} = await exec (cmd);
    console.log(stdout);

    // Also initalize the contract
    //Initalize the Contract
    const xmint = new anchor.Program<SolanaTypes>(
        IDL,
        CONTRACT_ADDRESS,
        new anchor.AnchorProvider(
            new anchor.web3.Connection(rpc),
            new anchor.Wallet(srcKey),
            {}));
    
    const [configAcc, _] = findProgramAddressSync([Buffer.from("config")], xmint.programId);
    
    // Deploy the SPL Token for Xmint
    const mintAuthorityPDA = findProgramAddressSync([Buffer.from("mint_authority")], xmint.programId)[0];

    const mint = await createMint(
        connection,
        srcKey,
        mintAuthorityPDA, 
        mintAuthorityPDA, 
        9, // We are using 9 to match the CLI decimal default exactly
    );

    await new Promise((r) => setTimeout(r, 15000)) // wait for the chain to recognize the program

    const metadataAccount = findProgramAddressSync([
        Buffer.from("metadata"),
        metaplexProgramID.toBuffer(),
        mint.toBuffer()
    ], metaplexProgramID)[0]

    const redeemerAcc = findProgramAddressSync([Buffer.from("redeemer")], xmint.programId)[0];

    // Initalize will also CPI into metaplex metadata program to setup metadata for the token
    await xmint.methods
            .initialize(
                `${src}-TOKEN`,
                `${src}T`,
                ""
            )
            .accounts({
                config: configAcc,
                owner: srcKey.publicKey,
                systemProgram: anchor.web3.SystemProgram.programId,
                mint: mint,
                mintAuthority: mintAuthorityPDA,
                rent: anchor.web3.SYSVAR_RENT_PUBKEY,
                metadataAccount: metadataAccount,
                metadataProgram: metaplexProgramID,
                redeemer: redeemerAcc
            })
            .rpc();

    await new Promise((r) => setTimeout(r, 15000)) // wait for the chain to recognize the metadata

    // Store deploy info
    fs.writeFileSync(`./deployinfo/${src}.deploy.json`, JSON.stringify({
        address: CONTRACT_ADDRESS,
        tokenAddress: mint,
        tokenReceipientAddress: redeemerAcc.toString(),
        vaas: []
    }, null, 4));
}

/**
 * Attest token from src chain and create wrapped on target chain
 * @param src 
 * @param target 
 * @param address 
 */
export async function attest(src: string, target:string, address:string = null){
    const srcNetwork = config.networks[src];
    const targetNetwork = config.networks[target];
    const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
    )));
    const connection = new anchor.web3.Connection(srcNetwork.rpc);

    // If we don't pass in an address (like for WSOL) we assume we want to attest the xmint token
    if(!address){
        address = srcDeployInfo.tokenAddress;
    }
    console.log(`Attesting ${address} from ${src} network onto ${target}`);
    
    setDefaultWasm("node");
    const tx = await attestFromSolana(
        connection,
        srcNetwork.bridgeAddress,
        srcNetwork.tokenBridgeAddress,
        srcKey.publicKey.toString(),
        address 
    );
    tx.partialSign(srcKey);
    const txid = await connection.sendRawTransaction(tx.serialize());
    console.log("TXID: ", txid);

    const attestVaa = await fetchVaa(src, txid, true);
    switch(targetNetwork.type){
        case "evm":
            await evm.createWrapped(target, src, attestVaa);
            break;
        case "solana":
            await createWrapped(target, src, attestVaa);
    }
}

/**
 * Fetches the signed VAA from the Gaurdian. Will use xmint as emitter if portal=false
 * @param src 
 * @param tx 
 * @param portal 
 */
async function fetchVaa(src:string, tx:string, portal:boolean=false):Promise<string>{
    const srcNetwork = config.networks[src];
    const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
    )));
    const connection = new anchor.web3.Connection(srcNetwork.rpc);
   
    setDefaultWasm("node");
    const emitterAddr = portal ? await getEmitterAddressSolana(srcNetwork.tokenBridgeAddress) : await getEmitterAddressSolana(srcDeployInfo.address);
    let transaction = await connection.getTransaction(tx); 
    let timeElapsed = 0;
    while (!transaction) {
        await new Promise((r) => setTimeout(r, 1000)) // wait for the chain to recognize the program
        transaction = await connection.getTransaction(tx);
        timeElapsed += 1;
    }
    console.log(`VAA from TX(${tx}) found in ${timeElapsed}s`);
    const seq = parseSequenceFromLogSolana(transaction);
    await new Promise((r) => setTimeout(r, 5000)); //wait for gaurdian to pick up messsage
    console.log(
        "Searching for: ",
        `${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
    );
    const vaaBytes = await (
        await fetch(
            `${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
        )
    ).json();

    if(!vaaBytes['vaaBytes']){
        throw new Error("VAA not found!");
    }

    console.log("VAA Found: ", vaaBytes.vaaBytes);
    return vaaBytes.vaaBytes;
}


export async function createWrapped(src:string, target:string, vaa:string){
    const srcNetwork = config.networks[src];
    const targetNetwork = config.networks[target];
    const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
    )));
    const connection = new anchor.web3.Connection(srcNetwork.rpc);

    setDefaultWasm("node");
    //Have to Post the VAA first before we can use it
    await postVaaSolanaWithRetry(
        connection,
        async (transaction) => {
            transaction.partialSign(srcKey);
            return transaction;
        },
        srcNetwork.bridgeAddress,
        srcKey.publicKey.toString(),
        Buffer.from(vaa, "base64"),
        10
    );

    const tx = await createWrappedOnSolana(
        connection,
        srcNetwork.bridgeAddress,
        srcNetwork.tokenBridgeAddress,
        srcKey.publicKey.toString(),
        Buffer.from(vaa, "base64")
    );
    tx.partialSign(srcKey);
    const txid = await connection.sendRawTransaction(tx.serialize());

    await new Promise((r) => setTimeout(r, 25000)); // wait for blocks to advance before fetching new foreign address
    const foreignAddress = await getForeignAssetSolana(
        connection,
        srcNetwork.tokenBridgeAddress,
        targetNetwork.wormholeChainId,
        tryNativeToUint8Array(targetDeployInfo.tokenAddress, targetNetwork.wormholeChainId)
    );
    console.log(`${src} Network has new PortalWrappedToken for ${target} network at ${foreignAddress}`);

    //If the attestation is WETH, save the ATA for config of the WETH mint as recipient address
}

export async function registerApp(src:string, target:string){
    const srcNetwork = config.networks[src];
    const targetNetwork = config.networks[target];
    const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
    )));
    const connection = new anchor.web3.Connection(srcNetwork.rpc);
    let targetEmitter:String;
    switch (targetNetwork.type){
        case 'evm': 
            targetEmitter = getEmitterAddressEth(targetDeployInfo.address);
            break;
        case 'solana':
            targetEmitter = await getEmitterAddressSolana(targetDeployInfo.address);
            break;
    }

    const xmint = new anchor.Program<SolanaTypes>(
        IDL,
        CONTRACT_ADDRESS,
        new anchor.AnchorProvider(
            connection,
            new anchor.Wallet(srcKey),
            {}));
 
    const [emitterAcc, emitterBmp] = findProgramAddressSync([
        Buffer.from("EmitterAddress"),
        byteify.serializeUint16(targetNetwork.wormholeChainId)
    ], xmint.programId);

    const [configAcc, _] = findProgramAddressSync([
        Buffer.from("config")
    ], xmint.programId);
    
    const tx = await xmint.methods
                            .registerChain(
                                targetNetwork.wormholeChainId,
                                Buffer.from(targetEmitter, 'hex'),
                            )
                            .accounts({
                                config: configAcc,
                                emitterAcc: emitterAcc,
                                owner: xmint.provider.publicKey,
                                systemProgram: anchor.web3.SystemProgram.programId
                            })  
                            .rpc();
    console.log(`Registered ${target} contract on ${src}`);
    // Alongside registering the App, go ahead register the tokens with one another
    // Register target token with src chain
    console.log(`Attesting ${target} token on ${src}`);
    switch(targetNetwork.type){
        case "evm":
            await evm.attest(target, src);
            break;
        case "solana":
            await attest(target, src);
            break;
    }
    console.log(`Attested ${target} token on ${src}`);
}

export async function balance(src: string, target: string) : Promise<string>{
    const srcNetwork = config.networks[src];
    const targetNetwork = config.networks[target];
    const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
    )));
    const connection = new anchor.web3.Connection(srcNetwork.rpc);

    if (src == target) {
        //Get Native Currency
        return (await connection.getBalance(srcKey.publicKey)).toString()
    }

    setDefaultWasm("node")
    // Else get the Token Balance of the Foreign Network's token on the Src Network
    const foreignAddress = await getForeignAssetSolana(
        connection,
        srcNetwork.tokenBridgeAddress,
        targetNetwork.wormholeChainId,
        tryNativeToUint8Array(targetDeployInfo.tokenAddress, targetNetwork.wormholeChainId)
    );

    const tokenAccountAddress = await getOrCreateAssociatedTokenAccount(
        connection,
        srcKey,
        new anchor.web3.PublicKey(foreignAddress),
        srcKey.publicKey,
    );

    const tokenAccount = await getAccount(
        connection,
        tokenAccountAddress.address
    )

    return tokenAccount.amount.toString();
}

export async function buyToken(src:string, target: string, amount:number){
    // Buy token on target chain with SOL on SRC chain
        // Create p3 that pays SOL
    
    const srcNetwork = config.networks[src];
    const targetNetwork = config.networks[target];
    const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
    )));
    const connection = new anchor.web3.Connection(srcNetwork.rpc);

    // For this project, 1 Native Token (1 SOL) will always equal 100 Chain tokens, no matter the source or target chains
    const solToTransfer = parseUnits((amount/100).toString(), "9");
    const targetChainAddress = tryNativeToUint8Array(targetDeployInfo.tokenReceipientAddress, targetNetwork.wormholeChainId);
    setDefaultWasm("node");

    // Recieve tokens into the ATA for payer for wrapped ETH0 Tokens
        // Requires getting wrapped mint data
    const wrappedTokenAddress = await getForeignAssetSolana(
        connection,
        srcNetwork.tokenBridgeAddress,
        targetNetwork.wormholeChainId,
        tryNativeToUint8Array(targetDeployInfo.tokenAddress, targetNetwork.wormholeChainId)
    );

    const targetTokenATA = await getOrCreateAssociatedTokenAccount(
        connection,
        srcKey,
        new anchor.web3.PublicKey(wrappedTokenAddress),
        srcKey.publicKey
    );

    const purchaseOrderPayload = tryNativeToUint8Array(targetTokenATA.address.toString(), srcNetwork.wormholeChainId);
    
    // Tokenbridge Authority Signer holds delegate authority to mutate the wSOL ATA account
    const tokenBridgePubKey = new anchor.web3.PublicKey(srcNetwork.tokenBridgeAddress);
    const tokenBridgeAuthoritySigner = findProgramAddressSync([Buffer.from("authority_signer")], tokenBridgePubKey)[0];

    // wSOL ATA Account
    const wSOLATAAcc =  await createWrappedNativeAccount(
        connection,
        srcKey,
        srcKey.publicKey,
        solToTransfer.toNumber(),
        new anchor.web3.Keypair(),
    )

    //wSOL Approve
    await approve(
        connection,
        srcKey,
        wSOLATAAcc,
        tokenBridgeAuthoritySigner,
        srcKey,
        solToTransfer.toBigInt()
    );

    console.log("Approved wSOL Transfer");
    await new Promise((r) => setTimeout(r, 16000)); //wait for accounts to finialize
    //p3 Generation
    const tx = await transferFromSolana(
        connection,
        srcNetwork.bridgeAddress,
        srcNetwork.tokenBridgeAddress,
        srcKey.publicKey.toString(),
        wSOLATAAcc.toString(),
        srcNetwork.wrappedNativeAddress,
        solToTransfer.toBigInt(),
        targetChainAddress,
        targetNetwork.wormholeChainId,
        tryNativeToUint8Array(wSOLATAAcc.toString(), srcNetwork.wormholeChainId),
        srcNetwork.wormholeChainId,
        srcKey.publicKey.toString(),
        BigInt(0),
        purchaseOrderPayload        
    );

    tx.partialSign(srcKey);
    const txid = await connection.sendRawTransaction(tx.serialize());
    console.log("SOL transferred: ", txid);
    const vaa = await fetchVaa(src, txid, true);

    /*
    await closeAccount(
        connection,
        srcKey,
        wSOLATAAcc,
        srcKey.publicKey,
        srcKey.publicKey
    );
    */

    return vaa;
}

export async function claimTokens(src:string, vaa:string){
    const srcNetwork = config.networks[src];
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
    )));
    const connection = new anchor.web3.Connection(srcNetwork.rpc);

    //Post VAA before trying to claim it
    setDefaultWasm("node");
    await postVaaSolanaWithRetry(
        connection,
        async (tx) => {
            tx.partialSign(srcKey);
            return tx;
        },
        srcNetwork.bridgeAddress,
        srcKey.publicKey.toString(),
        Buffer.from(vaa, "base64"),
        10
    );
    
    await new Promise((r) => setTimeout(r, 16000)); //wait for accounts to finialize

    const tx = await redeemOnSolana(
        connection,
        srcNetwork.bridgeAddress,
        srcNetwork.tokenBridgeAddress,
        srcKey.publicKey.toString(),
        Buffer.from(vaa, 'base64'),
    );
    tx.partialSign(srcKey);
    const txid = await connection.sendRawTransaction(tx.serialize());
    console.log("Claimed tokens on Solana: ", txid);
}

export async function submitForeignPurchase(src:string, target:string, vaa:string){
    const srcNetwork = config.networks[src];
    const targetNetwork = config.networks[target];
    const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
    const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
    const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
    )));
    const connection = new anchor.web3.Connection(srcNetwork.rpc);

    setDefaultWasm("node");
    // Post the VAA to Solana Chain for signature verification
    await postVaaSolanaWithRetry(
        connection,
        async (tx) => {
            tx.partialSign(srcKey);
            return tx;
        },
        srcNetwork.bridgeAddress,
        srcKey.publicKey.toString(),
        Buffer.from(vaa, "base64"),
        10
    );

    const xmint = new anchor.Program<SolanaTypes>(
        IDL,
        CONTRACT_ADDRESS,
        new anchor.AnchorProvider(
            connection,
            new anchor.Wallet(srcKey),
            {}));
    const { parse_vaa } = await importCoreWasm(); //this function can only be imported from the WASM right now, not directly
    const parsed_vaa = parse_vaa(Buffer.from(vaa, 'base64'));
    const vaaHash = getVaaHash(parsed_vaa); //await getSignedVAAHash(Buffer.from(vaa, "base64"));

    // Account that we stored the registered foreign emitter
    let emitterAddressAcc = findProgramAddressSync([
        byteify.serializeUint16(parsed_vaa.emitter_chain),
        Buffer.from(parsed_vaa.emitter_address, 'hex')
    ], new anchor.web3.PublicKey(srcNetwork.tokenBridgeAddress))[0];

    // A blank account we're creating just to keep track of already processed messages
    let targetEmitterAddress;
    switch (targetNetwork.type) {
        case "evm":
            targetEmitterAddress = getEmitterAddressEth(targetNetwork.tokenBridgeAddress);
            break;
        case "solana":
            targetEmitterAddress = await getEmitterAddressSolana(targetNetwork.tokenBridgeAddress);
    }
    let processedVaaKey = findProgramAddressSync([
        Buffer.from(targetEmitterAddress, "hex"),
        byteify.serializeUint16(parsed_vaa.emitter_chain),
        byteify.serializeUint64(parsed_vaa.sequence)
    ], xmint.programId)[0];

    // Account where the core bridge stored the vaa after the signatures checked out
    const tokenBridgePubKey = new anchor.web3.PublicKey(srcNetwork.tokenBridgeAddress);
    const coreBridgePubKey = new anchor.web3.PublicKey(srcNetwork.bridgeAddress);
    let coreBridgeVaaKey = findProgramAddressSync([
        Buffer.from("PostedVAA"),
        Buffer.from(vaaHash, 'hex')
    ], coreBridgePubKey)[0]
    const [configAcc, _] = findProgramAddressSync([Buffer.from("config")], xmint.programId);

    // Accounts for Completing P3
    const tokenBridgeConfigAcc = findProgramAddressSync([Buffer.from("config")], tokenBridgePubKey)[0];
    const tokenBridgeClaimAcc = findProgramAddressSync([
        Buffer.from(targetEmitterAddress, "hex"),
        byteify.serializeUint16(parsed_vaa.emitter_chain),
        byteify.serializeUint64(parsed_vaa.sequence)
    ], tokenBridgePubKey)[0];


    // WETH is being transferred in, so we need the WETH Portal Mint on Solana
    const wethMint = new anchor.web3.PublicKey(await getForeignAssetSolana(
        connection,
        srcNetwork.tokenBridgeAddress,
        targetNetwork.wormholeChainId,
        tryNativeToUint8Array(targetNetwork.wrappedNativeAddress, targetNetwork.wormholeChainId)
    ));

    const wethWrappedMeta = findProgramAddressSync([
        Buffer.from("meta"),
        wethMint.toBuffer()
    ], tokenBridgePubKey)[0];

    const mintAuthorityWrapped = findProgramAddressSync([
        Buffer.from("mint_signer")
    ], tokenBridgePubKey)[0];

    const redeemerAcc = findProgramAddressSync([Buffer.from("redeemer")], xmint.programId)[0];
    const wethAtaAcc = await getOrCreateAssociatedTokenAccount(
        connection,
        srcKey,
        wethMint,
        redeemerAcc,
        true // Allow off curve because the owner of the ATA acc is a PDA
    );

    // MINT SOL#T Tokens to Contract PDA Accounts
    const xmintTokenMint = new anchor.web3.PublicKey(srcDeployInfo.tokenAddress);
    const xmintAuthority = findProgramAddressSync([Buffer.from("mint_authority")], xmint.programId)[0];
    const xmintAtaAccount = await getOrCreateAssociatedTokenAccount(
        connection,
        srcKey,
        xmintTokenMint,
        xmintAuthority,
        true // Allow off curve because the owner of the ATA acc is a PDA
    );

    // P1 Portal Transfer back to Recepient Accounts
    const tokenBridgeMintCustody = findProgramAddressSync([xmintTokenMint.toBuffer()], tokenBridgePubKey)[0];
    const tokenBridgeAuthoritySigner = findProgramAddressSync([Buffer.from("authority_signer")], tokenBridgePubKey)[0];
    const tokenBridgeCustodySigner = findProgramAddressSync([Buffer.from("custody_signer")], tokenBridgePubKey)[0];
    const coreBridgeConfig = findProgramAddressSync([Buffer.from("Bridge")], coreBridgePubKey)[0];
    const transferMsgKey = new anchor.web3.Keypair();
    const tokenBridgeEmitter = findProgramAddressSync([Buffer.from("emitter")], tokenBridgePubKey)[0];
    const tokenBridgeSequenceKey = findProgramAddressSync([
        Buffer.from("Sequence"),
        tokenBridgeEmitter.toBuffer()
    ], coreBridgePubKey)[0];
    const coreBridgeFeeCollector = findProgramAddressSync([Buffer.from("fee_collector")], coreBridgePubKey)[0];

    // need to split the following into two because Solana account limit
    const receiptAcc = findProgramAddressSync([ Buffer.from(vaaHash, "hex") ], xmint.programId)[0];

    // Submit Foreign Purchase
    const submitTx = await xmint.methods 
        .submitForeignPurchase()
        .accounts({
            payer: srcKey.publicKey,
            systemProgram: anchor.web3.SystemProgram.programId,
            config: configAcc,
            coreBridgeVaa: coreBridgeVaaKey,
            processedVaa: processedVaaKey,
            emitterAcc: emitterAddressAcc,
            receipt: receiptAcc,

            tokenBridgeConfig: tokenBridgeConfigAcc,
            tokenBridgeClaimKey: tokenBridgeClaimAcc,
            wethAtaAccount: wethAtaAcc.address,
            redeemer:redeemerAcc,
            feeRecipient: wethAtaAcc.address,
            wethMint: wethMint,
            wethMintWrappedMeta: wethWrappedMeta,
            mintAuthorityWrapped: mintAuthorityWrapped,
            rentAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
            coreBridge: coreBridgePubKey,
            splProgram: TOKEN_PROGRAM_ID,

            tokenBridge: tokenBridgePubKey
        })
        .preInstructions([
            anchor.web3.ComputeBudgetProgram.requestUnits({
                units: 300000,
                additionalFee: 0,
            })
        ])
        .rpc();

    await new Promise((r) => setTimeout(r, 16000)); //wait for accounts to finialize

    // Claim Foreign Purchase
    const claimTx = await xmint.methods 
        .claimForeignPurchase()
        .accounts({
            payer: srcKey.publicKey,
            systemProgram: anchor.web3.SystemProgram.programId,
            config: configAcc,
            receipt: receiptAcc,

            rentAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
            tokenBridgeConfig: tokenBridgeConfigAcc,
            splProgram: TOKEN_PROGRAM_ID,

            xmintTokenMint: xmintTokenMint,
            xmintAuthority: xmintAuthority,
            xmintAtaAccount: xmintAtaAccount.address,
            
            tokenBridgeMintCustody: tokenBridgeMintCustody,
            tokenBridgeAuthoritySigner: tokenBridgeAuthoritySigner,
            tokenBridgeCustodySigner: tokenBridgeCustodySigner,
            coreBridgeConfig: coreBridgeConfig,
            xmintTransferMsgKey: transferMsgKey.publicKey,
            tokenBridgeEmitter: tokenBridgeEmitter,
            tokenBridgeSequenceKey: tokenBridgeSequenceKey,
            coreBridgeFeeCollector: coreBridgeFeeCollector,
            clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
            coreBridge: coreBridgePubKey,

            tokenBridge: tokenBridgePubKey
        })
        .preInstructions([
            anchor.web3.ComputeBudgetProgram.requestUnits({
                units: 300000,
                additionalFee: 0,
            })
        ])
        .signers([transferMsgKey])
        .rpc();

    const sfpVaa = await fetchVaa(src, claimTx, true);
    console.log(sfpVaa);
    return sfpVaa;
}


export async function sellToken(src:string, target:string, amount:number){}
export async function submitForeignSale(src:string, target:string, vaa:string){}

function getVaaHash(parsed_vaa){
    //Create VAA Hash to use in core bridge key
    let buffer_array = []
    buffer_array.push(byteify.serializeUint32(parsed_vaa.timestamp));
    buffer_array.push(byteify.serializeUint32(parsed_vaa.nonce));
    buffer_array.push(byteify.serializeUint16(parsed_vaa.emitter_chain));
    buffer_array.push(Uint8Array.from(parsed_vaa.emitter_address));
    buffer_array.push(byteify.serializeUint64(parsed_vaa.sequence));
    buffer_array.push(byteify.serializeUint8(parsed_vaa.consistency_level));
    buffer_array.push(Uint8Array.from(parsed_vaa.payload));
    const hash = keccak256(Buffer.concat(buffer_array));
    return hash.toString("hex");
}