@atomiqlabs/base
Version:
Base classes and interfaces for atomiq protocol
221 lines (220 loc) • 9.04 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SpvWithdrawalTransactionData = void 0;
const buffer_1 = require("buffer");
/**
* Represents the data of a single SPV vault (UTXO-controlled) vault withdrawal
*
* @category Chains
*/
class SpvWithdrawalTransactionData {
/**
* Deserializer parsing the chain-specific spv vault withdrawal data from a JSON-compatible object representation
*
* @param data
*/
static deserialize(data) {
if (SpvWithdrawalTransactionData.deserializers[data.type] != null) {
return new SpvWithdrawalTransactionData.deserializers[data.type](data);
}
throw new Error(`No deserializer found for spv withdrawal tx data type: ${data.type}`);
}
/**
* Parses and creates a withdrawal data from the bitcoin transaction
*
* @throws {Error} If the bitcoin transaction has invalid formatting
*/
constructor(btcTx) {
if (btcTx.ins.length < 2)
throw new Error("Need at least 2 inputs");
if (btcTx.outs.length < 2)
throw new Error("Need at least 2 outputs");
const nSequence0 = BigInt(btcTx.ins[0].sequence);
const nSequence1 = BigInt(btcTx.ins[1].sequence);
if ((nSequence0 & 0x80000000n) != 0x80000000n)
throw new Error("nSequence0 high bit not set!");
if ((nSequence1 & 0x80000000n) != 0x80000000n)
throw new Error("nSequence0 high bit not set!");
const callerFeeRate = nSequence0 & 0xfffffn;
const executionFeeRate = nSequence1 & 0xfffffn;
const frontingFeeRate = ((nSequence0 >> 10n) & 1047552n) | ((nSequence1 >> 20n) & 1023n);
const executionExpiry = btcTx.locktime + 1000000000;
if (executionExpiry >= Math.pow(2, 32))
throw new Error("Execution expiry overflow");
const opReturnOutput = btcTx.outs[1];
//Parse script
const opReturnData = buffer_1.Buffer.from(opReturnOutput.scriptPubKey.hex, "hex");
if (opReturnData.length === 0)
throw new Error("Output 1 empty script");
if (opReturnData.at(0) !== 0x6a)
throw new Error("Output 1 is not OP_RETURN");
const secondByte = opReturnData.at(1);
if (secondByte == null)
throw new Error("Output 1 OP_RETURN empty");
if (secondByte === 0)
throw new Error("Output 1 OP_RETURN followed by OP_0");
let data;
if (secondByte === 0x4c) { //OP_PUSHDATA1
const dataLength = opReturnData.at(2);
if (dataLength == null)
throw new Error("Output 1 OP_RETURN OP_PUSHDATA1 invalid length!");
data = opReturnData.subarray(3, 3 + dataLength);
if (data.length !== dataLength)
throw new Error("Output 1 OP_RETURN data length mismatch!");
}
else if (secondByte <= 0x4b) { //OP_PUSH<length>
const dataLength = secondByte;
data = opReturnData.subarray(2, 2 + dataLength);
if (data.length !== dataLength)
throw new Error("Output 1 OP_RETURN data length mismatch!");
}
else {
throw new Error("Output 1 invalid push opcode");
}
//Parse OP_RETURN data
const res = this.fromOpReturnData(data);
this.recipient = res.recipient;
this.rawAmounts = res.rawAmounts;
this.callerFeeRate = callerFeeRate;
this.frontingFeeRate = frontingFeeRate;
this.executionFeeRate = executionFeeRate;
this.executionHash = res.executionHash;
this.executionExpiry = executionExpiry;
this.btcTx = btcTx;
}
/**
* @inheritDoc
*/
serialize() {
return this.btcTx;
}
/**
* Gets the recipient of the funds for this withdrawal
*/
getRecipient() {
return this.recipient;
}
/**
* Returns the amounts of tokens that the recipient is gonna receive (NOTE: This returns raw token amounts,
* which must be scaled by their respective vault configured multiplier to represent the actual amount
* of tokens)
*/
getOutputWithoutFees() {
return this.rawAmounts;
}
/**
* Returns the fee paid out to the caller which submits the withdrawal transaction data on the smart chain,
* the fee is paid out from all the respective tokens being withdrawn from the vault (NOTE: This returns raw token amounts,
* which must be scaled by their respective vault configured multiplier to represent the actual amount
* of tokens)
*/
getCallerFee() {
return this.rawAmounts.map(val => val * this.callerFeeRate / 100000n);
}
/**
* Returns the fee paid out to the fronter which fronts the actual withdrawal on the smart chain,
* the fee is paid out from all the respective tokens being withdrawn from the vault (NOTE: This returns raw token amounts,
* which must be scaled by their respective vault configured multiplier to represent the actual amount
* of tokens)
*/
getFrontingFee() {
return this.rawAmounts.map(val => val * this.frontingFeeRate / 100000n);
}
/**
* Returns the fee that is transferred to the execution contract if swap+ execution action is assigned,
* (NOTE: This returns raw token amounts, which must be scaled by their respective vault configured
* multiplier to represent the actual amount of tokens)
*/
getExecutionFee() {
return [this.rawAmounts[0] * this.executionFeeRate / 100000n];
}
/**
* Returns the total amount of tokens withdrawn from the vault (including all the fees) (NOTE: This returns
* raw token amounts, which must be scaled by their respective vault configured multiplier to represent
* the actual amount of tokens)
*
* @throws {Error} In case the amounts overflow
*/
getTotalOutput() {
const amounts = [...this.getOutputWithoutFees()];
const callerFee = this.getCallerFee();
if (callerFee != null)
callerFee.forEach((fee, i) => {
if (fee == null || fee === 0n)
return;
if (amounts[i] == null)
throw new Error("Caller fee token out of bounds");
amounts[i] += fee;
});
const frontingFee = this.getFrontingFee();
if (frontingFee != null)
frontingFee.forEach((fee, i) => {
if (fee == null || fee === 0n)
return;
if (amounts[i] == null)
throw new Error("Fronting fee token out of bounds");
amounts[i] += fee;
});
const executionFee = this.getExecutionFee();
if (executionFee != null)
executionFee.forEach((fee, i) => {
if (fee == null || fee === 0n)
return;
if (amounts[i] == null)
throw new Error("Execution fee token out of bounds");
amounts[i] += fee;
});
amounts.forEach((val, index) => {
if (val >= 2n ** 64n)
throw new Error("Token " + index + " amount out of bounds");
});
return amounts;
}
/**
* Returns the execution action to be scheduled via the execution contract (swap+) or `null` if none specified
*/
getExecutionData() {
if (this.executionHash == null)
return null;
return {
executionHash: this.executionHash,
executionExpiry: this.executionExpiry
};
}
/**
* Gets the transaction ID of the bitcoin transaction authorizing the withdrawal
*/
getTxId() {
return this.btcTx.txid;
}
/**
* Gets the vault ownership UTXO that the bitcoin transaction spends
*/
getSpentVaultUtxo() {
const in0 = this.btcTx.ins[0];
return in0.txid + ":" + in0.vout;
}
/**
* Gets the new vault ownership UTXO created by this transaction
*/
getCreatedVaultUtxo() {
return this.getTxId() + ":0";
}
/**
* Gets the output locking script used for the new vault ownership UTXO
*/
getNewVaultScript() {
return buffer_1.Buffer.from(this.btcTx.outs[0].scriptPubKey.hex, "hex");
}
/**
* Gets the output btc amount (in satoshis) assigned to the new vault ownership UTXO
*/
getNewVaultBtcAmount() {
return this.btcTx.outs[0].value;
}
}
exports.SpvWithdrawalTransactionData = SpvWithdrawalTransactionData;
/**
* A mapping of deserializers for different spv vault withdrawal data types coming from different smart chain implementations
*/
SpvWithdrawalTransactionData.deserializers = {};