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 { ALPH_TOKEN_ID, DUST_AMOUNT } from "@alephium/web3";
import { AccountLayout, Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
import { Keypair, PublicKey, SystemProgram, Transaction as SolanaTransaction, } from "@solana/web3.js";
import { MsgExecuteContract } from "@terra-money/terra.js";
import { bigIntToBytes, getApplicationAddress, makeApplicationCallTxnFromObject, makeAssetTransferTxnWithSuggestedParamsFromObject, makePaymentTxnWithSuggestedParamsFromObject, OnApplicationComplete, } from "algosdk";
import { isNativeDenom } from "..";
import { TransferLocal, TransferRemote } from "../alephium-contracts/ts/scripts";
import { assetOptinCheck, getMessageFee, optin, } from "../algorand";
import { getEmitterAddressAlgorand } from "../bridge";
import { Bridge__factory, TokenImplementation__factory, } from "../ethers-contracts";
import { getBridgeFeeIx, ixFromRust } from "../solana";
import { importTokenWasm } from "../solana/wasm";
import { CHAIN_ID_ALEPHIUM, CHAIN_ID_SOLANA, coalesceChainId, createNonce, hexToUint8Array, textToUint8Array, WSOL_ADDRESS, } from "../utils";
import { safeBigIntToNumber } from "../utils/bigint";
export function transferLocalTokenFromAlph(signerProvider, tokenBridgeId, fromAddress, localTokenId, toChainId, toAddress, tokenAmount, messageFee, arbiterFee, consistencyLevel, nonce) {
    return __awaiter(this, void 0, void 0, function* () {
        const nonceHex = (typeof nonce !== "undefined") ? nonce : createNonce().toString('hex');
        return TransferLocal.execute(signerProvider, {
            initialFields: {
                tokenBridge: tokenBridgeId,
                fromAddress: fromAddress,
                localTokenId: localTokenId,
                alphChainId: BigInt(CHAIN_ID_ALEPHIUM),
                toChainId: BigInt(toChainId),
                toAddress: toAddress,
                tokenAmount: tokenAmount,
                arbiterFee: arbiterFee,
                nonce: nonceHex,
                consistencyLevel: BigInt(consistencyLevel)
            },
            attoAlphAmount: localTokenId === ALPH_TOKEN_ID ? messageFee + tokenAmount : messageFee,
            tokens: localTokenId === ALPH_TOKEN_ID
                ? []
                : [{ id: localTokenId, amount: tokenAmount }, { id: ALPH_TOKEN_ID, amount: DUST_AMOUNT * BigInt(2) }]
        });
    });
}
export function transferRemoteTokenFromAlph(signerProvider, tokenBridgeId, fromAddress, tokenPoolId, remoteTokenId, tokenChainId, toChainId, toAddress, tokenAmount, messageFee, arbiterFee, consistencyLevel, nonce) {
    return __awaiter(this, void 0, void 0, function* () {
        const nonceHex = (typeof nonce !== "undefined") ? nonce : createNonce().toString('hex');
        return TransferRemote.execute(signerProvider, {
            initialFields: {
                tokenBridge: tokenBridgeId,
                fromAddress: fromAddress,
                tokenPoolId: tokenPoolId,
                remoteTokenId: remoteTokenId,
                tokenChainId: BigInt(tokenChainId),
                toChainId: BigInt(toChainId),
                toAddress: toAddress,
                tokenAmount: tokenAmount,
                arbiterFee: arbiterFee,
                nonce: nonceHex,
                consistencyLevel: BigInt(consistencyLevel)
            },
            attoAlphAmount: messageFee,
            tokens: [{ id: tokenPoolId, amount: tokenAmount }, { id: ALPH_TOKEN_ID, amount: DUST_AMOUNT * BigInt(2) }]
        });
    });
}
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 = {}) {
    return __awaiter(this, void 0, void 0, function* () {
        const recipientChainId = coalesceChainId(recipientChain);
        const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
        const v = yield bridge.transferTokens(tokenAddress, amount, recipientChainId, recipientAddress, relayerFee, createNonce(), overrides);
        const receipt = yield v.wait();
        return receipt;
    });
}
export function transferFromEthNative(tokenBridgeAddress, signer, amount, recipientChain, recipientAddress, relayerFee = 0, overrides = {}) {
    return __awaiter(this, void 0, void 0, function* () {
        const recipientChainId = coalesceChainId(recipientChain);
        const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
        const v = yield bridge.wrapAndTransferETH(recipientChainId, recipientAddress, relayerFee, createNonce(), Object.assign(Object.assign({}, overrides), { value: amount }));
        const receipt = yield v.wait();
        return receipt;
    });
}
export function transferFromTerra(walletAddress, tokenBridgeAddress, tokenAddress, amount, recipientChain, recipientAddress, relayerFee = "0", payload = null) {
    return __awaiter(this, void 0, void 0, function* () {
        const recipientChainId = coalesceChainId(recipientChain);
        const nonce = Math.round(Math.random() * 100000);
        const isNativeAsset = isNativeDenom(tokenAddress);
        const mk_initiate_transfer = (info) => payload
            ? {
                initiate_transfer_with_payload: {
                    asset: {
                        amount,
                        info,
                    },
                    recipient_chain: recipientChainId,
                    recipient: Buffer.from(recipientAddress).toString("base64"),
                    fee: relayerFee,
                    nonce: nonce,
                    payload: payload,
                },
            }
            : {
                initiate_transfer: {
                    asset: {
                        amount,
                        info,
                    },
                    recipient_chain: recipientChainId,
                    recipient: Buffer.from(recipientAddress).toString("base64"),
                    fee: relayerFee,
                    nonce: nonce,
                },
            };
        return isNativeAsset
            ? [
                new MsgExecuteContract(walletAddress, tokenBridgeAddress, {
                    deposit_tokens: {},
                }, { [tokenAddress]: amount }),
                new MsgExecuteContract(walletAddress, tokenBridgeAddress, mk_initiate_transfer({
                    native_token: {
                        denom: tokenAddress,
                    },
                }), {}),
            ]
            : [
                new MsgExecuteContract(walletAddress, tokenAddress, {
                    increase_allowance: {
                        spender: tokenBridgeAddress,
                        amount: amount,
                        expires: {
                            never: {},
                        },
                    },
                }, {}),
                new MsgExecuteContract(walletAddress, tokenBridgeAddress, mk_initiate_transfer({
                    token: {
                        contract_addr: tokenAddress,
                    },
                }), {}),
            ];
    });
}
export function transferNativeSol(connection, bridgeAddress, tokenBridgeAddress, payerAddress, amount, targetAddress, targetChain, relayerFee = BigInt(0), payload = null) {
    return __awaiter(this, void 0, void 0, function* () {
        //https://github.com/solana-labs/solana-program-library/blob/master/token/js/client/token.js
        const rentBalance = yield Token.getMinBalanceRentForExemptAccount(connection);
        const mintPublicKey = new PublicKey(WSOL_ADDRESS);
        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: AccountLayout.span,
            programId: TOKEN_PROGRAM_ID,
        });
        //Send in the amount of SOL which we want converted to wSOL
        const initialBalanceTransferIx = SystemProgram.transfer({
            fromPubkey: payerPublicKey,
            lamports: Number(amount),
            toPubkey: ancillaryKeypair.publicKey,
        });
        //Initialize the account as a WSOL account, with the original payerAddress as owner
        const initAccountIx = yield Token.createInitAccountInstruction(TOKEN_PROGRAM_ID, mintPublicKey, ancillaryKeypair.publicKey, payerPublicKey);
        //Normal approve & transfer instructions, except that the wSOL is sent from the ancillary account.
        const { transfer_native_ix, transfer_native_with_payload_ix, approval_authority_address, } = yield importTokenWasm();
        const nonce = createNonce().readUInt32LE(0);
        const transferIx = yield getBridgeFeeIx(connection, bridgeAddress, payerAddress);
        const approvalIx = Token.createApproveInstruction(TOKEN_PROGRAM_ID, ancillaryKeypair.publicKey, new PublicKey(approval_authority_address(tokenBridgeAddress)), payerPublicKey, //owner
        [], new u64(amount.toString(16), 16));
        let messageKey = Keypair.generate();
        const ix = ixFromRust(payload !== null
            ? transfer_native_with_payload_ix(tokenBridgeAddress, bridgeAddress, payerAddress, messageKey.publicKey.toString(), ancillaryKeypair.publicKey.toString(), WSOL_ADDRESS, nonce, amount.valueOf(), relayerFee.valueOf(), targetAddress, coalesceChainId(targetChain), payload)
            : transfer_native_ix(tokenBridgeAddress, bridgeAddress, payerAddress, messageKey.publicKey.toString(), ancillaryKeypair.publicKey.toString(), WSOL_ADDRESS, nonce, amount.valueOf(), relayerFee.valueOf(), targetAddress, coalesceChainId(targetChain)));
        //Close the ancillary account for cleanup. Payer address receives any remaining funds
        const closeAccountIx = Token.createCloseAccountInstruction(TOKEN_PROGRAM_ID, ancillaryKeypair.publicKey, //account to close
        payerPublicKey, //Remaining funds destination
        payerPublicKey, //authority
        []);
        const { blockhash } = yield connection.getRecentBlockhash();
        const transaction = new SolanaTransaction();
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = new PublicKey(payerAddress);
        transaction.add(createAncillaryAccountIx);
        transaction.add(initialBalanceTransferIx);
        transaction.add(initAccountIx);
        transaction.add(transferIx, approvalIx, ix);
        transaction.add(closeAccountIx);
        transaction.partialSign(messageKey);
        transaction.partialSign(ancillaryKeypair);
        return transaction;
    });
}
export function transferFromSolana(connection, bridgeAddress, tokenBridgeAddress, payerAddress, fromAddress, mintAddress, amount, targetAddress, targetChain, originAddress, originChain, fromOwnerAddress, relayerFee = BigInt(0), payload = null) {
    return __awaiter(this, void 0, void 0, function* () {
        const originChainId = originChain
            ? coalesceChainId(originChain)
            : undefined;
        const nonce = createNonce().readUInt32LE(0);
        const transferIx = yield getBridgeFeeIx(connection, bridgeAddress, payerAddress);
        const { transfer_native_ix, transfer_wrapped_ix, transfer_native_with_payload_ix, transfer_wrapped_with_payload_ix, approval_authority_address, } = yield importTokenWasm();
        const approvalIx = Token.createApproveInstruction(TOKEN_PROGRAM_ID, new PublicKey(fromAddress), new PublicKey(approval_authority_address(tokenBridgeAddress)), new PublicKey(fromOwnerAddress || payerAddress), [], new u64(amount.toString(16), 16));
        let messageKey = Keypair.generate();
        const isSolanaNative = originChainId === undefined || originChainId === CHAIN_ID_SOLANA;
        if (!isSolanaNative && !originAddress) {
            throw new Error("originAddress is required when specifying originChain");
        }
        const ix = ixFromRust(isSolanaNative
            ? payload !== null
                ? transfer_native_with_payload_ix(tokenBridgeAddress, bridgeAddress, payerAddress, messageKey.publicKey.toString(), fromAddress, mintAddress, nonce, amount.valueOf(), relayerFee.valueOf(), targetAddress, coalesceChainId(targetChain), payload)
                : transfer_native_ix(tokenBridgeAddress, bridgeAddress, payerAddress, messageKey.publicKey.toString(), fromAddress, mintAddress, nonce, amount.valueOf(), relayerFee.valueOf(), targetAddress, coalesceChainId(targetChain))
            : payload !== null
                ? transfer_wrapped_with_payload_ix(tokenBridgeAddress, bridgeAddress, payerAddress, messageKey.publicKey.toString(), fromAddress, fromOwnerAddress || payerAddress, originChainId, // checked by isSolanaNative
                originAddress, // checked by throw
                nonce, amount.valueOf(), relayerFee.valueOf(), targetAddress, coalesceChainId(targetChain), payload)
                : transfer_wrapped_ix(tokenBridgeAddress, bridgeAddress, payerAddress, messageKey.publicKey.toString(), fromAddress, fromOwnerAddress || payerAddress, originChainId, // checked by isSolanaNative
                originAddress, // checked by throw
                nonce, amount.valueOf(), relayerFee.valueOf(), targetAddress, coalesceChainId(targetChain)));
        const transaction = new SolanaTransaction().add(transferIx, approvalIx, ix);
        const { blockhash } = yield connection.getRecentBlockhash();
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = new PublicKey(payerAddress);
        transaction.partialSign(messageKey);
        return transaction;
    });
}
/**
 * Transfers an asset from Algorand to a receiver on another chain
 * @param client AlgodV2 client
 * @param tokenBridgeId Application ID of the token bridge
 * @param bridgeId Application ID of the core bridge
 * @param sender Sending account
 * @param assetId Asset index
 * @param qty Quantity to transfer
 * @param receiver Receiving account
 * @param chain Reeiving chain
 * @param fee Transfer fee
 * @param payload payload for payload3 transfers
 * @returns Sequence number of confirmation
 */
export function transferFromAlgorand(client, tokenBridgeId, bridgeId, senderAddr, assetId, qty, receiver, chain, fee, payload = null) {
    return __awaiter(this, void 0, void 0, function* () {
        const recipientChainId = coalesceChainId(chain);
        const tokenAddr = getApplicationAddress(tokenBridgeId);
        const applAddr = getEmitterAddressAlgorand(tokenBridgeId);
        const txs = [];
        // "transferAsset"
        const { addr: emitterAddr, txs: emitterOptInTxs } = yield optin(client, senderAddr, bridgeId, BigInt(0), applAddr);
        txs.push(...emitterOptInTxs);
        let creator;
        let creatorAcctInfo;
        let wormhole = false;
        if (assetId !== BigInt(0)) {
            const assetInfo = yield client
                .getAssetByID(safeBigIntToNumber(assetId))
                .do();
            creator = assetInfo["params"]["creator"];
            creatorAcctInfo = yield client.accountInformation(creator).do();
            const authAddr = creatorAcctInfo["auth-addr"];
            if (authAddr === tokenAddr) {
                wormhole = true;
            }
        }
        const params = yield client.getTransactionParams().do();
        const msgFee = yield getMessageFee(client, bridgeId);
        if (msgFee > 0) {
            const payTxn = makePaymentTxnWithSuggestedParamsFromObject({
                from: senderAddr,
                suggestedParams: params,
                to: getApplicationAddress(tokenBridgeId),
                amount: msgFee,
            });
            txs.push({ tx: payTxn, signer: null });
        }
        if (!wormhole) {
            const bNat = Buffer.from("native", "binary").toString("hex");
            // "creator"
            const result = yield optin(client, senderAddr, tokenBridgeId, assetId, bNat);
            creator = result.addr;
            txs.push(...result.txs);
        }
        if (assetId !== BigInt(0) &&
            !(yield assetOptinCheck(client, assetId, creator))) {
            // Looks like we need to optin
            const payTxn = makePaymentTxnWithSuggestedParamsFromObject({
                from: senderAddr,
                to: creator,
                amount: 100000,
                suggestedParams: params,
            });
            txs.push({ tx: payTxn, signer: null });
            // The tokenid app needs to do the optin since it has signature authority
            const bOptin = textToUint8Array("optin");
            let txn = makeApplicationCallTxnFromObject({
                from: senderAddr,
                appIndex: safeBigIntToNumber(tokenBridgeId),
                onComplete: OnApplicationComplete.NoOpOC,
                appArgs: [bOptin, bigIntToBytes(assetId, 8)],
                foreignAssets: [safeBigIntToNumber(assetId)],
                accounts: [creator],
                suggestedParams: params,
            });
            txn.fee *= 2;
            txs.push({ tx: txn, signer: null });
        }
        const t = makeApplicationCallTxnFromObject({
            from: senderAddr,
            appIndex: safeBigIntToNumber(tokenBridgeId),
            onComplete: OnApplicationComplete.NoOpOC,
            appArgs: [textToUint8Array("nop")],
            suggestedParams: params,
        });
        txs.push({ tx: t, signer: null });
        let accounts = [];
        if (assetId === BigInt(0)) {
            const t = makePaymentTxnWithSuggestedParamsFromObject({
                from: senderAddr,
                to: creator,
                amount: qty,
                suggestedParams: params,
            });
            txs.push({ tx: t, signer: null });
            accounts = [emitterAddr, creator, creator];
        }
        else {
            const t = makeAssetTransferTxnWithSuggestedParamsFromObject({
                from: senderAddr,
                to: creator,
                suggestedParams: params,
                amount: qty,
                assetIndex: safeBigIntToNumber(assetId),
            });
            txs.push({ tx: t, signer: null });
            accounts = [emitterAddr, creator, creatorAcctInfo["address"]];
        }
        let args = [
            textToUint8Array("sendTransfer"),
            bigIntToBytes(assetId, 8),
            bigIntToBytes(qty, 8),
            hexToUint8Array(receiver),
            bigIntToBytes(recipientChainId, 8),
            bigIntToBytes(fee, 8),
        ];
        if (payload !== null) {
            args.push(payload);
        }
        let acTxn = makeApplicationCallTxnFromObject({
            from: senderAddr,
            appIndex: safeBigIntToNumber(tokenBridgeId),
            onComplete: OnApplicationComplete.NoOpOC,
            appArgs: args,
            foreignApps: [safeBigIntToNumber(bridgeId)],
            foreignAssets: [safeBigIntToNumber(assetId)],
            accounts: accounts,
            suggestedParams: params,
        });
        acTxn.fee *= 2;
        txs.push({ tx: acTxn, signer: null });
        return txs;
    });
}
