Skip to content

Commit 066ce8d

Browse files
committed
check for and display simplicity transaction inputs
- simplicity program displayed in base64 (not hex)
1 parent a99ea80 commit 066ce8d

File tree

4 files changed

+116
-27
lines changed

4 files changed

+116
-27
lines changed

client/src/lib/elements.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {formatAssetAmount} from "../views/util";
2+
3+
export const getSupply = (asset, t) => {
4+
let { chain_stats = {}, mempool_stats = {} } = asset
5+
let has_blinded_issuances =
6+
chain_stats.has_blinded_issuances || mempool_stats.has_blinded_issuances
7+
let is_native_asset = !asset.issuance_txin
8+
let circulating = is_native_asset
9+
? chain_stats.peg_in_amount +
10+
mempool_stats.peg_in_amount -
11+
chain_stats.peg_out_amount -
12+
mempool_stats.peg_out_amount -
13+
chain_stats.burned_amount -
14+
mempool_stats.burned_amount
15+
: has_blinded_issuances
16+
? null
17+
: chain_stats.issued_amount +
18+
mempool_stats.issued_amount -
19+
chain_stats.burned_amount -
20+
mempool_stats.burned_amount;
21+
22+
let totalSupply = circulating == null ? t`Confidential`
23+
: formatAssetAmount(circulating, asset.precision, t)
24+
return totalSupply
25+
}
26+
27+
28+
// Simplicity helpers (Elements only)
29+
30+
// Helper to check if witness has an annex (per BIP 341)
31+
const hasAnnex = (witness) => witness && witness.length >= 2 && witness[witness.length - 1].startsWith('50')
32+
33+
// Get the control block element from a witness stack (accounting for optional annex)
34+
const getControlBlock = witness => {
35+
if (!witness || witness.length < 3) return null
36+
const hasAnnexBlock = hasAnnex(witness)
37+
if (hasAnnexBlock) {
38+
return witness[witness.length - 2]
39+
}
40+
return witness[witness.length - 1]
41+
}
42+
43+
export const isSimplicityTapleafVersion = (controlBlock) => {
44+
return controlBlock && (controlBlock.startsWith('be') || controlBlock.startsWith('bf'))
45+
}
46+
47+
// Check if a vin is a P2TR Simplicity spend
48+
// A P2TR Simplicity spend has:
49+
// - 4 witness elements (or 5 with optional annex)
50+
// - is a simplicity Tapleaf
51+
export const isSimplicitySpend = (vin) => {
52+
if (!process.env.IS_ELEMENTS || !vin.witness) return false
53+
54+
const witnessLen = vin.witness.length
55+
const hasAnnexBlock = hasAnnex(vin.witness)
56+
57+
// Must be 4 elements without annex, or 5 elements with annex
58+
if (witnessLen !== 4 && !(witnessLen === 5 && hasAnnexBlock)) return false
59+
60+
const controlBlock = getControlBlock(vin.witness)
61+
return isSimplicityTapleafVersion(controlBlock)
62+
}
63+
64+
// Extract Simplicity witness components from a vin
65+
// Returns: { witnessData, program, cmr, controlBlock, annex }
66+
export const getSimplicityWitness = (vin) => {
67+
if (!isSimplicitySpend(vin)) return null
68+
69+
const witness = vin.witness
70+
const hasAnnexBlock = hasAnnex(witness)
71+
72+
return {
73+
witnessData: witness[0],
74+
program: witness[1],
75+
cmr: witness[2],
76+
controlBlock: witness[3],
77+
annex: hasAnnexBlock && witness.length === 5 ? witness[4] : null
78+
}
79+
}

client/src/views/asset-list.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Snabbdom from 'snabbdom-pragma'
2-
import { getSupply } from './util'
2+
import { getSupply } from '../lib/elements.js'
33
import layout from './layout'
44
import loader from '../components/loading'
55

client/src/views/tx-vin.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Snabbdom from 'snabbdom-pragma'
2-
import { linkToParentOut, formatOutAmount, formatAssetAmount, formatHex, linkToAddr, formatNumber } from './util'
2+
import { linkToParentOut, formatOutAmount, formatAssetAmount, formatHex, linkToAddr, formatNumber, hexToBase64 } from './util'
3+
import { isSimplicitySpend, getSimplicityWitness } from '../lib/elements.js'
34

45
const layout = (vin, desc, body, { t, ...S }) =>
56
<div class={{ vin: true, active: isActive(vin, S), unblinded: isUnblinded(vin) }}>
@@ -103,11 +104,38 @@ const standard = (vin, { isOpen, t, ...S }, assetMeta=getAssetMeta(vin, S)) => l
103104
</div>
104105
] }
105106

106-
{ vin.witness && <div className="vin-body-row">
107+
{ vin.witness && !isSimplicitySpend(vin) && <div className="vin-body-row">
107108
<div>{t`Witness`}</div>
108109
<div className="mono">{vin.witness.map(wit => [ ' ', wit.length ? wit : <em>&lt;empty&gt;</em> ])}</div>
109110
</div> }
110111

112+
{ isSimplicitySpend(vin) && ((sw = getSimplicityWitness(vin)) => [
113+
<div className="vin-body-row">
114+
<div>{t`Simplicity witness data`}</div>
115+
<div className="mono">{sw.witnessData}</div>
116+
</div>
117+
118+
, <div className="vin-body-row">
119+
<div>{t`Simplicity program`} <br></br> {t`(base64)`}</div>
120+
<div className="mono">{hexToBase64(sw.program)}</div>
121+
</div>
122+
123+
, <div className="vin-body-row">
124+
<div>{t`Simplicity CMR`}</div>
125+
<div className="mono">{sw.cmr}</div>
126+
</div>
127+
128+
, <div className="vin-body-row">
129+
<div>{t`Taproot control block`}</div>
130+
<div className="mono">{sw.controlBlock}</div>
131+
</div>
132+
133+
, sw.annex && <div className="vin-body-row">
134+
<div>{t`Taproot Annex`}</div>
135+
<div className="mono">{sw.annex}</div>
136+
</div>
137+
])() }
138+
111139
{ vin.inner_redeemscript_asm && <div className="vin-body-row">
112140
<div>{t`P2SH redeem script`}</div>
113141
<div className="mono">{vin.inner_redeemscript_asm}</div>

client/src/views/util.js

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,10 @@ export const formatVMB = bytes =>
9797
: '< 0.01 vMB'
9898

9999

100-
export const getSupply = (asset, t) => {
101-
let { chain_stats = {}, mempool_stats = {} } = asset
102-
let has_blinded_issuances =
103-
chain_stats.has_blinded_issuances || mempool_stats.has_blinded_issuances
104-
let is_native_asset = !asset.issuance_txin
105-
let circulating = is_native_asset
106-
? chain_stats.peg_in_amount +
107-
mempool_stats.peg_in_amount -
108-
chain_stats.peg_out_amount -
109-
mempool_stats.peg_out_amount -
110-
chain_stats.burned_amount -
111-
mempool_stats.burned_amount
112-
: has_blinded_issuances
113-
? null
114-
: chain_stats.issued_amount +
115-
mempool_stats.issued_amount -
116-
chain_stats.burned_amount -
117-
mempool_stats.burned_amount;
118-
119-
let totalSupply = circulating == null ? t`Confidential`
120-
: formatAssetAmount(circulating, asset.precision, t)
121-
return totalSupply
122-
}
100+
export const strTruncate = (str) => str.substr(0, 10) + '...' + str.substr(str.length-4, str.length);
101+
123102

124-
export const strTruncate = (str) => str.substr(0, 10) + '...' + str.substr(str.length-4, str.length);
103+
// Convert hex string to base64
104+
export const hexToBase64 = (hex) => {
105+
return Buffer.from(hex, 'hex').toString('base64')
106+
}

0 commit comments

Comments
 (0)