@debridge-finance/solana-contracts-client
Version:
[Docs](docs/README.md)
820 lines (819 loc) • 48.6 kB
JavaScript
import { __awaiter } from "tslib";
import { ComputeBudgetProgram, PublicKey, Secp256k1Program, SystemProgram, Transaction, TransactionInstruction, } from "@solana/web3.js";
import * as wasm from "@debridge-finance/debridge-external-call";
import { BN, Program } from "@coral-xyz/anchor";
import { DeBridgeResolver, TOKEN_METADATA_PROGRAM_ID, TOKEN_PROGRAM_ID, constants, crypto, findAssociatedTokenAddress, getTokenMetadataAddress, helpers, interfaces, programs, spl, txs, } from "@debridge-finance/solana-utils";
import { IDL as SettingsIdl } from "./idl/debridge_settings_program";
import { IDL as DebridgeIdl } from "./idl/debridge_program";
import { BridgeState, WalletCheckEnum, isMintBridge, isSendBridge, isSupportedChainInfoType, isActiveDiscount, isExtCallMetaAccumulation, } from "./interfaces";
import { Submission, instructions, packSignatures } from ".";
import { buildDebridgeDecoder } from "./decoder";
import { checkFlag, isAccountEmpty } from "./utils";
import { EXT_CALL_STORAGE_OFFSET, SEND_HASHED_DATA } from "./constants";
import { AssetFeeNotSupported, AssociatedWalletNotInitialized, BridgeNotExists, BridgePaused, ChainSupportInfoNotInitialized, ChainSupportInfoNotSupported, DiscountInfoMalformed, DiscountNotActive, DiscountNotExists, } from "./errors";
import { buildDecodedAccountInfoWithAddress } from "./accounts";
function newTxWithOptionalPriorityFee(priorityFee) {
const tx = new Transaction();
if (priorityFee) {
tx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: priorityFee.limit }));
if (priorityFee.microLamports)
tx.add(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee.microLamports }));
}
return tx;
}
export class ClientV2 {
constructor(connection, options) {
var _a, _b;
this.connection = connection;
this.feeBeneficiaryAccount = undefined;
this.cachedState = null;
if (!(options === null || options === void 0 ? void 0 : options.chainId)) {
throw new Error(`chainId is required`);
}
this.chainId = options.chainId;
this.settings = new Program(SettingsIdl, (_a = options === null || options === void 0 ? void 0 : options.settingsProgram) !== null && _a !== void 0 ? _a : programs.settings);
this.program = new Program(DebridgeIdl, (_b = options === null || options === void 0 ? void 0 : options.deBridgeProgram) !== null && _b !== void 0 ? _b : programs.deBridge);
this.logger = options === null || options === void 0 ? void 0 : options.clientLogger;
this.accountsResolver = DeBridgeResolver(this.program.programId, this.settings.programId).methods;
this.decoder = buildDebridgeDecoder(this.program, this.settings);
this.priorityFeeConfig = options === null || options === void 0 ? void 0 : options.priorityFeeConfig;
this.stateAccount = this.accountsResolver.getStateAddress()[0];
}
getState() {
return __awaiter(this, void 0, void 0, function* () {
if (this.cachedState !== null) {
return this.cachedState;
}
else {
const account = yield this.connection.getAccountInfo(this.stateAccount);
const decoded = this.decoder.decodeState(account);
this.cachedState = decoded;
if (this.cachedState) {
this.feeBeneficiaryAccount = this.cachedState.feeBeneficiary;
}
return this.cachedState;
}
});
}
getCalldataShortcut(flags, data) {
const calldata = data ? (typeof data === "string" ? helpers.hexToBuffer(data) : Buffer.from(data)) : undefined;
return checkFlag(flags, SEND_HASHED_DATA) ? calldata !== null && calldata !== void 0 ? calldata : crypto.hashExternalCallBytes() : crypto.hashExternalCallBytes(calldata);
}
splitDataForExternalCall(data, usePriorityFee = false) {
const EXTEND_IX_MAX_SIZE = 876 - (usePriorityFee ? 70 : 0);
const INIT_OR_UPDATE_CHUNK_SIZE = EXTEND_IX_MAX_SIZE + 20;
const result = [];
let currentPos = 0;
while (currentPos < data.length) {
const chunkSize = INIT_OR_UPDATE_CHUNK_SIZE;
const chunk = data.slice(currentPos, currentPos + chunkSize);
result.push({ data: chunk, offset: currentPos });
currentPos += chunkSize;
}
return result;
}
buildFillExtcallStorageTransactions(sourceChain, totalLength, chunks, storageKey, accounts, priorityFee) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (chunks.length === 0)
return null;
const storePriorityFee = priorityFee !== null && priorityFee !== void 0 ? priorityFee : (_a = this.priorityFeeConfig) === null || _a === void 0 ? void 0 : _a.storeExternalCall;
const sourceChainId = crypto.normalizeChainId(sourceChain);
const ixContext = {
claimer: accounts.executor,
externalCallMeta: accounts.externalCallMeta,
externalCallStorage: accounts.externalCallStorage,
};
const result = [];
for (const chunk of chunks) {
result.push({
instructions: newTxWithOptionalPriorityFee(storePriorityFee).add(yield instructions
.initOrUpdateExternalCallStorage(this.program, ixContext, {
externalCallLen: totalLength,
rawInstructions: chunk.data,
sourceChainId,
storageKey,
externalInstructionsOffset: chunk.offset,
})
.instruction()).instructions,
payer: accounts.executor,
});
}
return result;
});
}
prepareExternalCallTransactions(storageKey, sourceChain, data, accounts, priorityFee) {
var _a, _b, _c, _d, _e, _f;
return __awaiter(this, void 0, void 0, function* () {
let chunksToWrite = [];
const storePriorityFee = priorityFee !== null && priorityFee !== void 0 ? priorityFee : (_a = this.priorityFeeConfig) === null || _a === void 0 ? void 0 : _a.storeExternalCall;
const logger = (_b = this.logger) === null || _b === void 0 ? void 0 : _b.child({ method: "prepareExternalCallTransactions" });
if (((_c = accounts.submission) === null || _c === void 0 ? void 0 : _c.data) !== null) {
logger === null || logger === void 0 ? void 0 : logger.debug("Submission already claimed, hence calldata was already saved");
return null;
}
if (accounts.externalCallMeta) {
const decodedExtCallMeta = accounts.externalCallMeta.data;
if (!isExtCallMetaAccumulation((_d = decodedExtCallMeta === null || decodedExtCallMeta === void 0 ? void 0 : decodedExtCallMeta.data) !== null && _d !== void 0 ? _d : null)) {
logger === null || logger === void 0 ? void 0 : logger.debug(`ExternalCallMeta exists and is not in accumulation state, no need to save calldata`);
return null;
}
else {
// accumulation, already allocated, need to overwrite
if (((_e = accounts.externalCallStorage) === null || _e === void 0 ? void 0 : _e.data) === undefined || accounts.externalCallStorage.data === null) {
throw new Error(`storage should be already initialized, something is wrong`);
}
else if (data.equals((_f = accounts.externalCallStorage.data) === null || _f === void 0 ? void 0 : _f.slice(EXT_CALL_STORAGE_OFFSET))) {
logger === null || logger === void 0 ? void 0 : logger.debug(`Data was already stored correctly, no need to overwrite`);
return null;
}
else {
const onChainData = accounts.externalCallStorage.data.slice(EXT_CALL_STORAGE_OFFSET);
const OVERWRITE_CHUNK_SIZE = 870 - (storePriorityFee ? 50 : 0);
let offset = 0;
while (offset < data.length) {
const range = [offset, offset + OVERWRITE_CHUNK_SIZE];
const correctChunk = data.slice(...range);
if (!correctChunk.equals(onChainData.slice(...range))) {
logger === null || logger === void 0 ? void 0 : logger.debug(`Overwrite required for chunk: ${range[0]}..${range[1]}`);
chunksToWrite.push({ data: correctChunk, offset });
}
offset += OVERWRITE_CHUNK_SIZE;
}
}
}
}
else {
chunksToWrite = this.splitDataForExternalCall(data, storePriorityFee !== undefined);
}
return this.buildFillExtcallStorageTransactions(sourceChain, data.length, chunksToWrite, storageKey, {
executor: accounts.executor,
externalCallMeta: accounts.externalCallMeta.address,
externalCallStorage: accounts.externalCallStorage.address,
}, priorityFee);
});
}
/**
* Packs initCalldata and other instructions into single tx if possible
* If extCallTransactions contains both init & store txs that's pointless -> return extCall transactions as is
* @param instructions instructions list that should go before initCalldata (prefix) and after initCalldata ix (postfix)
* @param extCallTxs calldata txs or null
* @returns packed tx and modified extCallTransactions
*/
tryPackCalldataTransaction(ixs, extCallTxs) {
var _a;
const result = new Transaction();
if (ixs.prefix.length) {
result.add(...ixs.prefix);
}
if (extCallTxs) {
const txCopy = new Transaction(result);
let lastIx = null;
if (extCallTxs.length === 1) {
lastIx = (_a = extCallTxs.at(0)) === null || _a === void 0 ? void 0 : _a.instructions.at(0);
}
// if last ix is null, hence it wasn't modified and we don't need to process it
if (lastIx !== null) {
if (lastIx === undefined)
throw new Error(`Unreachable, empty store ext call tx`);
txCopy.add(lastIx);
txCopy.add(...ixs.postfix);
const txSize = txs.getTransactionSize(txCopy);
if (txSize !== null && txSize !== void 0 ? txSize : +Infinity < constants.MAX_TX_SIZE - 2) {
result.add(lastIx);
result.add(...ixs.postfix);
extCallTxs = null;
}
}
}
return {
transaction: result,
extCallTxs: extCallTxs,
};
}
buildSendContext(sender, tokenMint, chainTo, externalCall, options) {
var _a, _b;
const [bridge] = this.accountsResolver.getBridgeAddress(tokenMint);
const sendFromWallet = (_a = options === null || options === void 0 ? void 0 : options.sendFromWallet) !== null && _a !== void 0 ? _a : findAssociatedTokenAddress(sender, tokenMint)[0];
const [mintAuthority] = this.accountsResolver.getMintAuthorityAddress(bridge);
const [chainSupportInfo] = this.accountsResolver.getChainSupportInfoAddress(crypto.normalizeChainId(chainTo));
const [stakingWallet] = findAssociatedTokenAddress(mintAuthority, tokenMint);
const [nonceStorage] = this.accountsResolver.getNonceAddress();
const [bridgeFee] = (options === null || options === void 0 ? void 0 : options.useAssetFee)
? this.accountsResolver.getBridgeFeeAddress(bridge, chainTo)
: this.accountsResolver.getNoBridgeFeeAddress();
const [discount] = (options === null || options === void 0 ? void 0 : options.useDiscount)
? this.accountsResolver.getDiscountInfoAddress(sender)
: this.accountsResolver.getNoDiscountAddress();
const shortcut = (externalCall === null || externalCall === void 0 ? void 0 : externalCall.shortcut)
? typeof externalCall.shortcut === "string"
? helpers.hexToBuffer(externalCall.shortcut)
: Buffer.from(externalCall.shortcut)
: this.getCalldataShortcut((_b = externalCall === null || externalCall === void 0 ? void 0 : externalCall.flags) !== null && _b !== void 0 ? _b : 0, externalCall === null || externalCall === void 0 ? void 0 : externalCall.data);
const [externalCallStorage] = this.accountsResolver.getExternalCallStorageAddress(shortcut, sender, crypto.normalizeChainId(this.chainId));
const [externalCallMeta] = this.accountsResolver.getExternalCallMetaAddress(externalCallStorage);
const accounts = {
bridgeCtx: {
bridge,
tokenMint,
stakingWallet,
mintAuthority,
chainSupportInfo,
splTokenProgram: TOKEN_PROGRAM_ID,
settingsProgram: this.settings.programId,
},
stateCtx: {
state: this.stateAccount,
feeBeneficiary: this.feeBeneficiary,
},
nonceStorage,
sendFromWallet,
sendFrom: sender,
bridgeFee,
discount,
systemProgram: SystemProgram.programId,
externalCallStorage,
externalCallMeta,
};
const accountMeta = [
{ isSigner: false, isWritable: true, pubkey: accounts.bridgeCtx.bridge },
{ isSigner: false, isWritable: true, pubkey: accounts.bridgeCtx.tokenMint },
{
isSigner: false,
isWritable: true,
pubkey: accounts.bridgeCtx.stakingWallet,
},
{
isSigner: false,
isWritable: false,
pubkey: accounts.bridgeCtx.mintAuthority,
},
{
isSigner: false,
isWritable: false,
pubkey: accounts.bridgeCtx.chainSupportInfo,
},
{
isSigner: false,
isWritable: false,
pubkey: accounts.bridgeCtx.settingsProgram,
},
{
isSigner: false,
isWritable: false,
pubkey: accounts.bridgeCtx.splTokenProgram,
},
{ isSigner: false, isWritable: true, pubkey: accounts.stateCtx.state },
{
isSigner: false,
isWritable: true,
pubkey: accounts.stateCtx.feeBeneficiary,
},
{ isSigner: false, isWritable: true, pubkey: accounts.nonceStorage },
{ isSigner: false, isWritable: true, pubkey: accounts.sendFromWallet },
{ isSigner: false, isWritable: false, pubkey: accounts.systemProgram },
{ isSigner: false, isWritable: true, pubkey: accounts.externalCallStorage },
{ isSigner: false, isWritable: true, pubkey: accounts.externalCallMeta },
{ isSigner: true, isWritable: true, pubkey: accounts.sendFrom },
{ isSigner: false, isWritable: false, pubkey: accounts.discount },
{ isSigner: false, isWritable: false, pubkey: accounts.bridgeFee },
];
const flatAccounts = {
bridge,
tokenMint,
stakingWallet,
mintAuthority,
chainSupportInfo: accounts.bridgeCtx.chainSupportInfo,
splTokenProgram: accounts.bridgeCtx.splTokenProgram,
settingsProgram: accounts.bridgeCtx.settingsProgram,
state: accounts.stateCtx.state,
feeBeneficiary: accounts.stateCtx.feeBeneficiary,
nonceStorage,
sendFromWallet,
sendFrom: sender,
bridgeFee,
discount,
systemProgram: accounts.systemProgram,
externalCallStorage,
externalCallMeta,
};
return {
asFlat: flatAccounts,
asAccountMeta: accountMeta,
asIdl: accounts,
};
}
buildSendTransactions(sender, amount, receiverInfo, senderInfo, autoParams, options) {
var _a, _b, _c, _d, _e, _f;
return __awaiter(this, void 0, void 0, function* () {
const logger = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.child({ method: "buildSend" });
const result = newTxWithOptionalPriorityFee((_b = options === null || options === void 0 ? void 0 : options.sendPriorityFee) !== null && _b !== void 0 ? _b : (_c = this.priorityFeeConfig) === null || _c === void 0 ? void 0 : _c.send);
if (options === undefined)
options = {};
if (options.useAssetFee === undefined)
options.useAssetFee = false;
sender = new PublicKey(sender);
const tokenMint = new PublicKey(senderInfo.tokenMint);
const chainId = crypto.normalizeChainId(receiverInfo.chainTo);
const sendAccounts = this.buildSendContext(sender, tokenMint, chainId, autoParams !== null && autoParams !== void 0 ? autoParams : null, Object.assign({ sendFromWallet: (senderInfo === null || senderInfo === void 0 ? void 0 : senderInfo.sendFromWallet) ? new PublicKey(senderInfo.sendFromWallet) : undefined }, options)).asFlat;
const externalCallExists = (autoParams === null || autoParams === void 0 ? void 0 : autoParams.data) && !checkFlag(autoParams === null || autoParams === void 0 ? void 0 : autoParams.flags, SEND_HASHED_DATA);
const externalCallAccounts = [];
if (externalCallExists) {
externalCallAccounts.push(sendAccounts.externalCallMeta, sendAccounts.externalCallStorage);
}
const [discountData, bridgeFeeData, sendFromWalletData, stakingWalletData, chainSupportInfoData, bridgeAccountData, ...extCallChainData] = yield this.connection.getMultipleAccountsInfo([
sendAccounts.discount,
sendAccounts.bridgeFee,
sendAccounts.sendFromWallet,
sendAccounts.stakingWallet,
sendAccounts.chainSupportInfo,
sendAccounts.bridge,
...externalCallAccounts,
]);
const bridgeData = this.decoder.decodeBridge(bridgeAccountData);
if (!bridgeData)
throw new BridgeNotExists(sendAccounts.bridge);
let isPaused = false;
if (isMintBridge(bridgeData.data)) {
isPaused = bridgeData.data.mint.info.state === BridgeState.PAUSE;
}
else if (isSendBridge(bridgeData.data)) {
isPaused = bridgeData.data.send.info.state === BridgeState.PAUSE;
}
else {
throw new Error(`Bridge not exists or malformed`);
}
if (isPaused)
throw new BridgePaused(sendAccounts.bridge);
if (isAccountEmpty(stakingWalletData)) {
result.add(spl.createAssociatedWalletInstruction(tokenMint, sendAccounts.stakingWallet, sendAccounts.mintAuthority, sender));
}
if (options.useAssetFee) {
if (isAccountEmpty(bridgeFeeData))
throw new AssetFeeNotSupported(sendAccounts.bridgeFee);
}
if (isAccountEmpty(sendFromWalletData)) {
throw new AssociatedWalletNotInitialized(sender, sendAccounts.sendFromWallet);
}
if (options.useDiscount) {
if (isAccountEmpty(discountData)) {
throw new DiscountNotExists(sendAccounts.discount);
}
else {
const decodedDiscount = this.decoder.decodeDiscountInfo(discountData);
if (decodedDiscount === null)
throw new DiscountInfoMalformed(sendAccounts.discount);
if (!isActiveDiscount(decodedDiscount.data))
throw new DiscountNotActive(sendAccounts.discount);
}
}
const receiver = helpers.hexToBuffer(receiverInfo.receiver);
const chainSupportInfo = this.decoder.decodeChainSupportInfo(chainSupportInfoData);
const fallbackAddressBuffer = (autoParams === null || autoParams === void 0 ? void 0 : autoParams.fallbackAddress)
? helpers.hexToBuffer(autoParams.fallbackAddress)
: undefined;
if (chainSupportInfo === null) {
throw new ChainSupportInfoNotInitialized(sendAccounts.chainSupportInfo);
}
else if (!isSupportedChainInfoType(chainSupportInfo.data)) {
throw new ChainSupportInfoNotSupported(sendAccounts.chainSupportInfo);
}
else {
if (receiver.length !== chainSupportInfo.data.supported.chainAddressLen)
throw Error(`Bad receiver length! Got: ${receiver.length}, expected: ${chainSupportInfo.data.supported.chainAddressLen}`);
if (fallbackAddressBuffer && fallbackAddressBuffer.length !== chainSupportInfo.data.supported.chainAddressLen)
throw Error(`Bad fallback length! Got: ${fallbackAddressBuffer.length}, expected: ${chainSupportInfo.data.supported.chainAddressLen}`);
}
let submissionParams = undefined;
const externalCallShortcut = (autoParams === null || autoParams === void 0 ? void 0 : autoParams.shortcut)
? typeof autoParams.shortcut === "string"
? helpers.hexToBuffer(autoParams.shortcut)
: autoParams.shortcut
: this.getCalldataShortcut((_d = autoParams === null || autoParams === void 0 ? void 0 : autoParams.flags) !== null && _d !== void 0 ? _d : 0, autoParams === null || autoParams === void 0 ? void 0 : autoParams.data);
if (autoParams) {
if (fallbackAddressBuffer === undefined)
throw new Error(`Should never happen, fallbackAddressBuffer is undefined`);
submissionParams = {
executionFee: new BN((_e = autoParams.executionFee) !== null && _e !== void 0 ? _e : 0),
fallbackAddress: fallbackAddressBuffer,
externalCallShortcut: externalCallShortcut,
reservedFlag: interfaces.isBuffer(autoParams.flags)
? autoParams.flags
: new BN((_f = autoParams === null || autoParams === void 0 ? void 0 : autoParams.flags) !== null && _f !== void 0 ? _f : 0).toArrayLike(Buffer, "be", 32),
};
}
let extCallTxs = null;
if (externalCallExists) {
if (!(autoParams === null || autoParams === void 0 ? void 0 : autoParams.data)) {
throw new Error(`AutoParams.data field is empty while shortuct is not hash([]) && SEND_HASHED flag is not set`);
}
const [extCallMetaData, extCallStorageData] = extCallChainData;
const externalCallData = typeof autoParams.data === "string" ? helpers.hexToBuffer(autoParams.data) : autoParams.data;
extCallTxs = yield this.prepareExternalCallTransactions(externalCallShortcut, this.chainId, externalCallData, {
executor: sender,
externalCallMeta: buildDecodedAccountInfoWithAddress(sendAccounts.externalCallMeta, extCallMetaData, this.decoder.decodeExternalCallMeta),
externalCallStorage: buildDecodedAccountInfoWithAddress(sendAccounts.externalCallStorage, extCallStorageData, (data) => Buffer.from(data)),
});
}
const builder = instructions.sendInstruction(this.program, sendAccounts, {
amount: new BN(amount.toString()),
chainIdBuffer: chainId,
receiver,
useAssetFee: options.useAssetFee,
referralCode: options === null || options === void 0 ? void 0 : options.referralCode,
submissionParams,
});
return this.tryPackCalldataTransaction({ prefix: result.instructions, postfix: [yield builder.instruction()] }, extCallTxs);
});
}
buildClaimTransactions(executor, amount, receiverInfo, senderInfo, submissionInfo, autoParams, options) {
var _a, _b, _c, _d, _e;
return __awaiter(this, void 0, void 0, function* () {
if (options === undefined)
options = {};
if ((options === null || options === void 0 ? void 0 : options.createMissingWallets) === undefined) {
options.createMissingWallets = true;
}
const missingWalletsConfig = typeof options.createMissingWallets === "boolean"
? {
payerWallet: options.createMissingWallets
? { check: WalletCheckEnum.Create }
: { check: WalletCheckEnum.ThrowError },
receiverWallet: options.createMissingWallets
? { check: WalletCheckEnum.Create }
: { check: WalletCheckEnum.ThrowError },
stakingWallet: options.createMissingWallets
? { check: WalletCheckEnum.Create }
: { check: WalletCheckEnum.ThrowError },
}
: options.createMissingWallets;
executor = new PublicKey(executor);
const sourceChainId = crypto.normalizeChainId(senderInfo.chainFrom);
const subIdBuffer = typeof submissionInfo.submissionId === "string"
? helpers.hexToBuffer(submissionInfo.submissionId)
: submissionInfo.submissionId;
const logger = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.child({ submission: helpers.bufferToHex(subIdBuffer) });
logger === null || logger === void 0 ? void 0 : logger.info("Started to build claim tx");
const result = newTxWithOptionalPriorityFee((_b = options.claimPriorityFee) !== null && _b !== void 0 ? _b : (_c = this.priorityFeeConfig) === null || _c === void 0 ? void 0 : _c.claim);
const receiver = typeof receiverInfo.receiver === "string"
? receiverInfo.receiver.startsWith("0x")
? new PublicKey(helpers.hexToBuffer(receiverInfo.receiver))
: new PublicKey(receiverInfo.receiver)
: new PublicKey(receiverInfo.receiver);
const tokenMint = new PublicKey(receiverInfo.tokenMint);
let claimToWalletAccount = null;
let externalCallData = undefined;
if ((autoParams === null || autoParams === void 0 ? void 0 : autoParams.data) && autoParams.data !== "0x") {
externalCallData = typeof autoParams.data === "string" ? helpers.hexToBuffer(autoParams.data) : autoParams.data;
}
const [confirmationStorageAccount] = this.accountsResolver.getConfirmationsStorageAddress(subIdBuffer);
const [bridgeDataAccount] = this.accountsResolver.getBridgeAddress(tokenMint);
const [mintAuthorityAccount] = this.accountsResolver.getMintAuthorityAddress(bridgeDataAccount);
const [submissionAccount] = this.accountsResolver.getSubmissionAddress(subIdBuffer);
const [submissionAuthAccount] = this.accountsResolver.getSubmissionAuthAddress(submissionAccount);
const [stakingWalletAccount] = findAssociatedTokenAddress(mintAuthorityAccount, tokenMint);
const [payerWalletAccount] = ((_d = missingWalletsConfig.payerWallet) === null || _d === void 0 ? void 0 : _d.wallet)
? [new PublicKey(missingWalletsConfig.payerWallet.wallet)]
: findAssociatedTokenAddress(executor, tokenMint);
const claimToWalletOwner = externalCallData !== undefined ? submissionAuthAccount : receiver;
claimToWalletAccount = (receiverInfo === null || receiverInfo === void 0 ? void 0 : receiverInfo.claimToWallet) ? new PublicKey(receiverInfo.claimToWallet) : receiver;
if (!claimToWalletAccount)
[claimToWalletAccount] = findAssociatedTokenAddress(claimToWalletOwner, tokenMint);
const [claimMarker] = this.accountsResolver.getClaimMarkerAddress();
let fallback = receiver;
if (autoParams === null || autoParams === void 0 ? void 0 : autoParams.fallbackAddress) {
fallback = new PublicKey(autoParams.fallbackAddress.startsWith("0x") ? helpers.hexToBuffer(autoParams.fallbackAddress) : autoParams.fallbackAddress);
}
const [chainSupportInfoAccount] = this.accountsResolver.getChainSupportInfoAddress(sourceChainId);
const [externalCallStorageAccount] = this.accountsResolver.getExternalCallStorageAddress(subIdBuffer, executor, sourceChainId);
const [externalCallMetaAccount] = this.accountsResolver.getExternalCallMetaAddress(externalCallStorageAccount);
const [stakingWalletData, payerWalletData, submissionData, confirmationStorageData, externalCallStorageData, externalCallMetaData] = yield this.connection.getMultipleAccountsInfo([
stakingWalletAccount,
payerWalletAccount,
submissionAccount,
confirmationStorageAccount,
externalCallStorageAccount,
externalCallMetaAccount,
]);
if (isAccountEmpty(stakingWalletData)) {
if (missingWalletsConfig.stakingWallet.check === WalletCheckEnum.Create) {
result.add(spl.createAssociatedWalletInstruction(tokenMint, stakingWalletAccount, mintAuthorityAccount, executor));
}
else {
throw new AssociatedWalletNotInitialized(mintAuthorityAccount, stakingWalletAccount);
}
}
let claimAutoParams = undefined;
if (autoParams !== undefined && autoParams.executionFee !== "" && autoParams.fallbackAddress !== "") {
claimAutoParams = {
executionFee: crypto.denormalizeAmount(new BN((autoParams === null || autoParams === void 0 ? void 0 : autoParams.executionFee) || 0), 0),
nativeSender: helpers.hexToBuffer(senderInfo.sender),
reservedFlag: new BN((autoParams === null || autoParams === void 0 ? void 0 : autoParams.flags) || 0).toArrayLike(Buffer, "be", 32),
externalCallShortcut: crypto.hashExternalCallBytes(autoParams === null || autoParams === void 0 ? void 0 : autoParams.data),
};
}
const submission = new Submission.SubmissionState(this.decoder, this.connection, subIdBuffer, {
confirmationStorage: confirmationStorageAccount,
submission: submissionAccount,
externalCall: externalCallData !== undefined
? {
externalCallMeta: externalCallMetaAccount,
externalCallStorage: externalCallStorageAccount,
}
: null,
}, (_e = this.logger) === null || _e === void 0 ? void 0 : _e.child({ module: "submissionListener" }).debug, {
confirmationStorage: confirmationStorageData,
submission: submissionData,
externalCall: externalCallData !== undefined
? {
meta: externalCallMetaData,
storage: externalCallStorageData,
}
: null,
});
let extCallTxs = null;
if (externalCallData !== undefined) {
const submissionWithCalldata = submission;
extCallTxs = yield this.prepareExternalCallTransactions(subIdBuffer, sourceChainId, externalCallData, {
executor,
externalCallMeta: submissionWithCalldata.externalCallMeta,
externalCallStorage: submissionWithCalldata.externalCallStorage,
submission: submission.submission,
});
}
if (!(options === null || options === void 0 ? void 0 : options.confirmationStorageCreator)) {
if (submission.confirmationStorage !== null && submission.confirmationStorage.data) {
options.confirmationStorageCreator = submission.confirmationStorage.data.creator;
}
else {
options.confirmationStorageCreator = executor;
}
}
const builder = instructions.claimInstrution(this.program, {
tokenMint,
bridgeData: bridgeDataAccount,
submissionAddr: submissionAccount,
mintAuthority: mintAuthorityAccount,
stakingWallet: stakingWalletAccount,
chainSupportInfo: chainSupportInfoAccount,
claimToWallet: claimToWalletAccount,
fallbackAddress: fallback,
receiver,
confirmationStorage: confirmationStorageAccount,
confirmationStorageCreator: options.confirmationStorageCreator,
executor,
payerWallet: payerWalletAccount,
externalCallStorage: externalCallStorageAccount,
externalCallMeta: externalCallMetaAccount,
claimMarker,
state: this.stateAccount,
feeBeneficiary: this.feeBeneficiary,
settingsProgram: this.settings.programId,
}, {
sourceChainId,
amount: crypto.denormalizeAmount(new BN(amount.toString()), 0),
nonce: new BN(submissionInfo.nonce).toArrayLike(Buffer, "be", 32),
submissionParams: claimAutoParams,
});
return this.tryPackCalldataTransaction({ prefix: result.instructions, postfix: [yield builder.instruction()] }, extCallTxs);
});
}
buildStoreConfirmationsTransactions(payer, message, signatures, priorityFee) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const messageBuf = typeof message === "string" ? helpers.hexToBuffer(message) : Buffer.from(message);
const [confirmationStorage] = this.accountsResolver.getConfirmationsStorageAddress(messageBuf);
const storeSignaturesInstruction = yield instructions
.storeConfirmationsInstruction(this.settings, {
state: this.stateAccount,
confirmationStorage,
payer: payer,
}, { message: messageBuf })
.instruction();
const CHUNK_SIZE = 5;
const transactions = [];
priorityFee = priorityFee !== null && priorityFee !== void 0 ? priorityFee : (_a = this.priorityFeeConfig) === null || _a === void 0 ? void 0 : _a.storeConfirmations;
const signaturesBuf = signatures.map((s) => (typeof s === "string" ? helpers.hexToBuffer(s) : Buffer.from(s)));
for (let i = 0; i < signaturesBuf.length; i += CHUNK_SIZE) {
const transaction = newTxWithOptionalPriorityFee(priorityFee);
const signaturesChunk = signaturesBuf.slice(i, i + CHUNK_SIZE);
let instructionIndex = 0;
if (priorityFee) {
instructionIndex = (priorityFee === null || priorityFee === void 0 ? void 0 : priorityFee.microLamports) ? 2 : 1;
}
const packedSignatures = packSignatures(messageBuf, signaturesChunk, instructionIndex);
const signaturesInstruction = new TransactionInstruction({
keys: [],
programId: Secp256k1Program.programId,
data: packedSignatures,
});
transaction.add(signaturesInstruction, storeSignaturesInstruction);
transactions.push(transaction);
}
return transactions;
});
}
buildInitMintBridge(payer, deployInfo, priorityFee) {
return __awaiter(this, void 0, void 0, function* () {
const normalizedChainId = crypto.normalizeChainId(deployInfo.chainId.toString());
const debridgeId = crypto.hashDebridgeId(normalizedChainId, deployInfo.nativeAddress);
const debridgeIdBuf = helpers.hexToBuffer(debridgeId);
const [tokenMint] = this.accountsResolver.getTokenMintAddress(debridgeIdBuf);
const [bridgeData] = this.accountsResolver.getBridgeAddress(tokenMint);
const deployId = crypto.hashDeployInfo({
debridgeId,
decimals: deployInfo.decimals,
tokenName: deployInfo.name,
tokenSymbol: deployInfo.symbol,
});
const [confirmationStorage] = this.accountsResolver.getConfirmationsStorageAddress(helpers.hexToBuffer(deployId));
const [confStorageData] = yield this.connection.getMultipleAccountsInfo([confirmationStorage]);
let confirmationStorageCreator = payer;
if (confStorageData !== null) {
const decodedConfStorageData = this.decoder.decodeConfirmationStorage(confStorageData);
if ((decodedConfStorageData === null || decodedConfStorageData === void 0 ? void 0 : decodedConfStorageData.creator) !== undefined) {
confirmationStorageCreator = decodedConfStorageData.creator;
}
else {
throw new Error(`Confirmation storage for deployId: ${deployId} exists, but it's broken (${confirmationStorage.toBase58()})`);
}
}
const ix = yield instructions
.initializeMintBridgeInstruction(this.settings, {
bridgeData,
tokenMetadataMaster: this.accountsResolver.getTokenMetadataMasterAddress()[0],
confirmationStorage,
confirmationStorageCreator,
feeBeneficiary: this.feeBeneficiary,
payer,
state: this.stateAccount,
tokenMint,
mintAuthority: this.accountsResolver.getMintAuthorityAddress(bridgeData)[0],
tokenMetadata: getTokenMetadataAddress(tokenMint, TOKEN_METADATA_PROGRAM_ID)[0],
}, {
chainId: normalizedChainId,
decimals: deployInfo.decimals,
nativeTokenAddress: deployInfo.nativeAddress,
tokenName: deployInfo.name,
tokenSymbol: deployInfo.symbol,
})
.instruction();
return newTxWithOptionalPriorityFee(priorityFee).add(ix);
});
}
buildInitSendBridge(payer, tokenMint, priorityFee) {
return __awaiter(this, void 0, void 0, function* () {
const result = newTxWithOptionalPriorityFee(priorityFee);
const bridgeId = helpers.hexToBuffer(crypto.hashDebridgeId(this.chainId, tokenMint.toBuffer()));
const [bridgeData] = this.accountsResolver.getBridgeAddress(tokenMint);
const [mintAuthority] = this.accountsResolver.getMintAuthorityAddress(bridgeData);
const [stakingWallet] = findAssociatedTokenAddress(mintAuthority, tokenMint);
const [stakingWalletData, ix] = yield Promise.all([
this.connection.getAccountInfo(stakingWallet),
instructions
.initializeSendBridgeInstruction(this.settings, {
payer,
state: this.stateAccount,
bridgeData,
tokenMint,
bridgeIdMap: this.accountsResolver.getBridgeMapAddress(bridgeId)[0],
mintAuthority,
stakingWallet,
tokenMetadata: getTokenMetadataAddress(tokenMint, TOKEN_METADATA_PROGRAM_ID)[0],
})
.instruction(),
]);
if (stakingWalletData === null) {
result.add(spl.createAssociatedWalletInstruction(tokenMint, stakingWallet, mintAuthority, payer));
}
result.add(ix);
return result;
});
}
prepareRemainingExtCallAccounts(count, submission, submissionAuth, submissionWallet, offset, calldata) {
const executeContext = wasm.get_external_call_account_meta(calldata, offset, calldata.length, count, submission.toBase58(), submissionAuth.toBase58(), submissionWallet.toBase58());
const remainingAccounts = executeContext.remaning_accounts().map((item) => ({
isSigner: item.is_signer,
isWritable: item.is_writable,
pubkey: new PublicKey(item.pubkey),
}));
executeContext.free();
return [remainingAccounts, Buffer.from(executeContext.reversed_subsitution_bumps())];
}
buildExecuteExternalCallInstruction(accounts, submissionId, sourceChainId, count, subsitutionBumps) {
var _a, _b, _c, _d, _e, _f, _g, _h;
const subIdBuffer = Buffer.from(submissionId);
accounts.submission = (_a = accounts.submission) !== null && _a !== void 0 ? _a : this.accountsResolver.getSubmissionAddress(subIdBuffer)[0];
accounts.submissionAuth = (_b = accounts.submissionAuth) !== null && _b !== void 0 ? _b : this.accountsResolver.getSubmissionAuthAddress(accounts.submission)[0];
accounts.submissionWallet = (_c = accounts.submissionWallet) !== null && _c !== void 0 ? _c : findAssociatedTokenAddress(accounts.submissionAuth, accounts.tokenMint)[0];
accounts.bridge = (_d = accounts.bridge) !== null && _d !== void 0 ? _d : this.accountsResolver.getBridgeAddress(accounts.tokenMint)[0];
accounts.fallbackAddressWallet =
(_e = accounts.fallbackAddressWallet) !== null && _e !== void 0 ? _e : findAssociatedTokenAddress(accounts.fallbackAddress, accounts.tokenMint)[0];
accounts.externalCallStorage =
(_f = accounts.externalCallStorage) !== null && _f !== void 0 ? _f : this.accountsResolver.getExternalCallStorageAddress(subIdBuffer, accounts.executor, sourceChainId)[0];
accounts.externalCallMeta =
(_g = accounts.externalCallMeta) !== null && _g !== void 0 ? _g : this.accountsResolver.getExternalCallMetaAddress(accounts.externalCallStorage)[0];
accounts.rewardBeneficiaryWallet =
(_h = accounts.rewardBeneficiaryWallet) !== null && _h !== void 0 ? _h : findAssociatedTokenAddress(accounts.executor, accounts.tokenMint)[0];
const builder = instructions.executeExternalCallInstruction(this.program, {
bridge: accounts.bridge,
executor: accounts.executor,
externalCallMeta: accounts.externalCallMeta,
externalCallStorage: accounts.externalCallStorage,
fallbackAddress: accounts.fallbackAddress,
fallbackAddressWallet: accounts.fallbackAddressWallet,
originalClaimer: accounts.originalClaimer,
rewardBeneficiaryWallet: accounts.rewardBeneficiaryWallet,
state: this.stateAccount,
submission: accounts.submission,
submissionAuth: accounts.submissionAuth,
submissionWallet: accounts.submissionWallet,
tokenMint: accounts.tokenMint,
}, { count, submissionId: subIdBuffer, subsitutionBumps }, accounts.remaining);
return builder.instruction();
}
buildExecuteExternalCallTransaction(count, submissionId, executor, externalCallStorage, externalCallMeta, submission) {
var _a, _b;
if (!submission.data)
throw new Error(`Empty submission data`);
if (!externalCallMeta.data)
throw new Error(`Empty extCall meta data`);
if (!externalCallStorage.data)
throw new Error(`Empty extCall data`);
const [submissionAuth] = this.accountsResolver.getSubmissionAuthAddress(submission.address);
const [submissionWallet] = findAssociatedTokenAddress(submission.address, submission.data.tokenMint);
const [remaining, bumps] = this.prepareRemainingExtCallAccounts(count, submission.address, submissionAuth, submissionWallet, (_b = (_a = externalCallMeta.data.data.execution) === null || _a === void 0 ? void 0 : _a.offset.toNumber()) !== null && _b !== void 0 ? _b : EXT_CALL_STORAGE_OFFSET, externalCallStorage.data);
const execIx = this.buildExecuteExternalCallInstruction({
executor,
fallbackAddress: executor,
originalClaimer: submission.data.claimer,
remaining,
tokenMint: submission.data.tokenMint,
submission: submission.address,
submissionAuth,
submissionWallet,
externalCallMeta: externalCallMeta.address,
externalCallStorage: externalCallStorage.address,
}, submissionId, new BN(submission.data.sourceChainId).toNumber(), count, bumps);
}
/*
private async findOptimalExecuteCount(
externalCallStorage: AccountInfoWithAddress<Buffer>,
externalCallMeta: AccountInfoWithAddress<AccountStructByName<DebridgeProgram, "externalCallMeta">>,
) {
let execIx: TransactionInstruction | null = null;
let vtx: VersionedTransaction | null = null;
let optimalCountMin = 0;
let offset = EXT_CALL_STORAGE_OFFSET;
if (externalCallMeta.data?.data.execution) {
offset = externalCallMeta.data.data.execution.offset.toNumber();
}
let optimalCountMax = extCallDataToInstructions(externalCallData, offset).length + 1;
let middle: number = 0;
while (optimalCountMax - optimalCountMin > 1) {
middle = Math.floor((optimalCountMin + optimalCountMax) / 2);
logger.debug(`Current min: ${optimalCountMin}, max: ${optimalCountMax}, middle: ${middle}`);
try {
execIx = await this.buildExecuteExternalCallInstruction(submissionId, externalCallData, offset, middle, execAccounts);
vtx = new VersionedTransaction(
MessageV0.compile({
instructions: [...prefixInstructions, execIx],
payerKey: execAccounts.executor,
recentBlockhash: constants.FAKE_BLOCKHASH,
addressLookupTableAccounts: ALTs,
}),
);
const simulation = await this._connection.simulateTransaction(vtx, { replaceRecentBlockhash: true });
logger.debug(simulation.value.err);
if (simulation.value.err === null) {
optimalCountMin = middle;
} else {
optimalCountMax = middle;
}
} catch (e) {
logger.debug(e);
optimalCountMax = middle;
}
}
let optimal = Math.floor((optimalCountMax + optimalCountMin) / 2);
optimal = optimal === 0 ? 1 : optimal;
logger.debug(`Optimal count: ${optimal}`);
execIx = await this.buildExecuteExternalCallIx(submissionId, externalCallData, offset, optimal, execAccounts);
vtx = new VersionedTransaction(
MessageV0.compile({
instructions: [...prefixInstructions, execIx],
payerKey: execAccounts.executor,
recentBlockhash: constants.FAKE_BLOCKHASH,
addressLookupTableAccounts: ALTs,
}),
);
return [vtx, optimal];
}
*/
get feeBeneficiary() {
if (this.feeBeneficiaryAccount === undefined)
throw new Error(`State hasnt been fetched yet`);
return this.feeBeneficiaryAccount;
}
}
//# sourceMappingURL=optimizedClient.js.map