diff --git a/examples/package.json b/examples/package.json index e634db7cdb..bd1a9749ba 100644 --- a/examples/package.json +++ b/examples/package.json @@ -47,29 +47,26 @@ "docs": "typedoc" }, "devDependencies": { - "dotenv": "^16.3.1" + "dotenv": "^16.3.1", + "tsx": "^4.7.0" }, "dependencies": { "@wormhole-foundation/connect-sdk": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-solana": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-evm": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-cosmwasm": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-algorand": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-evm-core": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-solana-core": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-cosmwasm-core": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-algorand-core": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-evm-tokenbridge": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-solana-tokenbridge": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-cosmwasm-tokenbridge": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-algorand-tokenbridge": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-evm-cctp": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-solana-cctp": "^0.3.0-beta.5", - - "@wormhole-foundation/connect-sdk-cosmwasm-ibc": "^0.3.0-beta.5" + "@wormhole-foundation/connect-sdk-cosmwasm-ibc": "^0.3.0-beta.5", + "algosdk": "^2.7.0" } -} \ No newline at end of file +} diff --git a/examples/src/algoTokenBridge.ts b/examples/src/algoTokenBridge.ts index f3f7eb0a4c..8d8b78f398 100644 --- a/examples/src/algoTokenBridge.ts +++ b/examples/src/algoTokenBridge.ts @@ -11,32 +11,50 @@ import { import { TransferStuff, getStuff } from "./helpers"; // Import the platform specific packages -import { AlgorandPlatform } from "@wormhole-foundation/connect-sdk-algorand"; +import { AlgorandAddress, AlgorandPlatform } from "@wormhole-foundation/connect-sdk-algorand"; import { EvmPlatform } from "@wormhole-foundation/connect-sdk-evm"; -import { SolanaPlatform } from "@wormhole-foundation/connect-sdk-solana"; // Register the protocols import "@wormhole-foundation/connect-sdk-algorand-tokenbridge"; import "@wormhole-foundation/connect-sdk-evm-tokenbridge"; -import "@wormhole-foundation/connect-sdk-solana-tokenbridge"; + +/* +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 +*/ (async function () { - // init Wormhole object, passing config for which network + // Init Wormhole object, passing config for which network // to use (e.g. Mainnet/Testnet) and what Platforms to support - const wh = new Wormhole("Testnet", [AlgorandPlatform, SolanaPlatform, EvmPlatform]); + 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"); - // shortcut to allow transferring native gas token - const token: TokenId | "native" = "native"; + // Shortcut to allow transferring native gas token - worked 12-28-23 + // 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()); + + // Test Algorand wrapped ASA outbound with Testnet wAVAX 86783266 - worked 12-28-23 + // 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"); // 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.1"; + const amount = "0.0001"; // With automatic set to true, perform an automatic transfer. This will invoke a relayer // contract intermediary that knows to pick up the transfers @@ -58,15 +76,15 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge"; // Used to normalize the amount to account for the tokens decimals const decimals = + // @ts-ignore token === "native" ? BigInt(sendChain.config.nativeTokenDecimals) - : await wh.getDecimals(sendChain.chain, token); + : await wh.getDecimals(sendChain.chain, token.address); // 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 = - // "2daoPz9KyVkG8WGztfatMRx3EKbiRSUVGKAoCST9286eGrzXg5xowafBUUKfd3JrHzvd4AwoH57ujWaJ72k6oiCY"; + // let recoverTxid = undefined; + let recoverTxid = "0xdab98de823cd9e2ec3975bf366503dcd896a47a7ce3764fb964cc84b54f7159c"; // Avalanche-->Algorand // Finally create and perform the transfer given the parameters set above const xfer = !recoverTxid @@ -87,11 +105,11 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge"; txid: recoverTxid, }); - console.log(xfer); + console.log("xfer: ", xfer); // Log out the results - // if (xfer.getTransferState() <= TransferState.DestinationInitiated) { - // console.log(await xfer.completeTransfer(destination.signer)); - // } + if (xfer.getTransferState() <= TransferState.DestinationInitiated) { + console.log(await xfer.completeTransfer(destination.signer)); + } })(); async function tokenTransfer( diff --git a/examples/src/log.txt b/examples/src/log.txt new file mode 100644 index 0000000000..90028f8b89 --- /dev/null +++ b/examples/src/log.txt @@ -0,0 +1,159 @@ + * Executing task: npm run algo + + +> @wormhole-foundation/connect-sdk-examples@0.3.0-beta.5 algo +> tsx src/algoTokenBridge.ts + +Native address type for platform Algorand has already registered +Native address type for platform Evm has already registered +(node:18028) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. +(Use `node --trace-deprecation ...` to show where the warning was created) +Starting transfer +Signing: TokenBridge.wrapAndTransferETH for 0xBA3B58B8EF2EDf9D8e50a25C4e11138Afa372688 +Started transfer: [ + '0x97b28abc5906e7d3ff20594b77148fda5d7d52ccc4554e0eba48b50d06d79199' +] +Getting Attestation +Got Attestation: [ + { + chain: 'Avalanche', + emitter: UniversalAddress { type: 'Universal', address: [Uint8Array] }, + sequence: 15303n + } +] +Completing Transfer +vaa: { + protocolName: 'TokenBridge', + payloadName: 'Transfer', + payloadLiteral: 'TokenBridge:Transfer', + guardianSet: 0, + signatures: [ { guardianIndex: 0, signature: [Signature] } ], + timestamp: 1703790280, + nonce: 0, + emitterChain: 'Avalanche', + emitterAddress: UniversalAddress { + type: 'Universal', + address: Uint8Array(32) [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 97, 228, 78, 80, 108, 165, + 101, 158, 108, 11, 186, 155, 103, 133, 134, + 250, 45, 114, 151, 86 + ] + }, + sequence: 15303n, + consistencyLevel: 1, + payload: { + token: { amount: 10000n, address: [UniversalAddress], chain: 'Avalanche' }, + to: { address: [UniversalAddress], chain: 'Algorand' }, + fee: 0n + }, + hash: Uint8Array(32) [ + 196, 4, 161, 240, 184, 25, 174, 93, + 138, 251, 8, 180, 118, 43, 74, 43, + 207, 2, 48, 103, 183, 146, 241, 69, + 14, 90, 112, 208, 174, 229, 233, 155 + ] +} +accounts: [ + 'VMPR6NWIIQCZ4F3F7DA5S64ATYDNJRI4JPT3NQHUW37X4HZCN5D6ZQBAKM', + 'Z7AISPXE4YFESMU35JXBEM6JZSKTVGPASEUQXLM7XTEKVUHOYTRVQHVYVQ' +] +txs: [ + { + tx: Transaction { + name: 'Transaction', + tag: , + type: 'appl', + from: [Object], + appIndex: 86525623, + appOnComplete: 0, + appArgs: [Array], + appAccounts: [Array], + note: Uint8Array(0) [], + lease: Uint8Array(0) [], + flatFee: false, + genesisHash: , + fee: 0, + firstRound: 35741742, + lastRound: 35742742, + genesisID: 'testnet-v1.0', + group: undefined + }, + signer: { + addr: 'EZATROXX2HISIRZDRGXW4LRQ46Z6IUJYYIHU3PJGP7P5IQDPKVX42N767A', + signTxn: [Function: signTxn] + } + }, + { + tx: Transaction { + name: 'Transaction', + tag: , + type: 'appl', + from: [Object], + appIndex: 86525623, + appOnComplete: 0, + appArgs: [Array], + appAccounts: [Array], + note: Uint8Array(0) [], + lease: Uint8Array(0) [], + flatFee: false, + genesisHash: , + fee: 3000, + firstRound: 35741742, + lastRound: 35742742, + genesisID: 'testnet-v1.0', + group: undefined + }, + signer: null + } +] +Signing: TokenBridge.redeem with signer EZATROXX2HISIRZDRGXW4LRQ46Z6IUJYYIHU3PJGP7P5IQDPKVX42N767A for address KKPWL6OFVUFOAVQGGURJ2EGNZYZZDPEQ37CHEFLLIAFYTCVLP7UZPSV3ME +Signing: TokenBridge.redeem without signer for address KKPWL6OFVUFOAVQGGURJ2EGNZYZZDPEQ37CHEFLLIAFYTCVLP7UZPSV3ME +Signing: TokenBridge.redeem without signer for address KKPWL6OFVUFOAVQGGURJ2EGNZYZZDPEQ37CHEFLLIAFYTCVLP7UZPSV3ME +Completed Transfer: [ + '3F4EJTW2S53TQ5THXIQWGESIQKW35QTPPKBJHBN44AZ3MKNZO5XQ', + '5DXF3G2HRCMSUFVMCAUKY35F4D77O45UM7VCP43FLLYZJTCCFYLQ', + 'PCY7Q2RPLNJYHTFLZUGKGVBTXBC5F3A2F5OMTJVCV3PWIRLXBXCQ' +] +TokenTransfer { + wh: Wormhole { + _network: 'Testnet', + _platforms: Map(2) { 'Algorand' => [AlgorandPlatform], 'Evm' => [EvmPlatform] }, + _chains: Map(2) { 'Avalanche' => [EvmChain], 'Algorand' => [AlgorandChain] }, + config: { + api: 'https://api.testnet.wormholescan.io', + circleAPI: 'https://iris-api-sandbox.circle.com/v1/attestations', + chains: [Object] + } + }, + _state: 4, + transfer: { + token: 'native', + amount: 100000000000000n, + from: { chain: 'Avalanche', address: [EvmAddress] }, + to: { chain: 'Algorand', address: [AlgorandAddress] }, + automatic: false, + payload: undefined, + nativeGas: undefined + }, + txids: [ + { + chain: 'Avalanche', + txid: '0x97b28abc5906e7d3ff20594b77148fda5d7d52ccc4554e0eba48b50d06d79199' + }, + { + chain: 'Algorand', + txid: '3F4EJTW2S53TQ5THXIQWGESIQKW35QTPPKBJHBN44AZ3MKNZO5XQ' + }, + { + chain: 'Algorand', + txid: '5DXF3G2HRCMSUFVMCAUKY35F4D77O45UM7VCP43FLLYZJTCCFYLQ' + }, + { + chain: 'Algorand', + txid: 'PCY7Q2RPLNJYHTFLZUGKGVBTXBC5F3A2F5OMTJVCV3PWIRLXBXCQ' + } + ], + vaas: [ { id: [Object], vaa: [Object] } ] +} + * Terminal will be reused by tasks, press any key to close it. diff --git a/examples/src/tokenBridge.ts b/examples/src/tokenBridge.ts index 51290d5189..a98ea008fd 100644 --- a/examples/src/tokenBridge.ts +++ b/examples/src/tokenBridge.ts @@ -28,14 +28,14 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge"; const rcvChain = wh.getChain("Solana"); // shortcut to allow transferring native gas token - const token: TokenId<"Avalanche"> | "native" = "native"; + // const token: TokenId<"Avalanche"> | "native" = "native"; // A TokenId is just a `{chain, address}` pair and an alias for ChainAddress // The `address` field must be a parsed address. // You can get a TokenId (or ChainAddress) prepared for you // by calling the static `chainAddress` method on the Wormhole class. // e.g. - // const token = Wormhole.chainAddress("Avalanche", "0xd00ae08403B9bbb9124bB305C09058E32C39A48c"); // TokenId<"Avalanche"> + const token = Wormhole.chainAddress("Avalanche", "0xd00ae08403B9bbb9124bB305C09058E32C39A48c"); // TokenId<"Avalanche"> // Normalized given token decimals later but can just pass bigints as base units // Note: The Token bridge will dedust past 8 decimals diff --git a/package-lock.json b/package-lock.json index bb4de5f57c..eefd1383e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,10 +95,12 @@ "@wormhole-foundation/connect-sdk-solana": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-solana-cctp": "^0.3.0-beta.5", "@wormhole-foundation/connect-sdk-solana-core": "^0.3.0-beta.5", - "@wormhole-foundation/connect-sdk-solana-tokenbridge": "^0.3.0-beta.5" + "@wormhole-foundation/connect-sdk-solana-tokenbridge": "^0.3.0-beta.5", + "algosdk": "^2.7.0" }, "devDependencies": { - "dotenv": "^16.3.1" + "dotenv": "^16.3.1", + "tsx": "^4.7.0" }, "engines": { "node": ">=16" @@ -983,6 +985,374 @@ "resolved": "https://registry.npmjs.org/@ensdomains/eth-ens-namehash/-/eth-ens-namehash-2.0.15.tgz", "integrity": "sha512-JRDFP6+Hczb1E0/HhIg0PONgBYasfGfDheujmfxaZaAv/NAH4jE6Kf48WbqfRZdxt4IZI3jl3Ri7sZ1nP09lgw==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.10.tgz", + "integrity": "sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.10.tgz", + "integrity": "sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.10.tgz", + "integrity": "sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.10.tgz", + "integrity": "sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz", + "integrity": "sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.10.tgz", + "integrity": "sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.10.tgz", + "integrity": "sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.10.tgz", + "integrity": "sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.10.tgz", + "integrity": "sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.10.tgz", + "integrity": "sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.10.tgz", + "integrity": "sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.10.tgz", + "integrity": "sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.10.tgz", + "integrity": "sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.10.tgz", + "integrity": "sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.10.tgz", + "integrity": "sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.10.tgz", + "integrity": "sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.10.tgz", + "integrity": "sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.10.tgz", + "integrity": "sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.10.tgz", + "integrity": "sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.10.tgz", + "integrity": "sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.10.tgz", + "integrity": "sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.10.tgz", + "integrity": "sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.10.tgz", + "integrity": "sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@ethersproject/abi": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", @@ -4522,6 +4892,44 @@ "es6-promise": "^4.0.3" } }, + "node_modules/esbuild": { + "version": "0.19.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.10.tgz", + "integrity": "sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.10", + "@esbuild/android-arm": "0.19.10", + "@esbuild/android-arm64": "0.19.10", + "@esbuild/android-x64": "0.19.10", + "@esbuild/darwin-arm64": "0.19.10", + "@esbuild/darwin-x64": "0.19.10", + "@esbuild/freebsd-arm64": "0.19.10", + "@esbuild/freebsd-x64": "0.19.10", + "@esbuild/linux-arm": "0.19.10", + "@esbuild/linux-arm64": "0.19.10", + "@esbuild/linux-ia32": "0.19.10", + "@esbuild/linux-loong64": "0.19.10", + "@esbuild/linux-mips64el": "0.19.10", + "@esbuild/linux-ppc64": "0.19.10", + "@esbuild/linux-riscv64": "0.19.10", + "@esbuild/linux-s390x": "0.19.10", + "@esbuild/linux-x64": "0.19.10", + "@esbuild/netbsd-x64": "0.19.10", + "@esbuild/openbsd-x64": "0.19.10", + "@esbuild/sunos-x64": "0.19.10", + "@esbuild/win32-arm64": "0.19.10", + "@esbuild/win32-ia32": "0.19.10", + "@esbuild/win32-x64": "0.19.10" + } + }, "node_modules/escalade": { "version": "3.1.1", "license": "MIT", @@ -4919,6 +5327,20 @@ "version": "1.0.0", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4983,6 +5405,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "license": "ISC", @@ -7609,6 +8043,15 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "dev": true, @@ -8493,6 +8936,25 @@ "version": "2.6.2", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.0.tgz", + "integrity": "sha512-I+t79RYPlEYlHn9a+KzwrvEwhJg35h/1zHsLC2JXvhC2mdynMv6Zxzvhv5EMV6VF5qJlLlkSnMVvdZV3PSIGcg==", + "dev": true, + "dependencies": { + "esbuild": "~0.19.10", + "get-tsconfig": "^4.7.2" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tweetnacl": { "version": "1.0.3", "license": "Unlicense" diff --git a/platforms/algorand/protocols/core/src/core.ts b/platforms/algorand/protocols/core/src/core.ts index 934ea93937..2476800dfc 100644 --- a/platforms/algorand/protocols/core/src/core.ts +++ b/platforms/algorand/protocols/core/src/core.ts @@ -10,6 +10,7 @@ import { VAA, WormholeCore, WormholeMessageId, + encoding, toChainId, } from "@wormhole-foundation/connect-sdk"; import { @@ -78,17 +79,10 @@ export class AlgorandWormholeCore throw new Error("Method not implemented."); } - async parseTransaction(txid: string): Promise { - console.log("Txid: ", txid); - const result = await this.connection.pendingTransactionInformation(txid).do(); - console.log("Result: ", result); - - // QUESTIONBW: To make this work, I had to use the tokenBridgeAppId. Expected? + async parseTransaction(txId: string): Promise { + const result = await this.connection.pendingTransactionInformation(txId).do(); const emitterAddr = new UniversalAddress(this.getEmitterAddressAlgorand(this.tokenBridgeAppId)); - console.log("parseTransaction emitterAddr: ", emitterAddr); - const sequence = this.parseSequenceFromLogAlgorand(result); - console.log("sequence: ", sequence); return [ { chain: this.chain, @@ -100,9 +94,8 @@ export class AlgorandWormholeCore private getEmitterAddressAlgorand(appId: bigint): string { const appAddr: string = getApplicationAddress(appId); - const decAppAddr: Uint8Array = decodeAddress(appAddr).publicKey; - const hexAppAddr: string = Buffer.from(decAppAddr).toString("hex"); - console.log("core.ts Emitter address: ", hexAppAddr); + const decodedAppAddr: Uint8Array = decodeAddress(appAddr).publicKey; + const hexAppAddr: string = encoding.hex.encode(decodedAppAddr); return hexAppAddr; } @@ -118,7 +111,7 @@ export class AlgorandWormholeCore }); } if (!sequence) { - throw new Error("Sequence not found"); + throw new Error("parseSequenceFromLogAlgorand - Sequence not found"); } return sequence; } diff --git a/platforms/algorand/protocols/tokenBridge/src/assets.ts b/platforms/algorand/protocols/tokenBridge/src/assets.ts index 90636be21b..f507f33563 100644 --- a/platforms/algorand/protocols/tokenBridge/src/assets.ts +++ b/platforms/algorand/protocols/tokenBridge/src/assets.ts @@ -5,6 +5,7 @@ import { } from "@wormhole-foundation/connect-sdk-algorand"; import { Algodv2, + SuggestedParams, Transaction, bigIntToBytes, getApplicationAddress, @@ -13,9 +14,12 @@ import { modelsv2, signLogicSigTransaction, } from "algosdk"; -import { StorageLsig } from "./storage"; +import { StorageLogicSig } from "./storage"; import { TransactionSet, WormholeWrappedInfo } from "./types"; import { SEED_AMT, decodeLocalState, safeBigIntToNumber } from "./utilities"; +import { varint } from "./bigVarint"; + +const accountExistsCache = new Set<[bigint, string]>(); /** * Returns a boolean if the asset is wrapped @@ -67,27 +71,58 @@ export async function getOriginalAssetOffAlgorand( const assetInfoResp = await client.getAssetByID(safeBigIntToNumber(assetId)).do(); const assetInfo = modelsv2.Asset.from_obj_for_encoding(assetInfoResp); const lsa = assetInfo.params.creator; - const dls = await decodeLocalState(client, tokenBridgeId, lsa); - const dlsBuffer: Buffer = Buffer.from(dls); - retVal.chainId = dlsBuffer.readInt16BE(92) as ChainId; - retVal.assetAddress = new Uint8Array(dlsBuffer.subarray(60, 60 + 32)); + const decodedLocalState = await decodeLocalState(client, tokenBridgeId, lsa); + retVal.chainId = Number(varint.decode(decodedLocalState, 92)) as ChainId; + retVal.assetAddress = new Uint8Array(decodedLocalState.subarray(60, 60 + 32)); return retVal; } /** - * Calculates the logic sig account for the application + * Checks to see if 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, False otherwise + */ +export async function accountExists( + client: Algodv2, + appId: bigint, + acctAddr: string, +): Promise { + if (accountExistsCache.has([appId, acctAddr])) return true; + + let ret = false; + try { + const acctInfoResp = await client.accountInformation(acctAddr).do(); + const acctInfo = modelsv2.Account.from_obj_for_encoding(acctInfoResp); + const als = acctInfo.appsLocalState; + if (!als) { + return ret; + } + als.forEach((app) => { + if (BigInt(app.id) === appId) { + accountExistsCache.add([appId, acctAddr]); + ret = true; + return; + } + }); + } catch (e) {} + return ret; +} + +/** + * Constructs opt in transactions * @param client An Algodv2 client * @param senderAddr Sender address * @param appId Application ID - * @param appIndex Application index - * @param emitterId Emitter address + * @param storage StorageLogicSig * @returns Address and array of TransactionSignerPairs */ export async function maybeOptInTx( client: Algodv2, senderAddr: string, appId: bigint, - storage: StorageLsig, + storage: StorageLogicSig, ): Promise { const appAddr: string = getApplicationAddress(appId); @@ -96,26 +131,24 @@ export async function maybeOptInTx( let exists = false; try { - // TODO: check - await client.accountInformation(storageAddress).do(); - exists = true; + // QUESTIONBW: Is this was you had in mind? + exists = await accountExists(client, appId, storageAddress); } catch {} let txs: TransactionSignerPair[] = []; if (!exists) { - // These are the suggested params from the system - const params = await client.getTransactionParams().do(); + const suggestedParams: SuggestedParams = await client.getTransactionParams().do(); const seedTxn = makePaymentTxnWithSuggestedParamsFromObject({ from: senderAddr, to: storageAddress, amount: SEED_AMT, - suggestedParams: params, + suggestedParams, }); seedTxn.fee = seedTxn.fee * 2; txs.push({ tx: seedTxn, signer: null }); const optinTxn = makeApplicationOptInTxnFromObject({ from: storageAddress, - suggestedParams: params, + suggestedParams, appIndex: safeBigIntToNumber(appId), rekeyTo: appAddr, }); diff --git a/platforms/algorand/protocols/tokenBridge/src/bigVarint.ts b/platforms/algorand/protocols/tokenBridge/src/bigVarint.ts index beac1869c6..7ca4d79013 100644 --- a/platforms/algorand/protocols/tokenBridge/src/bigVarint.ts +++ b/platforms/algorand/protocols/tokenBridge/src/bigVarint.ts @@ -39,6 +39,7 @@ export const varint = { return array; }, + decode: (data: Uint8Array, offset = 0) => { let i = BigInt(0); let n = 0; @@ -54,6 +55,7 @@ export const varint = { } while (0x80 <= b); return i; }, + encodeHex: (i: bigint | number, buffer?: ArrayBuffer, byteOffset?: number) => { return encoding.hex.encode(varint.encode(i, buffer, byteOffset)); }, diff --git a/platforms/algorand/protocols/tokenBridge/src/storage.ts b/platforms/algorand/protocols/tokenBridge/src/storage.ts index edf1e4589b..b20787aa06 100644 --- a/platforms/algorand/protocols/tokenBridge/src/storage.ts +++ b/platforms/algorand/protocols/tokenBridge/src/storage.ts @@ -10,17 +10,13 @@ import { varint } from "./bigVarint"; import { MAX_BITS } from "./utilities"; export interface PopulateData { - // App Id we're storing data for - appId: bigint; - appAddress: Uint8Array; - - // address for the emitter or contract or + appId: bigint; // App ID we're storing data for + appAddress: Uint8Array; // Address for the emitter, contract or Guardian address: Uint8Array; - // specific for the emi idx: bigint; } -export class StorageLsig { +export class StorageLogicSig { // Used only to cache the compiled bytecode constructor(private bytecode: Uint8Array) {} @@ -28,15 +24,14 @@ export class StorageLsig { return new LogicSigAccount(this.bytecode); } - // Get the storage lsig for a wormhole message id - static forMessageId(appId: bigint, whm: WormholeMessageId): StorageLsig { + // Get the storage lsig for a Wormhole message ID + static forMessageId(appId: bigint, whm: WormholeMessageId): StorageLogicSig { const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; - const emitterAddr = whm.emitter.toUniversalAddress().toUint8Array(); const chainIdBytes = encoding.bignum.toBytes(BigInt(toChainId(whm.chain)), 2); const address = encoding.bytes.concat(chainIdBytes, emitterAddr); - return StorageLsig.fromData({ + return StorageLogicSig.fromData({ appId, appAddress, idx: whm.sequence / BigInt(MAX_BITS), @@ -45,9 +40,9 @@ export class StorageLsig { } // Get the storage lsig for a wrapped asset - static forWrappedAsset(appId: bigint, token: TokenId): StorageLsig { + static forWrappedAsset(appId: bigint, token: TokenId): StorageLogicSig { const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; - return StorageLsig.fromData({ + return StorageLogicSig.fromData({ appId, appAddress, idx: BigInt(toChainId(token.chain)), @@ -55,10 +50,10 @@ export class StorageLsig { }); } - // Get the storage lsig for a wrapped asset - static forNativeAsset(appId: bigint, tokenId: bigint): StorageLsig { + // Get the storage lsig for a native asset + static forNativeAsset(appId: bigint, tokenId: bigint): StorageLogicSig { const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; - return StorageLsig.fromData({ + return StorageLogicSig.fromData({ appId, appAddress, idx: tokenId, @@ -67,9 +62,9 @@ export class StorageLsig { } // Get the storage lsig for the guardian set - static forGuardianSet(appId: bigint, idx: bigint | number): StorageLsig { + static forGuardianSet(appId: bigint, idx: bigint | number): StorageLogicSig { const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; - return StorageLsig.fromData({ + return StorageLogicSig.fromData({ appId, appAddress, idx: BigInt(idx), @@ -77,7 +72,7 @@ export class StorageLsig { }); } - static fromData(data: PopulateData): StorageLsig { + static fromData(data: PopulateData): StorageLogicSig { // This patches the binary of the TEAL program used to store data // to produce a logic sig that can be used to sign transactions // to store data in the its account local state for a given app @@ -94,6 +89,6 @@ export class StorageLsig { encoding.hex.encode(data.appAddress), "124431018100124431093203124431153203124422", ]; - return new StorageLsig(encoding.hex.decode(byteStrings.join(""))); + return new StorageLogicSig(encoding.hex.decode(byteStrings.join(""))); } } diff --git a/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts b/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts index 682f43b8f1..9f1bc208cd 100644 --- a/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts +++ b/platforms/algorand/protocols/tokenBridge/src/tokenBridge.ts @@ -52,7 +52,7 @@ import { getOriginalAssetOffAlgorand, maybeOptInTx, } from "./assets"; -import { StorageLsig } from "./storage"; +import { StorageLogicSig } from "./storage"; import { decodeLocalState, safeBigIntToNumber, checkBitsSet, getMessageFee } from "./utilities"; import { submitVAAHeader } from "./vaa"; @@ -135,7 +135,7 @@ export class AlgorandTokenBridge // Returns the address of the native version of this asset async getWrappedAsset(token: TokenId): Promise> { - const storageAccount = StorageLsig.forWrappedAsset(this.tokenBridgeAppId, token); + const storageAccount = StorageLogicSig.forWrappedAsset(this.tokenBridgeAppId, token); const lsa = storageAccount.lsig(); let asset: Uint8Array = await decodeLocalState( @@ -168,7 +168,7 @@ export class AlgorandTokenBridge chain: vaa.emitterChain, emitter: vaa.emitterAddress, }; - const sl = StorageLsig.forMessageId(this.tokenBridgeAppId, whm); + const sl = StorageLogicSig.forMessageId(this.tokenBridgeAppId, whm); try { const isBitSet = await checkBitsSet( this.connection, @@ -193,9 +193,10 @@ export class AlgorandTokenBridge const senderAddr = payer.toString(); const assetId = bytesToBigInt(new AlgorandAddress(token_to_attest.toString()).toUint8Array()); + console.log("assetId3: ", assetId); const txs: TransactionSignerPair[] = []; - const tbs = StorageLsig.fromData({ + const tbs = StorageLogicSig.fromData({ appId: this.coreAppId, appAddress: decodeAddress(this.coreAppAddress).publicKey, idx: BigInt(0), @@ -226,7 +227,7 @@ export class AlgorandTokenBridge } } - const nativeStorageAcct = StorageLsig.forNativeAsset(this.tokenBridgeAppId, assetId); + const nativeStorageAcct = StorageLogicSig.forNativeAsset(this.tokenBridgeAppId, assetId); const txns = await maybeOptInTx( this.connection, senderAddr, @@ -236,14 +237,14 @@ export class AlgorandTokenBridge creatorAddr = txns.address; txs.push(...txns.txs); - const suggParams: SuggestedParams = await this.connection.getTransactionParams().do(); + const suggestedParams: SuggestedParams = await this.connection.getTransactionParams().do(); const firstTxn = makeApplicationCallTxnFromObject({ from: senderAddr, appIndex: safeBigIntToNumber(this.tokenBridgeAppId), onComplete: OnApplicationComplete.NoOpOC, appArgs: [encoding.bytes.encode("nop")], - suggestedParams: suggParams, + suggestedParams, }); txs.push({ tx: firstTxn, signer: null }); @@ -251,7 +252,7 @@ export class AlgorandTokenBridge if (mfee > BigInt(0)) { const feeTxn = makePaymentTxnWithSuggestedParamsFromObject({ from: senderAddr, - suggestedParams: suggParams, + suggestedParams, to: this.tokenBridgeAddress, amount: mfee, }); @@ -272,7 +273,7 @@ export class AlgorandTokenBridge foreignAssets: [safeBigIntToNumber(assetId)], from: senderAddr, onComplete: OnApplicationComplete.NoOpOC, - suggestedParams: suggParams, + suggestedParams, }); if (mfee > BigInt(0)) { appTxn.fee *= 3; @@ -291,14 +292,14 @@ export class AlgorandTokenBridge async *submitAttestation( vaa: TokenBridge.AttestVAA, sender?: AnyAlgorandAddress, - params?: SuggestedParams, + suggestedParams?: SuggestedParams, ): AsyncGenerator> { if (!sender) throw new Error("Payer required to create attestation"); - if (!params) params = await this.connection.getTransactionParams().do(); + if (!suggestedParams) suggestedParams = await this.connection.getTransactionParams().do(); const senderAddr = sender.toString(); - const tokenStorage = StorageLsig.forWrappedAsset(this.tokenBridgeAppId, vaa.payload.token); + const tokenStorage = StorageLogicSig.forWrappedAsset(this.tokenBridgeAppId, vaa.payload.token); const tokenStorageAddress = tokenStorage.lsig().address(); const txs: TransactionSignerPair[] = []; @@ -321,7 +322,7 @@ export class AlgorandTokenBridge from: senderAddr, to: tokenStorageAddress, amount: 100000, - suggestedParams: params, + suggestedParams, }), }); let buf: Uint8Array = new Uint8Array(1); @@ -332,7 +333,7 @@ export class AlgorandTokenBridge appIndex: safeBigIntToNumber(this.tokenBridgeAppId), from: senderAddr, onComplete: OnApplicationComplete.NoOpOC, - suggestedParams: params, + suggestedParams, }), }); @@ -344,7 +345,7 @@ export class AlgorandTokenBridge appIndex: safeBigIntToNumber(this.tokenBridgeAppId), from: senderAddr, onComplete: OnApplicationComplete.NoOpOC, - suggestedParams: params, + suggestedParams, }), }); const receiveAttestSelector = encoding.bytes.encode("receiveAttest"); @@ -356,7 +357,7 @@ export class AlgorandTokenBridge foreignAssets: foreignAssets, from: senderAddr, onComplete: OnApplicationComplete.NoOpOC, - suggestedParams: params, + suggestedParams, }), }); @@ -386,7 +387,7 @@ export class AlgorandTokenBridge const recipientChainId = toChainId(chain); - const tbs = StorageLsig.fromData({ + const tbs = StorageLogicSig.fromData({ appId: this.coreAppId, appAddress: decodeAddress(this.coreAppAddress).publicKey, idx: BigInt(0), @@ -418,26 +419,26 @@ export class AlgorandTokenBridge wormhole = creatorAcct.authAddr === this.tokenBridgeAddress.toString(); } - const params: SuggestedParams = await this.connection.getTransactionParams().do(); + const suggestedParams: SuggestedParams = await this.connection.getTransactionParams().do(); const msgFee: bigint = await getMessageFee(this.connection, this.coreAppId); if (msgFee > 0) txs.push({ tx: makePaymentTxnWithSuggestedParamsFromObject({ from: senderAddr, - suggestedParams: params, to: this.tokenBridgeAddress, amount: msgFee, + suggestedParams, }), signer: null, }); if (!wormhole) { - const storage = StorageLsig.forNativeAsset(this.tokenBridgeAppId, assetId); + const nativeStorageAccount = StorageLogicSig.forNativeAsset(this.tokenBridgeAppId, assetId); const { address, txs } = await maybeOptInTx( this.connection, senderAddr, this.tokenBridgeAppId, - storage, + nativeStorageAccount, ); creator = address; txs.push(...txs); @@ -449,7 +450,7 @@ export class AlgorandTokenBridge from: senderAddr, to: creator, amount: 100000, - suggestedParams: params, + suggestedParams, }); txs.push({ tx: payTxn, signer: null }); // The tokenid app needs to do the optin since it has signature authority @@ -461,7 +462,7 @@ export class AlgorandTokenBridge appArgs: [bOptin, bigIntToBytes(assetId, 8)], foreignAssets: [safeBigIntToNumber(assetId)], accounts: [creator], - suggestedParams: params, + suggestedParams, }); txn.fee *= 2; txs.push({ tx: txn, signer: null }); @@ -472,7 +473,7 @@ export class AlgorandTokenBridge appIndex: safeBigIntToNumber(this.tokenBridgeAppId), onComplete: OnApplicationComplete.NoOpOC, appArgs: [encoding.bytes.encode("nop")], - suggestedParams: params, + suggestedParams, }); txs.push({ tx: t, signer: null }); @@ -482,7 +483,7 @@ export class AlgorandTokenBridge from: senderAddr, to: creator, amount: qty, - suggestedParams: params, + suggestedParams, }); txs.push({ tx: t, signer: null }); accounts = [emitterAddr, creator, creator]; @@ -490,7 +491,7 @@ export class AlgorandTokenBridge const t = makeAssetTransferTxnWithSuggestedParamsFromObject({ from: senderAddr, to: creator, - suggestedParams: params, + suggestedParams, amount: qty, assetIndex: safeBigIntToNumber(assetId), }); @@ -520,7 +521,7 @@ export class AlgorandTokenBridge foreignApps: [safeBigIntToNumber(this.coreAppId)], foreignAssets: [safeBigIntToNumber(assetId)], accounts: accounts, - suggestedParams: params, + suggestedParams, }); acTxn.fee *= 2; txs.push({ tx: acTxn, signer: null }); @@ -534,10 +535,12 @@ export class AlgorandTokenBridge sender: AnyAlgorandAddress, vaa: TokenBridge.TransferVAA, unwrapNative: boolean = true, - params?: SuggestedParams, + suggestedParams?: SuggestedParams, ) { - if (!params) params = await this.connection.getTransactionParams().do(); + if (!suggestedParams) suggestedParams = await this.connection.getTransactionParams().do(); + console.log("vaa: ", vaa); + console.log("vaa payload token: ", vaa.payload.token.address.toString()); const senderAddr = new AlgorandAddress(sender).toString(); let { accounts, txs } = await submitVAAHeader( this.connection, @@ -546,8 +549,10 @@ export class AlgorandTokenBridge vaa, senderAddr, ); + console.log("accounts: ", accounts); + console.log("txs: ", txs); - const tokenStorage = StorageLsig.forWrappedAsset(this.tokenBridgeAppId, vaa.payload.token); + const tokenStorage = StorageLogicSig.forWrappedAsset(this.tokenBridgeAppId, vaa.payload.token); const tokenStorageAddress = tokenStorage.lsig().address(); let foreignAssets: number[] = []; @@ -561,9 +566,11 @@ export class AlgorandTokenBridge if (asset.length > 8) { const tmp = Buffer.from(asset.slice(0, 8)); assetId = safeBigIntToNumber(tmp.readBigUInt64BE(0)); + console.log("assetId1: ", assetId); } } else { assetId = parseInt(vaa.payload.token.address.toString().slice(2), 16); + console.log("assetId2: ", assetId); } accounts.push(tokenStorageAddress); @@ -580,7 +587,7 @@ export class AlgorandTokenBridge foreignAssets.push(assetId); if (!(await assetOptinCheck(this.connection, BigInt(assetId), addr))) { if (senderAddr != addr) { - throw new Error("cannot ASA optin for somebody else (asset " + assetId.toString() + ")"); + throw new Error("Cannot ASA optin for somebody else (asset " + assetId.toString() + ")"); } txs.unshift({ @@ -588,7 +595,7 @@ export class AlgorandTokenBridge amount: 0, assetIndex: assetId, from: senderAddr, - suggestedParams: params, + suggestedParams, to: senderAddr, }), signer: null, @@ -597,16 +604,18 @@ export class AlgorandTokenBridge } accounts.push(addr); + const appCallObj = { + accounts: accounts, + appArgs: [encoding.bytes.encode("completeTransfer"), serialize(vaa)], + appIndex: safeBigIntToNumber(this.tokenBridgeAppId), + foreignAssets: foreignAssets, + from: senderAddr, + onComplete: OnApplicationComplete.NoOpOC, + suggestedParams, + }; + console.log("appCallObj: ", appCallObj); txs.push({ - tx: makeApplicationCallTxnFromObject({ - accounts: accounts, - appArgs: [encoding.bytes.encode("completeTransfer"), serialize(vaa)], - appIndex: safeBigIntToNumber(this.tokenBridgeAppId), - foreignAssets: foreignAssets, - from: senderAddr, - onComplete: OnApplicationComplete.NoOpOC, - suggestedParams: params, - }), + tx: makeApplicationCallTxnFromObject(appCallObj), signer: null, }); @@ -629,7 +638,7 @@ export class AlgorandTokenBridge foreignAssets: foreignAssets, from: senderAddr, onComplete: OnApplicationComplete.NoOpOC, - suggestedParams: params, + suggestedParams, }), signer: null, }); diff --git a/platforms/algorand/protocols/tokenBridge/src/vaa.ts b/platforms/algorand/protocols/tokenBridge/src/vaa.ts index da73350bfc..399f68a382 100644 --- a/platforms/algorand/protocols/tokenBridge/src/vaa.ts +++ b/platforms/algorand/protocols/tokenBridge/src/vaa.ts @@ -10,7 +10,7 @@ import { signLogicSigTransaction, } from "algosdk"; import { maybeOptInTx } from "./assets"; -import { StorageLsig } from "./storage"; +import { StorageLogicSig } from "./storage"; import { ALGO_VERIFY, ALGO_VERIFY_HASH, @@ -28,7 +28,7 @@ type SubmitVAAState = { * 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 vaa The VAA (just the header is used) * @param senderAddr Sending account address * @param appid Application ID * @returns Promise with current VAA state @@ -42,8 +42,8 @@ export async function submitVAAHeader( ): Promise { let txs: TransactionSignerPair[] = []; - // Get storage acct for message id - const msgStorage = StorageLsig.forMessageId(appid, { + // Get storage acct for message ID + const msgStorage = StorageLogicSig.forMessageId(appid, { chain: vaa.emitterChain, sequence: vaa.sequence, emitter: vaa.emitterAddress, @@ -56,8 +56,8 @@ export async function submitVAAHeader( ); txs.push(...seqOptInTxs); - // Get storage account for guardian set - const gsStorage = StorageLsig.forGuardianSet(coreId, vaa.guardianSet); + // Get storage account for Guardian set + const gsStorage = StorageLogicSig.forGuardianSet(coreId, vaa.guardianSet); const { address: guardianAddr, txs: guardianOptInTxs } = await maybeOptInTx( client, senderAddr, @@ -68,10 +68,10 @@ export async function submitVAAHeader( let accts: string[] = [seqAddr, guardianAddr]; - // Get the guardian keys + // Get the Guardian keys const keys: Uint8Array = await decodeLocalState(client, coreId, guardianAddr); - const params: SuggestedParams = await client.getTransactionParams().do(); + const suggestedParams: 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 congested network by reducing the @@ -92,7 +92,7 @@ export async function submitVAAHeader( for (let nt = 0; nt < numTxns; nt++) { let sigs = vaa.signatures.slice(nt, nt + MAX_SIGS_PER_TXN); - // The keyset is the set of guardians that correspond + // 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() let arraySize: number = sigs.length * GuardianKeyLen; @@ -100,7 +100,7 @@ export async function submitVAAHeader( for (let i = 0; i < sigs.length; 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 + // Use that index to get the appropriate Guardian key const sig = sigs[i * SIG_LEN]; const key = keys.slice( sig.guardianIndex * GuardianKeyLen + 1, @@ -124,7 +124,7 @@ export async function submitVAAHeader( appIndex: safeBigIntToNumber(coreId), from: ALGO_VERIFY_HASH, onComplete: OnApplicationComplete.NoOpOC, - suggestedParams: params, + suggestedParams, }); appTxn.fee = 0; txs.push({ @@ -141,7 +141,7 @@ export async function submitVAAHeader( appIndex: safeBigIntToNumber(coreId), from: senderAddr, onComplete: OnApplicationComplete.NoOpOC, - suggestedParams: params, + suggestedParams, }); appTxn.fee = appTxn.fee * (2 + numTxns); // Was 1 txs.push({ tx: appTxn, signer: null }); diff --git a/platforms/algorand/src/address.ts b/platforms/algorand/src/address.ts index 8f104e132b..4e6c5b9810 100644 --- a/platforms/algorand/src/address.ts +++ b/platforms/algorand/src/address.ts @@ -23,7 +23,9 @@ export class AlgorandAddress implements Address { constructor(address: AnyAlgorandAddress) { if (AlgorandAddress.instanceof(address)) { - this.address = address.address; + const a = address as unknown as AlgorandAddress; + this.address = a.address; + return; } else if (UniversalAddress.instanceof(address)) { this.address = encodeAddress(address.toUint8Array()); } else if (typeof address === "string" && isValidAddress(address)) { @@ -33,6 +35,8 @@ export class AlgorandAddress implements Address { } else if (address instanceof Uint8Array && address.byteLength === 8) { // ASA IDs are 8 bytes; this is padded to 32 bytes like addresses this.address = encodeAddress(encoding.bytes.zpad(address, AlgorandAddress.byteSize)); + } else if (typeof address === "bigint") { + this.address = encodeAddress(encoding.bignum.toBytes(address, AlgorandAddress.byteSize)); } else throw new Error(`Invalid Algorand address or ASA ID: ${address}`); } diff --git a/platforms/algorand/src/platform.ts b/platforms/algorand/src/platform.ts index 2e5e3fee47..1c39245667 100644 --- a/platforms/algorand/src/platform.ts +++ b/platforms/algorand/src/platform.ts @@ -70,10 +70,23 @@ export class AlgorandPlatform extends PlatformContext { + if (h.startsWith("0x")) h = h.slice(2); + return new Uint8Array(Buffer.from(h, "hex")); + }; + static anyAlgorandAddressToAsaId(address: AnyAlgorandAddress): number { + /* + QUESTIONBW: Redeeming on Algorand an incoming Avalanche USDC transfer is failing here. + It seems like I need to use the StorageLsig class to get the storage contract and then decode its state, + but this module is in the tokenBridge module, which I shouldn't need to import here in the platform itself + */ + console.log("addressArg", address); const addr = new AlgorandAddress(address.toString()); + console.log("addr", addr); const lastEightBytes = addr.toUint8Array().slice(-8); const asaId = Number(bytesToBigInt(lastEightBytes)); + console.log("asaId", asaId); return asaId; } @@ -145,6 +158,11 @@ export class AlgorandPlatform extends PlatformContext extends PlatformContext