Last active
May 18, 2022 10:33
-
-
Save tiero/e4ab3bf899785ff5e55218171955421f to your computer and use it in GitHub Desktop.
Spend an advanced transaction combining LDK and Marina Web Provider
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as ecc from 'tiny-secp256k1'; | |
import { AssetHash, confidential, networks, address, Psbt, script } from 'liquidjs-lib'; | |
import { craftMultipleRecipientsPset, greedyCoinSelector, UnblindedOutput, AddressInterface } from 'ldk'; | |
async function main() { | |
// 0. First, let's get our coins available from marina | |
// NOTICE: This is not yet deployed version yet 👉 https://github.com/vulpemventures/marina/issues/342 | |
// Can be done now with LDK's `fetchAndUnblindUtxos` method | |
const coins = await window.marina.getCoins(); | |
const changeAddress = await window.marina.getNextChangeAddress(); | |
// 1. create an empty psbt object | |
const pset = new Psbt({ network: networks.regtest }); | |
// 2. add a custom OP_RETURN output to psbt | |
pset.addOutput({ | |
script: script.compile([script.OPS.OP_RETURN, Buffer.from('hello world', 'utf-8')]), | |
value: confidential.satoshiToConfidentialValue(0), | |
asset: AssetHash.fromHex(networks.regtest.assetHash, false).bytes, | |
nonce: Buffer.alloc(0), | |
}); | |
// 3. add P2TR address(es) as recipient(s) to psbt | |
const recipients = [ | |
{ | |
asset: networks.regtest.assetHash, | |
value: 50000, | |
//P2TR address | |
address: "ert1pjapc40taf80art5zfda8tsy9pt5gq3tsuuw5r7n3arfgjexeytmqa2qrvy", | |
}, | |
] | |
// We need to be sure if the PSBT has other inputs added by someone else before marina does the funding | |
const marinaInputLastIndex = pset.data.inputs.length - 1 | |
const marinaOutputLastIndex = pset.data.outputs.length - 1; | |
// 4. Serialize as base64 the psbt to be passed to LDK | |
const tx = pset.toBase64(); | |
// 5. Craft the transaction with multiple outputs and add fee & change output to the psbt | |
const unsignedTx = craftMultipleRecipientsPset({ | |
psetBase64: tx, | |
unspents: (coins as UnblindedOutput[]), | |
recipients, | |
coinSelector: greedyCoinSelector(), | |
changeAddressByAsset: (_: string) => changeAddress.confidentialAddress, | |
addFee: true, | |
}); | |
// deserialize and inspect the transaction | |
const ptx = Psbt.fromBase64(unsignedTx); | |
//console.log(decoded.TX.toHex()); | |
// 6. Blind the marina change output (and unblind marina's input to do so) | |
// create a map input index => blinding private key | |
// we need this to unblind the utxo data | |
// add your outputs and craftMultipleRecipientsPset | |
const inputBlindingMap = new Map<number, Buffer>(); | |
// here if we get -1 means before marina funding, there were no inputs, so we can safely loop over all inputs in the transasction | |
const marinaInputs = marinaInputLastIndex === -1 ? ptx.data.inputs : ptx.data.inputs.slice(marinaInputLastIndex + 1); | |
// loop over the inputs, get the blinding private key for each of them and add to the map | |
marinaInputs.forEach(async (input, index) => { | |
const blindPrivateKey = await getBlindingKeyByScript( | |
input.witnessUtxo.script.toString('hex') | |
); | |
inputBlindingMap.set( | |
index, | |
Buffer.from(blindPrivateKey, 'hex') | |
) | |
}); | |
// create a map output index => blinding PUBLIC (!) key | |
// this is needed to blind the marina change output | |
const outputBlindingMap = new Map<number, Buffer>() | |
// we know the last output is the fee output and we need to slice the outputs before that and after the marina last index as well | |
const feeOutputIndex = ptx.data.outputs.length -1; | |
const marinaOutputs = marinaOutputLastIndex === -1 ? ptx.data.outputs : ptx.data.outputs.slice(marinaOutputLastIndex + 1, feeOutputIndex - 1); | |
marinaOutputs.forEach((_, index) => { | |
outputBlindingMap.set( | |
index, | |
// this is the blinding publick key of the change output for marina | |
address.fromConfidential(changeAddress.confidentialAddress).blindingKey | |
); | |
}); | |
await ptx.blindOutputsByIndex( | |
Psbt.ECCKeysGenerator(ecc), | |
inputBlindingMap, | |
outputBlindingMap | |
); | |
// 7. Sign the transaction's inputs with Marina | |
const signedTx = await window.marina.signTransaction(ptx.toBase64()); | |
// 8. Broadcast the transaction to the network (need to ba added to Marina) | |
const txid = await window.marina.broadcastTransaction(signedTx); | |
console.log(txid); | |
} | |
async function getBlindingKeyByScript(script: string): Promise<string> { | |
try { | |
// get addresses from marina | |
const addresses = await window.marina.getAddresses(); | |
// find the address of the requested script | |
let found: AddressInterface | undefined; | |
addresses.forEach((addr: AddressInterface) => { | |
const currentScript = address | |
.toOutputScript(addr.confidentialAddress) | |
.toString('hex'); | |
if (currentScript === script) { | |
found = addr; | |
} | |
}); | |
if (!found) throw new Error('no blinding key for script ' + script); | |
return found.blindingPrivateKey; | |
} catch (e) { | |
throw e; | |
} | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment