Compare commits

...

10 Commits

Author SHA1 Message Date
sairaj mote
cd80c73bc7 updating btcOperator module
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
2024-01-19 01:30:47 +05:30
sairaj mote
229b7c1e93 adding multi API support 2024-01-03 22:51:44 +05:30
sairaj mote
e479233f27 Revert "Update btcOperator.js"
This reverts commit 7064345bc9.
2023-12-29 04:27:45 +05:30
sairaj mote
7064345bc9 Update btcOperator.js 2023-12-28 02:22:57 +05:30
sairaj mote
402f983571 Create push-dappbundle.yml 2023-12-18 22:12:29 +05:30
sairaj mote
32e3c45a28 Fixed btc to flo address conversion 2023-11-20 14:05:35 +05:30
sairaj mote
a783fac9db Added member data copy 2023-11-09 00:17:28 +05:30
sairaj mote
bbb0242166 script-path address creation will show control block to users now 2023-11-08 16:54:43 +05:30
sairaj mote
d663512ab5 Added script-path routing 2023-11-08 15:45:39 +05:30
sairaj mote
8ed229b3fe Adding script-path fee editing 2023-11-07 16:30:02 +05:30
7 changed files with 760 additions and 222 deletions

32
.github/workflows/push-dappbundle.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Workflow push to Dappbundle
on: [push]
jobs:
build:
name: Build
runs-on: self-hosted
steps:
- name: Executing remote command
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.R_HOST }}
username: ${{ secrets.P_USERNAME }}
password: ${{ secrets.P_PASSWORD }}
port: ${{ secrets.SSH_PORT }}
script: |
if [ -d "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle" ]; then
echo "Folder exists. Skipping Git clone."
else
echo "Folder does not exist. Cloning repository..."
cd ${{ secrets.DEPLOYMENT_LOCATION}}/ && git clone https://github.com/ranchimall/dappbundle.git
fi
if [ -d "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}" ]; then
echo "Repository exists. Remove folder "
rm -r "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}"
fi
echo "Cloning repository..."
cd ${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle && git clone https://github.com/ranchimall/${{ github.event.repository.name }}
cd "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}" && rm -rf .gitattributes .git .github .gitignore
cd ${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/ && git add . && git commit -m "Workflow updating files of ${{ github.event.repository.name }}" && git push "https://ranchimalldev:${{ secrets.RM_ACCESS_TOKEN }}@github.com/ranchimall/dappbundle.git"

View File

@ -233,7 +233,7 @@ details[open] > summary .down-arrow {
transform: rotate(180deg);
}
fieldset {
sm-form fieldset {
border: none;
}
@ -470,6 +470,10 @@ ul {
height: 100%;
}
.fw-500 {
font-weight: 500;
}
.padding-block-1 {
padding-block: 1rem;
}
@ -586,6 +590,20 @@ ul {
white-space: pre-line;
}
.copy-button {
display: inline-flex;
justify-content: center;
cursor: pointer;
border: none;
padding: 0.4rem;
background-color: rgba(var(--text-color, 17, 17, 17), 0.06);
border-radius: var(--button-border-radius, 0.3rem);
font-size: 0.7rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05rem;
}
.card {
background-color: rgba(var(--foreground-color), 1);
border-radius: 0.5rem;
@ -1298,6 +1316,27 @@ body.loaded .nav-item__indicator {
background-color: var(--accent-color);
}
#generate_script_path_address_popup fieldset {
padding: 1rem;
border-radius: 0.5rem;
border: solid thin rgba(var(--text-color), 0.3);
}
#generate_script_path_address_popup fieldset legend {
font-size: 0.9rem;
font-weight: 500;
padding: 0 0.5rem;
margin-left: -0.5rem;
}
#generate_script_path_address_popup .taproot-member sm-copy {
font-weight: 500;
font-size: 0.9rem;
}
#generate_script_path_address_popup .taproot-member:not(:last-of-type) {
padding-bottom: 1rem;
margin-bottom: 0.5rem;
border-bottom: solid thin rgba(var(--text-color), 0.3);
}
@media only screen and (max-width: 640px) {
.hide-on-small {
display: none;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -212,8 +212,10 @@ details[open] {
transform: rotate(180deg);
}
}
fieldset {
border: none;
sm-form {
fieldset {
border: none;
}
}
sm-input {
--border-radius: 0.5rem;
@ -430,6 +432,9 @@ ul {
.h-100 {
height: 100%;
}
.fw-500 {
font-weight: 500;
}
.padding-block-1 {
padding-block: 1rem;
@ -536,6 +541,19 @@ ul {
.ws-pre-line {
white-space: pre-line;
}
.copy-button {
display: inline-flex;
justify-content: center;
cursor: pointer;
border: none;
padding: 0.4rem;
background-color: rgba(var(--text-color, (17, 17, 17)), 0.06);
border-radius: var(--button-border-radius, 0.3rem);
font-size: 0.7rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05rem;
}
.card {
background-color: rgba(var(--foreground-color), 1);
@ -1203,6 +1221,30 @@ body.loaded .nav-item {
}
}
}
#generate_script_path_address_popup {
fieldset {
padding: 1rem;
border-radius: 0.5rem;
border: solid thin rgba(var(--text-color), 0.3);
legend {
font-size: 0.9rem;
font-weight: 500;
padding: 0 0.5rem;
margin-left: -0.5rem;
}
}
.taproot-member {
sm-copy {
font-weight: 500;
font-size: 0.9rem;
}
&:not(:last-of-type) {
padding-bottom: 1rem;
margin-bottom: 0.5rem;
border-bottom: solid thin rgba(var(--text-color), 0.3);
}
}
}
@media only screen and (max-width: 640px) {
.hide-on-small {
display: none;

View File

@ -149,7 +149,7 @@
</button>
</div>
</header>
<div id="generate_script_path_address_popup__content"></div>
<div id="generate_script_path_address_popup__content" class="grid gap-2"></div>
</sm-popup>
<sm-popup id="convert_to_taproot_popup">
<header slot="header" class="popup__header">
@ -277,6 +277,14 @@
popupStack.peek().popup.hide()
}
function copyToClipboard(data) {
navigator.clipboard.writeText(data).then(() => {
notify('Copied to clipboard', 'success')
}).catch(() => {
notify('Failed to copy to clipboard', 'error')
})
}
document.addEventListener('popupopened', e => {
switch (e.target.id) {
case 'generate_key_path_address_popup': {
@ -296,7 +304,6 @@
case 'generate_script_path_address_popup': {
const { wif, tr: { address } } = getTaprootAddress()
const privateKey = coinjs.wif2privkey(wif).privkey
console.log(wif, address)
const scriptInputs = [1];
const renderScriptInput = (index) => html`
<div class="flex gap-0-5">
@ -318,23 +325,49 @@
}
function generateScriptPathAddress() {
const schnorrPublicKey = secp256k1_schnorr.getPublicKey(hex.decode(privateKey));
const taprootTree = [...document.querySelectorAll('.member-script-input')].map(input => {
let memberScripts = [...document.querySelectorAll('.member-script-input')].map(input => input.value.trim());
memberScripts = [...new Set(memberScripts)] // remove duplicates
const taprootTree = memberScripts.map(script => {
return {
script: input.value.trim(),
script,
leafVersion: 0xc0,
}
})
const { address } = taproot.p2tr(
const { address, leaves } = taproot.p2tr(
schnorrPublicKey,
taprootTree,
undefined,
true
);
renderElem(getRef('generate_script_path_address_popup__content'), html`
<div class="grid gap-1">
<h4>Generated Taproot script-path address</h4>
<sm-copy value="${address}"></sm-copy>
</div>
<fieldset class="grid gap-0-5">
<legend>Taproot script-path address</legend>
<b class="wrap-around fw-500" style="font-size:0.9rem">${address}</b>
</fieldset>
<fieldset class="grid gap-0-5">
<legend>Members</legend>
<ul class="grid gap-0-5">
${leaves.map((leaf, index) => html`
<li class="grid gap-1 taproot-member">
<div class="flex align-center space-between">
<h5>Member #${index + 1}</h5>
<button class="copy-button" onclick=${() => copyToClipboard(`Member #${index + 1}\n\nScript: ${hex.encode(leaf.script)}\n\nControl block: ${hex.encode(leaf.controlBlock)}`)}>
COPY
</button>
</div>
<div class="grid">
<p class="label">Script</p>
<b class="wrap-around" style="font-size:0.9rem">${hex.encode(leaf.script)}</b>
</div>
<div class="grid">
<p class="label">Control block</p>
<b class="wrap-around" style="font-size:0.9rem">${hex.encode(leaf.controlBlock)}</b>
</div>
</li>
`)}
</ul>
</fieldset>
`);
}
function renderScriptInputList() {
@ -501,7 +534,7 @@
.then(() => {
selectedCurrency = localStorage.getItem('taproot-wallet-currency') || 'btc'
setTimeout(() => {
document.getElementById('currency_selector').value = selectedCurrency
getRef('currency_selector').value = selectedCurrency
}, 100)
})
.catch(e => {
@ -1338,8 +1371,8 @@
const [suggestedFeeStatus, setSuggestedFeeStatus] = $signal(['idle'])
const [feeType, setFeeType] = $signal('suggested')
const [atTaprootStep, setAtTaprootStep] = $signal(1);
const [spendingType, setSpendingType] = $signal('key-path');
router.addRoute('send', (state) => {
let { params: { type = 'key-path' } } = state
const [isSignRequired, setIsSignRequired] = $signal(false)
const renderSendPage = () => {
let feeSection = ''
@ -1363,7 +1396,7 @@
`
}
let form = '';
if (spendingType() === 'script-path') {
if (type === 'script-path') {
switch (atTaprootStep()) {
case 1:
form = html`
@ -1511,12 +1544,12 @@
<h3>
Perform Transaction
</h3>
<sm-chips onchange=${e => setSpendingType(e.target.value)}>
<sm-chip value="key-path" ?selected=${spendingType() === 'key-path'}>Key-path</sm-chip>
<sm-chip value="script-path" ?selected=${spendingType() === 'script-path'}>Script-path</sm-chip>
<sm-chips onchange=${e => location.hash = `/send?type=${e.target.value}`}>
<sm-chip value="key-path" ?selected=${type === 'key-path'}>Key-path</sm-chip>
<sm-chip value="script-path" ?selected=${type === 'script-path'}>Script-path</sm-chip>
</sm-chips>
</div>
${spendingType() === 'script-path' ? html`
${type === 'script-path' ? html`
<ul id="taproot_steps" class="steps">
${[1, 2].map(step => html`
<li class=${`step ${atTaprootStep() === step ? 'active' : ''} ${atTaprootStep() > step ? 'done' : ''}`}></li>
@ -1815,7 +1848,7 @@
convertedTo = 'BTC'
break;
case 'btc':
convertedAddress = floCrypto.getFloID(addressToConvert);
convertedAddress = floCrypto.toFloID(addressToConvert);
convertedTo = 'FLO'
break;
}
@ -2105,15 +2138,13 @@
<svg class="icon visible" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <title>Show password</title> <path d="M0 0h24v24H0z" fill="none"></path> <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"> </path> </svg>
</label>
</sm-input>
</div>
<div class="grid gap-0-5">
${finalScriptWitness.length > 3 ? html`
<sm-input id="signature_place_input" placeholder="Position of signature. (1, 2, ...)" type="number" min="1" max=${requiredSolutions + 1} error-text=${`Value must be between 1 and ${requiredSolutions + 1}`} animate required></sm-input>
` : html`
<sm-input id="signature_place_input" placeholder="Position of signature" type="number" value="1" readonly animate required></sm-input>
`}
<p>*The generated signature will be used as one of the solutions.</p>
</div>
${finalScriptWitness.length > 3 ? html`
<sm-input id="signature_place_input" placeholder="Position of signature. (1, 2, ...)" type="number" min="1" max=${requiredSolutions + 1} error-text=${`Value must be between 1 and ${requiredSolutions + 1}`} animate required></sm-input>
` : html`
<sm-input id="signature_place_input" placeholder="Position of signature" class="hidden" type="number" value="1" readonly animate required></sm-input>
`}
` : ''}
<div class="grid gap-0-5">
<p>
@ -2124,7 +2155,7 @@
</sm-input>
</div>
<div class="multi-state-button">
<button id="increase_fee" class="button button--primary" onclick=${increaseFee} type="submit" disabled>Increase fee</button>
<button id="increase_fee" class="button button--primary" onclick=${() => increaseFee('script-path')} type="submit" disabled>Increase fee</button>
</div>
</sm-form>
`)
@ -2174,12 +2205,12 @@
</sm-input>
</div>
<div class="multi-state-button">
<button id="increase_fee" class="button button--primary" onclick=${increaseFee} type="submit" disabled>Increase fee</button>
<button id="increase_fee" class="button button--primary" onclick=${() => increaseFee('key-path')} type="submit" disabled>Increase fee</button>
</div>
</sm-form>
`)
}
document.getElementById('new_fee').querySelector('.currency-symbol').innerHTML = currencyIcons[selectedCurrency]
getRef('new_fee').querySelector('.currency-symbol').innerHTML = currencyIcons[selectedCurrency]
openPopup('increase_fee_popup')
} catch (e) {
console.error(e)
@ -2187,39 +2218,54 @@
buttonLoader(button, false)
}
}
async function increaseFee() {
buttonLoader(document.getElementById('increase_fee'), true)
const newFee = parseFloat((parseFloat(document.getElementById('new_fee').value.trim()) / (globalExchangeRate[selectedCurrency] || 1)).toFixed(8))
const privateKeys = []
document.querySelectorAll('.increase-fee-sender').forEach(sender => {
const address = sender.querySelector('.sender__address').textContent.trim()
const privateKey = sender.querySelector('.sender__private-key').value.trim()
if (!btcOperator.verifyKey(address, privateKey))
return notify(`Invalid private key for address ${address}`, 'error')
if (privateKey) {
privateKeys.push(privateKey)
}
})
console.log(changingFeeOf, newFee, privateKeys)
async function increaseFee(type = 'key-path') {
try {
let signedTxHex = await taprootEditFee({
txId: changingFeeOf,
new_fee: newFee,
private_keys: privateKeys
})
btcOperator.broadcastTx(signedTxHex).then(txId => {
buttonLoader(getRef('increase_fee'), true)
const newFee = parseFloat((parseFloat(getRef('new_fee').value.trim()) / (globalExchangeRate[selectedCurrency] || 1)).toFixed(8))
let txHex
if (type === 'key-path') {
const privateKeys = []
document.querySelectorAll('.increase-fee-sender').forEach(sender => {
const address = sender.querySelector('.sender__address').textContent.trim()
const privateKey = sender.querySelector('.sender__private-key').value.trim()
if (!btcOperator.verifyKey(address, privateKey))
return notify(`Invalid private key for address ${address}`, 'error')
if (privateKey) {
privateKeys.push(privateKey)
}
})
txHex = await taprootEditFee({
txId: changingFeeOf,
newFee: newFee,
privateKeys
})
} else {
const providedSolutions = [...document.querySelectorAll('.solution-input')].map(input => input.value.trim())
const signaturePlace = parseInt(getRef('signature_place_input').value.trim())
const signerPrivateKey = getRef('signer_input') ? getRef('signer_input').value.trim() : null;
const editParams = {
txId: changingFeeOf,
newFee,
providedSolutions,
signaturePlace,
}
if (signerPrivateKey)
editParams.signerSecretKey = hex.decode(coinjs.wif2privkey(signerPrivateKey).privkey)
txHex = await taprootEditFee(editParams)
}
btcOperator.broadcastTx(txHex).then(txId => {
console.log(txId)
closePopup()
showTransactionResult('success', txId)
}).catch(e => {
notify(e, 'error')
}).finally(_ => {
buttonLoader(document.getElementById('increase_fee'), false)
buttonLoader(getRef('increase_fee'), false)
changingFeeOf = null
})
} catch (err) {
notify(e, 'error')
buttonLoader(document.getElementById('increase_fee'), false)
buttonLoader(getRef('increase_fee'), false)
}
}
router.addRoute('create', state => {
@ -2487,29 +2533,27 @@
})
}
const taprootEditFee = function ({ txId, new_fee, private_keys, change_only = true }) {
const taprootEditFee = function ({ txId, newFee, privateKeys, changeOnly = true, signerSecretKey, providedSolutions, signaturePlace }) {
return new Promise((resolve, reject) => {
if (!Array.isArray(private_keys))
private_keys = [private_keys];
getTaprootTx(txId).then(({ tx, rawTx }) => {
const isScriptPath = tx.inputs[0].finalScriptWitness && tx.inputs[0].finalScriptWitness.length > 1;
const parsedTx = parseTransaction(rawTx)
if (parsedTx.fee >= new_fee)
if (parsedTx.fee >= newFee)
return reject("Fees can only be increased");
//editable addresses in output values (for fee increase)
parsedTx edit_output_address = new Set();
if (change_only === true) //allow only change values (ie, sender address) to be edited to inc fee
const edit_output_address = new Set();
if (changeOnly === true) //allow only change values (ie, sender address) to be edited to inc fee
parsedTx.inputs.forEach(inp => edit_output_address.add(inp.address));
else if (change_only === false) //allow all output values to be edited
else if (changeOnly === false) //allow all output values to be edited
parsedTx.outputs.forEach(out => edit_output_address.add(out.address));
else if (typeof change_only == 'string') // allow only given receiver id output to be edited
edit_output_address.add(change_only);
else if (Array.isArray(change_only)) //allow only given set of receiver id outputs to be edited
change_only.forEach(id => edit_output_address.add(id));
else if (typeof changeOnly == 'string') // allow only given receiver id output to be edited
edit_output_address.add(changeOnly);
else if (Array.isArray(changeOnly)) //allow only given set of receiver id outputs to be edited
changeOnly.forEach(id => edit_output_address.add(id));
//edit output values to increase fee
let inc_fee = btcOperator.util.BTC_to_Sat(new_fee - parsedTx.fee);
let inc_fee = btcOperator.util.BTC_to_Sat(newFee - parsedTx.fee);
if (inc_fee < MIN_FEE_UPDATE)
return reject(`Insufficient additional fee. Minimum increment: ${MIN_FEE_UPDATE}`);
for (let i = tx.outs.length - 1; i >= 0 && inc_fee > 0; i--) //reduce in reverse order
@ -2527,19 +2571,36 @@
}
}
if (inc_fee > 0) {
let max_possible_fee = btcOperator.util.BTC_to_Sat(new_fee) - inc_fee; //in satoshi
let max_possible_fee = btcOperator.util.BTC_to_Sat(newFee) - inc_fee; //in satoshi
return reject(`Insufficient output values to increase fee. Maximum fee possible: ${btcOperator.util.Sat_to_BTC(max_possible_fee)}`);
}
tx.outs = tx.outs.filter(o => o.value >= DUST_AMT); //remove all output with value less than DUST amount
if (isScriptPath) {
tx.inputs.forEach((input, index) => {
// remove every element from the finalScriptWitness except the last two
const scriptWitness = input.finalScriptWitness.slice(-2)
const [userScript, controlBlock] = scriptWitness;
const controlBlockVersion = taproot.TaprootControlBlock.decode(controlBlock).version
const providedSolutions = scriptWitness.slice(0, -1)
if (providedSolutions && providedSolutions.length)
scriptWitness.unshift(...providedSolutions)
if (signerSecretKey) {
const { amount } = consumedUtxos[index]
const hash = tx.preimageWitnessV1(index, [script], 0, [amount], undefined, userScript, controlBlockVersion)
const sig = secp.schnorr.signSync(hash, signerSecretKey, new Uint8Array(32))
scriptWitness.splice((signaturePlace - 1), 0, sig)
}
input.finalScriptWitness = scriptWitness
})
} else {
//remove existing signatures and reset the scripts
if (!Array.isArray(privateKeys))
privateKeys = [privateKeys];
let wif_keys = [];
for (let i in tx.ins) {
const addr = parsedTx.inputs[i].address;
//find the correct key for addr
const privKey = private_keys.find(pk => btcOperator.verifyKey(addr, pk));
const privKey = privateKeys.find(pk => btcOperator.verifyKey(addr, pk));
if (!privKey)
return reject(`Private key missing for ${addr}`);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long