-
Notifications
You must be signed in to change notification settings - Fork 22
add the package to broadcast to UnrollStep #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,7 @@ export namespace Unroll { | |||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export type UnrollStep = { | ||||||||||||||||||||||
| tx: Transaction; | ||||||||||||||||||||||
| pkg: [parent: string, child: string]; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
louisinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
|
|
@@ -187,10 +188,12 @@ export namespace Unroll { | |||||||||||||||||||||
| tx.finalize(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const pkg = await this.bumper.bumpP2A(tx); | ||||||||||||||||||||||
| return { | ||||||||||||||||||||||
| type: StepType.UNROLL, | ||||||||||||||||||||||
| tx, | ||||||||||||||||||||||
| do: doUnroll(this.bumper, this.explorer, tx), | ||||||||||||||||||||||
| pkg, | ||||||||||||||||||||||
| do: doUnroll(this.explorer, pkg), | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -227,111 +230,125 @@ export namespace Unroll { | |||||||||||||||||||||
| vtxoTxids: string[], | ||||||||||||||||||||||
| outputAddress: string | ||||||||||||||||||||||
| ): Promise<string> { | ||||||||||||||||||||||
| const chainTip = await wallet.onchainProvider.getChainTip(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| let vtxos = await wallet.getVtxos({ withUnrolled: true }); | ||||||||||||||||||||||
| vtxos = vtxos.filter((vtxo) => vtxoTxids.includes(vtxo.txid)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (vtxos.length === 0) { | ||||||||||||||||||||||
| throw new Error("No vtxos to complete unroll"); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const inputs: TransactionInputUpdate[] = []; | ||||||||||||||||||||||
| let totalAmount = 0n; | ||||||||||||||||||||||
| const txWeightEstimator = TxWeightEstimator.create(); | ||||||||||||||||||||||
| for (const vtxo of vtxos) { | ||||||||||||||||||||||
| if (!vtxo.isUnrolled) { | ||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||
| `Vtxo ${vtxo.txid}:${vtxo.vout} is not fully unrolled, use unroll first` | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| const signedTx = await prepareUnrollTransaction( | ||||||||||||||||||||||
| wallet, | ||||||||||||||||||||||
| vtxoTxids, | ||||||||||||||||||||||
| outputAddress | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| await wallet.onchainProvider.broadcastTransaction(signedTx.hex); | ||||||||||||||||||||||
| return signedTx.id; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const txStatus = await wallet.onchainProvider.getTxStatus( | ||||||||||||||||||||||
| vtxo.txid | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| if (!txStatus.confirmed) { | ||||||||||||||||||||||
| throw new Error(`tx ${vtxo.txid} is not confirmed`); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Prepares the transaction that spends the CSV path to complete unrolling a VTXO. | ||||||||||||||||||||||
| * @param wallet the wallet owning the VTXO(s) | ||||||||||||||||||||||
| * @param vtxoTxIds the txids of the VTXO(s) to complete unroll | ||||||||||||||||||||||
| * @param outputAddress the address to send the unrolled funds to | ||||||||||||||||||||||
| * @throws if the VTXO(s) are not fully unrolled, if the txids are not found, if the tx is not confirmed, if no exit path is found or not available | ||||||||||||||||||||||
| * @returns the transaction spending the unrolled funds | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export async function prepareUnrollTransaction( | ||||||||||||||||||||||
| wallet: Wallet, | ||||||||||||||||||||||
| vtxoTxIds: string[], | ||||||||||||||||||||||
| outputAddress: string | ||||||||||||||||||||||
| ): Promise<Transaction> { | ||||||||||||||||||||||
| const chainTip = await wallet.onchainProvider.getChainTip(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| let vtxos = await wallet.getVtxos({ withUnrolled: true }); | ||||||||||||||||||||||
| vtxos = vtxos.filter((vtxo) => vtxoTxIds.includes(vtxo.txid)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (vtxos.length === 0) { | ||||||||||||||||||||||
| throw new Error("No vtxos to complete unroll"); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const exit = availableExitPath( | ||||||||||||||||||||||
| { height: txStatus.blockHeight, time: txStatus.blockTime }, | ||||||||||||||||||||||
| chainTip, | ||||||||||||||||||||||
| vtxo | ||||||||||||||||||||||
| const inputs: TransactionInputUpdate[] = []; | ||||||||||||||||||||||
| let totalAmount = 0n; | ||||||||||||||||||||||
| const txWeightEstimator = TxWeightEstimator.create(); | ||||||||||||||||||||||
| for (const vtxo of vtxos) { | ||||||||||||||||||||||
| if (!vtxo.isUnrolled) { | ||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||
| `Vtxo ${vtxo.txid}:${vtxo.vout} is not fully unrolled, use unroll first` | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| if (!exit) { | ||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||
| `no available exit path found for vtxo ${vtxo.txid}:${vtxo.vout}` | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const spendingLeaf = VtxoScript.decode(vtxo.tapTree).findLeaf( | ||||||||||||||||||||||
| hex.encode(exit.script) | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| if (!spendingLeaf) { | ||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||
| `spending leaf not found for vtxo ${vtxo.txid}:${vtxo.vout}` | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| const txStatus = await wallet.onchainProvider.getTxStatus(vtxo.txid); | ||||||||||||||||||||||
| if (!txStatus.confirmed) { | ||||||||||||||||||||||
| throw new Error(`tx ${vtxo.txid} is not confirmed`); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| totalAmount += BigInt(vtxo.value); | ||||||||||||||||||||||
| inputs.push({ | ||||||||||||||||||||||
| txid: vtxo.txid, | ||||||||||||||||||||||
| index: vtxo.vout, | ||||||||||||||||||||||
| tapLeafScript: [spendingLeaf], | ||||||||||||||||||||||
| sequence: 0xffffffff - 1, | ||||||||||||||||||||||
| witnessUtxo: { | ||||||||||||||||||||||
| amount: BigInt(vtxo.value), | ||||||||||||||||||||||
| script: VtxoScript.decode(vtxo.tapTree).pkScript, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| sighashType: SigHash.DEFAULT, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| txWeightEstimator.addTapscriptInput( | ||||||||||||||||||||||
| 64, | ||||||||||||||||||||||
| spendingLeaf[1].length, | ||||||||||||||||||||||
| TaprootControlBlock.encode(spendingLeaf[0]).length | ||||||||||||||||||||||
| const exit = availableExitPath( | ||||||||||||||||||||||
| { height: txStatus.blockHeight, time: txStatus.blockTime }, | ||||||||||||||||||||||
| chainTip, | ||||||||||||||||||||||
| vtxo | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| if (!exit) { | ||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||
| `no available exit path found for vtxo ${vtxo.txid}:${vtxo.vout}` | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const tx = new Transaction({ version: 2 }); | ||||||||||||||||||||||
| for (const input of inputs) { | ||||||||||||||||||||||
| tx.addInput(input); | ||||||||||||||||||||||
| const spendingLeaf = VtxoScript.decode(vtxo.tapTree).findLeaf( | ||||||||||||||||||||||
| hex.encode(exit.script) | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| if (!spendingLeaf) { | ||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||
| `spending leaf not found for vtxo ${vtxo.txid}:${vtxo.vout}` | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| txWeightEstimator.addP2TROutput(); | ||||||||||||||||||||||
| totalAmount += BigInt(vtxo.value); | ||||||||||||||||||||||
| inputs.push({ | ||||||||||||||||||||||
| txid: vtxo.txid, | ||||||||||||||||||||||
| index: vtxo.vout, | ||||||||||||||||||||||
| tapLeafScript: [spendingLeaf], | ||||||||||||||||||||||
| sequence: 0xffffffff - 1, | ||||||||||||||||||||||
| witnessUtxo: { | ||||||||||||||||||||||
| amount: BigInt(vtxo.value), | ||||||||||||||||||||||
| script: VtxoScript.decode(vtxo.tapTree).pkScript, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| sighashType: SigHash.DEFAULT, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| txWeightEstimator.addTapscriptInput( | ||||||||||||||||||||||
| 64, | ||||||||||||||||||||||
| spendingLeaf[1].length, | ||||||||||||||||||||||
| TaprootControlBlock.encode(spendingLeaf[0]).length | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| let feeRate = await wallet.onchainProvider.getFeeRate(); | ||||||||||||||||||||||
| if (!feeRate || feeRate < Wallet.MIN_FEE_RATE) { | ||||||||||||||||||||||
| feeRate = Wallet.MIN_FEE_RATE; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| const feeAmount = txWeightEstimator.vsize().fee(BigInt(feeRate)); | ||||||||||||||||||||||
| if (feeAmount > totalAmount) { | ||||||||||||||||||||||
| throw new Error("fee amount is greater than the total amount"); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| const tx = new Transaction({ version: 2 }); | ||||||||||||||||||||||
| for (const input of inputs) { | ||||||||||||||||||||||
| tx.addInput(input); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| tx.addOutputAddress(outputAddress, totalAmount - feeAmount); | ||||||||||||||||||||||
| txWeightEstimator.addP2TROutput(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const signedTx = await wallet.identity.sign(tx); | ||||||||||||||||||||||
| signedTx.finalize(); | ||||||||||||||||||||||
| let feeRate = await wallet.onchainProvider.getFeeRate(); | ||||||||||||||||||||||
| if (!feeRate || feeRate < Wallet.MIN_FEE_RATE) { | ||||||||||||||||||||||
| feeRate = Wallet.MIN_FEE_RATE; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| const feeAmount = txWeightEstimator.vsize().fee(BigInt(feeRate)); | ||||||||||||||||||||||
|
Comment on lines
+326
to
+330
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Search for getFeeRate definition and return type
rg -n -B2 -A10 'getFeeRate\(' --type tsRepository: arkade-os/ts-sdk Length of output: 5641 🏁 Script executed: #!/bin/bash
# Check BigInt usage with feeRate
rg -n 'BigInt.*feeRate|BigInt.*fee' --type tsRepository: arkade-os/ts-sdk Length of output: 154 🏁 Script executed: #!/bin/bash
# Check the type of feeRate in context
rg -n 'feeRate.*=' --type ts | head -20Repository: arkade-os/ts-sdk Length of output: 754 🏁 Script executed: #!/bin/bash
# Look for Wallet.MIN_FEE_RATE definition and its type
rg -n 'MIN_FEE_RATE' --type ts -B2 -A2Repository: arkade-os/ts-sdk Length of output: 1668 Convert fractional Line 330 converts Other code in the repository handles this correctly using Proposed fix- const feeAmount = txWeightEstimator.vsize().fee(BigInt(feeRate));
+ const feeAmount = txWeightEstimator.vsize().fee(BigInt(Math.ceil(feeRate)));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| if (feeAmount > totalAmount) { | ||||||||||||||||||||||
| throw new Error("fee amount is greater than the total amount"); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| await wallet.onchainProvider.broadcastTransaction(signedTx.hex); | ||||||||||||||||||||||
| tx.addOutputAddress(outputAddress, totalAmount - feeAmount); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return signedTx.id; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| const signedTx = await wallet.identity.sign(tx); | ||||||||||||||||||||||
| signedTx.finalize(); | ||||||||||||||||||||||
| return signedTx; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function sleep(ms: number): Promise<void> { | ||||||||||||||||||||||
| return new Promise((resolve) => setTimeout(resolve, ms)); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function doUnroll( | ||||||||||||||||||||||
| bumper: AnchorBumper, | ||||||||||||||||||||||
| onchainProvider: OnchainProvider, | ||||||||||||||||||||||
| tx: Transaction | ||||||||||||||||||||||
| pkg: Unroll.UnrollStep["pkg"] | ||||||||||||||||||||||
| ): () => Promise<void> { | ||||||||||||||||||||||
| return async () => { | ||||||||||||||||||||||
| const [parent, child] = await bumper.bumpP2A(tx); | ||||||||||||||||||||||
| await onchainProvider.broadcastTransaction(parent, child); | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| return () => | ||||||||||||||||||||||
| onchainProvider.broadcastTransaction(...pkg).then(() => undefined); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
louisinger marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| function doWait( | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.