Skip to content

Commit

Permalink
[sign tx] visuals
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeesun Kim authored and Jeesun Kim committed Apr 29, 2024
1 parent 342211d commit f630b99
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 55 deletions.
199 changes: 151 additions & 48 deletions src/app/(sidebar)/transaction/sign/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useStore } from "@/store/useStore";

import { XdrPicker } from "@/components/FormElements/XdrPicker";
import { TextPicker } from "@/components/FormElements/TextPicker";
import { WithInfoText } from "@/components/WithInfoText";
import { validate } from "@/validate";

import { FEE_BUMP_TX_FIELDS, TX_FIELDS } from "@/constants/signTransactionPage";
Expand All @@ -27,7 +28,9 @@ export default function SignTransaction() {
const [tx, setTx] = useState<FeeBumpTransaction | Transaction | undefined>(
undefined,
);
const [isTxImported, setIsTxImported] = useState<boolean>(false);

const [signer, setSigner] = useState<string>("");
const [signerErrorMsg, setSignerErrorMsg] = useState<string>("");

const onChange = (value: string) => {
setTxErrMsg("");
Expand All @@ -50,17 +53,21 @@ export default function SignTransaction() {
const onImport = () => {
try {
const transaction = TransactionBuilder.fromXDR(txEnv, network.passphrase);
setIsTxImported(true);

setTx(transaction);
} catch (e) {
setIsTxImported(false);
setTxErrMsg("Unable to import a transaction envelope");
}
};

const rendeImportView = () => {
const renderImportView = () => {
return (
<>
<div className="SignTx__Overview">
<div className="PageHeader">
<Text size="md" as="h1" weight="medium">
Sign Transaction
</Text>
</div>
<Card>
<div className="SignTx__xdr">
<XdrPicker
Expand Down Expand Up @@ -113,7 +120,7 @@ export default function SignTransaction() {
one signature if there are multiple source accounts or signing keys.
</Text>
</Alert>
</>
</div>
);
};

Expand Down Expand Up @@ -147,55 +154,151 @@ export default function SignTransaction() {

return (
<>
<Card>
<div className="SignTx__FieldViewer">
{mergedFields.map((field) => {
const className =
field.value.toString().length >= MIN_LENGTH_FOR_FULL_WIDTH_FIELD
? "full-width"
: "half-width";

if (field.label.includes("XDR")) {
return (
<div className={className} key={field.label}>
<XdrPicker
readOnly
id={field.label}
label={field.label}
value={field.value.toString()}
/>
</div>
);
} else {
return (
<div className={className} key={field.label}>
<TextPicker
readOnly
id={field.label}
label={field.label}
value={field.value.toString()}
copyButton={{
position: "right",
}}
/>
</div>
);
}
})}
<div className="SignTx__Overview">
<div className="PageHeader">
<Text size="md" as="h1" weight="medium">
Transaction Overview
</Text>
<Button
size="md"
variant="error"
icon={<Icon.RefreshCw01 />}
iconPosition="right"
>
Clear and import new
</Button>
</div>
</Card>

<Card>
<div className="SignTx__FieldViewer">
{mergedFields.map((field) => {
const className =
field.value.toString().length >=
MIN_LENGTH_FOR_FULL_WIDTH_FIELD
? "full-width"
: "half-width";

if (field.label.includes("XDR")) {
return (
<div className={className} key={field.label}>
<XdrPicker
readOnly
id={field.label}
label={field.label}
value={field.value.toString()}
/>
</div>
);
} else {
return (
<div className={className} key={field.label}>
<TextPicker
readOnly
id={field.label}
label={field.label}
value={field.value.toString()}
copyButton={{
position: "right",
}}
/>
</div>
);
}
})}
</div>
</Card>
</div>
<div className="SignTx__Signs">
<div className="PageHeader">
<WithInfoText href="https://developers.stellar.org/docs/learn/encyclopedia/signatures-multisig">
<Text size="md" as="h1" weight="medium">
Signatures
</Text>
</WithInfoText>
</div>

<Card>
<div className="SignTx__Field">
<div className="full-width">
<TextPicker
id="signer"
label="Add Signer"
placeholder="Secret ket (starting with S) or hash preimage (in hex)"
onChange={(e) => {
setSigner(e.target.value);

const error = validate.secretKey(e.target.value);

if (error) {
setSignerErrorMsg(error);
}
}}
error={signerErrorMsg}
value={signer}
/>
</div>

<div className="full-width">
<TextPicker
id="bip-path"
label="BIP Path"
placeholder="BIP path in format: 44'/148'/0'"
onChange={(e) => {
setSigner(e.target.value);

const error = validate.secretKey(e.target.value);

if (error) {
setSignerErrorMsg(error);
}
}}
error={signerErrorMsg}
value={signer}
note="Note: Trezor devices require upper time bounds to be set (non-zero), otherwise the signature will not be verified"
/>
</div>

<div className="SignTx__Buttons">
<div>
<Button
// disabled={isLoading || isFetching}
size="md"
variant="secondary"
// onClick={}
>
Sign with secret key
</Button>

<Button
// disabled={isLoading || isFetching}
size="md"
variant="secondary"
// onClick={}
>
Sign with wallet and submit
</Button>
</div>
<div>
<Button
// disabled={isLoading || isFetching}
size="md"
variant="tertiary"
// onClick={}
>
Add signature
</Button>
</div>
</div>
</div>
</Card>
</div>
</>
);
};

return (
<div className="SignTx">
<div className="PageHeader">
<Text size="md" as="h1" weight="medium">
{isTxImported ? "Transaction Overview" : "Sign Transaction"}
</Text>
</div>
{isTxValid && tx ? renderOverviewView() : rendeImportView()}
{isTxValid && tx ? renderOverviewView() : renderImportView()}
</div>
);
}
42 changes: 35 additions & 7 deletions src/styles/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,13 @@
.SignTx {
display: flex;
flex-direction: column;
gap: pxToRem(12px);
gap: pxToRem(24px);

&__Overview {
display: flex;
flex-direction: column;
gap: pxToRem(12px);
}

&__xdr {
display: flex;
Expand Down Expand Up @@ -443,24 +449,46 @@
}
}

&__FieldViewer {
&__FieldViewer,
&__Field {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: pxToRem(16px);

.half-width {
grid-template-columns: 1fr 1fr;
}

.full-width {
grid-column: 1 / 3;
}
}

&__FieldViewer {
.Textarea textarea,
.Input__container,
.Input__copyButton button,
input {
background: var(--sds-clr-gray-03);
}
}

.half-width {
grid-template-columns: 1fr 1fr;
}
&__Signs {
display: flex;
flex-direction: column;
gap: pxToRem(12px);
}

.full-width {
grid-column: 1 / 3;
&__Buttons {
display: flex;
flex-direction: row;
grid-column: 1 / 3;
gap: pxToRem(12px);
justify-content: space-between;

div {
display: flex;
gap: pxToRem(8px);
}
}
}
4 changes: 4 additions & 0 deletions src/validate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { amount } from "./methods/amount";
import { asset } from "./methods/asset";
import { assetCode } from "./methods/assetCode";
import { assetMulti } from "./methods/assetMulti";
import { bipPath } from "./methods/bipPath";
import { memo } from "./methods/memo";
import { positiveInt } from "./methods/positiveInt";
import { publicKey } from "./methods/publicKey";
import { secretKey } from "./methods/secretKey";
import { timeBounds } from "./methods/timeBounds";
import { transactionHash } from "./methods/transactionHash";
import { xdr } from "./methods/xdr";
Expand All @@ -14,9 +16,11 @@ export const validate = {
asset,
assetCode,
assetMulti,
bipPath,
memo,
positiveInt,
publicKey,
secretKey,
timeBounds,
transactionHash,
xdr,
Expand Down
10 changes: 10 additions & 0 deletions src/validate/methods/bipPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const bipPath = (value: string) => {
const regexp = /44'\/148'\/(\d+)'/;
const match = regexp.exec(value);

if (!(match && match[1].length > 0)) {
return "Invalid BIP path. Please provide it in format 44'/148'/x'. We call 44'/148'/0' the primary account";
}

return false;
};
15 changes: 15 additions & 0 deletions src/validate/methods/secretKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { StrKey } from "@stellar/stellar-sdk";

export const secretKey = (value: string) => {
if (value.startsWith("S")) {
if (!StrKey.isValidEd25519SecretSeed(value)) {
return "Invalid secret key.";
}
} else {
if (!value.match(/^[0-9a-f]{2,128}$/gi) || value.length % 2 == 1) {
return `Invalid hex value. Please provide up to 64 bytes in hexadecimal format.`;
}
}

return false;
};

0 comments on commit f630b99

Please sign in to comment.