Created
March 13, 2024 01:06
-
-
Save mikedotexe/33ba89eb54f9f5b75f08a6dcfbf4dac0 to your computer and use it in GitHub Desktop.
Chain signatures (shell of BOS frontend)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const ProgressBar = VM.require("mike.near/widget/ProgressBar"); | |
console.log('props', props); | |
const yoctoZeroes = "000000000000000000000000"; | |
const [stakingAmount, setStakingAmount] = useState("6"); | |
const [unstakingWithdrawlAmount, setUnstakingWithdrawlAmount] = useState("19"); | |
const StakeUnstakeWithdraw = ({ validator, method, amount }) => { | |
const openAnotherModal = ({validator, method, amount}) => { | |
// Let's explicitly have these in a switch so no one tries any funny business with methods | |
switch (method) { | |
case 'stake': | |
Near.call(validator, "deposit_and_stake", { amount: `${stakingAmount}${yoctoZeroes}` }, null, amount); | |
break; | |
case 'unstake': | |
Near.call(validator, "unstake", { amount: unstakingWithdrawlAmount + yoctoZeroes }); | |
break; | |
case 'withdraw': | |
Near.call(validator, "withdraw", { amount: unstakingWithdrawlAmount + yoctoZeroes }); | |
break; | |
} | |
}; | |
const handleCancel = () => { | |
// eventually add fade-out effect | |
setShowStakingModal(false); | |
}; | |
const handleUnstake = (amount) => { | |
console.log("Unstaking…", amount); | |
openAnotherModal({validator, method, amount}); | |
}; | |
const handleStake = (amount) => { | |
console.log("Staking…", amount); | |
openAnotherModal({validator, method, amount}); | |
}; | |
const handleWithdraw = () => { | |
console.log("Withdrawing…", unstakingWithdrawlAmount); | |
openAnotherModal({validator, method, amount: unstakingWithdrawlAmount}); | |
}; | |
const containerStyle = { | |
boxShadow: "0 3px 6px rgba(0, 0, 0, 0.6)", | |
border: "3px solid #f2f1e9", | |
backgroundColor: "rgba(0, 0, 0, .7)", | |
background: | |
"radial-gradient(circle at center, rgba(255, 255, 255, 0.7), rgba(0, 0, 0, 0.7) 100%), radial-gradient(circle at 10% 90%, rgba(200, 200, 255, 0.8), transparent 60%),radial-gradient(circle at 50% 50%, rgba(151, 151, 255, 0.7), transparent 50%)", | |
padding: "20px", | |
width: "100%", | |
margin: "0 auto", | |
borderRadius: "13px", | |
overflow: "auto", | |
display: "flex", | |
flexDirection: "column", | |
transition: "opacity 0.3s ease-out", | |
}; | |
const titleStyle = { | |
textAlign: "center", | |
color: "#f2f1e9", | |
fontWeight: "bold", | |
marginBottom: "6px", | |
textShadow: ` | |
-1px -1px 0 #000, | |
1px -1px 0 #000, | |
-1px 1px 0 #000, | |
1px 1px 0 #000`, | |
letterSpacing: "1px", | |
}; | |
return ( | |
<div style={containerStyle}> | |
<h2 style={titleStyle}>{method} NEAR</h2> | |
{method == "stake" ? ( | |
<input | |
value={stakingAmount} | |
onChange={(e) => setStakingAmount(e.target.value)} | |
style={{ | |
padding: "13px", | |
width: "80%", | |
margin: "13px auto", | |
display: "block", | |
}} | |
/> | |
) : ( | |
<input | |
value={unstakingWithdrawlAmount} | |
onChange={(e) => setUnstakingWithdrawlAmount(e.target.value)} | |
style={{ | |
padding: "13px", | |
width: "80%", | |
margin: "13px auto", | |
display: "block", | |
}} | |
/> | |
)} | |
<button | |
onClick={(e) => { | |
// console.log('aloha method', method); | |
if (method === "unstake") { | |
handleUnstake(`${stakingAmount}${yoctoZeroes}`); | |
} else if (method === "stake") { | |
// Assuming you have a function handleStake for the "stake" action | |
handleStake(`${stakingAmount}${yoctoZeroes}`); | |
} else if (method === "withdraw") { | |
// handleWithdraw(`${stakingAmount}${yoctoZeroes}`); | |
handleWithdraw(`${stakingAmount}${yoctoZeroes}`); | |
} | |
}} | |
style={{padding: "13px", margin: "13px auto", display: "block"}} | |
> | |
<span style={{ | |
textTransform: "capitalize", | |
}}>{method}</span> | |
</button> | |
<button | |
onClick={handleCancel} | |
style={{padding: "13px", margin: "13px auto", display: "block"}} | |
> | |
Cancel | |
</button> | |
</div> | |
); | |
}; | |
// Interesting, this seems to need to be above the StakeUnstakeWithdrawModal declaration | |
const walletUnstake = ({ validator, amount }) => { | |
setStakingModalData({ validator, method: "unstake", amount }); | |
setShowStakingModal(true); | |
}; | |
const walletWithdraw = ({ validator, amount }) => { | |
console.log('aloha walletWithdraw amount', amount); | |
setStakingModalData({ validator, method: "withdraw" }); | |
setShowStakingModal(true); | |
}; | |
const walletStake = ({ validator, amount }) => { | |
console.log('aloha stake validator', validator) | |
setStakingModalData({ validator, method: "stake", amount }); | |
setShowStakingModal(true); | |
}; | |
// State initialization | |
const [progressVal, setProgressVal] = useState(0); | |
const [progressMax, setProgressMax] = useState(1_000); | |
const [started, setStarted] = useState(false); | |
const [showProgressBar, setShowProgressBar] = useState(true); | |
const [validatorStakingDetails, setValidatorStakingDetails] = useState([]); | |
const [isLoading, setIsLoading] = useState(true); | |
const [showStakingModal, setShowStakingModal] = useState(false); | |
const [stakingModalData, setStakingModalData] = useState(false); | |
const YOCTO_DIGITS = 24; | |
// being modified from near-js utils | |
function cleanupAmount(amt) { | |
// Remove commas by splitting on them and joining the parts back together | |
return amt.split(",").join("").trim(); | |
} | |
function trimLeadingZeroes(str) { | |
// Find the first non-zero character | |
let firstNonZeroIndex = 0; | |
while (firstNonZeroIndex < str.length && str[firstNonZeroIndex] === "0") { | |
firstNonZeroIndex++; | |
} | |
// Return the substring from the first non-zero character or '0' if all characters were zeroes | |
return firstNonZeroIndex === str.length | |
? "0" | |
: str.substring(firstNonZeroIndex); | |
} | |
function parseNearAmount(amt) { | |
if (!amt) { | |
return null; | |
} | |
amt = cleanupAmount(amt); | |
const split = amt.split("."); | |
const wholePart = split[0]; | |
const fracPart = split[1] || ""; | |
// Check for valid format | |
if (split.length > 2 || fracPart.length > YOCTO_DIGITS) { | |
throw new Error(`Cannot parse '${amt}' as NEAR amount`); | |
} | |
// Combine the whole part and the fractional part padded to NEAR_NOMINATION_EXP length | |
return trimLeadingZeroes(wholePart + fracPart.padEnd(YOCTO_DIGITS, "0")); | |
} | |
// end modified from near-js utils | |
// this is a strange function that'll help with removing the modal nicely | |
// seems to be somewhat a BOS-related piquant feature | |
useEffect(async () => { | |
if (progressVal >= progressMax) { | |
const timer = setTimeout(() => setIsLoading(false), 300); | |
return () => clearTimeout(timer); | |
} | |
}, [progressVal, progressMax]); | |
const LoadingModal = () => { | |
// This helps with the flash of red if it hasn't loaded yet | |
if (!!!ProgressBar) { | |
return <></>; | |
} else { | |
if (!!!context.accountId) { | |
console.log("(BOS variable) context.accountId not ready"); | |
return; | |
} | |
return ( | |
<div | |
style={{ | |
position: "fixed", | |
top: "50%", | |
left: "50%", | |
transform: "translate(-50%, -150%)", | |
padding: "6px", | |
width: "19%", | |
height: "13%", | |
backgroundColor: "rgba(0, 0, 0, 0.2)", | |
backdropFilter: "blur(6px)", | |
display: isLoading ? "flex" : "none", | |
justifyContent: "center", | |
alignItems: "center", | |
borderRadius: "300px", | |
transition: "opacity 0.2s ease-out, background-color 0.2s ease", | |
opacity: isLoading ? 1 : 0, | |
zIndex: 999, | |
background: `linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.2) 100%)`, | |
boxShadow: "0 8px 32px 0 rgba(31, 38, 135, 0.37)", | |
}} | |
> | |
<ProgressBar key={progressVal} value={progressVal} max={progressMax} /> | |
</div> | |
); | |
} | |
}; | |
// debug by commenting this badboy out to see it | |
const stakingModalDisplayStyles = { | |
display: showStakingModal ? "flex" : "none", | |
opacity: showStakingModal ? 1 : 0, | |
}; | |
const StakeUnstakeWithdrawModal = () => { | |
if (!StakeUnstakeWithdraw) { | |
return <></>; | |
} else { | |
const handleOutsideClick = (e) => { | |
setShowStakingModal(false); | |
}; | |
const outerDivStyle = { | |
position: "fixed", | |
top: "6%", | |
left: 0, | |
width: "100%", | |
backgroundImage: "radial-gradient(ellipse, rgba(0, 0, 0, 0.3) 66%, transparent 80%)", | |
display: "flex", | |
justifyContent: "center", | |
zIndex: 998, | |
cursor: "pointer", | |
...stakingModalDisplayStyles, | |
}; | |
const innerDivStyle = { | |
cursor: "auto", | |
position: "relative", | |
width: "37%", | |
height: "auto", | |
overflowY: "hidden", | |
padding: "1%", | |
background: "radial-gradient(ellipse, rgba(151, 151, 255, .8) 19%, rgba(0, 0, 0, 0.8) 100%)", | |
borderRadius: "13px", | |
boxShadow: "0 0 15px rgba(255, 255, 255, 0.6), 0 0 20px rgba(255, 255, 255, 0.4), 0 0 25px rgba(255, 255, 255, 0.3), 0 0 30px rgba(255, 255, 255, 0.2), 0 0 35px rgba(255, 255, 255, 0.1), 0 0 40px rgba(255, 255, 255, 0.05)", | |
display: "flex", | |
flexDirection: "column", | |
justifyContent: "center", | |
}; | |
return ( | |
<div style={outerDivStyle} onClick={handleOutsideClick}> | |
<div style={innerDivStyle} onClick={(e) => e.stopPropagation()}> | |
<StakeUnstakeWithdraw | |
validator={stakingModalData.validator} | |
method={stakingModalData.method} | |
amount={stakingModalData.amount} | |
/> | |
</div> | |
</div> | |
); | |
} | |
}; | |
// begin styles | |
const formContainerStyle = { | |
background: "radial-gradient(circle at top left, rgba(0, 0, 0, 0.6) 30%, rgba(0, 0, 0, 0.2) 70%)", | |
filter: "drop-shadow(3px 3px 3px rgba(255, 255, 255, 0.6))", | |
backdropFilter: "contrast(0.1)", | |
padding: "20px", | |
margin: "20px auto", | |
color: "#fff", | |
borderRadius: "6px", | |
boxShadow: "0 0 1px 0 #f2f1e9", | |
}; | |
const leftSideStyle = { | |
display: "flex", | |
flexDirection: "column", | |
gap: "10px", | |
flex: "1 1 auto", | |
}; | |
const formStyle = { | |
display: "flex", | |
alignItems: "flex-end", | |
justifyContent: "space-between", | |
gap: "20px", | |
}; | |
const rightSideStyle = { | |
background: "#111", | |
borderRadius: "6px", | |
color: "#f2f1e9", | |
border: "none", | |
padding: "20px 20px 20px 20px", | |
display: "flex", | |
flexDirection: "row", | |
justifyContent: "space-between", | |
gap: "10px", | |
flex: "1 1 600px", | |
minHeight: "fit-content", | |
marginLeft: "20px", | |
}; | |
const rightBoxStyle = { | |
flex: "1", | |
display: "flex", | |
flexDirection: "column", | |
justifyContent: "center", | |
alignItems: "center", | |
padding: "20px", | |
borderRadius: "6px", | |
background: "rgba(255, 255, 255, 0.1)", | |
border: "1px solid rgba(255, 255, 255, 0.2)", | |
}; | |
const fieldStyle = { | |
background: "rgba(255, 255, 255, 0.1)", | |
border: "1px solid #f2f1e9", | |
borderRadius: "4px", | |
color: "#fff", | |
padding: "10px 15px", | |
}; | |
const buttonStyle = { | |
background: "linear-gradient(to right, #9797ff 6%, rgba(151, 151, 255, 0.6) 100%)", | |
color: "#fff", | |
border: "none", | |
borderRadius: "4px", | |
padding: "10px 20px", | |
cursor: "pointer", | |
}; | |
// end styles | |
return ( | |
<> | |
{!context.accountId && ( | |
<div style={{textAlign: "center", color: "white"}}> | |
<h1>Please login →</h1> | |
</div> | |
)} | |
<div style={{ | |
position: "relative", | |
width: "100%", | |
filter: "contrast(166%)", | |
}}> | |
<div | |
style={{ | |
fontFamily: "'Lucida Console', Monaco, monospace", | |
padding: "13px 16px", | |
minWidth: "475px", | |
maxWidth: "900px", | |
margin: "0 auto", | |
background: `radial-gradient(circle at top right, slategray, transparent 80%), | |
radial-gradient(circle at center, darkslategray, transparent 83%)`, | |
backgroundImage: 'url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiPjxkZWZzPjxmaWx0ZXIgaWQ9InNsYXRlTm9pc2UiIHg9Ii01MCUiIHk9Ii01MCUiIHdpZHRoPSIyMDAlIiBoZWlnaHQ9IjIwMCUiPjxmZVR1cmJ1bGVuY2UgdHlwZT0iZnJhY3RhbE5vaXNlIiBiYXNlRnJlcXVlbmN5PSIxLjkiIG51bU9jdGF2ZXM9IjYiIHJlc3VsdD0ibm9pc2UiLz48ZmVHYXVzc2lhbkJsdXIgaW49Im5vaXNlIiBzdGREZXZpYXRpb249IjIiIHJlc3VsdD0iYmx1cnJlZE5vaXNlIi8+PGZlQ29tcG9uZW50VHJhbnNmZXIgaW49InNsYXRlTm9pc2UiPjxmZUZ1bmNSIHR5cGU9ImxpbmVhciIgc2xvcGU9IjAuOSIgaW50ZXJjZXB0PSIwLjA1Ii8+PGZlRnVuY0cgdHlwZT0ibGluZWFyIiBzbG9wZT0iMC45IiBpbnRlcmNlcHQ9IjAuMDUiLz48ZmVGdW5jQiB0eXBlPSJsaW5lYXIiIHNsb3BlPSIwLjk1IiBpbnRlcmNlcHQ9IjAuMSIvPjwvZmVDb21wb25lbnRUcmFuc2Zlcj48L2ZpbHRlcj48L2RlZnM+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsdGVyPSJ1cmwoI3NsYXRlTm9pc2UpIiBmaWxsPSJkYXJrZ3JheSIgLz48L3N2Zz4=")', | |
backgroundBlendMode: "hard-light", | |
borderRadius: "16px 16px 0 0", | |
filter: "saturate(6) grayscale(6%)", | |
}} | |
> | |
{/* todo: check for modals and stuff */} | |
<h1 style={{ | |
textAlign: "center", | |
color: "#f2f1e9", | |
textShadow: `-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,3px 3px 3px #000`, | |
letterSpacing: "6px", | |
textTransform: "uppercase", | |
}}>Chain Signatures</h1> | |
<div style={formContainerStyle}> | |
<h3>Bitcoin</h3> | |
<div style={{ | |
height: "6px", | |
background: "linear-gradient(to right, goldenrod, rgba(255, 255, 255, 0.3), goldenrod)", | |
filter: "saturate(13%)", | |
marginBottom: "13px", | |
}}></div> | |
<div style={formStyle}> | |
<div style={leftSideStyle}> | |
<label style={{color: "#fff"}}>HD Path</label> | |
<input style={fieldStyle} type="text" value="m/44'/0'/0'/0"/> | |
<label style={{color: "#fff"}}>NEAR Account</label> | |
<input style={fieldStyle} type="text" value={context.accountId} readOnly/> | |
</div> | |
<div style={rightSideStyle}> | |
<div style={rightBoxStyle}> | |
<h4 style={{margin: 0, padding: 0, color: "#fff"}}>Balance</h4> | |
<button style={buttonStyle} onClick={() => refreshBalance('bitcoin')}>Refresh</button> | |
</div> | |
<div style={rightBoxStyle}> | |
{/* Right side content, the second box */} | |
<p style={{margin: 0, padding: 0, color: "#fff"}}>Right side content</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div style={formContainerStyle}> | |
<h3>Ethereum</h3> | |
<div style={{ | |
height: "6px", | |
background: "linear-gradient(to right, blue, rgba(255, 255, 255, 0.3), blue)", | |
filter: "saturate(13%)", | |
marginBottom: "13px", | |
}}></div> | |
<div style={formStyle}> | |
<div style={leftSideStyle}> | |
<label style={{color: "#fff"}}>HD Path</label> | |
<input style={fieldStyle} type="text" value="m/44'/0'/0'/0"/> | |
<label style={{color: "#fff"}}>NEAR Account</label> | |
<input style={fieldStyle} type="text" value={context.accountId} readOnly/> | |
</div> | |
<div style={rightSideStyle}> | |
<div style={rightBoxStyle}> | |
<h4 style={{margin: 0, padding: 0, color: "#fff"}}>Balance</h4> | |
<button style={buttonStyle} onClick={() => refreshBalance('eth')}>Refresh</button> | |
</div> | |
<div style={rightBoxStyle}> | |
<p style={{margin: 0, padding: 0, color: "#fff"}}>Right side content</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div style={formContainerStyle}> | |
<h3>Dogecoin</h3> | |
<div style={{ | |
height: "6px", | |
background: "linear-gradient(to right, silver, rgba(255, 255, 255, 0.3), silver)", | |
filter: "saturate(13%)", | |
marginBottom: "13px", | |
}}></div> | |
<div style={formStyle}> | |
<div style={leftSideStyle}> | |
<label style={{color: "#fff"}}>HD Path</label> | |
<input style={fieldStyle} type="text" value="m/44'/0'/0'/0"/> | |
<label style={{color: "#fff"}}>NEAR Account</label> | |
<input style={fieldStyle} type="text" value={context.accountId} readOnly/> | |
</div> | |
<div style={rightSideStyle}> | |
<div style={rightBoxStyle}> | |
<h4 style={{margin: 0, padding: 0, color: "#fff"}}>Balance</h4> | |
<button style={buttonStyle} onClick={() => refreshBalance('doge')}>Refresh</button> | |
</div> | |
<div style={rightBoxStyle}> | |
<p style={{margin: 0, padding: 0, color: "#fff"}}>Right side content</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div style={formContainerStyle}> | |
<h3>Celestia</h3> | |
<div style={{ | |
height: "6px", | |
background: "linear-gradient(to right, #6633ff, rgba(255, 255, 255, 0.3), #6633ff)", | |
filter: "saturate(13%)", | |
marginBottom: "13px", | |
}}></div> | |
<div style={formStyle}> | |
<div style={leftSideStyle}> | |
<label style={{color: "#fff"}}>HD Path</label> | |
<input style={fieldStyle} type="text" value="m/44'/0'/0'/0"/> | |
<label style={{color: "#fff"}}>NEAR Account</label> | |
<input style={fieldStyle} type="text" value={context.accountId} readOnly/> | |
</div> | |
<div style={rightSideStyle}> | |
<div style={rightBoxStyle}> | |
<h4 style={{margin: 0, padding: 0, color: "#fff"}}>Balance</h4> | |
<button style={buttonStyle} onClick={() => refreshBalance('tia')}>Refresh</button> | |
</div> | |
<div style={rightBoxStyle}> | |
<p style={{margin: 0, padding: 0, color: "#fff"}}>Right side content</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</> | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment