@oyl/sdk
Version:
412 lines • 16.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.poolPsbt = exports.splitAlkaneUtxos = exports.createNewPool = exports.createNewPoolPsbt = exports.getPoolId = exports.AlkanesAMMPoolFactoryDecoder = exports.parseAlkaneIdFromHex = exports.PoolFactoryOpcodes = void 0;
const tslib_1 = require("tslib");
const alkanes_1 = require("../alkanes/alkanes");
const u128_1 = require("@magiceden-oss/runestone-lib/dist/src/integer/u128");
const u32_1 = require("@magiceden-oss/runestone-lib/dist/src/integer/u32");
const bytes_1 = require("alkanes/lib/bytes");
const proto_runestone_upgrade_1 = require("alkanes/lib/protorune/proto_runestone_upgrade");
const protoruneruneid_1 = require("alkanes/lib/protorune/protoruneruneid");
const protostone_1 = require("alkanes/lib/protorune/protostone");
const __1 = require("..");
const bitcoin = tslib_1.__importStar(require("bitcoinjs-lib"));
const psbt_1 = require("../psbt");
const btc_1 = require("../btc");
const pool_1 = require("./pool");
const utils_1 = require("./utils");
var PoolFactoryOpcodes;
(function (PoolFactoryOpcodes) {
PoolFactoryOpcodes[PoolFactoryOpcodes["INIT_POOL"] = 0] = "INIT_POOL";
PoolFactoryOpcodes[PoolFactoryOpcodes["CREATE_NEW_POOL"] = 1] = "CREATE_NEW_POOL";
PoolFactoryOpcodes[PoolFactoryOpcodes["FIND_EXISTING_POOL_ID"] = 2] = "FIND_EXISTING_POOL_ID";
PoolFactoryOpcodes[PoolFactoryOpcodes["GET_ALL_POOLS"] = 3] = "GET_ALL_POOLS";
})(PoolFactoryOpcodes = exports.PoolFactoryOpcodes || (exports.PoolFactoryOpcodes = {}));
const parseAlkaneIdFromHex = (hex) => {
const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
const blockHex = cleanHex.slice(0, 32);
const txHex = cleanHex.slice(32);
const reversedBlockHex = Buffer.from(blockHex, 'hex')
.reverse()
.toString('hex');
const reversedTxHex = Buffer.from(txHex, 'hex').reverse().toString('hex');
const block = parseInt(reversedBlockHex, 16).toString();
const tx = parseInt(reversedTxHex, 16).toString();
return { block, tx };
};
exports.parseAlkaneIdFromHex = parseAlkaneIdFromHex;
class AlkanesAMMPoolFactoryDecoder {
decodeCreateNewPool(execution) {
if (!execution?.alkanes?.[0]?.u?.[1]?.[0] ||
!execution?.alkanes?.[0]?.u?.[0]) {
return undefined;
}
return {
lpTokens: execution.alkanes[0].u[1][0].toString(),
alkaneId: {
block: execution.alkanes[0].u[0][0][0],
tx: execution.alkanes[0].u[0][1][0],
},
};
}
decodeFindExistingPoolId(execution) {
if (!execution?.data || execution.data === '0x') {
return undefined;
}
const bytes = (0, exports.parseAlkaneIdFromHex)(execution.data);
return {
alkaneId: {
block: bytes.block.toString(),
tx: bytes.tx.toString(),
},
};
}
decodeGetAllPools(execution) {
if (!execution?.data || execution.data === '0x') {
return undefined;
}
const data = execution.data.startsWith('0x')
? execution.data.slice(2)
: execution.data;
const countBytes = Buffer.from(data.slice(0, 32), 'hex');
const count = parseInt(countBytes.reverse().toString('hex'), 16);
const pools = [];
for (let i = 0; i < count; i++) {
const offset = 32 + i * 64;
const blockBytes = Buffer.from(data.slice(offset, offset + 32), 'hex');
const block = parseInt(blockBytes.reverse().toString('hex'), 16).toString();
const txBytes = Buffer.from(data.slice(offset + 32, offset + 64), 'hex');
const tx = parseInt(txBytes.reverse().toString('hex'), 16).toString();
pools.push({ block, tx });
}
return { count, pools };
}
async decodeAllPoolsDetails(factoryExecution, provider) {
// Get all pool IDs
const allPools = this.decodeGetAllPools(factoryExecution);
if (!allPools)
return undefined;
const poolDecoder = new pool_1.AlkanesAMMPoolDecoder();
const poolsWithDetails = [];
// For each pool ID, simulate a call to get its details
for (const poolId of allPools.pools) {
const request = {
alkanes: [],
transaction: '0x',
block: '0x',
height: '20000',
txindex: 0,
target: poolId,
inputs: [utils_1.PoolOpcodes.POOL_DETAILS.toString()],
pointer: 0,
refundPointer: 0,
vout: 0,
};
try {
const result = await provider.alkanes.simulate(request);
const poolDetails = poolDecoder.decodePoolDetails(result.execution.data);
if (poolDetails) {
poolsWithDetails.push({
...poolDetails,
poolId,
});
}
}
catch (error) {
console.error(`Error getting details for pool ${poolId.block}:${poolId.tx}:`, error);
}
}
return {
count: poolsWithDetails.length,
pools: poolsWithDetails,
};
}
static decodeSimulation(result, opcode) {
if (!result || typeof result.status === 'undefined') {
return {
success: false,
error: 'Invalid simulation result',
gasUsed: 0,
};
}
const decoder = new AlkanesAMMPoolFactoryDecoder();
let decoded;
switch (opcode) {
case PoolFactoryOpcodes.INIT_POOL:
// Not implemented
break;
case PoolFactoryOpcodes.CREATE_NEW_POOL:
decoded = decoder.decodeCreateNewPool(result.execution);
break;
case PoolFactoryOpcodes.FIND_EXISTING_POOL_ID:
decoded = decoder.decodeFindExistingPoolId(result.execution);
break;
case PoolFactoryOpcodes.GET_ALL_POOLS:
decoded = decoder.decodeGetAllPools(result.execution);
break;
default:
decoded = undefined;
}
return decoded;
}
}
exports.AlkanesAMMPoolFactoryDecoder = AlkanesAMMPoolFactoryDecoder;
const getPoolId = async () => { };
exports.getPoolId = getPoolId;
const createNewPoolPsbt = async ({ calldata, token0, token0Amount, token1, token1Amount, gatheredUtxos, feeRate, account, provider, }) => {
const tokens = [
{ alkaneId: token0, amount: token0Amount },
{ alkaneId: token1, amount: token1Amount },
];
const { alkaneUtxos, edicts, totalSatoshis } = await (0, exports.splitAlkaneUtxos)(tokens, account, provider);
const protostone = (0, proto_runestone_upgrade_1.encodeRunestoneProtostone)({
protostones: [
protostone_1.ProtoStone.message({
edicts,
protocolTag: 1n,
pointer: 0,
refundPointer: 0,
calldata: (0, bytes_1.encipher)([]),
}),
protostone_1.ProtoStone.message({
protocolTag: 1n,
pointer: 0,
refundPointer: 0,
calldata: (0, bytes_1.encipher)(calldata),
}),
],
}).encodedRunestone;
const { psbt } = await (0, exports.poolPsbt)({
alkaneUtxos: {
alkaneUtxos: alkaneUtxos,
totalSatoshis: totalSatoshis,
},
protostone,
gatheredUtxos,
feeRate,
account,
provider,
});
const { fee } = await (0, psbt_1.getEstimatedFee)({
psbt,
provider,
feeRate,
});
const { psbt: finalPsbt } = await (0, exports.poolPsbt)({
alkaneUtxos: {
alkaneUtxos: alkaneUtxos,
totalSatoshis: totalSatoshis,
},
fee,
gatheredUtxos,
account,
protostone,
provider,
feeRate,
});
return { psbt: finalPsbt, fee };
};
exports.createNewPoolPsbt = createNewPoolPsbt;
const createNewPool = async ({ calldata, token0, token0Amount, token1, token1Amount, gatheredUtxos, feeRate, account, signer, provider, }) => {
const { psbt } = await (0, exports.createNewPoolPsbt)({
calldata,
token0,
token0Amount,
token1,
token1Amount,
gatheredUtxos,
feeRate,
account,
provider,
});
const { signedPsbt } = await signer.signAllInputs({
rawPsbt: psbt,
finalize: true,
});
const pushResult = await provider.pushPsbt({
psbtBase64: signedPsbt,
});
return pushResult;
};
exports.createNewPool = createNewPool;
//@dev we use output 5 for because that is the virtual output for the 2nd protostone. The index count starts after the total number of outputs in the txn.
const splitAlkaneUtxos = async (tokens, account, provider) => {
let tokenUtxos;
const allTokenUtxos = await Promise.all(tokens.map(async (token) => {
return (0, alkanes_1.findAlkaneUtxos)({
address: account.taproot.address,
greatestToLeast: false,
provider,
targetNumberOfAlkanes: Number(token.amount),
alkaneId: token.alkaneId,
});
}));
tokenUtxos = {
alkaneUtxos: allTokenUtxos
.flatMap((t) => t.alkaneUtxos)
.filter((utxo, index, self) => index === self.findIndex((u) => u.txId === utxo.txId)),
totalSatoshis: allTokenUtxos.reduce((acc, t) => acc + t.totalSatoshis, 0),
};
const edicts = tokens.flatMap((token) => {
return [
{
id: new protoruneruneid_1.ProtoruneRuneId((0, u128_1.u128)(BigInt(token.alkaneId.block)), (0, u128_1.u128)(BigInt(token.alkaneId.tx))),
amount: (0, u128_1.u128)(token.amount),
output: (0, u32_1.u32)(5),
},
];
});
const protostone = (0, proto_runestone_upgrade_1.encodeRunestoneProtostone)({
protostones: [
protostone_1.ProtoStone.edicts({
protocolTag: 1n,
edicts,
}),
],
}).encodedRunestone;
return {
alkaneUtxos: tokenUtxos.alkaneUtxos,
totalSatoshis: tokenUtxos.totalSatoshis,
protostone,
edicts,
};
};
exports.splitAlkaneUtxos = splitAlkaneUtxos;
const poolPsbt = async ({ alkaneUtxos, gatheredUtxos, account, protostone, provider, feeRate, fee = 0, }) => {
try {
const originalGatheredUtxos = gatheredUtxos;
const minTxSize = (0, btc_1.minimumFee)({
taprootInputCount: 2,
nonTaprootInputCount: 0,
outputCount: 2,
});
let calculatedFee = Math.max(minTxSize * feeRate, 250);
let finalFee = fee === 0 ? calculatedFee : fee;
gatheredUtxos = (0, __1.findXAmountOfSats)(originalGatheredUtxos.utxos, Number(finalFee) + 546);
let psbt = new bitcoin.Psbt({ network: provider.network });
if (alkaneUtxos) {
for await (const utxo of alkaneUtxos.alkaneUtxos) {
if ((0, __1.getAddressType)(utxo.address) === 0) {
const previousTxHex = await provider.esplora.getTxHex(utxo.txId);
psbt.addInput({
hash: utxo.txId,
index: parseInt(utxo.txIndex),
nonWitnessUtxo: Buffer.from(previousTxHex, 'hex'),
});
}
if ((0, __1.getAddressType)(utxo.address) === 2) {
const redeemScript = bitcoin.script.compile([
bitcoin.opcodes.OP_0,
bitcoin.crypto.hash160(Buffer.from(account.nestedSegwit.pubkey, 'hex')),
]);
psbt.addInput({
hash: utxo.txId,
index: parseInt(utxo.txIndex),
redeemScript: redeemScript,
witnessUtxo: {
value: utxo.satoshis,
script: bitcoin.script.compile([
bitcoin.opcodes.OP_HASH160,
bitcoin.crypto.hash160(redeemScript),
bitcoin.opcodes.OP_EQUAL,
]),
},
});
}
if ((0, __1.getAddressType)(utxo.address) === 1 ||
(0, __1.getAddressType)(utxo.address) === 3) {
psbt.addInput({
hash: utxo.txId,
index: parseInt(utxo.txIndex),
witnessUtxo: {
value: utxo.satoshis,
script: Buffer.from(utxo.script, 'hex'),
},
});
}
}
}
if (fee === 0 && gatheredUtxos.utxos.length > 1) {
const txSize = (0, btc_1.minimumFee)({
taprootInputCount: gatheredUtxos.utxos.length,
nonTaprootInputCount: 0,
outputCount: 2,
});
finalFee = txSize * feeRate < 250 ? 250 : txSize * feeRate;
if (gatheredUtxos.totalAmount < finalFee) {
throw new __1.OylTransactionError(Error('Insufficient Balance'));
}
}
if (gatheredUtxos.totalAmount < finalFee) {
throw new __1.OylTransactionError(Error('Insufficient Balance'));
}
for (let i = 0; i < gatheredUtxos.utxos.length; i++) {
if ((0, __1.getAddressType)(gatheredUtxos.utxos[i].address) === 0) {
const previousTxHex = await provider.esplora.getTxHex(gatheredUtxos.utxos[i].txId);
psbt.addInput({
hash: gatheredUtxos.utxos[i].txId,
index: gatheredUtxos.utxos[i].outputIndex,
nonWitnessUtxo: Buffer.from(previousTxHex, 'hex'),
});
}
if ((0, __1.getAddressType)(gatheredUtxos.utxos[i].address) === 2) {
const redeemScript = bitcoin.script.compile([
bitcoin.opcodes.OP_0,
bitcoin.crypto.hash160(Buffer.from(account.nestedSegwit.pubkey, 'hex')),
]);
psbt.addInput({
hash: gatheredUtxos.utxos[i].txId,
index: gatheredUtxos.utxos[i].outputIndex,
redeemScript: redeemScript,
witnessUtxo: {
value: gatheredUtxos.utxos[i].satoshis,
script: bitcoin.script.compile([
bitcoin.opcodes.OP_HASH160,
bitcoin.crypto.hash160(redeemScript),
bitcoin.opcodes.OP_EQUAL,
]),
},
});
}
if ((0, __1.getAddressType)(gatheredUtxos.utxos[i].address) === 1 ||
(0, __1.getAddressType)(gatheredUtxos.utxos[i].address) === 3) {
psbt.addInput({
hash: gatheredUtxos.utxos[i].txId,
index: gatheredUtxos.utxos[i].outputIndex,
witnessUtxo: {
value: gatheredUtxos.utxos[i].satoshis,
script: Buffer.from(gatheredUtxos.utxos[i].scriptPk, 'hex'),
},
});
}
}
psbt.addOutput({
address: account.taproot.address,
value: 546,
});
const output = { script: protostone, value: 0 };
psbt.addOutput(output);
const changeAmount = gatheredUtxos.totalAmount +
(alkaneUtxos?.totalSatoshis || 0) -
finalFee -
546;
psbt.addOutput({
address: account[account.spendStrategy.changeAddress].address,
value: changeAmount,
});
const formattedPsbtTx = await (0, __1.formatInputsToSign)({
_psbt: psbt,
senderPublicKey: account.taproot.pubkey,
network: provider.network,
});
return {
psbt: formattedPsbtTx.toBase64(),
psbtHex: formattedPsbtTx.toHex(),
};
}
catch (error) {
throw new __1.OylTransactionError(error);
}
};
exports.poolPsbt = poolPsbt;
//# sourceMappingURL=factory.js.map