// Algorand.ts
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 { bigIntToBytes, decodeAddress, encodeAddress, getApplicationAddress, LogicSigAccount, makeApplicationCallTxnFromObject, makeApplicationOptInTxnFromObject, makeAssetTransferTxnWithSuggestedParamsFromObject, makePaymentTxnWithSuggestedParamsFromObject, OnApplicationComplete, signLogicSigTransaction, } from "algosdk";
import abi from "algosdk";
import { BigNumber } from "ethers";
import { keccak256 } from "ethers/lib/utils";
import { getEmitterAddressAlgorand } from "../bridge";
import { CHAIN_ID_ALGORAND, hexToUint8Array, textToHexString, textToUint8Array, uint8ArrayToHex, } from "../utils";
import { safeBigIntToNumber } from "../utils/bigint";
import { TmplSig } from "./TmplSig";
const SEED_AMT = 1002000;
const ZERO_PAD_BYTES = "0000000000000000000000000000000000000000000000000000000000000000";
const MAX_KEYS = 15;
const MAX_BYTES_PER_KEY = 127;
const BITS_PER_BYTE = 8;
export const BITS_PER_KEY = MAX_BYTES_PER_KEY * BITS_PER_BYTE;
const MAX_BYTES = MAX_BYTES_PER_KEY * MAX_KEYS;
export const MAX_BITS = BITS_PER_BYTE * MAX_BYTES;
const MAX_SIGS_PER_TXN = 9;
const ALGO_VERIFY_HASH = "EZATROXX2HISIRZDRGXW4LRQ46Z6IUJYYIHU3PJGP7P5IQDPKVX42N767A";
const ALGO_VERIFY = new Uint8Array([
    6, 32, 4, 1, 0, 32, 20, 38, 1, 0, 49, 32, 50, 3, 18, 68, 49, 1, 35, 18, 68,
    49, 16, 129, 6, 18, 68, 54, 26, 1, 54, 26, 3, 54, 26, 2, 136, 0, 3, 68, 34,
    67, 53, 2, 53, 1, 53, 0, 40, 53, 240, 40, 53, 241, 52, 0, 21, 53, 5, 35, 53,
    3, 35, 53, 4, 52, 3, 52, 5, 12, 65, 0, 68, 52, 1, 52, 0, 52, 3, 129, 65, 8,
    34, 88, 23, 52, 0, 52, 3, 34, 8, 36, 88, 52, 0, 52, 3, 129, 33, 8, 36, 88, 7,
    0, 53, 241, 53, 240, 52, 2, 52, 4, 37, 88, 52, 240, 52, 241, 80, 2, 87, 12,
    20, 18, 68, 52, 3, 129, 66, 8, 53, 3, 52, 4, 37, 8, 53, 4, 66, 255, 180, 34,
    137,
]);
let accountExistsCache = new Set();
/**
 * Return the message fee for the core bridge
 * @param client An Algodv2 client
 * @param bridgeId The application ID of the core bridge
 * @returns The message fee for the core bridge
 */
export function getMessageFee(client, bridgeId) {
    return __awaiter(this, void 0, void 0, function* () {
        const applInfo = yield client
            .getApplicationByID(safeBigIntToNumber(bridgeId))
            .do();
        const globalState = applInfo["params"]["global-state"];
        const key = Buffer.from("MessageFee", "binary").toString("base64");
        let ret = BigInt(0);
        globalState.forEach((el) => {
            if (el["key"] === key) {
                ret = BigInt(el["value"]["uint"]);
                return;
            }
        });
        return ret;
    });
}
/**
 * Checks to see it the account exists for the application
 * @param client An Algodv2 client
 * @param appId Application ID
 * @param acctAddr Account address to check
 * @returns true, if account exists for application.  Otherwise, returns false
 */
export function accountExists(client, appId, acctAddr) {
    return __awaiter(this, void 0, void 0, function* () {
        if (accountExistsCache.has([appId, acctAddr]))
            return true;
        let ret = false;
        try {
            const acctInfo = yield client.accountInformation(acctAddr).do();
            const als = acctInfo["apps-local-state"];
            if (!als) {
                return ret;
            }
            als.forEach((app) => {
                if (BigInt(app["id"]) === appId) {
                    accountExistsCache.add([appId, acctAddr]);
                    ret = true;
                    return;
                }
            });
        }
        catch (e) { }
        return ret;
    });
}
/**
 * Calculates the logic sig account for the application
 * @param client An Algodv2 client
 * @param appId Application ID
 * @param appIndex Application index
 * @param emitterId Emitter address
 * @returns LogicSigAccountInfo
 */
export function calcLogicSigAccount(client, appId, appIndex, emitterId) {
    return __awaiter(this, void 0, void 0, function* () {
        let data = {
            addrIdx: appIndex,
            appAddress: getEmitterAddressAlgorand(appId),
            appId: appId,
            emitterId: emitterId,
        };
        const ts = new TmplSig(client);
        const lsa = yield ts.populate(data);
        const sigAddr = lsa.address();
        const doesExist = yield accountExists(client, appId, sigAddr);
        return {
            lsa,
            doesExist,
        };
    });
}
/**
 * Calculates the logic sig account for the application
 * @param client An Algodv2 client
 * @param senderAddr Sender address
 * @param appId Application ID
 * @param appIndex Application index
 * @param emitterId Emitter address
 * @returns Address and array of TransactionSignerPairs
 */
export function optin(client, senderAddr, appId, appIndex, emitterId) {
    return __awaiter(this, void 0, void 0, function* () {
        const appAddr = getApplicationAddress(appId);
        // Check to see if we need to create this
        const { doesExist, lsa } = yield calcLogicSigAccount(client, appId, appIndex, emitterId);
        const sigAddr = lsa.address();
        let txs = [];
        if (!doesExist) {
            // These are the suggested params from the system
            const params = yield client.getTransactionParams().do();
            const seedTxn = makePaymentTxnWithSuggestedParamsFromObject({
                from: senderAddr,
                to: sigAddr,
                amount: SEED_AMT,
                suggestedParams: params,
            });
            seedTxn.fee = seedTxn.fee * 2;
            txs.push({ tx: seedTxn, signer: null });
            const optinTxn = makeApplicationOptInTxnFromObject({
                from: sigAddr,
                suggestedParams: params,
                appIndex: safeBigIntToNumber(appId),
                rekeyTo: appAddr,
            });
            optinTxn.fee = 0;
            txs.push({
                tx: optinTxn,
                signer: {
                    addr: lsa.address(),
                    signTxn: (txn) => Promise.resolve(signLogicSigTransaction(txn, lsa).blob),
                },
            });
            accountExistsCache.add([appId, lsa.address()]);
        }
        return {
            addr: sigAddr,
            txs,
        };
    });
}
function extract3(buffer, start, size) {
    return buffer.slice(start, start + size);
}
export function _parseVAAAlgorand(vaa) {
    let ret = {};
    let buf = Buffer.from(vaa);
    ret.version = buf.readIntBE(0, 1);
    ret.index = buf.readIntBE(1, 4);
    ret.siglen = buf.readIntBE(5, 1);
    const siglen = ret.siglen;
    if (siglen) {
        ret.signatures = extract3(vaa, 6, siglen * 66);
    }
    const sigs = [];
    for (let i = 0; i < siglen; i++) {
        const start = 6 + i * 66;
        const len = 66;
        const sigBuf = extract3(vaa, start, len);
        sigs.push(sigBuf);
    }
    ret.sigs = sigs;
    let off = siglen * 66 + 6;
    ret.digest = vaa.slice(off); // This is what is actually signed...
    ret.timestamp = buf.readIntBE(off, 4);
    off += 4;
    ret.nonce = buf.readIntBE(off, 4);
    off += 4;
    ret.chainRaw = Buffer.from(extract3(vaa, off, 2)).toString("hex");
    ret.chain = buf.readIntBE(off, 2);
    off += 2;
    ret.emitter = Buffer.from(extract3(vaa, off, 32)).toString("hex");
    off += 32;
    ret.sequence = buf.readBigUInt64BE(off);
    off += 8;
    ret.consistency = buf.readIntBE(off, 1);
    off += 1;
    ret.Meta = "Unknown";
    if (!Buffer.compare(extract3(buf, off, 32), Buffer.from("000000000000000000000000000000000000000000546f6b656e427269646765", "hex"))) {
        ret.Meta = "TokenBridge";
        ret.module = extract3(vaa, off, 32);
        off += 32;
        ret.action = buf.readIntBE(off, 1);
        off += 1;
        if (ret.action === 1) {
            ret.Meta = "TokenBridge RegisterChain";
            ret.targetChain = buf.readIntBE(off, 2);
            off += 2;
            ret.EmitterChainID = buf.readIntBE(off, 2);
            off += 2;
            ret.targetEmitter = extract3(vaa, off, 32);
            off += 32;
        }
        else if (ret.action === 2) {
            ret.Meta = "TokenBridge UpgradeContract";
            ret.targetChain = buf.readIntBE(off, 2);
            off += 2;
            ret.newContract = extract3(vaa, off, 32);
            off += 32;
        }
    }
    else if (!Buffer.compare(extract3(buf, off, 32), Buffer.from("00000000000000000000000000000000000000000000000000000000436f7265", "hex"))) {
        ret.Meta = "CoreGovernance";
        ret.module = extract3(vaa, off, 32);
        off += 32;
        ret.action = buf.readIntBE(off, 1);
        off += 1;
        ret.targetChain = buf.readIntBE(off, 2);
        off += 2;
        ret.NewGuardianSetIndex = buf.readIntBE(off, 4);
    }
    //    ret.len=vaa.slice(off).length)
    //    ret.act=buf.readIntBE(off, 1))
    if (vaa.slice(off).length === 100 && buf.readIntBE(off, 1) === 2) {
        ret.Meta = "TokenBridge Attest";
        ret.Type = buf.readIntBE(off, 1);
        off += 1;
        ret.Contract = uint8ArrayToHex(extract3(vaa, off, 32));
        off += 32;
        ret.FromChain = buf.readIntBE(off, 2);
        off += 2;
        ret.Decimals = buf.readIntBE(off, 1);
        off += 1;
        ret.Symbol = extract3(vaa, off, 32);
        off += 32;
        ret.Name = extract3(vaa, off, 32);
    }
    if (vaa.slice(off).length === 133 && buf.readIntBE(off, 1) === 1) {
        ret.Meta = "TokenBridge Transfer";
        ret.Type = buf.readIntBE(off, 1);
        off += 1;
        ret.Amount = extract3(vaa, off, 32);
        off += 32;
        ret.Contract = uint8ArrayToHex(extract3(vaa, off, 32));
        off += 32;
        ret.FromChain = buf.readIntBE(off, 2);
        off += 2;
        ret.ToAddress = extract3(vaa, off, 32);
        off += 32;
        ret.ToChain = buf.readIntBE(off, 2);
        off += 2;
        ret.Fee = extract3(vaa, off, 32);
    }
    if (off >= buf.length) {
        return ret;
    }
    if (buf.readIntBE(off, 1) === 3) {
        ret.Meta = "TokenBridge Transfer With Payload";
        ret.Type = buf.readIntBE(off, 1);
        off += 1;
        ret.Amount = extract3(vaa, off, 32);
        off += 32;
        ret.Contract = uint8ArrayToHex(extract3(vaa, off, 32));
        off += 32;
        ret.FromChain = buf.readIntBE(off, 2);
        off += 2;
        ret.ToAddress = extract3(vaa, off, 32);
        off += 32;
        ret.ToChain = buf.readIntBE(off, 2);
        off += 2;
        ret.Fee = extract3(vaa, off, 32);
        off += 32;
        ret.Payload = vaa.slice(off);
    }
    return ret;
}
/**
 * Returns the local data for an application ID
 * @param client Algodv2 client
 * @param appId Application ID of interest
 * @param address Address of the account
 * @returns Uint8Array of data squirreled away
 */
export function decodeLocalState(client, appId, address) {
    return __awaiter(this, void 0, void 0, function* () {
        let app_state = null;
        const ai = yield client.accountInformation(address).do();
        for (const app of ai["apps-local-state"]) {
            if (BigInt(app["id"]) === appId) {
                app_state = app["key-value"];
                break;
            }
        }
        let ret = Buffer.alloc(0);
        let empty = Buffer.alloc(0);
        if (app_state) {
            const e = Buffer.alloc(127);
            const m = Buffer.from("meta");
            let sk = [];
            let vals = new Map();
            for (const kv of app_state) {
                const k = Buffer.from(kv["key"], "base64");
                const key = k.readInt8();
                if (!Buffer.compare(k, m)) {
                    continue;
                }
                const v = Buffer.from(kv["value"]["bytes"], "base64");
                if (Buffer.compare(v, e)) {
                    vals.set(key.toString(), v);
                    sk.push(key.toString());
                }
            }
            sk.sort((a, b) => a.localeCompare(b, "en", { numeric: true }));
            sk.forEach((v) => {
                ret = Buffer.concat([ret, vals.get(v) || empty]);
            });
        }
        return new Uint8Array(ret);
    });
}
/**
 * Checks if the asset has been opted in by the receiver
 * @param client Algodv2 client
 * @param asset Algorand asset index
 * @param receiver Account address
 * @returns True if the asset was opted in, else false
 */
export function assetOptinCheck(client, asset, receiver) {
    return __awaiter(this, void 0, void 0, function* () {
        const acctInfo = yield client.accountInformation(receiver).do();
        const assets = acctInfo.assets;
        let ret = false;
        assets.forEach((a) => {
            const assetId = BigInt(a["asset-id"]);
            if (assetId === asset) {
                ret = true;
                return;
            }
        });
        return ret;
    });
}
class SubmitVAAState {
    constructor(vaaMap, accounts, txs, guardianAddr) {
        this.vaaMap = vaaMap;
        this.accounts = accounts;
        this.txs = txs;
        this.guardianAddr = guardianAddr;
    }
}
/**
 * Submits just the header of the VAA
 * @param client AlgodV2 client
 * @param bridgeId Application ID of the core bridge
 * @param vaa The VAA (Just the header is used)
 * @param senderAddr Sending account address
 * @param appid Application ID
 * @returns Current VAA state
 */
export function submitVAAHeader(client, bridgeId, vaa, senderAddr, appid) {
    return __awaiter(this, void 0, void 0, function* () {
        // A lot of our logic here depends on parseVAA and knowing what the payload is..
        const parsedVAA = _parseVAAAlgorand(vaa);
        const seq = parsedVAA.sequence / BigInt(MAX_BITS);
        const chainRaw = parsedVAA.chainRaw; // TODO: this needs to be a hex string
        const em = parsedVAA.emitter; // TODO: this needs to be a hex string
        const index = parsedVAA.index;
        let txs = [];
        // "seqAddr"
        const { addr: seqAddr, txs: seqOptInTxs } = yield optin(client, senderAddr, appid, seq, chainRaw + em);
        txs.push(...seqOptInTxs);
        const guardianPgmName = textToHexString("guardian");
        // And then the signatures to help us verify the vaa_s
        // "guardianAddr"
        const { addr: guardianAddr, txs: guardianOptInTxs } = yield optin(client, senderAddr, bridgeId, BigInt(index), guardianPgmName);
        txs.push(...guardianOptInTxs);
        let accts = [seqAddr, guardianAddr];
        // When we attest for a new token, we need some place to store the info... later we will need to
        // mirror the other way as well
        const keys = yield decodeLocalState(client, bridgeId, guardianAddr);
        const params = yield client
            .getTransactionParams()
            .do();
        // We don't pass the entire payload in but instead just pass it pre digested.  This gets around size
        // limitations with lsigs AND reduces the cost of the entire operation on a conjested network by reducing the
        // bytes passed into the transaction
        // This is a 2 pass digest
        const digest = keccak256(keccak256(parsedVAA.digest)).slice(2);
        // How many signatures can we process in a single txn... we can do 9!
        // There are likely upwards of 19 signatures.  So, we ned to split things up
        const numSigs = parsedVAA.siglen;
        let numTxns = Math.floor(numSigs / MAX_SIGS_PER_TXN) + 1;
        const SIG_LEN = 66;
        const BSIZE = SIG_LEN * MAX_SIGS_PER_TXN;
        const signatures = parsedVAA.signatures;
        const verifySigArg = textToUint8Array("verifySigs");
        const lsa = new LogicSigAccount(ALGO_VERIFY);
        for (let nt = 0; nt < numTxns; nt++) {
            let sigs = signatures.slice(nt * BSIZE);
            if (sigs.length > BSIZE) {
                sigs = sigs.slice(0, BSIZE);
            }
            // The keyset is the set of guardians that correspond
            // to the current set of signatures in this loop.
            // Each signature in 20 bytes and comes from decodeLocalState()
            const GuardianKeyLen = 20;
            const numSigsThisTxn = sigs.length / SIG_LEN;
            let arraySize = numSigsThisTxn * GuardianKeyLen;
            let keySet = new Uint8Array(arraySize);
            for (let i = 0; i < numSigsThisTxn; i++) {
                // The first byte of the sig is the relative index of that signature in the signatures array
                // Use that index to get the appropriate guardian key
                const idx = sigs[i * SIG_LEN];
                const key = keys.slice(idx * GuardianKeyLen + 1, (idx + 1) * GuardianKeyLen + 1);
                keySet.set(key, i * 20);
            }
            const appTxn = makeApplicationCallTxnFromObject({
                appArgs: [verifySigArg, sigs, keySet, hexToUint8Array(digest)],
                accounts: accts,
                appIndex: safeBigIntToNumber(bridgeId),
                from: ALGO_VERIFY_HASH,
                onComplete: OnApplicationComplete.NoOpOC,
                suggestedParams: params,
            });
            appTxn.fee = 0;
            txs.push({
                tx: appTxn,
                signer: {
                    addr: lsa.address(),
                    signTxn: (txn) => Promise.resolve(signLogicSigTransaction(txn, lsa).blob),
                },
            });
        }
        const appTxn = makeApplicationCallTxnFromObject({
            appArgs: [textToUint8Array("verifyVAA"), vaa],
            accounts: accts,
            appIndex: safeBigIntToNumber(bridgeId),
            from: senderAddr,
            onComplete: OnApplicationComplete.NoOpOC,
            suggestedParams: params,
        });
        appTxn.fee = appTxn.fee * (1 + numTxns);
        txs.push({ tx: appTxn, signer: null });
        return new SubmitVAAState(parsedVAA, accts, txs, guardianAddr);
    });
}
/**
 * Submits the VAA to the application
 * @param client AlgodV2 client
 * @param tokenBridgeId Application ID of the token bridge
 * @param bridgeId Application ID of the core bridge
 * @param vaa The VAA to be submitted
 * @param senderAddr Sending account address
 * @returns Confirmation log
 */
export function _submitVAAAlgorand(client, tokenBridgeId, bridgeId, vaa, senderAddr) {
    return __awaiter(this, void 0, void 0, function* () {
        let sstate = yield submitVAAHeader(client, bridgeId, vaa, senderAddr, tokenBridgeId);
        let parsedVAA = sstate.vaaMap;
        let accts = sstate.accounts;
        let txs = sstate.txs;
        // If this happens to be setting up a new guardian set, we probably need it as well...
        if (parsedVAA.Meta === "CoreGovernance" &&
            parsedVAA.action === 2 &&
            parsedVAA.NewGuardianSetIndex !== undefined) {
            const ngsi = parsedVAA.NewGuardianSetIndex;
            const guardianPgmName = textToHexString("guardian");
            // "newGuardianAddr"
            const { addr: newGuardianAddr, txs: newGuardianOptInTxs } = yield optin(client, senderAddr, bridgeId, BigInt(ngsi), guardianPgmName);
            accts.push(newGuardianAddr);
            txs.unshift(...newGuardianOptInTxs);
        }
        // When we attest for a new token, we need some place to store the info... later we will need to
        // mirror the other way as well
        const meta = parsedVAA.Meta;
        let chainAddr = "";
        if ((meta === "TokenBridge Attest" ||
            meta === "TokenBridge Transfer" ||
            meta === "TokenBridge Transfer With Payload") &&
            parsedVAA.Contract !== undefined) {
            if (parsedVAA.FromChain !== CHAIN_ID_ALGORAND && parsedVAA.FromChain) {
                // "TokenBridge chainAddr"
                const result = yield optin(client, senderAddr, tokenBridgeId, BigInt(parsedVAA.FromChain), parsedVAA.Contract);
                chainAddr = result.addr;
                txs.unshift(...result.txs);
            }
            else {
                const assetId = hexToNativeAssetBigIntAlgorand(parsedVAA.Contract);
                // "TokenBridge native chainAddr"
                const result = yield optin(client, senderAddr, tokenBridgeId, assetId, textToHexString("native"));
                chainAddr = result.addr;
                txs.unshift(...result.txs);
            }
            accts.push(chainAddr);
        }
        const params = yield client
            .getTransactionParams()
            .do();
        if (meta === "CoreGovernance") {
            txs.push({
                tx: makeApplicationCallTxnFromObject({
                    appArgs: [textToUint8Array("governance"), vaa],
                    accounts: accts,
                    appIndex: safeBigIntToNumber(bridgeId),
                    from: senderAddr,
                    onComplete: OnApplicationComplete.NoOpOC,
                    suggestedParams: params,
                }),
                signer: null,
            });
            txs.push({
                tx: makeApplicationCallTxnFromObject({
                    appArgs: [textToUint8Array("nop"), bigIntToBytes(5, 8)],
                    appIndex: safeBigIntToNumber(bridgeId),
                    from: senderAddr,
                    onComplete: OnApplicationComplete.NoOpOC,
                    suggestedParams: params,
                }),
                signer: null,
            });
        }
        if (meta === "TokenBridge RegisterChain" ||
            meta === "TokenBridge UpgradeContract") {
            txs.push({
                tx: makeApplicationCallTxnFromObject({
                    appArgs: [textToUint8Array("governance"), vaa],
                    accounts: accts,
                    appIndex: safeBigIntToNumber(tokenBridgeId),
                    foreignApps: [safeBigIntToNumber(bridgeId)],
                    from: senderAddr,
                    onComplete: OnApplicationComplete.NoOpOC,
                    suggestedParams: params,
                }),
                signer: null,
            });
        }
        if (meta === "TokenBridge Attest") {
            let asset = yield decodeLocalState(client, tokenBridgeId, chainAddr);
            let foreignAssets = [];
            if (asset.length > 8) {
                const tmp = Buffer.from(asset.slice(0, 8));
                foreignAssets.push(safeBigIntToNumber(tmp.readBigUInt64BE(0)));
            }
            txs.push({
                tx: makePaymentTxnWithSuggestedParamsFromObject({
                    from: senderAddr,
                    to: chainAddr,
                    amount: 100000,
                    suggestedParams: params,
                }),
                signer: null,
            });
            let buf = new Uint8Array(1);
            buf[0] = 0x01;
            txs.push({
                tx: makeApplicationCallTxnFromObject({
                    appArgs: [textToUint8Array("nop"), buf],
                    appIndex: safeBigIntToNumber(tokenBridgeId),
                    from: senderAddr,
                    onComplete: OnApplicationComplete.NoOpOC,
                    suggestedParams: params,
                }),
                signer: null,
            });
            buf = new Uint8Array(1);
            buf[0] = 0x02;
            txs.push({
                tx: makeApplicationCallTxnFromObject({
                    appArgs: [textToUint8Array("nop"), buf],
                    appIndex: safeBigIntToNumber(tokenBridgeId),
                    from: senderAddr,
                    onComplete: OnApplicationComplete.NoOpOC,
                    suggestedParams: params,
                }),
                signer: null,
            });
            txs.push({
                tx: makeApplicationCallTxnFromObject({
                    accounts: accts,
                    appArgs: [textToUint8Array("receiveAttest"), vaa],
                    appIndex: safeBigIntToNumber(tokenBridgeId),
                    foreignAssets: foreignAssets,
                    from: senderAddr,
                    onComplete: OnApplicationComplete.NoOpOC,
                    suggestedParams: params,
                }),
                signer: null,
            });
            txs[txs.length - 1].tx.fee = txs[txs.length - 1].tx.fee * 2;
        }
        if ((meta === "TokenBridge Transfer" ||
            meta === "TokenBridge Transfer With Payload") &&
            parsedVAA.Contract !== undefined) {
            let foreignAssets = [];
            let a = 0;
            if (parsedVAA.FromChain !== CHAIN_ID_ALGORAND) {
                let asset = yield decodeLocalState(client, tokenBridgeId, chainAddr);
                if (asset.length > 8) {
                    const tmp = Buffer.from(asset.slice(0, 8));
                    a = safeBigIntToNumber(tmp.readBigUInt64BE(0));
                }
            }
            else {
                a = parseInt(parsedVAA.Contract, 16);
            }
            // The receiver needs to be optin in to receive the coins... Yeah, the relayer pays for this
            let aid = 0;
            let addr = "";
            if (parsedVAA.ToAddress !== undefined) {
                if (parsedVAA.ToChain === 8 && parsedVAA.Type === 3) {
                    aid = Number(hexToNativeAssetBigIntAlgorand(uint8ArrayToHex(parsedVAA.ToAddress)));
                    addr = getApplicationAddress(aid);
                }
                else {
                    addr = encodeAddress(parsedVAA.ToAddress);
                }
            }
            if (a !== 0) {
                foreignAssets.push(a);
                if (!(yield assetOptinCheck(client, BigInt(a), addr))) {
                    if (senderAddr != addr) {
                        throw new Error("cannot ASA optin for somebody else (asset " + a.toString() + ")");
                    }
                    txs.unshift({
                        tx: makeAssetTransferTxnWithSuggestedParamsFromObject({
                            amount: 0,
                            assetIndex: a,
                            from: senderAddr,
                            suggestedParams: params,
                            to: senderAddr,
                        }),
                        signer: null,
                    });
                }
            }
            accts.push(addr);
            txs.push({
                tx: makeApplicationCallTxnFromObject({
                    accounts: accts,
                    appArgs: [textToUint8Array("completeTransfer"), vaa],
                    appIndex: safeBigIntToNumber(tokenBridgeId),
                    foreignAssets: foreignAssets,
                    from: senderAddr,
                    onComplete: OnApplicationComplete.NoOpOC,
                    suggestedParams: params,
                }),
                signer: null,
            });
            // We need to cover the inner transactions
            if (parsedVAA.Fee !== undefined &&
                Buffer.compare(parsedVAA.Fee, Buffer.from(ZERO_PAD_BYTES, "hex")) === 0)
                txs[txs.length - 1].tx.fee = txs[txs.length - 1].tx.fee * 2;
            else
                txs[txs.length - 1].tx.fee = txs[txs.length - 1].tx.fee * 3;
            if (meta === "TokenBridge Transfer With Payload") {
                txs[txs.length - 1].tx.appForeignApps = [aid];
                let m = abi.ABIMethod.fromSignature("portal_transfer(byte[])byte[]");
                txs.push({
                    tx: makeApplicationCallTxnFromObject({
                        appArgs: [
                            m.getSelector(),
                            m.args[0].type.encode(vaa),
                        ],
                        appIndex: aid,
                        foreignAssets: foreignAssets,
                        from: senderAddr,
                        onComplete: OnApplicationComplete.NoOpOC,
                        suggestedParams: params,
                    }),
                    signer: null,
                });
            }
        }
        return txs;
    });
}
export function uint8ArrayToNativeStringAlgorand(a) {
    return encodeAddress(a);
}
export function hexToNativeStringAlgorand(s) {
    return uint8ArrayToNativeStringAlgorand(hexToUint8Array(s));
}
export function nativeStringToHexAlgorand(s) {
    return uint8ArrayToHex(decodeAddress(s).publicKey);
}
export function hexToNativeAssetBigIntAlgorand(s) {
    return BigNumber.from(hexToUint8Array(s)).toBigInt();
}
export function hexToNativeAssetStringAlgorand(s) {
    return BigNumber.from(hexToUint8Array(s)).toString();
}
