@wormhole-foundation/sdk-solana-tbtc
Version:
SDK for Solana chains, used in conjunction with @wormhole-foundation/sdk
182 lines • 9.46 kB
JavaScript
"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