diff --git a/connect/src/protocols/tokenTransfer.ts b/connect/src/protocols/tokenTransfer.ts index 97fcf030f..1ec11167a 100644 --- a/connect/src/protocols/tokenTransfer.ts +++ b/connect/src/protocols/tokenTransfer.ts @@ -185,7 +185,6 @@ export class TokenTransfer const fromChain = this.wh.getChain(this.transfer.from.chain); this.txids = await TokenTransfer.transfer(fromChain, this.transfer, signer); this._state = TransferState.SourceInitiated; - return this.txids.map(({ txid }) => txid); } @@ -225,7 +224,6 @@ export class TokenTransfer timeout, ); } - this._state = TransferState.Attested; return this.attestations.map((vaa) => vaa.id); } @@ -254,8 +252,8 @@ export class TokenTransfer attestation as TokenTransferVAA, signer, ); - this.txids.push(...redeemTxids); + this._state = TransferState.DestinationInitiated; return redeemTxids.map(({ txid }) => txid); } diff --git a/examples/package.json b/examples/package.json index 6cdbf5216..cad20ed3f 100644 --- a/examples/package.json +++ b/examples/package.json @@ -33,13 +33,13 @@ }, "sideEffects": false, "scripts": { - "algo": "tsx src/algoTokenBridge.ts", + "algo": "cd ../platforms/algorand/protocols/tokenBridge && npm run build && cd - && tsx src/algoTokenBridge.ts", "tb": "tsx src/tokenBridge.ts", "cctp": "tsx src/cctp.ts", "demo": "tsx src/index.ts", "cosmos": "tsx src/cosmos.ts", "retb": "cd .. && npm run build && cd - && npm run tb", - "msg": "cd ../platforms/algorand/protocols/core && npm run build && cd - && tsx src/messaging.ts", + "msg": "tsx src/messaging.ts", "clean": "rm -rf ./dist && rm -f ./*.tsbuildinfo", "lint": "npm run prettier && eslint --fix", "prettier": "prettier --write ./src", diff --git a/examples/src/algoTokenBridge.ts b/examples/src/algoTokenBridge.ts index b9cfed971..730c8232d 100644 --- a/examples/src/algoTokenBridge.ts +++ b/examples/src/algoTokenBridge.ts @@ -36,11 +36,11 @@ import "@wormhole-foundation/connect-sdk-evm-tokenbridge"; const wh = new Wormhole("Testnet", [AlgorandPlatform, EvmPlatform]); // Grab chain Contexts -- these hold a reference to a cached rpc client - const sendChain = wh.getChain("Algorand"); - const rcvChain = wh.getChain("Avalanche"); + const sendChain = wh.getChain("Avalanche"); + const rcvChain = wh.getChain("Algorand"); // Shortcut to allow transferring native gas token - worked 12-28-23 - // const token: TokenId | "native" = "native"; + const token: TokenId | "native" = "native"; // Test Algorand native ASA outbound with Testnet USDC 10458941 - worked 12-28-23 // const token = Wormhole.chainAddress("Algorand", new AlgorandAddress(BigInt(10458941)).toString()); @@ -49,7 +49,7 @@ import "@wormhole-foundation/connect-sdk-evm-tokenbridge"; // const token = Wormhole.chainAddress("Algorand", new AlgorandAddress(BigInt(86783266)).toString()); // Test other chain wrapped token back to Algorand ASA with Avalanche wUSDC 0x12EB0d635FD4C5692d779755Ba82b33F6439fc73 - Failing on redeem 12-28-23 - const token = Wormhole.chainAddress("Avalanche", "0x12EB0d635FD4C5692d779755Ba82b33F6439fc73"); + // const token = Wormhole.chainAddress("Avalanche", "0x12EB0d635FD4C5692d779755Ba82b33F6439fc73"); // Normalized given token decimals later but can just pass bigints as base units // Note: The Token bridge will dedust past 8 decimals @@ -83,8 +83,9 @@ import "@wormhole-foundation/connect-sdk-evm-tokenbridge"; // Set this to the transfer txid of the initiating transaction to recover a token transfer // and attempt to fetch details about its progress. - // let recoverTxid = undefined; - let recoverTxid = "0xdab98de823cd9e2ec3975bf366503dcd896a47a7ce3764fb964cc84b54f7159c"; // Avalanche-->Algorand + let recoverTxid = undefined; + // let recoverTxid = "0xdab98de823cd9e2ec3975bf366503dcd896a47a7ce3764fb964cc84b54f7159c"; // Avalanche-->Algorand + // recoverTxid = "0x83b4438b9135eef05734beea4fd4e41d644b1d07196c491e9576bf0ed24a9797"; // Finally create and perform the transfer given the parameters set above const xfer = !recoverTxid @@ -107,7 +108,7 @@ import "@wormhole-foundation/connect-sdk-evm-tokenbridge"; console.log("xfer: ", xfer); // Log out the results - if (xfer.getTransferState() <= TransferState.DestinationInitiated) { + if (xfer.getTransferState() < TransferState.DestinationInitiated) { console.log(await xfer.completeTransfer(destination.signer)); } })(); @@ -125,7 +126,6 @@ async function tokenTransfer( }; payload?: Uint8Array; }, - roundTrip?: boolean, ): Promise> { // Create a TokenTransfer object to track the state of // the transfer over time @@ -170,26 +170,4 @@ async function tokenTransfer( console.log(`Completed Transfer: `, destTxids); return xfer; - // // No need to send back, dip - // if (!roundTrip) return xfer; - - // // We can look up the destination asset for this transfer given the context of - // // the sending chain and token and destination chain - // const token = await TokenTransfer.lookupDestinationToken( - // route.source.chain, - // route.destination.chain, - // xfer.transfer, - // ); - // console.log(token); - - // // The wrapped token may have a different number of decimals - // // to make things easy, lets just send the amount from the VAA back - // const amount = xfer.vaas![0]!.vaa!.payload.token.amount; - // return await tokenTransfer(wh, { - // ...route, - // token, - // amount, - // source: route.destination, - // destination: route.source, - // }); } diff --git a/platforms/algorand/protocols/core/src/core.ts b/platforms/algorand/protocols/core/src/core.ts index 9efbf9187..1c8e7a35e 100644 --- a/platforms/algorand/protocols/core/src/core.ts +++ b/platforms/algorand/protocols/core/src/core.ts @@ -69,15 +69,16 @@ export class AlgorandWormholeCore this.tokenBridgeAppAddress = getApplicationAddress(tokenBridge); } - async *verifyMessage(sender: AnyAlgorandAddress, vaa: VAA) { + async *verifyMessage(sender: AnyAlgorandAddress, vaa: VAA, appId?: bigint) { const address = new AlgorandAddress(sender).toString(); const txset = await submitVAAHeader( this.connection, this.coreAppId, - this.coreAppId, + appId ?? this.coreAppId, vaa, address, ); + for (const tx of txset.txs) { yield this.createUnsignedTx(tx, "Core.verifyMessage"); } diff --git a/platforms/algorand/protocols/tokenBridge/src/assets.ts b/platforms/algorand/protocols/tokenBridge/src/assets.ts deleted file mode 100644 index 659b639aa..000000000 --- a/platforms/algorand/protocols/tokenBridge/src/assets.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { safeBigIntToNumber } from "@wormhole-foundation/connect-sdk-algorand"; -import { Algodv2, modelsv2 } from "algosdk"; - -/** - * 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 Promise with True if the asset was opted in, False otherwise - */ -export async function isOptedIn(client: Algodv2, address: string, asset: bigint): Promise { - try { - const acctInfoResp = await client - .accountAssetInformation(address, safeBigIntToNumber(asset)) - .do(); - const acctInfo = modelsv2.AccountAssetResponse.from_obj_for_encoding(acctInfoResp); - return acctInfo.assetHolding.amount > 0; - } catch {} - return false; -} diff --git a/platforms/algorand/protocols/tokenBridge/src/index.ts b/platforms/algorand/protocols/tokenBridge/src/index.ts index a6b033abf..354f400a2 100644 --- a/platforms/algorand/protocols/tokenBridge/src/index.ts +++ b/platforms/algorand/protocols/tokenBridge/src/index.ts @@ -12,6 +12,4 @@ declare global { registerProtocol(_platform, "TokenBridge", AlgorandTokenBridge); -export * from "./assets"; -export * from "./tokenBridge"; export * from "./tokenBridge"; diff --git a/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts b/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts index 003565546..3a581ca5c 100644 --- a/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts +++ b/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts @@ -33,9 +33,16 @@ import { decodeLocalState, getMessageFee, safeBigIntToNumber, + isOptedIn, varint, } from "@wormhole-foundation/connect-sdk-algorand"; import { + AlgorandWormholeCore, + maybeCreateStorageTx, + submitVAAHeader, +} from "@wormhole-foundation/connect-sdk-algorand-core"; +import { + ABIMethod, ABIType, Algodv2, OnApplicationComplete, @@ -49,14 +56,7 @@ import { makeAssetTransferTxnWithSuggestedParamsFromObject, makePaymentTxnWithSuggestedParamsFromObject, modelsv2, - ABIMethod, } from "algosdk"; -import { isOptedIn } from "./assets"; -import { submitVAAHeader } from "@wormhole-foundation/connect-sdk-algorand-core/src/vaa"; -import { - AlgorandWormholeCore, - maybeCreateStorageTx, -} from "@wormhole-foundation/connect-sdk-algorand-core"; import "@wormhole-foundation/connect-sdk-algorand-core"; @@ -74,6 +74,8 @@ export class AlgorandTokenBridge readonly tokenBridgeAppId: bigint; readonly tokenBridgeAddress: string; + static completeTransfer = encoding.bytes.encode("completeTransfer"); + constructor( readonly network: N, readonly chain: C, @@ -556,43 +558,40 @@ export class AlgorandTokenBridge console.log("vaa payload token: ", vaa.payload.token.address.toString()); const senderAddr = new AlgorandAddress(sender).toString(); - //yield *this.coreBridge.verifyMessage(senderAddr, vaa); - let { accounts, txs } = await submitVAAHeader( + const { accounts, txs } = await submitVAAHeader( this.connection, this.coreAppId, this.tokenBridgeAppId, vaa, senderAddr, ); - console.log("accounts: ", accounts); - console.log("txs: ", txs); const tokenStorage = StorageLogicSig.forWrappedAsset(this.tokenBridgeAppId, vaa.payload.token); const tokenStorageAddress = tokenStorage.address(); let foreignAssets: number[] = []; let assetId: number = 0; - if (vaa.payload.token.chain !== "Algorand") { + if (vaa.payload.token.chain !== this.chain) { let asset = await decodeLocalState( this.connection, this.tokenBridgeAppId, tokenStorageAddress, ); - if (asset.length > 8) { - const tmp = Buffer.from(asset.slice(0, 8)); - assetId = safeBigIntToNumber(tmp.readBigUInt64BE(0)); - console.log("assetId1: ", assetId); - } + assetId = safeBigIntToNumber(encoding.bignum.decode(asset.slice(0, 8))); } else { - assetId = parseInt(vaa.payload.token.address.toString().slice(2), 16); - console.log("assetId2: ", assetId); + assetId = safeBigIntToNumber( + encoding.bignum.decode(vaa.payload.token.address.toUint8Array().slice(0, 8)), + ); } + accounts.push(tokenStorageAddress); let aid = 0; let addr = ""; if (vaa.payloadName === "TransferWithPayload") { - aid = Number(bytesToBigInt(vaa.payload.to.address.toUint8Array())); + aid = safeBigIntToNumber( + encoding.bignum.decode(vaa.payload.to.address.toUint8Array().slice(0, 8)), + ); addr = getApplicationAddress(aid); } else { addr = encodeAddress(vaa.payload.to.address.toUint8Array()); @@ -621,13 +620,14 @@ export class AlgorandTokenBridge accounts.push(addr); const appCallObj = { accounts: accounts, - appArgs: [encoding.bytes.encode("completeTransfer"), serialize(vaa)], + appArgs: [AlgorandTokenBridge.completeTransfer, serialize(vaa)], appIndex: safeBigIntToNumber(this.tokenBridgeAppId), foreignAssets: foreignAssets, from: senderAddr, onComplete: OnApplicationComplete.NoOpOC, suggestedParams, }; + console.log("appCallObj: ", appCallObj); txs.push({ tx: makeApplicationCallTxnFromObject(appCallObj), @@ -635,11 +635,11 @@ export class AlgorandTokenBridge }); // We need to cover the inner transactions - if (vaa.payloadName === "Transfer" && vaa.payload.fee !== undefined && vaa.payload.fee === 0n) { - 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; - } + txs[txs.length - 1].tx.fee = + txs[txs.length - 1].tx.fee * + (vaa.payloadName === "Transfer" && vaa.payload.fee !== undefined && vaa.payload.fee === 0n + ? 2 + : 3); if (vaa.payloadName === "TransferWithPayload") { txs[txs.length - 1].tx.appForeignApps = [aid]; diff --git a/platforms/algorand/src/utilities.ts b/platforms/algorand/src/utilities.ts index 23b02becf..448811264 100644 --- a/platforms/algorand/src/utilities.ts +++ b/platforms/algorand/src/utilities.ts @@ -169,3 +169,21 @@ export async function decodeLocalState( } 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 Promise with True if the asset was opted in, False otherwise + */ +export async function isOptedIn(client: Algodv2, address: string, asset: bigint): Promise { + try { + const acctInfoResp = await client + .accountAssetInformation(address, safeBigIntToNumber(asset)) + .do(); + const acctInfo = modelsv2.AccountAssetResponse.from_obj_for_encoding(acctInfoResp); + return acctInfo.assetHolding.amount > 0; + } catch {} + return false; +}