UNPKG

@locker-labs/sdk

Version:

A utility library for enhancing your ERC-4337 experience

154 lines (140 loc) 6.05 kB
import * as anchor from "@coral-xyz/anchor"; import { PublicKey, Connection } from '@solana/web3.js'; import fetch from 'node-fetch'; import { getAssociatedTokenAddress, getAccount } from "@solana/spl-token"; import type { Address } from "viem"; import { type MessageTransmitter } from './target/types/message_transmitter'; import { type TokenMessengerMinter } from './target/types/token_messenger_minter'; import * as MessageTransmitterIDL from './target/idl/message_transmitter.json'; import * as TokenMessengerMinterIDL from './target/idl/token_messenger_minter.json'; import * as evmMessageTransmitterAbi from './abi/evm/message_transmitter.json'; import { EChain } from "../../../accounts/tokens.js"; import { CCTP_DOMAIN_IDS, CCTP_EVM_CONTRACTS } from './cctpConstants.js'; import type { IBridgeFromSolanaParams } from "../../types.js"; /** * Returns the Anchor programs for MessageTransmitter + TokenMessengerMinter. */ export const getPrograms = (provider: anchor.AnchorProvider) => { // Initialize contracts // Initialize workspace with explicit program IDs from devnet const messageTransmitterProgram = new anchor.Program( // You'll need to import these IDLs MessageTransmitterIDL as anchor.Idl, new PublicKey("CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd"), provider ) as anchor.Program<MessageTransmitter>; const tokenMessengerMinterProgram = new anchor.Program( TokenMessengerMinterIDL as anchor.Idl, new PublicKey("CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3"), provider ) as anchor.Program<TokenMessengerMinter>; return { messageTransmitterProgram, tokenMessengerMinterProgram }; } /** * Finds PDAs needed for depositForBurn. */ export const getDepositForBurnPdas = ( { messageTransmitterProgram, tokenMessengerMinterProgram }: ReturnType<typeof getPrograms>, usdcAddress: PublicKey, destinationDomain: number ) => { const messageTransmitterAccount = findProgramAddress("message_transmitter", messageTransmitterProgram.programId); const tokenMessengerAccount = findProgramAddress("token_messenger", tokenMessengerMinterProgram.programId); const tokenMinterAccount = findProgramAddress("token_minter", tokenMessengerMinterProgram.programId); const localToken = findProgramAddress("local_token", tokenMessengerMinterProgram.programId, [usdcAddress]); const remoteTokenMessengerKey = findProgramAddress("remote_token_messenger", tokenMessengerMinterProgram.programId, [ destinationDomain.toString(), ]); const authorityPda = findProgramAddress("sender_authority", tokenMessengerMinterProgram.programId); return { messageTransmitterAccount, tokenMessengerAccount, tokenMinterAccount, localToken, remoteTokenMessengerKey, authorityPda, }; }; /** * Helper function to fetch Circle's attestation data for a specific Solana transaction. */ export async function getMessages(txHash: string, irisApiUrl: string, solanaChain: EChain) { let attestationResponse: any = {}; const solanaDomain = CCTP_DOMAIN_IDS[solanaChain]; while ( attestationResponse.error || !attestationResponse.messages || attestationResponse.messages?.[0]?.attestation === 'PENDING' ) { const response = await fetch(`${irisApiUrl}/messages/${solanaDomain}/${txHash}`); attestationResponse = await response.json(); // Wait 2 seconds to avoid potential rate-limiting if ( attestationResponse.error || !attestationResponse.messages || attestationResponse.messages?.[0]?.attestation === 'PENDING' ) { await new Promise((resolve) => setTimeout(resolve, 2000)); } } return attestationResponse; } /** * Convert an EVM address (0x1234...) into a zero-padded 32-byte hex string. */ export function evmAddressToBytes32(address: string): `0x${string}` { return `0x000000000000000000000000${address.replace('0x', '')}` as `0x${string}`; } /** * Convenience wrapper for PublicKey.findProgramAddressSync */ export function findProgramAddress( label: string, programId: PublicKey, extraSeeds: (string | number[] | Buffer | PublicKey)[] = [] ) { const seeds = [Buffer.from(anchor.utils.bytes.utf8.encode(label))]; for (const extraSeed of extraSeeds) { if (typeof extraSeed === 'string') { seeds.push(Buffer.from(anchor.utils.bytes.utf8.encode(extraSeed))); } else if (Array.isArray(extraSeed)) { seeds.push(Buffer.from(new Uint8Array(extraSeed))); } else if (Buffer.isBuffer(extraSeed)) { seeds.push(Buffer.from(extraSeed)); } else { seeds.push(Buffer.from(extraSeed.toBuffer())); } } const [publicKey, bump] = PublicKey.findProgramAddressSync(seeds, programId); return { publicKey, bump }; } export async function findOrCreateUserTokenAccount( params: IBridgeFromSolanaParams ): Promise<PublicKey> { const { solanaSigner, solanaTokenAddress, solanaRpcUrl } = params; const solanaTokenPublicKey = new PublicKey(solanaTokenAddress); const connection = new Connection(solanaRpcUrl); try { // Get the associated token account address const tokenAccount = await getAssociatedTokenAddress( solanaTokenPublicKey, solanaSigner.publicKey ); // Check if the account exists await getAccount(connection, tokenAccount); return tokenAccount; } catch (error) { throw new Error("Token account not found"); } } export function getMessageTransmitterFromChain(chain: EChain) { if (chain === EChain.SOLANA || chain === EChain.SOLANA_DEVNET) { throw new Error(`Unsupported chain: ${chain}`); } const address = CCTP_EVM_CONTRACTS.V1.MESSAGE_TRANSMITTER[chain] as Address; console.log('Message transmitter address:', address); return { abi: evmMessageTransmitterAbi, address, } }