@atomiqlabs/chain-evm
Version:
EVM specific base implementation
630 lines (629 loc) • 34.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVMSpvVaultContract = exports.unpackOwnerAndVaultId = exports.packOwnerAndVaultId = void 0;
const base_1 = require("@atomiqlabs/base");
const buffer_1 = require("buffer");
const EVMContractBase_1 = require("../contract/EVMContractBase");
const SpvVaultContractAbi_1 = require("./SpvVaultContractAbi");
const EVMBtcRelay_1 = require("../btcrelay/EVMBtcRelay");
const Utils_1 = require("../../utils/Utils");
const ethers_1 = require("ethers");
const EVMAddresses_1 = require("../chain/modules/EVMAddresses");
const EVMSpvVaultData_1 = require("./EVMSpvVaultData");
const EVMSpvWithdrawalData_1 = require("./EVMSpvWithdrawalData");
const EVMFees_1 = require("../chain/modules/EVMFees");
const promise_cache_ts_1 = require("promise-cache-ts");
function decodeUtxo(utxo) {
const [txId, vout] = utxo.split(":");
return {
txHash: "0x" + buffer_1.Buffer.from(txId, "hex").reverse().toString("hex"),
vout: BigInt(vout)
};
}
function packOwnerAndVaultId(owner, vaultId) {
if (owner.length !== 42)
throw new Error("Invalid owner address");
return owner.toLowerCase() + base_1.BigIntBufferUtils.toBuffer(vaultId, "be", 12).toString("hex");
}
exports.packOwnerAndVaultId = packOwnerAndVaultId;
function unpackOwnerAndVaultId(data) {
return [(0, ethers_1.getAddress)(data.substring(0, 42)), BigInt("0x" + data.substring(42, 66))];
}
exports.unpackOwnerAndVaultId = unpackOwnerAndVaultId;
class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
constructor(chainInterface, btcRelay, bitcoinRpc, contractAddress, contractDeploymentHeight) {
super(chainInterface, contractAddress, SpvVaultContractAbi_1.SpvVaultContractAbi, contractDeploymentHeight);
this.claimTimeout = 180;
this.logger = (0, Utils_1.getLogger)("EVMSpvVaultContract: ");
this.vaultParamsCache = new promise_cache_ts_1.PromiseLruCache(5000);
this.btcRelay = btcRelay;
this.bitcoinRpc = bitcoinRpc;
}
//Transactions
async Open(signer, vault, feeRate) {
const { txHash, vout } = decodeUtxo(vault.getUtxo());
const tokens = vault.getTokenData();
if (tokens.length !== 2)
throw new Error("Must specify exactly 2 tokens for vault!");
const tx = await this.contract.open.populateTransaction(vault.vaultId, vault.getVaultParamsStruct(), txHash, vout);
tx.from = signer;
EVMFees_1.EVMFees.applyFeeRate(tx, EVMSpvVaultContract.GasCosts.OPEN, feeRate);
return tx;
}
async Deposit(signer, vault, rawAmounts, feeRate) {
let totalGas = EVMSpvVaultContract.GasCosts.DEPOSIT_BASE;
let value = 0n;
if (vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase()) {
value += rawAmounts[0] * vault.token0.multiplier;
}
else {
if (rawAmounts[0] > 0n)
totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
}
if (vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase()) {
value += (rawAmounts[1] ?? 0n) * vault.token1.multiplier;
}
else {
if (rawAmounts[1] != null && rawAmounts[1] > 0n && vault.token0.token.toLowerCase() !== vault.token1.token.toLowerCase())
totalGas += EVMSpvVaultContract.GasCosts.DEPOSIT_ERC20;
}
const tx = await this.contract.deposit.populateTransaction(vault.owner, vault.vaultId, vault.getVaultParamsStruct(), rawAmounts[0], rawAmounts[1] ?? 0n, { value });
tx.from = signer;
EVMFees_1.EVMFees.applyFeeRate(tx, totalGas, feeRate);
return tx;
}
async Front(signer, vault, data, withdrawalSequence, feeRate) {
let value = 0n;
const frontingAmount = data.getFrontingAmount();
if (vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase())
value += frontingAmount[0] * vault.token0.multiplier;
if (vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress().toLowerCase())
value += (frontingAmount[1] ?? 0n) * vault.token1.multiplier;
const tx = await this.contract.front.populateTransaction(vault.owner, vault.vaultId, vault.getVaultParamsStruct(), withdrawalSequence, data.getTxHash(), data.serializeToStruct(), { value });
tx.from = signer;
EVMFees_1.EVMFees.applyFeeRate(tx, this.getFrontGas(signer, vault, data), feeRate);
return tx;
}
async Claim(signer, vault, data, blockheader, merkle, position, feeRate) {
const tx = await this.contract.claim.populateTransaction(vault.owner, vault.vaultId, vault.getVaultParamsStruct(), "0x" + data.btcTx.hex, blockheader.serializeToStruct(), merkle, position);
tx.from = signer;
EVMFees_1.EVMFees.applyFeeRate(tx, this.getClaimGas(signer, vault, data), feeRate);
return tx;
}
async checkWithdrawalTx(tx) {
const result = await this.contract.parseBitcoinTx(buffer_1.Buffer.from(tx.btcTx.hex, "hex"));
if (result == null)
throw new Error("Failed to parse transaction!");
}
createVaultData(owner, vaultId, utxo, confirmations, tokenData) {
if (tokenData.length !== 2)
throw new Error("Must specify 2 tokens in tokenData!");
const vaultParams = {
btcRelayContract: this.btcRelay.contractAddress,
token0: tokenData[0].token,
token1: tokenData[1].token,
token0Multiplier: tokenData[0].multiplier,
token1Multiplier: tokenData[1].multiplier,
confirmations: BigInt(confirmations)
};
const spvVaultParametersCommitment = (0, ethers_1.keccak256)(ethers_1.AbiCoder.defaultAbiCoder().encode(["address", "address", "address", "uint192", "uint192", "uint256"], [vaultParams.btcRelayContract, vaultParams.token0, vaultParams.token1, vaultParams.token0Multiplier, vaultParams.token1Multiplier, vaultParams.confirmations]));
return Promise.resolve(new EVMSpvVaultData_1.EVMSpvVaultData(owner, vaultId, {
spvVaultParametersCommitment,
utxoTxHash: ethers_1.ZeroHash,
utxoVout: 0n,
openBlockheight: 0n,
withdrawCount: 0n,
depositCount: 0n,
token0Amount: 0n,
token1Amount: 0n
}, vaultParams, utxo));
}
//Getters
async getFronterAddress(owner, vaultId, withdrawal) {
const frontingAddress = await this.contract.getFronterById(owner, vaultId, "0x" + withdrawal.getFrontingId());
if (frontingAddress === ethers_1.ZeroAddress)
return null;
return frontingAddress;
}
async getFronterAddresses(withdrawals) {
const result = {};
let promises = [];
//TODO: We can upgrade this to use multicall
for (let { owner, vaultId, withdrawal } of withdrawals) {
promises.push(this.getFronterAddress(owner, vaultId, withdrawal).then(val => {
result[withdrawal.getTxId()] = val;
}));
if (promises.length >= this.Chain.config.maxParallelCalls) {
await Promise.all(promises);
promises = [];
}
}
await Promise.all(promises);
return result;
}
async getVaultData(owner, vaultId) {
const vaultState = await this.contract.getVault(owner, vaultId);
const vaultParams = await this.vaultParamsCache.getOrComputeAsync(vaultState.spvVaultParametersCommitment, async () => {
const blockheight = Number(vaultState.openBlockheight);
const events = await this.Events.getContractBlockEvents(["Opened"], [
"0x" + owner.substring(2).padStart(64, "0"),
(0, ethers_1.hexlify)(base_1.BigIntBufferUtils.toBuffer(vaultId, "be", 32))
], blockheight);
const foundEvent = events.find(event => (0, EVMSpvVaultData_1.getVaultParamsCommitment)(event.args.params) === vaultState.spvVaultParametersCommitment);
if (foundEvent == null)
throw new Error("Valid open event not found!");
return foundEvent.args.params;
});
if (vaultParams.btcRelayContract.toLowerCase() !== this.btcRelay.contractAddress.toLowerCase())
return null;
return new EVMSpvVaultData_1.EVMSpvVaultData(owner, vaultId, vaultState, vaultParams);
}
async getMultipleVaultData(vaults) {
const result = {};
let promises = [];
//TODO: We can upgrade this to use multicall
for (let { owner, vaultId } of vaults) {
promises.push(this.getVaultData(owner, vaultId).then(val => {
result[owner] ?? (result[owner] = {});
result[owner][vaultId.toString(10)] = val;
}));
if (promises.length >= this.Chain.config.maxParallelCalls) {
await Promise.all(promises);
promises = [];
}
}
await Promise.all(promises);
return result;
}
async getVaultLatestUtxo(owner, vaultId) {
const vaultState = await this.contract.getVault(owner, vaultId);
const utxo = (0, EVMSpvVaultData_1.getVaultUtxoFromState)(vaultState);
if (utxo === "0000000000000000000000000000000000000000000000000000000000000000:0")
return null;
return utxo;
}
async getVaultLatestUtxos(vaults) {
const result = {};
let promises = [];
//TODO: We can upgrade this to use multicall
for (let { owner, vaultId } of vaults) {
promises.push(this.getVaultLatestUtxo(owner, vaultId).then(val => {
result[owner] ?? (result[owner] = {});
result[owner][vaultId.toString(10)] = val;
}));
if (promises.length >= this.Chain.config.maxParallelCalls) {
await Promise.all(promises);
promises = [];
}
}
await Promise.all(promises);
return result;
}
async getAllVaults(owner) {
const openedVaults = new Map();
await this.Events.findInContractEventsForward(["Opened", "Closed"], owner == null ? null : [
"0x" + owner.substring(2).padStart(64, "0")
], (event) => {
const vaultIdentifier = event.args.owner + ":" + event.args.vaultId.toString(10);
if (event.eventName === "Opened") {
const _event = event;
openedVaults.set(vaultIdentifier, _event.args.params);
}
else {
openedVaults.delete(vaultIdentifier);
}
return null;
});
const vaults = [];
for (let [identifier, vaultParams] of openedVaults.entries()) {
const [owner, vaultIdStr] = identifier.split(":");
const vaultState = await this.contract.getVault(owner, BigInt(vaultIdStr));
if (vaultState.spvVaultParametersCommitment === (0, EVMSpvVaultData_1.getVaultParamsCommitment)(vaultParams)) {
vaults.push(new EVMSpvVaultData_1.EVMSpvVaultData(owner, BigInt(vaultIdStr), vaultState, vaultParams));
}
}
return vaults;
}
parseWithdrawalEvent(event) {
switch (event.eventName) {
case "Fronted":
const frontedEvent = event;
const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
return {
type: base_1.SpvWithdrawalStateType.FRONTED,
txId: event.transactionHash,
owner: ownerFront,
vaultId: vaultIdFront,
recipient: frontedEvent.args.recipient,
fronter: frontedEvent.args.caller
};
case "Claimed":
const claimedEvent = event;
const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
return {
type: base_1.SpvWithdrawalStateType.CLAIMED,
txId: event.transactionHash,
owner: ownerClaim,
vaultId: vaultIdClaim,
recipient: claimedEvent.args.recipient,
claimer: claimedEvent.args.caller,
fronter: claimedEvent.args.frontingAddress
};
case "Closed":
const closedEvent = event;
return {
type: base_1.SpvWithdrawalStateType.CLOSED,
txId: event.transactionHash,
owner: closedEvent.args.owner,
vaultId: closedEvent.args.vaultId,
error: closedEvent.args.error
};
default:
return null;
}
}
async getWithdrawalState(withdrawalTx, scStartHeight) {
const txHash = buffer_1.Buffer.from(withdrawalTx.getTxId(), "hex").reverse();
const events = ["Fronted", "Claimed", "Closed"];
const keys = [null, null, (0, ethers_1.hexlify)(txHash)];
let result;
if (scStartHeight == null) {
result = await this.Events.findInContractEvents(events, keys, async (event) => {
const result = this.parseWithdrawalEvent(event);
if (result != null)
return result;
});
}
else {
result = await this.Events.findInContractEventsForward(events, keys, async (event) => {
const result = this.parseWithdrawalEvent(event);
if (result == null)
return;
if (result.type === base_1.SpvWithdrawalStateType.FRONTED) {
//Check if still fronted
const fronterAddress = await this.getFronterAddress(result.owner, result.vaultId, withdrawalTx);
//Not fronted now, there should be a claim/close event after the front event, continue
if (fronterAddress == null)
return;
}
return result;
}, scStartHeight);
}
result ?? (result = {
type: base_1.SpvWithdrawalStateType.NOT_FOUND
});
return result;
}
async getWithdrawalStates(withdrawalTxs) {
var _a;
const result = {};
const events = ["Fronted", "Claimed", "Closed"];
for (let i = 0; i < withdrawalTxs.length; i += this.Chain.config.maxLogTopics) {
const checkWithdrawalTxs = withdrawalTxs.slice(i, i + this.Chain.config.maxLogTopics);
const checkWithdrawalTxsMap = new Map(checkWithdrawalTxs.map(val => [val.withdrawal.getTxId(), val.withdrawal]));
let scStartHeight = null;
for (let val of checkWithdrawalTxs) {
if (val.scStartBlockheight == null) {
scStartHeight = null;
break;
}
scStartHeight = Math.min(scStartHeight ?? Infinity, val.scStartBlockheight);
}
const keys = [null, null, checkWithdrawalTxs.map(withdrawal => (0, ethers_1.hexlify)(buffer_1.Buffer.from(withdrawal.withdrawal.getTxId(), "hex").reverse()))];
if (scStartHeight == null) {
await this.Events.findInContractEvents(events, keys, async (event) => {
const _event = event;
const btcTxId = buffer_1.Buffer.from(_event.args.btcTxHash.substring(2), "hex").reverse().toString("hex");
if (!checkWithdrawalTxsMap.has(btcTxId)) {
this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${btcTxId}, but transaction not found in input params!`);
return null;
}
const eventResult = this.parseWithdrawalEvent(event);
if (eventResult == null)
return null;
checkWithdrawalTxsMap.delete(btcTxId);
result[btcTxId] = eventResult;
if (checkWithdrawalTxsMap.size === 0)
return true; //All processed
});
}
else {
await this.Events.findInContractEventsForward(events, keys, async (event) => {
const _event = event;
const btcTxId = buffer_1.Buffer.from(_event.args.btcTxHash.substring(2), "hex").reverse().toString("hex");
const withdrawalTx = checkWithdrawalTxsMap.get(btcTxId);
if (withdrawalTx == null) {
this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${btcTxId}, but transaction not found in input params!`);
return;
}
const eventResult = this.parseWithdrawalEvent(event);
if (eventResult == null)
return;
if (eventResult.type === base_1.SpvWithdrawalStateType.FRONTED) {
//Check if still fronted
const fronterAddress = await this.getFronterAddress(eventResult.owner, eventResult.vaultId, withdrawalTx);
//Not fronted now, so there should be a claim/close event after the front event, continue
if (fronterAddress == null)
return;
//Fronted still, so this should be the latest current state
}
checkWithdrawalTxsMap.delete(btcTxId);
result[btcTxId] = eventResult;
if (checkWithdrawalTxsMap.size === 0)
return true; //All processed
}, scStartHeight);
}
}
for (let val of withdrawalTxs) {
result[_a = val.withdrawal.getTxId()] ?? (result[_a] = {
type: base_1.SpvWithdrawalStateType.NOT_FOUND
});
}
return result;
}
getWithdrawalData(btcTx) {
return Promise.resolve(new EVMSpvWithdrawalData_1.EVMSpvWithdrawalData(btcTx));
}
//OP_RETURN data encoding/decoding
fromOpReturnData(data) {
return EVMSpvVaultContract.fromOpReturnData(data);
}
static fromOpReturnData(data) {
let rawAmount0 = 0n;
let rawAmount1 = 0n;
let executionHash = null;
if (data.length === 28) {
rawAmount0 = data.readBigInt64BE(20).valueOf();
}
else if (data.length === 36) {
rawAmount0 = data.readBigInt64BE(20).valueOf();
rawAmount1 = data.readBigInt64BE(28).valueOf();
}
else if (data.length === 60) {
rawAmount0 = data.readBigInt64BE(20).valueOf();
executionHash = data.slice(28, 60).toString("hex");
}
else if (data.length === 68) {
rawAmount0 = data.readBigInt64BE(20).valueOf();
rawAmount1 = data.readBigInt64BE(28).valueOf();
executionHash = data.slice(36, 68).toString("hex");
}
else {
throw new Error("Invalid OP_RETURN data length!");
}
const recipient = "0x" + data.slice(0, 20).toString("hex");
if (!EVMAddresses_1.EVMAddresses.isValidAddress(recipient))
throw new Error("Invalid recipient specified");
return { executionHash, rawAmounts: [rawAmount0, rawAmount1], recipient: (0, ethers_1.getAddress)(recipient) };
}
toOpReturnData(recipient, rawAmounts, executionHash) {
return EVMSpvVaultContract.toOpReturnData(recipient, rawAmounts, executionHash);
}
static toOpReturnData(recipient, rawAmounts, executionHash) {
if (!EVMAddresses_1.EVMAddresses.isValidAddress(recipient))
throw new Error("Invalid recipient specified");
if (rawAmounts.length < 1)
throw new Error("At least 1 amount needs to be specified");
if (rawAmounts.length > 2)
throw new Error("At most 2 amounts need to be specified");
rawAmounts.forEach(val => {
if (val < 0n)
throw new Error("Negative raw amount specified");
if (val >= 2n ** 64n)
throw new Error("Raw amount overflow");
});
if (executionHash != null) {
if (buffer_1.Buffer.from(executionHash, "hex").length !== 32)
throw new Error("Invalid execution hash");
}
const recipientBuffer = buffer_1.Buffer.from(recipient.substring(2).padStart(40, "0"), "hex");
const amount0Buffer = base_1.BigIntBufferUtils.toBuffer(rawAmounts[0], "be", 8);
const amount1Buffer = rawAmounts[1] == null || rawAmounts[1] === 0n ? buffer_1.Buffer.alloc(0) : base_1.BigIntBufferUtils.toBuffer(rawAmounts[1], "be", 8);
const executionHashBuffer = executionHash == null ? buffer_1.Buffer.alloc(0) : buffer_1.Buffer.from(executionHash, "hex");
return buffer_1.Buffer.concat([
recipientBuffer,
amount0Buffer,
amount1Buffer,
executionHashBuffer
]);
}
//Actions
async claim(signer, vault, txs, synchronizer, initAta, txOptions) {
const result = await this.txsClaim(signer.getAddress(), vault, txs, synchronizer, initAta, txOptions?.feeRate);
const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
return signature;
}
async deposit(signer, vault, rawAmounts, txOptions) {
const result = await this.txsDeposit(signer.getAddress(), vault, rawAmounts, txOptions?.feeRate);
const txHashes = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
return txHashes[txHashes.length - 1];
}
async frontLiquidity(signer, vault, realWithdrawalTx, withdrawSequence, txOptions) {
const result = await this.txsFrontLiquidity(signer.getAddress(), vault, realWithdrawalTx, withdrawSequence, txOptions?.feeRate);
const txHashes = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
return txHashes[txHashes.length - 1];
}
async open(signer, vault, txOptions) {
const result = await this.txsOpen(signer.getAddress(), vault, txOptions?.feeRate);
const [signature] = await this.Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
return signature;
}
//Transactions
async txsClaim(signer, vault, txs, synchronizer, initAta, feeRate) {
if (!vault.isOpened())
throw new Error("Cannot claim from a closed vault!");
feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
const txsWithMerkleProofs = [];
for (let tx of txs) {
const merkleProof = await this.bitcoinRpc.getMerkleProof(tx.tx.btcTx.txid, tx.tx.btcTx.blockhash);
this.logger.debug("txsClaim(): merkle proof computed: ", merkleProof);
txsWithMerkleProofs.push({
...merkleProof,
...tx
});
}
const evmTxs = [];
const storedHeaders = await EVMBtcRelay_1.EVMBtcRelay.getCommitedHeadersAndSynchronize(signer, this.btcRelay, txsWithMerkleProofs.filter(tx => tx.storedHeader == null).map(tx => {
return {
blockhash: tx.tx.btcTx.blockhash,
blockheight: tx.blockheight,
requiredConfirmations: vault.getConfirmations()
};
}), evmTxs, synchronizer, feeRate);
if (storedHeaders == null)
throw new Error("Cannot fetch committed header!");
for (let tx of txsWithMerkleProofs) {
evmTxs.push(await this.Claim(signer, vault, tx.tx, tx.storedHeader ?? storedHeaders[tx.tx.btcTx.blockhash], tx.merkle, tx.pos, feeRate));
}
this.logger.debug("txsClaim(): " + evmTxs.length + " claim TXs created claiming " + txs.length + " txs, owner: " + vault.getOwner() +
" vaultId: " + vault.getVaultId().toString(10));
return evmTxs;
}
async txsDeposit(signer, vault, rawAmounts, feeRate) {
var _a;
if (!vault.isOpened())
throw new Error("Cannot deposit to a closed vault!");
feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
const txs = [];
let realAmount0 = 0n;
let realAmount1 = 0n;
//Approve first
const requiredApprovals = {};
if (rawAmounts[0] != null && rawAmounts[0] !== 0n) {
if (vault.token0.token.toLowerCase() !== this.Chain.getNativeCurrencyAddress().toLowerCase()) {
realAmount0 = rawAmounts[0] * vault.token0.multiplier;
requiredApprovals[vault.token0.token.toLowerCase()] = realAmount0;
}
}
if (rawAmounts[1] != null && rawAmounts[1] !== 0n) {
if (vault.token1.token.toLowerCase() !== this.Chain.getNativeCurrencyAddress().toLowerCase()) {
realAmount1 = rawAmounts[1] * vault.token1.multiplier;
requiredApprovals[_a = vault.token1.token.toLowerCase()] ?? (requiredApprovals[_a] = 0n);
requiredApprovals[vault.token1.token.toLowerCase()] += realAmount1;
}
}
const requiredApprovalTxns = await Promise.all(Object.keys(requiredApprovals).map(token => this.Chain.Tokens.checkAndGetApproveTx(signer, token, requiredApprovals[token], this.contractAddress, feeRate)));
requiredApprovalTxns.forEach(tx => tx != null && txs.push(tx));
txs.push(await this.Deposit(signer, vault, rawAmounts, feeRate));
this.logger.debug("txsDeposit(): deposit TX created," +
" token0: " + vault.token0.token + " rawAmount0: " + rawAmounts[0].toString(10) + " amount0: " + realAmount0.toString(10) +
" token1: " + vault.token1.token + " rawAmount1: " + (rawAmounts[1] ?? 0n).toString(10) + " amount1: " + realAmount1.toString(10));
return txs;
}
async txsFrontLiquidity(signer, vault, realWithdrawalTx, withdrawSequence, feeRate) {
var _a;
if (!vault.isOpened())
throw new Error("Cannot front on a closed vault!");
feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
const txs = [];
let realAmount0 = 0n;
let realAmount1 = 0n;
//Approve first
const rawAmounts = realWithdrawalTx.getFrontingAmount();
//Approve first
const requiredApprovals = {};
if (rawAmounts[0] != null && rawAmounts[0] !== 0n) {
if (vault.token0.token.toLowerCase() !== this.Chain.getNativeCurrencyAddress().toLowerCase()) {
realAmount0 = rawAmounts[0] * vault.token0.multiplier;
requiredApprovals[vault.token0.token.toLowerCase()] = realAmount0;
}
}
if (rawAmounts[1] != null && rawAmounts[1] !== 0n) {
if (vault.token1.token.toLowerCase() !== this.Chain.getNativeCurrencyAddress().toLowerCase()) {
realAmount1 = rawAmounts[1] * vault.token1.multiplier;
requiredApprovals[_a = vault.token1.token.toLowerCase()] ?? (requiredApprovals[_a] = 0n);
requiredApprovals[vault.token1.token.toLowerCase()] += realAmount1;
}
}
const requiredApprovalTxns = await Promise.all(Object.keys(requiredApprovals).map(token => this.Chain.Tokens.checkAndGetApproveTx(signer, token, requiredApprovals[token], this.contractAddress, feeRate)));
requiredApprovalTxns.forEach(tx => tx != null && txs.push(tx));
txs.push(await this.Front(signer, vault, realWithdrawalTx, withdrawSequence, feeRate));
this.logger.debug("txsFrontLiquidity(): front TX created," +
" token0: " + vault.token0.token + " rawAmount0: " + rawAmounts[0].toString(10) + " amount0: " + realAmount0.toString(10) +
" token1: " + vault.token1.token + " rawAmount1: " + (rawAmounts[1] ?? 0n).toString(10) + " amount1: " + realAmount1.toString(10));
return txs;
}
async txsOpen(signer, vault, feeRate) {
if (vault.isOpened())
throw new Error("Cannot open an already opened vault!");
feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
const tx = await this.Open(signer, vault, feeRate);
this.logger.debug("txsOpen(): open TX created, owner: " + vault.getOwner() +
" vaultId: " + vault.getVaultId().toString(10));
return [tx];
}
getClaimGas(signer, vault, data) {
let totalGas = EVMSpvVaultContract.GasCosts.CLAIM_BASE;
if (data == null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
const transferFee = vault == null || vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
totalGas += transferFee;
if (data == null || data.frontingFeeRate > 0n)
totalGas += transferFee; //Also needs to pay out to fronter
if (data == null || (data.callerFeeRate > 0n && !data.isRecipient(signer)))
totalGas += transferFee; //Also needs to pay out to caller
}
if (data == null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
const transferFee = vault == null || vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
EVMSpvVaultContract.GasCosts.CLAIM_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.CLAIM_ERC20_TRANSFER;
totalGas += transferFee;
if (data == null || data.frontingFeeRate > 0n)
totalGas += transferFee; //Also needs to pay out to fronter
if (data == null || (data.callerFeeRate > 0n && !data.isRecipient(signer)))
totalGas += transferFee; //Also needs to pay out to caller
}
if (data == null || (data.executionHash != null && data.executionHash !== ethers_1.ZeroHash))
totalGas += EVMSpvVaultContract.GasCosts.CLAIM_EXECUTION_SCHEDULE;
return totalGas;
}
getFrontGas(signer, vault, data) {
let totalGas = EVMSpvVaultContract.GasCosts.FRONT_BASE;
if (data == null || (data.rawAmounts[0] != null && data.rawAmounts[0] > 0n)) {
totalGas += vault == null || vault.token0.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
}
if (data == null || (data.rawAmounts[1] != null && data.rawAmounts[1] > 0n)) {
totalGas += vault == null || vault.token1.token.toLowerCase() === this.Chain.getNativeCurrencyAddress() ?
EVMSpvVaultContract.GasCosts.FRONT_NATIVE_TRANSFER : EVMSpvVaultContract.GasCosts.FRONT_ERC20_TRANSFER;
}
if (data == null || (data.executionHash != null && data.executionHash !== ethers_1.ZeroHash))
totalGas += EVMSpvVaultContract.GasCosts.FRONT_EXECUTION_SCHEDULE;
return totalGas;
}
async getClaimFee(signer, vault, withdrawalData, feeRate) {
feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
return EVMFees_1.EVMFees.getGasFee(this.getClaimGas(signer, vault, withdrawalData), feeRate);
}
async getFrontFee(signer, vault, withdrawalData, feeRate) {
vault ?? (vault = EVMSpvVaultData_1.EVMSpvVaultData.randomVault());
feeRate ?? (feeRate = await this.Chain.Fees.getFeeRate());
let totalFee = EVMFees_1.EVMFees.getGasFee(this.getFrontGas(signer, vault, withdrawalData), feeRate);
if (withdrawalData == null || (withdrawalData.rawAmounts[0] != null && withdrawalData.rawAmounts[0] > 0n)) {
if (vault.token0.token.toLowerCase() !== this.Chain.getNativeCurrencyAddress().toLowerCase()) {
totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
}
}
if (withdrawalData == null || (withdrawalData.rawAmounts[1] != null && withdrawalData.rawAmounts[1] > 0n)) {
if (vault.token1.token.toLowerCase() !== this.Chain.getNativeCurrencyAddress().toLowerCase()) {
if (vault.token1.token.toLowerCase() !== vault.token0.token.toLowerCase() || withdrawalData == null || withdrawalData.rawAmounts[0] == null || withdrawalData.rawAmounts[0] === 0n) {
totalFee += await this.Chain.Tokens.getApproveFee(feeRate);
}
}
}
return totalFee;
}
}
exports.EVMSpvVaultContract = EVMSpvVaultContract;
EVMSpvVaultContract.GasCosts = {
DEPOSIT_BASE: 15000 + 21000,
DEPOSIT_ERC20: 40000,
OPEN: 80000 + 21000,
CLAIM_BASE: 85000 + 21000,
CLAIM_NATIVE_TRANSFER: 85000,
CLAIM_ERC20_TRANSFER: 40000,
CLAIM_EXECUTION_SCHEDULE: 30000,
FRONT_BASE: 75000 + 21000,
FRONT_NATIVE_TRANSFER: 85000,
FRONT_ERC20_TRANSFER: 40000,
FRONT_EXECUTION_SCHEDULE: 30000
};