From ecc9d41dcfb228060fd6dad6e7f9309e2de12ef0 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Thu, 10 Aug 2023 18:43:45 -0300 Subject: [PATCH 01/19] create associated account works --- package-lock.json | 249 ++++- package.json | 1 + .../tbtc/solana/WormholeGateway.v2.ts | 136 +++ .../tbtc/solana/WormholeGatewayIdl.ts | 962 ++++++++++++++++++ .../SolanaCreateAssociatedAddress.tsx | 1 + src/hooks/useHandleTransfer.tsx | 4 +- src/utils/consts.ts | 2 + 7 files changed, 1332 insertions(+), 23 deletions(-) create mode 100644 src/assets/providers/tbtc/solana/WormholeGateway.v2.ts create mode 100644 src/assets/providers/tbtc/solana/WormholeGatewayIdl.ts diff --git a/package-lock.json b/package-lock.json index c2cc92708..227afc1af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "@near-wallet-selector/near-wallet": "^7.9.1", "@near-wallet-selector/nightly": "^7.9.1", "@near-wallet-selector/sender": "^7.9.1", + "@project-serum/anchor": "^0.26.0", "@project-serum/serum": "^0.13.60", "@reduxjs/toolkit": "^1.6.1", "@solana/spl-token": "^0.1.6", @@ -7311,33 +7312,70 @@ } }, "node_modules/@project-serum/anchor": { - "version": "0.11.1", - "license": "(MIT OR Apache-2.0)", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@project-serum/anchor/-/anchor-0.26.0.tgz", + "integrity": "sha512-Nq+COIjE1135T7qfnOHEn7E0q39bQTgXLFk837/rgFe6Hkew9WML7eHsS+lSYD2p3OJaTiUOHTAq1lHy36oIqQ==", "dependencies": { - "@project-serum/borsh": "^0.2.2", - "@solana/web3.js": "^1.17.0", + "@coral-xyz/borsh": "^0.26.0", + "@solana/web3.js": "^1.68.0", "base64-js": "^1.5.1", "bn.js": "^5.1.2", "bs58": "^4.0.1", - "buffer-layout": "^1.2.0", - "camelcase": "^5.3.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", "crypto-hash": "^1.3.0", "eventemitter3": "^4.0.7", - "find": "^0.3.0", "js-sha256": "^0.9.0", "pako": "^2.0.3", "snake-case": "^3.0.4", + "superstruct": "^0.15.4", "toml": "^3.0.0" }, "engines": { "node": ">=11" } }, - "node_modules/@project-serum/anchor/node_modules/camelcase": { - "version": "5.3.1", - "license": "MIT", + "node_modules/@project-serum/anchor/node_modules/@coral-xyz/borsh": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.26.0.tgz", + "integrity": "sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ==", + "dependencies": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "peerDependencies": { + "@solana/web3.js": "^1.68.0" + } + }, + "node_modules/@project-serum/anchor/node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/@project-serum/anchor/node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/@project-serum/anchor/node_modules/pako": { @@ -7352,6 +7390,30 @@ "tslib": "^2.0.3" } }, + "node_modules/@project-serum/anchor/node_modules/superstruct": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz", + "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==" + }, + "node_modules/@project-serum/anchor/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/@project-serum/anchor/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/@project-serum/anchor/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/@project-serum/borsh": { "version": "0.2.5", "license": "Apache-2.0", @@ -7380,6 +7442,52 @@ "node": ">=10" } }, + "node_modules/@project-serum/serum/node_modules/@project-serum/anchor": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@project-serum/anchor/-/anchor-0.11.1.tgz", + "integrity": "sha512-oIdm4vTJkUy6GmE6JgqDAuQPKI7XM4TPJkjtoIzp69RZe0iAD9JP2XHx7lV1jLdYXeYHqDXfBt3zcq7W91K6PA==", + "dependencies": { + "@project-serum/borsh": "^0.2.2", + "@solana/web3.js": "^1.17.0", + "base64-js": "^1.5.1", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.0", + "camelcase": "^5.3.1", + "crypto-hash": "^1.3.0", + "eventemitter3": "^4.0.7", + "find": "^0.3.0", + "js-sha256": "^0.9.0", + "pako": "^2.0.3", + "snake-case": "^3.0.4", + "toml": "^3.0.0" + }, + "engines": { + "node": ">=11" + } + }, + "node_modules/@project-serum/serum/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@project-serum/serum/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/@project-serum/serum/node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/@project-serum/sol-wallet-adapter": { "version": "0.2.6", "license": "Apache-2.0", @@ -23905,7 +24013,8 @@ }, "node_modules/find": { "version": "0.3.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/find/-/find-0.3.0.tgz", + "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", "dependencies": { "traverse-chain": "~0.1.0" } @@ -41725,7 +41834,8 @@ }, "node_modules/traverse-chain": { "version": "0.1.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==" }, "node_modules/trim-right": { "version": "1.0.1", @@ -49670,26 +49780,51 @@ } }, "@project-serum/anchor": { - "version": "0.11.1", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@project-serum/anchor/-/anchor-0.26.0.tgz", + "integrity": "sha512-Nq+COIjE1135T7qfnOHEn7E0q39bQTgXLFk837/rgFe6Hkew9WML7eHsS+lSYD2p3OJaTiUOHTAq1lHy36oIqQ==", "requires": { - "@project-serum/borsh": "^0.2.2", - "@solana/web3.js": "^1.17.0", + "@coral-xyz/borsh": "^0.26.0", + "@solana/web3.js": "^1.68.0", "base64-js": "^1.5.1", "bn.js": "^5.1.2", "bs58": "^4.0.1", - "buffer-layout": "^1.2.0", - "camelcase": "^5.3.1", + "buffer-layout": "^1.2.2", + "camelcase": "^6.3.0", + "cross-fetch": "^3.1.5", "crypto-hash": "^1.3.0", "eventemitter3": "^4.0.7", - "find": "^0.3.0", "js-sha256": "^0.9.0", "pako": "^2.0.3", "snake-case": "^3.0.4", + "superstruct": "^0.15.4", "toml": "^3.0.0" }, "dependencies": { - "camelcase": { - "version": "5.3.1" + "@coral-xyz/borsh": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.26.0.tgz", + "integrity": "sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ==", + "requires": { + "bn.js": "^5.1.2", + "buffer-layout": "^1.2.0" + } + }, + "cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "requires": { + "node-fetch": "^2.6.12" + } + }, + "node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "requires": { + "whatwg-url": "^5.0.0" + } }, "pako": { "version": "2.0.4" @@ -49700,6 +49835,30 @@ "dot-case": "^3.0.4", "tslib": "^2.0.3" } + }, + "superstruct": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz", + "integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } } } }, @@ -49718,6 +49877,48 @@ "@solana/web3.js": "^1.21.0", "bn.js": "^5.1.2", "buffer-layout": "^1.2.0" + }, + "dependencies": { + "@project-serum/anchor": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@project-serum/anchor/-/anchor-0.11.1.tgz", + "integrity": "sha512-oIdm4vTJkUy6GmE6JgqDAuQPKI7XM4TPJkjtoIzp69RZe0iAD9JP2XHx7lV1jLdYXeYHqDXfBt3zcq7W91K6PA==", + "requires": { + "@project-serum/borsh": "^0.2.2", + "@solana/web3.js": "^1.17.0", + "base64-js": "^1.5.1", + "bn.js": "^5.1.2", + "bs58": "^4.0.1", + "buffer-layout": "^1.2.0", + "camelcase": "^5.3.1", + "crypto-hash": "^1.3.0", + "eventemitter3": "^4.0.7", + "find": "^0.3.0", + "js-sha256": "^0.9.0", + "pako": "^2.0.3", + "snake-case": "^3.0.4", + "toml": "^3.0.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + } } }, "@project-serum/sol-wallet-adapter": { @@ -62026,6 +62227,8 @@ }, "find": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/find/-/find-0.3.0.tgz", + "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", "requires": { "traverse-chain": "~0.1.0" } @@ -74074,7 +74277,9 @@ } }, "traverse-chain": { - "version": "0.1.0" + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", + "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==" }, "trim-right": { "version": "1.0.1", diff --git a/package.json b/package.json index 115ca3d89..85207c0d5 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@near-wallet-selector/near-wallet": "^7.9.1", "@near-wallet-selector/nightly": "^7.9.1", "@near-wallet-selector/sender": "^7.9.1", + "@project-serum/anchor": "^0.26.0", "@project-serum/serum": "^0.13.60", "@reduxjs/toolkit": "^1.6.1", "@solana/spl-token": "^0.1.6", diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts new file mode 100644 index 000000000..143902a8d --- /dev/null +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -0,0 +1,136 @@ +import { AnchorProvider, Program } from '@project-serum/anchor'; +import { Connection, PublicKey, PublicKeyInitData, SYSVAR_RENT_PUBKEY, Transaction } from '@solana/web3.js'; +import { SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, THRESHOLD_GATEWAYS, THRESHOLD_TBTC_CONTRACTS, getWalletAddressNative } from '../../../../utils/consts'; +import { CHAIN_ID_SOLANA, ChainId, SignedVaa, hexToNativeString, parseTokenTransferVaa, parseTransferPayload } from '@certusone/wormhole-sdk'; +import { WormholeGatewayIdl } from './WormholeGatewayIdl'; +import * as coreBridge from '@certusone/wormhole-sdk/lib/esm/solana/wormhole'; +import * as tokenBridge from '@certusone/wormhole-sdk/lib/esm/solana/tokenBridge'; +import { SolanaWallet } from '@xlabs-libs/wallet-aggregator-solana'; +import { createNonce } from '@certusone/wormhole-sdk/lib/esm/utils/createNonce'; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + Token, + TOKEN_PROGRAM_ID, + } from "@solana/spl-token"; + +const WORMHOLE_GATEWAY_PROGRAM_ID = new PublicKey(THRESHOLD_GATEWAYS[CHAIN_ID_SOLANA]); +const TBTC_PROGRAM_ID = new PublicKey(THRESHOLD_TBTC_CONTRACTS[CHAIN_ID_SOLANA]); +const CORE_BRIDGE_PROGRAM_ID = new PublicKey(CORE_BRIDGE_ADDRESS); +const TOKEN_BRIDGE_PROGRAM_ID = new PublicKey(TOKEN_BRIDGE_ADDRESS); + +function newAnchorProvider(connection: Connection, wallet: SolanaWallet) { + return new AnchorProvider(connection, { + async signTransaction(tx): Promise { + return await wallet.signTransaction(tx); + }, + async signAllTransactions(txs): Promise { + return await wallet.getAdapter().signAllTransactions!(txs); + }, + publicKey: wallet.getAdapter().publicKey! + }, {}); +} + +function getCustodianPDA(): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from("redeemer")], + new PublicKey(WORMHOLE_GATEWAY_PROGRAM_ID) + )[0]; +} + +function getWrappedTbtcTokenPDA(): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from("wrapped-token")], + WORMHOLE_GATEWAY_PROGRAM_ID + )[0]; +} + +function getWrappedTbtcMintPDA(): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from("wrapped-mint")], + WORMHOLE_GATEWAY_PROGRAM_ID + )[0]; +} + +function getMintPDA(): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from("tbtc-mint")], + TBTC_PROGRAM_ID + )[0]; +} + +function getConfigPDA(): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from("config")], + TBTC_PROGRAM_ID + )[0]; +} + +function getMinterInfoPDA(minter: PublicKey): PublicKey { + return PublicKey.findProgramAddressSync( + [Buffer.from("minter-info"), minter.toBuffer()], + TBTC_PROGRAM_ID + )[0]; +} + +export function newThresholdWormholeGateway(connection: Connection, wallet: SolanaWallet) { + const provider = newAnchorProvider(connection, wallet); + const program = new Program(WormholeGatewayIdl, WORMHOLE_GATEWAY_PROGRAM_ID, provider); + + const receiveTbtc = async (signedVAA: SignedVaa, payer: PublicKeyInitData): Promise => { + const parsed = parseTokenTransferVaa(signedVAA); + const recipient = new PublicKey(payer); + const tbtcMint = getMintPDA(); + const recipientToken = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipient + ); + const custodian = getCustodianPDA(); + const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, tbtcMint); + const wrappedTbtcMint = getWrappedTbtcMintPDA() + const recipientWrappedToken = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + wrappedTbtcMint, + recipient + ); + const tx = program.methods + .receiveTbtc(Array.from(parsed.hash)) + .accounts({ + payer: new PublicKey(wallet.getAddress()!), + custodian, + postedVaa: coreBridge.derivePostedVaaKey(CORE_BRIDGE_PROGRAM_ID, parsed.hash), + tokenBridgeClaim: coreBridge.deriveClaimKey(TOKEN_BRIDGE_PROGRAM_ID, parsed.emitterAddress, parsed.emitterChain, parsed.sequence), + wrappedTbtcToken: getWrappedTbtcTokenPDA(), + wrappedTbtcMint, + tbtcMint, + recipientToken, + recipient, + recipientWrappedToken, + tbtcConfig: getConfigPDA(), + tbtcMinterInfo: getMinterInfoPDA(custodian), + tokenBridgeConfig: tokenBridge.deriveTokenBridgeConfigKey(TOKEN_BRIDGE_PROGRAM_ID), + tokenBridgeRegisteredEmitter: tokenBridge.deriveEndpointKey(TOKEN_BRIDGE_PROGRAM_ID, parsed.emitterChain, parsed.emitterAddress), + tokenBridgeWrappedAsset, + tokenBridgeMintAuthority: tokenBridge.deriveMintAuthorityKey(TOKEN_BRIDGE_PROGRAM_ID), + rent: SYSVAR_RENT_PUBKEY, + tbtcProgram: TBTC_PROGRAM_ID, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID + + }) + .transaction(); + return tx; + } + const sendTbtc = async (amount: bigint, recipientChain: ChainId, recipientAddress: Uint8Array): Promise => { + const nonce = createNonce().readUInt32LE(0); + const call = program.methods.sendTbtcGateway(amount, recipientChain, recipientAddress, nonce); + return call.transaction(); + } + return { + sendTbtc, + receiveTbtc + } +} + diff --git a/src/assets/providers/tbtc/solana/WormholeGatewayIdl.ts b/src/assets/providers/tbtc/solana/WormholeGatewayIdl.ts new file mode 100644 index 000000000..b75a00382 --- /dev/null +++ b/src/assets/providers/tbtc/solana/WormholeGatewayIdl.ts @@ -0,0 +1,962 @@ +import { Idl } from "@project-serum/anchor"; + +export const WormholeGatewayIdl: Idl = { + version: "0.1.0", + name: "wormhole_gateway", + instructions: [ + { + name: "initialize", + accounts: [ + { + name: "authority", + isMut: true, + isSigner: true, + }, + { + name: "custodian", + isMut: true, + isSigner: false, + }, + { + name: "tbtcMint", + isMut: false, + isSigner: false, + docs: [ + "TBTC Program's mint PDA address bump is saved in this program's config. Ordinarily, we would", + "not have to deserialize this account. But we do in this case to make sure the TBTC program", + "has been initialized before this program.", + ], + }, + { + name: "wrappedTbtcMint", + isMut: false, + isSigner: false, + }, + { + name: "wrappedTbtcToken", + isMut: true, + isSigner: false, + }, + { + name: "tokenBridgeSender", + isMut: false, + isSigner: false, + docs: [ + "sign for transferring via Token Bridge program with a message.", + ], + }, + { + name: "systemProgram", + isMut: false, + isSigner: false, + }, + { + name: "tokenProgram", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "mintingLimit", + type: "u64", + }, + ], + }, + { + name: "changeAuthority", + accounts: [ + { + name: "custodian", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + { + name: "newAuthority", + isMut: false, + isSigner: false, + }, + ], + args: [], + }, + { + name: "cancelAuthorityChange", + accounts: [ + { + name: "custodian", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [], + }, + { + name: "takeAuthority", + accounts: [ + { + name: "custodian", + isMut: true, + isSigner: false, + }, + { + name: "pendingAuthority", + isMut: false, + isSigner: true, + }, + ], + args: [], + }, + { + name: "updateGatewayAddress", + accounts: [ + { + name: "custodian", + isMut: false, + isSigner: false, + }, + { + name: "gatewayInfo", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: true, + isSigner: true, + }, + { + name: "systemProgram", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "args", + type: { + defined: "UpdateGatewayAddressArgs", + }, + }, + ], + }, + { + name: "updateMintingLimit", + accounts: [ + { + name: "custodian", + isMut: true, + isSigner: false, + }, + { + name: "authority", + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: "newLimit", + type: "u64", + }, + ], + }, + { + name: "receiveTbtc", + accounts: [ + { + name: "payer", + isMut: true, + isSigner: true, + }, + { + name: "custodian", + isMut: true, + isSigner: false, + }, + { + name: "postedVaa", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeClaim", + isMut: true, + isSigner: false, + docs: [ + "transfer. By checking whether this account exists is a short-circuit way of bailing out", + "early if this transfer has already been redeemed (as opposed to letting the Token Bridge", + "instruction fail).", + ], + }, + { + name: "wrappedTbtcToken", + isMut: true, + isSigner: false, + docs: ["Custody account."], + }, + { + name: "wrappedTbtcMint", + isMut: true, + isSigner: false, + docs: [ + "This mint is owned by the Wormhole Token Bridge program. This PDA address is stored in the", + "custodian account.", + ], + }, + { + name: "tbtcMint", + isMut: true, + isSigner: false, + }, + { + name: "recipientToken", + isMut: true, + isSigner: false, + docs: [ + "Token account for minted tBTC.", + "", + "NOTE: Because the recipient is encoded in the transfer message payload, we can check the", + "authority from the deserialized VAA. But we should still check whether the authority is the", + "zero address in access control.", + ], + }, + { + name: "recipient", + isMut: false, + isSigner: false, + docs: ["be created for him."], + }, + { + name: "recipientWrappedToken", + isMut: true, + isSigner: false, + docs: [ + "The gateway will create an associated token account for the recipient if it doesn't exist.", + "", + "NOTE: When the minting limit increases, the recipient can use this token account to mint", + "tBTC using the deposit_wormhole_tbtc instruction.", + ], + }, + { + name: "tbtcConfig", + isMut: false, + isSigner: false, + }, + { + name: "tbtcMinterInfo", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeConfig", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeRegisteredEmitter", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeWrappedAsset", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeMintAuthority", + isMut: false, + isSigner: false, + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + { + name: "tbtcProgram", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeProgram", + isMut: false, + isSigner: false, + }, + { + name: "coreBridgeProgram", + isMut: false, + isSigner: false, + }, + { + name: "associatedTokenProgram", + isMut: false, + isSigner: false, + }, + { + name: "tokenProgram", + isMut: false, + isSigner: false, + }, + { + name: "systemProgram", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "messageHash", + type: { + array: ["u8", 32], + }, + }, + ], + }, + { + name: "sendTbtcGateway", + accounts: [ + { + name: "custodian", + isMut: true, + isSigner: false, + }, + { + name: "gatewayInfo", + isMut: false, + isSigner: false, + }, + { + name: "wrappedTbtcToken", + isMut: true, + isSigner: false, + docs: ["Custody account."], + }, + { + name: "wrappedTbtcMint", + isMut: true, + isSigner: false, + }, + { + name: "tbtcMint", + isMut: true, + isSigner: false, + }, + { + name: "senderToken", + isMut: true, + isSigner: false, + }, + { + name: "sender", + isMut: true, + isSigner: true, + }, + { + name: "tokenBridgeConfig", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeWrappedAsset", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeTransferAuthority", + isMut: false, + isSigner: false, + }, + { + name: "coreBridgeData", + isMut: true, + isSigner: false, + }, + { + name: "coreMessage", + isMut: true, + isSigner: false, + }, + { + name: "tokenBridgeCoreEmitter", + isMut: false, + isSigner: false, + }, + { + name: "coreEmitterSequence", + isMut: true, + isSigner: false, + }, + { + name: "coreFeeCollector", + isMut: true, + isSigner: false, + }, + { + name: "clock", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeSender", + isMut: false, + isSigner: false, + docs: [ + "sign for transferring via Token Bridge program with a message.", + ], + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeProgram", + isMut: false, + isSigner: false, + }, + { + name: "coreBridgeProgram", + isMut: false, + isSigner: false, + }, + { + name: "tokenProgram", + isMut: false, + isSigner: false, + }, + { + name: "systemProgram", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "args", + type: { + defined: "SendTbtcGatewayArgs", + }, + }, + ], + }, + { + name: "sendTbtcWrapped", + accounts: [ + { + name: "custodian", + isMut: true, + isSigner: false, + }, + { + name: "wrappedTbtcToken", + isMut: true, + isSigner: false, + docs: ["Custody account."], + }, + { + name: "wrappedTbtcMint", + isMut: true, + isSigner: false, + }, + { + name: "tbtcMint", + isMut: true, + isSigner: false, + }, + { + name: "senderToken", + isMut: true, + isSigner: false, + }, + { + name: "sender", + isMut: true, + isSigner: true, + }, + { + name: "tokenBridgeConfig", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeWrappedAsset", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeTransferAuthority", + isMut: false, + isSigner: false, + }, + { + name: "coreBridgeData", + isMut: true, + isSigner: false, + }, + { + name: "coreMessage", + isMut: true, + isSigner: false, + }, + { + name: "tokenBridgeCoreEmitter", + isMut: false, + isSigner: false, + }, + { + name: "coreEmitterSequence", + isMut: true, + isSigner: false, + }, + { + name: "coreFeeCollector", + isMut: true, + isSigner: false, + }, + { + name: "clock", + isMut: false, + isSigner: false, + }, + { + name: "rent", + isMut: false, + isSigner: false, + }, + { + name: "tokenBridgeProgram", + isMut: false, + isSigner: false, + }, + { + name: "coreBridgeProgram", + isMut: false, + isSigner: false, + }, + { + name: "tokenProgram", + isMut: false, + isSigner: false, + }, + { + name: "systemProgram", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "args", + type: { + defined: "SendTbtcWrappedArgs", + }, + }, + ], + }, + { + name: "depositWormholeTbtc", + accounts: [ + { + name: "custodian", + isMut: true, + isSigner: false, + docs: [ + "NOTE: This account also acts as a minter for the TBTC program.", + ], + }, + { + name: "wrappedTbtcToken", + isMut: true, + isSigner: false, + docs: [ + "This token account is owned by this program, whose mint is the wrapped TBTC mint. This PDA", + "address is stored in the custodian account.", + ], + }, + { + name: "wrappedTbtcMint", + isMut: false, + isSigner: false, + docs: [ + "This mint is owned by the Wormhole Token Bridge program. This PDA address is stored in the", + "custodian account.", + ], + }, + { + name: "tbtcMint", + isMut: true, + isSigner: false, + docs: [ + "This mint is owned by the TBTC program. This PDA address is stored in the custodian account.", + ], + }, + { + name: "recipientWrappedToken", + isMut: true, + isSigner: false, + }, + { + name: "recipientToken", + isMut: true, + isSigner: false, + }, + { + name: "recipient", + isMut: false, + isSigner: true, + docs: [ + "This program requires that the owner of the TBTC token account sign for TBTC being minted", + "into his account.", + ], + }, + { + name: "tbtcConfig", + isMut: false, + isSigner: false, + }, + { + name: "tbtcMinterInfo", + isMut: false, + isSigner: false, + }, + { + name: "tokenProgram", + isMut: false, + isSigner: false, + }, + { + name: "tbtcProgram", + isMut: false, + isSigner: false, + }, + ], + args: [ + { + name: "amount", + type: "u64", + }, + ], + }, + ], + accounts: [ + { + name: "Custodian", + type: { + kind: "struct", + fields: [ + { + name: "bump", + type: "u8", + }, + { + name: "authority", + type: "publicKey", + }, + { + name: "pendingAuthority", + type: { + option: "publicKey", + }, + }, + { + name: "tbtcMint", + type: "publicKey", + }, + { + name: "wrappedTbtcMint", + type: "publicKey", + }, + { + name: "wrappedTbtcToken", + type: "publicKey", + }, + { + name: "tokenBridgeSender", + type: "publicKey", + }, + { + name: "tokenBridgeSenderBump", + type: "u8", + }, + { + name: "mintingLimit", + type: "u64", + }, + { + name: "mintedAmount", + type: "u64", + }, + ], + }, + }, + { + name: "GatewayInfo", + type: { + kind: "struct", + fields: [ + { + name: "bump", + type: "u8", + }, + { + name: "address", + type: { + array: ["u8", 32], + }, + }, + ], + }, + }, + ], + types: [ + { + name: "UpdateGatewayAddressArgs", + type: { + kind: "struct", + fields: [ + { + name: "chain", + type: "u16", + }, + { + name: "address", + type: { + array: ["u8", 32], + }, + }, + ], + }, + }, + { + name: "SendTbtcGatewayArgs", + type: { + kind: "struct", + fields: [ + { + name: "amount", + type: "u64", + }, + { + name: "recipientChain", + type: "u16", + }, + { + name: "recipient", + type: { + array: ["u8", 32], + }, + }, + { + name: "nonce", + type: "u32", + }, + ], + }, + }, + { + name: "SendTbtcWrappedArgs", + type: { + kind: "struct", + fields: [ + { + name: "amount", + type: "u64", + }, + { + name: "recipientChain", + type: "u16", + }, + { + name: "recipient", + type: { + array: ["u8", 32], + }, + }, + { + name: "arbiterFee", + type: "u64", + }, + { + name: "nonce", + type: "u32", + }, + ], + }, + }, + ], + events: [ + { + name: "WormholeTbtcReceived", + fields: [ + { + name: "receiver", + type: "publicKey", + index: false, + }, + { + name: "amount", + type: "u64", + index: false, + }, + ], + }, + { + name: "WormholeTbtcSent", + fields: [ + { + name: "amount", + type: "u64", + index: false, + }, + { + name: "recipientChain", + type: "u16", + index: false, + }, + { + name: "gateway", + type: { + array: ["u8", 32], + }, + index: false, + }, + { + name: "recipient", + type: { + array: ["u8", 32], + }, + index: false, + }, + { + name: "arbiterFee", + type: "u64", + index: false, + }, + { + name: "nonce", + type: "u32", + index: false, + }, + ], + }, + { + name: "WormholeTbtcDeposited", + fields: [ + { + name: "depositor", + type: "publicKey", + index: false, + }, + { + name: "amount", + type: "u64", + index: false, + }, + ], + }, + { + name: "GatewayAddressUpdated", + fields: [ + { + name: "chain", + type: "u16", + index: false, + }, + { + name: "gateway", + type: { + array: ["u8", 32], + }, + index: false, + }, + ], + }, + { + name: "MintingLimitUpdated", + fields: [ + { + name: "mintingLimit", + type: "u64", + index: false, + }, + ], + }, + ], + errors: [ + { + code: 6016, + name: "MintingLimitExceeded", + msg: "Cannot mint more than the minting limit", + }, + { + code: 6032, + name: "IsNotAuthority", + msg: "Only custodian authority is permitted for this action", + }, + { + code: 6034, + name: "IsNotPendingAuthority", + msg: "Not valid pending authority to take authority", + }, + { + code: 6036, + name: "NoPendingAuthorityChange", + msg: "No pending authority", + }, + { + code: 6048, + name: "ZeroRecipient", + msg: "0x0 recipient not allowed", + }, + { + code: 6064, + name: "NotEnoughWrappedTbtc", + msg: "Not enough wormhole tBTC in the gateway to bridge", + }, + { + code: 6080, + name: "ZeroAmount", + msg: "Amount must not be 0", + }, + { + code: 6112, + name: "TransferAlreadyRedeemed", + msg: "Token Bridge transfer already redeemed", + }, + { + code: 6128, + name: "InvalidEthereumTbtc", + msg: "Token chain and address do not match Ethereum's tBTC", + }, + { + code: 6144, + name: "NoTbtcTransferred", + msg: "No tBTC transferred", + }, + { + code: 6160, + name: "RecipientZeroAddress", + msg: "0x0 receiver not allowed", + }, + { + code: 6176, + name: "MintedAmountUnderflow", + msg: "Not enough minted by the gateway to satisfy sending tBTC", + }, + { + code: 6178, + name: "MintedAmountOverflow", + msg: "Minted amount after deposit exceeds u64", + }, + ], +}; \ No newline at end of file diff --git a/src/components/SolanaCreateAssociatedAddress.tsx b/src/components/SolanaCreateAssociatedAddress.tsx index 9d1676589..1937748c6 100644 --- a/src/components/SolanaCreateAssociatedAddress.tsx +++ b/src/components/SolanaCreateAssociatedAddress.tsx @@ -130,6 +130,7 @@ export default function SolanaCreateAssociatedAddress({ const { blockhash } = await connection.getRecentBlockhash(); transaction.recentBlockhash = blockhash; transaction.feePayer = new PublicKey(payerPublicKey); + console.log(transaction); await signSendAndConfirm(solanaWallet, transaction); setIsCreating(false); setAssociatedAccountExists(true); diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index 8d4458a9f..0e4f54321 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -333,9 +333,11 @@ async function evm( let receipt: ContractReceipt; + debugger; + console.log('sending tBTC to solana') if (isTBTC && THRESHOLD_GATEWAYS[chainId]) { const sourceAddress = THRESHOLD_GATEWAYS[chainId].toLowerCase(); - + console.log('in sending tBTC to solana') const L2WormholeGateway = new Contract( sourceAddress, ThresholdL2WormholeGateway, diff --git a/src/utils/consts.ts b/src/utils/consts.ts index c7607604e..da8a6bffd 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -420,6 +420,7 @@ const THRESHOLD_GATEWAYS_TESTNET: any = { [CHAIN_ID_OPTIMISM]: "0x6449F4381f3d63bDfb36B3bDc375724aD3cD4621", [CHAIN_ID_ARBITRUM]: "0x31A15e213B59E230b45e8c5c99dAFAc3d1236Ee2", [CHAIN_ID_BASE]: "0xe3e0511EEbD87F08FbaE4486419cb5dFB06e1343", + [CHAIN_ID_SOLANA]: "87MEvHZCXE3ML5rrmh5uX1FbShHmRXXS32xJDGbQ7h5t", // Solana TBTC Gateway Program } as const; export const THRESHOLD_GATEWAYS: any = { @@ -442,6 +443,7 @@ const THRESHOLD_TBTC_CONTRACTS_TESTNET: any = { [CHAIN_ID_OPTIMISM]: "0x1a53759DE2eADf73bd0b05c07a4F1F5B7912dA3d", [CHAIN_ID_ARBITRUM]: "0x85727F4725A4B2834e00Db1AA8e1b843a188162F", [CHAIN_ID_BASE]: "0x783349cd20f26CE12e747b1a17bC38D252c9e119", + [CHAIN_ID_SOLANA]: "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU", // Solana TBTC Mint } as const; export const THRESHOLD_TBTC_CONTRACTS: any = { From 317dabc541761f374ab72aba88ddb22533a99f30 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Thu, 10 Aug 2023 19:04:20 -0300 Subject: [PATCH 02/19] fix base58 address convertion for Solana gateway program --- src/hooks/useHandleTransfer.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index 0e4f54321..8c2ce0580 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -36,6 +36,7 @@ import { CHAIN_ID_SUI, CHAIN_ID_ETH, CHAIN_ID_POLYGON, + tryNativeToUint8Array, } from "@certusone/wormhole-sdk"; import { transferTokens } from "@certusone/wormhole-sdk/lib/esm/aptos/api/tokenBridge"; import { CHAIN_ID_NEAR } from "@certusone/wormhole-sdk/lib/esm"; @@ -44,7 +45,7 @@ import { Connection } from "@solana/web3.js"; import algosdk from "algosdk"; import { Types } from "aptos"; import { BigNumber, Contract, ContractReceipt, Signer } from "ethers"; -import { arrayify, parseUnits, zeroPad } from "ethers/lib/utils"; +import { parseUnits, zeroPad } from "ethers/lib/utils"; import { useSnackbar } from "notistack"; import { useCallback, useMemo } from "react"; import { useDispatch, useSelector } from "react-redux"; @@ -333,11 +334,8 @@ async function evm( let receipt: ContractReceipt; - debugger; - console.log('sending tBTC to solana') if (isTBTC && THRESHOLD_GATEWAYS[chainId]) { const sourceAddress = THRESHOLD_GATEWAYS[chainId].toLowerCase(); - console.log('in sending tBTC to solana') const L2WormholeGateway = new Contract( sourceAddress, ThresholdL2WormholeGateway, @@ -901,11 +899,11 @@ export function useHandleTransfer() { THRESHOLD_GATEWAYS[targetChain] && targetAddress ) { - const tbtcGateway = arrayify(THRESHOLD_GATEWAYS[targetChain]); + const tbtcGateway = tryNativeToUint8Array(THRESHOLD_GATEWAYS[targetChain], targetChain); return { - receivingContract: zeroPad(tbtcGateway, 32), - payload: targetAddress, - }; + receivingContract: tbtcGateway, + payload: targetAddress, + }; } return null; }, [isTBTC, originChain, targetAddress, targetChain]); From 20d9ef2c95cf7b831bea42ab8361eea765b58fa5 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Thu, 10 Aug 2023 21:07:23 -0300 Subject: [PATCH 03/19] pull accounts from custodia account --- .../tbtc/solana/WormholeGateway.v2.ts | 84 +++++++---------- src/hooks/useHandleRedeem.tsx | 89 ++++++++++++------- src/hooks/useSyncTargetAddress.ts | 8 ++ src/utils/consts.ts | 3 + 4 files changed, 101 insertions(+), 83 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index 143902a8d..4fad290a3 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -1,7 +1,7 @@ import { AnchorProvider, Program } from '@project-serum/anchor'; import { Connection, PublicKey, PublicKeyInitData, SYSVAR_RENT_PUBKEY, Transaction } from '@solana/web3.js'; -import { SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, THRESHOLD_GATEWAYS, THRESHOLD_TBTC_CONTRACTS, getWalletAddressNative } from '../../../../utils/consts'; -import { CHAIN_ID_SOLANA, ChainId, SignedVaa, hexToNativeString, parseTokenTransferVaa, parseTransferPayload } from '@certusone/wormhole-sdk'; +import { SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, THRESHOLD_GATEWAYS, THRESHOLD_TBTC_CONTRACTS, THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET } from '../../../../utils/consts'; +import { CHAIN_ID_SOLANA, ChainId, SignedVaa, parseTokenTransferVaa, parseTransferPayload } from '@certusone/wormhole-sdk'; import { WormholeGatewayIdl } from './WormholeGatewayIdl'; import * as coreBridge from '@certusone/wormhole-sdk/lib/esm/solana/wormhole'; import * as tokenBridge from '@certusone/wormhole-sdk/lib/esm/solana/tokenBridge'; @@ -14,7 +14,7 @@ import { } from "@solana/spl-token"; const WORMHOLE_GATEWAY_PROGRAM_ID = new PublicKey(THRESHOLD_GATEWAYS[CHAIN_ID_SOLANA]); -const TBTC_PROGRAM_ID = new PublicKey(THRESHOLD_TBTC_CONTRACTS[CHAIN_ID_SOLANA]); +const TBTC_PROGRAM_ID = new PublicKey(THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET); const CORE_BRIDGE_PROGRAM_ID = new PublicKey(CORE_BRIDGE_ADDRESS); const TOKEN_BRIDGE_PROGRAM_ID = new PublicKey(TOKEN_BRIDGE_ADDRESS); @@ -37,27 +37,6 @@ function getCustodianPDA(): PublicKey { )[0]; } -function getWrappedTbtcTokenPDA(): PublicKey { - return PublicKey.findProgramAddressSync( - [Buffer.from("wrapped-token")], - WORMHOLE_GATEWAY_PROGRAM_ID - )[0]; -} - -function getWrappedTbtcMintPDA(): PublicKey { - return PublicKey.findProgramAddressSync( - [Buffer.from("wrapped-mint")], - WORMHOLE_GATEWAY_PROGRAM_ID - )[0]; -} - -function getMintPDA(): PublicKey { - return PublicKey.findProgramAddressSync( - [Buffer.from("tbtc-mint")], - TBTC_PROGRAM_ID - )[0]; -} - function getConfigPDA(): PublicKey { return PublicKey.findProgramAddressSync( [Buffer.from("config")], @@ -78,48 +57,53 @@ export function newThresholdWormholeGateway(connection: Connection, wallet: Sola const receiveTbtc = async (signedVAA: SignedVaa, payer: PublicKeyInitData): Promise => { const parsed = parseTokenTransferVaa(signedVAA); + const payload = parseTransferPayload(parsed.payload); const recipient = new PublicKey(payer); - const tbtcMint = getMintPDA(); + const custodian = getCustodianPDA(); + const custodianData = await program.account.custodian.fetch(custodian); + const tbtcMint = new PublicKey(custodianData.tbtcMint as string); + const wrappedTbtcToken = new PublicKey(custodianData.wrappedTbtcToken as string); + const wrappedTbtcMint = new PublicKey(custodianData.wrappedTbtcMint as string); const recipientToken = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, tbtcMint, recipient ); - const custodian = getCustodianPDA(); const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, tbtcMint); - const wrappedTbtcMint = getWrappedTbtcMintPDA() const recipientWrappedToken = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, wrappedTbtcMint, recipient ); + const accounts = { + payer: new PublicKey(wallet.getAddress()!), + custodian, + postedVaa: coreBridge.derivePostedVaaKey(CORE_BRIDGE_PROGRAM_ID, parsed.hash), + tokenBridgeClaim: coreBridge.deriveClaimKey(TOKEN_BRIDGE_PROGRAM_ID, parsed.emitterAddress, parsed.emitterChain, parsed.sequence), + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + recipientToken, + recipient, + recipientWrappedToken, + tbtcConfig: getConfigPDA(), + tbtcMinterInfo: getMinterInfoPDA(custodian), + tokenBridgeConfig: tokenBridge.deriveTokenBridgeConfigKey(TOKEN_BRIDGE_PROGRAM_ID), + tokenBridgeRegisteredEmitter: tokenBridge.deriveEndpointKey(TOKEN_BRIDGE_PROGRAM_ID, parsed.emitterChain, parsed.emitterAddress), + tokenBridgeWrappedAsset, + tokenBridgeMintAuthority: tokenBridge.deriveMintAuthorityKey(TOKEN_BRIDGE_PROGRAM_ID), + rent: SYSVAR_RENT_PUBKEY, + tbtcProgram: TBTC_PROGRAM_ID, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID + + } + console.debug(Object.entries(accounts).map(([key, value]) => `${key}: ${value}`)); const tx = program.methods .receiveTbtc(Array.from(parsed.hash)) - .accounts({ - payer: new PublicKey(wallet.getAddress()!), - custodian, - postedVaa: coreBridge.derivePostedVaaKey(CORE_BRIDGE_PROGRAM_ID, parsed.hash), - tokenBridgeClaim: coreBridge.deriveClaimKey(TOKEN_BRIDGE_PROGRAM_ID, parsed.emitterAddress, parsed.emitterChain, parsed.sequence), - wrappedTbtcToken: getWrappedTbtcTokenPDA(), - wrappedTbtcMint, - tbtcMint, - recipientToken, - recipient, - recipientWrappedToken, - tbtcConfig: getConfigPDA(), - tbtcMinterInfo: getMinterInfoPDA(custodian), - tokenBridgeConfig: tokenBridge.deriveTokenBridgeConfigKey(TOKEN_BRIDGE_PROGRAM_ID), - tokenBridgeRegisteredEmitter: tokenBridge.deriveEndpointKey(TOKEN_BRIDGE_PROGRAM_ID, parsed.emitterChain, parsed.emitterAddress), - tokenBridgeWrappedAsset, - tokenBridgeMintAuthority: tokenBridge.deriveMintAuthorityKey(TOKEN_BRIDGE_PROGRAM_ID), - rent: SYSVAR_RENT_PUBKEY, - tbtcProgram: TBTC_PROGRAM_ID, - tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, - coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID - - }) + .accounts(accounts) .transaction(); return tx; } diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index 5e814b604..65fa05002 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -89,6 +89,7 @@ import { getSuiProvider } from "../utils/sui"; import { useSuiWallet } from "../contexts/SuiWalletContext"; import { redeemOnSui } from "../utils/suiRedeemHotfix"; import { ThresholdL2WormholeGateway } from "../utils/ThresholdL2WormholeGateway"; +import { newThresholdWormholeGateway } from "../assets/providers/tbtc/solana/WormholeGateway.v2"; async function algo( dispatch: any, @@ -339,7 +340,8 @@ async function solana( wallet: SolanaWallet, payerAddress: string, //TODO: we may not need this since we have wallet signedVAA: Uint8Array, - isNative: boolean + isNative: boolean, + isTbtc: boolean ) { dispatch(setIsRedeeming(true)); try { @@ -347,37 +349,58 @@ async function solana( throw new Error("wallet.signTransaction is undefined"); } const connection = new Connection(SOLANA_HOST, "confirmed"); - await postVaaSolanaWithRetry( - connection, - wallet.signTransaction.bind(wallet), - SOL_BRIDGE_ADDRESS, - payerAddress, - Buffer.from(signedVAA), - MAX_VAA_UPLOAD_RETRIES_SOLANA - ); - // TODO: how do we retry in between these steps - const transaction = isNative - ? await redeemAndUnwrapOnSolana( - connection, - SOL_BRIDGE_ADDRESS, - SOL_TOKEN_BRIDGE_ADDRESS, - payerAddress, - signedVAA - ) - : await redeemOnSolana( - connection, - SOL_BRIDGE_ADDRESS, - SOL_TOKEN_BRIDGE_ADDRESS, - payerAddress, - signedVAA - ); - const txid = await signSendAndConfirm(wallet, transaction); - // TODO: didn't want to make an info call we didn't need, can we get the block without it by modifying the above call? - dispatch(setRedeemTx({ id: txid, block: 1 })); - enqueueSnackbar(null, { - content: Transaction confirmed, - }); + if (isTbtc) { + await postVaaSolanaWithRetry( + connection, + wallet.signTransaction.bind(wallet), + SOL_BRIDGE_ADDRESS, + payerAddress, + Buffer.from(signedVAA), + MAX_VAA_UPLOAD_RETRIES_SOLANA + ); + const tbtcGateway = newThresholdWormholeGateway(connection, wallet); + // TODO: how do we retry in between these steps + const transaction = await tbtcGateway.receiveTbtc(signedVAA, payerAddress); + const txid = await signSendAndConfirm(wallet, transaction); + // TODO: didn't want to make an info call we didn't need, can we get the block without it by modifying the above call? + dispatch(setRedeemTx({ id: txid, block: 1 })); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + } else { + await postVaaSolanaWithRetry( + connection, + wallet.signTransaction.bind(wallet), + SOL_BRIDGE_ADDRESS, + payerAddress, + Buffer.from(signedVAA), + MAX_VAA_UPLOAD_RETRIES_SOLANA + ); + // TODO: how do we retry in between these steps + const transaction = isNative + ? await redeemAndUnwrapOnSolana( + connection, + SOL_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS, + payerAddress, + signedVAA + ) + : await redeemOnSolana( + connection, + SOL_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS, + payerAddress, + signedVAA + ); + const txid = await signSendAndConfirm(wallet, transaction); + // TODO: didn't want to make an info call we didn't need, can we get the block without it by modifying the above call? + dispatch(setRedeemTx({ id: txid, block: 1 })); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + } } catch (e) { + console.log(e); enqueueSnackbar(null, { content: {parseError(e)}, }); @@ -496,7 +519,7 @@ export function useHandleRedeem() { !!solPK && signedVAA ) { - solana(dispatch, enqueueSnackbar, solanaWallet, solPK, signedVAA, false); + solana(dispatch, enqueueSnackbar, solanaWallet, solPK, signedVAA, false, isTBTC); } else if (isTerraChain(targetChain) && !!terraWallet && signedVAA) { terra( dispatch, @@ -569,7 +592,7 @@ export function useHandleRedeem() { !!solPK && signedVAA ) { - solana(dispatch, enqueueSnackbar, solanaWallet, solPK, signedVAA, true); + solana(dispatch, enqueueSnackbar, solanaWallet, solPK, signedVAA, true, isTBTC); } else if (isTerraChain(targetChain) && !!terraWallet && signedVAA) { terra( dispatch, diff --git a/src/hooks/useSyncTargetAddress.ts b/src/hooks/useSyncTargetAddress.ts index 8261208f2..19ee631ea 100644 --- a/src/hooks/useSyncTargetAddress.ts +++ b/src/hooks/useSyncTargetAddress.ts @@ -27,6 +27,7 @@ import { setTargetAddressHex as setNFTTargetAddressHex } from "../store/nftSlice import { selectNFTTargetAsset, selectNFTTargetChain, + selectTransferIsTBTC, selectTransferTargetAsset, selectTransferTargetChain, selectTransferTargetParsedTokenAccount, @@ -64,6 +65,7 @@ function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) { const { account: aptosAddress } = useAptosContext(); const { accountId: nearAccountId, wallet } = useNearContext(); const { address: injAddress } = useInjectiveContext(); + const isTBTC = useSelector(selectTransferIsTBTC); const suiWallet = useSuiWallet(); const suiAddress = suiWallet?.getAddress(); const setTargetAddressHex = nft @@ -80,6 +82,11 @@ function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) { ); } // TODO: have the user explicitly select an account on solana + else if (!nft && targetChain === CHAIN_ID_SOLANA && isTBTC) { + dispatch( + setTargetAddressHex(solPK) + ); + } else if ( !nft && // only support existing, non-derived token accounts for token transfers (nft flow doesn't check balance) targetChain === CHAIN_ID_SOLANA && @@ -233,6 +240,7 @@ function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) { aptosAddress, injAddress, suiAddress, + isTBTC ]); } diff --git a/src/utils/consts.ts b/src/utils/consts.ts index da8a6bffd..7971ecb9d 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -446,6 +446,9 @@ const THRESHOLD_TBTC_CONTRACTS_TESTNET: any = { [CHAIN_ID_SOLANA]: "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU", // Solana TBTC Mint } as const; +export const THRESHOLD_TBTC_SOLANA_MINT_TESTNET = "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU"; +export const THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET = "Gj93RRt6QB7FjmyokAD5rcMAku7pq3Fk2Aa8y6nNbwsV"; + export const THRESHOLD_TBTC_CONTRACTS: any = { ...(CLUSTER === "mainnet" ? THRESHOLD_TBTC_CONTRACTS_MAINNET From 0c0cba00f5e49422000ac457414a7c96df1751c3 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 00:24:50 -0300 Subject: [PATCH 04/19] implement send< btct --- .../tbtc/solana/WormholeGateway.v2.ts | 97 +++++++++++-- src/hooks/useHandleRedeem.tsx | 2 +- src/hooks/useHandleTransfer.tsx | 132 +++++++++++------- 3 files changed, 171 insertions(+), 60 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index 4fad290a3..7f3b17779 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -1,12 +1,11 @@ -import { AnchorProvider, Program } from '@project-serum/anchor'; -import { Connection, PublicKey, PublicKeyInitData, SYSVAR_RENT_PUBKEY, Transaction } from '@solana/web3.js'; -import { SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, THRESHOLD_GATEWAYS, THRESHOLD_TBTC_CONTRACTS, THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET } from '../../../../utils/consts'; -import { CHAIN_ID_SOLANA, ChainId, SignedVaa, parseTokenTransferVaa, parseTransferPayload } from '@certusone/wormhole-sdk'; +import { AnchorProvider, BN, Program } from '@project-serum/anchor'; +import { Connection, PublicKey, PublicKeyInitData, SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY, Transaction } from '@solana/web3.js'; +import { SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, THRESHOLD_GATEWAYS, THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET } from '../../../../utils/consts'; +import { CHAIN_ID_SOLANA, ChainId, SignedVaa, parseTokenTransferVaa } from '@certusone/wormhole-sdk'; import { WormholeGatewayIdl } from './WormholeGatewayIdl'; import * as coreBridge from '@certusone/wormhole-sdk/lib/esm/solana/wormhole'; import * as tokenBridge from '@certusone/wormhole-sdk/lib/esm/solana/tokenBridge'; import { SolanaWallet } from '@xlabs-libs/wallet-aggregator-solana'; -import { createNonce } from '@certusone/wormhole-sdk/lib/esm/utils/createNonce'; import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, @@ -51,13 +50,39 @@ function getMinterInfoPDA(minter: PublicKey): PublicKey { )[0]; } +function getTokenBridgeCoreEmitter() { + const [tokenBridgeCoreEmitter] = PublicKey.findProgramAddressSync( + [Buffer.from("emitter")], + TOKEN_BRIDGE_PROGRAM_ID + ); + return tokenBridgeCoreEmitter; +} + +async function getTokenBridgeSequence(connection: Connection) { + const emitter = getTokenBridgeCoreEmitter(); + return coreBridge + .getSequenceTracker(connection, + emitter, + CORE_BRIDGE_PROGRAM_ID + ) + .then((tracker) => tracker.sequence); +} + +function getCoreMessagePDA(sequence: bigint): PublicKey { + const encodedSequence = Buffer.alloc(8); + encodedSequence.writeBigUInt64LE(sequence); + return PublicKey.findProgramAddressSync( + [Buffer.from("msg"), encodedSequence], + WORMHOLE_GATEWAY_PROGRAM_ID + )[0]; +} + export function newThresholdWormholeGateway(connection: Connection, wallet: SolanaWallet) { const provider = newAnchorProvider(connection, wallet); const program = new Program(WormholeGatewayIdl, WORMHOLE_GATEWAY_PROGRAM_ID, provider); const receiveTbtc = async (signedVAA: SignedVaa, payer: PublicKeyInitData): Promise => { const parsed = parseTokenTransferVaa(signedVAA); - const payload = parseTransferPayload(parsed.payload); const recipient = new PublicKey(payer); const custodian = getCustodianPDA(); const custodianData = await program.account.custodian.fetch(custodian); @@ -107,10 +132,62 @@ export function newThresholdWormholeGateway(connection: Connection, wallet: Sola .transaction(); return tx; } - const sendTbtc = async (amount: bigint, recipientChain: ChainId, recipientAddress: Uint8Array): Promise => { - const nonce = createNonce().readUInt32LE(0); - const call = program.methods.sendTbtcGateway(amount, recipientChain, recipientAddress, nonce); - return call.transaction(); + + const sendTbtc = async ( + amount: bigint, + recipientChain: ChainId, + recipientAddress: Uint8Array, + sender: Uint8Array, + senderToken: Uint8Array + ): Promise => { + const custodian = getCustodianPDA(); + const custodianData = await program.account.custodian.fetch(custodian); + const tbtcMint = new PublicKey(custodianData.tbtcMint as string); + const wrappedTbtcToken = new PublicKey(custodianData.wrappedTbtcToken as string); + const wrappedTbtcMint = new PublicKey(custodianData.wrappedTbtcMint as string); + const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, tbtcMint); + const tokenBridgeConfig = tokenBridge.deriveTokenBridgeConfigKey(TOKEN_BRIDGE_PROGRAM_ID); + const tokenBridgeTransferAuthority = tokenBridge.deriveAuthoritySignerKey(TOKEN_BRIDGE_PROGRAM_ID); + const coreFeeCollector = coreBridge.deriveFeeCollectorKey(CORE_BRIDGE_PROGRAM_ID); + const sequence = await getTokenBridgeSequence(connection); + const coreMessage = getCoreMessagePDA(sequence); + const coreBridgeData = coreBridge.deriveWormholeBridgeDataKey(CORE_BRIDGE_PROGRAM_ID); + const tokenBridgeCoreEmitter = getTokenBridgeCoreEmitter(); + const coreEmitterSequence = coreBridge.deriveEmitterSequenceKey( + tokenBridgeCoreEmitter, + CORE_BRIDGE_PROGRAM_ID + ); + const accounts = { + custodian, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + //senderToken, associated token account + //sender, associated token account owner + tokenBridgeConfig, + tokenBridgeWrappedAsset, + tokenBridgeTransferAuthority, + coreBridgeData, + coreMessage, + tokenBridgeCoreEmitter, + coreEmitterSequence, + coreFeeCollector, + clock: SYSVAR_CLOCK_PUBKEY, + rent: SYSVAR_RENT_PUBKEY, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, + } + const args = { + amount: new BN(amount.toString()), + recipientChain, + recipientAddress, + nonce: 0, + } + const tx = program.methods + .sendTbtcGateway(args) + .accounts(accounts) + .transaction(); + return tx; } return { sendTbtc, diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index 65fa05002..1ea1e2a49 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -489,7 +489,6 @@ export function useHandleRedeem() { const { enqueueSnackbar } = useSnackbar(); const targetChain = useSelector(selectTransferTargetChain); const isTBTC = useSelector(selectTransferIsTBTC); - const { publicKey: solPK, wallet: solanaWallet } = useSolanaWallet(); const { signer } = useEthereumProvider(targetChain); const { wallet: terraWallet } = useTerraWallet(targetChain); @@ -637,6 +636,7 @@ export function useHandleRedeem() { injWallet, injAddress, suiWallet, + isTBTC, ]); const handleAcalaRelayerRedeemClick = useCallback(async () => { diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index 8c2ce0580..786b469cb 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -130,6 +130,7 @@ import { } from "@certusone/wormhole-sdk/lib/cjs/sui"; import { useSuiWallet } from "../contexts/SuiWalletContext"; import { ThresholdL2WormholeGateway } from "../utils/ThresholdL2WormholeGateway"; +import { newThresholdWormholeGateway } from "../assets/providers/tbtc/solana/WormholeGateway.v2"; type AdditionalPayloadOverride = { receivingContract: Uint8Array; @@ -586,7 +587,8 @@ async function solana( maybeAdditionalPayload: MaybeAdditionalPayloadFn, originAddressStr?: string, originChain?: ChainId, - relayerFee?: string + relayerFee?: string, + isTBTC: boolean = false ) { dispatch(setIsSending(true)); try { @@ -598,55 +600,87 @@ async function solana( const originAddress = originAddressStr ? zeroPad(hexToUint8Array(originAddressStr), 32) : undefined; - const promise = isNative - ? transferNativeSol( - connection, - SOL_BRIDGE_ADDRESS, - SOL_TOKEN_BRIDGE_ADDRESS, - payerAddress, - transferAmountParsed.toBigInt(), - additionalPayload?.receivingContract || targetAddress, - targetChain, - feeParsed.toBigInt(), - additionalPayload?.payload - ) - : transferFromSolana( - connection, - SOL_BRIDGE_ADDRESS, - SOL_TOKEN_BRIDGE_ADDRESS, - payerAddress, - fromAddress, - mintAddress, - transferAmountParsed.toBigInt(), - additionalPayload?.receivingContract || targetAddress, - targetChain, - originAddress, - originChain, - undefined, - feeParsed.toBigInt(), - additionalPayload?.payload - ); - const transaction = await promise; - const txid = await signSendAndConfirm(wallet, transaction); - enqueueSnackbar(null, { - content: Transaction confirmed, - }); - const info = await connection.getTransaction(txid); - if (!info) { - throw new Error("An error occurred while fetching the transaction info"); + + if (isTBTC) { + const wormholeGateway = newThresholdWormholeGateway(connection, wallet); + const transaction = await wormholeGateway.sendTbtc( + transferAmountParsed.toBigInt(), + targetChain, + new Uint8Array(0), + new Uint8Array(0), + new Uint8Array(0) + ) + const txid = await signSendAndConfirm(wallet, transaction); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + const info = await connection.getTransaction(txid); + if (!info) { + throw new Error("An error occurred while fetching the transaction info"); + } + dispatch(setTransferTx({ id: txid, block: info.slot })); + const sequence = parseSequenceFromLogSolana(info); + const emitterAddress = await getEmitterAddressSolana( + SOL_TOKEN_BRIDGE_ADDRESS + ); + await fetchSignedVAA( + CHAIN_ID_SOLANA, + emitterAddress, + sequence, + enqueueSnackbar, + dispatch + ); + } else { + const promise = isNative + ? transferNativeSol( + connection, + SOL_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS, + payerAddress, + transferAmountParsed.toBigInt(), + additionalPayload?.receivingContract || targetAddress, + targetChain, + feeParsed.toBigInt(), + additionalPayload?.payload + ) + : transferFromSolana( + connection, + SOL_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS, + payerAddress, + fromAddress, + mintAddress, + transferAmountParsed.toBigInt(), + additionalPayload?.receivingContract || targetAddress, + targetChain, + originAddress, + originChain, + undefined, + feeParsed.toBigInt(), + additionalPayload?.payload + ); + const transaction = await promise; + const txid = await signSendAndConfirm(wallet, transaction); + enqueueSnackbar(null, { + content: Transaction confirmed, + }); + const info = await connection.getTransaction(txid); + if (!info) { + throw new Error("An error occurred while fetching the transaction info"); + } + dispatch(setTransferTx({ id: txid, block: info.slot })); + const sequence = parseSequenceFromLogSolana(info); + const emitterAddress = await getEmitterAddressSolana( + SOL_TOKEN_BRIDGE_ADDRESS + ); + await fetchSignedVAA( + CHAIN_ID_SOLANA, + emitterAddress, + sequence, + enqueueSnackbar, + dispatch + ); } - dispatch(setTransferTx({ id: txid, block: info.slot })); - const sequence = parseSequenceFromLogSolana(info); - const emitterAddress = await getEmitterAddressSolana( - SOL_TOKEN_BRIDGE_ADDRESS - ); - await fetchSignedVAA( - CHAIN_ID_SOLANA, - emitterAddress, - sequence, - enqueueSnackbar, - dispatch - ); } catch (e) { handleError(e, enqueueSnackbar, dispatch); } From 9fc9cf04b830fe22878b56460407de1ffb9055c6 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 11:03:16 -0300 Subject: [PATCH 05/19] update tokenBridgeWrappedAsset account derivation to send tbtc to solana --- .../providers/tbtc/solana/WormholeGateway.v2.ts | 11 +++++------ src/hooks/useHandleRedeem.tsx | 3 ++- src/hooks/useHandleTransfer.tsx | 2 +- src/hooks/useSyncTargetAddress.ts | 8 ++++++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index 7f3b17779..f9dfd2745 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -95,7 +95,7 @@ export function newThresholdWormholeGateway(connection: Connection, wallet: Sola tbtcMint, recipient ); - const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, tbtcMint); + const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, wrappedTbtcMint); const recipientWrappedToken = await Token.getAssociatedTokenAddress( ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, @@ -124,10 +124,9 @@ export function newThresholdWormholeGateway(connection: Connection, wallet: Sola tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID - } - console.debug(Object.entries(accounts).map(([key, value]) => `${key}: ${value}`)); + }; const tx = program.methods - .receiveTbtc(Array.from(parsed.hash)) + .receiveTbtc(parsed.hash) .accounts(accounts) .transaction(); return tx; @@ -145,7 +144,7 @@ export function newThresholdWormholeGateway(connection: Connection, wallet: Sola const tbtcMint = new PublicKey(custodianData.tbtcMint as string); const wrappedTbtcToken = new PublicKey(custodianData.wrappedTbtcToken as string); const wrappedTbtcMint = new PublicKey(custodianData.wrappedTbtcMint as string); - const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, tbtcMint); + const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, wrappedTbtcMint); const tokenBridgeConfig = tokenBridge.deriveTokenBridgeConfigKey(TOKEN_BRIDGE_PROGRAM_ID); const tokenBridgeTransferAuthority = tokenBridge.deriveAuthoritySignerKey(TOKEN_BRIDGE_PROGRAM_ID); const coreFeeCollector = coreBridge.deriveFeeCollectorKey(CORE_BRIDGE_PROGRAM_ID); @@ -163,7 +162,7 @@ export function newThresholdWormholeGateway(connection: Connection, wallet: Sola wrappedTbtcMint, tbtcMint, //senderToken, associated token account - //sender, associated token account owner + //sender, associated token account owner, tokenBridgeConfig, tokenBridgeWrappedAsset, tokenBridgeTransferAuthority, diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index 1ea1e2a49..e4434c60e 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -350,7 +350,7 @@ async function solana( } const connection = new Connection(SOLANA_HOST, "confirmed"); if (isTbtc) { - await postVaaSolanaWithRetry( + const txidVaaPosted = await postVaaSolanaWithRetry( connection, wallet.signTransaction.bind(wallet), SOL_BRIDGE_ADDRESS, @@ -358,6 +358,7 @@ async function solana( Buffer.from(signedVAA), MAX_VAA_UPLOAD_RETRIES_SOLANA ); + console.log("txidVaaPosted", txidVaaPosted.map(tx => tx.signature)); const tbtcGateway = newThresholdWormholeGateway(connection, wallet); // TODO: how do we retry in between these steps const transaction = await tbtcGateway.receiveTbtc(signedVAA, payerAddress); diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index 786b469cb..c5806d042 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -385,7 +385,6 @@ async function evm( : {}; const additionalPayload = maybeAdditionalPayload(); - receipt = isNative ? await transferFromEthNative( getTokenBridgeAddressForChain(chainId), @@ -934,6 +933,7 @@ export function useHandleTransfer() { targetAddress ) { const tbtcGateway = tryNativeToUint8Array(THRESHOLD_GATEWAYS[targetChain], targetChain); + console.log(uint8ArrayToHex(targetAddress), uint8ArrayToHex(tbtcGateway)); return { receivingContract: tbtcGateway, payload: targetAddress, diff --git a/src/hooks/useSyncTargetAddress.ts b/src/hooks/useSyncTargetAddress.ts index 19ee631ea..49ba6c7e8 100644 --- a/src/hooks/useSyncTargetAddress.ts +++ b/src/hooks/useSyncTargetAddress.ts @@ -82,9 +82,13 @@ function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) { ); } // TODO: have the user explicitly select an account on solana - else if (!nft && targetChain === CHAIN_ID_SOLANA && isTBTC) { + else if (!nft && solPK && targetChain === CHAIN_ID_SOLANA && isTBTC) { dispatch( - setTargetAddressHex(solPK) + setTargetAddressHex( + uint8ArrayToHex( + zeroPad(new PublicKey(solPK).toBytes(), 32) + ) + ) ); } else if ( From e83a53cf419ccea85a77379f7dc22057c730a3ca Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 11:20:17 -0300 Subject: [PATCH 06/19] setup sender arguments --- src/assets/providers/tbtc/solana/WormholeGateway.v2.ts | 10 +++++----- src/hooks/useHandleTransfer.tsx | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index f9dfd2745..d0480d857 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -1,7 +1,7 @@ import { AnchorProvider, BN, Program } from '@project-serum/anchor'; import { Connection, PublicKey, PublicKeyInitData, SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY, Transaction } from '@solana/web3.js'; import { SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, THRESHOLD_GATEWAYS, THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET } from '../../../../utils/consts'; -import { CHAIN_ID_SOLANA, ChainId, SignedVaa, parseTokenTransferVaa } from '@certusone/wormhole-sdk'; +import { CHAIN_ID_SOLANA, ChainId, SignedVaa, hexToUint8Array, parseTokenTransferVaa } from '@certusone/wormhole-sdk'; import { WormholeGatewayIdl } from './WormholeGatewayIdl'; import * as coreBridge from '@certusone/wormhole-sdk/lib/esm/solana/wormhole'; import * as tokenBridge from '@certusone/wormhole-sdk/lib/esm/solana/tokenBridge'; @@ -136,8 +136,8 @@ export function newThresholdWormholeGateway(connection: Connection, wallet: Sola amount: bigint, recipientChain: ChainId, recipientAddress: Uint8Array, - sender: Uint8Array, - senderToken: Uint8Array + sender: string, + senderToken: string ): Promise => { const custodian = getCustodianPDA(); const custodianData = await program.account.custodian.fetch(custodian); @@ -161,8 +161,8 @@ export function newThresholdWormholeGateway(connection: Connection, wallet: Sola wrappedTbtcToken, wrappedTbtcMint, tbtcMint, - //senderToken, associated token account - //sender, associated token account owner, + senderToken: new PublicKey(senderToken), // associated token account + sender: new PublicKey(sender),//sender, associated token account owner, tokenBridgeConfig, tokenBridgeWrappedAsset, tokenBridgeTransferAuthority, diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index c5806d042..7c78b728d 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -605,9 +605,9 @@ async function solana( const transaction = await wormholeGateway.sendTbtc( transferAmountParsed.toBigInt(), targetChain, - new Uint8Array(0), - new Uint8Array(0), - new Uint8Array(0) + targetAddress, + fromAddress, + mintAddress ) const txid = await signSendAndConfirm(wallet, transaction); enqueueSnackbar(null, { From f668e4963719e65e5702fb2c89830b4f1fcdfa0d Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 11:35:43 -0300 Subject: [PATCH 07/19] [skip ci] - run prettier --- .../tbtc/solana/WormholeGateway.v2.ts | 393 ++++++++++-------- .../tbtc/solana/WormholeGatewayIdl.ts | 2 +- src/hooks/useHandleRedeem.tsx | 30 +- src/hooks/useHandleTransfer.tsx | 21 +- src/hooks/useSyncTargetAddress.ts | 9 +- src/utils/consts.ts | 6 +- 6 files changed, 278 insertions(+), 183 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index d0480d857..f31ce7a8e 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -1,196 +1,263 @@ -import { AnchorProvider, BN, Program } from '@project-serum/anchor'; -import { Connection, PublicKey, PublicKeyInitData, SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY, Transaction } from '@solana/web3.js'; -import { SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, THRESHOLD_GATEWAYS, THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET } from '../../../../utils/consts'; -import { CHAIN_ID_SOLANA, ChainId, SignedVaa, hexToUint8Array, parseTokenTransferVaa } from '@certusone/wormhole-sdk'; -import { WormholeGatewayIdl } from './WormholeGatewayIdl'; -import * as coreBridge from '@certusone/wormhole-sdk/lib/esm/solana/wormhole'; -import * as tokenBridge from '@certusone/wormhole-sdk/lib/esm/solana/tokenBridge'; -import { SolanaWallet } from '@xlabs-libs/wallet-aggregator-solana'; +import { AnchorProvider, BN, Program } from "@project-serum/anchor"; import { - ASSOCIATED_TOKEN_PROGRAM_ID, - Token, - TOKEN_PROGRAM_ID, - } from "@solana/spl-token"; + Connection, + PublicKey, + PublicKeyInitData, + SYSVAR_CLOCK_PUBKEY, + SYSVAR_RENT_PUBKEY, + Transaction, +} from "@solana/web3.js"; +import { + SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, + SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, + THRESHOLD_GATEWAYS, + THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET, +} from "../../../../utils/consts"; +import { + CHAIN_ID_SOLANA, + ChainId, + SignedVaa, + hexToUint8Array, + parseTokenTransferVaa, +} from "@certusone/wormhole-sdk"; +import { WormholeGatewayIdl } from "./WormholeGatewayIdl"; +import * as coreBridge from "@certusone/wormhole-sdk/lib/esm/solana/wormhole"; +import * as tokenBridge from "@certusone/wormhole-sdk/lib/esm/solana/tokenBridge"; +import { SolanaWallet } from "@xlabs-libs/wallet-aggregator-solana"; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + Token, + TOKEN_PROGRAM_ID, +} from "@solana/spl-token"; -const WORMHOLE_GATEWAY_PROGRAM_ID = new PublicKey(THRESHOLD_GATEWAYS[CHAIN_ID_SOLANA]); +const WORMHOLE_GATEWAY_PROGRAM_ID = new PublicKey( + THRESHOLD_GATEWAYS[CHAIN_ID_SOLANA] +); const TBTC_PROGRAM_ID = new PublicKey(THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET); const CORE_BRIDGE_PROGRAM_ID = new PublicKey(CORE_BRIDGE_ADDRESS); const TOKEN_BRIDGE_PROGRAM_ID = new PublicKey(TOKEN_BRIDGE_ADDRESS); function newAnchorProvider(connection: Connection, wallet: SolanaWallet) { - return new AnchorProvider(connection, { - async signTransaction(tx): Promise { - return await wallet.signTransaction(tx); - }, - async signAllTransactions(txs): Promise { - return await wallet.getAdapter().signAllTransactions!(txs); - }, - publicKey: wallet.getAdapter().publicKey! - }, {}); + return new AnchorProvider( + connection, + { + async signTransaction(tx): Promise { + return await wallet.signTransaction(tx); + }, + async signAllTransactions(txs): Promise { + return await wallet.getAdapter().signAllTransactions!(txs); + }, + publicKey: wallet.getAdapter().publicKey!, + }, + {} + ); } function getCustodianPDA(): PublicKey { - return PublicKey.findProgramAddressSync( - [Buffer.from("redeemer")], - new PublicKey(WORMHOLE_GATEWAY_PROGRAM_ID) - )[0]; + return PublicKey.findProgramAddressSync( + [Buffer.from("redeemer")], + new PublicKey(WORMHOLE_GATEWAY_PROGRAM_ID) + )[0]; } function getConfigPDA(): PublicKey { - return PublicKey.findProgramAddressSync( - [Buffer.from("config")], - TBTC_PROGRAM_ID - )[0]; + return PublicKey.findProgramAddressSync( + [Buffer.from("config")], + TBTC_PROGRAM_ID + )[0]; } function getMinterInfoPDA(minter: PublicKey): PublicKey { - return PublicKey.findProgramAddressSync( - [Buffer.from("minter-info"), minter.toBuffer()], - TBTC_PROGRAM_ID - )[0]; + return PublicKey.findProgramAddressSync( + [Buffer.from("minter-info"), minter.toBuffer()], + TBTC_PROGRAM_ID + )[0]; } function getTokenBridgeCoreEmitter() { - const [tokenBridgeCoreEmitter] = PublicKey.findProgramAddressSync( + const [tokenBridgeCoreEmitter] = PublicKey.findProgramAddressSync( [Buffer.from("emitter")], TOKEN_BRIDGE_PROGRAM_ID - ); - return tokenBridgeCoreEmitter; + ); + return tokenBridgeCoreEmitter; } async function getTokenBridgeSequence(connection: Connection) { - const emitter = getTokenBridgeCoreEmitter(); - return coreBridge - .getSequenceTracker(connection, - emitter, - CORE_BRIDGE_PROGRAM_ID - ) + const emitter = getTokenBridgeCoreEmitter(); + return coreBridge + .getSequenceTracker(connection, emitter, CORE_BRIDGE_PROGRAM_ID) .then((tracker) => tracker.sequence); } function getCoreMessagePDA(sequence: bigint): PublicKey { - const encodedSequence = Buffer.alloc(8); - encodedSequence.writeBigUInt64LE(sequence); - return PublicKey.findProgramAddressSync( - [Buffer.from("msg"), encodedSequence], - WORMHOLE_GATEWAY_PROGRAM_ID - )[0]; + const encodedSequence = Buffer.alloc(8); + encodedSequence.writeBigUInt64LE(sequence); + return PublicKey.findProgramAddressSync( + [Buffer.from("msg"), encodedSequence], + WORMHOLE_GATEWAY_PROGRAM_ID + )[0]; } -export function newThresholdWormholeGateway(connection: Connection, wallet: SolanaWallet) { - const provider = newAnchorProvider(connection, wallet); - const program = new Program(WormholeGatewayIdl, WORMHOLE_GATEWAY_PROGRAM_ID, provider); - - const receiveTbtc = async (signedVAA: SignedVaa, payer: PublicKeyInitData): Promise => { - const parsed = parseTokenTransferVaa(signedVAA); - const recipient = new PublicKey(payer); - const custodian = getCustodianPDA(); - const custodianData = await program.account.custodian.fetch(custodian); - const tbtcMint = new PublicKey(custodianData.tbtcMint as string); - const wrappedTbtcToken = new PublicKey(custodianData.wrappedTbtcToken as string); - const wrappedTbtcMint = new PublicKey(custodianData.wrappedTbtcMint as string); - const recipientToken = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - tbtcMint, - recipient - ); - const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, wrappedTbtcMint); - const recipientWrappedToken = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - wrappedTbtcMint, - recipient - ); - const accounts = { - payer: new PublicKey(wallet.getAddress()!), - custodian, - postedVaa: coreBridge.derivePostedVaaKey(CORE_BRIDGE_PROGRAM_ID, parsed.hash), - tokenBridgeClaim: coreBridge.deriveClaimKey(TOKEN_BRIDGE_PROGRAM_ID, parsed.emitterAddress, parsed.emitterChain, parsed.sequence), - wrappedTbtcToken, - wrappedTbtcMint, - tbtcMint, - recipientToken, - recipient, - recipientWrappedToken, - tbtcConfig: getConfigPDA(), - tbtcMinterInfo: getMinterInfoPDA(custodian), - tokenBridgeConfig: tokenBridge.deriveTokenBridgeConfigKey(TOKEN_BRIDGE_PROGRAM_ID), - tokenBridgeRegisteredEmitter: tokenBridge.deriveEndpointKey(TOKEN_BRIDGE_PROGRAM_ID, parsed.emitterChain, parsed.emitterAddress), - tokenBridgeWrappedAsset, - tokenBridgeMintAuthority: tokenBridge.deriveMintAuthorityKey(TOKEN_BRIDGE_PROGRAM_ID), - rent: SYSVAR_RENT_PUBKEY, - tbtcProgram: TBTC_PROGRAM_ID, - tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, - coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID +export function newThresholdWormholeGateway( + connection: Connection, + wallet: SolanaWallet +) { + const provider = newAnchorProvider(connection, wallet); + const program = new Program( + WormholeGatewayIdl, + WORMHOLE_GATEWAY_PROGRAM_ID, + provider + ); - }; - const tx = program.methods - .receiveTbtc(parsed.hash) - .accounts(accounts) - .transaction(); - return tx; - } + const receiveTbtc = async ( + signedVAA: SignedVaa, + payer: PublicKeyInitData + ): Promise => { + const parsed = parseTokenTransferVaa(signedVAA); + const recipient = new PublicKey(payer); + const custodian = getCustodianPDA(); + const custodianData = await program.account.custodian.fetch(custodian); + const tbtcMint = new PublicKey(custodianData.tbtcMint as string); + const wrappedTbtcToken = new PublicKey( + custodianData.wrappedTbtcToken as string + ); + const wrappedTbtcMint = new PublicKey( + custodianData.wrappedTbtcMint as string + ); + const recipientToken = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipient + ); + const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey( + TOKEN_BRIDGE_PROGRAM_ID, + wrappedTbtcMint + ); + const recipientWrappedToken = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + wrappedTbtcMint, + recipient + ); + const accounts = { + payer: new PublicKey(wallet.getAddress()!), + custodian, + postedVaa: coreBridge.derivePostedVaaKey( + CORE_BRIDGE_PROGRAM_ID, + parsed.hash + ), + tokenBridgeClaim: coreBridge.deriveClaimKey( + TOKEN_BRIDGE_PROGRAM_ID, + parsed.emitterAddress, + parsed.emitterChain, + parsed.sequence + ), + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + recipientToken, + recipient, + recipientWrappedToken, + tbtcConfig: getConfigPDA(), + tbtcMinterInfo: getMinterInfoPDA(custodian), + tokenBridgeConfig: tokenBridge.deriveTokenBridgeConfigKey( + TOKEN_BRIDGE_PROGRAM_ID + ), + tokenBridgeRegisteredEmitter: tokenBridge.deriveEndpointKey( + TOKEN_BRIDGE_PROGRAM_ID, + parsed.emitterChain, + parsed.emitterAddress + ), + tokenBridgeWrappedAsset, + tokenBridgeMintAuthority: tokenBridge.deriveMintAuthorityKey( + TOKEN_BRIDGE_PROGRAM_ID + ), + rent: SYSVAR_RENT_PUBKEY, + tbtcProgram: TBTC_PROGRAM_ID, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, + }; + const tx = program.methods + .receiveTbtc(parsed.hash) + .accounts(accounts) + .transaction(); + return tx; + }; - const sendTbtc = async ( - amount: bigint, - recipientChain: ChainId, - recipientAddress: Uint8Array, - sender: string, - senderToken: string - ): Promise => { - const custodian = getCustodianPDA(); - const custodianData = await program.account.custodian.fetch(custodian); - const tbtcMint = new PublicKey(custodianData.tbtcMint as string); - const wrappedTbtcToken = new PublicKey(custodianData.wrappedTbtcToken as string); - const wrappedTbtcMint = new PublicKey(custodianData.wrappedTbtcMint as string); - const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey(TOKEN_BRIDGE_PROGRAM_ID, wrappedTbtcMint); - const tokenBridgeConfig = tokenBridge.deriveTokenBridgeConfigKey(TOKEN_BRIDGE_PROGRAM_ID); - const tokenBridgeTransferAuthority = tokenBridge.deriveAuthoritySignerKey(TOKEN_BRIDGE_PROGRAM_ID); - const coreFeeCollector = coreBridge.deriveFeeCollectorKey(CORE_BRIDGE_PROGRAM_ID); - const sequence = await getTokenBridgeSequence(connection); - const coreMessage = getCoreMessagePDA(sequence); - const coreBridgeData = coreBridge.deriveWormholeBridgeDataKey(CORE_BRIDGE_PROGRAM_ID); - const tokenBridgeCoreEmitter = getTokenBridgeCoreEmitter(); - const coreEmitterSequence = coreBridge.deriveEmitterSequenceKey( - tokenBridgeCoreEmitter, - CORE_BRIDGE_PROGRAM_ID - ); - const accounts = { - custodian, - wrappedTbtcToken, - wrappedTbtcMint, - tbtcMint, - senderToken: new PublicKey(senderToken), // associated token account - sender: new PublicKey(sender),//sender, associated token account owner, - tokenBridgeConfig, - tokenBridgeWrappedAsset, - tokenBridgeTransferAuthority, - coreBridgeData, - coreMessage, - tokenBridgeCoreEmitter, - coreEmitterSequence, - coreFeeCollector, - clock: SYSVAR_CLOCK_PUBKEY, - rent: SYSVAR_RENT_PUBKEY, - tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, - coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, - } - const args = { - amount: new BN(amount.toString()), - recipientChain, - recipientAddress, - nonce: 0, - } - const tx = program.methods - .sendTbtcGateway(args) - .accounts(accounts) - .transaction(); - return tx; - } - return { - sendTbtc, - receiveTbtc - } + const sendTbtc = async ( + amount: bigint, + recipientChain: ChainId, + recipientAddress: Uint8Array, + sender: string, + senderToken: string + ): Promise => { + const custodian = getCustodianPDA(); + const custodianData = await program.account.custodian.fetch(custodian); + const tbtcMint = new PublicKey(custodianData.tbtcMint as string); + const wrappedTbtcToken = new PublicKey( + custodianData.wrappedTbtcToken as string + ); + const wrappedTbtcMint = new PublicKey( + custodianData.wrappedTbtcMint as string + ); + const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey( + TOKEN_BRIDGE_PROGRAM_ID, + wrappedTbtcMint + ); + const tokenBridgeConfig = tokenBridge.deriveTokenBridgeConfigKey( + TOKEN_BRIDGE_PROGRAM_ID + ); + const tokenBridgeTransferAuthority = tokenBridge.deriveAuthoritySignerKey( + TOKEN_BRIDGE_PROGRAM_ID + ); + const coreFeeCollector = coreBridge.deriveFeeCollectorKey( + CORE_BRIDGE_PROGRAM_ID + ); + const sequence = await getTokenBridgeSequence(connection); + const coreMessage = getCoreMessagePDA(sequence); + const coreBridgeData = coreBridge.deriveWormholeBridgeDataKey( + CORE_BRIDGE_PROGRAM_ID + ); + const tokenBridgeCoreEmitter = getTokenBridgeCoreEmitter(); + const coreEmitterSequence = coreBridge.deriveEmitterSequenceKey( + tokenBridgeCoreEmitter, + CORE_BRIDGE_PROGRAM_ID + ); + const accounts = { + custodian, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + senderToken: new PublicKey(senderToken), // associated token account + sender: new PublicKey(sender), //sender, associated token account owner, + tokenBridgeConfig, + tokenBridgeWrappedAsset, + tokenBridgeTransferAuthority, + coreBridgeData, + coreMessage, + tokenBridgeCoreEmitter, + coreEmitterSequence, + coreFeeCollector, + clock: SYSVAR_CLOCK_PUBKEY, + rent: SYSVAR_RENT_PUBKEY, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, + }; + const args = { + amount: new BN(amount.toString()), + recipientChain, + recipientAddress, + nonce: 0, + }; + const tx = program.methods + .sendTbtcGateway(args) + .accounts(accounts) + .transaction(); + return tx; + }; + return { + sendTbtc, + receiveTbtc, + }; } - diff --git a/src/assets/providers/tbtc/solana/WormholeGatewayIdl.ts b/src/assets/providers/tbtc/solana/WormholeGatewayIdl.ts index b75a00382..1b9ecfb7c 100644 --- a/src/assets/providers/tbtc/solana/WormholeGatewayIdl.ts +++ b/src/assets/providers/tbtc/solana/WormholeGatewayIdl.ts @@ -959,4 +959,4 @@ export const WormholeGatewayIdl: Idl = { msg: "Minted amount after deposit exceeds u64", }, ], -}; \ No newline at end of file +}; diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index e4434c60e..31ca7e517 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -358,10 +358,16 @@ async function solana( Buffer.from(signedVAA), MAX_VAA_UPLOAD_RETRIES_SOLANA ); - console.log("txidVaaPosted", txidVaaPosted.map(tx => tx.signature)); + console.log( + "txidVaaPosted", + txidVaaPosted.map((tx) => tx.signature) + ); const tbtcGateway = newThresholdWormholeGateway(connection, wallet); // TODO: how do we retry in between these steps - const transaction = await tbtcGateway.receiveTbtc(signedVAA, payerAddress); + const transaction = await tbtcGateway.receiveTbtc( + signedVAA, + payerAddress + ); const txid = await signSendAndConfirm(wallet, transaction); // TODO: didn't want to make an info call we didn't need, can we get the block without it by modifying the above call? dispatch(setRedeemTx({ id: txid, block: 1 })); @@ -519,7 +525,15 @@ export function useHandleRedeem() { !!solPK && signedVAA ) { - solana(dispatch, enqueueSnackbar, solanaWallet, solPK, signedVAA, false, isTBTC); + solana( + dispatch, + enqueueSnackbar, + solanaWallet, + solPK, + signedVAA, + false, + isTBTC + ); } else if (isTerraChain(targetChain) && !!terraWallet && signedVAA) { terra( dispatch, @@ -592,7 +606,15 @@ export function useHandleRedeem() { !!solPK && signedVAA ) { - solana(dispatch, enqueueSnackbar, solanaWallet, solPK, signedVAA, true, isTBTC); + solana( + dispatch, + enqueueSnackbar, + solanaWallet, + solPK, + signedVAA, + true, + isTBTC + ); } else if (isTerraChain(targetChain) && !!terraWallet && signedVAA) { terra( dispatch, diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index 7c78b728d..92a2814f9 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -608,14 +608,16 @@ async function solana( targetAddress, fromAddress, mintAddress - ) + ); const txid = await signSendAndConfirm(wallet, transaction); enqueueSnackbar(null, { content: Transaction confirmed, }); const info = await connection.getTransaction(txid); if (!info) { - throw new Error("An error occurred while fetching the transaction info"); + throw new Error( + "An error occurred while fetching the transaction info" + ); } dispatch(setTransferTx({ id: txid, block: info.slot })); const sequence = parseSequenceFromLogSolana(info); @@ -665,7 +667,9 @@ async function solana( }); const info = await connection.getTransaction(txid); if (!info) { - throw new Error("An error occurred while fetching the transaction info"); + throw new Error( + "An error occurred while fetching the transaction info" + ); } dispatch(setTransferTx({ id: txid, block: info.slot })); const sequence = parseSequenceFromLogSolana(info); @@ -932,12 +936,15 @@ export function useHandleTransfer() { THRESHOLD_GATEWAYS[targetChain] && targetAddress ) { - const tbtcGateway = tryNativeToUint8Array(THRESHOLD_GATEWAYS[targetChain], targetChain); + const tbtcGateway = tryNativeToUint8Array( + THRESHOLD_GATEWAYS[targetChain], + targetChain + ); console.log(uint8ArrayToHex(targetAddress), uint8ArrayToHex(tbtcGateway)); return { - receivingContract: tbtcGateway, - payload: targetAddress, - }; + receivingContract: tbtcGateway, + payload: targetAddress, + }; } return null; }, [isTBTC, originChain, targetAddress, targetChain]); diff --git a/src/hooks/useSyncTargetAddress.ts b/src/hooks/useSyncTargetAddress.ts index 49ba6c7e8..8ac9989af 100644 --- a/src/hooks/useSyncTargetAddress.ts +++ b/src/hooks/useSyncTargetAddress.ts @@ -85,13 +85,10 @@ function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) { else if (!nft && solPK && targetChain === CHAIN_ID_SOLANA && isTBTC) { dispatch( setTargetAddressHex( - uint8ArrayToHex( - zeroPad(new PublicKey(solPK).toBytes(), 32) - ) + uint8ArrayToHex(zeroPad(new PublicKey(solPK).toBytes(), 32)) ) ); - } - else if ( + } else if ( !nft && // only support existing, non-derived token accounts for token transfers (nft flow doesn't check balance) targetChain === CHAIN_ID_SOLANA && targetTokenAccountPublicKey @@ -244,7 +241,7 @@ function useSyncTargetAddress(shouldFire: boolean, nft?: boolean) { aptosAddress, injAddress, suiAddress, - isTBTC + isTBTC, ]); } diff --git a/src/utils/consts.ts b/src/utils/consts.ts index 7971ecb9d..3c1842ce9 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -446,8 +446,10 @@ const THRESHOLD_TBTC_CONTRACTS_TESTNET: any = { [CHAIN_ID_SOLANA]: "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU", // Solana TBTC Mint } as const; -export const THRESHOLD_TBTC_SOLANA_MINT_TESTNET = "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU"; -export const THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET = "Gj93RRt6QB7FjmyokAD5rcMAku7pq3Fk2Aa8y6nNbwsV"; +export const THRESHOLD_TBTC_SOLANA_MINT_TESTNET = + "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU"; +export const THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET = + "Gj93RRt6QB7FjmyokAD5rcMAku7pq3Fk2Aa8y6nNbwsV"; export const THRESHOLD_TBTC_CONTRACTS: any = { ...(CLUSTER === "mainnet" From 1a78837ba7a7737bece60ced5d3f889fabc65057 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 11:37:43 -0300 Subject: [PATCH 08/19] remove unecesary console.log --- src/components/SolanaCreateAssociatedAddress.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/SolanaCreateAssociatedAddress.tsx b/src/components/SolanaCreateAssociatedAddress.tsx index 1937748c6..9d1676589 100644 --- a/src/components/SolanaCreateAssociatedAddress.tsx +++ b/src/components/SolanaCreateAssociatedAddress.tsx @@ -130,7 +130,6 @@ export default function SolanaCreateAssociatedAddress({ const { blockhash } = await connection.getRecentBlockhash(); transaction.recentBlockhash = blockhash; transaction.feePayer = new PublicKey(payerPublicKey); - console.log(transaction); await signSendAndConfirm(solanaWallet, transaction); setIsCreating(false); setAssociatedAccountExists(true); From f44cbbead343c617fb6efe161deb9abc7fb0e448 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 11:43:17 -0300 Subject: [PATCH 09/19] remove unecesary console.log --- src/hooks/useHandleRedeem.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index 31ca7e517..8d503a379 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -350,7 +350,11 @@ async function solana( } const connection = new Connection(SOLANA_HOST, "confirmed"); if (isTbtc) { - const txidVaaPosted = await postVaaSolanaWithRetry( + // TODO compute the amount of tx that postVaaSolanaWithRetry + // will create to notice the user up front + // we could call createPostSignedVaaTransactions to create fake txs + // and read the length of the array + await postVaaSolanaWithRetry( connection, wallet.signTransaction.bind(wallet), SOL_BRIDGE_ADDRESS, @@ -358,10 +362,6 @@ async function solana( Buffer.from(signedVAA), MAX_VAA_UPLOAD_RETRIES_SOLANA ); - console.log( - "txidVaaPosted", - txidVaaPosted.map((tx) => tx.signature) - ); const tbtcGateway = newThresholdWormholeGateway(connection, wallet); // TODO: how do we retry in between these steps const transaction = await tbtcGateway.receiveTbtc( From 7e08a5cad44993d47cc599acb89ab21196e81497 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 14:45:14 -0300 Subject: [PATCH 10/19] fix parameters to send back to L1 --- .../tbtc/solana/WormholeGateway.v2.ts | 138 ++++++++++++++---- src/hooks/useHandleRedeem.tsx | 2 - src/hooks/useHandleTransfer.tsx | 10 +- 3 files changed, 114 insertions(+), 36 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index f31ce7a8e..b1ca35d58 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -14,10 +14,10 @@ import { THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET, } from "../../../../utils/consts"; import { + CHAIN_ID_ETH, CHAIN_ID_SOLANA, ChainId, SignedVaa, - hexToUint8Array, parseTokenTransferVaa, } from "@certusone/wormhole-sdk"; import { WormholeGatewayIdl } from "./WormholeGatewayIdl"; @@ -98,6 +98,50 @@ function getCoreMessagePDA(sequence: bigint): PublicKey { )[0]; } +function getGatewayInfoPDA(targetChain: number): PublicKey { + const encodedChain = Buffer.alloc(2); + encodedChain.writeUInt16LE(targetChain); + return PublicKey.findProgramAddressSync( + [Buffer.from("gateway-info"), encodedChain], + WORMHOLE_GATEWAY_PROGRAM_ID + )[0]; +} + +type SendTbtcArgs = { + +} + +type SendTbtcWrappedAccounts = {} + +type SendTbtcGatewayAccounts = { + +} + +/** + * will off board from Solana to L1 (Ethereum) + */ +function sendTbtcWrapped(program: Program, args: SendTbtcArgs, accounts: SendTbtcWrappedAccounts) { + const tx = program.methods + .sendTbtcWrapped({ + ...args, + arbiterFee: new BN(0) + }) + .accounts(accounts) + .transaction(); + return tx; +} + +/** + * Send tBtc beween gateways allow burn and mint of tBtc + */ +function sendTbtcGateway(program: Program, args: SendTbtcArgs, accounts: SendTbtcGatewayAccounts) { + const tx = program.methods + .sendTbtcGateway(args) + .accounts(accounts) + .transaction(); + return tx; +} + export function newThresholdWormholeGateway( connection: Connection, wallet: SolanaWallet @@ -224,37 +268,71 @@ export function newThresholdWormholeGateway( tokenBridgeCoreEmitter, CORE_BRIDGE_PROGRAM_ID ); - const accounts = { - custodian, - wrappedTbtcToken, - wrappedTbtcMint, - tbtcMint, - senderToken: new PublicKey(senderToken), // associated token account - sender: new PublicKey(sender), //sender, associated token account owner, - tokenBridgeConfig, - tokenBridgeWrappedAsset, - tokenBridgeTransferAuthority, - coreBridgeData, - coreMessage, - tokenBridgeCoreEmitter, - coreEmitterSequence, - coreFeeCollector, - clock: SYSVAR_CLOCK_PUBKEY, - rent: SYSVAR_RENT_PUBKEY, - tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, - coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, - }; + const gatewayInfo = getGatewayInfoPDA(recipientChain); + const tokenBridgeSender = tokenBridge.deriveSenderAccountKey( + WORMHOLE_GATEWAY_PROGRAM_ID + ); const args = { - amount: new BN(amount.toString()), - recipientChain, - recipientAddress, - nonce: 0, + amount: new BN(amount.toString()), + recipientChain, + recipient: recipientAddress, + nonce: 0, }; - const tx = program.methods - .sendTbtcGateway(args) - .accounts(accounts) - .transaction(); - return tx; + const associatedTokenAccount = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + new PublicKey(senderToken), // this might error + new PublicKey(wallet.getAddress()!) + ) + if (recipientChain === CHAIN_ID_ETH) { // L1 off board + const wrappedAccounts = { + custodian, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + senderToken: associatedTokenAccount, // associated token account + sender: new PublicKey(wallet.getAddress()!), //sender, associated token account owner, + tokenBridgeConfig, + tokenBridgeWrappedAsset, + tokenBridgeTransferAuthority, + coreBridgeData, + coreMessage, + tokenBridgeCoreEmitter, + coreEmitterSequence, + coreFeeCollector, + clock: SYSVAR_CLOCK_PUBKEY, + rent: SYSVAR_RENT_PUBKEY, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID + } + console.log('accounts', Object.entries(wrappedAccounts).map(([k, v]) => [k, v.toBase58()])); + console.log('args', Object.entries(args).map(([k, v]) => [k, v.toString()])); + return sendTbtcWrapped(program, args, wrappedAccounts); + } else { + const gatewayAccounts = { + custodian, + gatewayInfo, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + senderToken: associatedTokenAccount, // associated token account + sender: new PublicKey(wallet.getAddress()!), //sender, associated token account owner, + tokenBridgeConfig, + tokenBridgeWrappedAsset, + tokenBridgeTransferAuthority, + coreBridgeData, + coreMessage, + tokenBridgeCoreEmitter, + coreEmitterSequence, + coreFeeCollector, + clock: SYSVAR_CLOCK_PUBKEY, + tokenBridgeSender, + rent: SYSVAR_RENT_PUBKEY, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, + } + return sendTbtcGateway(program, args, gatewayAccounts); + } }; return { sendTbtc, diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index 8d503a379..e7a9039d6 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -363,13 +363,11 @@ async function solana( MAX_VAA_UPLOAD_RETRIES_SOLANA ); const tbtcGateway = newThresholdWormholeGateway(connection, wallet); - // TODO: how do we retry in between these steps const transaction = await tbtcGateway.receiveTbtc( signedVAA, payerAddress ); const txid = await signSendAndConfirm(wallet, transaction); - // TODO: didn't want to make an info call we didn't need, can we get the block without it by modifying the above call? dispatch(setRedeemTx({ id: txid, block: 1 })); enqueueSnackbar(null, { content: Transaction confirmed, diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index 92a2814f9..cccb98bb1 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -96,6 +96,7 @@ import { THRESHOLD_ARBITER_FEE, THRESHOLD_NONCE, THRESHOLD_GATEWAYS, + THRESHOLD_TBTC_CONTRACTS, } from "../utils/consts"; import { getSignedVAAWithRetry } from "../utils/getSignedVAAWithRetry"; import { @@ -584,10 +585,10 @@ async function solana( targetAddress: Uint8Array, isNative: boolean, maybeAdditionalPayload: MaybeAdditionalPayloadFn, + isTBTC: boolean, originAddressStr?: string, originChain?: ChainId, - relayerFee?: string, - isTBTC: boolean = false + relayerFee?: string ) { dispatch(setIsSending(true)); try { @@ -600,7 +601,7 @@ async function solana( ? zeroPad(hexToUint8Array(originAddressStr), 32) : undefined; - if (isTBTC) { + if (THRESHOLD_TBTC_CONTRACTS[originChain!] === mintAddress) { const wormholeGateway = newThresholdWormholeGateway(connection, wallet); const transaction = await wormholeGateway.sendTbtc( transferAmountParsed.toBigInt(), @@ -685,6 +686,7 @@ async function solana( ); } } catch (e) { + console.trace(e); handleError(e, enqueueSnackbar, dispatch); } } @@ -940,7 +942,6 @@ export function useHandleTransfer() { THRESHOLD_GATEWAYS[targetChain], targetChain ); - console.log(uint8ArrayToHex(targetAddress), uint8ArrayToHex(tbtcGateway)); return { receivingContract: tbtcGateway, payload: targetAddress, @@ -995,6 +996,7 @@ export function useHandleTransfer() { targetAddress, isNative, maybeAdditionalPayload, + isTBTC, originAsset, originChain, relayerFee From 44d3c218baa6635b52a4c6a77da6457196910f3f Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 17:19:16 -0300 Subject: [PATCH 11/19] create ata for the redeem process --- .../tbtc/solana/WormholeGateway.v2.ts | 27 ++++++++++++------- .../TokenSelectors/EvmTokenPicker.tsx | 6 +++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index b1ca35d58..d070b73ac 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -169,11 +169,20 @@ export function newThresholdWormholeGateway( custodianData.wrappedTbtcMint as string ); const recipientToken = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - tbtcMint, - recipient - ); + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipient + ); + const recipientTokenAta = Token.createAssociatedTokenAccountInstruction( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipientToken, + recipient, // owner + recipient // payer + ) + console.log(recipientToken); const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey( TOKEN_BRIDGE_PROGRAM_ID, wrappedTbtcMint @@ -222,11 +231,13 @@ export function newThresholdWormholeGateway( tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, }; - const tx = program.methods + const tx = await program.methods .receiveTbtc(parsed.hash) .accounts(accounts) .transaction(); - return tx; + return new Transaction() + .add(recipientTokenAta) + .add(tx); }; const sendTbtc = async ( @@ -305,8 +316,6 @@ export function newThresholdWormholeGateway( tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID } - console.log('accounts', Object.entries(wrappedAccounts).map(([k, v]) => [k, v.toBase58()])); - console.log('args', Object.entries(args).map(([k, v]) => [k, v.toString()])); return sendTbtcWrapped(program, args, wrappedAccounts); } else { const gatewayAccounts = { diff --git a/src/components/TokenSelectors/EvmTokenPicker.tsx b/src/components/TokenSelectors/EvmTokenPicker.tsx index 74d6795a8..fc312354e 100644 --- a/src/components/TokenSelectors/EvmTokenPicker.tsx +++ b/src/components/TokenSelectors/EvmTokenPicker.tsx @@ -90,6 +90,12 @@ export default function EvmTokenPicker( const isMigrationEligible = useCallback( (address: string) => { + // TODO at the end of a transfer from from Solanato Eth an error happens + /** + * Select as source solana + * Select as target eth + * Sent the tokens, and hit transfer more tokens button + */ const assetMap = getMigrationAssetMap(chainId); return !!assetMap.get(getEthAddress(address)); }, From aa44c68a65cc329f260953a22ebaad113ac4a277 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 18:39:11 -0300 Subject: [PATCH 12/19] handle case for sending from non L1 to a non L1 evm chain --- .../tbtc/solana/WormholeGateway.v2.ts | 4 +- src/hooks/useCheckIfWormholeWrapped.ts | 49 ++++++++++++------- src/hooks/useHandleRedeem.tsx | 5 +- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index d070b73ac..1ec55029b 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -235,9 +235,7 @@ export function newThresholdWormholeGateway( .receiveTbtc(parsed.hash) .accounts(accounts) .transaction(); - return new Transaction() - .add(recipientTokenAta) - .add(tx); + return tx; }; const sendTbtc = async ( diff --git a/src/hooks/useCheckIfWormholeWrapped.ts b/src/hooks/useCheckIfWormholeWrapped.ts index baba582a0..81b0dd862 100644 --- a/src/hooks/useCheckIfWormholeWrapped.ts +++ b/src/hooks/useCheckIfWormholeWrapped.ts @@ -57,6 +57,7 @@ import { XPLA_LCD_CLIENT_CONFIG, THRESHOLD_TBTC_CONTRACTS, TBTC_ASSET_ADDRESS, + THRESHOLD_TBTC_SOLANA_MINT_TESTNET, } from "../utils/consts"; import { getOriginalAssetNear, makeNearAccount } from "../utils/near"; import { LCDClient as XplaLCDClient } from "@xpla/xpla.js"; @@ -151,23 +152,37 @@ function useCheckIfWormholeWrapped(nft?: boolean) { } if (sourceChain === CHAIN_ID_SOLANA && sourceAsset) { try { - const connection = new Connection(SOLANA_HOST, "confirmed"); - const wrappedInfo = makeStateSafe( - await (nft - ? getOriginalAssetSolNFT( - connection, - SOL_NFT_BRIDGE_ADDRESS, - sourceAsset - ) - : getOriginalAssetSol( - connection, - SOL_TOKEN_BRIDGE_ADDRESS, - sourceAsset - )) - ); - if (!cancelled) { - dispatch(setSourceWormholeWrappedInfo(wrappedInfo)); - } + // Check if is tBtc canonical on Solana + // TODO improve the check and centralice the login on just one place + if (THRESHOLD_TBTC_SOLANA_MINT_TESTNET === sourceAsset) { + console.log("selected tBTC on canonical chain"); + dispatch( + setSourceWormholeWrappedInfo({ + isWrapped: true, + chainId: CHAIN_ID_ETH, + assetAddress: TBTC_ASSET_ADDRESS, + }) + ); + return; + } else { + const connection = new Connection(SOLANA_HOST, "confirmed"); + const wrappedInfo = makeStateSafe( + await (nft + ? getOriginalAssetSolNFT( + connection, + SOL_NFT_BRIDGE_ADDRESS, + sourceAsset + ) + : getOriginalAssetSol( + connection, + SOL_TOKEN_BRIDGE_ADDRESS, + sourceAsset + )) + ); + if (!cancelled) { + dispatch(setSourceWormholeWrappedInfo(wrappedInfo)); + } + } } catch (e) {} } if (isTerraChain(sourceChain) && sourceAsset) { diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index e7a9039d6..df336b0e1 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -171,7 +171,10 @@ async function evm( try { let receipt; - + /** + * if THRESHOLD_GATEWAYS[chainId] has something + * we have a gateway contract on the target chain to use + */ const isCanonicalTarget = !!THRESHOLD_GATEWAYS[chainId]; if (isTBTC && isCanonicalTarget) { console.log("redeem tbtc on canonical"); From 60fcd910cfdc1af09b914ba98ad77ff9a08917c3 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 19:05:08 -0300 Subject: [PATCH 13/19] cosmetic changes --- .../tbtc/solana/WormholeGateway.v2.ts | 243 +++++++++++------- src/hooks/useCheckIfWormholeWrapped.ts | 5 +- src/utils/consts.ts | 12 +- 3 files changed, 158 insertions(+), 102 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index 1ec55029b..389bb99b8 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -11,10 +11,10 @@ import { SOL_BRIDGE_ADDRESS as CORE_BRIDGE_ADDRESS, SOL_TOKEN_BRIDGE_ADDRESS as TOKEN_BRIDGE_ADDRESS, THRESHOLD_GATEWAYS, - THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET, + THRESHOLD_TBTC_SOLANA_PROGRAM, } from "../../../../utils/consts"; import { - CHAIN_ID_ETH, + CHAIN_ID_ETH, CHAIN_ID_SOLANA, ChainId, SignedVaa, @@ -33,7 +33,7 @@ import { const WORMHOLE_GATEWAY_PROGRAM_ID = new PublicKey( THRESHOLD_GATEWAYS[CHAIN_ID_SOLANA] ); -const TBTC_PROGRAM_ID = new PublicKey(THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET); +const TBTC_PROGRAM_ID = new PublicKey(THRESHOLD_TBTC_SOLANA_PROGRAM); const CORE_BRIDGE_PROGRAM_ID = new PublicKey(CORE_BRIDGE_ADDRESS); const TOKEN_BRIDGE_PROGRAM_ID = new PublicKey(TOKEN_BRIDGE_ADDRESS); @@ -99,47 +99,96 @@ function getCoreMessagePDA(sequence: bigint): PublicKey { } function getGatewayInfoPDA(targetChain: number): PublicKey { - const encodedChain = Buffer.alloc(2); - encodedChain.writeUInt16LE(targetChain); - return PublicKey.findProgramAddressSync( - [Buffer.from("gateway-info"), encodedChain], - WORMHOLE_GATEWAY_PROGRAM_ID - )[0]; + const encodedChain = Buffer.alloc(2); + encodedChain.writeUInt16LE(targetChain); + return PublicKey.findProgramAddressSync( + [Buffer.from("gateway-info"), encodedChain], + WORMHOLE_GATEWAY_PROGRAM_ID + )[0]; } type SendTbtcArgs = { + amount: BN; + recipientChain: number; + recipient: Uint8Array; + nonce: number; +}; -} - -type SendTbtcWrappedAccounts = {} +type SendTbtcWrappedAccounts = { + custodian: PublicKey; + wrappedTbtcToken: PublicKey; + wrappedTbtcMint: PublicKey; + tbtcMint: PublicKey; + senderToken: PublicKey; + sender: PublicKey; + tokenBridgeConfig: PublicKey; + tokenBridgeWrappedAsset: PublicKey; + tokenBridgeTransferAuthority: PublicKey; + coreBridgeData: PublicKey; + coreMessage: PublicKey; + tokenBridgeCoreEmitter: PublicKey; + coreEmitterSequence: PublicKey; + coreFeeCollector: PublicKey; + clock: PublicKey; + rent: PublicKey; + tokenBridgeProgram: PublicKey; + coreBridgeProgram: PublicKey; +}; type SendTbtcGatewayAccounts = { - -} + custodian: PublicKey; + gatewayInfo: PublicKey; + wrappedTbtcToken: PublicKey; + wrappedTbtcMint: PublicKey; + tbtcMint: PublicKey; + senderToken: PublicKey; + sender: PublicKey; + tokenBridgeConfig: PublicKey; + tokenBridgeWrappedAsset: PublicKey; + tokenBridgeTransferAuthority: PublicKey; + coreBridgeData: PublicKey; + coreMessage: PublicKey; + tokenBridgeCoreEmitter: PublicKey; + coreEmitterSequence: PublicKey; + coreFeeCollector: PublicKey; + clock: PublicKey; + tokenBridgeSender: PublicKey; + rent: PublicKey; + tokenBridgeProgram: PublicKey; + coreBridgeProgram: PublicKey; +}; /** * will off board from Solana to L1 (Ethereum) */ -function sendTbtcWrapped(program: Program, args: SendTbtcArgs, accounts: SendTbtcWrappedAccounts) { - const tx = program.methods - .sendTbtcWrapped({ - ...args, - arbiterFee: new BN(0) - }) - .accounts(accounts) - .transaction(); - return tx; +function sendTbtcWrapped( + program: Program, + args: SendTbtcArgs, + accounts: SendTbtcWrappedAccounts +) { + const tx = program.methods + .sendTbtcWrapped({ + ...args, + arbiterFee: new BN(0), + }) + .accounts(accounts) + .transaction(); + return tx; } /** * Send tBtc beween gateways allow burn and mint of tBtc */ -function sendTbtcGateway(program: Program, args: SendTbtcArgs, accounts: SendTbtcGatewayAccounts) { - const tx = program.methods - .sendTbtcGateway(args) - .accounts(accounts) - .transaction(); - return tx; +function sendTbtcGateway( + program: Program, + args: SendTbtcArgs, + accounts: SendTbtcGatewayAccounts +) { + const tx = program.methods + .sendTbtcGateway(args) + .accounts(accounts) + .transaction(); + return tx; } export function newThresholdWormholeGateway( @@ -169,20 +218,22 @@ export function newThresholdWormholeGateway( custodianData.wrappedTbtcMint as string ); const recipientToken = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - tbtcMint, - recipient - ); + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipient + ); +/* const recipientTokenAta = Token.createAssociatedTokenAccountInstruction( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - tbtcMint, - recipientToken, - recipient, // owner - recipient // payer - ) + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipientToken, + recipient, // owner + recipient // payer + ); console.log(recipientToken); +*/ const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey( TOKEN_BRIDGE_PROGRAM_ID, wrappedTbtcMint @@ -279,66 +330,66 @@ export function newThresholdWormholeGateway( ); const gatewayInfo = getGatewayInfoPDA(recipientChain); const tokenBridgeSender = tokenBridge.deriveSenderAccountKey( - WORMHOLE_GATEWAY_PROGRAM_ID + WORMHOLE_GATEWAY_PROGRAM_ID ); const args = { - amount: new BN(amount.toString()), - recipientChain, - recipient: recipientAddress, - nonce: 0, + amount: new BN(amount.toString()), + recipientChain, + recipient: recipientAddress, + nonce: 0, }; const associatedTokenAccount = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - new PublicKey(senderToken), // this might error - new PublicKey(wallet.getAddress()!) - ) - if (recipientChain === CHAIN_ID_ETH) { // L1 off board - const wrappedAccounts = { - custodian, - wrappedTbtcToken, - wrappedTbtcMint, - tbtcMint, - senderToken: associatedTokenAccount, // associated token account - sender: new PublicKey(wallet.getAddress()!), //sender, associated token account owner, - tokenBridgeConfig, - tokenBridgeWrappedAsset, - tokenBridgeTransferAuthority, - coreBridgeData, - coreMessage, - tokenBridgeCoreEmitter, - coreEmitterSequence, - coreFeeCollector, - clock: SYSVAR_CLOCK_PUBKEY, - rent: SYSVAR_RENT_PUBKEY, - tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, - coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID - } - return sendTbtcWrapped(program, args, wrappedAccounts); + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + new PublicKey(senderToken), + new PublicKey(wallet.getAddress()!) + ); + if (recipientChain === CHAIN_ID_ETH) { + const wrappedAccounts = { + custodian, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + senderToken: associatedTokenAccount, // associated token account + sender: new PublicKey(wallet.getAddress()!), //sender, associated token account owner, + tokenBridgeConfig, + tokenBridgeWrappedAsset, + tokenBridgeTransferAuthority, + coreBridgeData, + coreMessage, + tokenBridgeCoreEmitter, + coreEmitterSequence, + coreFeeCollector, + clock: SYSVAR_CLOCK_PUBKEY, + rent: SYSVAR_RENT_PUBKEY, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, + }; + return sendTbtcWrapped(program, args, wrappedAccounts); } else { - const gatewayAccounts = { - custodian, - gatewayInfo, - wrappedTbtcToken, - wrappedTbtcMint, - tbtcMint, - senderToken: associatedTokenAccount, // associated token account - sender: new PublicKey(wallet.getAddress()!), //sender, associated token account owner, - tokenBridgeConfig, - tokenBridgeWrappedAsset, - tokenBridgeTransferAuthority, - coreBridgeData, - coreMessage, - tokenBridgeCoreEmitter, - coreEmitterSequence, - coreFeeCollector, - clock: SYSVAR_CLOCK_PUBKEY, - tokenBridgeSender, - rent: SYSVAR_RENT_PUBKEY, - tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, - coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, - } - return sendTbtcGateway(program, args, gatewayAccounts); + const gatewayAccounts = { + custodian, + gatewayInfo, + wrappedTbtcToken, + wrappedTbtcMint, + tbtcMint, + senderToken: associatedTokenAccount, // associated token account + sender: new PublicKey(wallet.getAddress()!), //sender, associated token account owner, + tokenBridgeConfig, + tokenBridgeWrappedAsset, + tokenBridgeTransferAuthority, + coreBridgeData, + coreMessage, + tokenBridgeCoreEmitter, + coreEmitterSequence, + coreFeeCollector, + clock: SYSVAR_CLOCK_PUBKEY, + tokenBridgeSender, + rent: SYSVAR_RENT_PUBKEY, + tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, + coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, + }; + return sendTbtcGateway(program, args, gatewayAccounts); } }; return { diff --git a/src/hooks/useCheckIfWormholeWrapped.ts b/src/hooks/useCheckIfWormholeWrapped.ts index 81b0dd862..f102ae467 100644 --- a/src/hooks/useCheckIfWormholeWrapped.ts +++ b/src/hooks/useCheckIfWormholeWrapped.ts @@ -57,7 +57,6 @@ import { XPLA_LCD_CLIENT_CONFIG, THRESHOLD_TBTC_CONTRACTS, TBTC_ASSET_ADDRESS, - THRESHOLD_TBTC_SOLANA_MINT_TESTNET, } from "../utils/consts"; import { getOriginalAssetNear, makeNearAccount } from "../utils/near"; import { LCDClient as XplaLCDClient } from "@xpla/xpla.js"; @@ -154,7 +153,7 @@ function useCheckIfWormholeWrapped(nft?: boolean) { try { // Check if is tBtc canonical on Solana // TODO improve the check and centralice the login on just one place - if (THRESHOLD_TBTC_SOLANA_MINT_TESTNET === sourceAsset) { + if (THRESHOLD_TBTC_CONTRACTS[sourceChain] === sourceAsset) { console.log("selected tBTC on canonical chain"); dispatch( setSourceWormholeWrappedInfo({ @@ -182,7 +181,7 @@ function useCheckIfWormholeWrapped(nft?: boolean) { if (!cancelled) { dispatch(setSourceWormholeWrappedInfo(wrappedInfo)); } - } + } } catch (e) {} } if (isTerraChain(sourceChain) && sourceAsset) { diff --git a/src/utils/consts.ts b/src/utils/consts.ts index 3c1842ce9..b3ce66d9c 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -446,11 +446,17 @@ const THRESHOLD_TBTC_CONTRACTS_TESTNET: any = { [CHAIN_ID_SOLANA]: "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU", // Solana TBTC Mint } as const; -export const THRESHOLD_TBTC_SOLANA_MINT_TESTNET = - "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU"; -export const THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET = +const THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET = "Gj93RRt6QB7FjmyokAD5rcMAku7pq3Fk2Aa8y6nNbwsV"; +// TODO update this when mainnet tbtc solana program is deployed +const THRESHOLD_TBTC_SOLANA_PROGRAM_MAINNET = ""; + +export const THRESHOLD_TBTC_SOLANA_PROGRAM: any = + CLUSTER === "mainnet" + ? THRESHOLD_TBTC_SOLANA_PROGRAM_MAINNET + : THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET; + export const THRESHOLD_TBTC_CONTRACTS: any = { ...(CLUSTER === "mainnet" ? THRESHOLD_TBTC_CONTRACTS_MAINNET From c3744a7f0aef94269e95ddea67fcdf96d33ef8f2 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 22:20:25 -0300 Subject: [PATCH 14/19] initialize associate token account if not exist --- .../tbtc/solana/WormholeGateway.v2.ts | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index 389bb99b8..27163c4b1 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -217,23 +217,26 @@ export function newThresholdWormholeGateway( const wrappedTbtcMint = new PublicKey( custodianData.wrappedTbtcMint as string ); - const recipientToken = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - tbtcMint, - recipient - ); -/* - const recipientTokenAta = Token.createAssociatedTokenAccountInstruction( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - tbtcMint, - recipientToken, - recipient, // owner - recipient // payer + const recipientTokenKey = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipient ); - console.log(recipientToken); -*/ + const transaction = new Transaction(); + const recipientToken = await connection.getAccountInfo(recipientTokenKey); + console.log(recipientToken, recipientTokenKey.toBase58()); + if (!recipientToken) { + const recipientTokenAtaIx = Token.createAssociatedTokenAccountInstruction( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipientTokenKey, + recipient, // owner + recipient // payer + ); + transaction.add(recipientTokenAtaIx); + } const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey( TOKEN_BRIDGE_PROGRAM_ID, wrappedTbtcMint @@ -260,7 +263,7 @@ export function newThresholdWormholeGateway( wrappedTbtcToken, wrappedTbtcMint, tbtcMint, - recipientToken, + recipientToken: recipientTokenKey, recipient, recipientWrappedToken, tbtcConfig: getConfigPDA(), @@ -282,11 +285,11 @@ export function newThresholdWormholeGateway( tokenBridgeProgram: TOKEN_BRIDGE_PROGRAM_ID, coreBridgeProgram: CORE_BRIDGE_PROGRAM_ID, }; - const tx = await program.methods + const receiveTbtcIx = await program.methods .receiveTbtc(parsed.hash) .accounts(accounts) - .transaction(); - return tx; + .instruction(); + return transaction.add(receiveTbtcIx); }; const sendTbtc = async ( From bf2ffcf0d895759068c0078ff31fd1e41bd72ebf Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Fri, 11 Aug 2023 23:43:16 -0300 Subject: [PATCH 15/19] call the gateway if the asset is tBtc v2 --- src/components/TokenSelectors/EvmTokenPicker.tsx | 9 +++++++-- src/hooks/useHandleTransfer.tsx | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/TokenSelectors/EvmTokenPicker.tsx b/src/components/TokenSelectors/EvmTokenPicker.tsx index fc312354e..f59f86481 100644 --- a/src/components/TokenSelectors/EvmTokenPicker.tsx +++ b/src/components/TokenSelectors/EvmTokenPicker.tsx @@ -96,8 +96,13 @@ export default function EvmTokenPicker( * Select as target eth * Sent the tokens, and hit transfer more tokens button */ - const assetMap = getMigrationAssetMap(chainId); - return !!assetMap.get(getEthAddress(address)); + try { + const assetMap = getMigrationAssetMap(chainId); + return !!assetMap.get(getEthAddress(address)); + } catch (e) { + console.error(e); + return false; + } }, [chainId] ); diff --git a/src/hooks/useHandleTransfer.tsx b/src/hooks/useHandleTransfer.tsx index cccb98bb1..1c4c62740 100644 --- a/src/hooks/useHandleTransfer.tsx +++ b/src/hooks/useHandleTransfer.tsx @@ -601,7 +601,7 @@ async function solana( ? zeroPad(hexToUint8Array(originAddressStr), 32) : undefined; - if (THRESHOLD_TBTC_CONTRACTS[originChain!] === mintAddress) { + if (THRESHOLD_TBTC_CONTRACTS[CHAIN_ID_SOLANA] === mintAddress) { const wormholeGateway = newThresholdWormholeGateway(connection, wallet); const transaction = await wormholeGateway.sendTbtc( transferAmountParsed.toBigInt(), From 66bc3591c2566362499fd85145737c72a1a2d022 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Sat, 12 Aug 2023 00:25:11 -0300 Subject: [PATCH 16/19] [skip ci] - run prettier --- .../tbtc/solana/WormholeGateway.v2.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts index 27163c4b1..b111b648c 100644 --- a/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts +++ b/src/assets/providers/tbtc/solana/WormholeGateway.v2.ts @@ -218,24 +218,24 @@ export function newThresholdWormholeGateway( custodianData.wrappedTbtcMint as string ); const recipientTokenKey = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - tbtcMint, - recipient + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipient ); const transaction = new Transaction(); const recipientToken = await connection.getAccountInfo(recipientTokenKey); console.log(recipientToken, recipientTokenKey.toBase58()); if (!recipientToken) { - const recipientTokenAtaIx = Token.createAssociatedTokenAccountInstruction( - ASSOCIATED_TOKEN_PROGRAM_ID, - TOKEN_PROGRAM_ID, - tbtcMint, - recipientTokenKey, - recipient, // owner - recipient // payer - ); - transaction.add(recipientTokenAtaIx); + const recipientTokenAtaIx = Token.createAssociatedTokenAccountInstruction( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + tbtcMint, + recipientTokenKey, + recipient, // owner + recipient // payer + ); + transaction.add(recipientTokenAtaIx); } const tokenBridgeWrappedAsset = tokenBridge.deriveWrappedMetaKey( TOKEN_BRIDGE_PROGRAM_ID, From 76823fe5def8fd9100a07810962304365b67eafa Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Sat, 12 Aug 2023 07:43:01 -0300 Subject: [PATCH 17/19] add mainnet address --- src/utils/consts.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/consts.ts b/src/utils/consts.ts index b3ce66d9c..ffa149ee6 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -413,6 +413,7 @@ const THRESHOLD_GATEWAYS_MAINNET: any = { [CHAIN_ID_OPTIMISM]: "0x1293a54e160D1cd7075487898d65266081A15458", [CHAIN_ID_ARBITRUM]: "0x1293a54e160D1cd7075487898d65266081A15458", [CHAIN_ID_BASE]: "0x09959798B95d00a3183d20FaC298E4594E599eab", + [CHAIN_ID_SOLANA]: "87MEvHZCXE3ML5rrmh5uX1FbShHmRXXS32xJDGbQ7h5t", // Solana TBTC Gateway Program } as const; const THRESHOLD_GATEWAYS_TESTNET: any = { @@ -435,6 +436,7 @@ const THRESHOLD_TBTC_CONTRACTS_MAINNET: any = { [CHAIN_ID_OPTIMISM]: "0x6c84a8f1c29108F47a79964b5Fe888D4f4D0dE40", [CHAIN_ID_ARBITRUM]: "0x6c84a8f1c29108F47a79964b5Fe888D4f4D0dE40", [CHAIN_ID_BASE]: "0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b", + [CHAIN_ID_SOLANA]: "6DNSN2BJsaPFdFFc1zP37kkeNe4Usc1Sqkzr9C9vPWcU", // Solana TBTC Mint } as const; const THRESHOLD_TBTC_CONTRACTS_TESTNET: any = { @@ -450,7 +452,8 @@ const THRESHOLD_TBTC_SOLANA_PROGRAM_TESTNET = "Gj93RRt6QB7FjmyokAD5rcMAku7pq3Fk2Aa8y6nNbwsV"; // TODO update this when mainnet tbtc solana program is deployed -const THRESHOLD_TBTC_SOLANA_PROGRAM_MAINNET = ""; +const THRESHOLD_TBTC_SOLANA_PROGRAM_MAINNET = + "Gj93RRt6QB7FjmyokAD5rcMAku7pq3Fk2Aa8y6nNbwsV"; export const THRESHOLD_TBTC_SOLANA_PROGRAM: any = CLUSTER === "mainnet" From 1772ba49d1ea80ffc0f9e0509a9805c76b3eb5c1 Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Sat, 12 Aug 2023 17:09:09 -0300 Subject: [PATCH 18/19] [skip ci] - bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 85207c0d5..08c06afbc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@xlabs/portal-bridge-ui", - "version": "0.1.61", + "version": "0.1.62", "private": true, "dependencies": { "@certusone/wormhole-sdk": "^0.9.22", From ff9baa2ca68e5957d9b8f73861da802536a12ebc Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Sat, 12 Aug 2023 23:06:01 -0300 Subject: [PATCH 19/19] code review observation --- src/hooks/useHandleRedeem.tsx | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/hooks/useHandleRedeem.tsx b/src/hooks/useHandleRedeem.tsx index df336b0e1..92be801dc 100644 --- a/src/hooks/useHandleRedeem.tsx +++ b/src/hooks/useHandleRedeem.tsx @@ -352,19 +352,19 @@ async function solana( throw new Error("wallet.signTransaction is undefined"); } const connection = new Connection(SOLANA_HOST, "confirmed"); + // TODO compute the amount of tx that postVaaSolanaWithRetry + // will create to notice the user up front + // we could call createPostSignedVaaTransactions to create fake txs + // and read the length of the array + await postVaaSolanaWithRetry( + connection, + wallet.signTransaction.bind(wallet), + SOL_BRIDGE_ADDRESS, + payerAddress, + Buffer.from(signedVAA), + MAX_VAA_UPLOAD_RETRIES_SOLANA + ); if (isTbtc) { - // TODO compute the amount of tx that postVaaSolanaWithRetry - // will create to notice the user up front - // we could call createPostSignedVaaTransactions to create fake txs - // and read the length of the array - await postVaaSolanaWithRetry( - connection, - wallet.signTransaction.bind(wallet), - SOL_BRIDGE_ADDRESS, - payerAddress, - Buffer.from(signedVAA), - MAX_VAA_UPLOAD_RETRIES_SOLANA - ); const tbtcGateway = newThresholdWormholeGateway(connection, wallet); const transaction = await tbtcGateway.receiveTbtc( signedVAA, @@ -376,14 +376,6 @@ async function solana( content: Transaction confirmed, }); } else { - await postVaaSolanaWithRetry( - connection, - wallet.signTransaction.bind(wallet), - SOL_BRIDGE_ADDRESS, - payerAddress, - Buffer.from(signedVAA), - MAX_VAA_UPLOAD_RETRIES_SOLANA - ); // TODO: how do we retry in between these steps const transaction = isNative ? await redeemAndUnwrapOnSolana(