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 { ACCOUNT_SIZE, NATIVE_MINT, TOKEN_PROGRAM_ID, createCloseAccountInstruction, createInitializeAccountInstruction, getMinimumBalanceForRentExemptAccount, } from "@solana/spl-token";
import { Keypair, PublicKey, Transaction as SolanaTransaction, SystemProgram, } from "@solana/web3.js";
import { Bridge__factory, TokenImplementation__factory, } from "../ethers-contracts";
import { createApproveAuthoritySignerInstruction, createTransferNativeInstruction, createTransferNativeWithPayloadInstruction, createTransferWrappedInstruction, createTransferWrappedWithPayloadInstruction, } from "../solana/tokenBridge";
import { CHAIN_ID_SOLANA, coalesceChainId, createNonce, } from "../utils";
export function getAllowanceEth(tokenBridgeAddress, tokenAddress, signer) {
    return __awaiter(this, void 0, void 0, function* () {
        const token = TokenImplementation__factory.connect(tokenAddress, signer);
        const signerAddress = yield signer.getAddress();
        const allowance = yield token.allowance(signerAddress, tokenBridgeAddress);
        return allowance;
    });
}
export function approveEth(tokenBridgeAddress, tokenAddress, signer, amount, overrides = {}) {
    return __awaiter(this, void 0, void 0, function* () {
        const token = TokenImplementation__factory.connect(tokenAddress, signer);
        return yield (yield token.approve(tokenBridgeAddress, amount, overrides)).wait();
    });
}
export function transferFromEth(tokenBridgeAddress, signer, tokenAddress, amount, recipientChain, recipientAddress, relayerFee = 0, overrides = {}, payload = null) {
    return __awaiter(this, void 0, void 0, function* () {
        const recipientChainId = coalesceChainId(recipientChain);
        const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
        const v = payload === null
            ? yield bridge.transferTokens(tokenAddress, amount, recipientChainId, recipientAddress, relayerFee, createNonce(), overrides)
            : yield bridge.transferTokensWithPayload(tokenAddress, amount, recipientChainId, recipientAddress, createNonce(), payload, overrides);
        const receipt = yield v.wait();
        return receipt;
    });
}
export function transferFromEthNative(tokenBridgeAddress, signer, amount, recipientChain, recipientAddress, relayerFee = 0, overrides = {}, payload = null) {
    return __awaiter(this, void 0, void 0, function* () {
        const recipientChainId = coalesceChainId(recipientChain);
        const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
        const v = payload === null
            ? yield bridge.wrapAndTransferETH(recipientChainId, recipientAddress, relayerFee, createNonce(), Object.assign(Object.assign({}, overrides), { value: amount }))
            : yield bridge.wrapAndTransferETHWithPayload(recipientChainId, recipientAddress, createNonce(), payload, Object.assign(Object.assign({}, overrides), { value: amount }));
        const receipt = yield v.wait();
        return receipt;
    });
}
export function transferNativeSol(connection, bridgeAddress, tokenBridgeAddress, payerAddress, amount, targetAddress, targetChain, relayerFee = BigInt(0), payload = null, commitment) {
    return __awaiter(this, void 0, void 0, function* () {
        const rentBalance = yield getMinimumBalanceForRentExemptAccount(connection, commitment);
        const payerPublicKey = new PublicKey(payerAddress);
        const ancillaryKeypair = Keypair.generate();
        //This will create a temporary account where the wSOL will be created.
        const createAncillaryAccountIx = SystemProgram.createAccount({
            fromPubkey: payerPublicKey,
            newAccountPubkey: ancillaryKeypair.publicKey,
            lamports: rentBalance,
            space: ACCOUNT_SIZE,
            programId: TOKEN_PROGRAM_ID,
        });
        //Send in the amount of SOL which we want converted to wSOL
        const initialBalanceTransferIx = SystemProgram.transfer({
            fromPubkey: payerPublicKey,
            lamports: amount,
            toPubkey: ancillaryKeypair.publicKey,
        });
        //Initialize the account as a WSOL account, with the original payerAddress as owner
        const initAccountIx = createInitializeAccountInstruction(ancillaryKeypair.publicKey, NATIVE_MINT, payerPublicKey);
        //Normal approve & transfer instructions, except that the wSOL is sent from the ancillary account.
        const approvalIx = createApproveAuthoritySignerInstruction(tokenBridgeAddress, ancillaryKeypair.publicKey, payerPublicKey, amount);
        const message = Keypair.generate();
        const nonce = createNonce().readUInt32LE(0);
        const tokenBridgeTransferIx = payload !== null
            ? createTransferNativeWithPayloadInstruction(tokenBridgeAddress, bridgeAddress, payerAddress, message.publicKey, ancillaryKeypair.publicKey, NATIVE_MINT, nonce, amount, Buffer.from(targetAddress), coalesceChainId(targetChain), payload)
            : createTransferNativeInstruction(tokenBridgeAddress, bridgeAddress, payerAddress, message.publicKey, ancillaryKeypair.publicKey, NATIVE_MINT, nonce, amount, relayerFee, Buffer.from(targetAddress), coalesceChainId(targetChain));
        //Close the ancillary account for cleanup. Payer address receives any remaining funds
        const closeAccountIx = createCloseAccountInstruction(ancillaryKeypair.publicKey, //account to close
        payerPublicKey, //Remaining funds destination
        payerPublicKey //authority
        );
        const { blockhash } = yield connection.getLatestBlockhash(commitment);
        const transaction = new SolanaTransaction();
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = payerPublicKey;
        transaction.add(createAncillaryAccountIx, initialBalanceTransferIx, initAccountIx, approvalIx, tokenBridgeTransferIx, closeAccountIx);
        transaction.partialSign(message, ancillaryKeypair);
        return transaction;
    });
}
export function transferFromSolana(connection, bridgeAddress, tokenBridgeAddress, payerAddress, fromAddress, mintAddress, amount, targetAddress, targetChain, originAddress, originChain, fromOwnerAddress, relayerFee = BigInt(0), payload = null, commitment) {
    return __awaiter(this, void 0, void 0, function* () {
        const originChainId = originChain
            ? coalesceChainId(originChain)
            : undefined;
        if (fromOwnerAddress === undefined) {
            fromOwnerAddress = payerAddress;
        }
        const nonce = createNonce().readUInt32LE(0);
        const approvalIx = createApproveAuthoritySignerInstruction(tokenBridgeAddress, fromAddress, fromOwnerAddress, amount);
        const message = Keypair.generate();
        const isSolanaNative = originChainId === undefined || originChainId === CHAIN_ID_SOLANA;
        if (!isSolanaNative && !originAddress) {
            return Promise.reject("originAddress is required when specifying originChain");
        }
        const tokenBridgeTransferIx = isSolanaNative
            ? payload !== null
                ? createTransferNativeWithPayloadInstruction(tokenBridgeAddress, bridgeAddress, payerAddress, message.publicKey, fromAddress, mintAddress, nonce, amount, targetAddress, coalesceChainId(targetChain), payload)
                : createTransferNativeInstruction(tokenBridgeAddress, bridgeAddress, payerAddress, message.publicKey, fromAddress, mintAddress, nonce, amount, relayerFee, targetAddress, coalesceChainId(targetChain))
            : payload !== null
                ? createTransferWrappedWithPayloadInstruction(tokenBridgeAddress, bridgeAddress, payerAddress, message.publicKey, fromAddress, fromOwnerAddress, originChainId, originAddress, nonce, amount, targetAddress, coalesceChainId(targetChain), payload)
                : createTransferWrappedInstruction(tokenBridgeAddress, bridgeAddress, payerAddress, message.publicKey, fromAddress, fromOwnerAddress, originChainId, originAddress, nonce, amount, relayerFee, targetAddress, coalesceChainId(targetChain));
        const transaction = new SolanaTransaction().add(approvalIx, tokenBridgeTransferIx);
        const { blockhash } = yield connection.getLatestBlockhash(commitment);
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = new PublicKey(payerAddress);
        transaction.partialSign(message);
        return transaction;
    });
}
