@locker-labs/sdk
Version:
A utility library for enhancing your ERC-4337 experience
154 lines (140 loc) • 6.05 kB
text/typescript
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,
}
}