// Algorand.ts import algosdk, { Algodv2, bigIntToBytes, decodeAddress, encodeAddress, getApplicationAddress, LogicSigAccount, makeApplicationCallTxnFromObject, makeApplicationOptInTxnFromObject, makeAssetTransferTxnWithSuggestedParamsFromObject, makePaymentTxnWithSuggestedParamsFromObject, OnApplicationComplete, signLogicSigTransaction, Transaction, } from "algosdk"; import abi from "algosdk"; import { BigNumber } from "ethers"; import { keccak256 } from "ethers/lib/utils"; import { getEmitterAddressAlgorand } from "../bridge"; import { ChainId, CHAIN_ID_ALGORAND, hexToUint8Array, textToHexString, textToUint8Array, uint8ArrayToHex, } from "../utils"; import { safeBigIntToNumber } from "../utils/bigint"; import { PopulateData, TmplSig } from "./TmplSig"; const SEED_AMT: number = 1002000; const ZERO_PAD_BYTES = "0000000000000000000000000000000000000000000000000000000000000000"; const MAX_KEYS: number = 15; const MAX_BYTES_PER_KEY: number = 127; const BITS_PER_BYTE: number = 8; export const BITS_PER_KEY: number = MAX_BYTES_PER_KEY * BITS_PER_BYTE; const MAX_BYTES: number = MAX_BYTES_PER_KEY * MAX_KEYS; export const MAX_BITS: number = BITS_PER_BYTE * MAX_BYTES; const MAX_SIGS_PER_TXN: number = 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<[bigint, string]>(); type Signer = { addr: string; signTxn(txn: Transaction): Promise; }; export type TransactionSignerPair = { tx: Transaction; signer: Signer | null; }; export type OptInResult = { addr: string; txs: TransactionSignerPair[]; }; /** * 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 async function getMessageFee( client: Algodv2, bridgeId: bigint ): Promise { const applInfo: Record = await client .getApplicationByID(safeBigIntToNumber(bridgeId)) .do(); const globalState = applInfo["params"]["global-state"]; const key: string = Buffer.from("MessageFee", "binary").toString("base64"); let ret = BigInt(0); globalState.forEach((el: any) => { 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 async function accountExists( client: Algodv2, appId: bigint, acctAddr: string ): Promise { if (accountExistsCache.has([appId, acctAddr])) return true; let ret = false; try { const acctInfo = await client.accountInformation(acctAddr).do(); const als: Record[] = 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; } export type LogicSigAccountInfo = { lsa: LogicSigAccount; doesExist: boolean; }; /** * 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 async function calcLogicSigAccount( client: algosdk.Algodv2, appId: bigint, appIndex: bigint, emitterId: string ): Promise { let data: PopulateData = { addrIdx: appIndex, appAddress: getEmitterAddressAlgorand(appId), appId: appId, emitterId: emitterId, }; const ts: TmplSig = new TmplSig(client); const lsa: LogicSigAccount = await ts.populate(data); const sigAddr: string = lsa.address(); const doesExist: boolean = await 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 async function optin( client: Algodv2, senderAddr: string, appId: bigint, appIndex: bigint, emitterId: string ): Promise { const appAddr: string = getApplicationAddress(appId); // Check to see if we need to create this const { doesExist, lsa } = await calcLogicSigAccount( client, appId, appIndex, emitterId ); const sigAddr: string = lsa.address(); let txs: TransactionSignerPair[] = []; if (!doesExist) { // These are the suggested params from the system const params = await 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: Transaction) => Promise.resolve(signLogicSigTransaction(txn, lsa).blob), }, }); accountExistsCache.add([appId, lsa.address()]); } return { addr: sigAddr, txs, }; } function extract3(buffer: any, start: number, size: number) { return buffer.slice(start, start + size); } /** * Parses the VAA into a Map * @param vaa The VAA to be parsed * @returns The Map containing the parsed elements of the VAA */ export function _parseVAAAlgorand(vaa: Uint8Array): Map { let ret = new Map(); let buf = Buffer.from(vaa); ret.set("version", buf.readIntBE(0, 1)); ret.set("index", buf.readIntBE(1, 4)); ret.set("siglen", buf.readIntBE(5, 1)); const siglen = ret.get("siglen"); if (siglen) { ret.set("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.set("sigs", sigs); let off = siglen * 66 + 6; ret.set("digest", vaa.slice(off)); // This is what is actually signed... ret.set("timestamp", buf.readIntBE(off, 4)); off += 4; ret.set("nonce", buf.readIntBE(off, 4)); off += 4; ret.set("chainRaw", Buffer.from(extract3(vaa, off, 2)).toString("hex")); ret.set("chain", buf.readIntBE(off, 2)); off += 2; ret.set("emitter", Buffer.from(extract3(vaa, off, 32)).toString("hex")); off += 32; ret.set("sequence", buf.readBigUInt64BE(off)); off += 8; ret.set("consistency", buf.readIntBE(off, 1)); off += 1; ret.set("Meta", "Unknown"); if ( !Buffer.compare( extract3(buf, off, 32), Buffer.from( "000000000000000000000000000000000000000000546f6b656e427269646765", "hex" ) ) ) { ret.set("Meta", "TokenBridge"); ret.set("module", extract3(vaa, off, 32)); off += 32; ret.set("action", buf.readIntBE(off, 1)); off += 1; if (ret.get("action") === 1) { ret.set("Meta", "TokenBridge RegisterChain"); ret.set("targetChain", buf.readIntBE(off, 2)); off += 2; ret.set("EmitterChainID", buf.readIntBE(off, 2)); off += 2; ret.set("targetEmitter", extract3(vaa, off, 32)); off += 32; } else if (ret.get("action") === 2) { ret.set("Meta", "TokenBridge UpgradeContract"); ret.set("targetChain", buf.readIntBE(off, 2)); off += 2; ret.set("newContract", extract3(vaa, off, 32)); off += 32; } } else if ( !Buffer.compare( extract3(buf, off, 32), Buffer.from( "00000000000000000000000000000000000000000000000000000000436f7265", "hex" ) ) ) { ret.set("Meta", "CoreGovernance"); ret.set("module", extract3(vaa, off, 32)); off += 32; ret.set("action", buf.readIntBE(off, 1)); off += 1; ret.set("targetChain", buf.readIntBE(off, 2)); off += 2; ret.set("NewGuardianSetIndex", buf.readIntBE(off, 4)); } // ret.set("len", vaa.slice(off).length) // ret.set("act", buf.readIntBE(off, 1)) if (vaa.slice(off).length === 100 && buf.readIntBE(off, 1) === 2) { ret.set("Meta", "TokenBridge Attest"); ret.set("Type", buf.readIntBE(off, 1)); off += 1; ret.set("Contract", uint8ArrayToHex(extract3(vaa, off, 32))); off += 32; ret.set("FromChain", buf.readIntBE(off, 2)); off += 2; ret.set("Decimals", buf.readIntBE(off, 1)); off += 1; ret.set("Symbol", extract3(vaa, off, 32)); off += 32; ret.set("Name", extract3(vaa, off, 32)); } if (vaa.slice(off).length === 133 && buf.readIntBE(off, 1) === 1) { ret.set("Meta", "TokenBridge Transfer"); ret.set("Type", buf.readIntBE(off, 1)); off += 1; ret.set("Amount", extract3(vaa, off, 32)); off += 32; ret.set("Contract", uint8ArrayToHex(extract3(vaa, off, 32))); off += 32; ret.set("FromChain", buf.readIntBE(off, 2)); off += 2; ret.set("ToAddress", extract3(vaa, off, 32)); off += 32; ret.set("ToChain", buf.readIntBE(off, 2)); off += 2; ret.set("Fee", extract3(vaa, off, 32)); } if (off >= buf.length) { return ret; } if (buf.readIntBE(off, 1) === 3) { ret.set("Meta", "TokenBridge Transfer With Payload"); ret.set("Type", buf.readIntBE(off, 1)); off += 1; ret.set("Amount", extract3(vaa, off, 32)); off += 32; ret.set("Contract", uint8ArrayToHex(extract3(vaa, off, 32))); off += 32; ret.set("FromChain", buf.readIntBE(off, 2)); off += 2; ret.set("ToAddress", extract3(vaa, off, 32)); off += 32; ret.set("ToChain", buf.readIntBE(off, 2)); off += 2; ret.set("Fee", extract3(vaa, off, 32)); off += 32; ret.set("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 async function decodeLocalState( client: Algodv2, appId: bigint, address: string ): Promise { let app_state = null; const ai = await 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: string[] = []; let vals: Map = new Map(); for (const kv of app_state) { const k = Buffer.from(kv["key"], "base64"); const key: number = k.readInt8(); if (!Buffer.compare(k, m)) { continue; } const v: Buffer = 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 async function assetOptinCheck( client: Algodv2, asset: bigint, receiver: string ): Promise { const acctInfo = await client.accountInformation(receiver).do(); const assets: Array = acctInfo.assets; let ret = false; assets.forEach((a) => { const assetId = BigInt(a["asset-id"]); if (assetId === asset) { ret = true; return; } }); return ret; } class SubmitVAAState { vaaMap: Map; accounts: string[]; txs: TransactionSignerPair[]; guardianAddr: string; constructor( vaaMap: Map, accounts: string[], txs: TransactionSignerPair[], guardianAddr: string ) { 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 async function submitVAAHeader( client: Algodv2, bridgeId: bigint, vaa: Uint8Array, senderAddr: string, appid: bigint ): Promise { // A lot of our logic here depends on parseVAA and knowing what the payload is.. const parsedVAA: Map = _parseVAAAlgorand(vaa); const seq: bigint = parsedVAA.get("sequence") / BigInt(MAX_BITS); const chainRaw: string = parsedVAA.get("chainRaw"); // TODO: this needs to be a hex string const em: string = parsedVAA.get("emitter"); // TODO: this needs to be a hex string const index: number = parsedVAA.get("index"); let txs: TransactionSignerPair[] = []; // "seqAddr" const { addr: seqAddr, txs: seqOptInTxs } = await 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 } = await optin( client, senderAddr, bridgeId, BigInt(index), guardianPgmName ); txs.push(...guardianOptInTxs); let accts: string[] = [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: Uint8Array = await decodeLocalState( client, bridgeId, guardianAddr ); const params: algosdk.SuggestedParams = await 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.get("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: number = parsedVAA.get("siglen"); let numTxns: number = Math.floor(numSigs / MAX_SIGS_PER_TXN) + 1; const SIG_LEN: number = 66; const BSIZE: number = SIG_LEN * MAX_SIGS_PER_TXN; const signatures: Uint8Array = parsedVAA.get("signatures"); const verifySigArg: Uint8Array = textToUint8Array("verifySigs"); const lsa = new LogicSigAccount(ALGO_VERIFY); for (let nt = 0; nt < numTxns; nt++) { let sigs: Uint8Array = 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: number = 20; const numSigsThisTxn = sigs.length / SIG_LEN; let arraySize: number = numSigsThisTxn * GuardianKeyLen; let keySet: Uint8Array = 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: Transaction) => 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 async function _submitVAAAlgorand( client: Algodv2, tokenBridgeId: bigint, bridgeId: bigint, vaa: Uint8Array, senderAddr: string ): Promise { let sstate = await 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.get("Meta") === "CoreGovernance" && parsedVAA.get("action") === 2 ) { const ngsi = parsedVAA.get("NewGuardianSetIndex"); const guardianPgmName = textToHexString("guardian"); // "newGuardianAddr" const { addr: newGuardianAddr, txs: newGuardianOptInTxs } = await optin( client, senderAddr, bridgeId, 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.get("Meta"); let chainAddr: string = ""; if ( meta === "TokenBridge Attest" || meta === "TokenBridge Transfer" || meta === "TokenBridge Transfer With Payload" ) { if (parsedVAA.get("FromChain") !== CHAIN_ID_ALGORAND) { // "TokenBridge chainAddr" const result = await optin( client, senderAddr, tokenBridgeId, parsedVAA.get("FromChain"), parsedVAA.get("Contract") ); chainAddr = result.addr; txs.unshift(...result.txs); } else { const assetId = hexToNativeAssetBigIntAlgorand(parsedVAA.get("Contract")); // "TokenBridge native chainAddr" const result = await optin( client, senderAddr, tokenBridgeId, assetId, textToHexString("native") ); chainAddr = result.addr; txs.unshift(...result.txs); } accts.push(chainAddr); } const params: algosdk.SuggestedParams = await 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: Uint8Array = await decodeLocalState( client, tokenBridgeId, chainAddr ); let foreignAssets: number[] = []; 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: Uint8Array = 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" ) { let foreignAssets: number[] = []; let a: number = 0; if (parsedVAA.get("FromChain") !== CHAIN_ID_ALGORAND) { let asset = await 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.get("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.get("ToChain") === 8) && (parsedVAA.get("Type") === 3)) { aid = Number(hexToNativeAssetBigIntAlgorand(parsedVAA.get("ToAddress"))); addr = getApplicationAddress(aid); } else { addr = encodeAddress(hexToUint8Array(parsedVAA.get("ToAddress"))); } if (a !== 0) { foreignAssets.push(a); if (!(await 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 ( Buffer.compare( parsedVAA.get("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 as abi.ABIType).encode(vaa)], appIndex: aid, foreignAssets: foreignAssets, from: senderAddr, onComplete: OnApplicationComplete.NoOpOC, suggestedParams: params, }), signer: null, }); } } return txs; } export function uint8ArrayToNativeStringAlgorand(a: Uint8Array): string { return encodeAddress(a); } export function hexToNativeStringAlgorand(s: string): string { return uint8ArrayToNativeStringAlgorand(hexToUint8Array(s)); } export function nativeStringToHexAlgorand(s: string): string { return uint8ArrayToHex(decodeAddress(s).publicKey); } export function hexToNativeAssetBigIntAlgorand(s: string): bigint { return BigNumber.from(hexToUint8Array(s)).toBigInt(); } export function hexToNativeAssetStringAlgorand(s: string): string { return BigNumber.from(hexToUint8Array(s)).toString(); }