@wormhole-foundation/sdk-solana-cctp
Version:
SDK for Solana, used in conjunction with @wormhole-foundation/sdk
126 lines • 7.24 kB
JavaScript
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
;