diff --git a/connect/src/protocols/tokenTransfer.ts b/connect/src/protocols/tokenTransfer.ts index 2524b9e78..162664038 100644 --- a/connect/src/protocols/tokenTransfer.ts +++ b/connect/src/protocols/tokenTransfer.ts @@ -368,7 +368,6 @@ export class TokenTransfer // otherwise, check to see if it is a wrapped token locally lookup = await tb.getOriginalAsset(token.address); } catch (e) { - console.error(e); // not a from-chain native wormhole-wrapped one lookup = token; } diff --git a/core/base/src/utils/encoding.ts b/core/base/src/utils/encoding.ts index eef808eeb..4ed7d2b18 100644 --- a/core/base/src/utils/encoding.ts +++ b/core/base/src/utils/encoding.ts @@ -32,8 +32,11 @@ export const b58 = { }; export const bignum = { - decode: (input: string | Uint8Array) => - typeof input === "string" ? BigInt(input) : BigInt(hex.encode(input, true)), + decode: (input: string | Uint8Array) => { + if (typeof input !== "string") input = hex.encode(input, true); + if (input === "" || input === "0x") return 0n; + return BigInt(input); + }, encode: (input: bigint, prefix: boolean = false) => bignum.toString(input, prefix), toString: (input: bigint, prefix: boolean = false) => { let str = input.toString(16); diff --git a/examples/package.json b/examples/package.json index cad20ed3f..fa65ba262 100644 --- a/examples/package.json +++ b/examples/package.json @@ -34,6 +34,7 @@ "sideEffects": false, "scripts": { "algo": "cd ../platforms/algorand/protocols/tokenBridge && npm run build && cd - && tsx src/algoTokenBridge.ts", + "wrapped": "cd ../platforms/algorand/protocols/tokenBridge && npm run build && cd - && tsx src/createWrapped.ts", "tb": "tsx src/tokenBridge.ts", "cctp": "tsx src/cctp.ts", "demo": "tsx src/index.ts", @@ -64,6 +65,6 @@ "@wormhole-foundation/connect-sdk-evm-cctp": "^0.3.0-beta.6", "@wormhole-foundation/connect-sdk-solana-cctp": "^0.3.0-beta.6", "@wormhole-foundation/connect-sdk-cosmwasm-ibc": "^0.3.0-beta.6", - "algosdk":"^2.7.0" + "algosdk": "^2.7.0" } -} +} \ No newline at end of file diff --git a/examples/src/algoTokenBridge.ts b/examples/src/algoTokenBridge.ts index 50a633b0f..4bd138533 100644 --- a/examples/src/algoTokenBridge.ts +++ b/examples/src/algoTokenBridge.ts @@ -5,8 +5,8 @@ import { TokenId, TokenTransfer, TransferState, - UniversalAddress, Wormhole, + encoding, isTokenId, normalizeAmount, } from "@wormhole-foundation/connect-sdk"; @@ -21,16 +21,19 @@ import { SolanaPlatform } from "@wormhole-foundation/connect-sdk-solana"; import "@wormhole-foundation/connect-sdk-algorand-tokenbridge"; import "@wormhole-foundation/connect-sdk-evm-tokenbridge"; import "@wormhole-foundation/connect-sdk-solana-tokenbridge"; +import algosdk, { Algodv2 } from "algosdk"; +import { AlgorandSigner } from "@wormhole-foundation/connect-sdk-algorand/src/testing"; /* -1. Algorand native to other chain -2. Return wrapped ALGO from other chain to Algorand native -3. Algorand ASA to other chain -4. Return wrapped ASA from other chain to Algorand ASA -5. Other chain native to Algorand wrapped token -6. Return Algorand wrapped token to other chain native -7. Other chain token to Algorand wrapped token -8. Return Algorand wrapped token to other chain token +# Scenario | Status | TxID +1. Algorand native ALGO to other chain | OK | NK7DK5CLRU2HWNHFNBNFCLM5RLNBVICBUYEW6FBEQVMCVFBBG7JA +2. Return wrapped ALGO from other chain to Algorand native ALGO | FAIL | 4JGo9dwVv8XVTyf9CDXzX5QD4aBc4ZB6sHF7wFqw5LNLstqkRFmumwvu8HATddBVDcybKAAvrACfw1UEw3TD122b +3. Algorand ASA to other chain | OK_Ava | BNRWXLRWR7FVYMBBWHNWWCF65YQBDJAHVA5AMWADEC6K3WH76VYQ +4. Return wrapped token from other chain to original Algorand ASA | FAIL | 0x0dc8e8a052de3c62cda7d8a8211ac49c3c2c43d8841ee462e309c3d1abccbda4 +5. Other chain native asset orand wrapped token | OK | 4wapEufhVAtv8oRqdrRzEovBHKkJDDD3m2pf1Jq3XtpvFwqA2YUijFqFufrGNyxY54vohmy3tsXCb2frcuBNa61T +6. Return Algorand wrapped token to other chain native asset | OK | TD5OWVV6BED5VFAGXBUWVITW6EX3KYOPXEJQLGMFIXM3JJ75HULQ +7. Other chain token to Algorand wrapped token | | +8. Return Algorand wrapped token to other chain token | | */ (async function () { @@ -43,14 +46,18 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge"; const rcvChain = wh.getChain("Solana"); // Shortcut to allow transferring native gas token - const token: TokenId | "native" = "native"; - // const token = Wormhole.chainAddress("Algorand", "86897238"); + // const token: TokenId | "native" = "native"; + + const token = Wormhole.chainAddress("Algorand", "10458941"); // USDC on Algorand + // const token = Wormhole.chainAddress("Avalanche", "0x12EB0d635FD4C5692d779755Ba82b33F6439fc73"); // wUSDC on Avalanche + // const token = Wormhole.chainAddress("Algorand", "86897238"); // wSOL on Algorand + // const token = Wormhole.chainAddress("Solana", "9rU2jFrzA5zDDmt9yR7vEABvXCUNJ1YgGigdTb9oCaTv"); // wALGO on Solana // Normalized given token decimals later but can just pass bigints as base units // Note: The Token bridge will dedust past 8 decimals // this means any amount specified past that point will be returned // to the caller - const amount = "0.0001"; + const amount = "0.00001"; // With automatic set to true, perform an automatic transfer. This will invoke a relayer // contract intermediary that knows to pick up the transfers @@ -79,7 +86,10 @@ import "@wormhole-foundation/connect-sdk-solana-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; - // recoverTxid = "BCAAZRCXAVTKKPIOTUE32GH6LQCW3CSCR3HAEM3I65KNH7PUUPKA"; + // recoverTxid = + // "4JGo9dwVv8XVTyf9CDXzX5QD4aBc4ZB6sHF7wFqw5LNLstqkRFmumwvu8HATddBVDcybKAAvrACfw1UEw3TD122b"; // Recover scenario 2 + // recoverTxid = + // "0x0dc8e8a052de3c62cda7d8a8211ac49c3c2c43d8841ee462e309c3d1abccbda4"; // Recover scenario 4 // Finally create and perform the transfer given the parameters set above const xfer = !recoverTxid diff --git a/examples/src/createWrapped.ts b/examples/src/createWrapped.ts new file mode 100644 index 000000000..ea904cbef --- /dev/null +++ b/examples/src/createWrapped.ts @@ -0,0 +1,91 @@ +import { TokenId, Wormhole, signSendWait } from "@wormhole-foundation/connect-sdk"; +import { AlgorandPlatform } from "@wormhole-foundation/connect-sdk-algorand"; +import { SolanaPlatform } from "@wormhole-foundation/connect-sdk-solana"; +import { getStuff } from "./helpers"; + +import "@wormhole-foundation/connect-sdk-algorand-tokenbridge"; +import "@wormhole-foundation/connect-sdk-solana-tokenbridge"; + +(async function () { + const wh = new Wormhole("Testnet", [AlgorandPlatform, SolanaPlatform]); + + // Original Token to Attest + const token: TokenId = Wormhole.chainAddress("Algorand", "10458941"); + + // grab context and signer + const origChain = wh.getChain(token.chain); + const { signer: origSigner } = await getStuff(origChain); + + // Note: if the VAA is not produced before the attempt to retrieve it times out + // you should set this value to the txid logged in the previous run + let txid = undefined; + // txid = "0x55127b9c8af46aaeea9ef28d8bf91e1aff920422fc1c9831285eb0f39ddca2fe"; + + txid = "FPNHIFFUZDVPT5SATZQZZ7DFGZMPCCHEFBCB5EZQJV4RRK3ZYTVA"; + txid = "GWZU432ERFU3NES4MA7IAAP6DX73F5VRSSIWGJVC5JRHOH6UMWEQ"; + + if (!txid) { + // create attestation from origin chain, the same VAA + // can be used across all chains + const tb = await origChain.getTokenBridge(); + const attestTxns = tb.createAttestation( + token.address, + Wormhole.parseAddress(origSigner.chain(), origSigner.address()), + ); + const txids = await signSendWait(origChain, attestTxns, origSigner); + txid = txids[0].txid; + console.log("Created attestation (save this): ", txid); + } + + // Get the wormhole message id from the transaction logs + const msgs = await origChain.parseTransaction(txid); + console.log(msgs); + + // Get the Signed VAA from the API + const timeout = 60_000; // 60 seconds + const vaa = await wh.getVaa(msgs[0], "TokenBridge:AttestMeta", timeout); + if (!vaa) throw new Error("VAA not found after retries exhausted, try extending the timeout"); + + console.log(vaa.payload.token.address); + + // Check if its attested and if not + // submit the attestation to the token bridge on the + // destination chain + const chain = "Solana"; + const destChain = wh.getChain(chain); + const { signer } = await getStuff(destChain); + + // grab a ref to the token bridge + const tb = await destChain.getTokenBridge(); + try { + // try to get the wrapped version, an error here likely means + // its not been attested + const wrapped = await tb.getWrappedAsset(token); + console.log("already wrapped"); + return { chain, address: wrapped }; + } catch (e) {} + + // no wrapped asset, needs to be attested + console.log("attesting asset"); + await signSendWait( + destChain, + tb.submitAttestation(vaa, Wormhole.parseAddress(signer.chain(), signer.address())), + signer, + ); + + async function waitForIt() { + do { + // check again + try { + const wrapped = await tb.getWrappedAsset(token); + return { chain, address: wrapped }; + } catch (e) { + console.error(e); + } + console.log("Waiting before checking again..."); + await new Promise((r) => setTimeout(r, 2000)); + } while (true); + } + + console.log("wrapped: ", await waitForIt()); +})(); diff --git a/package-lock.json b/package-lock.json index e2d273827..c76c6d746 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9444,10 +9444,10 @@ }, "platforms/algorand": { "name": "@wormhole-foundation/connect-sdk-algorand", - "version": "0.3.0-beta.5", + "version": "0.3.0-beta.6", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/connect-sdk": "^0.3.0-beta.5", + "@wormhole-foundation/connect-sdk": "^0.3.0-beta.6", "algosdk": "2.7.0" }, "engines": { @@ -9456,11 +9456,11 @@ }, "platforms/algorand/protocols/core": { "name": "@wormhole-foundation/connect-sdk-algorand-core", - "version": "0.3.0-beta.5", + "version": "0.3.0-beta.6", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/connect-sdk": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.5" + "@wormhole-foundation/connect-sdk": "^0.3.0-beta.6", + "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.6" }, "engines": { "node": ">=16" @@ -9468,12 +9468,12 @@ }, "platforms/algorand/protocols/tokenBridge": { "name": "@wormhole-foundation/connect-sdk-algorand-tokenbridge", - "version": "0.3.0-beta.5", + "version": "0.3.0-beta.6", "license": "Apache-2.0", "dependencies": { - "@wormhole-foundation/connect-sdk": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-algorand-core": "^0.3.0-beta.5" + "@wormhole-foundation/connect-sdk": "^0.3.0-beta.6", + "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.6", + "@wormhole-foundation/connect-sdk-algorand-core": "^0.3.0-beta.6" }, "engines": { "node": ">=16" diff --git a/platforms/algorand/package.json b/platforms/algorand/package.json index 8b9ef1b6b..aacda7021 100644 --- a/platforms/algorand/package.json +++ b/platforms/algorand/package.json @@ -1,6 +1,6 @@ { "name": "@wormhole-foundation/connect-sdk-algorand", - "version": "0.3.0-beta.5", + "version": "0.3.0-beta.6", "repository": { "type": "git", "url": "git+https://github.com/wormhole-foundation/connect-sdk.git" @@ -43,7 +43,7 @@ "test": "jest --config ./jest.config.ts" }, "dependencies": { - "@wormhole-foundation/connect-sdk": "^0.3.0-beta.5", + "@wormhole-foundation/connect-sdk": "^0.3.0-beta.6", "algosdk": "2.7.0" } } \ No newline at end of file diff --git a/platforms/algorand/protocols/core/package.json b/platforms/algorand/protocols/core/package.json index 540cfed35..5075823a7 100644 --- a/platforms/algorand/protocols/core/package.json +++ b/platforms/algorand/protocols/core/package.json @@ -1,6 +1,6 @@ { "name": "@wormhole-foundation/connect-sdk-algorand-core", - "version": "0.3.0-beta.5", + "version": "0.3.0-beta.6", "repository": { "type": "git", "url": "git+https://github.com/wormhole-foundation/connect-sdk.git" @@ -42,7 +42,7 @@ "prettier": "prettier --write ./src" }, "dependencies": { - "@wormhole-foundation/connect-sdk": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.5" + "@wormhole-foundation/connect-sdk": "^0.3.0-beta.6", + "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.6" } } \ No newline at end of file diff --git a/platforms/algorand/protocols/tokenBridge/package.json b/platforms/algorand/protocols/tokenBridge/package.json index 3ed96dace..2fdf949bc 100644 --- a/platforms/algorand/protocols/tokenBridge/package.json +++ b/platforms/algorand/protocols/tokenBridge/package.json @@ -1,6 +1,6 @@ { "name": "@wormhole-foundation/connect-sdk-algorand-tokenbridge", - "version": "0.3.0-beta.5", + "version": "0.3.0-beta.6", "repository": { "type": "git", "url": "git+https://github.com/wormhole-foundation/connect-sdk.git" @@ -42,8 +42,8 @@ "prettier": "prettier --write ./src" }, "dependencies": { - "@wormhole-foundation/connect-sdk": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-algorand-core": "^0.3.0-beta.5" + "@wormhole-foundation/connect-sdk": "^0.3.0-beta.6", + "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.6", + "@wormhole-foundation/connect-sdk-algorand-core": "^0.3.0-beta.6" } } \ No newline at end of file diff --git a/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts b/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts index d360c6438..9d0e3848f 100644 --- a/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts +++ b/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts @@ -148,7 +148,10 @@ export class AlgorandTokenBridge assetInfo.params.creator, ); - const chainId = encoding.bignum.decode(decodedLocalState.slice(92, 94)); + if (decodedLocalState.length < 94) throw new Error("Invalid local state data"); + + const chainBytes = decodedLocalState.slice(92, 94); + const chainId = encoding.bignum.decode(chainBytes); const assetAddress = decodedLocalState.slice(60, 60 + 32); return { @@ -402,6 +405,8 @@ export class AlgorandTokenBridge const chainId = toChainId(recipient.chain); const receiver = recipient.address.toUniversalAddress().toUint8Array(); + const suggestedParams: SuggestedParams = await this.connection.getTransactionParams().do(); + const fee = BigInt(0); const tbs = StorageLogicSig.fromData({ @@ -415,7 +420,13 @@ export class AlgorandTokenBridge const { accounts: [emitterAddr], txs: emitterOptInTxs, - } = await maybeCreateStorageTx(this.connection, senderAddr, this.coreAppId, tbs); + } = await maybeCreateStorageTx( + this.connection, + senderAddr, + this.coreAppId, + tbs, + suggestedParams, + ); txs.push(...emitterOptInTxs); // Check that the auth address of the creator @@ -427,12 +438,12 @@ export class AlgorandTokenBridge const assetInfoResp: Record = await this.connection.getAssetByID(assetId).do(); const asset = modelsv2.Asset.from_obj_for_encoding(assetInfoResp); creator = asset.params.creator; + const creatorAcctInfoResp = await this.connection.accountInformation(creator).do(); creatorAcct = modelsv2.Account.from_obj_for_encoding(creatorAcctInfoResp); wormhole = creatorAcct.authAddr === this.tokenBridgeAddress.toString(); } - const suggestedParams: SuggestedParams = await this.connection.getTransactionParams().do(); const msgFee: bigint = await getMessageFee(this.connection, this.coreAppId); if (msgFee > 0) txs.push({ @@ -458,6 +469,7 @@ export class AlgorandTokenBridge senderAddr, this.tokenBridgeAppId, nativeStorageAccount, + suggestedParams, ); creator = address; txs.push(...txs); @@ -471,7 +483,7 @@ export class AlgorandTokenBridge amount: 100000, suggestedParams, }); - txs.push({ tx: payTxn, signer: null }); + txs.unshift({ tx: payTxn, signer: null }); // The tokenid app needs to do the optin since it has signature authority let txn = makeApplicationCallTxnFromObject({ from: senderAddr, @@ -483,7 +495,7 @@ export class AlgorandTokenBridge suggestedParams, }); txn.fee *= 2; - txs.push({ tx: txn, signer: null }); + txs.unshift({ tx: txn, signer: null }); } const t = makeApplicationCallTxnFromObject({ diff --git a/platforms/algorand/src/address.ts b/platforms/algorand/src/address.ts index 4f93f1f2e..d1f6a7138 100644 --- a/platforms/algorand/src/address.ts +++ b/platforms/algorand/src/address.ts @@ -37,16 +37,14 @@ export class AlgorandAddress implements Address { this.address = encoding.bytes.zpad( encoding.bignum.toBytes(BigInt(address), 8), AlgorandAddress.byteSize, - false, ); } else if (typeof address === "bigint") { this.address = encoding.bytes.zpad( encoding.bignum.toBytes(address, 8), AlgorandAddress.byteSize, - false, ); } else if (address instanceof Uint8Array && address.byteLength === 8) { - this.address = encoding.bytes.zpad(address, AlgorandAddress.byteSize, false); + this.address = encoding.bytes.zpad(address, AlgorandAddress.byteSize); } else throw new Error(`Invalid Algorand address or ASA ID: ${address}`); } @@ -67,7 +65,7 @@ export class AlgorandAddress implements Address { } toBigInt(): bigint { - return encoding.bignum.decode(this.toUint8Array().slice(0, 8)); + return encoding.bignum.decode(this.toUint8Array().slice(24, 32)); } toInt(): number { diff --git a/platforms/algorand/src/testing/signer.ts b/platforms/algorand/src/testing/signer.ts index 798ca2b26..6f7f642ea 100644 --- a/platforms/algorand/src/testing/signer.ts +++ b/platforms/algorand/src/testing/signer.ts @@ -26,6 +26,7 @@ export class AlgorandSigner return toNative(this.chain, mint.toBase58()); } catch (_) {} - throw ErrNotWrapped(token.address.toUniversalAddress().toString()); + throw ErrNotWrapped( + `${mint}: ${token.address.toUniversalAddress().toString()}`, + ); } async isTransferCompleted(vaa: TokenBridge.TransferVAA): Promise {