This commit is contained in:
siim-m committed 2024-06-14 18:48:46 +03:00
1 parent 5a1adc441f
commit f873e07db4
19 files changed
+3549

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
.DS_Store
node_modules
build/index.html
+16
View File
@@ -0,0 +1,16 @@
.PHONY: install
install:
npm install
.PHONY: build
build: install
npm run build && PUSHTX_SINGLE_FILE=1 npm run build
# hash and sign all important files - both the source and built versions
.PHONY: sign
sign:
find . -type f \
\( -iname '*.js' -o -iname '*.ts' -o -iname '*.html' -o -iname '*.css' -o -iname '*.json' \) \
! -path './node_modules/*' \
-exec shasum -a 256 {} \; > SHA256SUMS && \
gpg --detach-sign --armor --digest-algo SHA256 --yes SHA256SUMS
+46
View File
@@ -0,0 +1,46 @@
# COLDCARD Website Implementation
This is the code for the NFC Push TX implementation at <https://coldcard.com/pushtx>.
## Overview
We use the APIs of mempool.space and blockstream.info to broadcast the transaction
and for retrieving additional transaction details afterwards.
In an edge case where the transaction is already confirmed (e.g. same Push TX URL
used twice), the APIs respond with a 400 error. If that happens, we use
[bitcoinjs-lib](https://github.com/bitcoinjs/bitcoinjs-lib) to compute the TXID
and try to fetch the details anyway.
## How to Build
To build, you need Node.js and NPM installed. There's a `Makefile` target for
installing dependencies and running the build:
```
make build
```
This builds two versions:
- Separate JS and CSS files that power <https://coldcard.com/pushtx> - saved to `build/`
- A self-contained HTML where all JS and CSS is inlined, which is useful for easy
self-hosting - saved to `build-single-file/`.
## Don't Trust, Verify
The `SHA256SUMS` file includes hashes of all important files in this repo and is
signed with the PGP key `3F4D 2119 6E14 FD39 3835 492D F960 23C5 8E14 72C9` in
`SHA256SUMS.asc` (detached signature).
To check the signature:
```
gpg --verify SHA256SUMS.asc
```
To check the hashes match:
```
shasum -a 256 -c SHA256SUMS
```
+14
View File
@@ -0,0 +1,14 @@
791562fe4df2457484ffeb21774990451c59ca1818ccf71003334fe2576dc9a0 ./index.html
5ef88a316223b466c86c5e87991c982591b2b354d365a13d7451201c651e7ced ./build-single-file/index.html
7f8469776f50d7d766cbe3e438e290baf2d91815a3d8233f248b6e412bc8c94e ./package-lock.json
d035d1b528f544331f8c697da0d407427f409b4fbb6f86232ec1445c16e85a7e ./package.json
68df121854e628c7d84ee41b7d86875626cc342aa0c8b0f939af322eb4ba1723 ./tsconfig.json
648d982c9f40db8a813a7fbc9c971b63f85a84a07a307474f71f85653afbdfde ./build/index.html
2462f20149c235e936de6f9420cc0a4630aeb9c26f5dc6c5b906c11edbfc720c ./build/pushtx.js
31ebc58b766680c1000cabd7b7da7eeb943aff58ebc805beba608e15ffc7a3f6 ./build/pushtx.css
2ede67555dd622a2be580b3e15fdde6463413f63e79913f54faa75d819a4cc01 ./vite.config.ts
91d383919e958aa174cb525b7b1c415e50e125b32349673235ac850d88ea4a0b ./src/main.ts
b41fd5552c271453ce7ee1c661e72495705e5da0e005828b17f4ad057ba91e87 ./src/types.ts
65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5 ./src/vite-env.d.ts
4e6dd4e5d1689110a3645a7ee5f82fac8ec32d4aad8c00d906be60f64ebca678 ./src/style.css
54cc1fff898ecd3f437d82947889bbcab5f861904e61c9c1eea9f792a3f32582 ./src/config.ts
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEP00hGW4U/Tk4NUkt+WAjxY4UcskFAmZsZVgACgkQ+WAjxY4U
csmR2w//QkLrh012rneiKhThO1xSgkeeFv05lKDmKDZHxB6m+0YATqmkgZx0dnwo
X4rva4QBgqjr/H5sZI364pNi4B+pgzLV1PjEMvsvEcRMi8mQL9Y+GyTGUE0GndJW
oWQ4aYcVsV0Qz0mcEGNsupe7NOFKZQhTooSULv3gOtfkwbAejlFJRBiddYQzc9aX
GZYM9S3dVV5DK0zeNf8Z5UofEyFY6vJ8nP6bmVzGgzmI5gzjFKPNH8aLpyQvkdQI
0VNZ1P4HZQH9iw4z7wIHCqmCdPdERx/3roQmQ++v83ieozCNelamkRdGWnT4sV9T
PpigNofqolgxauf506fdfewPqQQ/Ir1EQGAE7G+E/lCxFe+P6lipPkwdJGr6v/g6
Co1WhEij0r++COF4oj9TPQCDtsEhCP2vMbomDL+pnOIGPuNtZvKKLJiYa93+o5QR
RWo5m1ZP9yU+xs8v+5Gp/xQAZQD7AYGllCkNG89L+bkXvuFbHBh20hWXjPisGMcb
MyC5iN7L+x3lu/t2iq+XvmNo9K504WWMzmzVbI2FWxTabbEA0IwQSZhbZDeGHAYP
tBfLnnOzlcYXS3RTJXPE5QrHop6UIyitrzVH/TkJABJOmWUKhm/to43lBA2+NNEg
nOUrhJl0BnYHmPgTkjVqvuPzxe+2dgquYfbwJ/W+sPSsosSpySQ=
=wiI5
-----END PGP SIGNATURE-----
+140
View File
@@ -0,0 +1,140 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>COLDCARD NFC Transaction Broadcast</title>
<script type="module" crossorigin>var Ws=Object.defineProperty;var js=(e,t,r)=>t in e?Ws(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var Zt=(e,t,r)=>(js(e,typeof t!="symbol"?t+"":t,r),r);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))n(i);new MutationObserver(i=>{for(const c of i)if(c.type==="childList")for(const s of c.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&n(s)}).observe(document,{childList:!0,subtree:!0});function r(i){const c={};return i.integrity&&(c.integrity=i.integrity),i.referrerPolicy&&(c.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?c.credentials="include":i.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function n(i){if(i.ep)return;i.ep=!0;const c=r(i);fetch(i.href,c)}})();function Vs(e){if(e.__esModule)return e;var t=e.default;if(typeof t=="function"){var r=function n(){return this instanceof n?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};r.prototype=t.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(e).forEach(function(n){var i=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(r,n,i.get?i:{enumerable:!0,get:function(){return e[n]}})}),r}var lo={},se={},Vr={};Vr.byteLength=qs;Vr.toByteArray=Ys;Vr.fromByteArray=Zs;var $e=[],Pe=[],Gs=typeof Uint8Array<"u"?Uint8Array:Array,wn="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(var Lt=0,Xs=wn.length;Lt<Xs;++Lt)$e[Lt]=wn[Lt],Pe[wn.charCodeAt(Lt)]=Lt;Pe[45]=62;Pe[95]=63;function yo(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");r===-1&&(r=t);var n=r===t?0:4-r%4;return[r,n]}function qs(e){var t=yo(e),r=t[0],n=t[1];return(r+n)*3/4-n}function zs(e,t,r){return(t+r)*3/4-r}function Ys(e){var t,r=yo(e),n=r[0],i=r[1],c=new Gs(zs(e,n,i)),s=0,a=i>0?n-4:n,p;for(p=0;p<a;p+=4)t=Pe[e.charCodeAt(p)]<<18|Pe[e.charCodeAt(p+1)]<<12|Pe[e.charCodeAt(p+2)]<<6|Pe[e.charCodeAt(p+3)],c[s++]=t>>16&255,c[s++]=t>>8&255,c[s++]=t&255;return i===2&&(t=Pe[e.charCodeAt(p)]<<2|Pe[e.charCodeAt(p+1)]>>4,c[s++]=t&255),i===1&&(t=Pe[e.charCodeAt(p)]<<10|Pe[e.charCodeAt(p+1)]<<4|Pe[e.charCodeAt(p+2)]>>2,c[s++]=t>>8&255,c[s++]=t&255),c}function Js(e){return $e[e>>18&63]+$e[e>>12&63]+$e[e>>6&63]+$e[e&63]}function Qs(e,t,r){for(var n,i=[],c=t;c<r;c+=3)n=(e[c]<<16&16711680)+(e[c+1]<<8&65280)+(e[c+2]&255),i.push(Js(n));return i.join("")}function Zs(e){for(var t,r=e.length,n=r%3,i=[],c=16383,s=0,a=r-n;s<a;s+=c)i.push(Qs(e,s,s+c>a?a:s+c));return n===1?(t=e[r-1],i.push($e[t>>2]+$e[t<<4&63]+"==")):n===2&&(t=(e[r-2]<<8)+e[r-1],i.push($e[t>>10]+$e[t>>4&63]+$e[t<<2&63]+"=")),i.join("")}var Vn={};/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */Vn.read=function(e,t,r,n,i){var c,s,a=i*8-n-1,p=(1<<a)-1,w=p>>1,f=-7,d=r?i-1:0,S=r?-1:1,y=e[t+d];for(d+=S,c=y&(1<<-f)-1,y>>=-f,f+=a;f>0;c=c*256+e[t+d],d+=S,f-=8);for(s=c&(1<<-f)-1,c>>=-f,f+=n;f>0;s=s*256+e[t+d],d+=S,f-=8);if(c===0)c=1-w;else{if(c===p)return s?NaN:(y?-1:1)*(1/0);s=s+Math.pow(2,n),c=c-w}return(y?-1:1)*s*Math.pow(2,c-n)};Vn.write=function(e,t,r,n,i,c){var s,a,p,w=c*8-i-1,f=(1<<w)-1,d=f>>1,S=i===23?Math.pow(2,-24)-Math.pow(2,-77):0,y=n?0:c-1,_=n?1:-1,T=t<0||t===0&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=f):(s=Math.floor(Math.log(t)/Math.LN2),t*(p=Math.pow(2,-s))<1&&(s--,p*=2),s+d>=1?t+=S/p:t+=S*Math.pow(2,1-d),t*p>=2&&(s++,p/=2),s+d>=f?(a=0,s=f):s+d>=1?(a=(t*p-1)*Math.pow(2,i),s=s+d):(a=t*Math.pow(2,d-1)*Math.pow(2,i),s=0));i>=8;e[r+y]=a&255,y+=_,a/=256,i-=8);for(s=s<<i|a,w+=i;w>0;e[r+y]=s&255,y+=_,s/=256,w-=8);e[r+y-_]|=T*128};/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/(function(e){const t=Vr,r=Vn,n=typeof Symbol=="function"&&typeof Symbol.for=="function"?Symbol.for("nodejs.util.inspect.custom"):null;e.Buffer=f,e.SlowBuffer=v,e.INSPECT_MAX_BYTES=50;const i=2147483647;e.kMaxLength=i;const{Uint8Array:c,ArrayBuffer:s,SharedArrayBuffer:a}=globalThis;f.TYPED_ARRAY_SUPPORT=p(),!f.TYPED_ARRAY_SUPPORT&&typeof console<"u"&&typeof console.error=="function"&&console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support.");function p(){try{const h=new c(1),o={foo:function(){return 42}};return Object.setPrototypeOf(o,c.prototype),Object.setPrototypeOf(h,o),h.foo()===42}catch{return!1}}Object.defineProperty(f.prototype,"parent",{enumerable:!0,get:function(){if(f.isBuffer(this))return this.buffer}}),Object.defineProperty(f.prototype,"offset",{enumerable:!0,get:function(){if(f.isBuffer(this))return this.byteOffset}});function w(h){if(h>i)throw new RangeError('The value "'+h+'" is invalid for option "size"');const o=new c(h);return Object.setPrototypeOf(o,f.prototype),o}function f(h,o,u){if(typeof h=="number"){if(typeof o=="string")throw new TypeError('The "string" argument must be of type string. Received type number');return _(h)}return d(h,o,u)}f.poolSize=8192;function d(h,o,u){if(typeof h=="string")return T(h,o);if(s.isView(h))return x(h);if(h==null)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof h);if(Ue(h,s)||h&&Ue(h.buffer,s)||typeof a<"u"&&(Ue(h,a)||h&&Ue(h.buffer,a)))return N(h,o,u);if(typeof h=="number")throw new TypeError('The "value" argument must not be of type number. Received type number');const l=h.valueOf&&h.valueOf();if(l!=null&&l!==h)return f.from(l,o,u);const g=k(h);if(g)return g;if(typeof Symbol<"u"&&Symbol.toPrimitive!=null&&typeof h[Symbol.toPrimitive]=="function")return f.from(h[Symbol.toPrimitive]("string"),o,u);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof h)}f.from=function(h,o,u){return d(h,o,u)},Object.setPrototypeOf(f.prototype,c.prototype),Object.setPrototypeOf(f,c);function S(h){if(typeof h!="number")throw new TypeError('"size" argument must be of type number');if(h<0)throw new RangeError('The value "'+h+'" is invalid for option "size"')}function y(h,o,u){return S(h),h<=0?w(h):o!==void 0?typeof u=="string"?w(h).fill(o,u):w(h).fill(o):w(h)}f.alloc=function(h,o,u){return y(h,o,u)};function _(h){return S(h),w(h<0?0:m(h)|0)}f.allocUnsafe=function(h){return _(h)},f.allocUnsafeSlow=function(h){return _(h)};function T(h,o){if((typeof o!="string"||o==="")&&(o="utf8"),!f.isEncoding(o))throw new TypeError("Unknown encoding: "+o);const u=I(h,o)|0;let l=w(u);const g=l.write(h,o);return g!==u&&(l=l.slice(0,g)),l}function P(h){const o=h.length<0?0:m(h.length)|0,u=w(o);for(let l=0;l<o;l+=1)u[l]=h[l]&255;return u}function x(h){if(Ue(h,c)){const o=new c(h);return N(o.buffer,o.byteOffset,o.byteLength)}return P(h)}function N(h,o,u){if(o<0||h.byteLength<o)throw new RangeError('"offset" is outside of buffer bounds');if(h.byteLength<o+(u||0))throw new RangeError('"length" is outside of buffer bounds');let l;return o===void 0&&u===void 0?l=new c(h):u===void 0?l=new c(h,o):l=new c(h,o,u),Object.setPrototypeOf(l,f.prototype),l}function k(h){if(f.isBuffer(h)){const o=m(h.length)|0,u=w(o);return u.length===0||h.copy(u,0,0,o),u}if(h.length!==void 0)return typeof h.length!="number"||yn(h.length)?w(0):P(h);if(h.type==="Buffer"&&Array.isArray(h.data))return P(h.data)}function m(h){if(h>=i)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i.toString(16)+" bytes");return h|0}function v(h){return+h!=h&&(h=0),f.alloc(+h)}f.isBuffer=function(o){return o!=null&&o._isBuffer===!0&&o!==f.prototype},f.compare=function(o,u){if(Ue(o,c)&&(o=f.from(o,o.offset,o.byteLength)),Ue(u,c)&&(u=f.from(u,u.offset,u.byteLength)),!f.isBuffer(o)||!f.isBuffer(u))throw new TypeError('The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array');if(o===u)return 0;let l=o.length,g=u.length;for(let b=0,A=Math.min(l,g);b<A;++b)if(o[b]!==u[b]){l=o[b],g=u[b];break}return l<g?-1:g<l?1:0},f.isEncoding=function(o){switch(String(o).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},f.concat=function(o,u){if(!Array.isArray(o))throw new TypeError('"list" argument must be an Array of Buffers');if(o.length===0)return f.alloc(0);let l;if(u===void 0)for(u=0,l=0;l<o.length;++l)u+=o[l].length;const g=f.allocUnsafe(u);let b=0;for(l=0;l<o.length;++l){let A=o[l];if(Ue(A,c))b+A.length>g.length?(f.isBuffer(A)||(A=f.from(A)),A.copy(g,b)):c.prototype.set.call(g,A,b);else if(f.isBuffer(A))A.copy(g,b);else throw new TypeError('"list" argument must be an Array of Buffers');b+=A.length}return g};funcLine truncated
`,bech32:"bc",bip32:{public:76067358,private:76066276},pubKeyHash:0,scriptHash:5,wif:128};ae.regtest={messagePrefix:`Bitcoin Signed Message:
`,bech32:"bcrt",bip32:{public:70617039,private:70615956},pubKeyHash:111,scriptHash:196,wif:239};ae.testnet={messagePrefix:`Bitcoin Signed Message:
`,bech32:"tb",bip32:{public:70617039,private:70615956},pubKeyHash:111,scriptHash:196,wif:239};var gn={},Gr={},En={},ze={};Object.defineProperty(ze,"__esModule",{value:!0});ze.encode=ze.decode=ze.check=void 0;function gc(e){if(e.length<8||e.length>72||e[0]!==48||e[1]!==e.length-2||e[2]!==2)return!1;const t=e[3];if(t===0||5+t>=e.length||e[4+t]!==2)return!1;const r=e[5+t];return!(r===0||6+t+r!==e.length||e[4]&128||t>1&&e[4]===0&&!(e[5]&128)||e[t+6]&128||r>1&&e[t+6]===0&&!(e[t+7]&128))}ze.check=gc;function Ec(e){if(e.length<8)throw new Error("DER sequence length is too short");if(e.length>72)throw new Error("DER sequence length is too long");if(e[0]!==48)throw new Error("Expected DER sequence");if(e[1]!==e.length-2)throw new Error("DER sequence length is invalid");if(e[2]!==2)throw new Error("Expected DER integer");const t=e[3];if(t===0)throw new Error("R length is zero");if(5+t>=e.length)throw new Error("R length is too long");if(e[4+t]!==2)throw new Error("Expected DER integer (2)");const r=e[5+t];if(r===0)throw new Error("S length is zero");if(6+t+r!==e.length)throw new Error("S length is invalid");if(e[4]&128)throw new Error("R value is negative");if(t>1&&e[4]===0&&!(e[5]&128))throw new Error("R value excessively padded");if(e[t+6]&128)throw new Error("S value is negative");if(r>1&&e[t+6]===0&&!(e[t+7]&128))throw new Error("S value excessively padded");return{r:e.slice(4,4+t),s:e.slice(6+t)}}ze.decode=Ec;function mc(e,t){const r=e.length,n=t.length;if(r===0)throw new Error("R length is zero");if(n===0)throw new Error("S length is zero");if(r>33)throw new Error("R length is too long");if(n>33)throw new Error("S length is too long");if(e[0]&128)throw new Error("R value is negative");if(t[0]&128)throw new Error("S value is negative");if(r>1&&e[0]===0&&!(e[1]&128))throw new Error("R value excessively padded");if(n>1&&t[0]===0&&!(t[1]&128))throw new Error("S value excessively padded");const i=E.allocUnsafe(6+r+n);return i[0]=48,i[1]=i.length-2,i[2]=2,i[3]=e.length,e.copy(i,4),i[4+r]=2,i[5+r]=t.length,t.copy(i,6+r),i}ze.encode=mc;var ft={};Object.defineProperty(ft,"__esModule",{value:!0});ft.REVERSE_OPS=ft.OPS=void 0;const Fn={OP_FALSE:0,OP_0:0,OP_PUSHDATA1:76,OP_PUSHDATA2:77,OP_PUSHDATA4:78,OP_1NEGATE:79,OP_RESERVED:80,OP_TRUE:81,OP_1:81,OP_2:82,OP_3:83,OP_4:84,OP_5:85,OP_6:86,OP_7:87,OP_8:88,OP_9:89,OP_10:90,OP_11:91,OP_12:92,OP_13:93,OP_14:94,OP_15:95,OP_16:96,OP_NOP:97,OP_VER:98,OP_IF:99,OP_NOTIF:100,OP_VERIF:101,OP_VERNOTIF:102,OP_ELSE:103,OP_ENDIF:104,OP_VERIFY:105,OP_RETURN:106,OP_TOALTSTACK:107,OP_FROMALTSTACK:108,OP_2DROP:109,OP_2DUP:110,OP_3DUP:111,OP_2OVER:112,OP_2ROT:113,OP_2SWAP:114,OP_IFDUP:115,OP_DEPTH:116,OP_DROP:117,OP_DUP:118,OP_NIP:119,OP_OVER:120,OP_PICK:121,OP_ROLL:122,OP_ROT:123,OP_SWAP:124,OP_TUCK:125,OP_CAT:126,OP_SUBSTR:127,OP_LEFT:128,OP_RIGHT:129,OP_SIZE:130,OP_INVERT:131,OP_AND:132,OP_OR:133,OP_XOR:134,OP_EQUAL:135,OP_EQUALVERIFY:136,OP_RESERVED1:137,OP_RESERVED2:138,OP_1ADD:139,OP_1SUB:140,OP_2MUL:141,OP_2DIV:142,OP_NEGATE:143,OP_ABS:144,OP_NOT:145,OP_0NOTEQUAL:146,OP_ADD:147,OP_SUB:148,OP_MUL:149,OP_DIV:150,OP_MOD:151,OP_LSHIFT:152,OP_RSHIFT:153,OP_BOOLAND:154,OP_BOOLOR:155,OP_NUMEQUAL:156,OP_NUMEQUALVERIFY:157,OP_NUMNOTEQUAL:158,OP_LESSTHAN:159,OP_GREATERTHAN:160,OP_LESSTHANOREQUAL:161,OP_GREATERTHANOREQUAL:162,OP_MIN:163,OP_MAX:164,OP_WITHIN:165,OP_RIPEMD160:166,OP_SHA1:167,OP_SHA256:168,OP_HASH160:169,OP_HASH256:170,OP_CODESEPARATOR:171,OP_CHECKSIG:172,OP_CHECKSIGVERIFY:173,OP_CHECKMULTISIG:174,OP_CHECKMULTISIGVERIFY:175,OP_NOP1:176,OP_NOP2:177,OP_CHECKLOCKTIMEVERIFY:177,OP_NOP3:178,OP_CHECKSEQUENCEVERIFY:178,OP_NOP4:179,OP_NOP5:180,OP_NOP6:181,OP_NOP7:182,OP_NOP8:183,OP_NOP9:184,OP_NOP10:185,OP_CHECKSIGADD:186,OP_PUBKEYHASH:253,OP_PUBKEY:254,OP_INVALIDOPCODE:255};ft.OPS=Fn;const go={};ft.REVERSE_OPS=go;for(const e of Object.keys(Fn)){const t=Fn[e];go[t]=e}var ut={};Object.defineProperty(ut,"__esModule",{value:!0});ut.decode=ut.encode=ut.encodingLength=void 0;const ct=ft;function Eo(e){return e<ct.OPS.OP_PUSHDATA1?1:e<=255?2:e<=65535?3:5}ut.encodingLength=Eo;function Sc(e,t,r){const n=Eo(t);return n===1?e.writeUInt8(t,r):n===2?(e.writeUInt8(ct.OPS.OP_PUSHDATA1,r),e.writeUInt8(t,r+1)):n===3?(e.writeUInt8(ct.OPS.OP_PUSHDATA2,r),e.writeUInt16LE(t,r+1)):(e.writeUInt8(ct.OPS.OP_PUSHDATA4,r),e.writeUInt32LE(t,r+1)),n}ut.encode=Sc;function bc(e,t){const r=e.readUInt8(t);let n,i;if(r<ct.OPS.OP_PUSHDATA1)n=r,i=1;else if(r===ct.OPS.OP_PUSHDATA1){if(t+2>e.length)return null;n=e.readUInt8(t+1),i=2}else if(r===ct.OPS.OP_PUSHDATA2){if(t+3>e.length)return null;n=e.readUInt16LE(t+1),i=3}else{if(t+5>e.length)return null;if(r!==ct.OPS.OP_PUSHDATA4)throw new Error("Unexpected opcode");n=e.readUInt32LE(t+1),i=5}return{opcode:r,number:n,size:i}}ut.decode=bc;var zt={};Object.defineProperty(zt,"__esModule",{value:!0});zt.encode=zt.decode=void 0;function _c(e,t,r){t=t||4,r=r===void 0?!0:r;const n=e.length;if(n===0)return 0;if(n>t)throw new TypeError("Script number overflow");if(r&&!(e[n-1]&127)&&(n<=1||Line truncated
*********************
PROCEED WITH CAUTION!
*********************`),a=c.hashForSignature(e,w,s)}return{script:w,sighashType:s,hash:a}}function yh(e,t,r,n){const i=[];if(t.tapInternalKey){const s=_s(e,t,n);s&&i.push(s)}if(t.tapScriptSig){const s=t.tapScriptSig.map(a=>a.pubkey);i.push(...s)}return i.map(s=>Wn(e,t,r,s,n)).flat()}function _s(e,t,r){const{script:n}=wi(e,t,r);return(0,re.isP2TR)(n)?n.subarray(2,34):null}function fo(e){return e.length===64?e:e.subarray(0,64)}function Wn(e,t,r,n,i,c,s){const a=i.__TX,p=t.sighashType||we.Transaction.SIGHASH_DEFAULT;Ts(p,s);const w=r.map((_,T)=>wi(T,_,i)),f=w.map(_=>_.script),d=w.map(_=>_.value),S=[];if(t.tapInternalKey&&!c){const _=_s(e,t,i)||E.from([]);if((0,J.toXOnly)(n).equals(_)){const T=a.hashForWitnessV1(e,f,d,p);S.push({pubkey:n,hash:T})}}const y=(t.tapLeafScript||[]).filter(_=>(0,re.pubkeyInScript)(n,_.script)).map(_=>{const T=(0,oh.tapleafHash)({output:_.script,version:_.leafVersion});return Object.assign({hash:T},_)}).filter(_=>!c||c.equals(_.hash)).map(_=>{const T=a.hashForWitnessV1(e,f,d,p,_.hash);return{pubkey:n,hash:T,leafHash:_.hash}});return S.concat(y)}function Ts(e,t){if(t&&t.indexOf(e)<0){const r=mh(e);throw new Error(`Sighash type is not allowed. Retry the sign method passing the sighashTypes array of whitelisted types. Sighash type: ${r}`)}}function wh(e,t,r){let n;switch(t){case"multisig":const i=Eh(e,r);n=ve.p2ms({output:e,signatures:i});break;case"pubkey":n=ve.p2pk({output:e,signature:r[0].signature});break;case"pubkeyhash":n=ve.p2pkh({output:e,pubkey:r[0].pubkey,signature:r[0].signature});break;case"witnesspubkeyhash":n=ve.p2wpkh({output:e,pubkey:r[0].pubkey,signature:r[0].signature});break}return n}function gh(e,t,r){const n=r.__TX,i={script:null,isSegwit:!1,isP2SH:!1,isP2WSH:!1};if(i.isP2SH=!!t.redeemScript,i.isP2WSH=!!t.witnessScript,t.witnessScript)i.script=t.witnessScript;else if(t.redeemScript)i.script=t.redeemScript;else if(t.nonWitnessUtxo){const c=fn(r,t,e),s=n.ins[e].index;i.script=c.outs[s].script}else t.witnessUtxo&&(i.script=t.witnessUtxo.script);return(t.witnessScript||(0,re.isP2WPKH)(i.script))&&(i.isSegwit=!0),i}function ho(e,t,r){const n=(0,Se.checkForInput)(t,e);if(!n.bip32Derivation||n.bip32Derivation.length===0)throw new Error("Need bip32Derivation to sign with HD");const i=n.bip32Derivation.map(s=>{if(s.masterFingerprint.equals(r.fingerprint))return s}).filter(s=>!!s);if(i.length===0)throw new Error("Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint");return i.map(s=>{const a=r.derivePath(s.path);if(!s.pubkey.equals(a.publicKey))throw new Error("pubkey did not match bip32Derivation");return a})}function Eh(e,t){return ve.p2ms({output:e}).pubkeys.map(n=>(t.filter(i=>i.pubkey.equals(n))[0]||{}).signature).filter(n=>!!n)}function Is(e){let t=0;function r(s){return t+=s,e.slice(t-s,t)}function n(){const s=ro.decode(e,t);return t+=ro.decode.bytes,s}function i(){return r(n())}function c(){const s=n(),a=[];for(let p=0;p<s;p++)a.push(i());return a}return c()}function mh(e){let t=e&we.Transaction.SIGHASH_ANYONECANPAY?"SIGHASH_ANYONECANPAY | ":"";switch(e&31){case we.Transaction.SIGHASH_ALL:t+="SIGHASH_ALL";break;case we.Transaction.SIGHASH_SINGLE:t+="SIGHASH_SINGLE";break;case we.Transaction.SIGHASH_NONE:t+="SIGHASH_NONE";break}return t}function jn(e,t,r){e.__NON_WITNESS_UTXO_BUF_CACHE[r]=t.nonWitnessUtxo;const n=we.Transaction.fromBuffer(t.nonWitnessUtxo);e.__NON_WITNESS_UTXO_TX_CACHE[r]=n;const i=e,c=r;delete t.nonWitnessUtxo,Object.defineProperty(t,"nonWitnessUtxo",{enumerable:!0,get(){const s=i.__NON_WITNESS_UTXO_BUF_CACHE[c],a=i.__NON_WITNESS_UTXO_TX_CACHE[c];if(s!==void 0)return s;{const p=a.toBuffer();return i.__NON_WITNESS_UTXO_BUF_CACHE[c]=p,p}},set(s){i.__NON_WITNESS_UTXO_BUF_CACHE[c]=s}})}function ks(e,t,r,n){let i=0;e.forEach((p,w)=>{if(n&&p.finalScriptSig&&(t.ins[w].script=p.finalScriptSig),n&&p.finalScriptWitness&&(t.ins[w].witness=Is(p.finalScriptWitness)),p.witnessUtxo)i+=p.witnessUtxo.value;else if(p.nonWitnessUtxo){const f=fn(r,p,w),d=t.ins[w].index,S=f.outs[d];i+=S.value}});const c=t.outs.reduce((p,w)=>p+w.value,0),s=i-c;if(s<0)throw new Error("Outputs are spending more than Inputs");const a=t.virtualSize();r.__FEE=s,r.__EXTRACTED_TX=t,r.__FEE_RATE=Math.floor(s/a)}function fn(e,t,r){const n=e.__NON_WITNESS_UTXO_TX_CACHE;return n[r]||jn(e,t,r),n[r]}function As(e,t,r){const{script:n}=wi(e,t,r);return n}function wi(e,t,r){if(t.witnessUtxo!==void 0)return{script:t.witnessUtxo.script,value:t.witnessUtxo.value};if(t.nonWitnessUtxo!==void 0){const i=fn(r,t,e).outs[r.__TX.ins[e].index];return{script:i.script,value:i.value}}else throw new Error("Can't find pubkey in input without Utxo data")}function Sh(e,t,r,n){const i=As(r,t,n),{meaningfulScript:c}=hn(i,r,"input",t.redeemScript,t.witnessScript);return(0,re.pubkeyInScript)(e,c)}function bh(e,t,r,n){const i=n.__TX.outs[r].script,{meaningfulScript:c}=hn(i,r,"output",t.redeemScript,t.witnessScript);return(0,re.pubkeyInScript)(e,c)}function _h(e){if(!e)return;const t=Ye.decompile(e);if(!tLine truncated
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="m10.97 4.97-.02.022-3.473 4.425-2.093-2.094a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05"/>
</svg>`,error:`
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/>
</svg>`,info:`
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
</svg>`,progress:`
<svg class="pushtx-spin" width="32" height="32" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle style="opacity: 0.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>`};async function dr(e){try{return[null,await e]}catch(t){return t instanceof Error?[t,null]:typeof t=="string"?[new Error(t),null]:[new Error,null]}}function po(e){const t=e.replace(/-/g,"+").replace(/_/g,"/").padEnd(e.length+(4-e.length%4)%4,"="),r=atob(t);return new Uint8Array([...r].map(n=>n.charCodeAt(0)))}class Bs extends Error{constructor(r,n,i,c){super();Zt(this,"url");Zt(this,"status");Zt(this,"statusText");Zt(this,"body");this.url=r,this.name="FetchStatusError",this.status=n,this.statusText=i,this.body=c}}const xs=async(e,t)=>{const r=await fetch(e,t);if(!r.ok){const[n,i]=await dr(r.text());throw new Bs(e.toString(),r.status,r.statusText,i)}return r};async function Ph(e,t){if(t!=="BTC"&&t!=="XTN")throw new Error("Unsupported network: "+t);const n=vs[t].map(s=>xs(s,{method:"POST",body:e.toHex()})),[i,c]=await dr(Promise.any(n).then(s=>s.text()));if(c)return c;if(i instanceof AggregateError){const s=i.errors.filter(a=>a instanceof Bs);if(s.length>0){let a=`
<p>The transaction was rejected by all providers:</p>
<ul>
`;throw s.forEach(p=>{a+=`<li>${p.url}: ${p.body}</li>`}),a+="</ul>",new Error(a)}}throw new Error("<p>Could not connect to any push servers. Make sure you are connected to the Internet and try again.</p>")}async function vh(e){e[0]==="#"&&(e=e.slice(1));const t=new URLSearchParams(e),r=t.get("t"),n=t.get("c"),i=t.get("n")||"BTC";if(!r)throw new Error("Invalid URL - missing transaction.");if(!n||n.length!==11)throw new Error("Invalid URL - missing or incomplete checksum. The URL is probably truncated");let c;if(i==="BTC"||i==="XTN")c=i;else throw i==="XRT"?new Error("Regtest transactions are not supported."):new Error(`Invalid URL. The network "${i}" is not recognized.`);let s,a;try{s=po(r),a=po(n)}catch{throw new Error("Invalid URL encoding. The URL is probably corrupted.")}const p=new Uint8Array(await crypto.subtle.digest("SHA-256",s));if(!a.every((f,d)=>f===p[d+24]))throw new Error("Checksum mismatch in URL. Some bytes corrupted in transit. Try again.");return{tx:lo.Transaction.fromBuffer(wo.from(s)),network:c}}async function Bh(e,t){if(t!=="BTC"&&t!=="XTN")throw new Error("Unsupported network: "+t);const n=vs[t].map(i=>xs(i+"/"+e).then(c=>c.json()));return await Promise.any(n)}function xh(e){const t=e.vin.map(n=>`
<tr>
<td>
<span class="address">
<span>${n.prevout.scriptpubkey_address.slice(0,-8)}</span>
<span>${n.prevout.scriptpubkey_address.slice(-8)}</span>
</span>
</td>
<td>
<span class="value">${(n.prevout.value/1e8).toFixed(8)}</span>
</td>
</tr>
`).join(""),r=e.vout.map(n=>`
<tr>
<td>
<span class="address">
<span>${n.scriptpubkey_address.slice(0,-8)}</span>
<span>${n.scriptpubkey_address.slice(-8)}</span>
</span>
</td>
<td>
<span class="value">${(n.value/1e8).toFixed(8)}</span>
</td>
</tr>
`).join("");return`
<div class="pushtx-details">
<div class="inputs">
<table>
<thead>
<tr>
<th>Inputs</th>
<th></th>
</tr>
</thead>
<tbody>
${t}
</tbody>
</table>
</div>
<div class="outputs">
<table>
<thead>
<tr>
<th>Outputs</th>
<th></th>
</tr>
</thead>
<tbody>
${r}
</tbody>
</table>
</div>
<div class="fee">
<strong>Fee:</strong> <span class="value">${(e.fee/1e8).toFixed(8)}</span>
</div>
</div>
`}function Kt(e,t){const r=Oh[e];return`
<div class="pushtx-message pushtx-message--${e}" role="alert">
${r}
<div>${t}</div>
</div>
`}async function Us(){const e=document.querySelector(".pushtx-message-area"),t=document.querySelector(".pushtx-details-area");if(!e||!t)throw new Error("Need message and details areas in HTML.");if(e.innerHTML="",t.innerHTML="",!window.location.hash){const d=`
<p><strong>Did you get here by accident?</strong></p>
<p>
This page is meant to be loaded together with transaction data using the
<strong>COLDCARD NFC Push TX feature</strong>. The complete URL should look something like this (but longer):
</p>
<p><code>https://coldcard.com/pushtx#t=AgAAAAMNCxXtp2GVYVhkRXHLMmdZFs4p3kbFK ⋯ ABf&c=uiSVRda-1tw</code></p>
`;e.innerHTML=Kt("info",d);return}e.innerHTML=Kt("progress","Sending transaction, please wait...");const[r,n]=await dr(vh(window.location.hash));if(r){e.innerHTML=Kt("error",r.message);return}const{tx:i,network:c}=n,s=i.getId(),[a,p]=await dr(Ph(i,c)),[w,f]=await dr(Bh(s,c));if(p||f){const d=f!=null&&f.status.confirmed?"This transaction has already been confirmed.":"The transaction has been sent and is waiting to be confirmed.",S=Ah[c].map(([y,_])=>`<li><a href="${_}${s}" target="_blank" rel="noopener">${y}</a></li>`).join("");e.innerHTML=Kt("success",`<p>${d} Transaction ID:</p>
<p class="txid">${s}</p>
<p>Verify on a block explorer:</p>
<ul>${S}</ul>
`),f&&(t.innerHTML=xh(f));return}if(a&&w)e.innerHTML=Kt("error",a.message);else if(w){e.innerHTML=Kt("error",w.message);return}}Us();window.addEventListener("hashchange",Us);
</script>
<style rel="stylesheet" crossorigin>.pushtx *{margin:0;padding:0;box-sizing:border-box;color:inherit!important}.pushtx ul{list-style-position:inside}.pushtx .monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pushtx-spin{animation:spin 1s linear infinite}.pushtx-message{display:flex;gap:1rem;padding:1rem;border:1px solid transparent;border-radius:.25rem;width:100%}.pushtx-message>:first-child{flex-shrink:0}.pushtx-message>:last-child{overflow:hidden;overflow-wrap:break-word;display:flex;flex-direction:column;gap:.5rem}.pushtx-message--success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.pushtx-message--info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.pushtx-message--progress{color:#004085;background-color:#cce5ff;border-color:#b8daff}.pushtx-message--error{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.pushtx-message .txid{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875em;font-weight:700}.pushtx-details{display:flex;flex-direction:column;margin-block:2rem;padding:.5rem;border-radius:.25rem;overflow:hidden;gap:.5rem;border:1px solid #e2e8f0}.pushtx-details table{border-collapse:collapse;width:100%}.pushtx-details th{text-align:left}.pushtx-details :is(th,td){border-width:0px;padding-block:.125rem}.pushtx-details :is(td:first-child,th:first-child){width:75%;max-width:0px}.pushtx-details :is(td:last-child,th:last-child){text-align:right}.pushtx-details :is(.address,.value){font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.pushtx-details .address{display:flex;padding-right:1em}.pushtx-details .address :first-child{overflow:hidden;text-overflow:ellipsis}.pushtx-details .fee{text-align:right}
</style>
</head>
<body>
<main class="pushtx" style="
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
max-width: 1024px;
margin: 5vh auto;
">
<h1 style="margin-bottom: 2rem;">COLDCARD NFC Transaction Broadcast</h1>
<div>
<div class="pushtx-message-area"></div>
<div class="pushtx-details-area"></div>
</div>
</main>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
.pushtx *{margin:0;padding:0;box-sizing:border-box;color:inherit!important}.pushtx ul{list-style-position:inside}.pushtx .monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pushtx-spin{animation:spin 1s linear infinite}.pushtx-message{display:flex;gap:1rem;padding:1rem;border:1px solid transparent;border-radius:.25rem;width:100%}.pushtx-message>:first-child{flex-shrink:0}.pushtx-message>:last-child{overflow:hidden;overflow-wrap:break-word;display:flex;flex-direction:column;gap:.5rem}.pushtx-message--success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.pushtx-message--info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.pushtx-message--progress{color:#004085;background-color:#cce5ff;border-color:#b8daff}.pushtx-message--error{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.pushtx-message .txid{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875em;font-weight:700}.pushtx-details{display:flex;flex-direction:column;margin-block:2rem;padding:.5rem;border-radius:.25rem;overflow:hidden;gap:.5rem;border:1px solid #e2e8f0}.pushtx-details table{border-collapse:collapse;width:100%}.pushtx-details th{text-align:left}.pushtx-details :is(th,td){border-width:0px;padding-block:.125rem}.pushtx-details :is(td:first-child,th:first-child){width:75%;max-width:0px}.pushtx-details :is(td:last-child,th:last-child){text-align:right}.pushtx-details :is(.address,.value){font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.pushtx-details .address{display:flex;padding-right:1em}.pushtx-details .address :first-child{overflow:hidden;text-overflow:ellipsis}.pushtx-details .fee{text-align:right}
+111
View File
@@ -0,0 +1,111 @@
var Ws=Object.defineProperty;var js=(e,t,r)=>t in e?Ws(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var Zt=(e,t,r)=>(js(e,typeof t!="symbol"?t+"":t,r),r);(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))n(i);new MutationObserver(i=>{for(const c of i)if(c.type==="childList")for(const s of c.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&n(s)}).observe(document,{childList:!0,subtree:!0});function r(i){const c={};return i.integrity&&(c.integrity=i.integrity),i.referrerPolicy&&(c.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?c.credentials="include":i.crossOrigin==="anonymous"?c.credentials="omit":c.credentials="same-origin",c}function n(i){if(i.ep)return;i.ep=!0;const c=r(i);fetch(i.href,c)}})();function Vs(e){if(e.__esModule)return e;var t=e.default;if(typeof t=="function"){var r=function n(){return this instanceof n?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};r.prototype=t.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(e).forEach(function(n){var i=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(r,n,i.get?i:{enumerable:!0,get:function(){return e[n]}})}),r}var lo={},se={},Vr={};Vr.byteLength=qs;Vr.toByteArray=Ys;Vr.fromByteArray=Zs;var $e=[],Pe=[],Gs=typeof Uint8Array<"u"?Uint8Array:Array,wn="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(var Lt=0,Xs=wn.length;Lt<Xs;++Lt)$e[Lt]=wn[Lt],Pe[wn.charCodeAt(Lt)]=Lt;Pe[45]=62;Pe[95]=63;function yo(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");r===-1&&(r=t);var n=r===t?0:4-r%4;return[r,n]}function qs(e){var t=yo(e),r=t[0],n=t[1];return(r+n)*3/4-n}function zs(e,t,r){return(t+r)*3/4-r}function Ys(e){var t,r=yo(e),n=r[0],i=r[1],c=new Gs(zs(e,n,i)),s=0,a=i>0?n-4:n,p;for(p=0;p<a;p+=4)t=Pe[e.charCodeAt(p)]<<18|Pe[e.charCodeAt(p+1)]<<12|Pe[e.charCodeAt(p+2)]<<6|Pe[e.charCodeAt(p+3)],c[s++]=t>>16&255,c[s++]=t>>8&255,c[s++]=t&255;return i===2&&(t=Pe[e.charCodeAt(p)]<<2|Pe[e.charCodeAt(p+1)]>>4,c[s++]=t&255),i===1&&(t=Pe[e.charCodeAt(p)]<<10|Pe[e.charCodeAt(p+1)]<<4|Pe[e.charCodeAt(p+2)]>>2,c[s++]=t>>8&255,c[s++]=t&255),c}function Js(e){return $e[e>>18&63]+$e[e>>12&63]+$e[e>>6&63]+$e[e&63]}function Qs(e,t,r){for(var n,i=[],c=t;c<r;c+=3)n=(e[c]<<16&16711680)+(e[c+1]<<8&65280)+(e[c+2]&255),i.push(Js(n));return i.join("")}function Zs(e){for(var t,r=e.length,n=r%3,i=[],c=16383,s=0,a=r-n;s<a;s+=c)i.push(Qs(e,s,s+c>a?a:s+c));return n===1?(t=e[r-1],i.push($e[t>>2]+$e[t<<4&63]+"==")):n===2&&(t=(e[r-2]<<8)+e[r-1],i.push($e[t>>10]+$e[t>>4&63]+$e[t<<2&63]+"=")),i.join("")}var Vn={};/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */Vn.read=function(e,t,r,n,i){var c,s,a=i*8-n-1,p=(1<<a)-1,w=p>>1,f=-7,d=r?i-1:0,S=r?-1:1,y=e[t+d];for(d+=S,c=y&(1<<-f)-1,y>>=-f,f+=a;f>0;c=c*256+e[t+d],d+=S,f-=8);for(s=c&(1<<-f)-1,c>>=-f,f+=n;f>0;s=s*256+e[t+d],d+=S,f-=8);if(c===0)c=1-w;else{if(c===p)return s?NaN:(y?-1:1)*(1/0);s=s+Math.pow(2,n),c=c-w}return(y?-1:1)*s*Math.pow(2,c-n)};Vn.write=function(e,t,r,n,i,c){var s,a,p,w=c*8-i-1,f=(1<<w)-1,d=f>>1,S=i===23?Math.pow(2,-24)-Math.pow(2,-77):0,y=n?0:c-1,_=n?1:-1,T=t<0||t===0&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=f):(s=Math.floor(Math.log(t)/Math.LN2),t*(p=Math.pow(2,-s))<1&&(s--,p*=2),s+d>=1?t+=S/p:t+=S*Math.pow(2,1-d),t*p>=2&&(s++,p/=2),s+d>=f?(a=0,s=f):s+d>=1?(a=(t*p-1)*Math.pow(2,i),s=s+d):(a=t*Math.pow(2,d-1)*Math.pow(2,i),s=0));i>=8;e[r+y]=a&255,y+=_,a/=256,i-=8);for(s=s<<i|a,w+=i;w>0;e[r+y]=s&255,y+=_,s/=256,w-=8);e[r+y-_]|=T*128};/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/(function(e){const t=Vr,r=Vn,n=typeof Symbol=="function"&&typeof Symbol.for=="function"?Symbol.for("nodejs.util.inspect.custom"):null;e.Buffer=f,e.SlowBuffer=v,e.INSPECT_MAX_BYTES=50;const i=2147483647;e.kMaxLength=i;const{Uint8Array:c,ArrayBuffer:s,SharedArrayBuffer:a}=globalThis;f.TYPED_ARRAY_SUPPORT=p(),!f.TYPED_ARRAY_SUPPORT&&typeof console<"u"&&typeof console.error=="function"&&console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support.");function p(){try{const h=new c(1),o={foo:function(){return 42}};return Object.setPrototypeOf(o,c.prototype),Object.setPrototypeOf(h,o),h.foo()===42}catch{return!1}}Object.defineProperty(f.prototype,"parent",{enumerable:!0,get:function(){if(f.isBuffer(this))return this.buffer}}),Object.defineProperty(f.prototype,"offset",{enumerable:!0,get:function(){if(f.isBuffer(this))return this.byteOffset}});function w(h){if(h>i)throw new RangeError('The value "'+h+'" is invalid for option "size"');const o=new c(h);return Object.setPrototypeOf(o,f.prototype),o}function f(h,o,u){if(typeof h=="number"){if(typeof o=="string")throw new TypeError('The "string" argument must be of type string. Received type number');return _(h)}return d(h,o,u)}f.poolSize=8192;function d(h,o,u){if(typeof h=="string")return T(h,o);if(s.isView(h))return x(h);if(h==null)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof h);if(Ue(h,s)||h&&Ue(h.buffer,s)||typeof a<"u"&&(Ue(h,a)||h&&Ue(h.buffer,a)))return N(h,o,u);if(typeof h=="number")throw new TypeError('The "value" argument must not be of type number. Received type number');const l=h.valueOf&&h.valueOf();if(l!=null&&l!==h)return f.from(l,o,u);const g=k(h);if(g)return g;if(typeof Symbol<"u"&&Symbol.toPrimitive!=null&&typeof h[Symbol.toPrimitive]=="function")return f.from(h[Symbol.toPrimitive]("string"),o,u);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof h)}f.from=function(h,o,u){return d(h,o,u)},Object.setPrototypeOf(f.prototype,c.prototype),Object.setPrototypeOf(f,c);function S(h){if(typeof h!="number")throw new TypeError('"size" argument must be of type number');if(h<0)throw new RangeError('The value "'+h+'" is invalid for option "size"')}function y(h,o,u){return S(h),h<=0?w(h):o!==void 0?typeof u=="string"?w(h).fill(o,u):w(h).fill(o):w(h)}f.alloc=function(h,o,u){return y(h,o,u)};function _(h){return S(h),w(h<0?0:m(h)|0)}f.allocUnsafe=function(h){return _(h)},f.allocUnsafeSlow=function(h){return _(h)};function T(h,o){if((typeof o!="string"||o==="")&&(o="utf8"),!f.isEncoding(o))throw new TypeError("Unknown encoding: "+o);const u=I(h,o)|0;let l=w(u);const g=l.write(h,o);return g!==u&&(l=l.slice(0,g)),l}function P(h){const o=h.length<0?0:m(h.length)|0,u=w(o);for(let l=0;l<o;l+=1)u[l]=h[l]&255;return u}function x(h){if(Ue(h,c)){const o=new c(h);return N(o.buffer,o.byteOffset,o.byteLength)}return P(h)}function N(h,o,u){if(o<0||h.byteLength<o)throw new RangeError('"offset" is outside of buffer bounds');if(h.byteLength<o+(u||0))throw new RangeError('"length" is outside of buffer bounds');let l;return o===void 0&&u===void 0?l=new c(h):u===void 0?l=new c(h,o):l=new c(h,o,u),Object.setPrototypeOf(l,f.prototype),l}function k(h){if(f.isBuffer(h)){const o=m(h.length)|0,u=w(o);return u.length===0||h.copy(u,0,0,o),u}if(h.length!==void 0)return typeof h.length!="number"||yn(h.length)?w(0):P(h);if(h.type==="Buffer"&&Array.isArray(h.data))return P(h.data)}function m(h){if(h>=i)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i.toString(16)+" bytes");return h|0}function v(h){return+h!=h&&(h=0),f.alloc(+h)}f.isBuffer=function(o){return o!=null&&o._isBuffer===!0&&o!==f.prototype},f.compare=function(o,u){if(Ue(o,c)&&(o=f.from(o,o.offset,o.byteLength)),Ue(u,c)&&(u=f.from(u,u.offset,u.byteLength)),!f.isBuffer(o)||!f.isBuffer(u))throw new TypeError('The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array');if(o===u)return 0;let l=o.length,g=u.length;for(let b=0,A=Math.min(l,g);b<A;++b)if(o[b]!==u[b]){l=o[b],g=u[b];break}return l<g?-1:g<l?1:0},f.isEncoding=function(o){switch(String(o).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},f.concat=function(o,u){if(!Array.isArray(o))throw new TypeError('"list" argument must be an Array of Buffers');if(o.length===0)return f.alloc(0);let l;if(u===void 0)for(u=0,l=0;l<o.length;++l)u+=o[l].length;const g=f.allocUnsafe(u);let b=0;for(l=0;l<o.length;++l){let A=o[l];if(Ue(A,c))b+A.length>g.length?(f.isBuffer(A)||(A=f.from(A)),A.copy(g,b)):c.prototype.set.call(g,A,b);else if(f.isBuffer(A))A.copy(g,b);else throw new TypeError('"list" argument must be an Array of Buffers');b+=A.length}return g};funcLine truncated
`,bech32:"bc",bip32:{public:76067358,private:76066276},pubKeyHash:0,scriptHash:5,wif:128};ae.regtest={messagePrefix:`Bitcoin Signed Message:
`,bech32:"bcrt",bip32:{public:70617039,private:70615956},pubKeyHash:111,scriptHash:196,wif:239};ae.testnet={messagePrefix:`Bitcoin Signed Message:
`,bech32:"tb",bip32:{public:70617039,private:70615956},pubKeyHash:111,scriptHash:196,wif:239};var gn={},Gr={},En={},ze={};Object.defineProperty(ze,"__esModule",{value:!0});ze.encode=ze.decode=ze.check=void 0;function gc(e){if(e.length<8||e.length>72||e[0]!==48||e[1]!==e.length-2||e[2]!==2)return!1;const t=e[3];if(t===0||5+t>=e.length||e[4+t]!==2)return!1;const r=e[5+t];return!(r===0||6+t+r!==e.length||e[4]&128||t>1&&e[4]===0&&!(e[5]&128)||e[t+6]&128||r>1&&e[t+6]===0&&!(e[t+7]&128))}ze.check=gc;function Ec(e){if(e.length<8)throw new Error("DER sequence length is too short");if(e.length>72)throw new Error("DER sequence length is too long");if(e[0]!==48)throw new Error("Expected DER sequence");if(e[1]!==e.length-2)throw new Error("DER sequence length is invalid");if(e[2]!==2)throw new Error("Expected DER integer");const t=e[3];if(t===0)throw new Error("R length is zero");if(5+t>=e.length)throw new Error("R length is too long");if(e[4+t]!==2)throw new Error("Expected DER integer (2)");const r=e[5+t];if(r===0)throw new Error("S length is zero");if(6+t+r!==e.length)throw new Error("S length is invalid");if(e[4]&128)throw new Error("R value is negative");if(t>1&&e[4]===0&&!(e[5]&128))throw new Error("R value excessively padded");if(e[t+6]&128)throw new Error("S value is negative");if(r>1&&e[t+6]===0&&!(e[t+7]&128))throw new Error("S value excessively padded");return{r:e.slice(4,4+t),s:e.slice(6+t)}}ze.decode=Ec;function mc(e,t){const r=e.length,n=t.length;if(r===0)throw new Error("R length is zero");if(n===0)throw new Error("S length is zero");if(r>33)throw new Error("R length is too long");if(n>33)throw new Error("S length is too long");if(e[0]&128)throw new Error("R value is negative");if(t[0]&128)throw new Error("S value is negative");if(r>1&&e[0]===0&&!(e[1]&128))throw new Error("R value excessively padded");if(n>1&&t[0]===0&&!(t[1]&128))throw new Error("S value excessively padded");const i=E.allocUnsafe(6+r+n);return i[0]=48,i[1]=i.length-2,i[2]=2,i[3]=e.length,e.copy(i,4),i[4+r]=2,i[5+r]=t.length,t.copy(i,6+r),i}ze.encode=mc;var ft={};Object.defineProperty(ft,"__esModule",{value:!0});ft.REVERSE_OPS=ft.OPS=void 0;const Fn={OP_FALSE:0,OP_0:0,OP_PUSHDATA1:76,OP_PUSHDATA2:77,OP_PUSHDATA4:78,OP_1NEGATE:79,OP_RESERVED:80,OP_TRUE:81,OP_1:81,OP_2:82,OP_3:83,OP_4:84,OP_5:85,OP_6:86,OP_7:87,OP_8:88,OP_9:89,OP_10:90,OP_11:91,OP_12:92,OP_13:93,OP_14:94,OP_15:95,OP_16:96,OP_NOP:97,OP_VER:98,OP_IF:99,OP_NOTIF:100,OP_VERIF:101,OP_VERNOTIF:102,OP_ELSE:103,OP_ENDIF:104,OP_VERIFY:105,OP_RETURN:106,OP_TOALTSTACK:107,OP_FROMALTSTACK:108,OP_2DROP:109,OP_2DUP:110,OP_3DUP:111,OP_2OVER:112,OP_2ROT:113,OP_2SWAP:114,OP_IFDUP:115,OP_DEPTH:116,OP_DROP:117,OP_DUP:118,OP_NIP:119,OP_OVER:120,OP_PICK:121,OP_ROLL:122,OP_ROT:123,OP_SWAP:124,OP_TUCK:125,OP_CAT:126,OP_SUBSTR:127,OP_LEFT:128,OP_RIGHT:129,OP_SIZE:130,OP_INVERT:131,OP_AND:132,OP_OR:133,OP_XOR:134,OP_EQUAL:135,OP_EQUALVERIFY:136,OP_RESERVED1:137,OP_RESERVED2:138,OP_1ADD:139,OP_1SUB:140,OP_2MUL:141,OP_2DIV:142,OP_NEGATE:143,OP_ABS:144,OP_NOT:145,OP_0NOTEQUAL:146,OP_ADD:147,OP_SUB:148,OP_MUL:149,OP_DIV:150,OP_MOD:151,OP_LSHIFT:152,OP_RSHIFT:153,OP_BOOLAND:154,OP_BOOLOR:155,OP_NUMEQUAL:156,OP_NUMEQUALVERIFY:157,OP_NUMNOTEQUAL:158,OP_LESSTHAN:159,OP_GREATERTHAN:160,OP_LESSTHANOREQUAL:161,OP_GREATERTHANOREQUAL:162,OP_MIN:163,OP_MAX:164,OP_WITHIN:165,OP_RIPEMD160:166,OP_SHA1:167,OP_SHA256:168,OP_HASH160:169,OP_HASH256:170,OP_CODESEPARATOR:171,OP_CHECKSIG:172,OP_CHECKSIGVERIFY:173,OP_CHECKMULTISIG:174,OP_CHECKMULTISIGVERIFY:175,OP_NOP1:176,OP_NOP2:177,OP_CHECKLOCKTIMEVERIFY:177,OP_NOP3:178,OP_CHECKSEQUENCEVERIFY:178,OP_NOP4:179,OP_NOP5:180,OP_NOP6:181,OP_NOP7:182,OP_NOP8:183,OP_NOP9:184,OP_NOP10:185,OP_CHECKSIGADD:186,OP_PUBKEYHASH:253,OP_PUBKEY:254,OP_INVALIDOPCODE:255};ft.OPS=Fn;const go={};ft.REVERSE_OPS=go;for(const e of Object.keys(Fn)){const t=Fn[e];go[t]=e}var ut={};Object.defineProperty(ut,"__esModule",{value:!0});ut.decode=ut.encode=ut.encodingLength=void 0;const ct=ft;function Eo(e){return e<ct.OPS.OP_PUSHDATA1?1:e<=255?2:e<=65535?3:5}ut.encodingLength=Eo;function Sc(e,t,r){const n=Eo(t);return n===1?e.writeUInt8(t,r):n===2?(e.writeUInt8(ct.OPS.OP_PUSHDATA1,r),e.writeUInt8(t,r+1)):n===3?(e.writeUInt8(ct.OPS.OP_PUSHDATA2,r),e.writeUInt16LE(t,r+1)):(e.writeUInt8(ct.OPS.OP_PUSHDATA4,r),e.writeUInt32LE(t,r+1)),n}ut.encode=Sc;function bc(e,t){const r=e.readUInt8(t);let n,i;if(r<ct.OPS.OP_PUSHDATA1)n=r,i=1;else if(r===ct.OPS.OP_PUSHDATA1){if(t+2>e.length)return null;n=e.readUInt8(t+1),i=2}else if(r===ct.OPS.OP_PUSHDATA2){if(t+3>e.length)return null;n=e.readUInt16LE(t+1),i=3}else{if(t+5>e.length)return null;if(r!==ct.OPS.OP_PUSHDATA4)throw new Error("Unexpected opcode");n=e.readUInt32LE(t+1),i=5}return{opcode:r,number:n,size:i}}ut.decode=bc;var zt={};Object.defineProperty(zt,"__esModule",{value:!0});zt.encode=zt.decode=void 0;function _c(e,t,r){t=t||4,r=r===void 0?!0:r;const n=e.length;if(n===0)return 0;if(n>t)throw new TypeError("Script number overflow");if(r&&!(e[n-1]&127)&&(n<=1|| Line truncated
*********************
PROCEED WITH CAUTION!
*********************`),a=c.hashForSignature(e,w,s)}return{script:w,sighashType:s,hash:a}}function yh(e,t,r,n){const i=[];if(t.tapInternalKey){const s=_s(e,t,n);s&&i.push(s)}if(t.tapScriptSig){const s=t.tapScriptSig.map(a=>a.pubkey);i.push(...s)}return i.map(s=>Wn(e,t,r,s,n)).flat()}function _s(e,t,r){const{script:n}=wi(e,t,r);return(0,re.isP2TR)(n)?n.subarray(2,34):null}function fo(e){return e.length===64?e:e.subarray(0,64)}function Wn(e,t,r,n,i,c,s){const a=i.__TX,p=t.sighashType||we.Transaction.SIGHASH_DEFAULT;Ts(p,s);const w=r.map((_,T)=>wi(T,_,i)),f=w.map(_=>_.script),d=w.map(_=>_.value),S=[];if(t.tapInternalKey&&!c){const _=_s(e,t,i)||E.from([]);if((0,J.toXOnly)(n).equals(_)){const T=a.hashForWitnessV1(e,f,d,p);S.push({pubkey:n,hash:T})}}const y=(t.tapLeafScript||[]).filter(_=>(0,re.pubkeyInScript)(n,_.script)).map(_=>{const T=(0,oh.tapleafHash)({output:_.script,version:_.leafVersion});return Object.assign({hash:T},_)}).filter(_=>!c||c.equals(_.hash)).map(_=>{const T=a.hashForWitnessV1(e,f,d,p,_.hash);return{pubkey:n,hash:T,leafHash:_.hash}});return S.concat(y)}function Ts(e,t){if(t&&t.indexOf(e)<0){const r=mh(e);throw new Error(`Sighash type is not allowed. Retry the sign method passing the sighashTypes array of whitelisted types. Sighash type: ${r}`)}}function wh(e,t,r){let n;switch(t){case"multisig":const i=Eh(e,r);n=ve.p2ms({output:e,signatures:i});break;case"pubkey":n=ve.p2pk({output:e,signature:r[0].signature});break;case"pubkeyhash":n=ve.p2pkh({output:e,pubkey:r[0].pubkey,signature:r[0].signature});break;case"witnesspubkeyhash":n=ve.p2wpkh({output:e,pubkey:r[0].pubkey,signature:r[0].signature});break}return n}function gh(e,t,r){const n=r.__TX,i={script:null,isSegwit:!1,isP2SH:!1,isP2WSH:!1};if(i.isP2SH=!!t.redeemScript,i.isP2WSH=!!t.witnessScript,t.witnessScript)i.script=t.witnessScript;else if(t.redeemScript)i.script=t.redeemScript;else if(t.nonWitnessUtxo){const c=fn(r,t,e),s=n.ins[e].index;i.script=c.outs[s].script}else t.witnessUtxo&&(i.script=t.witnessUtxo.script);return(t.witnessScript||(0,re.isP2WPKH)(i.script))&&(i.isSegwit=!0),i}function ho(e,t,r){const n=(0,Se.checkForInput)(t,e);if(!n.bip32Derivation||n.bip32Derivation.length===0)throw new Error("Need bip32Derivation to sign with HD");const i=n.bip32Derivation.map(s=>{if(s.masterFingerprint.equals(r.fingerprint))return s}).filter(s=>!!s);if(i.length===0)throw new Error("Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint");return i.map(s=>{const a=r.derivePath(s.path);if(!s.pubkey.equals(a.publicKey))throw new Error("pubkey did not match bip32Derivation");return a})}function Eh(e,t){return ve.p2ms({output:e}).pubkeys.map(n=>(t.filter(i=>i.pubkey.equals(n))[0]||{}).signature).filter(n=>!!n)}function Is(e){let t=0;function r(s){return t+=s,e.slice(t-s,t)}function n(){const s=ro.decode(e,t);return t+=ro.decode.bytes,s}function i(){return r(n())}function c(){const s=n(),a=[];for(let p=0;p<s;p++)a.push(i());return a}return c()}function mh(e){let t=e&we.Transaction.SIGHASH_ANYONECANPAY?"SIGHASH_ANYONECANPAY | ":"";switch(e&31){case we.Transaction.SIGHASH_ALL:t+="SIGHASH_ALL";break;case we.Transaction.SIGHASH_SINGLE:t+="SIGHASH_SINGLE";break;case we.Transaction.SIGHASH_NONE:t+="SIGHASH_NONE";break}return t}function jn(e,t,r){e.__NON_WITNESS_UTXO_BUF_CACHE[r]=t.nonWitnessUtxo;const n=we.Transaction.fromBuffer(t.nonWitnessUtxo);e.__NON_WITNESS_UTXO_TX_CACHE[r]=n;const i=e,c=r;delete t.nonWitnessUtxo,Object.defineProperty(t,"nonWitnessUtxo",{enumerable:!0,get(){const s=i.__NON_WITNESS_UTXO_BUF_CACHE[c],a=i.__NON_WITNESS_UTXO_TX_CACHE[c];if(s!==void 0)return s;{const p=a.toBuffer();return i.__NON_WITNESS_UTXO_BUF_CACHE[c]=p,p}},set(s){i.__NON_WITNESS_UTXO_BUF_CACHE[c]=s}})}function ks(e,t,r,n){let i=0;e.forEach((p,w)=>{if(n&&p.finalScriptSig&&(t.ins[w].script=p.finalScriptSig),n&&p.finalScriptWitness&&(t.ins[w].witness=Is(p.finalScriptWitness)),p.witnessUtxo)i+=p.witnessUtxo.value;else if(p.nonWitnessUtxo){const f=fn(r,p,w),d=t.ins[w].index,S=f.outs[d];i+=S.value}});const c=t.outs.reduce((p,w)=>p+w.value,0),s=i-c;if(s<0)throw new Error("Outputs are spending more than Inputs");const a=t.virtualSize();r.__FEE=s,r.__EXTRACTED_TX=t,r.__FEE_RATE=Math.floor(s/a)}function fn(e,t,r){const n=e.__NON_WITNESS_UTXO_TX_CACHE;return n[r]||jn(e,t,r),n[r]}function As(e,t,r){const{script:n}=wi(e,t,r);return n}function wi(e,t,r){if(t.witnessUtxo!==void 0)return{script:t.witnessUtxo.script,value:t.witnessUtxo.value};if(t.nonWitnessUtxo!==void 0){const i=fn(r,t,e).outs[r.__TX.ins[e].index];return{script:i.script,value:i.value}}else throw new Error("Can't find pubkey in input without Utxo data")}function Sh(e,t,r,n){const i=As(r,t,n),{meaningfulScript:c}=hn(i,r,"input",t.redeemScript,t.witnessScript);return(0,re.pubkeyInScript)(e,c)}function bh(e,t,r,n){const i=n.__TX.outs[r].script,{meaningfulScript:c}=hn(i,r,"output",t.redeemScript,t.witnessScript);return(0,re.pubkeyInScript)(e,c)}function _h(e){if(!e)return;const t=Ye.decompile(e);if(!t Line truncated
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="m10.97 4.97-.02.022-3.473 4.425-2.093-2.094a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05"/>
</svg>`,error:`
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/>
</svg>`,info:`
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
</svg>`,progress:`
<svg class="pushtx-spin" width="32" height="32" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle style="opacity: 0.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>`};async function dr(e){try{return[null,await e]}catch(t){return t instanceof Error?[t,null]:typeof t=="string"?[new Error(t),null]:[new Error,null]}}function po(e){const t=e.replace(/-/g,"+").replace(/_/g,"/").padEnd(e.length+(4-e.length%4)%4,"="),r=atob(t);return new Uint8Array([...r].map(n=>n.charCodeAt(0)))}class Bs extends Error{constructor(r,n,i,c){super();Zt(this,"url");Zt(this,"status");Zt(this,"statusText");Zt(this,"body");this.url=r,this.name="FetchStatusError",this.status=n,this.statusText=i,this.body=c}}const xs=async(e,t)=>{const r=await fetch(e,t);if(!r.ok){const[n,i]=await dr(r.text());throw new Bs(e.toString(),r.status,r.statusText,i)}return r};async function Ph(e,t){if(t!=="BTC"&&t!=="XTN")throw new Error("Unsupported network: "+t);const n=vs[t].map(s=>xs(s,{method:"POST",body:e.toHex()})),[i,c]=await dr(Promise.any(n).then(s=>s.text()));if(c)return c;if(i instanceof AggregateError){const s=i.errors.filter(a=>a instanceof Bs);if(s.length>0){let a=`
<p>The transaction was rejected by all providers:</p>
<ul>
`;throw s.forEach(p=>{a+=`<li>${p.url}: ${p.body}</li>`}),a+="</ul>",new Error(a)}}throw new Error("<p>Could not connect to any push servers. Make sure you are connected to the Internet and try again.</p>")}async function vh(e){e[0]==="#"&&(e=e.slice(1));const t=new URLSearchParams(e),r=t.get("t"),n=t.get("c"),i=t.get("n")||"BTC";if(!r)throw new Error("Invalid URL - missing transaction.");if(!n||n.length!==11)throw new Error("Invalid URL - missing or incomplete checksum. The URL is probably truncated");let c;if(i==="BTC"||i==="XTN")c=i;else throw i==="XRT"?new Error("Regtest transactions are not supported."):new Error(`Invalid URL. The network "${i}" is not recognized.`);let s,a;try{s=po(r),a=po(n)}catch{throw new Error("Invalid URL encoding. The URL is probably corrupted.")}const p=new Uint8Array(await crypto.subtle.digest("SHA-256",s));if(!a.every((f,d)=>f===p[d+24]))throw new Error("Checksum mismatch in URL. Some bytes corrupted in transit. Try again.");return{tx:lo.Transaction.fromBuffer(wo.from(s)),network:c}}async function Bh(e,t){if(t!=="BTC"&&t!=="XTN")throw new Error("Unsupported network: "+t);const n=vs[t].map(i=>xs(i+"/"+e).then(c=>c.json()));return await Promise.any(n)}function xh(e){const t=e.vin.map(n=>`
<tr>
<td>
<span class="address">
<span>${n.prevout.scriptpubkey_address.slice(0,-8)}</span>
<span>${n.prevout.scriptpubkey_address.slice(-8)}</span>
</span>
</td>
<td>
<span class="value">${(n.prevout.value/1e8).toFixed(8)}</span>
</td>
</tr>
`).join(""),r=e.vout.map(n=>`
<tr>
<td>
<span class="address">
<span>${n.scriptpubkey_address.slice(0,-8)}</span>
<span>${n.scriptpubkey_address.slice(-8)}</span>
</span>
</td>
<td>
<span class="value">${(n.value/1e8).toFixed(8)}</span>
</td>
</tr>
`).join("");return`
<div class="pushtx-details">
<div class="inputs">
<table>
<thead>
<tr>
<th>Inputs</th>
<th></th>
</tr>
</thead>
<tbody>
${t}
</tbody>
</table>
</div>
<div class="outputs">
<table>
<thead>
<tr>
<th>Outputs</th>
<th></th>
</tr>
</thead>
<tbody>
${r}
</tbody>
</table>
</div>
<div class="fee">
<strong>Fee:</strong> <span class="value">${(e.fee/1e8).toFixed(8)}</span>
</div>
</div>
`}function Kt(e,t){const r=Oh[e];return`
<div class="pushtx-message pushtx-message--${e}" role="alert">
${r}
<div>${t}</div>
</div>
`}async function Us(){const e=document.querySelector(".pushtx-message-area"),t=document.querySelector(".pushtx-details-area");if(!e||!t)throw new Error("Need message and details areas in HTML.");if(e.innerHTML="",t.innerHTML="",!window.location.hash){const d=`
<p><strong>Did you get here by accident?</strong></p>
<p>
This page is meant to be loaded together with transaction data using the
<strong>COLDCARD NFC Push TX feature</strong>. The complete URL should look something like this (but longer):
</p>
<p><code>https://coldcard.com/pushtx#t=AgAAAAMNCxXtp2GVYVhkRXHLMmdZFs4p3kbFK ⋯ ABf&c=uiSVRda-1tw</code></p>
`;e.innerHTML=Kt("info",d);return}e.innerHTML=Kt("progress","Sending transaction, please wait...");const[r,n]=await dr(vh(window.location.hash));if(r){e.innerHTML=Kt("error",r.message);return}const{tx:i,network:c}=n,s=i.getId(),[a,p]=await dr(Ph(i,c)),[w,f]=await dr(Bh(s,c));if(p||f){const d=f!=null&&f.status.confirmed?"This transaction has already been confirmed.":"The transaction has been sent and is waiting to be confirmed.",S=Ah[c].map(([y,_])=>`<li><a href="${_}${s}" target="_blank" rel="noopener">${y}</a></li>`).join("");e.innerHTML=Kt("success",`<p>${d} Transaction ID:</p>
<p class="txid">${s}</p>
<p>Verify on a block explorer:</p>
<ul>${S}</ul>
`),f&&(t.innerHTML=xh(f));return}if(a&&w)e.innerHTML=Kt("error",a.message);else if(w){e.innerHTML=Kt("error",w.message);return}}Us();window.addEventListener("hashchange",Us);
+27
View File
@@ -0,0 +1,27 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>COLDCARD NFC Transaction Broadcast</title>
</head>
<body>
<main class="pushtx" style="
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
max-width: 1024px;
margin: 5vh auto;
">
<h1 style="margin-bottom: 2rem;">COLDCARD NFC Transaction Broadcast</h1>
<div>
<div class="pushtx-message-area"></div>
<div class="pushtx-details-area"></div>
</div>
<script type="module" src="/src/main.ts"></script>
</main>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
# COLDCARD NFC Push TX
🔜
+2465
View File
File diff suppressed because it is too large. Load diff
+19
View File
@@ -0,0 +1,19 @@
{
"name": "coldcard-pushtx",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"@types/node": "20.14.2",
"bitcoinjs-lib": "6.1.6",
"typescript": "5.4.5",
"vite": "5.2.13",
"vite-plugin-node-polyfills": "0.22.0",
"vite-plugin-singlefile": "2.0.1"
}
}
+53
View File
@@ -0,0 +1,53 @@
export const TX_API_URLS = {
// POST to these when pushing a TX (body is transaction HEX)
// GET .../{txid} to get TX details
BTC: ['https://mempool.space/api/tx', 'https://blockstream.info/api/tx'],
XTN: ['https://mempool.space/testnet/api/tx', 'https://blockstream.info/testnet/api/tx'],
} as const;
export const BLOCK_EXPLORER_CONFIG = {
// Name and URL prefix of block explorers that we link to after TX is pushed
// TXID will be appended to the URL
BTC: [
['mempool.space', 'https://mempool.space/tx/'],
['blockstream.info', 'https://blockstream.info/tx/'],
['btcscan.org', 'https://btcscan.org/tx/'],
['btc.com', 'https://explorer.btc.com/btc/transaction/'],
],
XTN: [
['mempool.space', 'https://mempool.space/testnet/tx/'],
['blockstream.info', 'https://blockstream.info/testnet/tx/'],
['blockcypher.com', 'https://live.blockcypher.com/btc-testnet/tx/'],
],
} as const;
export const MESSAGE_ICONS = {
// SVGs for icons/spinner on the message box
// - taken from https://icons.getbootstrap.com
// - but inlining because easier to bundle into a single file this way
success: `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="m10.97 4.97-.02.022-3.473 4.425-2.093-2.094a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05"/>
</svg>`,
error: `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/>
</svg>`,
info: `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
</svg>`,
// note: spinning animation is defined in CSS file
progress: `
<svg class="pushtx-spin" width="32" height="32" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle style="opacity: 0.25;" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>`,
} as const;
+398
View File
@@ -0,0 +1,398 @@
import { Transaction } from 'bitcoinjs-lib';
import { Buffer } from 'buffer';
import { BLOCK_EXPLORER_CONFIG, MESSAGE_ICONS, TX_API_URLS } from './config';
import './style.css';
import { MempoolTxData, Network } from './types';
/**
* Helper for awaiting either the return value or error of a promise.
* Very useful for avoiding endless try/catch nesting and scoping hell.
*/
async function collect<T>(promise: Promise<T>): Promise<[null, T] | [Error, null]> {
try {
return [null, await promise];
} catch (err) {
if (err instanceof Error) {
return [err, null];
} else if (typeof err === 'string') {
return [new Error(err), null];
}
return [new Error(), null];
}
}
/**
* Convert a base64url string to a Uint8Array
*/
function b64UrlToBytes(base64Url: string): Uint8Array {
const base64 = base64Url
.replace(/-/g, '+')
.replace(/_/g, '/')
.padEnd(base64Url.length + ((4 - (base64Url.length % 4)) % 4), '=');
const binaryString = atob(base64);
return new Uint8Array([...binaryString].map((char) => char.charCodeAt(0)));
}
/**
* Custom error for fetchSafe below.
*/
class FetchStatusError extends Error {
url: string;
status: number;
statusText: string;
body: string | null;
constructor(url: string, status: number, statusText: string, body: string | null) {
super();
this.url = url;
this.name = 'FetchStatusError';
this.status = status;
this.statusText = statusText;
this.body = body;
}
}
/**
* Wrapper around fetch that throws an error if the response is not 2xx.
*/
const fetchSafe: typeof fetch = async (input, init) => {
const resp = await fetch(input, init);
if (!resp.ok) {
const [_, body] = await collect(resp.text());
throw new FetchStatusError(input.toString(), resp.status, resp.statusText, body);
}
return resp;
};
/**
* Send a transaction to some providers:
* - Currently mempool.space and blockstream.info, perhaps more in the future
* - Supporting mainnet and testnet
*/
async function pushTx(tx: Transaction, network: Network): Promise<string> {
if (network !== 'BTC' && network !== 'XTN') {
throw new Error('Unsupported network: ' + network);
}
const urls = TX_API_URLS[network];
const promises = urls.map((url) => fetchSafe(url, { method: 'POST', body: tx.toHex() }));
const [err, txid] = await collect(Promise.any(promises).then((res) => res.text()));
if (txid) {
return txid;
}
if (err instanceof AggregateError) {
const fetchStatusErrors = err.errors.filter(
(e): e is FetchStatusError => e instanceof FetchStatusError
);
if (fetchStatusErrors.length > 0) {
let errMsg = `
<p>The transaction was rejected by all providers:</p>
<ul>
`;
fetchStatusErrors.forEach((e) => {
errMsg += `<li>${e.url}: ${e.body}</li>`;
});
errMsg += `</ul>`;
throw new Error(errMsg);
}
}
throw new Error(
`<p>Could not connect to any push servers. Make sure you are connected to the Internet and try again.</p>`
);
}
/**
* Parse the transaction data and network from the URL fragment
* @param fragment - The URL fragment, e.g. #t=base64tx&c=base64checksum&n=XTN
*/
async function parseFragment(fragment: string) {
if (fragment[0] === '#') {
fragment = fragment.slice(1);
}
const params = new URLSearchParams(fragment);
const t = params.get('t');
const c = params.get('c');
const n = params.get('n') || 'BTC';
if (!t) {
throw new Error('Invalid URL - missing transaction.');
}
if (!c || c.length !== 11) {
throw new Error('Invalid URL - missing or incomplete checksum. The URL is probably truncated');
}
let network: Network;
if (n === 'BTC' || n === 'XTN') {
network = n;
} else if (n === 'XRT') {
throw new Error('Regtest transactions are not supported.');
} else {
throw new Error(`Invalid URL. The network "${n}" is not recognized.`);
}
let txBytes: Uint8Array;
let checkBytes: Uint8Array;
try {
txBytes = b64UrlToBytes(t);
checkBytes = b64UrlToBytes(c);
} catch (err) {
throw new Error('Invalid URL encoding. The URL is probably corrupted.');
}
const txHash = new Uint8Array(await crypto.subtle.digest('SHA-256', txBytes));
if (!checkBytes.every((byte, i) => byte === txHash[i + 24])) {
throw new Error('Checksum mismatch in URL. Some bytes corrupted in transit. Try again.');
}
const tx = Transaction.fromBuffer(Buffer.from(txBytes));
return {
tx,
network,
};
}
/**
* Fetch the transaction details from mempool/blockstream to render useful information
* that we can't (easily) get from the transaction itself, e.g. the input and output addresses
* - Cancel other requests once the first one comes back OK.
*/
async function fetchTxData(txid: string, network: Network): Promise<MempoolTxData> {
if (network !== 'BTC' && network !== 'XTN') {
throw new Error('Unsupported network: ' + network);
}
const urls = TX_API_URLS[network];
const promises = urls.map((url) => fetchSafe(url + '/' + txid).then((resp) => resp.json()));
return await Promise.any(promises);
}
/**
* Get the HTML for the transaction details that can be rendered on the page.
*/
function renderTxDetails(txData: MempoolTxData): string {
const inputRows = txData.vin
.map((input) => {
return `
<tr>
<td>
<span class="address">
<span>${input.prevout.scriptpubkey_address.slice(0, -8)}</span>
<span>${input.prevout.scriptpubkey_address.slice(-8)}</span>
</span>
</td>
<td>
<span class="value">${(input.prevout.value / 1e8).toFixed(8)}</span>
</td>
</tr>
`;
})
.join('');
const outputRows = txData.vout
.map((output) => {
return `
<tr>
<td>
<span class="address">
<span>${output.scriptpubkey_address.slice(0, -8)}</span>
<span>${output.scriptpubkey_address.slice(-8)}</span>
</span>
</td>
<td>
<span class="value">${(output.value / 1e8).toFixed(8)}</span>
</td>
</tr>
`;
})
.join('');
return `
<div class="pushtx-details">
<div class="inputs">
<table>
<thead>
<tr>
<th>Inputs</th>
<th></th>
</tr>
</thead>
<tbody>
${inputRows}
</tbody>
</table>
</div>
<div class="outputs">
<table>
<thead>
<tr>
<th>Outputs</th>
<th></th>
</tr>
</thead>
<tbody>
${outputRows}
</tbody>
</table>
</div>
<div class="fee">
<strong>Fee:</strong> <span class="value">${(txData.fee / 1e8).toFixed(8)}</span>
</div>
</div>
`;
}
/**
* Get the HTML for a message box that can be rendered on the page.
* @param type - The type of message, e.g. 'success', 'error', 'info'
* @param message - The message to display - can be HTML
*/
function renderMessage(type: 'success' | 'error' | 'info' | 'progress', message: string): string {
const icon = MESSAGE_ICONS[type];
return `
<div class="pushtx-message pushtx-message--${type}" role="alert">
${icon}
<div>${message}</div>
</div>
`;
}
/**
* For testing - convert an exisiting HEX transaction to a URL fragment
*/
async function txToUrlFragment(hex: string, network: Network) {
const txBytes = new Uint8Array(hex.match(/.{2}/g)!.map((byte) => parseInt(byte, 16)));
const txHash = new Uint8Array(await crypto.subtle.digest('SHA-256', txBytes));
const tx = btoa(String.fromCharCode(...txBytes))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
const check = btoa(String.fromCharCode(...txHash.slice(24)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
let fragment = `#t=${tx}&c=${check}`;
if (network) {
fragment += `&n=${network}`;
}
}
async function run() {
const messageArea = document.querySelector<HTMLDivElement>('.pushtx-message-area');
const detailsArea = document.querySelector<HTMLDivElement>('.pushtx-details-area');
if (!messageArea || !detailsArea) {
throw new Error('Need message and details areas in HTML.');
}
messageArea.innerHTML = '';
detailsArea.innerHTML = '';
if (!window.location.hash) {
const msg = `
<p><strong>Did you get here by accident?</strong></p>
<p>
This page is meant to be loaded together with transaction data using the
<strong>COLDCARD NFC Push TX feature</strong>. The complete URL should look something like this (but longer):
</p>
<p><code>https://coldcard.com/pushtx#t=AgAAAAMNCxXtp2GVYVhkRXHLMmdZFs4p3kbFK ⋯ ABf&c=uiSVRda-1tw</code></p>
`;
messageArea.innerHTML = renderMessage('info', msg);
return;
}
messageArea.innerHTML = renderMessage('progress', 'Sending transaction, please wait...');
const [parseErr, parseResult] = await collect(parseFragment(window.location.hash));
if (parseErr) {
messageArea.innerHTML = renderMessage('error', parseErr.message);
return;
}
const { tx, network } = parseResult;
const txid = tx.getId();
const [pushErr, pushResult] = await collect(pushTx(tx, network));
// XXX - sometimes we get something like `{"code":-25,"message":"bad-txns-inputs-missingorspent"}`
// but the transaction is actually confirmed, so:
// - try to fetch the TX details by ID, even if pushing failed
// - if it's found, show a green message and say it's pending or confirmed
const [mempoolErr, mempoolTxData] = await collect(fetchTxData(txid, network));
if (pushResult || mempoolTxData) {
// push was successful and/or we got the TX details back
const msg = mempoolTxData?.status.confirmed
? 'This transaction has already been confirmed.'
: 'The transaction has been sent and is waiting to be confirmed.';
const listHtml = BLOCK_EXPLORER_CONFIG[network]
.map(
([name, url]) =>
`<li><a href="${url}${txid}" target="_blank" rel="noopener">${name}</a></li>`
)
.join('');
messageArea.innerHTML = renderMessage(
'success',
`<p>${msg} Transaction ID:</p>
<p class="txid">${txid}</p>
<p>Verify on a block explorer:</p>
<ul>${listHtml}</ul>
`
);
if (mempoolTxData) {
detailsArea.innerHTML = renderTxDetails(mempoolTxData);
}
return;
}
if (pushErr && mempoolErr) {
// pushing failed and we also couldn't fetch the TX details
messageArea.innerHTML = renderMessage('error', pushErr.message);
} else if (mempoolErr) {
messageArea.innerHTML = renderMessage('error', mempoolErr.message);
return;
}
}
run();
window.addEventListener('hashchange', run);
+130
View File
@@ -0,0 +1,130 @@
.pushtx * {
margin: 0;
padding: 0;
box-sizing: border-box;
color: inherit !important;
}
.pushtx ul {
list-style-position: inside;
}
.pushtx .monospace {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.pushtx-spin {
animation: spin 1s linear infinite;
}
.pushtx-message {
display: flex;
gap: 1rem;
padding: 1rem;
border: 1px solid transparent;
border-radius: 0.25rem;
width: 100%;
}
.pushtx-message > :first-child {
flex-shrink: 0;
}
.pushtx-message > :last-child {
overflow: hidden;
overflow-wrap: break-word;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.pushtx-message--success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb;
}
.pushtx-message--info {
color: #0c5460;
background-color: #d1ecf1;
border-color: #bee5eb;
}
.pushtx-message--progress {
color: #004085;
background-color: #cce5ff;
border-color: #b8daff;
}
.pushtx-message--error {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
}
.pushtx-message .txid {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 0.875em;
font-weight: 700;
}
.pushtx-details {
display: flex;
flex-direction: column;
margin-block: 2rem;
padding: 0.5rem;
border-radius: 0.25rem;
overflow: hidden;
gap: 0.5rem;
border: 1px solid #e2e8f0;
}
.pushtx-details table {
border-collapse: collapse;
width: 100%;
}
.pushtx-details th {
text-align: left;
}
.pushtx-details :is(th, td) {
border-width: 0px;
padding-block: 0.125rem;
}
.pushtx-details :is(td:first-child, th:first-child) {
width: 75%;
max-width: 0px;
}
.pushtx-details :is(td:last-child, th:last-child) {
text-align: right;
}
.pushtx-details :is(.address, .value) {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
}
.pushtx-details .address {
display: flex;
padding-right: 1em;
}
.pushtx-details .address :first-child {
overflow: hidden;
text-overflow: ellipsis;
}
.pushtx-details .fee {
text-align: right;
}
+25
View File
@@ -0,0 +1,25 @@
export type Network = 'BTC' | 'XTN' | 'XRT';
interface MempoolConfirmedStatus {
confirmed: true;
block_height: number;
}
interface MempoolUnconfirmedStatus {
confirmed: false;
}
export interface MempoolTxData {
vin: {
prevout: {
scriptpubkey_address: string;
value: number;
};
}[];
vout: {
scriptpubkey_address: string;
value: number;
}[];
fee: number;
status: MempoolConfirmedStatus | MempoolUnconfirmedStatus;
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+23
View File
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2021",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2021", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
+58
View File
@@ -0,0 +1,58 @@
import { defineConfig, type UserConfig } from 'vite';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import { viteSingleFile } from 'vite-plugin-singlefile';
const plugins = [
nodePolyfills({
// needed for bitcoinjs-lib
include: ['buffer'],
globals: {
Buffer: true,
},
}),
];
// "normal" configuration - building for coldcard.com/pushtx
// - creates a one JS and one CSS file
const websiteConfig: UserConfig = {
plugins,
build: {
outDir: 'build',
rollupOptions: {
output: {
entryFileNames: (chunkInfo) => {
if (chunkInfo.name !== 'index') {
throw new Error(
`Build failed. Expected a single JS chunk "index". Saw: "${chunkInfo.name}".`
);
}
return 'pushtx.js';
},
assetFileNames: (chunkInfo) => {
if (chunkInfo.name !== 'index.css') {
throw new Error(
`Build failed. Expected a single CSS chunk "index.css". Saw: "${chunkInfo.name}".`
);
}
return 'pushtx.css';
},
chunkFileNames: (chunkInfo) => {
throw new Error(`Build failed. Not expecting any chunk files. Saw: "${chunkInfo.name}".`);
},
},
},
},
};
// single file config - builds a single HTML file with everything inlined
// can be downloaded from coldcard.com/pushtx to self-host
const singleFileConfig: UserConfig = {
plugins: [...plugins, viteSingleFile()],
build: {
outDir: 'build-single-file',
},
};
const finalConfig = process.env.PUSHTX_SINGLE_FILE ? singleFileConfig : websiteConfig;
export default defineConfig(finalConfig);