UNPKG

@wormhole-foundation/sdk-solana-cctp

Version:

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

126 lines 7.24 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SolanaCircleBridge = void 0; const web3_js_1 = require("@solana/web3.js"); const sdk_connect_1 = require("@wormhole-foundation/sdk-connect"); const bn_js_1 = __importDefault(require("bn.js")); const spl_token_1 = require("@solana/spl-token"); const sdk_solana_1 = require("@wormhole-foundation/sdk-solana"); const index_js_1 = require("./utils/index.js"); const index_js_2 = require("./utils/instructions/index.js"); class SolanaCircleBridge { network; chain; connection; contracts; tokenMessenger; messageTransmitter; constructor(network, chain, connection, contracts) { this.network = network; this.chain = chain; this.connection = connection; this.contracts = contracts; if (network === 'Devnet') throw new Error('CircleBridge not supported on Devnet'); const msgTransmitterAddress = contracts.cctp?.messageTransmitter; if (!msgTransmitterAddress) throw new Error(`Circle Messenge Transmitter contract for domain ${chain} not found`); this.messageTransmitter = (0, index_js_1.createReadOnlyMessageTransmitterProgramInterface)(new web3_js_1.PublicKey(msgTransmitterAddress), this.connection); const tokenMessengerAddress = contracts.cctp?.tokenMessenger; if (!tokenMessengerAddress) throw new Error(`Circle Token Messenger contract for domain ${chain} not found`); this.tokenMessenger = (0, index_js_1.createReadOnlyTokenMessengerProgramInterface)(new web3_js_1.PublicKey(tokenMessengerAddress), this.connection); } static async fromRpc(provider, config) { const [network, chain] = await sdk_solana_1.SolanaPlatform.chainFromRpc(provider); const conf = config[chain]; if (conf.network !== network) throw new Error(`Network mismatch: ${conf.network} != ${network}`); return new SolanaCircleBridge(network, chain, provider, conf.contracts); } async *redeem(sender, message, attestation) { const usdc = new web3_js_1.PublicKey(sdk_connect_1.circle.usdcContract.get(this.network, this.chain)); const senderPk = new sdk_solana_1.SolanaAddress(sender).unwrap(); // If the ATA doesn't exist then create it const mintRecipient = new sdk_solana_1.SolanaAddress(message.payload.mintRecipient).unwrap(); const ata = await this.connection.getAccountInfo(mintRecipient); if (!ata) { const transaction = new web3_js_1.Transaction().add((0, spl_token_1.createAssociatedTokenAccountInstruction)(senderPk, mintRecipient, senderPk, usdc)); transaction.feePayer = senderPk; yield this.createUnsignedTx({ transaction }, 'CircleBridge.CreateATA'); } const ix = await (0, index_js_2.createReceiveMessageInstruction)(this.messageTransmitter.programId, this.tokenMessenger.programId, usdc, message, attestation, senderPk); const transaction = new web3_js_1.Transaction(); transaction.feePayer = senderPk; transaction.add(ix); yield this.createUnsignedTx({ transaction }, 'CircleBridge.Redeem'); } async *transfer(sender, recipient, amount) { const usdc = new web3_js_1.PublicKey(sdk_connect_1.circle.usdcContract.get(this.network, this.chain)); const senderPk = new sdk_solana_1.SolanaAddress(sender).unwrap(); const senderATA = (0, spl_token_1.getAssociatedTokenAddressSync)(usdc, senderPk); const destinationDomain = sdk_connect_1.circle.circleChainId.get(this.network, recipient.chain); const destinationAddress = recipient.address.toUniversalAddress(); const msgSndEvnet = web3_js_1.Keypair.generate(); const ix = await (0, index_js_2.createDepositForBurnInstruction)(this.messageTransmitter.programId, this.tokenMessenger.programId, usdc, destinationDomain, senderPk, senderATA, destinationAddress, amount, msgSndEvnet.publicKey); const transaction = new web3_js_1.Transaction(); transaction.feePayer = senderPk; transaction.add(ix); yield this.createUnsignedTx({ transaction, signers: [msgSndEvnet] }, 'CircleBridge.Transfer'); } async isTransferCompleted(message) { const usedNoncesAddress = (0, index_js_2.nonceAccount)(message.nonce, message.sourceDomain, this.messageTransmitter.programId); const firstNonce = (0, index_js_2.calculateFirstNonce)(message.nonce); // usedNonces should be a [u64;100] where each bit is a nonce flag const { usedNonces } = await this.messageTransmitter.account.usedNonces.fetch(usedNoncesAddress); // get the nonce index based on the account's first nonce const nonceIndex = Number(message.nonce - firstNonce); // get the the u64 the nonce's flag is in const nonceElement = usedNonces[Math.floor(nonceIndex / 64)]; if (!nonceElement) throw new Error('Invalid nonce byte index'); // get the nonce flag index and build a bitmask const nonceBitIndex = nonceIndex % 64; // NOTE: js does not correctly handle large bitshifts, leave these as bigint wrapped const mask = new bn_js_1.default((BigInt(1) << BigInt(nonceBitIndex)).toString()); return !nonceElement.and(mask).isZero(); } // Fetch the transaction logs and parse the CircleTransferMessage async parseTransactionDetails(txid) { const tx = await this.connection.getTransaction(txid); if (!tx || !tx.meta) throw new Error('Transaction not found'); const acctKeys = tx.transaction.message.getAccountKeys(); if (acctKeys.length < 2) throw new Error('No message account found'); const msgSendAccount = acctKeys.get(1); const accountData = await this.connection.getAccountInfo(msgSendAccount); if (!accountData) throw new Error('No account data found'); // TODO: why 44? const message = new Uint8Array(accountData.data).slice(44); const [msg, hash] = sdk_connect_1.CircleBridge.deserialize(message); const { payload: body } = msg; const xferSender = body.messageSender; const xferReceiver = body.mintRecipient; const sendChain = sdk_connect_1.circle.toCircleChain(this.network, msg.sourceDomain); const rcvChain = sdk_connect_1.circle.toCircleChain(this.network, msg.destinationDomain); const token = { chain: sendChain, address: body.burnToken }; return { from: { chain: sendChain, address: xferSender }, to: { chain: rcvChain, address: xferReceiver }, token: token, amount: body.amount, message: msg, id: { hash }, }; } createUnsignedTx(txReq, description, parallelizable = false) { return new sdk_solana_1.SolanaUnsignedTransaction(txReq, this.network, this.chain, description, parallelizable); } } exports.SolanaCircleBridge = SolanaCircleBridge; //# sourceMappingURL=circleBridge.js.map