UNPKG

@wormhole-foundation/sdk-solana-tbtc

Version:

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

178 lines 8.93 kB
import { contracts, toChainId, } from '@wormhole-foundation/sdk-connect'; import { SolanaAddress, SolanaPlatform, SolanaUnsignedTransaction, } from '@wormhole-foundation/sdk-solana'; import { utils as coreUtils, SolanaWormholeCore, } from '@wormhole-foundation/sdk-solana-core'; import { deriveAuthoritySignerKey, deriveEndpointKey, deriveMintAuthorityKey, deriveSenderAccountKey, deriveTokenBridgeConfigKey, deriveWrappedMetaKey, } from '@wormhole-foundation/sdk-solana-tokenbridge'; import { MessageV0, PublicKey, SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY, VersionedTransaction, } from '@solana/web3.js'; import { Program } from '@coral-xyz/anchor'; import { WormholeGatewayIdl } from './anchor-idl/gateway.js'; import { getConfigPda, getCoreMessagePda, getCustodianPda, getGatewayInfoPda, getMinterInfoPda, TBTC_PROGRAM_ID, } from './utils.js'; import { createAssociatedTokenAccountInstruction, getAssociatedTokenAddress, } from '@solana/spl-token'; import { BN } from 'bn.js'; export 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 Program(WormholeGatewayIdl, this.contracts.tbtc, { connection, }); this.tokenBridgeId = new PublicKey(this.contracts.tokenBridge); this.coreBridgeId = new PublicKey(this.contracts.coreBridge); } static async fromRpc(connection, config) { const [network, chain] = await 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 SolanaAddress(sender).unwrap(); const custodian = getCustodianPda(this.gateway.programId); const { tbtcMint, wrappedTbtcToken, wrappedTbtcMint } = await this.gateway.account.custodian.fetch(custodian); const tokenBridgeWrappedAsset = deriveWrappedMetaKey(this.tokenBridgeId, wrappedTbtcMint); const tokenBridgeConfig = deriveTokenBridgeConfigKey(this.tokenBridgeId); const tokenBridgeTransferAuthority = deriveAuthoritySignerKey(this.tokenBridgeId); const coreFeeCollector = coreUtils.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 coreUtils.getProgramSequenceTracker(this.connection, this.tokenBridgeId, this.coreBridgeId); const coreMessage = getCoreMessagePda(this.gateway.programId, sequence); const coreBridgeData = coreUtils.deriveWormholeBridgeDataKey(this.coreBridgeId); const tokenBridgeCoreEmitter = coreUtils.deriveWormholeEmitterKey(this.tokenBridgeId); const coreEmitterSequence = coreUtils.deriveEmitterSequenceKey(tokenBridgeCoreEmitter, this.coreBridgeId); const gatewayInfo = getGatewayInfoPda(this.gateway.programId, recipient.chain); const tokenBridgeSender = deriveSenderAccountKey(this.gateway.programId); const args = { amount: new BN(amount.toString()), recipientChain: toChainId(recipient.chain), recipient: [...recipient.address.toUniversalAddress().toUint8Array()], nonce: 0, }; const ata = await getAssociatedTokenAddress(tbtcMint, senderPk); const toGateway = 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: SYSVAR_CLOCK_PUBKEY, rent: 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(0), }) .accounts(commonAccounts) .instruction(); const { blockhash } = await this.connection.getLatestBlockhash(); const messageV0 = MessageV0.compile({ instructions: [ix], payerKey: senderPk, recentBlockhash: blockhash, }); const transaction = new 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 SolanaWormholeCore(this.network, this.chain, this.connection, this.contracts); yield* core.postVaa(sender, vaa); const instructions = []; const senderPk = new SolanaAddress(sender).unwrap(); const recipientPk = vaa.payload.payload.recipient .toNative(this.chain) .unwrap(); const custodian = getCustodianPda(this.gateway.programId); const { tbtcMint, wrappedTbtcToken, wrappedTbtcMint } = await this.gateway.account.custodian.fetch(custodian); const ata = await getAssociatedTokenAddress(tbtcMint, recipientPk); const ataExists = await this.connection.getAccountInfo(ata); if (!ataExists) { instructions.push(createAssociatedTokenAccountInstruction(senderPk, ata, recipientPk, tbtcMint)); } const tokenBridgeWrappedAsset = deriveWrappedMetaKey(this.tokenBridgeId, wrappedTbtcMint); const wrappedTokenAta = await getAssociatedTokenAddress(wrappedTbtcMint, recipientPk); instructions.push(await this.gateway.methods .receiveTbtc([...vaa.hash]) .accounts({ payer: senderPk, custodian, postedVaa: coreUtils.derivePostedVaaKey(this.coreBridgeId, Buffer.from(vaa.hash)), tokenBridgeClaim: coreUtils.deriveClaimKey(this.tokenBridgeId, vaa.emitterAddress.toUint8Array(), toChainId(vaa.emitterChain), vaa.sequence), wrappedTbtcToken, wrappedTbtcMint, tbtcMint, recipientToken: ata, recipient: recipientPk, recipientWrappedToken: wrappedTokenAta, tbtcConfig: getConfigPda(), tbtcMinterInfo: getMinterInfoPda(custodian), tokenBridgeConfig: deriveTokenBridgeConfigKey(this.tokenBridgeId), tokenBridgeRegisteredEmitter: deriveEndpointKey(this.tokenBridgeId, toChainId(vaa.emitterChain), vaa.emitterAddress.toUint8Array()), tokenBridgeWrappedAsset, tokenBridgeMintAuthority: deriveMintAuthorityKey(this.tokenBridgeId), rent: SYSVAR_RENT_PUBKEY, tbtcProgram: TBTC_PROGRAM_ID, tokenBridgeProgram: this.tokenBridgeId, coreBridgeProgram: this.coreBridgeId, }) .instruction()); const { blockhash } = await this.connection.getLatestBlockhash(); const messageV0 = MessageV0.compile({ instructions, payerKey: senderPk, recentBlockhash: blockhash, }); const transaction = new VersionedTransaction(messageV0); yield this.createUnsignedTransaction({ transaction }, 'TBTCBridge.Send'); } createUnsignedTransaction(txReq, description) { return new SolanaUnsignedTransaction(txReq, this.network, this.chain, description, false); } } //# sourceMappingURL=bridge.js.map