From ba549b787f50ab2bb3a5b23763f15bfef584bbcb Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Fri, 1 Dec 2023 20:55:24 +0530 Subject: [PATCH] feature and bug fixes -- added option to allow unconfirmed UTXOs while creating transaction --- css/main.css | 17 +++++++------- css/main.min.css | 2 +- css/main.scss | 18 +++++++-------- index.html | 47 ++++++++++++++++++++++++++++++-------- scripts/btcOperator.js | 29 +++++++++++++---------- scripts/btcOperator.min.js | 2 +- 6 files changed, 75 insertions(+), 40 deletions(-) diff --git a/css/main.css b/css/main.css index 0d73b8b..941e81d 100644 --- a/css/main.css +++ b/css/main.css @@ -929,7 +929,15 @@ ol li::before { font-size: 0.9rem; color: rgba(var(--text-color), 0.8); } -.transaction .pending-badge { +.transaction__amount { + font-size: 1rem; + font-weight: 700; +} +.transaction__id { + font-size: 0.9rem; +} + +.pending-badge { font-size: 0.8rem; padding: 0.2rem 0.5rem; border-radius: 0.5rem; @@ -938,13 +946,6 @@ ol li::before { font-weight: 500; justify-self: flex-start; } -.transaction__amount { - font-size: 1rem; - font-weight: 700; -} -.transaction__id { - font-size: 0.9rem; -} .tx-participant:not(:last-of-type) { margin-right: 0.5rem; diff --git a/css/main.min.css b/css/main.min.css index d24b992..46f3d85 100644 --- a/css/main.min.css +++ b/css/main.min.css @@ -1 +1 @@ -*{padding:0;margin:0;box-sizing:border-box;font-family:"Roboto",sans-serif}:root{font-size:clamp(1rem,1.2vmax,1.2rem)}html,body{height:100%}body{--accent-color: #4d77ff;--accent-color-rgb: 77, 119, 255;--text-color: 20, 20, 20;--foreground-color: 252, 253, 255;--background-color: 241, 243, 248;--danger-color: rgb(225, 58, 58);--green: #1cad59;--yellow: rgb(255, 207, 65);scrollbar-width:thin;scrollbar-gutter:stable;color:rgba(var(--text-color), 1);background-color:rgba(var(--background-color), 1);transition:background-color .3s;position:relative;display:flex;flex-direction:column}body[data-theme=dark]{--accent-color: #a0b6ff;--accent-color-rgb: 160, 182, 255;--text-color: 220, 220, 220;--foreground-color: 27, 28, 29;--background-color: 21, 22, 22;--danger-color: rgb(255, 106, 106);--green: #00e676;--yellow: rgb(255, 213, 5)}body[data-theme=dark] sm-popup::part(popup){background-color:rgba(var(--foreground-color), 1)}p,strong{font-size:.9rem;max-width:65ch;line-height:1.7;color:rgba(var(--text-color), 0.9)}.warning{line-height:normal;padding:1rem;background-color:khaki;border-radius:.5rem;font-weight:500;color:rgba(0,0,0,.7)}a{text-decoration:none;color:var(--accent-color)}a:focus-visible{box-shadow:0 0 0 .1rem rgba(var(--text-color), 1) inset}button,.button{-webkit-user-select:none;-moz-user-select:none;user-select:none;position:relative;display:inline-flex;border:none;background-color:rgba(0,0,0,0);overflow:hidden;-webkit-tap-highlight-color:rgba(0,0,0,0);align-items:center;font-size:.9rem;font-weight:500;white-space:nowrap;padding:.8rem;border-radius:.5rem;justify-content:center;flex-shrink:0}button:focus-visible,.button:focus-visible{outline:var(--accent-color) solid medium}button:not(:disabled),.button:not(:disabled){color:var(--accent-color);cursor:pointer}button .icon,.button .icon{fill:var(--accent-color)}.button{display:inline-flex;background-color:rgba(var(--text-color), 0.06)}.button--primary,.button--danger{color:rgba(var(--background-color), 1) !important}.button--primary .icon,.button--danger .icon{fill:rgba(var(--background-color), 1)}.button--primary{background-color:var(--accent-color)}.button--danger{background-color:var(--danger-color)}.button--small{padding:.4rem .5rem}.button--colored{color:var(--accent-color) !important}.button--colored .icon{fill:var(--accent-color)}.cta{text-transform:uppercase;font-size:.9rem;font-weight:700;letter-spacing:.05em;padding:.8rem 1rem}.icon{width:1.2rem;height:1.2rem;fill:rgba(var(--text-color), 0.8);flex-shrink:0}.icon-only{padding:.5rem;border-radius:.3rem}button:disabled{opacity:.5}a:-webkit-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:-moz-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}details summary{display:flex;gap:.3rem;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;align-items:center;color:var(--accent-color)}details summary .down-arrow{fill:var(--accent-color)}details[open] summary{margin-bottom:1rem}details[open]>summary .down-arrow{transform:rotate(180deg)}sm-input,sm-textarea{width:100%;--border-radius: 0.5rem;--background-color: rgba(var(--foreground-color), 1)}sm-input button .icon,sm-textarea button .icon{fill:var(--accent-color)}sm-button{--padding: 0.8rem}sm-button[variant=primary] .icon{fill:rgba(var(--background-color), 1)}sm-button[disabled] .icon{fill:rgba(var(--text-color), 0.6)}sm-button.danger{--background: var(--danger-color);color:rgba(var(--background-color), 1)}sm-spinner{--size: 1rem;--stroke-width: 0.1rem}sm-form{--gap: 1rem}sm-select{font-size:.9rem;--padding: 0.6rem 0.3rem 0.6rem 0.6rem}sm-option{font-size:.9rem}sm-chips{--gap: 0;background-color:rgba(var(--text-color), 0.06);border-radius:.5rem;padding:.3rem}sm-chip{font-size:.9rem;--border-radius: 0.3rem;-webkit-user-select:none;-moz-user-select:none;user-select:none}sm-chip[selected]{color:rgba(var(--background-color), 1);--background: var(--accent-color)}sm-notifications{z-index:100000}ul,ol{list-style:none}ol{counter-reset:item}ol li{position:relative;display:flex;align-items:flex-start;counter-increment:item}ol li:not(:last-of-type){padding-bottom:1.5rem}ol li:not(:last-of-type)::after{content:"";position:absolute;width:.1rem;height:calc(100% - 2.2rem);background:var(--accent-color);margin-left:.7rem;margin-top:2rem}ol li::before{content:counter(item);display:flex;align-items:center;justify-content:center;text-align:center;font-size:.9rem;font-weight:500;margin-top:.15rem;margin-right:1rem;line-height:1;width:1.5rem;height:1.5rem;border-radius:100%;flex-shrink:0;color:rgba(var(--text-color), 0.8);background:rgba(var(--text-color), 0.1)}.overflow-ellipsis{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.wrap-around{overflow-wrap:break-word;word-wrap:break-word;word-break:break-word}.full-bleed{grid-column:1/-1}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.sticky{position:-webkit-sticky;position:sticky}.top-0{top:0}.flex{display:flex}.flex-wrap{flex-wrap:wrap}.flex-1{flex:1}.flex-direction-column{flex-direction:column}.grid{display:grid}.flow-column{grid-auto-flow:column}.gap-0-3{gap:.3rem}.gap-0-5{gap:.5rem}.gap-1{gap:1rem}.gap-1-5{gap:1.5rem}.gap-2{gap:2rem}.gap-3{gap:3rem}.text-align-right{text-align:right}.align-content-start{align-content:flex-start}.align-items-start{align-items:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.text-center{text-align:center}.justify-start{justify-items:start}.justify-center{justify-content:center}.justify-right{margin-left:auto}.align-self-center{align-self:center}.align-self-end{align-self:end}.justify-self-center{justify-self:center}.justify-self-start{justify-self:start}.justify-self-end{justify-self:end}.flex-direction-column{flex-direction:column}.space-between{justify-content:space-between}.w-100{width:100%}.h-100{height:100%}.label{font-size:.9rem;color:rgba(var(--text-color), 0.8);font-weight:500;margin-bottom:.2rem}.button--primary .ripple,.button--danger .ripple{background:radial-gradient(circle, rgba(var(--background-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%)}.ripple{height:8rem;width:8rem;position:absolute;border-radius:50%;transform:scale(0);background:radial-gradient(circle, rgba(var(--text-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%);pointer-events:none}.interactive{position:relative;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:rgba(0,0,0,0)}.empty-state{display:grid;width:100%;padding:1.5rem 0}.observe-empty-state:empty{display:none !important}.observe-empty-state:not(:empty)+.empty-state{display:none !important}.bullet-point{display:flex;align-items:center;justify-content:center;margin:0 .8ch}.bullet-point::after{content:"";height:.4ch;width:.4ch;border-radius:.5em;background-color:var(--accent-color)}.margin-right-0-3{margin-right:.3rem}.margin-right-0-5{margin-right:.5rem}.margin-right-auto{margin-right:auto}.margin-left-0-5{margin-left:.5rem}.margin-left-auto{margin-left:auto}.margin-top-1-5{margin-top:1.5rem}.margin-bottom-0-5{margin-bottom:.5rem}.margin-bottom-1{margin-bottom:1rem}.margin-bottom-1-5{margin-bottom:1.5rem}.margin-bottom-2{margin-bottom:2rem}.icon-button{padding:.6rem;border-radius:.8rem;background-color:rgba(var(--text-color), 0.1);height:-webkit-max-content;height:-moz-max-content;height:max-content}.icon-button .icon{fill:var(--accent-color)}.page{height:100%}.page__header{display:flex;justify-content:space-between;margin-bottom:1.5rem;min-height:8rem}.page__header .grid{margin-top:auto}.page__header h1{margin-top:auto;font-size:2rem}#confirmation_popup,#prompt_popup{flex-direction:column}#confirmation_popup h4,#prompt_popup h4{margin-bottom:1.5rem}#confirmation_popup sm-button,#prompt_popup sm-button{margin:0}#confirmation_popup>.flex:last-of-type,#prompt_popup>.flex:last-of-type{padding:0;margin-top:1rem}#confirmation_popup>.flex:last-of-type sm-button:first-of-type,#prompt_popup>.flex:last-of-type sm-button:first-of-type{margin-right:.6rem;margin-left:auto}#prompt_message{margin-bottom:1.5rem}.popup__header{display:grid;gap:.5rem;width:100%;padding:0 1.5rem;align-items:center}.popup__header__close{padding:.5rem;margin-left:-0.5rem}#secondary_pages{display:flex;flex-direction:column;width:100%}#secondary_pages header{padding:1.5rem}#secondary_pages .inner-page{height:100%}#landing>section{justify-content:center;justify-items:center;align-items:center;text-align:center;padding:8vw 0}#landing h1{font-size:clamp(2rem,5vw,5rem)}#loading_page{display:grid;gap:1rem;place-content:center;place-items:center;position:fixed;top:0;left:0;width:100%;height:100%;z-index:20;background-color:rgba(var(--foreground-color), 1)}#main_header{padding:1.5rem max(1rem,4vw)}.app-brand{display:flex;gap:.3rem;align-items:center}.app-brand .icon{height:1.7rem;width:1.7rem}.app-name__company{font-size:.8rem;font-weight:500;color:rgba(var(--text-color), 0.8)}#main_card{display:grid;grid-template-columns:minmax(0, 1fr);grid-template-rows:auto 1fr;height:100%;width:100%;transition:background-color .3s;background-color:rgba(var(--foreground-color), 1)}#main_navbar{display:flex;background:rgba(var(--text-color), 0.03)}#main_navbar.hide-away{position:absolute}#main_navbar ul{display:flex;height:100%;width:100%}#main_navbar ul li{width:100%}.nav-item{position:relative;display:flex;flex:1;width:100%;flex-direction:column;align-items:center;justify-content:center;padding:.5rem .3rem;color:var(--text-color);font-size:.9rem;border-radius:.3rem;font-weight:500}.nav-item .icon{transition:transform .2s cubic-bezier(0.175, 0.885, 0.32, 1.275)}.nav-item__title{margin-top:.3rem;transition:opacity .2s,transform .2s cubic-bezier(0.175, 0.885, 0.32, 1.275)}.nav-item--active{color:var(--accent-color)}.nav-item--active .icon{fill:var(--accent-color);transform:translateY(50%)}.nav-item--active .nav-item__title{transform:translateY(100%);opacity:0}.nav-item__indicator{position:absolute;bottom:0;width:2rem;height:.3rem;background:var(--accent-color);border-radius:1rem 1rem 0 0;z-index:1}.inner-page{padding:0 1.5rem;flex:1;overflow-y:auto;align-content:start}.password-field label{display:flex}.password-field label input:checked~.visible{display:none}.password-field label input:not(:checked)~.invisible{display:none}.multi-state-button{display:grid;text-align:center;align-items:center;justify-items:center}.multi-state-button>*{grid-area:1/1/2/2}.multi-state-button button{z-index:1;width:100%}.scrolling-wrapper{overflow-y:auto}#pages_container{overflow-y:auto;padding:0 1rem}.primary-action{display:flex;padding:.8rem 1rem;gap:.5rem;white-space:normal;font-size:.9rem;border-radius:.5rem;background-color:rgba(0,0,0,0);border:thin solid rgba(var(--text-color), 0.3);text-align:left;flex-shrink:1}.primary-action .icon{fill:var(--accent-color)}#flo_id_warning{padding-bottom:1.5rem;border-bottom:thin solid rgba(var(--text-color), 0.3)}#flo_id_warning .icon{height:4rem;width:4rem;padding:1rem;background-color:#ffc107;border-radius:3rem;fill:rgba(0,0,0,.8);margin-bottom:1.5rem}.generated-id-card{display:grid;gap:1rem}.generated-id-card h5{margin-bottom:.3rem}#address_balance_card{padding:1.5rem;border-radius:.5rem;margin-top:.5rem;margin-bottom:1.5rem;background-color:rgba(var(--accent-color-rgb), 0.2)}#address_balance{font-size:1.2rem;font-weight:700;color:var(--accent-color)}.card{padding:.5rem 0;border:none}.card:not(:last-of-type){border-bottom:solid thin rgba(var(--text-color), 0.3)}.remove-card-wrapper{min-height:2rem}.sender-card{display:grid;gap:.5rem;margin-bottom:.5rem}.sender-card sm-input{border-radius:.5rem}.sender-card sm-input.error{border:var(--danger-color) solid}.receiver-card{display:grid;gap:.5rem}.balance-wrapper{padding-bottom:.5rem;border-bottom:thin solid rgba(var(--text-color), 0.3)}#transactions_list{display:grid;gap:2rem;padding-bottom:4rem}.transaction{grid-template-columns:auto 1fr;gap:.5rem 1rem;align-items:center;content-visibility:auto;contain-intrinsic-height:8rem}.transaction:not(:last-of-type){padding-bottom:2rem}.transaction__amount{white-space:nowrap}.transaction.out .transaction__icon .icon{fill:var(--danger-color)}.transaction.out .transaction__amount{color:var(--danger-color)}.transaction.out .transaction__amount::before{content:"- "}.transaction.in .transaction__icon .icon{fill:var(--green)}.transaction.in .transaction__amount{color:var(--green)}.transaction.in .transaction__amount::before{content:"+ "}.transaction.unconfirmed-tx .transaction__icon .icon{fill:var(--yellow)}.transaction__icon{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;background-color:rgba(var(--text-color), 0.03);border-radius:2rem}.transaction__receiver{font-weight:500;margin-bottom:.3rem;line-height:1.5}.transaction__time{font-size:.9rem;color:rgba(var(--text-color), 0.8)}.transaction .pending-badge{font-size:.8rem;padding:.2rem .5rem;border-radius:.5rem;background-color:var(--yellow);color:rgba(0,0,0,.8);font-weight:500;justify-self:flex-start}.transaction__amount{font-size:1rem;font-weight:700}.transaction__id{font-size:.9rem}.tx-participant:not(:last-of-type){margin-right:.5rem}.tx-participant:not(:last-of-type)::after{content:","}#tx_status{display:flex;align-items:center;gap:.5rem;padding:.6rem .8rem;border-radius:.5rem;background-color:rgba(var(--text-color), 0.03);color:var(--danger-color)}#tx_status .icon{fill:var(--danger-color)}#tx_technicals .tx-detail:first-of-type{position:relative}#tx_technicals .tx-detail:first-of-type::before{content:"";position:absolute;top:50%;left:0;transform:translateY(-50%);width:.2rem;height:calc(100% - 2rem);background-color:var(--accent-color);border-radius:0 1rem 1rem 0}.details-wrapper{display:flex;flex-wrap:wrap;gap:.5rem;width:100%}.tx-detail{display:grid;gap:.5rem;align-content:flex-start;background-color:rgba(var(--text-color), 0.03);padding:1rem;border-radius:.5rem;flex:1 1 8rem}.tx-detail .flex div{color:rgba(var(--text-color), 0.8);font-size:.9rem;font-weight:500}.tx-detail>div{font-weight:700;font-size:1rem;color:rgba(var(--text-color), 0.9)}#in_out_wrapper{display:grid;grid-template-columns:repeat(auto-fit, minmax(16rem, 1fr));gap:1rem;margin-bottom:3rem}.in-out-card{display:grid;grid-template-columns:minmax(0, 1fr) auto;gap:.5rem;padding:1rem;border-radius:.5rem;background-color:rgba(var(--text-color), 0.03)}.in-out-card:not(:last-of-type){margin-bottom:.5rem}.in-out-card>:first-child{margin-right:1rem}#selected_fee_tip{font-weight:500}#selected_fee_tip.error{color:var(--danger-color)}#selected_fee_tip.error .icon{fill:var(--danger-color)}.fab{position:absolute;right:0;bottom:0;margin:1.5rem;box-shadow:0 .5rem 1rem rgba(0,0,0,.2);z-index:2}#scroll_to_top{border-radius:3rem;background-color:rgba(var(--foreground-color), 1)}#txid{font-size:.9rem}.user-action-result__icon{justify-self:center;height:4rem;width:4rem;border-radius:5rem;-webkit-animation:popup 1s;animation:popup 1s}.user-action-result__icon.success{fill:rgba(var(--background-color), 1);padding:1rem;background-color:#0bbe56}.user-action-result__icon.failed{background-color:rgba(var(--text-color), 0.03);fill:var(--danger-color)}@-webkit-keyframes popup{0%{opacity:0;transform:scale(0.2) translateY(600%)}10%{transform:scale(0.2) translateY(5rem);opacity:1}40%{transform:scale(0.2) translateY(0)}80%{transform:scale(1.1) translateY(0)}100%{transform:scale(1) translateY(0)}}@keyframes popup{0%{opacity:0;transform:scale(0.2) translateY(600%)}10%{transform:scale(0.2) translateY(5rem);opacity:1}40%{transform:scale(0.2) translateY(0)}80%{transform:scale(1.1) translateY(0)}100%{transform:scale(1) translateY(0)}}.increase-fee-sender,.increase-fee-receiver{padding:1rem;border-radius:.5rem;border:solid thin rgba(var(--text-color), 0.3)}.input-action-wrapper{display:flex;gap:.5rem;flex-wrap:wrap;width:100%;align-items:start;container:input-action/inline-size}.input-action-wrapper sm-input{width:auto;flex:1;min-width:20rem}.input-action-wrapper button{min-width:7rem;min-height:3.15rem}@container input-action (max-width: 28rem){sm-input{width:100%;min-width:16rem !important}button{flex:1}}#conversion_view_selector{margin-right:auto;font-weight:500}.converted-card:not(:empty){border-radius:.5rem;padding:1rem;background-color:rgba(var(--text-color), 0.03)}.converted-card:not(:empty) sm-copy{font-weight:500;color:rgba(var(--text-color), 0.9)}@media screen and (max-width: 40rem){#main_navbar.hide-away{bottom:0;left:0;right:0}}@media screen and (min-width: 40rem){sm-popup{--width: 24rem}.popup__header{grid-column:1/-1;padding:1rem 1.5rem 0 1.5rem}body{align-items:center;justify-content:center}#main_card{grid-template-areas:"header" ".";position:relative;overflow:hidden;box-shadow:0 .1rem .2rem rgba(0,0,0,.05),0 1rem 3rem rgba(0,0,0,.2)}#main_card:not(.nav-hidden){grid-template-columns:auto 1fr;grid-template-rows:auto 1fr;grid-template-areas:"nav header" "nav ."}#main_header{grid-area:header}#main_navbar{grid-area:nav;border-top:none;flex-direction:column;background-color:rgba(37,110,255,.03)}#main_navbar ul{flex-direction:column;gap:.5rem;padding:.3rem}.nav-item{aspect-ratio:1/1}.nav-item__indicator{width:.25rem;height:50%;left:0;border-radius:0 1rem 1rem 0;bottom:auto}body[data-theme=dark] #main_navbar{background-color:rgba(0,0,0,.2)}.page{width:min(60rem,100%);margin:0 auto}#generate_btc_addr_popup,#retrieve_btc_addr_popup{--width: 28rem}#convert{width:min(72rem,100%)}#key_conversion_content{grid-template-columns:1fr 1fr;align-items:flex-start}#increase_fee_popup{--width: 30rem}}@media screen and (min-width: 48rem){.sender-card,.receiver-card{grid-template-columns:1fr 1fr}}@media(any-hover: hover){::-webkit-scrollbar{width:.5rem;height:.5rem}::-webkit-scrollbar-thumb{background:rgba(var(--text-color), 0.3);border-radius:1rem}::-webkit-scrollbar-thumb:hover{background:rgba(var(--text-color), 0.5)}.interactive:not([disabled]){transition:background-color .3s}.interactive:not([disabled]):hover{background-color:rgba(var(--text-color), 0.06)}button:not([disabled]),.button:not([disabled]){transition:background-color .3s,filter .3s}button:not([disabled]):hover,.button:not([disabled]):hover{filter:contrast(2)}}@supports(overflow: overlay){body{overflow:overlay}}.hidden{display:none !important} \ No newline at end of file +*{padding:0;margin:0;box-sizing:border-box;font-family:"Roboto",sans-serif}:root{font-size:clamp(1rem,1.2vmax,1.2rem)}html,body{height:100%}body{--accent-color: #4d77ff;--accent-color-rgb: 77, 119, 255;--text-color: 20, 20, 20;--foreground-color: 252, 253, 255;--background-color: 241, 243, 248;--danger-color: rgb(225, 58, 58);--green: #1cad59;--yellow: rgb(255, 207, 65);scrollbar-width:thin;scrollbar-gutter:stable;color:rgba(var(--text-color), 1);background-color:rgba(var(--background-color), 1);transition:background-color .3s;position:relative;display:flex;flex-direction:column}body[data-theme=dark]{--accent-color: #a0b6ff;--accent-color-rgb: 160, 182, 255;--text-color: 220, 220, 220;--foreground-color: 27, 28, 29;--background-color: 21, 22, 22;--danger-color: rgb(255, 106, 106);--green: #00e676;--yellow: rgb(255, 213, 5)}body[data-theme=dark] sm-popup::part(popup){background-color:rgba(var(--foreground-color), 1)}p,strong{font-size:.9rem;max-width:65ch;line-height:1.7;color:rgba(var(--text-color), 0.9)}.warning{line-height:normal;padding:1rem;background-color:khaki;border-radius:.5rem;font-weight:500;color:rgba(0,0,0,.7)}a{text-decoration:none;color:var(--accent-color)}a:focus-visible{box-shadow:0 0 0 .1rem rgba(var(--text-color), 1) inset}button,.button{-webkit-user-select:none;-moz-user-select:none;user-select:none;position:relative;display:inline-flex;border:none;background-color:rgba(0,0,0,0);overflow:hidden;-webkit-tap-highlight-color:rgba(0,0,0,0);align-items:center;font-size:.9rem;font-weight:500;white-space:nowrap;padding:.8rem;border-radius:.5rem;justify-content:center;flex-shrink:0}button:focus-visible,.button:focus-visible{outline:var(--accent-color) solid medium}button:not(:disabled),.button:not(:disabled){color:var(--accent-color);cursor:pointer}button .icon,.button .icon{fill:var(--accent-color)}.button{display:inline-flex;background-color:rgba(var(--text-color), 0.06)}.button--primary,.button--danger{color:rgba(var(--background-color), 1) !important}.button--primary .icon,.button--danger .icon{fill:rgba(var(--background-color), 1)}.button--primary{background-color:var(--accent-color)}.button--danger{background-color:var(--danger-color)}.button--small{padding:.4rem .5rem}.button--colored{color:var(--accent-color) !important}.button--colored .icon{fill:var(--accent-color)}.cta{text-transform:uppercase;font-size:.9rem;font-weight:700;letter-spacing:.05em;padding:.8rem 1rem}.icon{width:1.2rem;height:1.2rem;fill:rgba(var(--text-color), 0.8);flex-shrink:0}.icon-only{padding:.5rem;border-radius:.3rem}button:disabled{opacity:.5}a:-webkit-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:-moz-any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}a:any-link:focus-visible{outline:rgba(var(--text-color), 1) .1rem solid}details summary{display:flex;gap:.3rem;-webkit-user-select:none;-moz-user-select:none;user-select:none;cursor:pointer;align-items:center;color:var(--accent-color)}details summary .down-arrow{fill:var(--accent-color)}details[open] summary{margin-bottom:1rem}details[open]>summary .down-arrow{transform:rotate(180deg)}sm-input,sm-textarea{width:100%;--border-radius: 0.5rem;--background-color: rgba(var(--foreground-color), 1)}sm-input button .icon,sm-textarea button .icon{fill:var(--accent-color)}sm-button{--padding: 0.8rem}sm-button[variant=primary] .icon{fill:rgba(var(--background-color), 1)}sm-button[disabled] .icon{fill:rgba(var(--text-color), 0.6)}sm-button.danger{--background: var(--danger-color);color:rgba(var(--background-color), 1)}sm-spinner{--size: 1rem;--stroke-width: 0.1rem}sm-form{--gap: 1rem}sm-select{font-size:.9rem;--padding: 0.6rem 0.3rem 0.6rem 0.6rem}sm-option{font-size:.9rem}sm-chips{--gap: 0;background-color:rgba(var(--text-color), 0.06);border-radius:.5rem;padding:.3rem}sm-chip{font-size:.9rem;--border-radius: 0.3rem;-webkit-user-select:none;-moz-user-select:none;user-select:none}sm-chip[selected]{color:rgba(var(--background-color), 1);--background: var(--accent-color)}sm-notifications{z-index:100000}ul,ol{list-style:none}ol{counter-reset:item}ol li{position:relative;display:flex;align-items:flex-start;counter-increment:item}ol li:not(:last-of-type){padding-bottom:1.5rem}ol li:not(:last-of-type)::after{content:"";position:absolute;width:.1rem;height:calc(100% - 2.2rem);background:var(--accent-color);margin-left:.7rem;margin-top:2rem}ol li::before{content:counter(item);display:flex;align-items:center;justify-content:center;text-align:center;font-size:.9rem;font-weight:500;margin-top:.15rem;margin-right:1rem;line-height:1;width:1.5rem;height:1.5rem;border-radius:100%;flex-shrink:0;color:rgba(var(--text-color), 0.8);background:rgba(var(--text-color), 0.1)}.overflow-ellipsis{width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.wrap-around{overflow-wrap:break-word;word-wrap:break-word;word-break:break-word}.full-bleed{grid-column:1/-1}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.sticky{position:-webkit-sticky;position:sticky}.top-0{top:0}.flex{display:flex}.flex-wrap{flex-wrap:wrap}.flex-1{flex:1}.flex-direction-column{flex-direction:column}.grid{display:grid}.flow-column{grid-auto-flow:column}.gap-0-3{gap:.3rem}.gap-0-5{gap:.5rem}.gap-1{gap:1rem}.gap-1-5{gap:1.5rem}.gap-2{gap:2rem}.gap-3{gap:3rem}.text-align-right{text-align:right}.align-content-start{align-content:flex-start}.align-items-start{align-items:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.text-center{text-align:center}.justify-start{justify-items:start}.justify-center{justify-content:center}.justify-right{margin-left:auto}.align-self-center{align-self:center}.align-self-end{align-self:end}.justify-self-center{justify-self:center}.justify-self-start{justify-self:start}.justify-self-end{justify-self:end}.flex-direction-column{flex-direction:column}.space-between{justify-content:space-between}.w-100{width:100%}.h-100{height:100%}.label{font-size:.9rem;color:rgba(var(--text-color), 0.8);font-weight:500;margin-bottom:.2rem}.button--primary .ripple,.button--danger .ripple{background:radial-gradient(circle, rgba(var(--background-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%)}.ripple{height:8rem;width:8rem;position:absolute;border-radius:50%;transform:scale(0);background:radial-gradient(circle, rgba(var(--text-color), 0.3) 0%, rgba(0, 0, 0, 0) 50%);pointer-events:none}.interactive{position:relative;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:rgba(0,0,0,0)}.empty-state{display:grid;width:100%;padding:1.5rem 0}.observe-empty-state:empty{display:none !important}.observe-empty-state:not(:empty)+.empty-state{display:none !important}.bullet-point{display:flex;align-items:center;justify-content:center;margin:0 .8ch}.bullet-point::after{content:"";height:.4ch;width:.4ch;border-radius:.5em;background-color:var(--accent-color)}.margin-right-0-3{margin-right:.3rem}.margin-right-0-5{margin-right:.5rem}.margin-right-auto{margin-right:auto}.margin-left-0-5{margin-left:.5rem}.margin-left-auto{margin-left:auto}.margin-top-1-5{margin-top:1.5rem}.margin-bottom-0-5{margin-bottom:.5rem}.margin-bottom-1{margin-bottom:1rem}.margin-bottom-1-5{margin-bottom:1.5rem}.margin-bottom-2{margin-bottom:2rem}.icon-button{padding:.6rem;border-radius:.8rem;background-color:rgba(var(--text-color), 0.1);height:-webkit-max-content;height:-moz-max-content;height:max-content}.icon-button .icon{fill:var(--accent-color)}.page{height:100%}.page__header{display:flex;justify-content:space-between;margin-bottom:1.5rem;min-height:8rem}.page__header .grid{margin-top:auto}.page__header h1{margin-top:auto;font-size:2rem}#confirmation_popup,#prompt_popup{flex-direction:column}#confirmation_popup h4,#prompt_popup h4{margin-bottom:1.5rem}#confirmation_popup sm-button,#prompt_popup sm-button{margin:0}#confirmation_popup>.flex:last-of-type,#prompt_popup>.flex:last-of-type{padding:0;margin-top:1rem}#confirmation_popup>.flex:last-of-type sm-button:first-of-type,#prompt_popup>.flex:last-of-type sm-button:first-of-type{margin-right:.6rem;margin-left:auto}#prompt_message{margin-bottom:1.5rem}.popup__header{display:grid;gap:.5rem;width:100%;padding:0 1.5rem;align-items:center}.popup__header__close{padding:.5rem;margin-left:-0.5rem}#secondary_pages{display:flex;flex-direction:column;width:100%}#secondary_pages header{padding:1.5rem}#secondary_pages .inner-page{height:100%}#landing>section{justify-content:center;justify-items:center;align-items:center;text-align:center;padding:8vw 0}#landing h1{font-size:clamp(2rem,5vw,5rem)}#loading_page{display:grid;gap:1rem;place-content:center;place-items:center;position:fixed;top:0;left:0;width:100%;height:100%;z-index:20;background-color:rgba(var(--foreground-color), 1)}#main_header{padding:1.5rem max(1rem,4vw)}.app-brand{display:flex;gap:.3rem;align-items:center}.app-brand .icon{height:1.7rem;width:1.7rem}.app-name__company{font-size:.8rem;font-weight:500;color:rgba(var(--text-color), 0.8)}#main_card{display:grid;grid-template-columns:minmax(0, 1fr);grid-template-rows:auto 1fr;height:100%;width:100%;transition:background-color .3s;background-color:rgba(var(--foreground-color), 1)}#main_navbar{display:flex;background:rgba(var(--text-color), 0.03)}#main_navbar.hide-away{position:absolute}#main_navbar ul{display:flex;height:100%;width:100%}#main_navbar ul li{width:100%}.nav-item{position:relative;display:flex;flex:1;width:100%;flex-direction:column;align-items:center;justify-content:center;padding:.5rem .3rem;color:var(--text-color);font-size:.9rem;border-radius:.3rem;font-weight:500}.nav-item .icon{transition:transform .2s cubic-bezier(0.175, 0.885, 0.32, 1.275)}.nav-item__title{margin-top:.3rem;transition:opacity .2s,transform .2s cubic-bezier(0.175, 0.885, 0.32, 1.275)}.nav-item--active{color:var(--accent-color)}.nav-item--active .icon{fill:var(--accent-color);transform:translateY(50%)}.nav-item--active .nav-item__title{transform:translateY(100%);opacity:0}.nav-item__indicator{position:absolute;bottom:0;width:2rem;height:.3rem;background:var(--accent-color);border-radius:1rem 1rem 0 0;z-index:1}.inner-page{padding:0 1.5rem;flex:1;overflow-y:auto;align-content:start}.password-field label{display:flex}.password-field label input:checked~.visible{display:none}.password-field label input:not(:checked)~.invisible{display:none}.multi-state-button{display:grid;text-align:center;align-items:center;justify-items:center}.multi-state-button>*{grid-area:1/1/2/2}.multi-state-button button{z-index:1;width:100%}.scrolling-wrapper{overflow-y:auto}#pages_container{overflow-y:auto;padding:0 1rem}.primary-action{display:flex;padding:.8rem 1rem;gap:.5rem;white-space:normal;font-size:.9rem;border-radius:.5rem;background-color:rgba(0,0,0,0);border:thin solid rgba(var(--text-color), 0.3);text-align:left;flex-shrink:1}.primary-action .icon{fill:var(--accent-color)}#flo_id_warning{padding-bottom:1.5rem;border-bottom:thin solid rgba(var(--text-color), 0.3)}#flo_id_warning .icon{height:4rem;width:4rem;padding:1rem;background-color:#ffc107;border-radius:3rem;fill:rgba(0,0,0,.8);margin-bottom:1.5rem}.generated-id-card{display:grid;gap:1rem}.generated-id-card h5{margin-bottom:.3rem}#address_balance_card{padding:1.5rem;border-radius:.5rem;margin-top:.5rem;margin-bottom:1.5rem;background-color:rgba(var(--accent-color-rgb), 0.2)}#address_balance{font-size:1.2rem;font-weight:700;color:var(--accent-color)}.card{padding:.5rem 0;border:none}.card:not(:last-of-type){border-bottom:solid thin rgba(var(--text-color), 0.3)}.remove-card-wrapper{min-height:2rem}.sender-card{display:grid;gap:.5rem;margin-bottom:.5rem}.sender-card sm-input{border-radius:.5rem}.sender-card sm-input.error{border:var(--danger-color) solid}.receiver-card{display:grid;gap:.5rem}.balance-wrapper{padding-bottom:.5rem;border-bottom:thin solid rgba(var(--text-color), 0.3)}#transactions_list{display:grid;gap:2rem;padding-bottom:4rem}.transaction{grid-template-columns:auto 1fr;gap:.5rem 1rem;align-items:center;content-visibility:auto;contain-intrinsic-height:8rem}.transaction:not(:last-of-type){padding-bottom:2rem}.transaction__amount{white-space:nowrap}.transaction.out .transaction__icon .icon{fill:var(--danger-color)}.transaction.out .transaction__amount{color:var(--danger-color)}.transaction.out .transaction__amount::before{content:"- "}.transaction.in .transaction__icon .icon{fill:var(--green)}.transaction.in .transaction__amount{color:var(--green)}.transaction.in .transaction__amount::before{content:"+ "}.transaction.unconfirmed-tx .transaction__icon .icon{fill:var(--yellow)}.transaction__icon{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;background-color:rgba(var(--text-color), 0.03);border-radius:2rem}.transaction__receiver{font-weight:500;margin-bottom:.3rem;line-height:1.5}.transaction__time{font-size:.9rem;color:rgba(var(--text-color), 0.8)}.transaction__amount{font-size:1rem;font-weight:700}.transaction__id{font-size:.9rem}.pending-badge{font-size:.8rem;padding:.2rem .5rem;border-radius:.5rem;background-color:var(--yellow);color:rgba(0,0,0,.8);font-weight:500;justify-self:flex-start}.tx-participant:not(:last-of-type){margin-right:.5rem}.tx-participant:not(:last-of-type)::after{content:","}#tx_status{display:flex;align-items:center;gap:.5rem;padding:.6rem .8rem;border-radius:.5rem;background-color:rgba(var(--text-color), 0.03);color:var(--danger-color)}#tx_status .icon{fill:var(--danger-color)}#tx_technicals .tx-detail:first-of-type{position:relative}#tx_technicals .tx-detail:first-of-type::before{content:"";position:absolute;top:50%;left:0;transform:translateY(-50%);width:.2rem;height:calc(100% - 2rem);background-color:var(--accent-color);border-radius:0 1rem 1rem 0}.details-wrapper{display:flex;flex-wrap:wrap;gap:.5rem;width:100%}.tx-detail{display:grid;gap:.5rem;align-content:flex-start;background-color:rgba(var(--text-color), 0.03);padding:1rem;border-radius:.5rem;flex:1 1 8rem}.tx-detail .flex div{color:rgba(var(--text-color), 0.8);font-size:.9rem;font-weight:500}.tx-detail>div{font-weight:700;font-size:1rem;color:rgba(var(--text-color), 0.9)}#in_out_wrapper{display:grid;grid-template-columns:repeat(auto-fit, minmax(16rem, 1fr));gap:1rem;margin-bottom:3rem}.in-out-card{display:grid;grid-template-columns:minmax(0, 1fr) auto;gap:.5rem;padding:1rem;border-radius:.5rem;background-color:rgba(var(--text-color), 0.03)}.in-out-card:not(:last-of-type){margin-bottom:.5rem}.in-out-card>:first-child{margin-right:1rem}#selected_fee_tip{font-weight:500}#selected_fee_tip.error{color:var(--danger-color)}#selected_fee_tip.error .icon{fill:var(--danger-color)}.fab{position:absolute;right:0;bottom:0;margin:1.5rem;box-shadow:0 .5rem 1rem rgba(0,0,0,.2);z-index:2}#scroll_to_top{border-radius:3rem;background-color:rgba(var(--foreground-color), 1)}#txid{font-size:.9rem}.user-action-result__icon{justify-self:center;height:4rem;width:4rem;border-radius:5rem;-webkit-animation:popup 1s;animation:popup 1s}.user-action-result__icon.success{fill:rgba(var(--background-color), 1);padding:1rem;background-color:#0bbe56}.user-action-result__icon.failed{background-color:rgba(var(--text-color), 0.03);fill:var(--danger-color)}@-webkit-keyframes popup{0%{opacity:0;transform:scale(0.2) translateY(600%)}10%{transform:scale(0.2) translateY(5rem);opacity:1}40%{transform:scale(0.2) translateY(0)}80%{transform:scale(1.1) translateY(0)}100%{transform:scale(1) translateY(0)}}@keyframes popup{0%{opacity:0;transform:scale(0.2) translateY(600%)}10%{transform:scale(0.2) translateY(5rem);opacity:1}40%{transform:scale(0.2) translateY(0)}80%{transform:scale(1.1) translateY(0)}100%{transform:scale(1) translateY(0)}}.increase-fee-sender,.increase-fee-receiver{padding:1rem;border-radius:.5rem;border:solid thin rgba(var(--text-color), 0.3)}.input-action-wrapper{display:flex;gap:.5rem;flex-wrap:wrap;width:100%;align-items:start;container:input-action/inline-size}.input-action-wrapper sm-input{width:auto;flex:1;min-width:20rem}.input-action-wrapper button{min-width:7rem;min-height:3.15rem}@container input-action (max-width: 28rem){sm-input{width:100%;min-width:16rem !important}button{flex:1}}#conversion_view_selector{margin-right:auto;font-weight:500}.converted-card:not(:empty){border-radius:.5rem;padding:1rem;background-color:rgba(var(--text-color), 0.03)}.converted-card:not(:empty) sm-copy{font-weight:500;color:rgba(var(--text-color), 0.9)}@media screen and (max-width: 40rem){#main_navbar.hide-away{bottom:0;left:0;right:0}}@media screen and (min-width: 40rem){sm-popup{--width: 24rem}.popup__header{grid-column:1/-1;padding:1rem 1.5rem 0 1.5rem}body{align-items:center;justify-content:center}#main_card{grid-template-areas:"header" ".";position:relative;overflow:hidden;box-shadow:0 .1rem .2rem rgba(0,0,0,.05),0 1rem 3rem rgba(0,0,0,.2)}#main_card:not(.nav-hidden){grid-template-columns:auto 1fr;grid-template-rows:auto 1fr;grid-template-areas:"nav header" "nav ."}#main_header{grid-area:header}#main_navbar{grid-area:nav;border-top:none;flex-direction:column;background-color:rgba(37,110,255,.03)}#main_navbar ul{flex-direction:column;gap:.5rem;padding:.3rem}.nav-item{aspect-ratio:1/1}.nav-item__indicator{width:.25rem;height:50%;left:0;border-radius:0 1rem 1rem 0;bottom:auto}body[data-theme=dark] #main_navbar{background-color:rgba(0,0,0,.2)}.page{width:min(60rem,100%);margin:0 auto}#generate_btc_addr_popup,#retrieve_btc_addr_popup{--width: 28rem}#convert{width:min(72rem,100%)}#key_conversion_content{grid-template-columns:1fr 1fr;align-items:flex-start}#increase_fee_popup{--width: 30rem}}@media screen and (min-width: 48rem){.sender-card,.receiver-card{grid-template-columns:1fr 1fr}}@media(any-hover: hover){::-webkit-scrollbar{width:.5rem;height:.5rem}::-webkit-scrollbar-thumb{background:rgba(var(--text-color), 0.3);border-radius:1rem}::-webkit-scrollbar-thumb:hover{background:rgba(var(--text-color), 0.5)}.interactive:not([disabled]){transition:background-color .3s}.interactive:not([disabled]):hover{background-color:rgba(var(--text-color), 0.06)}button:not([disabled]),.button:not([disabled]){transition:background-color .3s,filter .3s}button:not([disabled]):hover,.button:not([disabled]):hover{filter:contrast(2)}}@supports(overflow: overlay){body{overflow:overlay}}.hidden{display:none !important} \ No newline at end of file diff --git a/css/main.scss b/css/main.scss index 8bdbeb2..b9f9fa0 100644 --- a/css/main.scss +++ b/css/main.scss @@ -868,15 +868,6 @@ ol { font-size: 0.9rem; color: rgba(var(--text-color), 0.8); } - .pending-badge { - font-size: 0.8rem; - padding: 0.2rem 0.5rem; - border-radius: 0.5rem; - background-color: var(--yellow); - color: rgba(0 0 0/ 0.8); - font-weight: 500; - justify-self: flex-start; - } &__amount { font-size: 1rem; font-weight: 700; @@ -885,6 +876,15 @@ ol { font-size: 0.9rem; } } +.pending-badge { + font-size: 0.8rem; + padding: 0.2rem 0.5rem; + border-radius: 0.5rem; + background-color: var(--yellow); + color: rgba(0 0 0/ 0.8); + font-weight: 500; + justify-self: flex-start; +} .tx-participant { &:not(:last-of-type) { &::after { diff --git a/index.html b/index.html index 5fecfb5..d74d1be 100644 --- a/index.html +++ b/index.html @@ -957,13 +957,16 @@ function buttonLoader(id, show) { const button = typeof id === 'string' ? document.getElementById(id) : id; - button.disabled = show; + if (!button) return + if (!button.dataset.hasOwnProperty('wasDisabled')) + button.dataset.wasDisabled = button.disabled const animOptions = { duration: 200, - fill: 'both', + fill: 'forwards', easing: 'ease' } if (show) { + button.disabled = true button.parentNode.append(document.createElement('sm-spinner')) button.animate([ { @@ -974,11 +977,22 @@ }, ], animOptions) } else { - button.getAnimations().forEach(anim => anim.cancel()) + button.disabled = button.dataset.wasDisabled === 'true'; + button.animate([ + { + clipPath: 'circle(0)', + }, + { + clipPath: 'circle(100%)', + }, + ], animOptions).onfinish = (e) => { + button.removeAttribute('data-original-state') + } const potentialTarget = button.parentNode.querySelector('sm-spinner') if (potentialTarget) potentialTarget.remove(); } } + let isMobileView = false const mobileQuery = window.matchMedia('(max-width: 40rem)') function handleMobileChange(e) { @@ -1166,7 +1180,7 @@ const queriedAddress = pagesData.params?.query || getRef('search_query_input').value.trim() const isSender = type === 'out' || type === 'self' const className = `transaction grid ${type} ${block === null ? 'unconfirmed-tx' : ''}` - return html.node` + return html.node/*html*/`
  • ${icon}
    @@ -1185,7 +1199,7 @@ ${isSender && !block ? html`
    - @@ -1261,6 +1275,22 @@ Unconfirmed ` : ''}
    + ${!block ? html` +
    +

    + Taking too long to confirm? +

    +

    + You can increase the fee to speed up confirmation. +

    +
    + +
    +
    + ` : ''}
    @@ -1988,10 +2018,9 @@ } let changingFeeOf = null - async function initFeeChange(e) { + async function initFeeChange(e, txid) { const button = e.target.closest('button') buttonLoader(button, true) - const txid = button.closest('li').dataset.txid changingFeeOf = txid try { const { inputs, outputs, fee: previousFee } = await btcOperator.getTx(txid) @@ -2020,7 +2049,7 @@ const amounts = outputs.map(output => 0.00000005) let recommendedFee = null if (!isMultisig) { - const { fee } = await btcOperator.createTx(senders, receivers, amounts) + const { fee } = await btcOperator.createTx(senders, receivers, amounts, null, { allowUnconfirmedUtxos: true }) if (fee > previousFee) recommendedFee = fee } @@ -2076,7 +2105,7 @@ document.getElementById('new_fee').querySelector('.currency-symbol').innerHTML = currencyIcons[selectedCurrency] openPopup('increase_fee_popup') } catch (e) { - console.error(e) + notify(e, 'error') } finally { buttonLoader(button, false) } diff --git a/scripts/btcOperator.js b/scripts/btcOperator.js index ebcb3c0..db05b40 100644 --- a/scripts/btcOperator.js +++ b/scripts/btcOperator.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //btcOperator v1.1.3d +(function (EXPORTS) { //btcOperator v1.1.4 /* BTC Crypto and API Operator */ const btcOperator = EXPORTS; @@ -400,12 +400,12 @@ } btcOperator.validateTxParameters = validateTxParameters; - function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) { + function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver, allowUnconfirmedUtxos = false) { return new Promise((resolve, reject) => { let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8)); const tx = coinjs.transaction(); let output_size = addOutputs(tx, receivers, amounts, change_address); - addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => { + addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos).then(result => { if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change) tx.outs[tx.outs.length - 1].value = util.BTC_to_Sat(result.change_amount); //values are in satoshi if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver @@ -439,10 +439,10 @@ } btcOperator.createTransaction = createTransaction; - function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) { + function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos = false) { return new Promise((resolve, reject) => { if (fee !== null) { - addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => { + addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false, { allowUnconfirmedUtxos }).then(result => { result.fee = fee; resolve(result); }).catch(error => reject(error)) @@ -451,8 +451,8 @@ let net_fee = BASE_TX_SIZE * fee_rate; net_fee += (output_size * fee_rate); (fee_from_receiver ? - addUTXOs(tx, senders, redeemScripts, total_amount, false) : - addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate) + addUTXOs(tx, senders, redeemScripts, total_amount, false, { allowUnconfirmedUtxos }) : + addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate, { allowUnconfirmedUtxos }) ).then(result => { result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8)); result.fee_rate = fee_rate; @@ -464,7 +464,7 @@ } btcOperator.addInputs = addInputs; - function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) { + function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = { allowUnconfirmedUtxos: false }) { return new Promise((resolve, reject) => { required_amount = parseFloat(required_amount.toFixed(8)); if (typeof rec_args.n === "undefined") { @@ -478,8 +478,9 @@ input_amount: rec_args.input_amount, change_amount: required_amount * -1 //required_amount will be -ve of change_amount }); - else if (rec_args.n >= senders.length) + else if (rec_args.n >= senders.length) { return reject("Insufficient Balance"); + } let addr = senders[rec_args.n], rs = redeemScripts[rec_args.n]; let addr_type = coinjs.addressDecode(addr).type; @@ -488,7 +489,9 @@ let utxos = result.unspent_outputs; //console.debug("add-utxo", addr, rs, required_amount, utxos); for (let i = 0; i < utxos.length && required_amount > 0; i++) { - if (!utxos[i].confirmations) //ignore unconfirmed utxo + if (utxos.length === 1 && rec_args.allowUnconfirmedUtxos) { + console.log('allowing unconfirmed utxos') + } else if (!utxos[i].confirmations) //ignore unconfirmed utxo continue; var script; if (!rs || !rs.length) //legacy script @@ -838,7 +841,9 @@ }) } - btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) { + btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = { + allowUnconfirmedUtxos: false + }) { return new Promise((resolve, reject) => { try { ({ @@ -859,7 +864,7 @@ if (redeemScripts.includes(null)) //TODO: segwit return reject("Unable to get redeem-script"); //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => { + createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver, options.allowUnconfirmedUtxos).then(result => { result.tx_hex = result.transaction.serialize(); delete result.transaction; resolve(result); diff --git a/scripts/btcOperator.min.js b/scripts/btcOperator.min.js index df19cd6..de31cde 100644 --- a/scripts/btcOperator.min.js +++ b/scripts/btcOperator.min.js @@ -1 +1 @@ -!function(EXPORTS){const btcOperator="object"===typeof module?module.exports:window.btcOperator={},URL="https://blockchain.info/",fetch_api=btcOperator.fetch=function(api,json_res=!0){return new Promise(((resolve,reject)=>{console.debug(URL+api),fetch(URL+api).then((response=>{response.ok?(json_res?response.json():response.text()).then((result=>resolve(result))).catch((error=>reject(error))):response.json().then((result=>reject(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},util=btcOperator.util={};util.Sat_to_BTC=value=>parseFloat((value/1e8).toFixed(8)),util.BTC_to_Sat=value=>parseInt(1e8*value);const broadcastTx=btcOperator.broadcastTx=rawTxHex=>new Promise(((resolve,reject)=>{console.log("txHex:",rawTxHex);fetch("https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"rawtx="+rawTxHex}).then((response=>{response.text().then((resultText=>{let r=resultText.match(/.*<\/result>/);if(r)if(r=r.pop().replace("","").replace("",""),"1"==r){let txid=resultText.match(/.*<\/txid>/).pop().replace("","").replace("","");resolve(txid)}else if("0"==r){let error;error=resultText.includes("")?resultText.match(/.*<\/message>/).pop().replace("","").replace("",""):resultText.match(/.*<\/response>/).pop().replace("","").replace("",""),reject(decodeURIComponent(error.replace(/\+/g," ")))}else reject(resultText);else reject(resultText)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}));Object.defineProperties(btcOperator,{newKeys:{get:()=>{let r=coinjs.newKeys();return r.segwitAddress=coinjs.segwitAddress(r.pubkey).address,r.bech32Address=coinjs.bech32Address(r.pubkey).address,r}},pubkey:{value:key=>key.length>=66?key:64==key.length?coinjs.newPubkey(key):coinjs.wif2pubkey(key).pubkey},address:{value:(key,prefix=void 0)=>coinjs.pubkey2address(btcOperator.pubkey(key),prefix)},segwitAddress:{value:key=>coinjs.segwitAddress(btcOperator.pubkey(key)).address},bech32Address:{value:key=>coinjs.bech32Address(btcOperator.pubkey(key)).address},bech32mAddress:{value:key=>segwit_addr.encode("bc",1,key)}}),coinjs.compressed=!0;const verifyKey=btcOperator.verifyKey=function(addr,key){if(addr&&key)switch(coinjs.addressDecode(addr).type){case"standard":return btcOperator.address(key)===addr;case"multisig":return btcOperator.segwitAddress(key)===addr;case"bech32":return btcOperator.bech32Address(key)===addr;case"bech32m":return btcOperator.bech32mAddress(key)===addr;default:return null}},validateAddress=btcOperator.validateAddress=function(addr){if(!addr)return;let type=coinjs.addressDecode(addr).type;return!!["standard","multisig","bech32","multisigBech32","bech32m"].includes(type)&&type};btcOperator.multiSigAddress=function(pubKeys,minRequired,bech32=!0){if(!Array.isArray(pubKeys))throw"pubKeys must be an array of public keys";if(pubKeys.lengthnew Promise(((resolve,reject)=>{fetch_api(`q/addressbalance/${addr}`).then((result=>resolve(util.Sat_to_BTC(result)))).catch((error=>reject(error)))}));const BASE_TX_SIZE=12,BASE_INPUT_SIZE=41,LEGACY_INPUT_SIZE=107,BECH32_INPUT_SIZE=27,BECH32_MULTISIG_INPUT_SIZE=35,SEGWIT_INPUT_SIZE=59,MULTISIG_INPUT_SIZE_ES=351,BASE_OUTPUT_SIZE=9,LEGACY_OUTPUT_SIZE=25,BECH32_OUTPUT_SIZE=23,BECH32_MULTISIG_OUTPUT_SIZE=34,SEGWIT_OUTPUT_SIZE=23;function _redeemScript(addr,key){let decode=coinjs.addressDecode(addr);switch(decode.type){case"standard":return!1;case"multisig":return key?coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript:null;case"bech32":case"'multisigBech32":return decode.redeemscript;case"bech32m":return decode.outstring;default:return null}}function _sizePerOutput(addr){switch(coinjs.addressDecode(addr).type){case"standard":return BASE_OUTPUT_SIZE+LEGACY_OUTPUT_SIZE;case"bech32":return BASE_OUTPUT_SIZE+BECH32_OUTPUT_SIZE;case"multisigBech32":return BASE_OUTPUT_SIZE+BECH32_MULTISIG_OUTPUT_SIZE;case"multisig":return BASE_OUTPUT_SIZE+SEGWIT_OUTPUT_SIZE;case"bech32m":return BASE_OUTPUT_SIZE+BECH32M_OUTPUT_SIZE;default:return null}}function validateTxParameters(parameters){let invalids=[];if(parameters.senders&&(Array.isArray(parameters.senders)||(parameters.senders=[parameters.senders]),parameters.senders.forEach((id=>validateAddress(id)?null:invalids.push(id))),invalids.length))throw"Invalid senders:"+invalids;if(parameters.privkeys){if(Array.isArray(parameters.privkeys)||(parameters.privkeys=[parameters.privkeys]),parameters.senders.length!=parameters.privkeys.length)throw"Array length for senders and privkeys should be equal";if(parameters.senders.forEach(((id,i)=>{let key=parameters.privkeys[i];verifyKey(id,key)||invalids.push(id),64===key.length&&(parameters.privkeys[i]=coinjs.privkey2wif(key))})),invalids.length)throw"Invalid private key for address:"+invalids}if(Array.isArray(parameters.receivers)||(parameters.receivers=[parameters.receivers]),parameters.receivers.forEach((id=>validateAddress(id)?null:invalids.push(id))),invalids.length)throw"Invalid receivers:"+invalids;if(parameters.change_address&&!validateAddress(parameters.change_address))throw"Invalid change_address:"+parameters.change_address;if(("number"!=typeof parameters.fee||parameters.fee<=0)&&null!==parameters.fee)throw"Invalid fee:"+parameters.fee;if(Array.isArray(parameters.amounts)||(parameters.amounts=[parameters.amounts]),parameters.receivers.length!=parameters.amounts.length)throw"Array length for receivers and amounts should be equal";if(parameters.amounts.forEach((a=>"number"!=typeof a||a<=0?invalids.push(a):null)),invalids.length)throw"Invalid amounts:"+invalids;return parameters}function createTransaction(senders,redeemScripts,receivers,amounts,fee,change_address,fee_from_receiver){return new Promise(((resolve,reject)=>{let total_amount=parseFloat(amounts.reduce(((t,a)=>t+a),0).toFixed(8));const tx=coinjs.transaction();let output_size=addOutputs(tx,receivers,amounts,change_address);addInputs(tx,senders,redeemScripts,total_amount,fee,output_size,fee_from_receiver).then((result=>{if(result.change_amount>0&&result.change_amount>result.fee&&(tx.outs[tx.outs.length-1].value=util.BTC_to_Sat(result.change_amount)),fee_from_receiver){let fee_remaining=util.BTC_to_Sat(result.fee);for(let i=0;i0;i++)fee_remaining0)return reject("Send amount is less than fee")}let filtered_outputs=[],dust_value=0;tx.outs.forEach((o=>o.value>=546?filtered_outputs.push(o):dust_value+=o.value)),tx.outs=filtered_outputs,result.fee+=util.Sat_to_BTC(dust_value),result.output_size=output_size,result.output_amount=total_amount-(fee_from_receiver?result.fee:0),result.total_size=BASE_TX_SIZE+output_size+result.input_size,result.transaction=tx,resolve(result)})).catch((error=>reject(error)))}))}function addInputs(tx,senders,redeemScripts,total_amount,fee,output_size,fee_from_receiver){return new Promise(((resolve,reject)=>{null!==fee?addUTXOs(tx,senders,redeemScripts,fee_from_receiver?total_amount:total_amount+fee,!1).then((result=>{result.fee=fee,resolve(result)})).catch((error=>reject(error))):new Promise(((resolve,reject)=>{fetch("https://api.blockchain.info/mempool/fees").then((response=>{response.ok?response.json().then((result=>resolve(util.Sat_to_BTC(result.regular)))).catch((error=>reject(error))):reject(response)})).catch((error=>reject(error)))})).then((fee_rate=>{let net_fee=BASE_TX_SIZE*fee_rate;net_fee+=output_size*fee_rate,(fee_from_receiver?addUTXOs(tx,senders,redeemScripts,total_amount,!1):addUTXOs(tx,senders,redeemScripts,total_amount+net_fee,fee_rate)).then((result=>{result.fee=parseFloat((net_fee+result.input_size*fee_rate).toFixed(8)),result.fee_rate=fee_rate,resolve(result)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}function addUTXOs(tx,senders,redeemScripts,required_amount,fee_rate,rec_args={}){return new Promise(((resolve,reject)=>{if(required_amount=parseFloat(required_amount.toFixed(8)),void 0===rec_args.n&&(rec_args.n=0,rec_args.input_size=0,rec_args.input_amount=0),required_amount<=0)return resolve({input_size:rec_args.input_size,input_amount:rec_args.input_amount,change_amount:-1*required_amount});if(rec_args.n>=senders.length)return reject("Insufficient Balance");let addr=senders[rec_args.n],rs=redeemScripts[rec_args.n],addr_type=coinjs.addressDecode(addr).type,size_per_input=function(addr,rs){switch(coinjs.addressDecode(addr).type){case"standard":return BASE_INPUT_SIZE+LEGACY_INPUT_SIZE;case"bech32":return BASE_INPUT_SIZE+BECH32_INPUT_SIZE;case"multisigBech32":return BASE_INPUT_SIZE+BECH32_MULTISIG_INPUT_SIZE;case"multisig":switch(coinjs.script().decodeRedeemScript(rs).type){case"segwit__":return BASE_INPUT_SIZE+SEGWIT_INPUT_SIZE;case"multisig__":return BASE_INPUT_SIZE+MULTISIG_INPUT_SIZE_ES;default:return null}default:return null}}(addr,rs);fetch_api(`unspent?active=${addr}`).then((result=>{let utxos=result.unspent_outputs;for(let i=0;i0;i++)if(utxos[i].confirmations){var script;if(rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)||"multisigBech32"===addr_type){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else script=utxos[i].script;tx.addinput(utxos[i].tx_hash_big_endian,utxos[i].tx_output_n,script,4294967293),rec_args.input_size+=size_per_input,rec_args.input_amount+=util.Sat_to_BTC(utxos[i].value),required_amount-=util.Sat_to_BTC(utxos[i].value),fee_rate&&(required_amount+=size_per_input*fee_rate)}rec_args.n+=1,addUTXOs(tx,senders,redeemScripts,required_amount,fee_rate,rec_args).then((result=>resolve(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}function addOutputs(tx,receivers,amounts,change_address){let size=0;for(let i in receivers)tx.addoutput(receivers[i],amounts[i]),size+=_sizePerOutput(receivers[i]);return tx.addoutput(change_address,0),size+=_sizePerOutput(change_address),size}function tx_fetch_for_editing(tx){return new Promise(((resolve,reject)=>{"string"==typeof tx&&/^[0-9a-f]{64}$/i.test(tx)?getTx.hex(tx).then((txhex=>resolve(deserializeTx(txhex)))).catch((error=>reject(error))):resolve(deserializeTx(tx))}))}BECH32M_OUTPUT_SIZE=35,btcOperator._redeemScript=_redeemScript,btcOperator.validateTxParameters=validateTxParameters,btcOperator.createTransaction=createTransaction,btcOperator.addInputs=addInputs,btcOperator.addUTXOs=addUTXOs,btcOperator.addOutputs=addOutputs,btcOperator.tx_fetch_for_editing=tx_fetch_for_editing;btcOperator.extractLastHexStrings=function(arr){const result=[];for(let i=0;i0){const lastHexString=innerArray[innerArray.length-1];result.push(lastHexString)}}return result};btcOperator.editFee=function(tx_hex,new_fee,private_keys,change_only=!0){return new Promise(((resolve,reject)=>{Array.isArray(private_keys)||(private_keys=[private_keys]),tx_fetch_for_editing(tx_hex).then((tx=>{parseTransaction(tx).then((tx_parsed=>{if(tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee<219)return reject("Insufficient additional fee. Minimum increment: 219");for(let i=tx.outs.length-1;i>=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=546));let wif_keys=[];for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)||"multisigBech32"===addr_decode.type){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),resolve(tx.serialize())})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},btcOperator.editFee_corewallet=function(tx_hex,new_fee,private_keys,change_only=!0){return new Promise(((resolve,reject)=>{Array.isArray(private_keys)||(private_keys=[private_keys]),tx_fetch_for_editing(tx_hex).then((tx=>{parseTransaction(tx).then((tx_parsed=>{if(tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee<219)return reject("Insufficient additional fee. Minimum increment: 219");for(let i=tx.outs.length-1;i>=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=546));let wif_keys=[],witness_position=0;for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer),"bech32"==addr_decode&&(witness_position+=1)}else if("multisigBech32"===addr_decode.type){let redeemScript=btcOperator.extractLastHexStrings(tx.witness)[witness_position];witness_position+=1;let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(redeemScript)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),btcOperator.checkSigned(tx)?resolve(tx.serialize()):reject("All private keys not present")})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},btcOperator.sendTx=function(senders,privkeys,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{createSignedTx(senders,privkeys,receivers,amounts,fee,options).then((result=>{broadcastTx(result.transaction.serialize()).then((txid=>resolve(txid))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))};const createSignedTx=btcOperator.createSignedTx=function(senders,privkeys,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{try{({senders:senders,privkeys:privkeys,receivers:receivers,amounts:amounts}=validateTxParameters({senders:senders,privkeys:privkeys,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}let redeemScripts=[],wif_keys=[];for(let i in senders){let rs=_redeemScript(senders[i],privkeys[i]);redeemScripts.push(rs),!1===rs?wif_keys.unshift(privkeys[i]):wif_keys.push(privkeys[i])}if(redeemScripts.includes(null))return reject("Unable to get redeem-script");createTransaction(senders,redeemScripts,receivers,amounts,fee,options.change_address||senders[0],options.fee_from_receiver).then((result=>{let tx=result.transaction;console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),console.debug("Signed:",tx.serialize()),resolve(result)})).catch((error=>reject(error)))}))};btcOperator.createTx=function(senders,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{try{({senders:senders,receivers:receivers,amounts:amounts}=validateTxParameters({senders:senders,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}let redeemScripts=senders.map((id=>_redeemScript(id)));if(redeemScripts.includes(null))return reject("Unable to get redeem-script");createTransaction(senders,redeemScripts,receivers,amounts,fee,options.change_address||senders[0],options.fee_from_receiver).then((result=>{result.tx_hex=result.transaction.serialize(),delete result.transaction,resolve(result)})).catch((error=>reject(error)))}))},btcOperator.createMultiSigTx=function(sender,redeemScript,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{let addr_type=validateAddress(sender);if(!["multisig","multisigBech32"].includes(addr_type))return reject("Invalid sender (multisig):"+sender);{let script=coinjs.script(),decode="multisig"==addr_type?script.decodeRedeemScript(redeemScript):script.decodeRedeemScriptBech32(redeemScript);if(!decode||decode.address!==sender)return reject("Invalid redeem-script")}try{({receivers:receivers,amounts:amounts}=validateTxParameters({receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}createTransaction([sender],[redeemScript],receivers,amounts,fee,options.change_address||sender,options.fee_from_receiver).then((result=>{result.tx_hex=result.transaction.serialize(),delete result.transaction,resolve(result)})).catch((error=>reject(error)))}))};const deserializeTx=btcOperator.deserializeTx=function(tx){if("string"==typeof tx||Array.isArray(tx))try{tx=coinjs.transaction().deserialize(tx)}catch{throw"Invalid transaction hex"}else if("object"!=typeof tx||"function"!=typeof tx.sign)throw"Invalid transaction object";return tx};btcOperator.signTx=function(tx,privkeys,sighashtype=1){tx=deserializeTx(tx),Array.isArray(privkeys)||(privkeys=[privkeys]);for(let i in privkeys)64===privkeys[i].length&&(privkeys[i]=coinjs.privkey2wif(privkeys[i]));return new Set(privkeys).forEach((key=>tx.sign(key,sighashtype))),tx.serialize()};const checkSigned=btcOperator.checkSigned=function(tx,bool=!0){tx=deserializeTx(tx);let n=[];for(let i in tx.ins){var s=tx.extractScriptKey(i);if("multisig"!==s.type&&"multisig_bech32"!==s.type)n.push("true"==s.signed||tx.witness[i]&&2==tx.witness[i].length);else{var rs=coinjs.script().decodeRedeemScript(s.script);let x={s:s.signatures,r:rs.signaturesRequired,t:rs.pubkeys.length};if(x.r>x.t)throw"signaturesRequired is more than publicKeys";x.s!0!==x)).length:n};btcOperator.checkIfSameTx=function(tx1,tx2){if(tx1=deserializeTx(tx1),tx2=deserializeTx(tx2),tx1.ins.length!==tx2.ins.length||tx1.outs.length!==tx2.outs.length)return!1;for(let i=0;inew Promise(((resolve,reject)=>{fetch_api(`rawtx/${txid}`).then((result=>resolve(result.out[i]))).catch((error=>reject(error)))})),parseTransaction=btcOperator.parseTransaction=function(tx){return new Promise(((resolve,reject)=>{tx=deserializeTx(tx);let result={},promises=[];for(let i=0;i{result.inputs=inputs.map((inp=>Object({address:inp.addr,value:util.Sat_to_BTC(inp.value)})));let signed=checkSigned(tx,!1);result.inputs.forEach(((inp,i)=>inp.signed=signed[i])),result.outputs=tx.outs.map((out=>{var address;switch(out.script.chunks[0]){case 0:address=util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]),coinjs.bech32.version,coinjs.bech32.hrp);break;case 169:address=util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]),coinjs.multisig);break;case 118:address=util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]),coinjs.pub)}return{address:address,value:util.Sat_to_BTC(out.value)}})),result.total_input=parseFloat(result.inputs.reduce(((a,inp)=>a+inp.value),0).toFixed(8)),result.total_output=parseFloat(result.outputs.reduce(((a,out)=>a+out.value),0).toFixed(8)),result.fee=parseFloat((result.total_input-result.total_output).toFixed(8)),resolve(result)})).catch((error=>reject(error)))}))};btcOperator.transactionID=function(tx){tx=deserializeTx(tx);let clone=coinjs.clone(tx);clone.witness=null;let raw_bytes=Crypto.util.hexToBytes(clone.serialize()),txid=Crypto.SHA256(Crypto.SHA256(raw_bytes,{asBytes:!0}),{asBytes:!0}).reverse();return Crypto.util.bytesToHex(txid)};const getLatestBlock=btcOperator.getLatestBlock=()=>new Promise(((resolve,reject)=>{fetch_api("q/getblockcount").then((result=>resolve(result))).catch((error=>reject(error)))})),getTx=btcOperator.getTx=txid=>new Promise(((resolve,reject)=>{fetch_api(`rawtx/${txid}`).then((result=>{console.debug("Tx:",result),getLatestBlock().then((latest_block=>resolve({block:result.block_height,txid:result.hash,time:1e3*result.time,confirmations:null===result.block_height?0:latest_block-result.block_height,size:result.size,fee:util.Sat_to_BTC(result.fee),inputs:result.inputs.map((i=>Object({address:i.prev_out.addr,value:util.Sat_to_BTC(i.prev_out.value)}))),total_input_value:util.Sat_to_BTC(result.inputs.reduce(((a,i)=>a+i.prev_out.value),0)),outputs:result.out.map((o=>Object({address:o.addr,value:util.Sat_to_BTC(o.value)}))),total_output_value:util.Sat_to_BTC(result.out.reduce(((a,o)=>a+o.value),0))})))})).catch((error=>reject(error)))}));getTx.hex=btcOperator.getTx.hex=txid=>new Promise(((resolve,reject)=>{fetch_api(`rawtx/${txid}?format=hex`,!1).then((result=>resolve(result))).catch((error=>reject(error)))})),btcOperator.getAddressData=address=>new Promise(((resolve,reject)=>{fetch_api(`rawaddr/${address}`).then((data=>{let details={};details.balance=util.Sat_to_BTC(data.final_balance),details.address=data.address,details.txs=data.txs.map((tx=>{let d={txid:tx.hash,time:1e3*tx.time,block:tx.block_height,tx_senders:{}};tx.inputs.forEach((i=>{i.prev_out.addr in d.tx_senders?d.tx_senders[i.prev_out.addr]+=i.prev_out.value:d.tx_senders[i.prev_out.addr]=i.prev_out.value})),d.tx_input_value=0;for(let s in d.tx_senders){let val=d.tx_senders[s];d.tx_senders[s]=util.Sat_to_BTC(val),d.tx_input_value+=val}d.tx_input_value=util.Sat_to_BTC(d.tx_input_value),d.tx_receivers={},tx.out.forEach((o=>{o.addr in d.tx_receivers?d.tx_receivers[o.addr]+=o.value:d.tx_receivers[o.addr]=o.value})),d.tx_output_value=0;for(let r in d.tx_receivers){let val=d.tx_receivers[r];d.tx_receivers[r]=util.Sat_to_BTC(val),d.tx_output_value+=val}return d.tx_output_value=util.Sat_to_BTC(d.tx_output_value),d.tx_fee=util.Sat_to_BTC(tx.fee),tx.result>0?(d.type="in",d.amount=util.Sat_to_BTC(tx.result),d.sender=Object.keys(d.tx_senders).filter((s=>s!==address))):Object.keys(d.tx_receivers).some((r=>r!==address))?(d.type="out",d.amount=util.Sat_to_BTC(-1*tx.result),d.receiver=Object.keys(d.tx_receivers).filter((r=>r!==address)),d.fee=d.tx_fee):(d.type="self",d.amount=d.tx_receivers[address],d.address=address),d})),resolve(details)})).catch((error=>reject(error)))})),btcOperator.getBlock=block=>new Promise(((resolve,reject)=>{fetch_api(`rawblock/${block}`).then((result=>resolve({height:result.height,hash:result.hash,merkle_root:result.mrkl_root,prev_block:result.prev_block,next_block:result.next_block[0],size:result.size,time:1e3*result.time,txs:result.tx.map((t=>Object({fee:t.fee,size:t.size,inputs:t.inputs.map((i=>Object({address:i.prev_out.addr,value:util.Sat_to_BTC(i.prev_out.value)}))),total_input_value:util.Sat_to_BTC(t.inputs.reduce(((a,i)=>a+i.prev_out.value),0)),outputs:t.out.map((o=>Object({address:o.addr,value:util.Sat_to_BTC(o.value)}))),total_output_value:util.Sat_to_BTC(t.out.reduce(((a,o)=>a+o.value),0))})))}))).catch((error=>reject(error)))}))}(); \ No newline at end of file +!function(EXPORTS){const btcOperator="object"===typeof module?module.exports:window.btcOperator={},URL="https://blockchain.info/",fetch_api=btcOperator.fetch=function(api,json_res=!0){return new Promise(((resolve,reject)=>{console.debug(URL+api),fetch(URL+api).then((response=>{response.ok?(json_res?response.json():response.text()).then((result=>resolve(result))).catch((error=>reject(error))):response.json().then((result=>reject(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},util=btcOperator.util={};util.Sat_to_BTC=value=>parseFloat((value/1e8).toFixed(8)),util.BTC_to_Sat=value=>parseInt(1e8*value);const broadcastTx=btcOperator.broadcastTx=rawTxHex=>new Promise(((resolve,reject)=>{console.log("txHex:",rawTxHex);fetch("https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"rawtx="+rawTxHex}).then((response=>{response.text().then((resultText=>{let r=resultText.match(/.*<\/result>/);if(r)if(r=r.pop().replace("","").replace("",""),"1"==r){let txid=resultText.match(/.*<\/txid>/).pop().replace("","").replace("","");resolve(txid)}else if("0"==r){let error;error=resultText.includes("")?resultText.match(/.*<\/message>/).pop().replace("","").replace("",""):resultText.match(/.*<\/response>/).pop().replace("","").replace("",""),reject(decodeURIComponent(error.replace(/\+/g," ")))}else reject(resultText);else reject(resultText)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}));Object.defineProperties(btcOperator,{newKeys:{get:()=>{let r=coinjs.newKeys();return r.segwitAddress=coinjs.segwitAddress(r.pubkey).address,r.bech32Address=coinjs.bech32Address(r.pubkey).address,r}},pubkey:{value:key=>key.length>=66?key:64==key.length?coinjs.newPubkey(key):coinjs.wif2pubkey(key).pubkey},address:{value:(key,prefix=void 0)=>coinjs.pubkey2address(btcOperator.pubkey(key),prefix)},segwitAddress:{value:key=>coinjs.segwitAddress(btcOperator.pubkey(key)).address},bech32Address:{value:key=>coinjs.bech32Address(btcOperator.pubkey(key)).address},bech32mAddress:{value:key=>segwit_addr.encode("bc",1,key)}}),coinjs.compressed=!0;const verifyKey=btcOperator.verifyKey=function(addr,key){if(addr&&key)switch(coinjs.addressDecode(addr).type){case"standard":return btcOperator.address(key)===addr;case"multisig":return btcOperator.segwitAddress(key)===addr;case"bech32":return btcOperator.bech32Address(key)===addr;case"bech32m":return btcOperator.bech32mAddress(key)===addr;default:return null}},validateAddress=btcOperator.validateAddress=function(addr){if(!addr)return;let type=coinjs.addressDecode(addr).type;return!!["standard","multisig","bech32","multisigBech32","bech32m"].includes(type)&&type};btcOperator.multiSigAddress=function(pubKeys,minRequired,bech32=!0){if(!Array.isArray(pubKeys))throw"pubKeys must be an array of public keys";if(pubKeys.lengthnew Promise(((resolve,reject)=>{fetch_api(`q/addressbalance/${addr}`).then((result=>resolve(util.Sat_to_BTC(result)))).catch((error=>reject(error)))}));const BASE_TX_SIZE=12,BASE_INPUT_SIZE=41,LEGACY_INPUT_SIZE=107,BECH32_INPUT_SIZE=27,BECH32_MULTISIG_INPUT_SIZE=35,SEGWIT_INPUT_SIZE=59,MULTISIG_INPUT_SIZE_ES=351,BASE_OUTPUT_SIZE=9,LEGACY_OUTPUT_SIZE=25,BECH32_OUTPUT_SIZE=23,BECH32_MULTISIG_OUTPUT_SIZE=34,SEGWIT_OUTPUT_SIZE=23;function _redeemScript(addr,key){let decode=coinjs.addressDecode(addr);switch(decode.type){case"standard":return!1;case"multisig":return key?coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript:null;case"bech32":case"'multisigBech32":return decode.redeemscript;case"bech32m":return decode.outstring;default:return null}}function _sizePerOutput(addr){switch(coinjs.addressDecode(addr).type){case"standard":return BASE_OUTPUT_SIZE+LEGACY_OUTPUT_SIZE;case"bech32":return BASE_OUTPUT_SIZE+BECH32_OUTPUT_SIZE;case"multisigBech32":return BASE_OUTPUT_SIZE+BECH32_MULTISIG_OUTPUT_SIZE;case"multisig":return BASE_OUTPUT_SIZE+SEGWIT_OUTPUT_SIZE;case"bech32m":return BASE_OUTPUT_SIZE+BECH32M_OUTPUT_SIZE;default:return null}}function validateTxParameters(parameters){let invalids=[];if(parameters.senders&&(Array.isArray(parameters.senders)||(parameters.senders=[parameters.senders]),parameters.senders.forEach((id=>validateAddress(id)?null:invalids.push(id))),invalids.length))throw"Invalid senders:"+invalids;if(parameters.privkeys){if(Array.isArray(parameters.privkeys)||(parameters.privkeys=[parameters.privkeys]),parameters.senders.length!=parameters.privkeys.length)throw"Array length for senders and privkeys should be equal";if(parameters.senders.forEach(((id,i)=>{let key=parameters.privkeys[i];verifyKey(id,key)||invalids.push(id),64===key.length&&(parameters.privkeys[i]=coinjs.privkey2wif(key))})),invalids.length)throw"Invalid private key for address:"+invalids}if(Array.isArray(parameters.receivers)||(parameters.receivers=[parameters.receivers]),parameters.receivers.forEach((id=>validateAddress(id)?null:invalids.push(id))),invalids.length)throw"Invalid receivers:"+invalids;if(parameters.change_address&&!validateAddress(parameters.change_address))throw"Invalid change_address:"+parameters.change_address;if(("number"!=typeof parameters.fee||parameters.fee<=0)&&null!==parameters.fee)throw"Invalid fee:"+parameters.fee;if(Array.isArray(parameters.amounts)||(parameters.amounts=[parameters.amounts]),parameters.receivers.length!=parameters.amounts.length)throw"Array length for receivers and amounts should be equal";if(parameters.amounts.forEach((a=>"number"!=typeof a||a<=0?invalids.push(a):null)),invalids.length)throw"Invalid amounts:"+invalids;return parameters}function createTransaction(senders,redeemScripts,receivers,amounts,fee,change_address,fee_from_receiver,allowUnconfirmedUtxos=!1){return new Promise(((resolve,reject)=>{let total_amount=parseFloat(amounts.reduce(((t,a)=>t+a),0).toFixed(8));const tx=coinjs.transaction();let output_size=addOutputs(tx,receivers,amounts,change_address);addInputs(tx,senders,redeemScripts,total_amount,fee,output_size,fee_from_receiver,allowUnconfirmedUtxos).then((result=>{if(result.change_amount>0&&result.change_amount>result.fee&&(tx.outs[tx.outs.length-1].value=util.BTC_to_Sat(result.change_amount)),fee_from_receiver){let fee_remaining=util.BTC_to_Sat(result.fee);for(let i=0;i0;i++)fee_remaining0)return reject("Send amount is less than fee")}let filtered_outputs=[],dust_value=0;tx.outs.forEach((o=>o.value>=546?filtered_outputs.push(o):dust_value+=o.value)),tx.outs=filtered_outputs,result.fee+=util.Sat_to_BTC(dust_value),result.output_size=output_size,result.output_amount=total_amount-(fee_from_receiver?result.fee:0),result.total_size=BASE_TX_SIZE+output_size+result.input_size,result.transaction=tx,resolve(result)})).catch((error=>reject(error)))}))}function addInputs(tx,senders,redeemScripts,total_amount,fee,output_size,fee_from_receiver,allowUnconfirmedUtxos=!1){return new Promise(((resolve,reject)=>{null!==fee?addUTXOs(tx,senders,redeemScripts,fee_from_receiver?total_amount:total_amount+fee,!1,{allowUnconfirmedUtxos:allowUnconfirmedUtxos}).then((result=>{result.fee=fee,resolve(result)})).catch((error=>reject(error))):new Promise(((resolve,reject)=>{fetch("https://api.blockchain.info/mempool/fees").then((response=>{response.ok?response.json().then((result=>resolve(util.Sat_to_BTC(result.regular)))).catch((error=>reject(error))):reject(response)})).catch((error=>reject(error)))})).then((fee_rate=>{let net_fee=BASE_TX_SIZE*fee_rate;net_fee+=output_size*fee_rate,(fee_from_receiver?addUTXOs(tx,senders,redeemScripts,total_amount,!1,{allowUnconfirmedUtxos:allowUnconfirmedUtxos}):addUTXOs(tx,senders,redeemScripts,total_amount+net_fee,fee_rate,{allowUnconfirmedUtxos:allowUnconfirmedUtxos})).then((result=>{result.fee=parseFloat((net_fee+result.input_size*fee_rate).toFixed(8)),result.fee_rate=fee_rate,resolve(result)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}function addUTXOs(tx,senders,redeemScripts,required_amount,fee_rate,rec_args={allowUnconfirmedUtxos:!1}){return new Promise(((resolve,reject)=>{if(required_amount=parseFloat(required_amount.toFixed(8)),void 0===rec_args.n&&(rec_args.n=0,rec_args.input_size=0,rec_args.input_amount=0),required_amount<=0)return resolve({input_size:rec_args.input_size,input_amount:rec_args.input_amount,change_amount:-1*required_amount});if(rec_args.n>=senders.length)return reject("Insufficient Balance");let addr=senders[rec_args.n],rs=redeemScripts[rec_args.n],addr_type=coinjs.addressDecode(addr).type,size_per_input=function(addr,rs){switch(coinjs.addressDecode(addr).type){case"standard":return BASE_INPUT_SIZE+LEGACY_INPUT_SIZE;case"bech32":return BASE_INPUT_SIZE+BECH32_INPUT_SIZE;case"multisigBech32":return BASE_INPUT_SIZE+BECH32_MULTISIG_INPUT_SIZE;case"multisig":switch(coinjs.script().decodeRedeemScript(rs).type){case"segwit__":return BASE_INPUT_SIZE+SEGWIT_INPUT_SIZE;case"multisig__":return BASE_INPUT_SIZE+MULTISIG_INPUT_SIZE_ES;default:return null}default:return null}}(addr,rs);fetch_api(`unspent?active=${addr}`).then((result=>{let utxos=result.unspent_outputs;for(let i=0;i0;i++){if(1===utxos.length&&rec_args.allowUnconfirmedUtxos)console.log("allowing unconfirmed utxos");else if(!utxos[i].confirmations)continue;var script;if(rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)||"multisigBech32"===addr_type){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else script=utxos[i].script;tx.addinput(utxos[i].tx_hash_big_endian,utxos[i].tx_output_n,script,4294967293),rec_args.input_size+=size_per_input,rec_args.input_amount+=util.Sat_to_BTC(utxos[i].value),required_amount-=util.Sat_to_BTC(utxos[i].value),fee_rate&&(required_amount+=size_per_input*fee_rate)}rec_args.n+=1,addUTXOs(tx,senders,redeemScripts,required_amount,fee_rate,rec_args).then((result=>resolve(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}function addOutputs(tx,receivers,amounts,change_address){let size=0;for(let i in receivers)tx.addoutput(receivers[i],amounts[i]),size+=_sizePerOutput(receivers[i]);return tx.addoutput(change_address,0),size+=_sizePerOutput(change_address),size}function tx_fetch_for_editing(tx){return new Promise(((resolve,reject)=>{"string"==typeof tx&&/^[0-9a-f]{64}$/i.test(tx)?getTx.hex(tx).then((txhex=>resolve(deserializeTx(txhex)))).catch((error=>reject(error))):resolve(deserializeTx(tx))}))}BECH32M_OUTPUT_SIZE=35,btcOperator._redeemScript=_redeemScript,btcOperator.validateTxParameters=validateTxParameters,btcOperator.createTransaction=createTransaction,btcOperator.addInputs=addInputs,btcOperator.addUTXOs=addUTXOs,btcOperator.addOutputs=addOutputs,btcOperator.tx_fetch_for_editing=tx_fetch_for_editing;btcOperator.extractLastHexStrings=function(arr){const result=[];for(let i=0;i0){const lastHexString=innerArray[innerArray.length-1];result.push(lastHexString)}}return result};btcOperator.editFee=function(tx_hex,new_fee,private_keys,change_only=!0){return new Promise(((resolve,reject)=>{Array.isArray(private_keys)||(private_keys=[private_keys]),tx_fetch_for_editing(tx_hex).then((tx=>{parseTransaction(tx).then((tx_parsed=>{if(tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee<219)return reject("Insufficient additional fee. Minimum increment: 219");for(let i=tx.outs.length-1;i>=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=546));let wif_keys=[];for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)||"multisigBech32"===addr_decode.type){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),resolve(tx.serialize())})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},btcOperator.editFee_corewallet=function(tx_hex,new_fee,private_keys,change_only=!0){return new Promise(((resolve,reject)=>{Array.isArray(private_keys)||(private_keys=[private_keys]),tx_fetch_for_editing(tx_hex).then((tx=>{parseTransaction(tx).then((tx_parsed=>{if(tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee<219)return reject("Insufficient additional fee. Minimum increment: 219");for(let i=tx.outs.length-1;i>=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=546));let wif_keys=[],witness_position=0;for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer),"bech32"==addr_decode&&(witness_position+=1)}else if("multisigBech32"===addr_decode.type){let redeemScript=btcOperator.extractLastHexStrings(tx.witness)[witness_position];witness_position+=1;let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(redeemScript)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),btcOperator.checkSigned(tx)?resolve(tx.serialize()):reject("All private keys not present")})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},btcOperator.sendTx=function(senders,privkeys,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{createSignedTx(senders,privkeys,receivers,amounts,fee,options).then((result=>{broadcastTx(result.transaction.serialize()).then((txid=>resolve(txid))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))};const createSignedTx=btcOperator.createSignedTx=function(senders,privkeys,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{try{({senders:senders,privkeys:privkeys,receivers:receivers,amounts:amounts}=validateTxParameters({senders:senders,privkeys:privkeys,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}let redeemScripts=[],wif_keys=[];for(let i in senders){let rs=_redeemScript(senders[i],privkeys[i]);redeemScripts.push(rs),!1===rs?wif_keys.unshift(privkeys[i]):wif_keys.push(privkeys[i])}if(redeemScripts.includes(null))return reject("Unable to get redeem-script");createTransaction(senders,redeemScripts,receivers,amounts,fee,options.change_address||senders[0],options.fee_from_receiver).then((result=>{let tx=result.transaction;console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),console.debug("Signed:",tx.serialize()),resolve(result)})).catch((error=>reject(error)))}))};btcOperator.createTx=function(senders,receivers,amounts,fee=null,options={allowUnconfirmedUtxos:!1}){return new Promise(((resolve,reject)=>{try{({senders:senders,receivers:receivers,amounts:amounts}=validateTxParameters({senders:senders,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}let redeemScripts=senders.map((id=>_redeemScript(id)));if(redeemScripts.includes(null))return reject("Unable to get redeem-script");createTransaction(senders,redeemScripts,receivers,amounts,fee,options.change_address||senders[0],options.fee_from_receiver,options.allowUnconfirmedUtxos).then((result=>{result.tx_hex=result.transaction.serialize(),delete result.transaction,resolve(result)})).catch((error=>reject(error)))}))},btcOperator.createMultiSigTx=function(sender,redeemScript,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{let addr_type=validateAddress(sender);if(!["multisig","multisigBech32"].includes(addr_type))return reject("Invalid sender (multisig):"+sender);{let script=coinjs.script(),decode="multisig"==addr_type?script.decodeRedeemScript(redeemScript):script.decodeRedeemScriptBech32(redeemScript);if(!decode||decode.address!==sender)return reject("Invalid redeem-script")}try{({receivers:receivers,amounts:amounts}=validateTxParameters({receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}createTransaction([sender],[redeemScript],receivers,amounts,fee,options.change_address||sender,options.fee_from_receiver).then((result=>{result.tx_hex=result.transaction.serialize(),delete result.transaction,resolve(result)})).catch((error=>reject(error)))}))};const deserializeTx=btcOperator.deserializeTx=function(tx){if("string"==typeof tx||Array.isArray(tx))try{tx=coinjs.transaction().deserialize(tx)}catch{throw"Invalid transaction hex"}else if("object"!=typeof tx||"function"!=typeof tx.sign)throw"Invalid transaction object";return tx};btcOperator.signTx=function(tx,privkeys,sighashtype=1){tx=deserializeTx(tx),Array.isArray(privkeys)||(privkeys=[privkeys]);for(let i in privkeys)64===privkeys[i].length&&(privkeys[i]=coinjs.privkey2wif(privkeys[i]));return new Set(privkeys).forEach((key=>tx.sign(key,sighashtype))),tx.serialize()};const checkSigned=btcOperator.checkSigned=function(tx,bool=!0){tx=deserializeTx(tx);let n=[];for(let i in tx.ins){var s=tx.extractScriptKey(i);if("multisig"!==s.type&&"multisig_bech32"!==s.type)n.push("true"==s.signed||tx.witness[i]&&2==tx.witness[i].length);else{var rs=coinjs.script().decodeRedeemScript(s.script);let x={s:s.signatures,r:rs.signaturesRequired,t:rs.pubkeys.length};if(x.r>x.t)throw"signaturesRequired is more than publicKeys";x.s!0!==x)).length:n};btcOperator.checkIfSameTx=function(tx1,tx2){if(tx1=deserializeTx(tx1),tx2=deserializeTx(tx2),tx1.ins.length!==tx2.ins.length||tx1.outs.length!==tx2.outs.length)return!1;for(let i=0;inew Promise(((resolve,reject)=>{fetch_api(`rawtx/${txid}`).then((result=>resolve(result.out[i]))).catch((error=>reject(error)))})),parseTransaction=btcOperator.parseTransaction=function(tx){return new Promise(((resolve,reject)=>{tx=deserializeTx(tx);let result={},promises=[];for(let i=0;i{result.inputs=inputs.map((inp=>Object({address:inp.addr,value:util.Sat_to_BTC(inp.value)})));let signed=checkSigned(tx,!1);result.inputs.forEach(((inp,i)=>inp.signed=signed[i])),result.outputs=tx.outs.map((out=>{var address;switch(out.script.chunks[0]){case 0:address=util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]),coinjs.bech32.version,coinjs.bech32.hrp);break;case 169:address=util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]),coinjs.multisig);break;case 118:address=util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]),coinjs.pub)}return{address:address,value:util.Sat_to_BTC(out.value)}})),result.total_input=parseFloat(result.inputs.reduce(((a,inp)=>a+inp.value),0).toFixed(8)),result.total_output=parseFloat(result.outputs.reduce(((a,out)=>a+out.value),0).toFixed(8)),result.fee=parseFloat((result.total_input-result.total_output).toFixed(8)),resolve(result)})).catch((error=>reject(error)))}))};btcOperator.transactionID=function(tx){tx=deserializeTx(tx);let clone=coinjs.clone(tx);clone.witness=null;let raw_bytes=Crypto.util.hexToBytes(clone.serialize()),txid=Crypto.SHA256(Crypto.SHA256(raw_bytes,{asBytes:!0}),{asBytes:!0}).reverse();return Crypto.util.bytesToHex(txid)};const getLatestBlock=btcOperator.getLatestBlock=()=>new Promise(((resolve,reject)=>{fetch_api("q/getblockcount").then((result=>resolve(result))).catch((error=>reject(error)))})),getTx=btcOperator.getTx=txid=>new Promise(((resolve,reject)=>{fetch_api(`rawtx/${txid}`).then((result=>{console.debug("Tx:",result),getLatestBlock().then((latest_block=>resolve({block:result.block_height,txid:result.hash,time:1e3*result.time,confirmations:null===result.block_height?0:latest_block-result.block_height,size:result.size,fee:util.Sat_to_BTC(result.fee),inputs:result.inputs.map((i=>Object({address:i.prev_out.addr,value:util.Sat_to_BTC(i.prev_out.value)}))),total_input_value:util.Sat_to_BTC(result.inputs.reduce(((a,i)=>a+i.prev_out.value),0)),outputs:result.out.map((o=>Object({address:o.addr,value:util.Sat_to_BTC(o.value)}))),total_output_value:util.Sat_to_BTC(result.out.reduce(((a,o)=>a+o.value),0))})))})).catch((error=>reject(error)))}));getTx.hex=btcOperator.getTx.hex=txid=>new Promise(((resolve,reject)=>{fetch_api(`rawtx/${txid}?format=hex`,!1).then((result=>resolve(result))).catch((error=>reject(error)))})),btcOperator.getAddressData=address=>new Promise(((resolve,reject)=>{fetch_api(`rawaddr/${address}`).then((data=>{let details={};details.balance=util.Sat_to_BTC(data.final_balance),details.address=data.address,details.txs=data.txs.map((tx=>{let d={txid:tx.hash,time:1e3*tx.time,block:tx.block_height,tx_senders:{}};tx.inputs.forEach((i=>{i.prev_out.addr in d.tx_senders?d.tx_senders[i.prev_out.addr]+=i.prev_out.value:d.tx_senders[i.prev_out.addr]=i.prev_out.value})),d.tx_input_value=0;for(let s in d.tx_senders){let val=d.tx_senders[s];d.tx_senders[s]=util.Sat_to_BTC(val),d.tx_input_value+=val}d.tx_input_value=util.Sat_to_BTC(d.tx_input_value),d.tx_receivers={},tx.out.forEach((o=>{o.addr in d.tx_receivers?d.tx_receivers[o.addr]+=o.value:d.tx_receivers[o.addr]=o.value})),d.tx_output_value=0;for(let r in d.tx_receivers){let val=d.tx_receivers[r];d.tx_receivers[r]=util.Sat_to_BTC(val),d.tx_output_value+=val}return d.tx_output_value=util.Sat_to_BTC(d.tx_output_value),d.tx_fee=util.Sat_to_BTC(tx.fee),tx.result>0?(d.type="in",d.amount=util.Sat_to_BTC(tx.result),d.sender=Object.keys(d.tx_senders).filter((s=>s!==address))):Object.keys(d.tx_receivers).some((r=>r!==address))?(d.type="out",d.amount=util.Sat_to_BTC(-1*tx.result),d.receiver=Object.keys(d.tx_receivers).filter((r=>r!==address)),d.fee=d.tx_fee):(d.type="self",d.amount=d.tx_receivers[address],d.address=address),d})),resolve(details)})).catch((error=>reject(error)))})),btcOperator.getBlock=block=>new Promise(((resolve,reject)=>{fetch_api(`rawblock/${block}`).then((result=>resolve({height:result.height,hash:result.hash,merkle_root:result.mrkl_root,prev_block:result.prev_block,next_block:result.next_block[0],size:result.size,time:1e3*result.time,txs:result.tx.map((t=>Object({fee:t.fee,size:t.size,inputs:t.inputs.map((i=>Object({address:i.prev_out.addr,value:util.Sat_to_BTC(i.prev_out.value)}))),total_input_value:util.Sat_to_BTC(t.inputs.reduce(((a,i)=>a+i.prev_out.value),0)),outputs:t.out.map((o=>Object({address:o.addr,value:util.Sat_to_BTC(o.value)}))),total_output_value:util.Sat_to_BTC(t.out.reduce(((a,o)=>a+o.value),0))})))}))).catch((error=>reject(error)))}))}(); \ No newline at end of file