UNPKG

@wormhole-foundation/sdk-solana-tbtc

Version:

SDK for Solana chains, used in conjunction with @wormhole-foundation/sdk

182 lines 9.46 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SolanaTBTCBridge = void 0; const sdk_connect_1 = require("@wormhole-foundation/sdk-connect"); const sdk_solana_1 = require("@wormhole-foundation/sdk-solana"); const sdk_solana_core_1 = require("@wormhole-foundation/sdk-solana-core"); const sdk_solana_tokenbridge_1 = require("@wormhole-foundation/sdk-solana-tokenbridge"); const web3_js_1 = require("@solana/web3.js"); const anchor_1 = require("@coral-xyz/anchor"); const gateway_js_1 = require("./anchor-idl/gateway.js"); const utils_js_1 = require("./utils.js"); const spl_token_1 = require("@solana/spl-token"); const bn_js_1 = require("bn.js"); class SolanaTBTCBridge { network; chain; connection; contracts; gateway; tokenBridgeId; coreBridgeId; constructor(network, chain, connection, contracts) { this.network = network; this.chain = chain; this.connection = connection; this.contracts = contracts; if (this.network !== 'Mainnet') { throw new Error('TBTC is only supported on Mainnet'); } if (!this.contracts.tbtc) { throw new Error('TBTC contract address is required'); } if (!this.contracts.tokenBridge) { throw new Error('TokenBridge contract address is required'); } if (!this.contracts.coreBridge) { throw new Error('CoreBridge contract address is required'); } this.gateway = new anchor_1.Program(gateway_js_1.WormholeGatewayIdl, this.contracts.tbtc, { connection, }); this.tokenBridgeId = new web3_js_1.PublicKey(this.contracts.tokenBridge); this.coreBridgeId = new web3_js_1.PublicKey(this.contracts.coreBridge); } static async fromRpc(connection, config) { const [network, chain] = await sdk_solana_1.SolanaPlatform.chainFromRpc(connection); const conf = config[chain]; if (conf.network !== network) throw new Error(`Network mismatch: ${conf.network} != ${network}`); return new SolanaTBTCBridge(network, chain, connection, conf.contracts); } async *transfer(sender, recipient, amount) { const senderPk = new sdk_solana_1.SolanaAddress(sender).unwrap(); const custodian = (0, utils_js_1.getCustodianPda)(this.gateway.programId); const { tbtcMint, wrappedTbtcToken, wrappedTbtcMint } = await this.gateway.account.custodian.fetch(custodian); const tokenBridgeWrappedAsset = (0, sdk_solana_tokenbridge_1.deriveWrappedMetaKey)(this.tokenBridgeId, wrappedTbtcMint); const tokenBridgeConfig = (0, sdk_solana_tokenbridge_1.deriveTokenBridgeConfigKey)(this.tokenBridgeId); const tokenBridgeTransferAuthority = (0, sdk_solana_tokenbridge_1.deriveAuthoritySignerKey)(this.tokenBridgeId); const coreFeeCollector = sdk_solana_core_1.utils.deriveFeeCollectorKey(this.coreBridgeId); // NOTE: There is a race condition where the sequence changes // while the transaction is in flight. This would cause the // transaction to fail and the user would have to retry. const { sequence } = await sdk_solana_core_1.utils.getProgramSequenceTracker(this.connection, this.tokenBridgeId, this.coreBridgeId); const coreMessage = (0, utils_js_1.getCoreMessagePda)(this.gateway.programId, sequence); const coreBridgeData = sdk_solana_core_1.utils.deriveWormholeBridgeDataKey(this.coreBridgeId); const tokenBridgeCoreEmitter = sdk_solana_core_1.utils.deriveWormholeEmitterKey(this.tokenBridgeId); const coreEmitterSequence = sdk_solana_core_1.utils.deriveEmitterSequenceKey(tokenBridgeCoreEmitter, this.coreBridgeId); const gatewayInfo = (0, utils_js_1.getGatewayInfoPda)(this.gateway.programId, recipient.chain); const tokenBridgeSender = (0, sdk_solana_tokenbridge_1.deriveSenderAccountKey)(this.gateway.programId); const args = { amount: new bn_js_1.BN(amount.toString()), recipientChain: (0, sdk_connect_1.toChainId)(recipient.chain), recipient: [...recipient.address.toUniversalAddress().toUint8Array()], nonce: 0, }; const ata = await (0, spl_token_1.getAssociatedTokenAddress)(tbtcMint, senderPk); const toGateway = sdk_connect_1.contracts.tbtc.get(this.network, recipient.chain); const commonAccounts = { custodian, wrappedTbtcToken, wrappedTbtcMint, tbtcMint, senderToken: ata, sender: senderPk, tokenBridgeConfig, tokenBridgeWrappedAsset, tokenBridgeTransferAuthority, coreBridgeData, coreMessage, tokenBridgeCoreEmitter, coreEmitterSequence, coreFeeCollector, clock: web3_js_1.SYSVAR_CLOCK_PUBKEY, rent: web3_js_1.SYSVAR_RENT_PUBKEY, tokenBridgeProgram: this.tokenBridgeId, coreBridgeProgram: this.coreBridgeId, }; const ix = toGateway ? await this.gateway.methods .sendTbtcGateway({ ...args }) .accounts({ ...commonAccounts, gatewayInfo, tokenBridgeSender, }) .instruction() : await this.gateway.methods .sendTbtcWrapped({ ...args, arbiterFee: new bn_js_1.BN(0), }) .accounts(commonAccounts) .instruction(); const { blockhash } = await this.connection.getLatestBlockhash(); const messageV0 = web3_js_1.MessageV0.compile({ instructions: [ix], payerKey: senderPk, recentBlockhash: blockhash, }); const transaction = new web3_js_1.VersionedTransaction(messageV0); yield this.createUnsignedTransaction({ transaction }, 'TBTCBridge.Send'); } async *redeem(sender, vaa) { if (vaa.payloadName !== 'GatewayTransfer') { throw new Error('Invalid VAA payload'); } const core = new sdk_solana_core_1.SolanaWormholeCore(this.network, this.chain, this.connection, this.contracts); yield* core.postVaa(sender, vaa); const instructions = []; const senderPk = new sdk_solana_1.SolanaAddress(sender).unwrap(); const recipientPk = vaa.payload.payload.recipient .toNative(this.chain) .unwrap(); const custodian = (0, utils_js_1.getCustodianPda)(this.gateway.programId); const { tbtcMint, wrappedTbtcToken, wrappedTbtcMint } = await this.gateway.account.custodian.fetch(custodian); const ata = await (0, spl_token_1.getAssociatedTokenAddress)(tbtcMint, recipientPk); const ataExists = await this.connection.getAccountInfo(ata); if (!ataExists) { instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(senderPk, ata, recipientPk, tbtcMint)); } const tokenBridgeWrappedAsset = (0, sdk_solana_tokenbridge_1.deriveWrappedMetaKey)(this.tokenBridgeId, wrappedTbtcMint); const wrappedTokenAta = await (0, spl_token_1.getAssociatedTokenAddress)(wrappedTbtcMint, recipientPk); instructions.push(await this.gateway.methods .receiveTbtc([...vaa.hash]) .accounts({ payer: senderPk, custodian, postedVaa: sdk_solana_core_1.utils.derivePostedVaaKey(this.coreBridgeId, Buffer.from(vaa.hash)), tokenBridgeClaim: sdk_solana_core_1.utils.deriveClaimKey(this.tokenBridgeId, vaa.emitterAddress.toUint8Array(), (0, sdk_connect_1.toChainId)(vaa.emitterChain), vaa.sequence), wrappedTbtcToken, wrappedTbtcMint, tbtcMint, recipientToken: ata, recipient: recipientPk, recipientWrappedToken: wrappedTokenAta, tbtcConfig: (0, utils_js_1.getConfigPda)(), tbtcMinterInfo: (0, utils_js_1.getMinterInfoPda)(custodian), tokenBridgeConfig: (0, sdk_solana_tokenbridge_1.deriveTokenBridgeConfigKey)(this.tokenBridgeId), tokenBridgeRegisteredEmitter: (0, sdk_solana_tokenbridge_1.deriveEndpointKey)(this.tokenBridgeId, (0, sdk_connect_1.toChainId)(vaa.emitterChain), vaa.emitterAddress.toUint8Array()), tokenBridgeWrappedAsset, tokenBridgeMintAuthority: (0, sdk_solana_tokenbridge_1.deriveMintAuthorityKey)(this.tokenBridgeId), rent: web3_js_1.SYSVAR_RENT_PUBKEY, tbtcProgram: utils_js_1.TBTC_PROGRAM_ID, tokenBridgeProgram: this.tokenBridgeId, coreBridgeProgram: this.coreBridgeId, }) .instruction()); const { blockhash } = await this.connection.getLatestBlockhash(); const messageV0 = web3_js_1.MessageV0.compile({ instructions, payerKey: senderPk, recentBlockhash: blockhash, }); const transaction = new web3_js_1.VersionedTransaction(messageV0); yield this.createUnsignedTransaction({ transaction }, 'TBTCBridge.Send'); } createUnsignedTransaction(txReq, description) { return new sdk_solana_1.SolanaUnsignedTransaction(txReq, this.network, this.chain, description, false); } } exports.SolanaTBTCBridge = SolanaTBTCBridge; //# sourceMappingURL=bridge.js.map