@locker-labs/sdk
Version:
A utility library for enhancing your ERC-4337 experience
106 lines • 5.36 kB
JavaScript
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { hexToBytes, keccak256, toHex, encodeAbiParameters } from 'viem';
import * as spl from '@solana/spl-token';
import * as anchor from "@coral-xyz/anchor";
import { CCTP_DOMAIN_IDS, CIRCLE_CONFIG } from './cctpConstants.js';
import { evmAddressToBytes32, findOrCreateUserTokenAccount, getDepositForBurnPdas, getMessages, getPrograms, getMessageTransmitterFromChain } from './cctpUtils.js';
import { USDC } from '../../../accounts/tokens.js';
/**
* Bridges tokens from Solana -> EVM using Circle's CCTP.
*/
export async function cctpBridgeTokenFromSolana(params) {
const { solanaSigner, solanaTokenAddress, amount, recipientAddress, recipientChain, solanaChain, solanaRpcUrl } = params;
const { irisApiUrl, } = CIRCLE_CONFIG[solanaChain];
// CCTP has bespoke domain IDs for each chain. These do not correspond to EVM
const destinationDomainId = CCTP_DOMAIN_IDS[recipientChain];
// SPL token account for the user
const userTokenAccount = await findOrCreateUserTokenAccount(params);
const expectedTokenAddress = USDC[solanaChain];
if (solanaTokenAddress !== expectedTokenAddress) {
throw new Error(`Cannot bridge token ${solanaTokenAddress} with CCTP in on ${solanaChain}. Expected USDC at ${expectedTokenAddress}.`);
}
// Create a new Provider based on the signer's Keypair
const connection = new Connection(solanaRpcUrl);
const provider = new anchor.AnchorProvider(connection, new anchor.Wallet(solanaSigner), {});
anchor.setProvider(provider);
const { messageTransmitterProgram, tokenMessengerMinterProgram } = getPrograms(provider);
// Convert the EVM address into a 32-byte format
const evmRecipientBytes32 = evmAddressToBytes32(recipientAddress);
const mintRecipient = new PublicKey(hexToBytes(evmRecipientBytes32));
// Get PDAs
const usdcPublicKey = new PublicKey(solanaTokenAddress);
const pdas = getDepositForBurnPdas({ messageTransmitterProgram, tokenMessengerMinterProgram }, usdcPublicKey, destinationDomainId);
// Generate a new keypair for the MessageSent event account.
const messageSentEventAccountKeypair = Keypair.generate();
// Anchor RPC call
console.log('Depositing for burn...');
const depositForBurnTx = await tokenMessengerMinterProgram.methods
.depositForBurn({
amount: new anchor.BN(amount),
destinationDomain: destinationDomainId,
mintRecipient,
})
.accounts({
owner: provider.wallet.publicKey,
eventRentPayer: provider.wallet.publicKey,
senderAuthorityPda: pdas.authorityPda.publicKey,
burnTokenAccount: userTokenAccount,
messageTransmitter: pdas.messageTransmitterAccount.publicKey,
tokenMessenger: pdas.tokenMessengerAccount.publicKey,
remoteTokenMessenger: pdas.remoteTokenMessengerKey.publicKey,
tokenMinter: pdas.tokenMinterAccount.publicKey,
localToken: pdas.localToken.publicKey,
burnTokenMint: usdcPublicKey,
messageTransmitterProgram: messageTransmitterProgram.programId,
tokenMessengerMinterProgram: tokenMessengerMinterProgram.programId,
messageSentEventData: messageSentEventAccountKeypair.publicKey,
tokenProgram: spl.TOKEN_PROGRAM_ID,
})
.signers([messageSentEventAccountKeypair])
.rpc();
// Fetch attestation from the Attestation Service
console.log(`Fetching attestation for tx ${depositForBurnTx}`);
const response = await getMessages(depositForBurnTx, irisApiUrl, solanaChain);
const { attestation, message, eventNonce } = response.messages[0];
// (Optional) reclaim event account rent
console.log('Reclaiming event account...');
const reclaimEventAccountTx = await messageTransmitterProgram.methods
.reclaimEventAccount({
attestation: Buffer.from(attestation.replace("0x", ""), "hex"),
})
.accounts({
payee: provider.wallet.publicKey,
messageTransmitter: pdas.messageTransmitterAccount.publicKey,
messageSentEventData: messageSentEventAccountKeypair.publicKey,
})
.rpc();
return {
depositTx: depositForBurnTx,
reclaimTx: reclaimEventAccountTx,
attestation,
message,
eventNonce,
recipientChain,
};
}
/**
* Receives tokens bridged from Solana -> EVM using Circle's CCTP.
*/
export async function cctpReceiveTokenFromSolana(cctpResponse, lockerClient) {
const { attestation, message, recipientChain, } = cctpResponse;
console.log('CCTP receive token from Solana...');
console.log('Attestation:', attestation);
console.log('Message:', message);
console.log('Recipient chain:', recipientChain);
const messageTransmitter = getMessageTransmitterFromChain(recipientChain);
const selector = keccak256(toHex('receiveMessage(bytes,bytes)')).slice(0, 10);
const suffixData = encodeAbiParameters([
{ name: "message", type: "bytes" },
{ name: "attestation", type: "bytes" },
], [message, attestation]);
const data = selector + suffixData.slice(2);
// Sends a receiveMessage userOp to complete the bridge
const response = await lockerClient.sendUserOps(messageTransmitter.address, data, BigInt(0));
return response;
}
//# sourceMappingURL=cctpBridge.js.map